Signing Transactions & Messages
Sign transaction messages and off-chain auth payloads with connected wallets.
Recipe
import { signTransactionMessageWithSigners } from "@solana/kit";
// wallet adapter provides TransactionSendingSigner compatible adapterWhen to reach for this:
- Any user-paid transaction in the browser.
- NFT mints, swaps, and game actions.
- Sign-in with Solana messages.
Working Example
"use client";
import { useWallet } from "@solana/wallet-adapter-react";
import {
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signTransactionMessageWithSigners,
createSolanaRpc,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
export function useSendSol() {
const { publicKey, signTransaction } = useWallet();
const rpc = createSolanaRpc(process.env.NEXT_PUBLIC_RPC_URL!);
return async function send(recipient: string, lamports: bigint) {
if (!publicKey || !signTransaction) throw new Error("Wallet not connected");
const { value: bh } = await rpc.getLatestBlockhash().send();
// Wire wallet signer helper from your adapter bridge
let msg = createTransactionMessage({ version: 0 });
// ... append transfer ix, sign with wallet, send via rpc
};
}What this demonstrates:
- Build message with kit; wallet provides signing capability.
- Fee payer must match connected pubkey.
- Simulate before wallet modal when possible.
Deep Dive
How It Works
- Wallets never expose secret keys - they sign inside the extension.
- Partial signing supported for multisig flows.
signMessagereturns ed25519 signature over arbitrary bytes.- TransactionSendingSigner bridges adapter to kit pipeline.
Gotchas
- Fee payer mismatch - wallet rejects. Fix: use connected address as payer.
- Oversized transactions - wallet UI fails. Fix: ALTs and fewer accounts.
- Blind signing on hardware - security risk. Fix: verified transaction preview.
- Assuming signTransaction returns confirmed tx - only signs; you still send.
- Reusing blockhash after long UX - expiry. Fix: rebuild message.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Server-sponsored tx | Gasless onboarding | Trustless self-custody |
| Mobile Wallet Adapter | React Native | Desktop extension only |
FAQs
Devnet or mainnet?
Match RPC URL, wallet network, and program IDs to one cluster.
kit vs web3.js?
Use @solana/kit 7.0.0 for new frontend code.
Testing?
Surfpool 0.12.0 or devnet with faucet SOL.
Commitment level?
confirmed for UX; finalized for high-value reads.
Next.js App Router?
Providers in client layout; reads can be server-side.
Wallet rejected sign?
Show retry; never assume approval.
Priority fees?
Compute budget instructions on congested mainnet.
Mobile?
Use Mobile Wallet Adapter for React Native apps.
Security?
Never expose server keypairs to the browser.
IDL changes?
Regenerate typed clients in CI.
Related
- Client-Side Signing - Next.js patterns
- Signing & Sending - kit send pipeline
- Keypairs & Signers - signer model
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.