Python

الإصدار 2.0 هو أول إصدار مستقر من حزمة Python. ويغطي دليل الترحيل إلى 2.0 أشكال التوقيع التي تشترك فيها مع Rust وTypeScript.

التثبيت

ثبّت الحزمة الأساسية، بالإضافة إلى إضافة اختيارية واحدة لكل واجهة خلفية تتطلب تبعية من المزوّد:

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)، واستيراد إحداها دون إضافتها الاختيارية يُطلق الاستثناء ImportError الذي يذكر اسم الإضافة المطلوب تثبيتها. أما Ledger فهو متاح في Rust فقط.

الاستخدام الأساسي

لكل واجهة خلفية dataclass للإعدادات ودالة مصنع غير متزامنة create_<backend>_signer تُرجع موقّعًا جاهزًا للاستخدام. جميع طرق التوقيع هي coroutines:

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 الإعدادات الخاص بها، وتستورد الواجهة الخلفية عند الحاجة فقط، لذا لا يلزم تثبيت سوى الإضافات التي تستخدمها:

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 الأساسي، ورفض إعادة التوجيه.

قدرات الموقّع

ترث كل واجهة خلفية من الفئة المجردة 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 الخاص بـ سولانا 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 هو معرّف مفتاح أو ARN أو اسم مستعار لمفتاح ECC_NIST_EDWARDS25519. مرّر client لـ boto3 KMS مُنشأً مسبقًا لاستخدام بيانات اعتماد أو نقاط نهاية مخصصة؛ وعندها يتم تجاهل 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 كتابة المعاملة ولا تبثّها، وتبثّها أنت كالمعتاد. ويتطلب محفظة ساخنة (hot wallet) ويجب أن تفعّله 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 للإرسال فقط. فواجهتها البرمجية تنفّذ دائمًا المعاملة المعتمدة من جانب الخادم وتتحمل رسوم الغاز، لذا فإن 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 كتابة الرسالة (أحدث 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 المعاملة على السلسلة أيًّا كان شكل الموقّع. فـ 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، الذي لا يحمل أي حقول. لاسترداد معرّف قبله المزوّد قبل الإلغاء، مرّر 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?