Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

solana-keychain (Python)

Flexible, framework-agnostic Solana transaction signing for Python applications

solana-keychain provides a unified interface for signing Solana transactions with multiple backend implementations. Whether you need local keypairs for development, enterprise vault integration, or managed wallet services, this library offers a consistent API across all signing methods.

Features

  • Unified interface: a single SolanaSigner contract for every backend
  • Async-first: sign_transaction / sign_message / is_available are coroutines
  • Verified wire format: golden-vector tests pin the exact serialized transaction bytes, so serialization can never silently drift
  • Safe errors: SignerError redacts sensitive detail from its message; match on its stable code values
  • Minimal core: built on solders for canonical transaction serialization and Ed25519 primitives

Supported Backends

Backend Use Case Module Status
Memory Local keypairs, development, testing solana_keychain.memory ✅ Available
Vault Enterprise key management with HashiCorp Vault solana_keychain.vault ✅ Available
Privy Embedded wallets with Privy infrastructure solana_keychain.privy ✅ Available
Turnkey Non-custodial key management via Turnkey solana_keychain.turnkey ✅ Available
AWS KMS AWS Key Management Service with Ed25519 signing solana_keychain.aws_kms ✅ Available
Fireblocks Fireblocks institutional custody platform solana_keychain.fireblocks ✅ Available
Fordefi Fordefi institutional MPC custody platform solana_keychain.fordefi ✅ Available
GCP KMS Google Cloud Key Management Service with Ed25519 signing solana_keychain.gcp_kms ✅ Available
Dfns Dfns wallet infrastructure with Ed25519 signing solana_keychain.dfns ✅ Available
Para MPC wallets with Para infrastructure solana_keychain.para ✅ Available
CDP Coinbase Developer Platform managed wallets solana_keychain.cdp ✅ Available
Crossmint Crossmint managed wallets solana_keychain.crossmint ✅ Available
Openfort Openfort backend wallets with TEE-stored keys solana_keychain.openfort ✅ Available
Utila Utila MPC wallet integration solana_keychain.utila ✅ Available

Installation

pip install solana-keychain              # memory + vault
pip install 'solana-keychain[aws-kms]'   # adds the AWS KMS backend
pip install 'solana-keychain[cdp]'       # adds the CDP backend
pip install 'solana-keychain[crossmint]' # adds the Crossmint backend
pip install 'solana-keychain[dfns]'      # adds the Dfns backend
pip install 'solana-keychain[fireblocks]' # adds the Fireblocks backend
pip install 'solana-keychain[fordefi]'   # adds the Fordefi backend
pip install 'solana-keychain[gcp-kms]'   # adds the GCP KMS backend
pip install 'solana-keychain[openfort]'  # adds the Openfort backend
pip install 'solana-keychain[privy]'     # adds the Privy backend
pip install 'solana-keychain[turnkey]'   # adds the Turnkey backend
pip install 'solana-keychain[utila]'     # adds the Utila backend

Requires Python 3.10+. Backends built on heavy provider SDKs ship as optional extras; importing such a backend without its extra raises an ImportError naming the extra to install. Extras-gated backends are imported from their submodule (e.g. from solana_keychain.aws_kms import create_aws_kms_signer), not from the package root.

Quick Start

Memory Signer (Local Development)

import asyncio

from solana_keychain import MemorySigner


async def main() -> None:
    # Build a signer from a base58 key, a "[1,2,...]" byte array, raw bytes,
    # or a Solana CLI keypair file.
    signer = MemorySigner.from_private_key_file("/path/to/keypair.json")
    print("address:", signer.pubkey)

    # Sign an arbitrary message.
    signature = await signer.sign_message(b"Hello Solana!")
    print("signature:", signature)

    # Sign a transaction (tx is a solders.transaction.VersionedTransaction):
    #   result = await signer.sign_transaction(tx)
    #   result.encoded_transaction  # base64 wire transaction
    #   result.signature            # this signer's signature
    #   result.is_complete          # are all required signatures present?
    #   result.transaction          # the authoritative signed transaction


asyncio.run(main())

Remote Backends

Every remote backend follows the same pattern: a config dataclass and an async create_<backend>_signer factory that returns a ready-to-use signer:

from solana_keychain import VaultSignerConfig, create_vault_signer

signer = await create_vault_signer(
    VaultSignerConfig(
        api_base_url="https://vault.example.com",
        token=os.environ["VAULT_TOKEN"],
        key_name="my-solana-key",
        public_key="4BuiY9QUUfPoAGNJBja3JapAuVWMc9c7in6UCgyC2zPR",
    )
)

Remote HTTP backends accept an optional http_client override in their config (an httpx.AsyncClient, for custom TLS or proxies); when unset, requests go through an HTTPS-enforcing one-shot client with a 60s timeout and redirects rejected.

Core API

Every backend subclasses the SolanaSigner ABC from solana_keychain.core, plus exactly the capability class matching its provider's shape:

class SolanaSigner(ABC):
    @property
    def pubkey(self) -> Pubkey: ...

    async def sign_message(self, message: bytes) -> Signature: ...

    async def is_available(self) -> bool: ...


class TransactionSigner(SolanaSigner):
    """Signs the caller's transaction as given; the caller broadcasts the result."""

    async def sign_transaction(self, transaction: VersionedTransaction) -> SignedTransaction: ...


class ModifyingSigner(SolanaSigner):
    """The provider rewrites the transaction before signing it; continue from
    `SignedTransaction.transaction`, never from the bytes submitted."""

    async def modify_and_sign_transaction(
        self, transaction: VersionedTransaction
    ) -> SignedTransaction: ...


class SendingSigner(SolanaSigner):
    """The provider signs and broadcasts server-side; the caller's transaction is
    never mutated, and the returned signature identifies what landed."""

    async def sign_and_send_transaction(self, transaction: VersionedTransaction) -> Signature: ...

Both signing entry points return a SignedTransaction(encoded_transaction, signature, is_complete, transaction); is_complete reports whether every required signature is present. A TransactionSigner signs the transaction in place and hands it back as transaction; a ModifyingSigner leaves the caller's object untouched, because solders messages are read-only, and hands back the provider's rewritten transaction instead. Only transaction is guaranteed to match encoded_transaction and the bytes signature covers. Legacy, v0 and v1 transactions are all accepted.

Errors are always SignerError with a stable code (SIGNER_INVALID_PRIVATE_KEY, SIGNER_SIGNING_FAILED, …). str()/repr() of a SignerError never include key material or raw remote responses.

Signer capabilities

The capability class a backend subclasses says whether the provider broadcasts; whether it can sign arbitrary bytes is fixed per backend:

Backend Capability class sign_message
memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort TransactionSigner yes
cdp TransactionSigner UTF-8 payloads only, otherwise SERIALIZATION_ERROR
utila TransactionSigner SIGNING_FAILED
crossmint SendingSigner SIGNING_FAILED
fordefi black box (FordefiBlackBoxSigner) TransactionSigner yes
fordefi native auto (FordefiNativeAutoSigner) SendingSigner yes
fordefi native manual (FordefiNativeManualSigner) ModifyingSigner yes

Crossmint executes every approved transaction server-side and exposes no sign-only API, so it is a SendingSigner only. It may rewrite the transaction to sponsor gas, in which case the returned signature identifies the transaction it landed rather than covering the caller's bytes; the caller's transaction is never modified.

Fordefi signing modes

create_fordefi_signer picks the Fordefi type from config.chain and config.push_mode, and each type rejects a config meant for another:

Config Signer Entry point
no chain FordefiBlackBoxSigner sign_transaction
chain, push_mode unset or "auto" FordefiNativeAutoSigner sign_and_send_transaction
chain, push_mode="manual" FordefiNativeManualSigner modify_and_sign_transaction

Black-box mode signs the caller's exact message bytes and leaves broadcasting to the caller. Native auto lets Fordefi update the blockhash and fees, then sign and broadcast; the caller's transaction is left untouched and the returned signature identifies what landed.

Native manual lets Fordefi rewrite the recent blockhash and the Compute Budget fee instructions, then sign without broadcasting, so the caller broadcasts. The returned signature covers Fordefi's bytes, not the ones submitted, and the rewrite is not diffed against them: Fordefi is trusted for the rewrite. Inspect result.transaction before broadcasting it. Fordefi must be the fee payer and must sign before every downstream signer, so a transaction that is not vault-paid or already carries a signature is rejected before submitting.

The signature is ed25519-verified against the returned transaction's own message at the vault's required-signer position; a signature that does not verify, or a returned transaction the vault does not sign, fails with SIGNER_SIGNING_FAILED and leaves the caller's transaction untouched.

import os

from solana_keychain.fordefi import FordefiSignerConfig, create_fordefi_signer

signer = await create_fordefi_signer(
    FordefiSignerConfig(
        access_token=os.environ["FORDEFI_ACCESS_TOKEN"],
        vault_id=os.environ["FORDEFI_VAULT_ID"],
        public_key=os.environ["FORDEFI_PUBLIC_KEY"],
        private_key_pem=os.environ["FORDEFI_PRIVATE_KEY_PEM"],
        chain="solana_mainnet",
        push_mode="manual",
    )
)

result = await signer.modify_and_sign_transaction(transaction)
if result.is_complete:
    # Broadcast result.encoded_transaction through your RPC client.
    pass
else:
    # Sign result.transaction with the downstream signers, reserialize, broadcast.
    pass

Bound what Fordefi may spend through config.fee, for example {"type": "custom", "priority_fee": "1000"}; that request is what Fordefi honours, and the returned fee instructions are not checked against it locally.

Fordefi normally refreshes the blockhash but does not return its exact lastValidBlockHeight, so broadcast manual results promptly rather than relying on a locally known block-height expiry.

Sign and Send

sign_and_send_transaction gets a transaction on chain with one call. A SendingSigner (Crossmint, Fordefi native auto) broadcasts through its provider and the send function is never called; a TransactionSigner or ModifyingSigner signs and the send function broadcasts the base64-encoded result:

from solana_keychain import sign_and_send_transaction

signature = await sign_and_send_transaction(signer, transaction, rpc_send)

Development

From the repo root (recipes bootstrap python/.venv automatically):

just py-test    # unit tests
just py-fmt     # ruff format + lint + mypy
just py-build   # sdist + wheel

Or manually:

cd python
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest

Golden wire-format vectors are pinned in tests/test_parity.py — the exact serialized bytes for one canonical transaction. Never regenerate them to make the suite pass; a mismatch means the library's output has drifted from the Solana wire format.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solana_keychain-2.0.0b1.tar.gz (94.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

solana_keychain-2.0.0b1-py3-none-any.whl (78.7 kB view details)

Uploaded Python 3

File details

Details for the file solana_keychain-2.0.0b1.tar.gz.

File metadata

  • Download URL: solana_keychain-2.0.0b1.tar.gz
  • Upload date:
  • Size: 94.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for solana_keychain-2.0.0b1.tar.gz
Algorithm Hash digest
SHA256 aeae0c2a41b3b4e666c88bd8a8d403af61efd435d3c346d735248cf6cf22ecd8
MD5 8f5552554ecb270be4cb9ab3c25ff94c
BLAKE2b-256 8af29fe90385a1f0c13993a7cb926c9e4ad663b9e8a015f11c8c966fcd4cb482

See more details on using hashes here.

Provenance

The following attestation bundles were made for solana_keychain-2.0.0b1.tar.gz:

Publisher: python-publish.yml on solana-foundation/solana-keychain

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solana_keychain-2.0.0b1-py3-none-any.whl.

File metadata

File hashes

Hashes for solana_keychain-2.0.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 0c088714e357fa7be623b5da446096ee6ece0d2c77bb37a14eb83906272245d0
MD5 d7ecf68c6b6afcb3692b2a909684db08
BLAKE2b-256 6918b466d60ce78076eae9dd86440f16700817b4b2332de0e230aa9a42aaf004

See more details on using hashes here.

Provenance

The following attestation bundles were made for solana_keychain-2.0.0b1-py3-none-any.whl:

Publisher: python-publish.yml on solana-foundation/solana-keychain

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2.0.0b1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page