Wersja 2.0 jest pierwszym stabilnym wydaniem pakietu Pythona. Przewodnik migracji do wersji 2.0 opisuje sposoby podpisywania, które pakiet ma wspólne z Rustem i TypeScriptem.
Instalacja
Zainstaluj pakiet bazowy oraz po jednym dodatku (extra) dla każdego backendu, który wymaga zależności od dostawcy:
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]'
Wymaga Pythona 3.10 lub nowszego. Transakcje to obiekty solders VersionedTransaction; akceptowane są wszystkie komunikaty: legacy, v0 i v1, z wyjątkiem Fireblocks z use_program_call=True, które akceptuje wyłącznie legacy i v0.
Memory, Vault i Para można importować z katalogu głównego pakietu. Każdy inny backend jest importowany z własnego podmodułu (na przykład solana_keychain.aws_kms), a import bez odpowiedniego dodatku (extra) zgłasza ImportError ze wskazaniem dodatku, który należy zainstalować. Ledger jest dostępny wyłącznie w Rust.
Podstawowe użycie
Każdy backend ma dataclass konfiguracji oraz asynchroniczną fabrykę create_<backend>_signer, która zwraca gotowego do użycia signera. Wszystkie metody podpisywania są korutynami:
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 zwraca SignedTransaction z polami encoded_transaction, signature, is_complete i transaction. transaction jest autorytatywną podpisaną transakcją: to od niej należy kontynuować, zwłaszcza w przypadku signera modyfikującego, którego wynik jest innym obiektem niż ten, który przekazano.
Ujednolicona fabryka
create_keychain_signer przyjmuje nazwę backendu oraz dataclass konfiguracji tego backendu i importuje backend leniwie (lazy), więc wystarczy zainstalować tylko te dodatki (extras), z których korzystasz:
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",),)
Nazwy są wymienione w SUPPORTED_BACKENDS: aws-kms, cdp, crossmint, dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para, privy, turnkey, utila i vault. Nieznana nazwa lub konfiguracja niewłaściwej klasy zgłasza SignerError z kodem SignerErrorCode.CONFIG_ERROR. Fabryka ma typ zwracany bazowy SolanaSigner, więc przed podpisaniem zawęź go za pomocą isinstance (zobacz Możliwości signera).
Zdalni klienci HTTP
Zdalne backendy HTTP akceptują opcjonalny parametr http_client (httpx.AsyncClient) na potrzeby niestandardowego TLS lub proxy. Gdy nie jest ustawiony, każde żądanie używa jednorazowego klienta z limitem czasu 60 sekund, wymuszonym HTTPS dla bazowego adresu URL i odrzucanymi przekierowaniami.
Możliwości signera
Każdy backend dziedziczy po abstrakcyjnej klasie bazowej SolanaSigner (pubkey, sign_message, is_available) oraz dokładnie jednej klasie możliwości:
TransactionSigner:sign_transaction(tx)podpisuje transakcję w miejscu i zwracaSignedTransaction. Transakcję wysyłasz samodzielnie.ModifyingSigner:modify_and_sign_transaction(tx)pozwala dostawcy przepisać transakcję, a następnie podpisuje ją bez wysyłania. Twój obiekt pozostaje nietknięty; kontynuuj od zwróconegoSignedTransaction.transaction.SendingSigner:sign_and_send_transaction(tx)podpisuje i wysyła transakcję za pośrednictwem dostawcy, a następnie zwracaSignaturetransakcji, która trafiła do łańcucha. Twoja transakcja nigdy nie jest modyfikowana.
| Backend | Klasa możliwości | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Tak |
| cdp | TransactionSigner | Tylko dane w UTF-8, w przeciwnym razie SERIALIZATION_ERROR |
| utila | TransactionSigner | Zgłasza SIGNING_FAILED |
fordefi black box (FordefiBlackBoxSigner) | TransactionSigner | Tak |
fordefi native manual (FordefiNativeManualSigner) | ModifyingSigner | Tak |
fordefi native auto (FordefiNativeAutoSigner) | SendingSigner | Tak |
| crossmint | SendingSigner | Zgłasza SIGNING_FAILED |
Zawężaj za pomocą isinstance. Wszystkie klasy możliwości są eksportowane z katalogu głównego pakietu:
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)
Inicjalizacja
Fabryki create_* oraz create_keychain_signer wykonują za Ciebie wszelką wymaganą konfigurację. Jeśli klasę signera Privy, Fireblocks, Dfns, Crossmint, Para, Openfort lub Utila tworzysz bezpośrednio, wywołaj await signer.init() przed użyciem. Do tego czasu pubkey i każde wywołanie podpisywania zgłaszają SignerErrorCode.NOT_INITIALIZED.
Konfiguracja backendów
Memory
Utwórz z pliku keypair Solana CLI, ciągu base58 lub "[1,2,...]", surowych bajtów albo obiektu Keypair z 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()))
Klucz znajduje się w pamięci procesu, a Python nie może go wyzerować. Używaj tego backendu do programowania i testowania.
HashiCorp Vault
Klucz musi być kluczem ed25519 w silniku transit. Zwykły HTTP jest akceptowany wyłącznie dla adresu pętli zwrotnej (loopback), na potrzeby 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 to identyfikator klucza, ARN lub alias klucza ECC_NIST_EDWARDS25519. Przekaż gotowego klienta boto3 KMS (client), aby użyć niestandardowych poświadczeń lub punktów końcowych; region jest wtedy ignorowany:
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
Wersja klucza musi być kluczem EC_SIGN_ED25519. client opcjonalnie przyjmuje gotowy KeyManagementServiceAsyncClient:
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",))
Portfele chronione kluczem autoryzacji przyjmują PrivyAuthorizationConfig jako 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 podpisuje operacją PROGRAM_CALL zamiast RAW. Jest wysyłane w trybie wyłącznie do podpisu (sign-only), więc Fireblocks ani nie przepisuje, ani nie wysyła transakcji, a Ty wysyłasz ją jak zwykle. Wymaga portfela hot i musi zostać włączone dla Twojego workspace'u przez Fireblocks.
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 działa wyłącznie w trybie wysyłania. Jego API zawsze wykonuje zatwierdzoną transakcję po stronie serwera i pokrywa opłaty gas, więc CrossmintSigner jest SendingSigner, a sign_message zgłasza SIGNING_FAILED. Zwrócony podpis identyfikuje transakcję, którą Crossmint przesłał do łańcucha, a ta może różnić się od Twojej. Ustawienie signer_secret (sekretu delegowanego signera xmsk1_...) powoduje automatyczne zatwierdzanie transakcji, co deleguje na Crossmint decyzję o tym, co zostanie zatwierdzone.
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 tylko podpisuje transakcje; sign_message zgłasza 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 ma trzy tryby podpisywania, ustalane podczas konstrukcji przez chain i push_mode. create_fordefi_signer wybiera klasę, każda klasa odrzuca konfigurację przeznaczoną dla innej, a wszystkie trzy podpisują wiadomości:
| Tryb | Konfiguracja | Klasa | Metoda |
|---|---|---|---|
| Black box | chain nieustawione | FordefiBlackBoxSigner | sign_transaction |
| Native auto | chain ustawione, push_mode nieustawione lub "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native manual | chain ustawione, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
Żądania API są uwierzytelniane podpisem żądania ECDSA P-256: podaj klucz jako private_key_pem lub przechowuj go w KMS/HSM, przekazując podklasę FordefiRequestSigner jako request_signer.
Tryb native auto: ustaw chain ("solana_devnet" lub "solana_mainnet"), a Fordefi może zastąpić blockhash i opłaty, a następnie samodzielnie podpisuje i wysyła transakcję. Akceptuje wyłącznie niepodpisaną transakcję, w której jedynym wymaganym podpisującym jest vault. fee jest przekazywane do Fordefi bez zmian:
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)
Tryb native manual: ustaw chain i push_mode="manual". Fordefi przepisuje wiadomość (recent blockhash, instrukcje opłat Compute Budget) i podpisuje ją, ale nie wysyła. Zwrócona transakcja zastępuje tę, którą przesłano, więc zawsze kontynuuj od result.transaction. Fordefi musi być płatnikiem opłaty (fee payer) i musi podpisać przed każdym innym wymaganym podpisującym: transakcja, która już zawiera podpisy, jest akceptowana, ale przepisanie je unieważnia. Przepisanie nie jest porównywane (diff), więc sprawdź wynik przed wysłaniem i wyślij go niezwłocznie, ponieważ Fordefi nie zwraca wysokości, przy której wygasa nowy 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
Wyślij sprawdzoną transakcję tak, jak w sekcji Podpisywanie i wysyłanie, a w razie niepowodzenia wysyłki przed ponowną próbą dokonaj uzgodnienia stanu.
Tryb black box: pomiń chain. Fordefi podpisuje dokładnie te bajty wiadomości, które przekażesz, i nie wysyła ich. Używaj go z vaultem Fordefi black box.
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(),))
Podpisywanie i wysyłanie
sign_and_send_transaction doprowadza transakcję do łańcucha niezależnie od rodzaju signera. SendingSigner wysyła ją przez swojego dostawcę, a Twoja funkcja wysyłająca nigdy nie jest wywoływana. TransactionSigner lub ModifyingSigner podpisuje, a Twoja funkcja wysyłająca przesyła wynik zakodowany w base64 (pakiet nie zawiera klienta 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)
Signer wyłącznie podpisujący, bez funkcji wysyłającej, zgłasza CONFIG_ERROR, a transakcja, której po podpisaniu nadal brakuje podpisów, zgłasza SIGNING_FAILED.
Obsługa błędów
Każdy błąd to SignerError ze stabilnym code z 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 i SIGNING_FAILED. Dopasowuj według code, a nie komunikatu: str() i repr() zawierają tylko stały, ogólny komunikat, nigdy materiał klucza ani surowe odpowiedzi dostawcy. status_code i provider_error_code są ustawiane, gdy błąd pochodził z odpowiedzi dostawcy.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") oznacza, że transakcja mogła trafić do łańcucha:
- W Crossmint i Fordefi native auto
provider_transaction_idjest ustawiane, gdy utworzenie zostało zaakceptowane, aidempotency_keyzawiera klucz wyprowadzony z wiadomości, pod którym wysłano żądanie utworzenia. Ponowne wysłanie identycznej bajt w bajt transakcji jest bezpieczne; przebudowana (na przykład z nowym blockhash) jest nowym transferem. - Z
sign_and_send_transaction, gdy funkcja wysyłająca zawiedzie, zgłasza go ztransaction_signatureustawionym na podpis ukończonej transakcji. - Fireblocks zgłasza go tylko wtedy, gdy sprawdzenie stanu (poll) wykaże, że Fireblocks wysłało transakcję
use_program_call, mimo że została przesłana w trybie sign-only.
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
Anulowane wywołanie ponownie zgłasza asyncio.CancelledError, który nie niesie żadnych pól. Aby odzyskać identyfikator zaakceptowany przez dostawcę przed anulowaniem, przekaż PendingTransactionId() (z solana_keychain.core) jako pending_transaction_id w konfiguracji Crossmint lub Fordefi native auto i odczytaj .get() po anulowaniu. Przed ponowną próbą uzgodnij stan z dostawcą.
Keychain weryfikuje etap podpisywania, a nie to, co robi transakcja: podpis zwrócony przez dostawcę jest weryfikowany (ed25519) względem lokalnie obliczonej wiadomości, zanim zostanie dołączony. Python nie może wyzerować buforów kluczy, więc w przypadku backendów z kluczem lokalnym (Memory oraz klucze API i pliki PEM przechowywane w konfiguracjach zdalnych backendów) traktuj całą pamięć procesu jako wrażliwą. Zobacz model bezpieczeństwa, aby dowiedzieć się, co gwarantuje każdy rodzaj podpisywania.
Dodawanie własnych signerów
Utwórz podklasę jednej klasy możliwości, która odpowiada Twojemu backendowi (TransactionSigner,
ModifyingSigner lub SendingSigner), z modułu solana_keychain.core. Zapoznaj się z
przewodnikiem po dodawaniu sygnatariuszy, aby zintegrować
dodatkowe usługi zarządzania kluczami.
Zasoby
Is this page helpful?