solana-keychain: one signing interface, 13 backends
The Solana Foundation's solana-keychain provides a unified SolanaSigner interface for TypeScript and Rust across 13 signing backends — from local keypairs to Fireblocks, AWS KMS, Privy, Turnkey, CDP, and more. Security audited by Accretion, 2026.
Every production Solana application eventually needs to answer the same question: where do private keys live in production? Local keypairs for development, then what — Fireblocks for the treasury, Privy for embedded wallets, AWS KMS for the relayer? Each provider has its own SDK, its own error types, its own async surface. You end up with signing logic scattered across your codebase tied to a specific vendor.
solana-keychain is the Solana Foundation's answer: a single SolanaSigner interface in TypeScript and Rust, with feature-gated backends for 13 providers. Write your signing code once; swap the backend without touching the call sites. Audited by Accretion in 2026.
The problem with vendor-specific signing
Signing code tends to spread. A relayer signs with a local keypair, a treasury operation goes through Fireblocks, an agent wallet is managed by Privy. Without an abstraction layer:
- Switching providers means finding every call site that touches signing and rewriting it against a new SDK.
- Testing requires mocking the vendor SDK, not just providing a deterministic key.
- Multi-environment setups (local dev with a memory key, staging with Vault, production with Fireblocks) need separate code paths or environment-switched factories.
solana-keychain solves this by defining a trait (Rust) and interface (TypeScript) that all backends implement identically.
The interface
In TypeScript, all keychain signers implement SolanaSigner from @solana/keychain-core, which is compatible with @solana/signers and @solana/kit:
interface SolanaSigner<TAddress extends string = string> {
readonly address: Address<TAddress>;
signMessages(messages: SignableMessage[]): Promise<SignatureDictionary[]>;
signTransactions(transactions: Transaction[]): Promise<SignatureDictionary[]>;
isAvailable(): Promise<boolean>;
}In Rust, all backends implement the SolanaSigner trait:
#[async_trait]
pub trait SolanaSigner: Send + Sync {
fn pubkey(&self) -> Pubkey;
async fn sign_transaction(&self, tx: &mut Transaction) -> Result<Signature, SignerError>;
async fn sign_message(&self, message: &[u8]) -> Result<Signature, SignerError>;
async fn is_available(&self) -> bool;
}Your application code depends on the trait, not the provider. The concrete backend is injected at the edge — in a config file or environment variable check at startup.
Backends
Both the TypeScript and Rust implementations cover the same 13 backends:
- Memory — local Ed25519 keypair. Default for development and testing. Zero external dependencies.
- HashiCorp Vault — enterprise key management with Vault Transit secrets engine.
- Privy — embedded wallet infrastructure; the right choice for consumer apps where users shouldn't manage keys.
- Turnkey — non-custodial key management via Turnkey's policy engine and secure enclaves.
- AWS KMS — Ed25519 signing via AWS Key Management Service. Works with IAM roles in production; environment variables locally. Key spec:
ECC_NIST_EDWARDS25519. - Fireblocks — institutional custody platform; the standard choice for treasury operations and protocol-owned wallets with compliance requirements.
- GCP KMS — Google Cloud Key Management Service with Ed25519 signing.
- Dfns — wallet infrastructure with Ed25519 signing via Dfns's MPC network.
- Para — MPC wallets via Para's API.
- CDP — Coinbase Developer Platform managed wallet infrastructure. Note: CDP's
sign_messageAPI accepts UTF-8 only — non-UTF-8 payloads return an error. - Crossmint — managed wallets (smart and MPC types). Note:
sign_messageis intentionally unsupported on the Crossmint signer. - Openfort — backend wallets with TEE-stored keys.
- Utila — MPC wallets with automated co-signer flow. Transaction signing requests are created with
publish=false; callers broadcast separately.
TypeScript: installation and usage
Install the umbrella package (includes all backends) or individual packages for a smaller footprint:
# Umbrella — all backends
pnpm add @solana/keychain
# Or individual packages
pnpm add @solana/keychain-privy
pnpm add @solana/keychain-aws-kms
pnpm add @solana/keychain-fireblocks
# etc.import { createKeychainSigner } from '@solana/keychain';
import { signTransactionWithSigners } from '@solana/signers';
// Swap the backend by changing this config — call sites stay the same
const signer = await createKeychainSigner({
backend: 'privy',
appId: process.env.PRIVY_APP_ID,
appSecret: process.env.PRIVY_APP_SECRET,
walletId: process.env.PRIVY_WALLET_ID,
});
const signedTx = await signTransactionWithSigners([signer], compiledTransaction);For Solana Kit users, the @solana/keychain-kit-plugin package installs a keychain signer directly as the payer or identity on a Kit client:
import { createClient } from '@solana/kit';
import { keychainSigner } from '@solana/keychain-kit-plugin';
const client = await createClient().use(
keychainSigner({ backend: 'fireblocks', /* Fireblocks config */ }),
);
// client.payer is now your Fireblocks signer
// In dev: swap to { backend: 'memory', privateKey: '...' }Rust: installation and usage
Feature flags keep binary size minimal — only compile the backends you need:
[dependencies]
# Memory only (default — for local dev / testing)
solana-keychain = "0.5"
# With AWS KMS
solana-keychain = { version = "0.5", features = ["aws_kms"] }
# With Fireblocks
solana-keychain = { version = "0.5", features = ["fireblocks"] }
# All backends
solana-keychain = { version = "0.5", features = ["all"] }use solana_keychain::{MemorySigner, SolanaSigner};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// In dev: MemorySigner from env
// In prod: swap to AwsKmsSigner::new(...) or FireblocksSigner::new(...)
// — the rest of the function doesn't change
let signer = MemorySigner::from_private_key_string(
&std::env::var("SOLANA_PRIVATE_KEY")?
)?;
println!("Pubkey: {}", signer.pubkey());
let message = b"Hello Solana!";
let sig = signer.sign_message(message).await?;
println!("Sig: {}", sig);
Ok(())
}AWS KMS in production, memory in CI:
use solana_keychain::{AwsKmsSigner, SolanaSigner};
let signer = AwsKmsSigner::new(
std::env::var("KMS_KEY_ARN")?,
std::env::var("SOLANA_PUBKEY")?,
None, // region from AWS_REGION env or SDK default
).await?;AWS credentials load from the default provider chain — IAM role on EC2/ECS/Lambda, environment variables locally, OIDC in CI. No explicit credential management needed in production.
Security audit
solana-keychain was audited by Accretion in 2026. The audit report is in the repository at audits/2026-accretion-solana-foundation-solana-keychain-audit-A26SFR2.pdf. Current audit status and the unaudited delta since the last audited commit are tracked in audits/AUDIT_STATUS.md.
For a library that abstracts over institutional signing infrastructure, the audit matters: bugs here can affect all backends, not just one.
When to use each backend
- Local development / CI — Memory. A keypair from an env variable, zero setup, deterministic tests.
- Consumer embedded wallets — Privy or Para. Users don't manage keys; MPC or server-side custody with recovery flows.
- Protocol treasury / compliance — Fireblocks or Dfns. Policy approval flows, audit logs, multi-approver signing.
- Cloud-native relayers — AWS KMS or GCP KMS. Integrates with existing IAM, no separate key management system.
- Enterprise on-prem — HashiCorp Vault. Works in air-gapped environments with existing Vault deployments.
- Agent wallets — CDP, Openfort, or Turnkey. Programmatic key creation and signing, suitable for agent loops that need to spin up wallets on demand.
Further reading
Keep reading
A payment channel where assets stay on Solana mainnet but transactions are private, instant, and controlled by the operator. Not a separate chain, not a bridge — escrow locks SPL tokens, the channel processes thousands of transactions at ~100ms, and withdrawals burn back to mainnet. The reference architecture for tokenised deposits and institutional capital markets.
Solana handles on-chain state. Turso handles everything else — user profiles, off-chain indexes, cached RPC data, leaderboards. Built on libSQL (a Rust-backed SQLite fork), it deploys to the edge, runs embedded locally, and speaks HTTP from Cloudflare Workers. Here's the architecture and the code.
Most Solana streaming assumes the consumer is somewhere else on the network, so it serializes and ships bytes over gRPC. But a lot of infra — RPC sidecars, indexers, MEV bots — runs on the same host as the validator. Qlaster is a Rust system that fans account/tx updates over shared-memory rings instead of sockets: zero-copy, eventfd-driven, SCM_RIGHTS fd passing. A look at the design.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.