
Description
Sign an arbitrary message on Solana using MetaMask Connect. Covers both the React wallet-adapter approach (useWallet) and the vanilla browser approach (wallet-standard features).
SKILL.md
Sign Solana Message with MetaMask
When to use
Use this skill when:
- Signing an arbitrary message on Solana with MetaMask Connect
- Implementing sign-in-with-Solana or message verification flows
- Using
useWallet().signMessagein a React app - Using the
solana:signMessagewallet-standard feature in a vanilla browser app
Workflow
Step 1: Encode the message
Solana message signing requires a Uint8Array. Use TextEncoder to convert a string:
const message = new TextEncoder().encode('Sign this message to verify your identity');
Step 2a: Sign with React wallet-adapter (useWallet)
Prerequisites: createSolanaClient has been awaited before rendering, WalletProvider is configured with wallets={[]}, and the user is connected. See the setup-solana-react-app skill.
import { useWallet } from '@solana/wallet-adapter-react';
function SignMessageButton() {
const { signMessage, publicKey, connected } = useWallet();
const handleSign = async () => {
if (!signMessage || !publicKey) {
console.error('Wallet does not support signMessage or is not connected');
return;
}
try {
const message = new TextEncoder().encode('Hello from MetaMask on Solana!');
const signature = await signMessage(message);
console.log('Signature (bytes):', signature);
console.log('Signature (hex):', Buffer.from(signature).toString('hex'));
console.log('Signer:', publicKey.toBase58());
} catch (err: any) {
if (err.code === 4001) {
console.log('User rejected the signature request');
return;
}
console.error('signMessage failed:', err);
}
};
return (
<button onClick={handleSign} disabled={!connected || !signMessage}>
Sign Message
</button>
);
}
Step 2b: Sign with vanilla browser (wallet-standard feature)
Prerequisites: createSolanaClient has been called and the wallet is connected via standard:connect. See the setup-solana-browser-app skill.
import { createSolanaClient } from '@metamask/connect-solana';
const solanaClient = await createSolanaClient({
dapp: { name: 'My DApp', url: window.location.href },
});
const wallet = solanaClient.getWallet();
// Connect first
const connectFeature = wallet.features['standard:connect'];
const { accounts } = await connectFeature.connect();
const account = accounts[0];
// Sign the message
const signMessageFeature = wallet.features['solana:signMessage'];
try {
const message = new TextEncoder().encode('Hello from MetaMask on Solana!');
const [{ signature }] = await signMessageFeature.signMessage({
account,
message,
});
console.log('Signature (hex):', Buffer.from(signature).toString('hex'));
console.log('Signer:', account.address);
} catch (err: any) {
if (err.code === 4001) {
console.log('User rejected the signature request');
} else {
console.error('signMessage failed:', err);
}
}
Step 3: Verify the signature (optional)
Use tweetnacl or @noble/ed25519 to verify the signature off-chain:
import nacl from 'tweetnacl';
const message = new TextEncoder().encode('Hello from MetaMask on Solana!');
const isValid = nacl.sign.detached.verify(
message,
signature, // Uint8Array from signMessage
publicKey.toBytes(), // Uint8Array of the signer's public key
);
console.log('Signature valid:', isValid);
Step 4: Error handling
Handle these common error scenarios:
| Error | Cause | Action |
|---|---|---|
Code 4001 | User rejected the request in MetaMask | Show retry UI, do not treat as app error |
signMessage is undefined | Wallet does not support message signing | Check signMessage exists before calling |
publicKey is null | Wallet not connected | Prompt user to connect first |
| Network error | MetaMask Mobile connection interrupted | Retry or reconnect |
try {
const signature = await signMessage(message);
} catch (err: any) {
switch (err.code) {
case 4001:
// User rejected — show retry button
break;
case -32002:
// Request already pending — wait for user to act in MetaMask
break;
default:
console.error('Unexpected error:', err);
}
}
Important Notes
- Messages must be
Uint8Array— usenew TextEncoder().encode(string)to convert. Do not pass raw strings tosignMessage. signMessagemay beundefined— always check thatsignMessageexists on the wallet adapter before calling it. Not all wallets support arbitrary message signing.- The signature is Ed25519 — Solana uses Ed25519 signatures. The returned
Uint8Arrayis 64 bytes. - User rejection is code
4001— handle it gracefully with a retry option. Do not log it as an error. - Wallet name is
"MetaMask"— case-sensitive, used to identify the MetaMask wallet in the adapter list. - 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.
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