A versão 2.0 é a primeira versão estável do pacote Python. O guia de migração para a 2.0 abrange os formatos de assinatura que partilha com Rust e TypeScript.
Instalação
Instale o pacote base, além de um extra por backend que necessite de uma dependência de fornecedor:
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]'
Requer Python 3.10 ou posterior. As transações são objetos
solders VersionedTransaction;
as mensagens legacy, v0 e v1 são todas aceites, exceto pela Fireblocks com
use_program_call=True, que aceita apenas legacy e v0.
Memory, Vault e Para podem ser importados a partir da raiz do pacote. Todos os restantes backends
são importados a partir do seu próprio submódulo (por exemplo, solana_keychain.aws_kms), e
importar um deles sem o respetivo extra gera um ImportError que indica o extra a
instalar. O Ledger é exclusivo do Rust.
Utilização básica
Cada backend tem uma dataclass de configuração e uma factory assíncrona create_<backend>_signer
que devolve um signatário pronto a usar. Todos os métodos de assinatura são corrotinas:
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 devolve um SignedTransaction com encoded_transaction,
signature, is_complete e transaction. transaction é a transação
assinada de referência: continue a partir dela, especialmente com um signatário modificador, cujo
resultado é um objeto diferente daquele que passou.
Factory unificada
create_keychain_signer recebe o nome de um backend e a dataclass de configuração
desse backend, e importa o backend de forma lazy, pelo que só é necessário instalar
os extras que utiliza:
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",),)
Os nomes estão listados em SUPPORTED_BACKENDS: aws-kms, cdp, crossmint,
dfns, fireblocks, fordefi, gcp-kms, memory, openfort, para,
privy, turnkey, utila e vault. Um nome desconhecido, ou uma configuração da
classe errada, gera SignerError com SignerErrorCode.CONFIG_ERROR. A
factory está tipada como devolvendo o SolanaSigner base, por isso restrinja-o com
isinstance antes de assinar (consulte Capacidades do signatário).
Clientes HTTP remotos
Os backends HTTP remotos aceitam um http_client opcional (um httpx.AsyncClient)
para TLS personalizado ou proxies. Quando não está definido, cada pedido usa um cliente de utilização única
com um tempo limite de 60 segundos, HTTPS obrigatório no URL base e redirecionamentos
rejeitados.
Capacidades do signatário
Cada backend é uma subclasse da ABC SolanaSigner (pubkey, sign_message,
is_available) e de exatamente uma classe de capacidade:
TransactionSigner:sign_transaction(tx)assina a sua transação no local e devolve umSignedTransaction. Cabe-lhe a si transmiti-la.ModifyingSigner:modify_and_sign_transaction(tx)permite que o fornecedor reescreva a transação e, em seguida, assina-a sem a transmitir. O seu objeto permanece intacto; continue a partir deSignedTransaction.transaction, devolvido.SendingSigner:sign_and_send_transaction(tx)assina e transmite através do fornecedor e devolve aSignatureda transação que foi confirmada. A sua transação nunca é alterada.
| Backend | Classe de capacidade | sign_message |
|---|---|---|
| memory, vault, privy, turnkey, aws-kms, fireblocks, gcp-kms, dfns, para, openfort | TransactionSigner | Sim |
| cdp | TransactionSigner | Apenas payloads UTF-8; caso contrário, SERIALIZATION_ERROR |
| utila | TransactionSigner | Gera SIGNING_FAILED |
fordefi black box (FordefiBlackBoxSigner) | TransactionSigner | Sim |
fordefi native manual (FordefiNativeManualSigner) | ModifyingSigner | Sim |
fordefi native auto (FordefiNativeAutoSigner) | SendingSigner | Sim |
| crossmint | SendingSigner | Gera SIGNING_FAILED |
Restrinja com isinstance. Todas as classes de capacidade são exportadas a partir da raiz
do pacote:
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)
Inicialização
As factories create_* e create_keychain_signer efetuam toda a configuração necessária
por si. Se construir diretamente a classe de signatário Privy, Fireblocks, Dfns, Crossmint, Para, Openfort ou
Utila, execute await signer.init() antes de usar. Até lá,
pubkey e todas as chamadas de assinatura geram SignerErrorCode.NOT_INITIALIZED.
Configuração do backend
Memory
Construa a partir de um ficheiro keypair da CLI da Solana, uma string base58 ou "[1,2,...]", bytes
brutos ou um Keypair de 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()))
A chave reside na memória do processo e o Python não consegue zerá-la. Utilize este backend para desenvolvimento e testes.
HashiCorp Vault
A chave tem de ser uma chave ed25519 no motor transit. O HTTP simples só é aceite
para um endereço de loopback, para 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 é um ID de chave, ARN ou alias de uma chave ECC_NIST_EDWARDS25519. Passe um
client KMS do boto3 pré-construído para credenciais ou endpoints personalizados; region
é então ignorado:
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
A versão da chave tem de ser uma chave EC_SIGN_ED25519. client aceita opcionalmente um
KeyManagementServiceAsyncClient pré-construído:
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",))
As carteiras protegidas por uma chave de autorização recebem um PrivyAuthorizationConfig como
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 assina com a operação PROGRAM_CALL em vez de RAW. É
enviada apenas para assinatura, pelo que a Fireblocks não reescreve nem transmite a
transação, e você transmite-a como habitual. Requer uma hot wallet e tem de ser
ativada para o seu workspace pela 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)
A Crossmint é apenas de envio. A sua API executa sempre uma transação aprovada
no lado do servidor e patrocina o gas, pelo que CrossmintSigner é um SendingSigner e
sign_message gera SIGNING_FAILED. A assinatura devolvida identifica a
transação que a Crossmint confirmou, que pode diferir da sua. Definir
signer_secret (um segredo de signatário delegado xmsk1_...) aprova as transações
automaticamente, o que delega na Crossmint a escolha do que é aprovado.
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
A Utila apenas assina transações; sign_message gera 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
A Fordefi tem três modos de assinatura, fixados na construção por chain e
push_mode. create_fordefi_signer escolhe a classe, cada classe rejeita uma
configuração destinada a outra, e as três assinam mensagens:
| Modo | Configuração | Classe | Método |
|---|---|---|---|
| Black box | chain não definido | FordefiBlackBoxSigner | sign_transaction |
| Native auto | chain definido, push_mode não definido ou "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| Native manual | chain definido, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
Os pedidos à API são autenticados com uma assinatura de pedido ECDSA P-256: forneça a
chave como private_key_pem ou mantenha-a num KMS/HSM passando uma
subclasse de FordefiRequestSigner como request_signer.
Modo native auto: defina chain ("solana_devnet" ou "solana_mainnet") e
a Fordefi pode substituir o blockhash e as taxas, e depois assina e transmite a
transação por si própria. Aceita apenas uma transação não assinada cujo único
signatário necessário seja o vault. fee é passado à Fordefi tal como está:
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)
Modo native manual: defina chain e push_mode="manual". A Fordefi reescreve
a mensagem (blockhash recente, instruções de taxa de Compute Budget) e assina-a,
mas não a transmite. A transação devolvida substitui a que submeteu,
pelo que deve continuar sempre a partir de result.transaction. A Fordefi tem de ser a entidade que paga a taxa e
tem de assinar antes de qualquer outro signatário necessário: uma transação que já contenha
assinaturas é aceite, mas a reescrita invalida-as. A reescrita não é comparada
(diff), por isso inspecione o resultado antes de transmitir e transmita rapidamente, uma vez que a Fordefi
não devolve a altura de expiração do novo 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
Transmita a transação inspecionada como em Assinar e enviar e reconcilie antes de tentar novamente se o envio falhar.
Modo black box: omita chain. A Fordefi assina exatamente os bytes da sua mensagem e
não transmite. Utilize-o com um vault black box da Fordefi.
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(),))
Assinar e enviar
sign_and_send_transaction coloca uma transação na chain, seja qual for o formato
do signatário. Um SendingSigner transmite através do seu fornecedor e a sua função de envio
nunca é chamada. Um TransactionSigner ou ModifyingSigner assina, e a sua função
de envio transmite o resultado codificado em base64 (o pacote não tem cliente 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)
Um signatário apenas de assinatura sem função de envio gera CONFIG_ERROR, e uma
transação à qual ainda faltem assinaturas após a assinatura gera SIGNING_FAILED.
Tratamento de erros
Toda a falha é um SignerError com um code estável de 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 e SIGNING_FAILED. Faça a correspondência por code, não pela mensagem:
str() e repr() contêm apenas uma mensagem genérica fixa, nunca material de chaves nem
respostas em bruto do fornecedor. status_code e provider_error_code são definidos quando a
falha resultou de uma resposta do fornecedor.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED") significa que
a transação pode ter sido confirmada:
- Na Crossmint e na Fordefi native auto,
provider_transaction_idé definido quando a criação foi aceite, eidempotency_keycontém a chave derivada da mensagem com a qual a criação foi enviada. Reenviar a transação idêntica byte a byte é seguro; uma transação reconstruída (por exemplo, com um novo blockhash) é uma nova transferência. - A partir de
sign_and_send_transaction, uma função de envio que falhe gera-o comtransaction_signaturedefinido como a assinatura da transação concluída. - A Fireblocks só o gera se uma consulta mostrar que a Fireblocks transmitiu uma
transação
use_program_call, apesar de ter sido enviada apenas para assinatura.
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
Uma chamada cancelada volta a gerar asyncio.CancelledError, que não contém campos. Para
recuperar um id que o fornecedor aceitou antes do cancelamento, passe um
PendingTransactionId() (de solana_keychain.core) como pending_transaction_id da
configuração Crossmint ou Fordefi native auto e leia .get() após
o cancelamento. Reconcilie com o fornecedor antes de tentar novamente.
O Keychain valida a etapa de assinatura, não o que a transação faz: uma assinatura devolvida pelo fornecedor é verificada em ed25519 face à mensagem calculada localmente antes de ser anexada. O Python não consegue zerar buffers de chaves, pelo que, com backends de chave local (Memory, e as chaves de API e PEMs mantidos nas configurações de backends remotos), trate toda a memória do processo como sensível. Consulte o modelo de segurança para saber o que cada formato de assinatura garante.
Adicionar signatários personalizados
Crie uma subclasse da única classe de capacidade correspondente ao seu backend (TransactionSigner,
ModifyingSigner ou SendingSigner) a partir de solana_keychain.core. Consulte o
guia Adding Signers para integrar
outros serviços de gestão de chaves.
Recursos
Is this page helpful?