Keypairs & Signers
Signers abstract who can sign transactions and messages in @solana/kit.
Recipe
Quick-reference recipe card - copy-paste ready.
import { generateKeyPairSigner, createKeyPairSignerFromBytes } from "@solana/kit";
const dev = await generateKeyPairSigner();
// or load solana-keygen JSON bytesWhen to reach for this:
- Server scripts that pay fees with a keypair file.
- Tests that need deterministic signers.
- Bridging wallet adapters into kit messages.
- Message signing for auth (off-chain).
Working Example
import {
generateKeyPairSigner,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signTransactionMessageWithSigners,
createSolanaRpc,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const payer = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const { value: bh } = await rpc.getLatestBlockhash().send();
let msg = createTransactionMessage({ version: 0 });
msg = setTransactionMessageFeePayerSigner(payer, msg);
msg = setTransactionMessageLifetimeUsingBlockhash(bh, msg);
msg = appendTransactionMessageInstruction(
getTransferSolInstruction({ source: payer, destination: recipient.address, amount: 1_000n }),
msg
);
const signed = await signTransactionMessageWithSigners(msg);What this demonstrates:
- Fee payer is a
Signer, not a raw public key string. signTransactionMessageWithSignersresolves signers embedded in the message.- Wallets implement the same signer interface at runtime.
Deep Dive
How It Works
- Ed25519 keypairs map to
Address(base58 public key). KeyPairSignerholds secret material for Node scripts.- Browser wallets expose
TransactionSendingSignerwithout revealing secrets. CryptoKeyimport paths exist for Web Crypto integrations.
Signer Types
| Type | Secret exposed | Typical source |
|---|---|---|
| Key pair | Yes (server) | JSON keypair file |
| Wallet | No | Phantom, Solflare |
| No-op | N/A | Placeholder in partial builds |
TypeScript Notes
// Never log or serialize secret key bytes
const { address } = await generateKeyPairSigner();Gotchas
- Fee payer as address string only - message won't sign. Fix:
setTransactionMessageFeePayerSigner. - Committing keypair JSON - full wallet drain risk. Fix: gitignore and secrets manager.
- Using same key on mainnet and devnet - accidental mainnet spends. Fix: per-cluster keypairs.
- Assuming wallet = fee payer - user may reject fee payer mismatch. Fix: align connected pubkey.
- Float SOL amounts - precision loss. Fix: bigint lamports.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Wallet Standard signers | Browser dApps | Headless CI scripts |
| Hardware wallet | High-value keys | Rapid local iteration |
| KMS/HSM service | Production servers | Local dev only |
FAQs
Does this work on devnet?
Yes - point RPC and wallet at the same cluster.
Can I mix kit and web3.js?
Avoid in one module - migrate wholesale per route.
What about wallet adapters?
Wallets provide signers; kit builds messages.
How do I pin @solana/kit?
Lock 7.0.0 in package.json and CI.
Where are priority fees?
Compute budget instructions before send.
How to test?
Surfpool 0.12.0 or local validator with devnet clones.
Lamports or SOL?
Always bigint lamports in kit code.
Which commitment?
confirmed for UI; finalized for treasury.
Next.js server components?
Reads on server; signing in client components.
Debug failed txs?
Simulate, read logs, verify account owners.
Related
- Building Transaction Messages - compose messages
- Signing & Sending - broadcast
- Wallet Basics - browser wallets
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.