Wersja 2.0 to pierwsze stabilne wydanie pakietu dla Pythona. Przewodnik migracji do wersji 2.0 omawia schematy podpisywania 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]'
Wymagany jest Python 3.10 lub nowszy. Transakcje to obiekty
solders VersionedTransaction;
akceptowane są wiadomości 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
importuje się z własnego podmodułu (na przykład solana_keychain.aws_kms), a
import bez odpowiedniego dodatku 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 gotowy do użycia signer. 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 wiarygodną,
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 został przekazany.
Ujednolicona fabryka
create_keychain_signer przyjmuje nazwę backendu oraz dataclass konfiguracji
tego backendu i importuje backend leniwie, więc instalować trzeba tylko te dodatki,
których używasz:
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 SolanaSigner (klasa bazowa), więc przed podpisaniem zawęź go za pomocą
isinstance (zob. Możliwości signerów).
Zdalni klienci HTTP
Zdalne backendy HTTP akceptują opcjonalny http_client (httpx.AsyncClient)
do niestandardowego TLS lub serwerów proxy. Gdy nie jest ustawiony, każde żądanie używa jednorazowego klienta
z limitem czasu 60 sekund, wymuszonym HTTPS w bazowym adresie URL oraz odrzucanymi
przekierowaniami.
Możliwości signerów
Każdy backend dziedziczy po abstrakcyjnej klasie bazowej SolanaSigner (pubkey, sign_message,
is_available) oraz po dokładnie jednej klasie możliwości:
TransactionSigner:sign_transaction(tx)podpisuje Twoją transakcję w miejscu i zwracaSignedTransaction. Wysyłasz ją 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ę przez dostawcę oraz zwracaSignaturetransakcji, która trafiła do sieci. 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 ładunki 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ą całą wymaganą konfigurację
za Ciebie. 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 jej wyzerować. Używaj tego backendu do programowania i testowania.
HashiCorp Vault
Kluczem musi być klucz ed25519 w silniku transit. Zwykły HTTP jest akceptowany
tylko dla adresu 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 client boto3 KMS, 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. Transakcja
jest wysyłana w trybie tylko do podpisu, więc Fireblocks ani jej nie przepisuje, ani nie wysyła,
a Ty wysyłasz ją jak zwykle. Wymaga to portfela hot i musi zostać
włączone dla Twojej przestrzeni roboczej 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 wysłał do sieci i która 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 tworzenia 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 albo 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, po czym 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ść (najnowszy blockhash, instrukcje opłat Compute Budget) i podpisuje ją,
ale jej nie wysyła. Zwrócona transakcja zastępuje tę, którą przesłano,
dlatego zawsze kontynuuj od result.transaction. Fordefi musi być płatnikiem opłat
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 szybko, ponieważ Fordefi
nie zwraca wysokości wygaśnięcia nowego blockhasha.
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ą uzgodnij jej stan.
Tryb black box: pomiń chain. Fordefi podpisuje dokładnie te bajty wiadomości, które mu 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 umieszcza transakcję w łańcuchu 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 ma 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 jest wyjątkiem SignerError ze stabilnym kodem 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ą wyłącznie stały, ogólny komunikat, nigdy materiał kluczowy 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 sieci:
- W Crossmint i Fordefi native auto
provider_transaction_idjest ustawiany, gdy utworzenie zostało zaakceptowane, aidempotency_keyzawiera klucz wyprowadzony z wiadomości, pod którym wysłano żądanie utworzenia. Ponowne wysłanie identycznej bajt po bajcie transakcji jest bezpieczne; przebudowana (na przykład z nowym blockhashem) jest nowym transferem. - W
sign_and_send_transactionbłąd funkcji wysyłającej zgłasza go ztransaction_signatureustawionym na podpis ukończonej transakcji. - Fireblocks zgłasza go tylko wtedy, gdy sprawdzenie stanu pokaże, że Fireblocks wysłało
transakcję
use_program_call, mimo że została przesłana w trybie tylko do podpisu.
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óre 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 jako ed25519 względem lokalnie obliczonej wiadomości, zanim zostanie dołączony. Python nie może zerować buforów kluczy, więc przy backendach z lokalnym kluczem (Memory oraz klucze API i 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. Zobacz
poradnik Dodawanie sygnatariuszy, aby zintegrować
dodatkowe usługi zarządzania kluczami.
Zasoby
Is this page helpful?