Версія 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-адреси та відхиленням
перенаправлень.
Можливості підписувачів
Кожен бекенд є підкласом ABC 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 виконують усе необхідне налаштування
за вас. Якщо ви створюєте клас підписувача 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 (секрету делегованого підписувача 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 має бути платником комісії і
має підписувати раніше за будь-якого іншого обов’язкового підписувача: транзакція, що вже містить
підписи, приймається, але переписування робить їх недійсними. Переписування не показується у вигляді diff,
тому перевіряйте результат перед трансляцією та транслюйте оперативно, оскільки 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 доправляє транзакцію в мережу незалежно від типу
підписувача. 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 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, який не містить полів. Щоб
відновити id, який провайдер прийняв до скасування, передайте
PendingTransactionId() (із solana_keychain.core) як pending_transaction_id конфігурації Crossmint або
Fordefi native auto і прочитайте .get() після
скасування. Узгодьте стан із провайдером перед повторною спробою.
Keychain перевіряє етап підписання, а не те, що робить транзакція: підпис, повернений провайдером, перевіряється за ed25519 щодо локально обчисленого повідомлення, перш ніж його буде додано. Python не може занулити буфери ключів, тож у разі бекендів із локальними ключами (Memory, а також API-ключів і PEM, що зберігаються в конфігураціях віддалених бекендів) вважайте всю пам’ять процесу чутливою. Дивіться модель безпеки, щоб дізнатися, що гарантує кожен варіант підписання.
Додавання власних підписувачів
Створіть підклас того одного класу можливостей, який відповідає вашому бекенду (TransactionSigner,
ModifyingSigner або SendingSigner), із solana_keychain.core. Щоб інтегрувати
додаткові сервіси керування ключами, перегляньте посібник «Додавання підписантів».
Ресурси
Is this page helpful?