
Skill
sign-multichain-solana-transaction
sign and send Solana transactions
Description
Sign and send Solana transactions using the multichain client's invokeMethod. Covers signTransaction, signAndSendTransaction, signMessage, building transactions with @solana/web3.js, base64 encoding, mainnet/devnet scopes, and selective disconnect.
SKILL.md
Sign Solana Transactions via Multichain Client
When to use
Use this skill when:
- Signing or sending Solana transactions through
invokeMethodon a multichain client - Building Solana transactions with
@solana/web3.jsand encoding them for the multichain API - Signing Solana messages through the multichain client
- Selecting the correct Solana CAIP-2 scope (mainnet, devnet)
- Disconnecting only Solana scopes while keeping EVM sessions active
Workflow
Step 1: Connect with Solana scopes
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain';
const client = await createMultichainClient({
dapp: { name: 'My DApp', url: window.location.href },
api: {
supportedNetworks: {
...getInfuraRpcUrls({ infuraApiKey: 'YOUR_INFURA_KEY', caipChainIds: ['eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'] }),
},
},
});
await client.connect(
[
'eip155:1',
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // mainnet
],
[],
); // resolves with no value — read session via client.provider.getSession()
Solana CAIP-2 scope identifiers:
| Network | CAIP-2 Scope |
|---|---|
| Mainnet | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
| Devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 |
| Testnet | solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z |
All three Solana scopes are modeled by the SDK; non-mainnet availability depends on the connected MetaMask build/version, so handle connection errors rather than assuming a cluster is present.
Step 2: Understand Solana RPC routing
All Solana methods route through the wallet. Unlike EVM where read calls go to an RPC node, every Solana invokeMethod call is handled by MetaMask. There is no RPC node fallback for Solana.
Step 3: Sign a message (signMessage)
Method names have no solana_ prefix. The message must be base64 encoded, and the signing account is passed as account: { address }.
const message = btoa('Hello from Solana via MetaMask!');
const result = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
request: {
method: 'signMessage',
params: {
account: { address: 'YourSolanaAddressBase58' },
message,
},
},
});
// result: { signature: <base58 string>, signedMessage: <base64>, signatureType: 'ed25519' }
console.log('Signature:', result.signature);
Step 4: Build a Solana transaction with @solana/web3.js
Build the transaction using @solana/web3.js, serialize it, then base64-encode for invokeMethod.
import {
Connection,
PublicKey,
SystemProgram,
Transaction,
clusterApiUrl,
} from '@solana/web3.js';
const connection = new Connection(clusterApiUrl('mainnet-beta'));
const fromPubkey = new PublicKey('YourSolanaPublicKey');
const toPubkey = new PublicKey('RecipientSolanaPublicKey');
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey,
toPubkey,
lamports: 1_000_000, // 0.001 SOL
}),
);
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.feePayer = fromPubkey;
const serialized = transaction.serialize({
requireAllSignatures: false,
verifySignatures: false,
});
const base64Transaction = Buffer.from(serialized).toString('base64');
Step 5: Sign a transaction (signTransaction)
Returns the signed transaction without broadcasting it.
const SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
const signResult = await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
method: 'signTransaction',
params: {
account: { address: 'YourSolanaAddressBase58' },
transaction: base64Transaction,
scope: SOLANA_MAINNET,
},
},
});
// The result field is `signedTransaction` (base64), not `transaction`
console.log('Signed transaction:', signResult.signedTransaction);
You can then broadcast the signed transaction yourself:
const signedBuffer = Buffer.from(signResult.signedTransaction, 'base64');
const txId = await connection.sendRawTransaction(signedBuffer);
console.log('Transaction ID:', txId);
Step 6: Sign and send a transaction (signAndSendTransaction)
Signs and broadcasts the transaction in one step.
const sendResult = await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
method: 'signAndSendTransaction',
params: {
account: { address: 'YourSolanaAddressBase58' },
transaction: base64Transaction,
scope: SOLANA_MAINNET,
},
},
});
// sendResult: { signature: <base58 transaction signature> }
console.log('Transaction signature:', sendResult.signature);
Step 7: Devnet transactions
Connect with the devnet scope and point @solana/web3.js at the devnet cluster:
const SOLANA_DEVNET = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';
await client.connect([SOLANA_DEVNET], []);
const connection = new Connection(clusterApiUrl('devnet'));
// Build transaction with devnet connection...
const base64Tx = buildAndSerializeTransaction(connection);
const result = await client.invokeMethod({
scope: SOLANA_DEVNET,
request: {
method: 'signAndSendTransaction',
params: {
account: { address: 'YourSolanaAddressBase58' },
transaction: base64Tx,
scope: SOLANA_DEVNET,
},
},
});
Step 8: Selective disconnect
Disconnect only Solana scopes while keeping EVM sessions active:
// Disconnect only Solana mainnet — EVM scopes remain connected
await client.disconnect(['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']);
// Disconnect all scopes (full session teardown)
await client.disconnect();
Step 9: Error handling
invokeMethod errors are wrapped in RPCInvokeMethodErr — its own code is always 53, and the wallet's original EIP-1193 / JSON-RPC code (e.g. 4001 user rejection) is on rpcCode:
import { RPCInvokeMethodErr } from '@metamask/connect-multichain';
try {
await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
method: 'signAndSendTransaction',
params: {
account: { address: 'YourSolanaAddressBase58' },
transaction: base64Tx,
scope: SOLANA_MAINNET,
},
},
});
} catch (err) {
if (err instanceof RPCInvokeMethodErr && err.rpcCode === 4001) {
// User rejected the transaction in MetaMask — not an app error
} else {
console.error('Solana transaction error:', err);
}
}
Important Notes
- All Solana methods go to the wallet. There is no RPC node routing for Solana — every
invokeMethodcall with a Solana scope prompts MetaMask. - Base64 encoding required. Transactions and messages must be base64-encoded strings, not raw buffers or hex.
- Use
@solana/web3.jsto build transactions. ConstructTransactionobjects, setrecentBlockhashandfeePayer, serialize withrequireAllSignatures: false, then base64-encode. - CAIP-2 genesis hash IDs. Mainnet is
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp. Devnet issolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1. These are not cluster URLs — they are genesis hash identifiers. - Solana networks. Mainnet, devnet, and testnet scopes are all modeled by the SDK; non-mainnet availability depends on the connected MetaMask build/version, so handle connection errors rather than assuming a cluster is present.
- Selective disconnect preserves other scopes. Passing specific Solana scopes to
disconnect()only revokes those scopes. EVM scopes remain active. - Connected scopes required.
invokeMethodfails if the Solana scope was not included in the originalconnect()call. - Method names have no
solana_prefix. The MetaMask Multichain API methods aresignMessage,signTransaction, andsignAndSendTransaction, each takingaccount: { address }in params. (solana_*-prefixed names are WalletConnect's schema, not MetaMask's.) signTransactionvssignAndSendTransaction: UsesignTransactionwhen you need to inspect or modify the signed output before broadcasting (result field:signedTransaction, base64). UsesignAndSendTransactionfor the common case where you want a single atomic operation (result field:signature, base58).
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