La versione 2.0 è la prima release stabile del pacchetto Python. La guida alla migrazione a 2.0 illustra le strutture di firma che condivide con Rust e TypeScript.
Installazione
Installa il pacchetto base, più un extra per ogni backend che richiede una dipendenza del provider:
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]'
Richiede Python 3.10 o successivo. Le transazioni sono oggetti
solders VersionedTransaction;
sono accettati i messaggi legacy, v0 e v1, tranne da Fireblocks con
use_program_call=True, che accetta solo legacy e v0.
Memory, Vault e Para sono importabili dalla radice del pacchetto. Ogni altro backend
viene importato dal proprio sottomodulo (ad esempio solana_keychain.aws_kms), e
importarne uno senza il relativo extra genera un ImportError che indica l'extra da
installare. Ledger è disponibile solo per Rust.
Utilizzo di base
Ogni backend ha una dataclass di configurazione e una factory asincrona create_<backend>_signer
che restituisce un signer pronto all'uso. Tutti i metodi di firma sono coroutine:
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 restituisce un SignedTransaction con encoded_transaction,
signature, is_complete e transaction. transaction è la transazione firmata
autorevole: prosegui da essa, soprattutto con un signer modificante, il cui
risultato è un oggetto diverso da quello che hai passato.
Factory unificata
create_keychain_signer accetta il nome di un backend e la dataclass di configurazione
di quel backend, e importa il backend in modo lazy, quindi occorre installare solo gli extra che
usi:
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",),)
I nomi sono elencati in SUPPORTED_BACKENDS: aws-kms, cdp, crossmint,
dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para,
privy, turnkey, utila e vault. Un nome sconosciuto, o una configurazione della
classe sbagliata, genera SignerError con SignerErrorCode.CONFIG_ERROR. La
factory è tipizzata come se restituisse il SolanaSigner di base, quindi restringi il tipo con
isinstance prima di firmare (vedi Capacità del signer).
Client HTTP remoti
I backend HTTP remoti accettano un http_client opzionale (un httpx.AsyncClient)
per TLS o proxy personalizzati. Se non è impostato, ogni richiesta usa un client monouso
con timeout di 60 secondi, HTTPS obbligatorio sull'URL di base e reindirizzamenti
rifiutati.
Capacità del signer
Ogni backend estende la ABC SolanaSigner (pubkey, sign_message,
is_available) più esattamente una classe di capacità:
TransactionSigner:sign_transaction(tx)firma la tua transazione sul posto e restituisce unSignedTransaction. La trasmissione spetta a te.ModifyingSigner:modify_and_sign_transaction(tx)consente al provider di riscrivere la transazione, per poi firmarla senza trasmetterla. Il tuo oggetto resta intatto; prosegui dalSignedTransaction.transactionrestituito.SendingSigner:sign_and_send_transaction(tx)firma e trasmette tramite il provider e restituisce laSignaturedella transazione andata a buon fine. La tua transazione non viene mai modificata.
| Backend | Classe di capacità | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Sì |
| cdp | TransactionSigner | Solo payload UTF-8, altrimenti SERIALIZATION_ERROR |
| utila | TransactionSigner | Genera SIGNING_FAILED |
fordefi black box (FordefiBlackBoxSigner) | TransactionSigner | Sì |
fordefi native manual (FordefiNativeManualSigner) | ModifyingSigner | Sì |
fordefi native auto (FordefiNativeAutoSigner) | SendingSigner | Sì |
| crossmint | SendingSigner | Genera SIGNING_FAILED |
Restringi il tipo con isinstance. Tutte le classi di capacità sono esportate dalla radice
del pacchetto:
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)
Inizializzazione
Le factory create_* e create_keychain_signer eseguono per te qualsiasi configurazione
necessaria. Se costruisci direttamente la classe signer di Privy, Fireblocks, Dfns, Crossmint, Para, Openfort o
Utila, esegui await signer.init() prima dell'uso. Fino ad allora,
pubkey e ogni chiamata di firma generano SignerErrorCode.NOT_INITIALIZED.
Configurazione dei backend
Memory
Crea da un file keypair della Solana CLI, una stringa base58 o "[1,2,...]", byte
grezzi, oppure un Keypair di 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 chiave risiede nella memoria del processo e Python non può azzerarla. Usa questo backend per sviluppo e test.
HashiCorp Vault
La chiave deve essere una chiave ed25519 nel transit engine. HTTP semplice è accettato
solo per un indirizzo loopback, per 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 è un ID chiave, un ARN o un alias per una chiave ECC_NIST_EDWARDS25519. Passa un
client KMS di boto3 già costruito per credenziali o endpoint personalizzati; region viene
in tal caso ignorato:
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 versione della chiave deve essere una chiave EC_SIGN_ED25519. client accetta facoltativamente un
KeyManagementServiceAsyncClient già costruito:
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",))
I wallet protetti da una chiave di autorizzazione accettano un PrivyAuthorizationConfig come
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 l'operazione PROGRAM_CALL anziché RAW. Viene
inviata in modalità solo firma, quindi Fireblocks non riscrive né trasmette la
transazione, e la trasmetti tu come di consueto. Richiede un hot wallet e deve essere
abilitata da Fireblocks per il tuo 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 è solo di invio. La sua API esegue sempre lato server una transazione approvata
e sponsorizza il gas, quindi CrossmintSigner è un SendingSigner e
sign_message genera SIGNING_FAILED. La firma restituita identifica la
transazione che Crossmint ha mandato a buon fine, che può differire dalla tua. Impostando
signer_secret (un secret di delegated signer xmsk1_...) le transazioni vengono approvate
automaticamente, il che delega a Crossmint la scelta di cosa approvare.
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 firma solo transazioni; sign_message genera 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 ha tre modalità di firma, fissate alla costruzione da chain e
push_mode. create_fordefi_signer sceglie la classe, ogni classe rifiuta una
configurazione destinata a un'altra, e tutte e tre firmano messaggi:
| Modalità | Configurazione | Classe | Metodo |
|---|---|---|---|
| Black box | chain non impostato | FordefiBlackBoxSigner | sign_transaction |
| Native auto | chain impostato, push_mode non impostato o "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native manual | chain impostato, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
Le richieste API sono autenticate con una firma della richiesta ECDSA P-256: fornisci la
chiave come private_key_pem, oppure conservala in un KMS/HSM passando una
sottoclasse di FordefiRequestSigner come request_signer.
Modalità native auto: imposta chain ("solana_devnet" o "solana_mainnet"), e
Fordefi può sostituire blockhash e commissioni, quindi firma e trasmette
autonomamente la transazione. Accetta solo una transazione non firmata il cui unico
firmatario richiesto è il vault. fee viene passato a Fordefi così com'è:
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)
Modalità native manual: imposta chain e push_mode="manual". Fordefi riscrive
il messaggio (recent blockhash, istruzioni di commissione Compute Budget) e lo firma,
ma non lo trasmette. La transazione restituita sostituisce quella che hai inviato,
quindi prosegui sempre da result.transaction. Fordefi deve essere il fee payer e
deve firmare prima di qualsiasi altro firmatario richiesto: una transazione che contiene già
firme viene accettata, ma la riscrittura le invalida. La riscrittura non viene confrontata con
l'originale, quindi ispeziona il risultato prima di trasmettere e trasmetti tempestivamente, poiché Fordefi
non restituisce l'altezza di scadenza del nuovo 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
Trasmetti la transazione ispezionata come in Firma e invio e riconcilia prima di riprovare se l'invio fallisce.
Modalità black box: ometti chain. Fordefi firma i byte esatti del tuo messaggio e
non trasmette. Usala con un vault black box di 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(),))
Firma e invio
sign_and_send_transaction porta una transazione on-chain qualunque sia la forma del signer.
Un SendingSigner trasmette tramite il proprio provider e la tua funzione di invio
non viene mai chiamata. Un TransactionSigner o ModifyingSigner firma, e la tua funzione di invio
trasmette il risultato codificato in base64 (il pacchetto non include un client 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 signer di sola firma senza funzione di invio genera CONFIG_ERROR, e una
transazione che dopo la firma risulta ancora priva di firme genera SIGNING_FAILED.
Gestione degli errori
Ogni errore è un SignerError con un code stabile da 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 e SIGNING_FAILED. Confronta il code, non il messaggio:
str() e repr() riportano solo un messaggio generico fisso, mai materiale di chiave o
risposte grezze del provider. status_code e provider_error_code sono impostati quando
l'errore proviene da una risposta del provider.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") significa che
la transazione potrebbe essere andata a buon fine:
- Su Crossmint e Fordefi native auto,
provider_transaction_idè impostato quando la creazione è stata accettata, eidempotency_keycontiene la chiave derivata dal messaggio con cui è partita la creazione. Reinviare la transazione identica byte per byte è sicuro; una ricostruita (ad esempio con un nuovo blockhash) è un nuovo trasferimento. - Da
sign_and_send_transaction, una funzione di invio che fallisce lo genera contransaction_signatureimpostato sulla firma della transazione completata. - Fireblocks lo genera solo se un polling mostra che Fireblocks ha trasmesso una
transazione
use_program_callnonostante fosse stata inviata in modalità 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 chiamata annullata rilancia asyncio.CancelledError, che non contiene campi. Per
recuperare un id che il provider ha accettato prima dell'annullamento, passa un
PendingTransactionId() (da solana_keychain.core) come pending_transaction_id della configurazione native auto di Crossmint o
Fordefi, e leggi .get() dopo
l'annullamento. Riconcilia con il provider prima di riprovare.
Keychain convalida il passaggio di firma, non ciò che fa la transazione: una firma restituita dal provider viene verificata con ed25519 rispetto al messaggio calcolato localmente prima di essere allegata. Python non può azzerare i buffer delle chiavi, quindi con i backend a chiave locale (Memory, e le chiavi API e i PEM contenuti nelle configurazioni dei backend remoti) considera sensibile l'intera memoria del processo. Consulta il modello di sicurezza per le garanzie offerte da ciascuna forma di firma.
Aggiungere signer personalizzati
Crea una sottoclasse della classe di capacità corrispondente al tuo backend (TransactionSigner,
ModifyingSigner o SendingSigner) da solana_keychain.core. Consulta la
guida all'aggiunta dei Signer per integrare
ulteriori servizi di gestione delle chiavi.
Risorse
Is this page helpful?