Solana Keychain

Enterprise-grade signing with pluggable backends, AWS KMS, Fireblocks, Turnkey, and more.

Solana Keychain integration is in active development. This page documents the architecture and planned implementation. Built on the Solana Foundation's official keychain SDK.

Overview#

By default, DAEMON stores wallet keypairs locally using your OS keychain (via Electron's safeStorage). This works great for solo development, but teams and enterprises need more: hardware security modules, cloud KMS, custodial platforms, and audit trails.

Solana Keychain is a unified signing library maintained by the Solana Foundation. It lets DAEMON support 10 signing backends through a single interface, without changing how the rest of the app works.

Supported Backends#

BackendPackageUse Case
Local KeypairBuilt-in (default)Solo development, testing
AWS KMS@solana/keychain-aws-kmsCloud-managed signing with IAM policies
GCP KMS@solana/keychain-gcp-kmsGoogle Cloud key management
HashiCorp Vault@solana/keychain-vaultSelf-hosted secret management
Turnkey@solana/keychain-turnkeyNon-custodial wallet infrastructure
Privy@solana/keychain-privyEmbedded wallet platform
Fireblocks@solana/keychain-fireblocksInstitutional custody with policies
Coinbase CDP@solana/keychain-cdpCoinbase Developer Platform
Dfns@solana/keychain-dfnsProgrammable key management
Crossmint@solana/keychain-crossmintManaged wallet transactions
Para@solana/keychain-paraMPC-based signing

How It Works#

DAEMON's wallet architecture already isolates all signing in the Electron main process. The renderer (UI) never sees private keys, it only sends signing requests over IPC. This makes keychain integration clean: we only change where the signing happens, nothow the UI works.

Current Flow (Local Keypair)

Renderer: wallet:swap-execute(walletId, ...)
    │
Main Process:
    withKeypair(walletId, async (keypair) => {
        SecureKeyService.getKey()      // OS keychain
          → safeStorage.decryptString()
          → bs58.decode()
          → Keypair.fromSecretKey()

        transaction.sign([keypair])
        connection.sendRawTransaction()
        keypair.secretKey.fill(0)       // zero memory
    })
    │
Renderer: ← { ok: true, data: signature }

New Flow (With Keychain)

Renderer: wallet:swap-execute(walletId, ...)
    │
Main Process:
    withSigner(walletId, async (signer) => {
        // Resolves backend based on wallet config:
        //   'memory'  → SecureKeyService (current behavior)
        //   'aws-kms' → createAwsKmsSigner(config)
        //   'vault'   → createVaultSigner(config)
        //   etc.

        signer.signTransactions([transaction])
        connection.sendRawTransaction()
    })
    │
Renderer: ← { ok: true, data: signature }
The renderer and all IPC contracts stay exactly the same. The UI doesn't need to know which backend signed the transaction.

Architecture#

Backend (Electron Main Process)

A new KeychainService acts as a factory that creates the right signer based on each wallet's configuration:

electron/services/KeychainService.ts
import {
  createAwsKmsSigner,
  createTurnkeySigner,
  createVaultSigner,
  type SolanaSigner,
} from "@solana/keychain";

type SignerType = "memory" | "aws-kms" | "gcp-kms"
  | "vault" | "turnkey" | "privy" | "fireblocks";

async function createSigner(
  walletId: string,
  config: SignerConfig
): Promise<SolanaSigner> {
  switch (config.type) {
    case "memory":
      return createMemorySigner(walletId);
    case "aws-kms":
      return createAwsKmsSigner({
        keyId: config.keyId,
        publicKey: config.publicKey,
        region: config.region,
        credentials: {
          accessKeyId: SecureKeyService.getKey(`AWS_KEY_${walletId}`),
          secretAccessKey: SecureKeyService.getKey(`AWS_SECRET_${walletId}`),
        },
      });
    case "vault":
      return createVaultSigner({
        keyName: config.keyName,
        publicKey: config.publicKey,
        vaultAddr: config.vaultAddr,
        vaultToken: SecureKeyService.getKey(`VAULT_TOKEN_${walletId}`),
      });
    // ... other backends
  }
}

The withSigner Wrapper

Replaces the existing withKeypair(), same pattern, different signer source:

electron/services/SolanaService.ts
async function withSigner<T>(
  walletId: string,
  fn: (signer: SolanaSigner) => Promise<T>
): Promise<T> {
  const wallet = db.getWallet(walletId);
  const signerConfig = wallet.signer_config
    ? JSON.parse(wallet.signer_config)
    : { type: "memory" };

  const signer = await createSigner(walletId, signerConfig);

  if (!(await signer.isAvailable())) {
    throw new Error("Signer backend not available");
  }

  return fn(signer);
}

Database Changes

SQL
ALTER TABLE wallets ADD COLUMN signer_type TEXT DEFAULT 'memory';
ALTER TABLE wallets ADD COLUMN signer_config TEXT;

-- Backend credentials stored in existing secure_keys table
-- e.g., AWS_KEY_{walletId}, VAULT_TOKEN_{walletId}

New IPC Handlers

HandlerPurpose
keychain:backendsList available signing backends
keychain:configureSet up a backend for a wallet
keychain:testTest backend connectivity

Frontend (Renderer)#

A new Signing Backend section in wallet settings lets users pick and configure their backend:

Backend Selector

Radio-style list showing all available backends. Local Keypair is the default. Each option shows the backend name, description, and connection status.

Configuration Forms

Backend-specific forms for entering credentials (API keys, regions, key IDs). All secrets are sent to the main process and stored encrypted, never held in the renderer.

Connection Test

One-click test button that calls the backend's isAvailable() method and reports success or error with actionable diagnostics.

Wallet Badge

Small indicator on the wallet panel showing which backend is active (e.g., "AWS KMS" badge next to the wallet name).

What Changes vs What Stays#

ComponentChanges?Details
SecureKeyServiceNoStill used for local keys + backend credentials
withKeypair()Yes → withSigner()Wrapper resolves signer type
Signing logicYesUses signer.signTransactions() instead of keypair.sign()
IPC contractMinimalExisting handlers unchanged, 3 new handlers added
Wallet UIYesNew backend settings section
Transaction historyNoIdentical, just stores signatures
Helius / portfolioNoRead-only, doesn't involve signing
Jupiter swapsSigning onlyQuote flow unchanged
PumpFun launchesSigning onlyToken creation flow unchanged
Rate limitingNoAll existing security checks stay

Backend Setup Examples#

AWS KMS

Requirements: an Ed25519 KMS key with SIGN_VERIFY usage.

// Key must be ECC_NIST_EDWARDS25519 algorithm
// IAM policy needs: kms:Sign and kms:DescribeKey

{
  backend: "aws-kms",
  keyId: "arn:aws:kms:us-east-1:123456:key/uuid",
  publicKey: "YourSolanaBase58Address",
  region: "us-east-1"
}

HashiCorp Vault

Requirements: Vault transit engine with an Ed25519 key.

// Setup:
// vault secrets enable transit
// vault write -f transit/keys/solana-key type=ed25519

{
  backend: "vault",
  vaultAddr: "https://vault.example.com:8200",
  keyName: "solana-key",
  publicKey: "YourSolanaBase58Address"
}

Turnkey

Non-custodial wallet infrastructure with policy controls.

{
  backend: "turnkey",
  organizationId: "your-org-id",
  privateKeyId: "your-key-id",
  publicKey: "YourSolanaBase58Address"
}

Migration Path#

  • Phase 1, Add @solana/keychain, build KeychainService, create withSigner() wrapper. Zero user-facing changes, local keypair behavior is identical.
  • Phase 2, Add keychain IPC handlers and backend selector UI. Users can now switch backends per-wallet.
  • Phase 3, Add backend-specific config forms one at a time (AWS KMS first, then Vault, Turnkey, etc.).

Security#

  • Keys never in renderer, All signing stays in the Electron main process behind IPC
  • Credentials encrypted, Backend API keys stored via SecureKeyService (OS keychain encryption)
  • Memory cleanup, Local keypairs zeroed after use, remote backends never expose raw keys
  • Health checks, isAvailable() called before every signing operation
  • Audit trail, All transactions logged to transaction_history table regardless of backend
  • Security audited, The solana-keychain SDK has undergone independent security review by Accretion