A versão 2.0 é a primeira versão estável do pacote Python. O guia de migração para a 2.0 cobre os formatos de assinatura que ele partilha com Rust e TypeScript.
Instalação
Instale o pacote base, mais um extra por backend que precise 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 superior. 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
outros backends são importados do seu próprio submódulo (por exemplo
solana_keychain.aws_kms), e importar um sem o respetivo extra gera um
ImportError que indica o extra a instalar. O Ledger é exclusivo de 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 do 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 preguiçosa (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 a base SolanaSigner, por isso restrinja o
tipo 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 herda 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. É você quem a difunde.ModifyingSigner:modify_and_sign_transaction(tx)permite que o fornecedor reescreva a transação e depois assina-a sem a difundir. O seu objeto não é alterado; continue a partir deSignedTransaction.transaction, devolvido.SendingSigner:sign_and_send_transaction(tx)assina e difunde através do fornecedor e devolve aSignatureda transação que foi concluída. A sua transação nunca é modificada.
| 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 o tipo 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 fazem 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, de uma string base58
ou "[1,2,...]", de bytes brutos ou de 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. Use 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 já 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 já 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 difunde
a transação, e você difunde como habitual. Requer uma hot wallet e tem de ser
ativado 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
do lado do servidor e patrocina o gas, por isso CrossmintSigner é um
SendingSigner e sign_message gera SIGNING_FAILED. A assinatura devolvida
identifica a transação que a Crossmint concluiu, a qual 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 os 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 difunde a
próprio a transação. 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 do Compute Budget)
e assina-a, mas não a difunde. A transação devolvida substitui a que submeteu,
por isso continue sempre a partir de result.transaction. A Fordefi tem de ser
a pagadora das taxas 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 difundir e difunda 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
Difunda a transação inspecionada como em Assinar e enviar e concilie antes de tentar novamente se o envio falhar.
Modo black box: omita chain. A Fordefi assina os bytes exatos da sua
mensagem e não difunde. Use-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 blockchain, qualquer que
seja o formato do signatário. Um SendingSigner difunde 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 difunde 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 a que ainda faltem assinaturas depois de assinada 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 brutas do fornecedor. status_code e
provider_error_code são definidos quando a falha provém de uma resposta do
fornecedor.
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED")
significa que a transação pode ter sido concluída:
- 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 sob a qual a criação foi enviada. Reenviar a transação idêntica byte a byte é seguro; uma 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 falha gera-o comtransaction_signaturedefinido como a assinatura da transação concluída. - A Fireblocks só o gera se uma consulta (poll) mostrar que a Fireblocks difundiu
uma transação
use_program_callapesar 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. Concilie com o fornecedor antes de tentar novamente.
O Keychain valida o salto de assinatura, não o que a transação faz: uma assinatura devolvida pelo fornecedor é verificada com ed25519 face à mensagem calculada localmente antes de ser anexada. O Python não consegue zerar buffers de chaves, por isso, 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 classe de capacidade que corresponde ao seu backend (TransactionSigner,
ModifyingSigner ou SendingSigner) a partir de solana_keychain.core. Consulte o
guia Adicionar signatários para integrar
outros serviços de gestão de chaves.
Recursos
Is this page helpful?