Версия 2.0 — первый стабильный выпуск пакета для Python. В руководстве по миграции на 2.0 описаны схемы подписания, общие с Rust и TypeScript.
Установка
Установите базовый пакет и по одному extra для каждого бэкенда, которому нужна зависимость от провайдера:
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]'
Требуется Python 3.10 или новее. Транзакции — это объекты
solders VersionedTransaction;
принимаются сообщения legacy, v0 и v1, кроме Fireblocks с
use_program_call=True: он принимает только legacy и v0.
Memory, Vault и Para можно импортировать из корня пакета. Все остальные бэкенды
импортируются из собственных подмодулей (например, solana_keychain.aws_kms), а
попытка импортировать бэкенд без его extra вызывает ImportError с названием
extra, который нужно установить. Ledger доступен только в Rust.
Базовое использование
У каждого бэкенда есть dataclass конфигурации и асинхронная фабрика
create_<backend>_signer, которая возвращает готовый к работе signer. Все методы подписания — корутины:
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 возвращает SignedTransaction с полями encoded_transaction,
signature, is_complete и transaction. transaction — это авторитетная
подписанная транзакция: продолжайте работу именно с ней, особенно при использовании модифицирующего signer, результат которого — другой объект, а не тот, что вы передали.
Единая фабрика
create_keychain_signer принимает название бэкенда и dataclass конфигурации
этого бэкенда и импортирует бэкенд лениво, поэтому устанавливать нужно только
те extras, которые вы используете:
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",),)
Названия перечислены в SUPPORTED_BACKENDS: aws-kms, cdp, crossmint,
dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para,
privy, turnkey, utila и vault. Неизвестное название или конфигурация
не того класса вызывает SignerError с кодом SignerErrorCode.CONFIG_ERROR.
Фабрика типизирована как возвращающая базовый SolanaSigner, поэтому перед подписанием сузьте тип с помощью
isinstance (см. Возможности signer).
Удалённые HTTP-клиенты
Удалённые HTTP-бэкенды принимают необязательный http_client (httpx.AsyncClient)
для настройки TLS или прокси. Если он не задан, каждый запрос использует одноразовый клиент
с тайм-аутом 60 секунд, обязательным HTTPS для базового URL и отклонением
перенаправлений.
Возможности signer
Каждый бэкенд наследует абстрактный класс SolanaSigner (pubkey, sign_message,
is_available) и ровно один класс возможностей:
TransactionSigner:sign_transaction(tx)подписывает вашу транзакцию на месте и возвращаетSignedTransaction. Отправляете её вы.ModifyingSigner:modify_and_sign_transaction(tx)позволяет провайдеру переписать транзакцию, затем подписывает её без отправки. Ваш объект остаётся нетронутым; продолжайте работу с возвращённымSignedTransaction.transaction.SendingSigner:sign_and_send_transaction(tx)подписывает и отправляет транзакцию через провайдера и возвращаетSignatureтранзакции, которая попала в сеть. Ваша транзакция никогда не изменяется.
| Бэкенд | Класс возможностей | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Да |
| cdp | TransactionSigner | Только данные в UTF-8, иначе SERIALIZATION_ERROR |
| utila | TransactionSigner | Вызывает SIGNING_FAILED |
fordefi black box (FordefiBlackBoxSigner) | TransactionSigner | Да |
fordefi native manual (FordefiNativeManualSigner) | ModifyingSigner | Да |
fordefi native auto (FordefiNativeAutoSigner) | SendingSigner | Да |
| crossmint | SendingSigner | Вызывает SIGNING_FAILED |
Сужайте тип с помощью isinstance. Все классы возможностей экспортируются из корня
пакета:
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)
Инициализация
Фабрики create_* и create_keychain_signer выполняют всю необходимую настройку
за вас. Если вы создаёте класс signer для Privy, Fireblocks, Dfns, Crossmint, Para, Openfort или
Utila напрямую, перед использованием вызовите await signer.init(). До этого
pubkey и любой вызов подписания вызывают SignerErrorCode.NOT_INITIALIZED.
Конфигурация бэкендов
Memory
Создавайте из файла keypair Solana CLI, строки в base58 или "[1,2,...]", «сырых»
байтов или Keypair из 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()))
Ключ хранится в памяти процесса, а Python не может его обнулить. Используйте этот бэкенд для разработки и тестирования.
HashiCorp Vault
Ключ должен быть ключом ed25519 в transit-движке. Обычный HTTP допускается
только для loopback-адреса, для 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 — это ID ключа, ARN или алиас ключа ECC_NIST_EDWARDS25519. Передайте
готовый клиент boto3 KMS client для нестандартных учётных данных или эндпоинтов; тогда region
игнорируется:
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
Версия ключа должна быть ключом EC_SIGN_ED25519. В client можно опционально передать
готовый 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",))
Кошельки, защищённые ключом авторизации, принимают PrivyAuthorizationConfig в качестве
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 подписывает с помощью операции PROGRAM_CALL вместо RAW. Она
отправляется в режиме «только подпись», поэтому Fireblocks не переписывает и не отправляет
транзакцию, а отправляете её вы, как обычно. Требуется горячий кошелёк, а 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 работает только в режиме отправки. Его API всегда выполняет одобренную транзакцию
на стороне сервера и оплачивает газ, поэтому CrossmintSigner — это SendingSigner, а
sign_message вызывает SIGNING_FAILED. Возвращённая подпись идентифицирует
транзакцию, которую отправил Crossmint, и она может отличаться от вашей. Если задать
signer_secret (секрет делегированного signer вида xmsk1_...), транзакции одобряются
автоматически, то есть выбор того, что одобрять, делегируется Crossmint.
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 только подписывает транзакции; sign_message вызывает 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 три режима подписания, которые фиксируются при создании параметрами chain и
push_mode. create_fordefi_signer выбирает класс, каждый класс отклоняет
конфигурацию, предназначенную для другого, и все три подписывают сообщения:
| Режим | Конфигурация | Класс | Метод |
|---|---|---|---|
| Black box | chain не задан | FordefiBlackBoxSigner | sign_transaction |
| Native auto | chain задан, push_mode не задан или "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native manual | chain задан, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
API-запросы аутентифицируются подписью запроса ECDSA P-256: передайте
ключ как private_key_pem или храните его в KMS/HSM, передав подкласс
FordefiRequestSigner в качестве request_signer.
Режим Native auto: задайте chain ("solana_devnet" или "solana_mainnet"), и
Fordefi может заменить blockhash и комиссии, после чего сам подписывает и отправляет
транзакцию. Принимается только неподписанная транзакция, единственным необходимым
подписантом которой является хранилище (vault). fee передаётся в Fordefi без изменений:
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: задайте chain и push_mode="manual". Fordefi переписывает
сообщение (recent blockhash, инструкции комиссии Compute Budget) и подписывает его,
но не отправляет. Возвращённая транзакция заменяет ту, что вы отправили,
поэтому всегда продолжайте работу с result.transaction. Fordefi должен быть плательщиком комиссии
и должен подписать раньше любого другого обязательного подписанта: транзакция, которая уже содержит
подписи, принимается, но переписывание их аннулирует. Изменения не сравниваются построчно,
поэтому проверьте результат перед отправкой и отправляйте без промедления, поскольку Fordefi
не возвращает высоту истечения срока действия нового 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
Отправьте проверенную транзакцию, как описано в разделе Подписание и отправка, и при сбое отправки сверьте состояние, прежде чем повторять попытку.
Режим Black box: не указывайте chain. Fordefi подписывает точные байты вашего сообщения и
не отправляет его. Используйте этот режим с хранилищем 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(),))
Подписание и отправка
sign_and_send_transaction доставляет транзакцию в сеть независимо от типа
signer. SendingSigner отправляет её через своего провайдера, а ваша функция отправки
не вызывается. TransactionSigner или ModifyingSigner подписывает, а ваша функция
отправки передаёт в сеть результат в кодировке base64 (в пакете нет 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, который только подписывает, при отсутствии функции отправки вызывает CONFIG_ERROR, а
транзакция, в которой после подписания всё ещё не хватает подписей, вызывает SIGNING_FAILED.
Обработка ошибок
Любой сбой — это SignerError со стабильным кодом code из 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 и SIGNING_FAILED. Ориентируйтесь на code, а не на сообщение:
str() и repr() содержат только фиксированное общее сообщение, но никогда не ключевой материал и не
сырые ответы провайдера. status_code и provider_error_code задаются, если
сбой пришёл из ответа провайдера.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") означает,
что транзакция, возможно, попала в сеть:
- В Crossmint и Fordefi native auto
provider_transaction_idзадаётся, когда создание было принято, аidempotency_keyсодержит ключ, полученный из сообщения, с которым было отправлено создание. Повторная отправка побайтово идентичной транзакции безопасна; пересобранная транзакция (например, с новым blockhash) — это новый перевод. - Из
sign_and_send_transactionеё вызывает сбой функции отправки, при этом вtransaction_signatureзаписана подпись завершённой транзакции. - Fireblocks вызывает её, только если при опросе выяснилось, что Fireblocks отправил
транзакцию
use_program_call, хотя она была отправлена в режиме «только подпись».
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
Отменённый вызов повторно вызывает asyncio.CancelledError, у которого нет полей. Чтобы
восстановить идентификатор, принятый провайдером до отмены, передайте
PendingTransactionId() (из solana_keychain.core) в качестве pending_transaction_id в конфигурации Crossmint или
Fordefi native auto и прочитайте .get() после
отмены. Перед повторной попыткой сверьте состояние с провайдером.
Keychain проверяет этап подписания, а не то, что делает транзакция: возвращённая провайдером подпись проверяется по ed25519 на соответствие локально вычисленному сообщению, прежде чем будет прикреплена. Python не может обнулять буферы ключей, поэтому при использовании бэкендов с локальными ключами (Memory, а также API-ключи и PEM, хранящиеся в конфигурациях удалённых бэкендов) считайте конфиденциальной всю память процесса. О том, что гарантирует каждая схема подписания, см. в модели безопасности.
Добавление собственных signer
Создайте подкласс того класса возможностей, который соответствует вашему бэкенду (TransactionSigner,
ModifyingSigner или SendingSigner), из solana_keychain.core. Чтобы интегрировать
дополнительные сервисы управления ключами, см.
руководство по добавлению подписантов.
Ресурсы
Is this page helpful?