
Skill
setup-solana-browser-app
integrate MetaMask Solana in browser apps
Description
Set up a vanilla browser (non-React) app with @metamask/connect-solana using wallet-standard features directly. Use when integrating MetaMask Solana without a framework or wallet adapter library.
SKILL.md
Setup Solana Browser App with MetaMask
When to use
Use this skill when:
- Integrating MetaMask with Solana in a vanilla JavaScript or non-React browser app
- Using wallet-standard features directly without
@solana/wallet-adapter-react - Building connect, sign, and send flows with the
SolanaClientAPI - Accessing wallet-standard features like
solana:signTransactionorsolana:signMessagedirectly
Workflow
Step 1: Install dependencies
npm install @metamask/connect-solana @metamask/connect-multichain @solana/web3.js
@metamask/connect-multichain is a regular dependency of @metamask/connect-solana and is installed transitively. (Only the 2.0.0 release briefly made it a peer dependency; 2.1.0 reverted that.) Installing it explicitly is harmless but not required. The SDK warns at runtime if duplicate or mismatched copies are resolved.
Step 2: Create the Solana client
import { createSolanaClient } from '@metamask/connect-solana';
const solanaClient = await createSolanaClient({
dapp: {
name: 'My Solana DApp',
url: window.location.href,
},
api: {
supportedNetworks: {
mainnet: 'https://api.mainnet-beta.solana.com',
},
},
});
createSolanaClient returns Promise<SolanaClient>:
| Property | Type | Description |
|---|---|---|
core | MultichainCore | The underlying multichain client instance |
getWallet() | () => Wallet | Returns the wallet-standard Wallet (from @wallet-standard/base) |
registerWallet() | () => Promise<void> | Manually register the wallet (auto-called unless skipAutoRegister: true) |
disconnect() | () => Promise<void> | Disconnect and revoke Solana scopes |
Step 3: Get the wallet and connect
const wallet = solanaClient.getWallet();
// The wallet exposes wallet-standard features
console.log('Wallet name:', wallet.name); // "MetaMask"
console.log('Available features:', Object.keys(wallet.features));
Connect using the standard:connect feature:
const connectFeature = wallet.features['standard:connect'];
const { accounts } = await connectFeature.connect();
if (accounts.length > 0) {
const account = accounts[0];
console.log('Address:', account.address);
console.log('Public key:', account.publicKey); // Uint8Array
console.log('Chains:', account.chains); // e.g. ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']
}
Step 4: Access wallet-standard features
The wallet exposes these wallet-standard features:
| Feature Key | Description |
|---|---|
standard:connect | Connect and request accounts |
standard:disconnect | Disconnect the wallet |
standard:events | Subscribe to account/chain change events |
solana:signIn | Sign-In-With-Solana (SIWS) authentication |
solana:signTransaction | Sign a transaction without sending |
solana:signAndSendTransaction | Sign and broadcast a transaction |
solana:signMessage | Sign an arbitrary message |
There is no solana:signAndSendAllTransactions feature — to batch, pass multiple inputs to signAndSendTransaction(...inputs) (it is variadic and returns one result per input).
Step 5: Sign a message
const signMessageFeature = wallet.features['solana:signMessage'];
const message = new TextEncoder().encode('Hello from MetaMask on Solana!');
const [{ signature }] = await signMessageFeature.signMessage({
account: accounts[0],
message,
});
console.log('Signature:', Buffer.from(signature).toString('hex'));
Step 6: Sign and send a transaction
import {
Connection,
Transaction,
SystemProgram,
PublicKey,
LAMPORTS_PER_SOL,
} from '@solana/web3.js';
const connection = new Connection('https://api.mainnet-beta.solana.com');
const { blockhash } = await connection.getLatestBlockhash();
const senderPubkey = new PublicKey(accounts[0].address);
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: senderPubkey,
toPubkey: new PublicKey('11111111111111111111111111111112'),
lamports: 0.001 * LAMPORTS_PER_SOL,
}),
);
transaction.recentBlockhash = blockhash;
transaction.feePayer = senderPubkey;
const serializedTransaction = transaction.serialize({
requireAllSignatures: false,
});
const signAndSendFeature = wallet.features['solana:signAndSendTransaction'];
const [{ signature: txSignature }] = await signAndSendFeature.signAndSendTransaction({
account: accounts[0],
transaction: serializedTransaction,
chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
});
// txSignature is a Uint8Array — encode with bs58 ('base58' is NOT a Buffer encoding).
// Requires `npm install bs58` and `import bs58 from 'bs58'` at the top of the file.
const signatureBase58 = bs58.encode(txSignature);
console.log('Transaction signature:', signatureBase58);
// confirmTransaction expects the base58 signature string
const confirmation = await connection.confirmTransaction(signatureBase58, 'confirmed');
console.log('Confirmed:', confirmation);
Step 7: Listen for account and chain changes
const eventsFeature = wallet.features['standard:events'];
eventsFeature.on('change', ({ accounts: newAccounts }) => {
if (newAccounts) {
console.log('Accounts changed:', newAccounts.map((a) => a.address));
}
});
Step 8: Disconnect
await solanaClient.disconnect();
Step 9: Full working example
import { createSolanaClient } from '@metamask/connect-solana';
import {
Connection,
Transaction,
SystemProgram,
PublicKey,
LAMPORTS_PER_SOL,
} from '@solana/web3.js';
async function main() {
const solanaClient = await createSolanaClient({
dapp: {
name: 'My Solana DApp',
url: window.location.href,
},
api: {
supportedNetworks: {
mainnet: 'https://api.mainnet-beta.solana.com',
},
},
});
const wallet = solanaClient.getWallet();
// Connect
const connectFeature = wallet.features['standard:connect'];
const { accounts } = await connectFeature.connect();
const account = accounts[0];
console.log('Connected:', account.address);
// Sign message
const signMessageFeature = wallet.features['solana:signMessage'];
const [{ signature }] = await signMessageFeature.signMessage({
account,
message: new TextEncoder().encode('Hello Solana!'),
});
console.log('Message signed');
// Send transaction
const connection = new Connection('https://api.mainnet-beta.solana.com');
const { blockhash } = await connection.getLatestBlockhash();
const senderPubkey = new PublicKey(account.address);
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: senderPubkey,
toPubkey: new PublicKey('11111111111111111111111111111112'),
lamports: 0.001 * LAMPORTS_PER_SOL,
}),
);
tx.recentBlockhash = blockhash;
tx.feePayer = senderPubkey;
const signAndSendFeature = wallet.features['solana:signAndSendTransaction'];
const [{ signature: txSig }] = await signAndSendFeature.signAndSendTransaction({
account,
transaction: tx.serialize({ requireAllSignatures: false }),
chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
});
console.log('Transaction sent');
// Disconnect
await solanaClient.disconnect();
}
main().catch(console.error);
Important Notes
createSolanaClientis async — alwaysawaitit before accessing the wallet. The factory returns aPromise<SolanaClient>.getInfuraRpcUrlshelper — usegetInfuraRpcUrls({ infuraApiKey: 'YOUR_KEY', networks: ['mainnet', 'devnet'] })from@metamask/connect-solanato auto-generatesupportedNetworksfrom Infura. ReturnsSolanaSupportedNetworks.- Wallet name is exactly
"MetaMask"— case-sensitive. Use this to identify the wallet if you enumerate registered wallets. - Feature keys are string constants — always access features via bracket notation (e.g.,
wallet.features['solana:signTransaction']), not dot notation. - Solana networks — mainnet, devnet, and testnet scopes are all modeled by the SDK and wallet-standard layer. Non-mainnet availability depends on the connected MetaMask build/version, so handle connection errors rather than assuming a cluster is present; for reads, point a
@solana/web3.jsConnectionat the matching cluster. skipAutoRegisteroption — passskipAutoRegister: truetocreateSolanaClientto prevent automatic wallet-standard registration. Useful when you want to control when the wallet becomes discoverable.analytics.integrationTypeoption — pass ananalytics: { integrationType: 'your-integration' }string tocreateSolanaClient(added in@metamask/connect-solana0.8.0) to tag analytics events with your integration identifier.- Injected Solana provider wins — since
@metamask/connect-solana1.0.0, if an injected Solana provider is already present (e.g. the MetaMask browser extension),createSolanaClientwill not announce its own wallet-standard provider. Don't expect two"MetaMask"entries in the registered wallets list. - Eager provider initialization + stable
getWallet()— since@metamask/connect-solana1.1.0,createSolanaClient()eagerly initializes the Solana wallet provider during creation; if the underlying multichain session already contains Solana scopes, the provider's accounts are populated by the time the client resolves (no need to wait forwallet_sessionChangedon cold start).getWallet()also returns the same wallet instance on every call now — safe to cache in a module-level constant, no need to re-await or recreate on subsequent access. disconnect()only revokes Solana scopes — EVM sessions managed by other clients remain active.- Chrome on Android has a known bug where the page may unload during the connection flow. Add a
beforeunloadlistener as a workaround:window.addEventListener('beforeunload', (e) => { e.preventDefault(); e.returnValue = ''; });
More skills from the metamask-connect-cursor-plugin repository
View all 18 skillsmigrate-from-sdk
migrate MetaMask SDK to connect-evm and connect-solana
Jul 13EthereumMigrationSDKWeb3migrate-wagmi-metamask-connector
migrate wagmi apps to MetaMask connector
Jul 13EthereumMigrationwagmiWeb3send-evm-transaction
send EVM transactions with MetaMask
Jul 13API DevelopmentEthereumWeb3send-solana-transaction
send Solana transactions via MetaMask
Jul 13PaymentsSolanaWeb3setup-evm-browser-app
scaffold EVM browser applications with MetaMask
Jul 13EthereumFrontendJavaScriptTypeScript +1setup-evm-react-app
scaffold React apps with MetaMask integration
Jul 13API DevelopmentEthereumFrontendReact +1
More from MetaMask
View publishersmart-accounts-kit
build dApps with MetaMask Smart Accounts
smart-accounts-kit
Jul 13API DevelopmentAuthEthereumPermissions +1x402-payments
build x402 payment flows
smart-accounts-kit
Jul 13API DevelopmentPaymentsWeb3x402discovery
discover and use services
ocap-kernel
Jul 13API DevelopmentSearchmetamask
interact with MetaMask wallet capabilities
ocap-kernel
Jul 13API DevelopmentEthereumWeb3wallet
manage wallet balances and transactions
ocap-kernel
Jul 13EthereumPaymentsWeb3metamask-connect
integrate MetaMask into dApps
connect-monorepo
Jul 13API DevelopmentEthereumSolanawagmi +1