Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt

Use this file to discover all available pages before exploring further.

TLDR:
  • solana-keychain is an audited Solana Foundation library that gives you one signing interface across 12 backends — local keypairs, Turnkey, Privy, AWS and GCP KMS, Fireblocks, HashiCorp Vault, and more.
  • The signer it returns is a native Solana Kit signer, so it drops straight into your normal transaction flow.
  • Moving from a local key to a managed signer is a one-line change — the rest of your build, sign, and send code stays identical.
  • The signed transaction goes out through your Chainstack endpoint like any other.

What solana-keychain is

solana-keychain is a Solana Foundation library that provides a single signing interface backed by many implementations. Instead of learning each provider’s SDK, you pick a backend and supply its credentials; how the key is stored and how signing happens is abstracted away. It ships for both TypeScript and Rust, is tree-shakeable, and has been audited by Accretion. The key design point: the signer you get back implements Solana Kit’s TransactionPartialSigner and MessagePartialSigner interfaces. It is a Kit signer — you pass it anywhere Kit expects one (fee payer, instruction signer) with no adapter. Your transaction-building and submission code does not change when you switch backends.
The packages are ESM-only. Your package.json needs "type": "module" (or a bundler that handles ESM), or you will hit a “no exports” error — the same requirement as Solana Kit itself.

One interface, many backends

There are two ways to construct a signer:
  • createKeychainSigner({ backend, ...config }) — the unified entry point. The backend field selects the implementation and narrows the config to that backend’s fields.
  • create<Backend>Signer(config) — per-backend functions (createMemorySigner, createTurnkeySigner, and so on), if you prefer to import only what you use.
Both return a Promise<SolanaSigner>. The examples below use the unified createKeychainSigner.

Sign and send with a local keypair

Start with the memory backend — a local keypair, the same thing you would use in a script or for development. First, get a Solana RPC endpoint:
  1. Sign up with Chainstack.
  2. Deploy a node.
  3. View node access and credentials.
Then install the packages and send a transfer. The signer plugs directly into the Kit flow.
npm install @solana/keychain @solana/kit @solana-program/system
TypeScript
import { createKeychainSigner } from "@solana/keychain";
import {
  address,
  appendTransactionMessageInstruction,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  getSignatureFromTransaction,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";

const rpc = createSolanaRpc("YOUR_CHAINSTACK_HTTPS_ENDPOINT");
const rpcSubscriptions = createSolanaRpcSubscriptions("YOUR_CHAINSTACK_WSS_ENDPOINT");

// Swap only this block to change backend — everything below stays the same.
// The memory backend accepts one of: keyPair, privateKey, privateKeyString, or
// privateKeyPath (a Solana CLI keypair JSON file; Node only).
const signer = await createKeychainSigner({
  backend: "memory",
  privateKeyPath: "/path/to/keypair.json",
});

const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const message = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(signer, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) =>
    appendTransactionMessageInstruction(
      getTransferSolInstruction({
        source: signer,
        destination: address("RecipientAddressHere"),
        amount: 1_000_000n, // 0.001 SOL
      }),
      tx,
    ),
);

const signedTransaction = await signTransactionMessageWithSigners(message);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
  commitment: "confirmed",
});
console.log("Sent:", getSignatureFromTransaction(signedTransaction));

Swap in a managed signer

To sign with a managed service instead of a local key, change only the createKeychainSigner call. Everything else — building the message, signing, sending through your Chainstack node — is untouched. On-chain, the transaction looks identical regardless of which backend signed it.
// Keep credentials in environment variables, not in source.
const signer = await createKeychainSigner({
  backend: "turnkey",
  organizationId: process.env.TURNKEY_ORG_ID,
  apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
  apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
  privateKeyId: process.env.TURNKEY_PRIVATE_KEY_ID, // the Turnkey key used to sign
  publicKey: process.env.TURNKEY_SOLANA_ADDRESS,    // base58 Solana address
});
For Privy, the signer needs the wallet ID — an opaque identifier from your Privy dashboard — not the wallet’s public key. Passing the address will not work.

Supported backends

Pass the backend value below to createKeychainSigner, or install the matching package and call its create<Backend>Signer function.
backendPackageWhat it is
memory@solana/keychain-memoryLocal keypair — development and scripts
turnkey@solana/keychain-turnkeyTurnkey wallet API
privy@solana/keychain-privyPrivy wallet API
aws-kms@solana/keychain-aws-kmsAWS KMS
gcp-kms@solana/keychain-gcp-kmsGoogle Cloud KMS
fireblocks@solana/keychain-fireblocksFireblocks
vault@solana/keychain-vaultHashiCorp Vault (transit engine)
dfns@solana/keychain-dfnsDfns
para@solana/keychain-paraPara
cdp@solana/keychain-cdpCoinbase Developer Platform
crossmint@solana/keychain-crossmintCrossmint
openfort@solana/keychain-openfortOpenfort

Gotchas

  • ESM only — set "type": "module" in package.json, as with Solana Kit.
  • privateKeyPath is Node-only — it reads from the filesystem. The other memory inputs (keyPair, privateKey, privateKeyString) work in any environment.
  • The memory backend stores the key as a non-extractable CryptoKey — you cannot read the raw key back out of the signer.
  • HashiCorp Vault needs the transit secrets engine enabled and an HTTPS address, and is the most involved backend to set up (tokens, namespaces, and TLS can all trip you up). The hosted services are far quicker to get signing.
  • For production, pin to a tagged release. The audit covers code through the v1.0.0 tag; backends added later (for example, memory and openfort in v1.2.0) are newer than the audited commit.

Rust

The Rust crate mirrors the TypeScript API. Backends are feature-gated, and you select the Solana SDK version for your validator target.
Cargo.toml
# Local keypair only:
solana-keychain = { version = "1.2", features = ["memory"] }

# Managed backends, e.g.:
# solana-keychain = { version = "1.2", features = ["turnkey", "fireblocks"] }

# For Agave 3.x/4.x, select the v3 SDK (sdk-v2 is the default):
# solana-keychain = { version = "1.2", features = ["memory", "sdk-v3"] }
Every backend implements the same SolanaSigner trait — pubkey, sign_transaction, sign_message, and is_available — so swapping backends is the same one-line change as in TypeScript.

Get your own node endpoint today

Start for free and get your app to production levels immediately. No credit card required.You can sign up with your GitHub, X, Google, or Microsoft account.

Additional resources

Last modified on May 29, 2026