La versión 2.0 es la primera versión estable del paquete de Python. La guía de migración a 2.0 cubre las formas de firma que comparte con Rust y TypeScript.
Instalación
Instala el paquete base, más un extra por cada backend que necesite una dependencia de 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
VersionedTransaction de solders;
se aceptan mensajes legacy, v0 y v1, excepto en Fireblocks con
use_program_call=True, que solo acepta 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 lanza un ImportError que indica el extra que hay que
instalar. Ledger es exclusivo de 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 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, 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 figuran 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 redúcelo 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 hereda 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 delSignedTransaction.transactiondevuelto.SendingSigner:sign_and_send_transaction(tx)firma y transmite a través del proveedor y devuelve laSignaturede la transacción que quedó registrada en la cadena. Tu transacción nunca se modifica.
| 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 |
Reduce 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 toda llamada de firma lanzan SignerErrorCode.NOT_INITIALIZED.
Configuración del backend
Memory
Se construye a partir de un archivo keypair de la CLI de Solana, una cadena en 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, para 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, ARN o alias de una clave ECC_NIST_EDWARDS25519. Pasa un
client de KMS de boto3 ya construido para 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 en modo solo firma, por lo que Fireblocks ni reescribe ni transmite la
transacción, y tú la transmites como de costumbre. Requiere una 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
en el 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 registró en la cadena, 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 debes continuar siempre desde result.transaction. Fordefi debe ser el pagador de comisiones y
debe firmar antes que cualquier otro firmante requerido: se acepta una transacción que ya lleve
firmas, pero la reescritura las invalida. La reescritura no muestra diferencias,
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 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 transmite. Úsalo 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 la forma
del firmante. Un SendingSigner transmite a través de su proveedor y tu función de envío
nunca se llama. Un TransactionSigner o ModifyingSigner firma, y tu función de envío
transmite el resultado codificado en base64 (el paquete no incluye 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 solo de firma sin 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.
Gestión de errores
Todo 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. Compara por code, no por 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 provino de una respuesta del proveedor.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") significa que
la transacción puede haberse registrado en la cadena:
- 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 transferencia nueva. - 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 muestra que Fireblocks transmitió una
transacción
use_program_calla pesar de haberse enviado en modo solo firma.
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 lleva campos. Para
recuperar un id que el proveedor aceptó antes de la cancelación, pasa un
PendingTransactionId() (de solana_keychain.core) como el
pending_transaction_id de la configuración de Crossmint o Fordefi native auto, y lee .get() después de
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 PEM contenidos en las configuraciones de backends remotos) trata toda la memoria del proceso como sensible. Consulta el modelo de seguridad para saber qué garantiza cada forma de firma.
Añadir firmantes personalizados
Crea una subclase de la clase de capacidad que corresponda a tu backend (TransactionSigner,
ModifyingSigner o SendingSigner) desde solana_keychain.core. Consulta la
guía de cómo añadir firmantes para integrar
otros servicios de gestión de claves.
Recursos
Is this page helpful?