La versión 2.0 es la primera versión estable del paquete de Python. La guía de migración a la 2.0 cubre los esquemas de firma que comparte con Rust y TypeScript.
Instalación
Instala el paquete base, más un extra por cada backend que requiera una dependencia del proveedor:
pip install solana-keychain # memory, vault and parapip install 'solana-keychain[aws-kms]' # AWS KMS (boto3)pip install 'solana-keychain[gcp-kms]' # GCP KMS (google-cloud-kms)pip install 'solana-keychain[privy]' # Privypip install 'solana-keychain[turnkey]' # Turnkeypip install 'solana-keychain[fireblocks]' # Fireblockspip install 'solana-keychain[cdp]' # Coinbase Developer Platformpip install 'solana-keychain[crossmint]' # Crossmintpip install 'solana-keychain[dfns]' # Dfnspip install 'solana-keychain[openfort]' # Openfortpip install 'solana-keychain[utila]' # Utilapip install 'solana-keychain[fordefi]' # Fordefi# Several at oncepip install 'solana-keychain[aws-kms,privy]'
Requiere Python 3.10 o posterior. Las transacciones son objetos
solders VersionedTransaction;
se aceptan mensajes legacy, v0 y v1, excepto en Fireblocks con
use_program_call=True, que acepta únicamente legacy y v0.
Memory, Vault y Para se pueden importar desde la raíz del paquete. Todos los demás backends
se importan desde su propio submódulo (por ejemplo, solana_keychain.aws_kms), y
importar uno sin su extra genera un ImportError que indica el extra que
hay que instalar. Ledger solo está disponible en Rust.
Uso básico
Cada backend tiene una dataclass de configuración y una factoría asíncrona
create_<backend>_signer que devuelve un firmante listo para usar. Todos los métodos de firma son corrutinas:
import asynciofrom solana_keychain import MemorySignerfrom solders.transaction import VersionedTransactionasync def sign(transaction: VersionedTransaction) -> str:signer = MemorySigner.from_private_key_file("/path/to/id.json")print("Signing with:", signer.pubkey)# Check availability (useful for remote signers)if not await signer.is_available():raise RuntimeError("Signer offline")result = await signer.sign_transaction(transaction)print("Signature:", result.signature)print("Fully signed:", result.is_complete)return result.encoded_transaction # base64 wire transaction
sign_transaction devuelve un SignedTransaction con encoded_transaction,
signature, is_complete y transaction. transaction es la transacción
firmada de referencia: continúa a partir de ella, especialmente con un firmante modificador, cuyo
resultado es un objeto distinto del que le pasaste.
Factoría unificada
create_keychain_signer recibe el nombre de un backend y la dataclass de
configuración de ese backend, e importa el backend de forma diferida (lazy), de modo que solo es necesario instalar los extras que uses:
from solana_keychain import create_keychain_signerfrom solana_keychain.privy import PrivySignerConfigsigner = await create_keychain_signer("privy",PrivySignerConfig(app_id="your-app-id",app_secret="your-app-secret",wallet_id="your-wallet-id",),)
Los nombres se enumeran en SUPPORTED_BACKENDS: aws-kms, cdp, crossmint,
dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para,
privy, turnkey, utila y vault. Un nombre desconocido, o una configuración de
una clase incorrecta, lanza SignerError con SignerErrorCode.CONFIG_ERROR. La
factoría está tipada como si devolviera el SolanaSigner base, así que acótalo con
isinstance antes de firmar (consulta Capacidades del firmante).
Clientes HTTP remotos
Los backends HTTP remotos aceptan un http_client opcional (un httpx.AsyncClient)
para TLS personalizado o proxies. Si no se define, cada solicitud usa un cliente de un solo uso
con un tiempo de espera de 60 segundos, HTTPS obligatorio en la URL base y redirecciones
rechazadas.
Capacidades del firmante
Cada backend es una subclase de la ABC SolanaSigner (pubkey, sign_message,
is_available) más exactamente una clase de capacidad:
TransactionSigner:sign_transaction(tx)firma tu transacción in situ y devuelve unSignedTransaction. Tú la transmites.ModifyingSigner:modify_and_sign_transaction(tx)permite que el proveedor reescriba la transacción y luego la firma sin transmitirla. Tu objeto no se modifica; continúa a partir deSignedTransaction.transaction, que se devuelve.SendingSigner:sign_and_send_transaction(tx)firma y transmite a través del proveedor y devuelve laSignaturede la transacción que quedó confirmada en la red. Tu transacción nunca se muta.
| Backend | Clase de capacidad | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Sí |
| cdp | TransactionSigner | Solo cargas útiles UTF-8; de lo contrario, SERIALIZATION_ERROR |
| utila | TransactionSigner | Lanza SIGNING_FAILED |
fordefi black box (FordefiBlackBoxSigner) | TransactionSigner | Sí |
fordefi native manual (FordefiNativeManualSigner) | ModifyingSigner | Sí |
fordefi native auto (FordefiNativeAutoSigner) | SendingSigner | Sí |
| crossmint | SendingSigner | Lanza SIGNING_FAILED |
Acota con isinstance. Todas las clases de capacidad se exportan desde la raíz del
paquete:
from solana_keychain import ModifyingSigner, SendingSigner, TransactionSignerif isinstance(signer, SendingSigner):signature = await signer.sign_and_send_transaction(transaction)elif isinstance(signer, ModifyingSigner):result = await signer.modify_and_sign_transaction(transaction)elif isinstance(signer, TransactionSigner):result = await signer.sign_transaction(transaction)
Inicialización
Las factorías create_* y create_keychain_signer realizan por ti toda la configuración
necesaria. Si construyes directamente la clase de firmante de Privy, Fireblocks, Dfns, Crossmint, Para, Openfort o
Utila, ejecuta await signer.init() antes de usarla. Hasta entonces,
pubkey y cualquier llamada de firma lanzan SignerErrorCode.NOT_INITIALIZED.
Configuración de backends
Memory
Constrúyelo a partir de un archivo keypair de Solana CLI, una cadena base58 o "[1,2,...]", bytes
sin procesar o un Keypair de solders:
from solana_keychain import MemorySigner, MemorySignerConfig, create_memory_signerfrom solders.keypair import Keypairsigner = MemorySigner.from_private_key_file("/path/to/id.json")signer = MemorySigner.from_private_key_string("base58_private_key")signer = MemorySigner.from_bytes(secret_key_bytes) # 64 or 32 bytessigner = await create_memory_signer(MemorySignerConfig(keypair=Keypair()))
La clave reside en la memoria del proceso y Python no puede ponerla a cero. Usa este backend para desarrollo y pruebas.
HashiCorp Vault
La clave debe ser una clave ed25519 en el motor transit. HTTP sin cifrar se acepta
solo para una dirección de loopback, con vault server -dev:
from solana_keychain import VaultSignerConfig, create_vault_signersigner = await create_vault_signer(VaultSignerConfig(api_base_url="https://vault.example.com:8200",token="hvs.xxxxx",key_name="my-solana-key",public_key="base58_public_key",))
AWS KMS
key_id es un ID de clave, un ARN o un alias de una clave ECC_NIST_EDWARDS25519. Pasa un
client de KMS de boto3 ya construido para usar credenciales o endpoints personalizados; en ese caso region
se ignora:
from solana_keychain.aws_kms import AwsKmsSignerConfig, create_aws_kms_signersigner = await create_aws_kms_signer(AwsKmsSignerConfig(key_id="alias/my-solana-key",public_key="base58_public_key",region="us-east-1", # optional, defaults to the AWS config chain))
GCP KMS
La versión de la clave debe ser una clave EC_SIGN_ED25519. client admite opcionalmente un
KeyManagementServiceAsyncClient ya construido:
from solana_keychain.gcp_kms import GcpKmsSignerConfig, create_gcp_kms_signersigner = await create_gcp_kms_signer(GcpKmsSignerConfig(key_name="projects/my-project/locations/us-east1/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1",public_key="base58_public_key",))
Privy
from solana_keychain.privy import PrivySignerConfig, create_privy_signersigner = await create_privy_signer(PrivySignerConfig(app_id="app_id",app_secret="app_secret",wallet_id="wallet_id",))
Las wallets protegidas por una clave de autorización reciben un PrivyAuthorizationConfig como
authorization_context.
Turnkey
from solana_keychain.turnkey import TurnkeySignerConfig, create_turnkey_signersigner = await create_turnkey_signer(TurnkeySignerConfig(api_public_key="api_public_key", # hex P-256api_private_key="api_private_key", # hex P-256organization_id="org_id",private_key_id="private_key_id",public_key="base58_public_key",))
Fireblocks
from solana_keychain.fireblocks import FireblocksSignerConfig, create_fireblocks_signersigner = await create_fireblocks_signer(FireblocksSignerConfig(api_key="api_key",private_key_pem="-----BEGIN RSA PRIVATE KEY-----\n...",vault_account_id="0",asset_id="SOL", # or "SOL_TEST" for devnet))
use_program_call=True firma con la operación PROGRAM_CALL en lugar de RAW. Se
envía solo para firmar, por lo que Fireblocks ni reescribe ni transmite la
transacción, y tú la transmites como de costumbre. Requiere una wallet caliente (hot wallet) y Fireblocks debe
habilitarlo para tu workspace.
CDP (Coinbase Developer Platform)
from solana_keychain.cdp import CdpSignerConfig, create_cdp_signersigner = await create_cdp_signer(CdpSignerConfig(api_key_id="api_key_id",api_key_secret="api_key_secret",wallet_secret="wallet_secret",address="base58_address",network="solana", # or "solana-devnet"; required for address lookup tables))
Crossmint
from solana_keychain.crossmint import CrossmintSignerConfig, create_crossmint_signersigner = await create_crossmint_signer(CrossmintSignerConfig(api_key="api_key",wallet_locator="wallet_locator",))signature = await signer.sign_and_send_transaction(transaction)
Crossmint es solo de envío. Su API siempre ejecuta una transacción aprobada
del lado del servidor y patrocina el gas, por lo que CrossmintSigner es un SendingSigner y
sign_message lanza SIGNING_FAILED. La firma devuelta identifica la
transacción que Crossmint confirmó, que puede diferir de la tuya. Definir
signer_secret (un secreto de firmante delegado xmsk1_...) aprueba las transacciones
automáticamente, lo que delega en Crossmint la decisión de qué se aprueba.
Dfns
from solana_keychain.dfns import DfnsSignerConfig, create_dfns_signersigner = await create_dfns_signer(DfnsSignerConfig(auth_token="auth_token",cred_id="cred_id",private_key_pem="-----BEGIN EC PRIVATE KEY-----\n...",wallet_id="wallet_id",))
Openfort
from solana_keychain.openfort import OpenfortSignerConfig, create_openfort_signersigner = await create_openfort_signer(OpenfortSignerConfig(secret_key="sk_...",account_id="acc_...",wallet_secret="wallet_secret",))
Para
from solana_keychain import ParaSignerConfig, create_para_signersigner = await create_para_signer(ParaSignerConfig(api_key="sk_...",wallet_id="wallet_uuid",))
Utila
Utila solo firma transacciones; sign_message lanza SIGNING_FAILED.
from solana_keychain.utila import UtilaSignerConfig, create_utila_signersigner = await create_utila_signer(UtilaSignerConfig(service_account_email="sa@example.com",service_account_private_key_pem="-----BEGIN PRIVATE KEY-----\n...",vault_id="vault_id",wallet_id="wallet_id",network="networks/solana-devnet",))
Fordefi
Fordefi tiene tres modos de firma, fijados en la construcción mediante chain y
push_mode. create_fordefi_signer elige la clase, cada clase rechaza una
configuración destinada a otra, y las tres firman mensajes:
| Modo | Configuración | Clase | Método |
|---|---|---|---|
| Black box | chain sin definir | FordefiBlackBoxSigner | sign_transaction |
| Native auto | chain definido, push_mode sin definir o "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native manual | chain definido, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
Las solicitudes a la API se autentican con una firma de solicitud ECDSA P-256: proporciona la
clave como private_key_pem, o mantenla en un KMS/HSM pasando una subclase de
FordefiRequestSigner como request_signer.
Modo native auto: define chain ("solana_devnet" o "solana_mainnet"), y
Fordefi puede reemplazar el blockhash y las comisiones, y luego firma y transmite la
transacción por sí mismo. Solo acepta una transacción sin firmar cuyo único
firmante requerido sea la vault. fee se pasa a Fordefi tal cual:
import osfrom solana_keychain.fordefi import FordefiSignerConfig, create_fordefi_signersigner = 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"], # vault address (base58)private_key_pem=open("./secret/private.pem").read(),chain="solana_devnet",fee={"type": "custom", "priority_fee": "1000"}, # optional))signature = await signer.sign_and_send_transaction(transaction)
Modo native manual: define chain y push_mode="manual". Fordefi reescribe
el mensaje (blockhash reciente, instrucciones de comisión de Compute Budget) y lo firma,
pero no lo transmite. La transacción devuelta reemplaza a la que enviaste,
por lo que siempre debes continuar a partir de result.transaction. Fordefi debe ser quien pague la comisión y
debe firmar antes que cualquier otro firmante requerido: se acepta una transacción que ya incluya
firmas, pero la reescritura las invalida. La reescritura no se muestra como diff,
por lo que debes inspeccionar el resultado antes de transmitir, y transmitir sin demora, ya que Fordefi
no devuelve la altura de expiración del nuevo blockhash.
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=open("./secret/private.pem").read(),chain="solana_devnet",push_mode="manual",))result = await signer.modify_and_sign_transaction(transaction)... # inspect result.transaction, then add any remaining signatures
Transmite la transacción inspeccionada como se indica en Firmar y enviar, y concilia antes de reintentar si el envío falla.
Modo black box: omite chain. Fordefi firma los bytes exactos de tu mensaje y
no los transmite. Usa este modo con una vault black box de Fordefi.
signer = await create_fordefi_signer(FordefiSignerConfig(access_token=os.environ["FORDEFI_ACCESS_TOKEN"],vault_id=os.environ["FORDEFI_BB_VAULT_ID"],public_key=os.environ["FORDEFI_BB_PUBLIC_KEY"],private_key_pem=open("./secret/private.pem").read(),))
Firmar y enviar
sign_and_send_transaction lleva una transacción a la cadena sea cual sea el
tipo de firmante. Un SendingSigner transmite a través de su proveedor y tu función de envío
nunca se invoca. Un TransactionSigner o un ModifyingSigner firma, y tu función de envío
transmite el resultado codificado en base64 (el paquete no incluye un cliente RPC):
import httpxfrom solana_keychain import sign_and_send_transactionfrom solders.signature import Signatureasync def send(encoded: str) -> Signature:async with httpx.AsyncClient() as client:response = await client.post("https://api.devnet.solana.com",json={"jsonrpc": "2.0","id": 1,"method": "sendTransaction","params": [encoded, {"encoding": "base64"}],},)return Signature.from_string(response.json()["result"])signature = await sign_and_send_transaction(signer, transaction, send)
Un firmante que solo firma y no tiene función de envío lanza CONFIG_ERROR, y una
transacción a la que aún le faltan firmas después de firmar lanza SIGNING_FAILED.
Manejo de errores
Cada fallo es un SignerError con un code estable de SignerErrorCode:
BROADCAST_UNCONFIRMED, CONFIG_ERROR, EXPECTED_SOLANA_SIGNER, HTTP_ERROR,
INVALID_PRIVATE_KEY, INVALID_PUBLIC_KEY, IO_ERROR, NOT_AVAILABLE,
NOT_INITIALIZED, OTHER, PARSING_ERROR, REMOTE_API_ERROR,
SERIALIZATION_ERROR y SIGNING_FAILED. Haz la comparación con code, no con el mensaje:
str() y repr() solo incluyen un mensaje genérico fijo, nunca material de claves ni
respuestas sin procesar del proveedor. status_code y provider_error_code se establecen cuando el
fallo procede de una respuesta del proveedor.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") significa que
la transacción pudo haberse confirmado en la red:
- En Crossmint y Fordefi native auto,
provider_transaction_idse establece cuando se aceptó la creación, eidempotency_keycontiene la clave derivada del mensaje con la que se envió la creación. Reenviar la transacción idéntica byte a byte es seguro; una reconstruida (por ejemplo, con un nuevo blockhash) es una nueva transferencia. - Desde
sign_and_send_transaction, una función de envío que falla lo lanza contransaction_signatureestablecido en la firma de la transacción completada. - Fireblocks lo lanza solo si una consulta periódica (poll) muestra que Fireblocks transmitió una
transacción
use_program_calla pesar de haberse enviado solo para firmar.
from solana_keychain import SignerError, SignerErrorCodetry:signature = await sign_and_send_transaction(signer, transaction, send)except SignerError as error:if error.code is SignerErrorCode.BROADCAST_UNCONFIRMED:reconcile(error.provider_transaction_id,error.idempotency_key,error.transaction_signature,)raise
Una llamada cancelada vuelve a lanzar asyncio.CancelledError, que no incluye campos. Para
recuperar un id que el proveedor aceptó antes de la cancelación, pasa un
PendingTransactionId() (de solana_keychain.core) como pending_transaction_id de la configuración de Crossmint o
Fordefi native auto, y lee .get() tras
la cancelación. Concilia con el proveedor antes de reintentar.
Keychain valida el salto de firma, no lo que hace la transacción: una firma devuelta por el proveedor se verifica con ed25519 contra el mensaje calculado localmente antes de adjuntarse. Python no puede poner a cero los búferes de claves, así que con backends de clave local (Memory, y las claves de API y los PEM almacenados en las configuraciones de backends remotos) trata toda la memoria del proceso como sensible. Consulta el modelo de seguridad para saber qué garantiza cada esquema de firma.
Añadir firmantes personalizados
Crea una subclase de la única clase de capacidad que coincida con tu backend (TransactionSigner,
ModifyingSigner o SendingSigner) de solana_keychain.core. Consulta la
guía para añadir firmantes para integrar
otros servicios de gestión de claves.
Recursos
Is this page helpful?