버전 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 메시지가 모두 허용되지만, use_program_call=True를 사용하는 Fireblocks는 예외로 legacy와 v0만 허용합니다.
Memory, Vault, Para는 패키지 루트에서 가져올 수 있습니다. 그 외의 모든 백엔드는 각자의 서브모듈(예: solana_keychain.aws_kms)에서 가져오며, extra 없이 가져오면 설치해야 할 extra의 이름이 담긴 ImportError가 발생합니다. Ledger는 Rust 전용입니다.
기본 사용법
모든 백엔드에는 config 데이터클래스와, 바로 사용할 수 있는 서명자를 반환하는 비동기 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은 encoded_transaction, signature, is_complete, transaction을 포함하는 SignedTransaction을 반환합니다. transaction은 신뢰할 수 있는 서명된 트랜잭션입니다. 특히 수정 서명자(modifying signer)를 사용할 때는 결과가 전달한 객체와 다른 객체이므로, 반드시 이 값을 이어서 사용하세요.
통합 팩토리
create_keychain_signer는 백엔드 이름과 해당 백엔드의 config 데이터클래스를 받아 백엔드를 지연 임포트하므로, 사용하는 extra만 설치하면 됩니다:
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. 알 수 없는 이름이나 잘못된 클래스의 config를 전달하면 SignerErrorCode.CONFIG_ERROR와 함께 SignerError가 발생합니다. 팩토리는 기본 SolanaSigner를 반환하는 것으로 타입이 지정되어 있으므로, 서명하기 전에 isinstance로 타입을 좁혀야 합니다(서명자 기능 참고).
원격 HTTP 클라이언트
원격 HTTP 백엔드는 커스텀 TLS나 프록시를 위해 선택적으로 http_client(httpx.AsyncClient)를 받을 수 있습니다. 설정하지 않으면 각 요청은 60초 타임아웃을 가진 일회성 클라이언트를 사용하며, 기본 URL에는 HTTPS가 강제되고 리디렉션은 거부됩니다.
서명자 기능
모든 백엔드는 SolanaSigner ABC(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 블랙박스(FordefiBlackBoxSigner) | TransactionSigner | 지원 |
fordefi 네이티브 수동(FordefiNativeManualSigner) | ModifyingSigner | 지원 |
fordefi 네이티브 자동(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
Solana CLI keypair 파일, base58 또는 "[1,2,...]" 문자열, 원시 바이트, 또는 solders의 Keypair로 생성할 수 있습니다:
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에서는 이를 제로화(zeroize)할 수 없습니다. 이 백엔드는 개발 및 테스트용으로 사용하세요.
HashiCorp Vault
키는 transit 엔진의 ed25519 키여야 합니다. 일반 HTTP는 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는 ECC_NIST_EDWARDS25519 키의 키 ID, ARN 또는 별칭입니다. 커스텀 자격 증명이나 엔드포인트를 사용하려면 미리 생성한 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",))
인증 키로 보호되는 지갑은 authorization_context로 PrivyAuthorizationConfig를 받습니다.
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는 RAW 대신 PROGRAM_CALL 작업으로 서명합니다. 이 요청은 서명 전용으로 전송되므로 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는 전송 전용입니다. 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가 클래스를 선택하고, 각 클래스는 다른 모드용 config를 거부하며, 세 가지 모두 메시지 서명을 지원합니다:
| 모드 | 구성 | 클래스 | 메서드 |
|---|---|---|---|
| 블랙박스 | chain 미설정 | FordefiBlackBoxSigner | sign_transaction |
| 네이티브 자동 | chain 설정, push_mode 미설정 또는 "auto" | FordefiNativeAutoSigner | sign_and_send_transaction |
| 네이티브 수동 | chain 설정, push_mode="manual" | FordefiNativeManualSigner | modify_and_sign_transaction |
API 요청은 ECDSA P-256 요청 서명으로 인증됩니다. 키를 private_key_pem으로 제공하거나, FordefiRequestSigner 서브클래스를 request_signer로 전달하여 KMS/HSM에 보관할 수 있습니다.
네이티브 자동 모드: chain("solana_devnet" 또는 "solana_mainnet")을 설정하면 Fordefi가 블록해시와 수수료를 교체할 수 있으며, 트랜잭션에 서명하고 직접 브로드캐스트합니다. 필수 서명자가 볼트(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)
네이티브 수동 모드: chain과 push_mode="manual"을 설정합니다. Fordefi는 메시지(최근 블록해시, Compute Budget 수수료 인스트럭션)를 재작성하고 서명하지만 브로드캐스트하지는 않습니다. 반환된 트랜잭션은 제출한 트랜잭션을 대체하므로, 항상 result.transaction을 이어서 사용하세요. Fordefi가 수수료 지불자(fee payer)여야 하며 다른 모든 필수 서명자보다 먼저 서명해야 합니다. 이미 서명이 포함된 트랜잭션도 허용되지만, 재작성 과정에서 해당 서명은 무효화됩니다. 재작성 내용은 diff로 제공되지 않으므로 브로드캐스트하기 전에 결과를 검토하고, Fordefi가 새 블록해시의 만료 높이를 반환하지 않으므로 신속하게 브로드캐스트하세요.
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
검토를 마친 트랜잭션은 서명 및 전송에서처럼 브로드캐스트하고, 전송에 실패하면 재시도하기 전에 상태를 확인(reconcile)하세요.
블랙박스 모드: chain을 생략합니다. Fordefi는 사용자의 메시지 바이트를 그대로 서명하며 브로드캐스트하지 않습니다. 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(),))
서명 및 전송
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를 발생시킵니다.
오류 처리
모든 실패는 SignerErrorCode의 고정된 code를 가진 SignerError로 나타납니다: 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 네이티브 자동 모드에서는 생성 요청이 수락되었을 때
provider_transaction_id가 설정되고,idempotency_key에는 생성 요청에 사용된 메시지로부터 파생된 키가 담깁니다. 바이트 단위로 동일한 트랜잭션을 다시 보내는 것은 안전하지만, 다시 구성한 트랜잭션(예: 새 블록해시 사용)은 새로운 전송으로 처리됩니다. 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를 복구하려면, solana_keychain.core의 PendingTransactionId()를 Crossmint 또는 Fordefi 네이티브 자동 config의 pending_transaction_id로 전달하고, 취소 후 .get()으로 값을 읽으세요. 재시도하기 전에 프로바이더와 상태를 확인(reconcile)하세요.
Keychain은 트랜잭션의 내용이 아니라 서명 구간(signing hop)을 검증합니다. 프로바이더가 반환한 서명은 첨부되기 전에 로컬에서 계산한 메시지에 대해 ed25519로 검증됩니다. Python은 키 버퍼를 제로화할 수 없으므로, 로컬 키 백엔드(Memory, 그리고 원격 백엔드 config에 보관된 API 키와 PEM)를 사용할 때는 프로세스 메모리 전체를 민감한 정보로 취급하세요. 각 서명 방식이 보장하는 내용은 보안 모델을 참고하세요.
커스텀 서명자 추가하기
백엔드에 맞는 기능 클래스 하나(TransactionSigner,
ModifyingSigner 또는 SendingSigner)를 solana_keychain.core에서 서브클래스로 만드세요. 추가 키 관리 서비스를 통합하려면
서명자 추가 가이드를 참고하세요.
리소스
Is this page helpful?