Версия 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, которая возвращает готовый к использованию подписант. Все методы подписи — корутины:
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 — это основная подписанная транзакция: продолжайте работу с ней, особенно при использовании изменяющего подписанта, результатом которого является другой объект, а не тот, что вы передали.
Единая фабрика
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 (см. Возможности подписантов).
Удалённые HTTP-клиенты
Удалённые HTTP-бэкенды принимают необязательный http_client (httpx.AsyncClient) для настройки TLS или прокси. Если он не задан, каждый запрос использует одноразовый клиент с таймаутом 60 секунд, обязательным HTTPS для базового URL и отклонением перенаправлений.
Возможности подписантов
Каждый бэкенд наследует абстрактный класс 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, чёрный ящик (FordefiBlackBoxSigner) | TransactionSigner | Да |
fordefi, нативный ручной режим (FordefiNativeManualSigner) | ModifyingSigner | Да |
fordefi, нативный автоматический режим (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 выполняют всю необходимую настройку за вас. Если вы создаёте класс подписанта 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. Передайте заранее созданный client boto3 KMS для пользовательских учётных данных или эндпоинтов; тогда 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 не изменяет и не отправляет транзакцию в сеть, а вы отправляете её как обычно. Требуется hot-кошелёк, а 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 (секрета делегированного подписанта 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 выбирает класс, каждый класс отклоняет
конфигурацию, предназначенную для другого, и все три подписывают сообщения:
| Режим | Конфигурация | Класс | Метод |
|---|---|---|---|
| Чёрный ящик | chain не задан | FordefiBlackBoxSigner | sign_transaction |
| Нативный автоматический | chain задан, push_mode не задан или "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Нативный ручной | chain задан, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
API-запросы аутентифицируются подписью запроса ECDSA P-256: передайте ключ как private_key_pem или храните его в KMS/HSM, передав подкласс FordefiRequestSigner в качестве request_signer.
Нативный автоматический режим: задайте 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)
Нативный ручной режим: задайте 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
Отправьте проверенную транзакцию, как описано в разделе Подпись и отправка, а при сбое отправки выполните сверку перед повторной попыткой.
Режим чёрного ящика: не указывайте chain. Fordefi подписывает ровно те байты сообщения, которые вы передали, и не отправляет их в сеть. Используйте его с vault 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 доставляет транзакцию в сеть независимо от типа подписанта. 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)
Подписант, умеющий только подписывать, без функции отправки вызывает 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 в нативном автоматическом режиме
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, который не содержит полей. Чтобы
восстановить id, принятый провайдером до отмены, передайте
PendingTransactionId() (из solana_keychain.core) как pending_transaction_id
в конфигурации Crossmint или Fordefi в нативном автоматическом режиме и прочитайте .get() после
отмены. Перед повторной попыткой выполните сверку с провайдером.
Keychain проверяет шаг подписи, а не то, что делает транзакция: подпись, возвращённая провайдером, перед присоединением проверяется по ed25519 относительно локально вычисленного сообщения. Python не может обнулить буферы ключей, поэтому при использовании бэкендов с локальными ключами (Memory, а также API-ключи и PEM, хранящиеся в конфигурациях удалённых бэкендов) считайте всю память процесса конфиденциальной. О том, что гарантирует каждая форма подписи, см. модель безопасности.
Добавление пользовательских подписантов
Создайте подкласс того класса возможностей, который соответствует вашему бэкенду (TransactionSigner,
ModifyingSigner или SendingSigner), из solana_keychain.core. Чтобы интегрировать
дополнительные сервисы управления ключами, см.
руководство по добавлению подписантов.
Ресурсы
Is this page helpful?