Python

Версія 2.0 — це перший стабільний випуск пакета для Python. У посібнику з міграції на 2.0 описано підходи до підписання, спільні з Rust і TypeScript.

Встановлення

Встановіть базовий пакет, а також по одній додатковій залежності (extra) для кожного бекенда, якому потрібна залежність від провайдера:

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]'

Потрібен 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 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 повертає SignedTransaction з полями encoded_transaction, signature, is_complete і transaction. transaction — це авторитетна підписана транзакція: продовжуйте роботу саме з нею, особливо у випадку підписувача, що змінює транзакцію, результат якого є іншим об’єктом, ніж той, який ви передали.

Уніфікована фабрика

create_keychain_signer приймає назву бекенда та dataclass конфігурації цього бекенда і імпортує бекенд ліниво, тому встановити потрібно лише ті extras, які ви використовуєте:

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",
),
)

Назви перелічено в 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, openfortTransactionSignerТак
cdpTransactionSignerЛише дані в UTF-8, інакше SERIALIZATION_ERROR
utilaTransactionSignerСпричиняє SIGNING_FAILED
fordefi black box (FordefiBlackBoxSigner)TransactionSignerТак
fordefi native manual (FordefiNativeManualSigner)ModifyingSignerТак
fordefi native auto (FordefiNativeAutoSigner)SendingSignerТак
crossmintSendingSignerСпричиняє SIGNING_FAILED

Звужуйте тип за допомогою isinstance. Усі класи можливостей експортуються з кореня пакета:

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)

Ініціалізація

Фабрики 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_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()))

Ключ зберігається в пам’яті процесу, а Python не може його занулити. Використовуйте цей бекенд для розробки та тестування.

HashiCorp Vault

Ключ має бути ключем ed25519 у рушії transit. Звичайний HTTP приймається лише для loopback-адреси, для 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 — це ID ключа, ARN або псевдонім ключа ECC_NIST_EDWARDS25519. Передайте готовий клієнт boto3 KMS як client для власних облікових даних чи кінцевих точок; тоді region ігнорується:

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

Версія ключа має бути ключем EC_SIGN_ED25519. client за потреби приймає готовий 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",
)
)

Гаманці, захищені ключем авторизації, приймають PrivyAuthorizationConfig як 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 підписує операцією PROGRAM_CALL замість RAW. Вона надсилається лише для підписання, тому Fireblocks не переписує і не транслює транзакцію, а ви транслюєте її як зазвичай. Потрібен гарячий гаманець, а 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 працює лише з надсиланням. Його API завжди виконує схвалену транзакцію на стороні сервера та спонсорує газ, тому CrossmintSigner є SendingSigner, а sign_message спричиняє SIGNING_FAILED. Повернутий підпис ідентифікує транзакцію, яку Crossmint відправив у мережу, і вона може відрізнятися від вашої. Встановлення signer_secret (секрету делегованого підписувача xmsk1_...) схвалює транзакції автоматично, що делегує Crossmint вибір того, що саме схвалюється.

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 лише підписує транзакції; sign_message спричиняє 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 має три режими підписання, які фіксуються під час створення через chain і push_mode. create_fordefi_signer вибирає клас, кожен клас відхиляє конфігурацію, призначену для іншого, і всі три підписують повідомлення:

РежимКонфігураціяКласМетод
Black boxchain не заданоFordefiBlackBoxSignersign_transaction
Native autochain задано, push_mode не задано або "auto"FordefiNativeAutoSignersign_and_send_transaction
Native manualchain задано, push_mode="manual"FordefiNativeManualSignermodify_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 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)

Режим 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 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)

Підписувач лише для підписання без функції надсилання спричиняє 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, 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

Скасований виклик повторно спричиняє 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?