Version 2.0 ist das erste stabile Release des Python-Pakets. Der Migrationsleitfaden für 2.0 behandelt die Signing-Formen, die es mit Rust und TypeScript gemeinsam hat.
Installation
Installiere das Basispaket sowie ein Extra für jedes Backend, das eine Provider-Abhängigkeit benötigt:
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]'
Erfordert Python 3.10 oder höher. Transaktionen sind
solders-VersionedTransaction-Objekte;
Legacy-, v0- und v1-Nachrichten werden alle akzeptiert, außer von Fireblocks mit
use_program_call=True, das nur Legacy und v0 akzeptiert.
Memory, Vault und Para lassen sich aus dem Paket-Root importieren. Jedes andere Backend
wird aus seinem eigenen Submodul importiert (zum Beispiel solana_keychain.aws_kms), und
der Import ohne das zugehörige Extra löst einen ImportError aus, der das zu installierende Extra nennt. Ledger gibt es nur für Rust.
Grundlegende Verwendung
Jedes Backend hat eine Config-Dataclass und eine asynchrone create_<backend>_signer-Factory, die einen sofort einsatzbereiten Signer zurückgibt. Alle Signing-Methoden sind Coroutinen:
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 gibt eine SignedTransaction mit encoded_transaction,
signature, is_complete und transaction zurück. transaction ist die maßgebliche
signierte Transaktion: Arbeite mit ihr weiter, besonders bei einem modifizierenden Signer, dessen
Ergebnis ein anderes Objekt ist als das, das du übergeben hast.
Einheitliche Factory
create_keychain_signer nimmt einen Backend-Namen und die Config-Dataclass dieses Backends entgegen
und importiert das Backend per Lazy Loading, sodass nur die Extras installiert sein müssen, die du verwendest:
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",),)
Die Namen sind in SUPPORTED_BACKENDS aufgelistet: aws-kms, cdp, crossmint,
dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para,
privy, turnkey, utila und vault. Ein unbekannter Name oder eine Config der
falschen Klasse löst einen SignerError mit SignerErrorCode.CONFIG_ERROR aus. Die
Factory ist als Rückgabe des Basistyps SolanaSigner typisiert, daher engst du ihn vor dem Signieren mit
isinstance ein (siehe Signer-Fähigkeiten).
Remote-HTTP-Clients
Remote-HTTP-Backends akzeptieren einen optionalen http_client (einen httpx.AsyncClient)
für benutzerdefiniertes TLS oder Proxys. Ist er nicht gesetzt, verwendet jede Anfrage einen einmaligen Client
mit 60 Sekunden Timeout, erzwungenem HTTPS für die Basis-URL und abgelehnten
Weiterleitungen.
Signer-Fähigkeiten
Jedes Backend erbt von der ABC SolanaSigner (pubkey, sign_message,
is_available) sowie von genau einer Capability-Klasse:
TransactionSigner:sign_transaction(tx)signiert deine Transaktion direkt und gibt eineSignedTransactionzurück. Du sendest sie selbst.ModifyingSigner:modify_and_sign_transaction(tx)lässt den Provider die Transaktion umschreiben und signiert sie anschließend, ohne sie zu senden. Dein Objekt bleibt unverändert; arbeite mit der zurückgegebenenSignedTransaction.transactionweiter.SendingSigner:sign_and_send_transaction(tx)signiert und sendet über den Provider und gibt dieSignatureder Transaktion zurück, die tatsächlich bestätigt wurde. Deine Transaktion wird nie verändert.
| Backend | Capability-Klasse | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Ja |
| cdp | TransactionSigner | Nur UTF-8-Payloads, sonst SERIALIZATION_ERROR |
| utila | TransactionSigner | Löst SIGNING_FAILED aus |
fordefi Black Box (FordefiBlackBoxSigner) | TransactionSigner | Ja |
fordefi Native Manual (FordefiNativeManualSigner) | ModifyingSigner | Ja |
fordefi Native Auto (FordefiNativeAutoSigner) | SendingSigner | Ja |
| crossmint | SendingSigner | Löst SIGNING_FAILED aus |
Mit isinstance einengen. Alle Capability-Klassen werden aus dem Paket-Root
exportiert:
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)
Initialisierung
Die create_*-Factories und create_keychain_signer übernehmen die erforderliche Einrichtung
für dich. Wenn du die Signer-Klasse für Privy, Fireblocks, Dfns, Crossmint, Para, Openfort oder
Utila direkt instanziierst, rufe vor der Verwendung await signer.init() auf. Bis dahin lösen
pubkey und jeder Signing-Aufruf SignerErrorCode.NOT_INITIALIZED aus.
Backend-Konfiguration
Memory
Erstellen aus einer Solana-CLI-keypair-Datei, einem base58- oder "[1,2,...]"-String, rohen
Bytes oder einem solders-Keypair:
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()))
Der Schlüssel liegt im Prozessspeicher, und Python kann ihn nicht mit Nullen überschreiben. Verwende dieses Backend für Entwicklung und Tests.
HashiCorp Vault
Der Schlüssel muss ein ed25519-Schlüssel in der Transit-Engine sein. Einfaches HTTP wird
nur für eine Loopback-Adresse akzeptiert, für 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 ist eine Key-ID, ein ARN oder ein Alias für einen ECC_NIST_EDWARDS25519-Schlüssel. Übergib einen
vorgefertigten boto3-KMS-client für benutzerdefinierte Anmeldedaten oder Endpunkte; region wird
dann ignoriert:
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
Die Schlüsselversion muss ein EC_SIGN_ED25519-Schlüssel sein. client nimmt optional einen
vorgefertigten KeyManagementServiceAsyncClient entgegen:
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",))
Wallets, die durch einen Autorisierungsschlüssel geschützt sind, erwarten eine PrivyAuthorizationConfig als
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 signiert mit der Operation PROGRAM_CALL statt RAW. Sie
wird nur zum Signieren gesendet, sodass Fireblocks die Transaktion weder umschreibt noch sendet
und du sie wie gewohnt sendest. Sie erfordert eine Hot Wallet und muss
von Fireblocks für deinen Workspace aktiviert werden.
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 unterstützt nur das Senden. Die API führt eine genehmigte Transaktion immer
serverseitig aus und übernimmt das Gas, daher ist CrossmintSigner ein SendingSigner und
sign_message löst SIGNING_FAILED aus. Die zurückgegebene Signatur identifiziert die
von Crossmint bestätigte Transaktion, die von deiner abweichen kann. Wenn du
signer_secret (ein delegiertes Signer-Secret im Format xmsk1_...) setzt, werden Transaktionen
automatisch genehmigt, wodurch die Entscheidung, was genehmigt wird, an Crossmint delegiert wird.
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 signiert nur Transaktionen; sign_message löst SIGNING_FAILED aus.
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 hat drei Signing-Modi, die bei der Erstellung durch chain und
push_mode festgelegt werden. create_fordefi_signer wählt die Klasse aus, jede Klasse lehnt eine
für eine andere Klasse gedachte Config ab, und alle drei signieren Nachrichten:
| Modus | Konfiguration | Klasse | Methode |
|---|---|---|---|
| Black Box | chain nicht gesetzt | FordefiBlackBoxSigner | sign_transaction |
| Native Auto | chain gesetzt, push_mode nicht gesetzt oder "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native Manual | chain gesetzt, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
API-Anfragen werden mit einer ECDSA-P-256-Request-Signatur authentifiziert: Übergib den
Schlüssel als private_key_pem oder halte ihn in einem KMS/HSM, indem du eine
FordefiRequestSigner-Unterklasse als request_signer übergibst.
Native-Auto-Modus: Setze chain ("solana_devnet" oder "solana_mainnet"), und
Fordefi kann Blockhash und Fee ersetzen, signiert und sendet dann die
Transaktion selbst. Akzeptiert wird nur eine unsignierte Transaktion, deren einziger erforderlicher
Signer der Vault ist. fee wird unverändert an Fordefi weitergegeben:
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)
Native-Manual-Modus: Setze chain und push_mode="manual". Fordefi schreibt
die Nachricht um (aktueller Blockhash, Anweisungen für das Compute Budget zur Fee) und signiert sie,
sendet sie aber nicht. Die zurückgegebene Transaktion ersetzt die von dir übermittelte,
daher arbeite immer mit result.transaction weiter. Fordefi muss der Fee-Payer sein und
vor allen anderen erforderlichen Signern signieren: Eine Transaktion, die bereits
Signaturen enthält, wird akzeptiert, doch das Umschreiben macht sie ungültig. Das Umschreiben wird nicht als Diff dargestellt,
prüfe also das Ergebnis vor dem Senden und sende zügig, da Fordefi
die Ablaufhöhe des neuen Blockhashs nicht zurückgibt.
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
Sende die geprüfte Transaktion wie unter Signieren und Senden beschrieben und führe bei einem fehlgeschlagenen Senden einen Abgleich durch, bevor du es erneut versuchst.
Black-Box-Modus: Lasse chain weg. Fordefi signiert deine exakten Nachrichten-Bytes und
sendet nicht. Verwende dies mit einem Fordefi-Black-Box-Vault.
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(),))
Signieren und Senden
sign_and_send_transaction bringt eine Transaktion unabhängig von der Form des Signers on-chain. Ein SendingSigner sendet über seinen Provider, und deine Send-Funktion
wird nie aufgerufen. Ein TransactionSigner oder ModifyingSigner signiert, und deine Send-
Funktion sendet das Base64-kodierte Ergebnis (das Paket hat keinen RPC-Client):
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)
Ein reiner Signing-Signer ohne Send-Funktion löst CONFIG_ERROR aus, und eine
Transaktion, der nach dem Signieren noch Signaturen fehlen, löst SIGNING_FAILED aus.
Fehlerbehandlung
Jeder Fehler ist ein SignerError mit einem stabilen code aus 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 und SIGNING_FAILED. Werte code aus, nicht die Nachricht:
str() und repr() enthalten nur eine feste generische Nachricht, niemals Schlüsselmaterial oder
rohe Provider-Antworten. status_code und provider_error_code werden gesetzt, wenn der
Fehler aus einer Provider-Antwort stammt.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") bedeutet,
dass die Transaktion möglicherweise bestätigt wurde:
- Bei Crossmint und Fordefi Native Auto wird
provider_transaction_idgesetzt, wenn die Erstellung akzeptiert wurde, undidempotency_keyenthält den aus der Nachricht abgeleiteten Schlüssel, unter dem die Erstellung gesendet wurde. Das erneute Senden der byte-identischen Transaktion ist sicher; eine neu aufgebaute (zum Beispiel mit einem neuen Blockhash) ist eine neue Überweisung. - Bei
sign_and_send_transactionlöst eine fehlschlagende Send-Funktion ihn aus, wobeitransaction_signatureauf die Signatur der abgeschlossenen Transaktion gesetzt ist. - Fireblocks löst ihn nur aus, wenn eine Abfrage zeigt, dass Fireblocks eine
use_program_call-Transaktion gesendet hat, obwohl sie nur zum Signieren gesendet wurde.
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
Ein abgebrochener Aufruf löst erneut asyncio.CancelledError aus, der keine Felder enthält. Um
eine vom Provider vor dem Abbruch akzeptierte ID wiederherzustellen, übergib ein
PendingTransactionId() (aus solana_keychain.core) als pending_transaction_id der Crossmint- oder
Fordefi-Native-Auto-Config und lies nach dem Abbruch .get() aus. Gleiche den Stand mit dem Provider ab, bevor du es erneut versuchst.
Keychain validiert den Signing-Schritt, nicht was die Transaktion bewirkt: Eine vom Provider zurückgegebene Signatur wird gegen die lokal berechnete Nachricht per ed25519 verifiziert, bevor sie angehängt wird. Python kann Schlüsselpuffer nicht mit Nullen überschreiben, daher gilt bei Backends mit lokalem Schlüssel (Memory sowie die API-Schlüssel und PEMs in den Remote-Backend- Configs) der gesamte Prozessspeicher als sensibel. Im Sicherheitsmodell findest du, was jede Signing-Form garantiert.
Benutzerdefinierte Signer hinzufügen
Leite von der einen Capability-Klasse, die zu deinem Backend passt (TransactionSigner,
ModifyingSigner oder SendingSigner), aus solana_keychain.core eine Unterklasse ab. Weitere Informationen zur Integration zusätzlicher Key-Management-Dienste findest du in der
Anleitung zum Hinzufügen von Signer.
Ressourcen
Is this page helpful?