Python

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 para
pip install 'solana-keychain[aws-kms]' # AWS KMS (boto3)
pip install 'solana-keychain[gcp-kms]' # GCP KMS (google-cloud-kms)
pip install 'solana-keychain[privy]' # Privy
pip install 'solana-keychain[turnkey]' # Turnkey
pip install 'solana-keychain[fireblocks]' # Fireblocks
pip install 'solana-keychain[cdp]' # Coinbase Developer Platform
pip install 'solana-keychain[crossmint]' # Crossmint
pip install 'solana-keychain[dfns]' # Dfns
pip install 'solana-keychain[openfort]' # Openfort
pip install 'solana-keychain[utila]' # Utila
pip install 'solana-keychain[fordefi]' # Fordefi
# Several at once
pip 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 asyncio
from solana_keychain import MemorySigner
from solders.transaction import VersionedTransaction
async 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_signer
from solana_keychain.privy import PrivySignerConfig
signer = 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 zwraca SignedTransaction. 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óconego SignedTransaction.transaction.
  • SendingSigner: sign_and_send_transaction(tx) podpisuje i wysyła transakcję przez dostawcę oraz zwraca Signature transakcji, która trafiła do sieci. Twoja transakcja nigdy nie jest modyfikowana.
BackendKlasa możliwościsign_message
memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfortTransactionSignerTak
cdpTransactionSignerTylko ładunki UTF-8, w przeciwnym razie SERIALIZATION_ERROR
utilaTransactionSignerZgłasza SIGNING_FAILED
fordefi black box (FordefiBlackBoxSigner)TransactionSignerTak
fordefi native manual (FordefiNativeManualSigner)ModifyingSignerTak
fordefi native auto (FordefiNativeAutoSigner)SendingSignerTak
crossmintSendingSignerZgł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, TransactionSigner
if 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_signer
from solders.keypair import Keypair
signer = 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 bytes
signer = 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_signer
signer = 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_signer
signer = 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_signer
signer = 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_signer
signer = 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_signer
signer = await create_turnkey_signer(
TurnkeySignerConfig(
api_public_key="api_public_key", # hex P-256
api_private_key="api_private_key", # hex P-256
organization_id="org_id",
private_key_id="private_key_id",
public_key="base58_public_key",
)
)

Fireblocks

from solana_keychain.fireblocks import FireblocksSignerConfig, create_fireblocks_signer
signer = 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_signer
signer = 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_signer
signer = 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_signer
signer = 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_signer
signer = await create_openfort_signer(
OpenfortSignerConfig(
secret_key="sk_...",
account_id="acc_...",
wallet_secret="wallet_secret",
)
)

Para

from solana_keychain import ParaSignerConfig, create_para_signer
signer = 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_signer
signer = 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:

TrybKonfiguracjaKlasaMetoda
Black boxchain nieustawioneFordefiBlackBoxSignersign_transaction
Native autochain ustawione, push_mode nieustawione lub "auto"FordefiNativeAutoSignersign_and_send_transaction
Native manualchain ustawione, push_mode="manual"FordefiNativeManualSignermodify_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 os
from solana_keychain.fordefi import FordefiSignerConfig, create_fordefi_signer
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"], # 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 httpx
from solana_keychain import sign_and_send_transaction
from solders.signature import Signature
async 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_id jest ustawiany, gdy utworzenie zostało zaakceptowane, a idempotency_key zawiera 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_transaction błąd funkcji wysyłającej zgłasza go z transaction_signature ustawionym 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, SignerErrorCode
try:
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?