2.0.0은 Go 모듈의 첫 번째 안정 릴리스이므로 마이그레이션할 내용이 없습니다. 2.0 마이그레이션 가이드에서는 Rust와 TypeScript에 적용된 것과 동일한 기능 분리를 다룹니다.
설치
모든 백엔드는 각각 독립된 Go 모듈이므로, 빌드와 모듈 그래프에는 직접 임포트한 백엔드만 포함됩니다.
go get github.com/solana-foundation/solana-keychain/go/core/v2 # Interfaces, errors, helpersgo get github.com/solana-foundation/solana-keychain/go/signers/memory/v2 # Local keypair signergo get github.com/solana-foundation/solana-keychain/go/signers/vault/v2 # HashiCorp Vaultgo get github.com/solana-foundation/solana-keychain/go/signers/awskms/v2 # AWS KMSgo get github.com/solana-foundation/solana-keychain/go/signers/gcpkms/v2 # GCP KMSgo get github.com/solana-foundation/solana-keychain/go/signers/privy/v2 # Privygo get github.com/solana-foundation/solana-keychain/go/signers/turnkey/v2 # Turnkeygo get github.com/solana-foundation/solana-keychain/go/signers/fireblocks/v2 # Fireblocksgo get github.com/solana-foundation/solana-keychain/go/signers/cdp/v2 # Coinbase Developer Platformgo get github.com/solana-foundation/solana-keychain/go/signers/crossmint/v2 # Crossmintgo get github.com/solana-foundation/solana-keychain/go/signers/dfns/v2 # Dfnsgo get github.com/solana-foundation/solana-keychain/go/signers/openfort/v2 # Openfortgo get github.com/solana-foundation/solana-keychain/go/signers/para/v2 # Parago get github.com/solana-foundation/solana-keychain/go/signers/utila/v2 # Utilago get github.com/solana-foundation/solana-keychain/go/signers/fordefi/v2 # Fordefi
모듈은 go/<module>/v2.0.0 형식으로 태그됩니다(예: go/signers/memory/v2.0.0). 모든 모듈은 Go 1.25.0이 필요하며, gcpkms만 google.golang.org/api가 설정한 최소 버전 때문에 Go 1.25.8이 필요합니다. Ledger는 Rust 전용이며 Go 모듈이 없습니다.
solana-go v2 및 v1 트랜잭션
이 모듈은 github.com/solana-foundation/solana-go/v2 v2.0.0을 기반으로 하며 *solana.Transaction을 받습니다. 레거시, v0, v1(solana.MessageVersionV1) 트랜잭션을 지원합니다. 단, UseProgramCall: true를 사용하는 Fireblocks는 레거시와 v0만 허용합니다.
기본 사용법
모든 서명자는 기본 core.SolanaSigner 인터페이스(Pubkey, SignMessage, IsAvailable)를 구현합니다. 메서드는 context.Context를 받으며 블로킹 방식으로 동작합니다. 또한 각 백엔드는 다음 기능 인터페이스 중 정확히 하나를 구현합니다.
core.TransactionSigner:SignTransaction은 전달받은 트랜잭션을 그대로 서명합니다.core.ModifyingSigner:ModifyAndSignTransaction은 공급자가 트랜잭션을 재작성한 뒤 서명하도록 합니다.core.SendingSigner:SignAndSendTransaction은 공급자가 서명과 브로드캐스트를 모두 수행하도록 합니다.
각 백엔드 패키지는 Config 구조체와 New 생성자를 제공합니다.
import ("context""fmt""github.com/solana-foundation/solana-go/v2""github.com/solana-foundation/solana-keychain/go/core/v2""github.com/solana-foundation/solana-keychain/go/signers/memory/v2")func signWithKeychain(ctx context.Context, tx *solana.Transaction) error {signer, err := memory.New(memory.Config{PrivateKeyPath: "/path/to/id.json"})if err != nil {return err}if !signer.IsAvailable(ctx) {return core.NewSignerError(core.CodeNotAvailable, "signer offline")}result, err := signer.SignTransaction(ctx, tx)if err != nil {return err}fmt.Println("signature:", result.Signature)fmt.Println("complete:", result.IsComplete())// result.EncodedTransaction is the base64 wire transactionreturn nil}
SignTransaction과 ModifyAndSignTransaction은 core.SignedTransaction(EncodedTransaction, Signature, Completeness)을 반환합니다. 원격 HTTP 백엔드는 선택적으로 HTTPClient와 HTTPClientConfig(타임아웃)를 받을 수 있습니다. 기본 클라이언트는 HTTPS를 강제하며, 직접 제공한 클라이언트는 이를 우회하므로 해당 클라이언트의 보안 상태는 사용자가 책임져야 합니다.
서명자 기능
트랜잭션에 서명할 수 없는 백엔드에는 SignTransaction 메서드가 아예 없습니다. 따라서 기능 여부는 구체 타입에서는 컴파일 타임에 결정되고, core.SolanaSigner에서는 타입 어서션으로 확인합니다.
| 백엔드 | 기능 | SignMessage |
|---|---|---|
| memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfort | TransactionSigner | 예 |
| cdp | TransactionSigner | UTF-8 페이로드만 지원 |
| utila | TransactionSigner | CodeSigningFailed와 함께 실패 |
fordefi 블랙박스 (fordefi.BlackBoxSigner) | TransactionSigner | 예 |
fordefi 네이티브 수동 (fordefi.NativeManualSigner) | ModifyingSigner | 예 |
fordefi 네이티브 자동 (fordefi.NativeAutoSigner) | SendingSigner | 예 |
| crossmint | SendingSigner | CodeSigningFailed와 함께 실패 |
switch s := signer.(type) {case core.SendingSigner:sig, err := s.SignAndSendTransaction(ctx, tx)case core.ModifyingSigner:signed, err := s.ModifyAndSignTransaction(ctx, tx)case core.TransactionSigner:signed, err := s.SignTransaction(ctx, tx)}
초기화
별도의 Init 단계는 없습니다. Privy, Fireblocks, Dfns, Crossmint, Para, Openfort, Utila는 New 내부에서 지갑 주소를 확인하며, 그래서 이들의 생성자는 context.Context를 받습니다. 서명자는 항상 New를 통해 생성하세요. 제로 값 상태의 Crossmint, Para, Utila 서명자는 제로 주소로 서명하는 대신 CodeNotInitialized와 함께 실패합니다.
모든 서명자로 서명 및 전송하기
core.SignAndSendTransaction은 서명자의 형태와 관계없이 트랜잭션을 온체인에 올립니다. SendingSigner는 공급자를 통해 브로드캐스트하며 전송 함수를 무시합니다. 그 외의 모든 형태는 서명만 수행하고, 사용자가 작성한 함수가 base64 결과를 브로드캐스트합니다. ModifyingSigner의 경우 이 결과는 공급자가 재작성한 트랜잭션입니다. Core는 RPC 의존성이 없습니다.
import "github.com/solana-foundation/solana-go/v2/rpc"client := rpc.New("https://api.devnet.solana.com")sig, err := core.SignAndSendTransaction(ctx, signer, tx,func(ctx context.Context, encoded string) (solana.Signature, error) {return client.SendEncodedTransaction(ctx, encoded)})
일괄 서명
일괄 서명은 독립 헬퍼 함수로 제공되며, 동시에 실행되고 속도 제한이 있는 API를 위해 요청별 지연 간격을 선택적으로 지정할 수 있습니다.
sigs, err := core.SignMessages(ctx, signer, [][]byte{msg1, msg2}, core.BatchOptions{MaxConcurrency: 4, // 0 = unboundedRequestDelay: 50 * time.Millisecond,})signed, err := core.SignTransactions(ctx, signer, txs, core.BatchOptions{})
core.SignTransactions는 core.TransactionSigner를 받으므로, 브로드캐스트를 직접 관리하는 서명자(Crossmint, Fordefi 네이티브 자동)와 Fordefi 네이티브 수동은 이 함수로 일괄 처리할 수 없습니다. 이들은 트랜잭션을 하나씩 호출하세요. 호출 시 공급자 측 작업이 생성되는 서명자(UseProgramCall을 사용하는 Fireblocks)는 core.BatchServerSideEffects를 통해 이를 알리며, 이 경우 일괄 처리는 순차적으로 실행됩니다.
백엔드 구성
Memory
PrivateKey(원시 바이트), PrivateKeyString(base58 또는 [1,2,...] 바이트 배열), PrivateKeyPath(Solana CLI keypair 파일) 중 정확히 하나만 제공하세요.
import "github.com/solana-foundation/solana-keychain/go/signers/memory/v2"signer, err := memory.New(memory.Config{PrivateKeyPath: "/path/to/id.json",})
Go에는 메모리를 확실하게 0으로 지우는 방법이 없습니다. 가비지 컬렉터가 수집되기 전까지 키 바이트를 복사해 보관할 수 있습니다(memory keypair, 그리고 Crossmint와 Openfort가 로컬에서 파생하는 키가 해당됩니다). 로컬 키 백엔드를 사용할 때는 프로세스 전체 메모리를 민감한 정보로 취급하세요.
HashiCorp Vault
import "github.com/solana-foundation/solana-keychain/go/signers/vault/v2"signer, err := vault.New(vault.Config{VaultAddr: "https://vault.example.com:8200",Token: "hvs.xxxxx",KeyName: "my-solana-key",Pubkey: "base58_public_key",})
AWS KMS
import "github.com/solana-foundation/solana-keychain/go/signers/awskms/v2"signer, err := awskms.New(ctx, awskms.Config{KeyID: "alias/my-solana-key",PublicKey: "base58_public_key",Region: "us-east-1", // optional})
GCP KMS
import "github.com/solana-foundation/solana-keychain/go/signers/gcpkms/v2"signer, err := gcpkms.New(ctx, gcpkms.Config{KeyName: "projects/my-project/locations/us-east1/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1",PublicKey: "base58_public_key",})if err != nil {return err}defer signer.Close()
Privy
import "github.com/solana-foundation/solana-keychain/go/signers/privy/v2"signer, err := privy.New(ctx, privy.Config{AppID: "app_id",AppSecret: "app_secret",WalletID: "wallet_id",})
Turnkey
import "github.com/solana-foundation/solana-keychain/go/signers/turnkey/v2"signer, err := turnkey.New(turnkey.Config{APIPublicKey: "api_public_key",APIPrivateKey: "api_private_key",OrganizationID: "org_id",PrivateKeyID: "private_key_id",PublicKey: "base58_public_key",})
Fireblocks
import "github.com/solana-foundation/solana-keychain/go/signers/fireblocks/v2"signer, err := fireblocks.New(ctx, fireblocks.Config{APIKey: "api_key",PrivateKeyPEM: "-----BEGIN RSA PRIVATE KEY-----\n...",VaultAccountID: "0",AssetID: "SOL", // or "SOL_TEST" for devnetUseProgramCall: false, // true signs via sign-only PROGRAM_CALL (legacy/v0, hot wallet)})
CDP (Coinbase Developer Platform)
import "github.com/solana-foundation/solana-keychain/go/signers/cdp/v2"signer, err := cdp.New(cdp.Config{APIKeyID: "api_key_id",APIKeySecret: "api_key_secret", // base64 Ed25519WalletSecret: "wallet_secret", // base64 PKCS#8 DERAddress: "base58_address",Network: cdp.NetworkMainnet, // or cdp.NetworkDevnet; required for address lookup tables})
CDP의 SignMessage는 UTF-8 페이로드만 허용합니다.
Crossmint
Crossmint는 전송 전용입니다. API가 승인된 모든 트랜잭션을 서버 측에서 실행하므로, *crossmint.Signer에는 SignAndSendTransaction만 있고 SignTransaction은 없습니다. 트랜잭션은 수정되지 않으며, 반환되는 서명은 Crossmint가 온체인에 반영한 트랜잭션을 식별합니다. Crossmint가 가스비를 대납하는 경우 이 트랜잭션은 사용자의 트랜잭션과 다를 수 있습니다.
import "github.com/solana-foundation/solana-keychain/go/signers/crossmint/v2"signer, err := crossmint.New(ctx, crossmint.Config{APIKey: "api_key",WalletLocator: "wallet_locator",SignerSecret: "", // optional xmsk1_ secret: auto-approves what Crossmint presents})sig, err := signer.SignAndSendTransaction(ctx, tx)
Dfns
import "github.com/solana-foundation/solana-keychain/go/signers/dfns/v2"signer, err := dfns.New(ctx, dfns.Config{AuthToken: "auth_token",CredID: "cred_id",PrivateKeyPEM: "-----BEGIN EC PRIVATE KEY-----\n...",WalletID: "wallet_id",})
Openfort
import "github.com/solana-foundation/solana-keychain/go/signers/openfort/v2"signer, err := openfort.New(ctx, openfort.Config{SecretKey: "sk_live_...",AccountID: "acc_...",WalletSecret: "wallet_secret", // base64 PKCS#8 DER or PEM})
Para
import "github.com/solana-foundation/solana-keychain/go/signers/para/v2"signer, err := para.New(ctx, para.Config{APIKey: "sk_...",WalletID: "wallet_id",})
Utila
Utila는 트랜잭션 서명만 지원합니다. SignMessage는 실패하며, 서명된 트랜잭션은 직접 브로드캐스트해야 합니다.
import "github.com/solana-foundation/solana-keychain/go/signers/utila/v2"signer, err := utila.New(ctx, utila.Config{ServiceAccountEmail: "sa@example.com",ServiceAccountPrivateKeyPEM: "-----BEGIN PRIVATE KEY-----\n...",VaultID: "vault_id",WalletID: "wallet_id",Network: "networks/solana-devnet",})
Fordefi
Fordefi는 세 가지 서명자 유형으로 제공되며, Config.Chain과 Config.PushMode에 따라 생성 시점에 고정됩니다. 세 가지 모두 메시지 서명을 지원합니다.
| Config | 유형 | 진입점 |
|---|---|---|
Chain 미설정 | *fordefi.BlackBoxSigner | SignTransaction |
Chain 설정, PushMode 미설정 또는 PushModeAuto | *fordefi.NativeAutoSigner | SignAndSendTransaction |
Chain 설정, PushMode: PushModeManual | *fordefi.NativeManualSigner | ModifyAndSignTransaction |
fordefi.New는 구성에 따라 분기하여 core.SolanaSigner를 반환합니다. fordefi.NewBlackBox, fordefi.NewNativeAuto, fordefi.NewNativeManual은 구체 타입을 반환하며 다른 모드의 구성은 거부합니다. API 요청은 ECDSA P-256 요청 서명으로 인증됩니다. PrivateKeyPEM을 제공하거나, 해당 키를 KMS/HSM에 보관하려면 RequestSigner를 제공하되, 둘 중 정확히 하나만 지정하세요.
네이티브 자동 모드: Fordefi가 블록해시와 수수료를 교체한 뒤 서명하고 브로드캐스트할 수 있습니다. 유일한 필수 서명자가 볼트(vault)인 서명되지 않은 트랜잭션만 허용합니다.
import "github.com/solana-foundation/solana-keychain/go/signers/fordefi/v2"signer, err := fordefi.NewNativeAuto(ctx, fordefi.Config{AccessToken: os.Getenv("FORDEFI_ACCESS_TOKEN"),VaultID: os.Getenv("FORDEFI_VAULT_ID"),PublicKey: os.Getenv("FORDEFI_PUBLIC_KEY"), // Solana vault address (base58)PrivateKeyPEM: string(pemBytes),Chain: fordefi.ChainSolanaDevnet, // or fordefi.ChainSolanaMainnetFee: &fordefi.Fee{Type: fordefi.FeeTypePriority,PriorityLevel: fordefi.PriorityMedium,},})sig, err := signer.SignAndSendTransaction(ctx, tx)
네이티브 수동 모드: PushMode: fordefi.PushModeManual로 설정합니다. Fordefi가 메시지(최근 블록해시, Compute Budget 수수료 명령)를 재작성하고 브로드캐스트 없이 서명합니다. ModifyAndSignTransaction은 tx를 Fordefi가 서명한 트랜잭션으로 교체하므로, 항상 이 트랜잭션을 이어서 사용하세요. 재작성으로 기존 서명이 무효화되므로 Fordefi가 수수료 지불자여야 하며 다른 필수 서명자보다 먼저 서명해야 합니다. 재작성 내용은 차이 비교(diff)가 제공되지 않으므로, 브로드캐스트하기 전에 결과를 직접 검토하세요.
signer, err := fordefi.NewNativeManual(ctx, fordefi.Config{AccessToken: os.Getenv("FORDEFI_ACCESS_TOKEN"),VaultID: os.Getenv("FORDEFI_VAULT_ID"),PublicKey: os.Getenv("FORDEFI_PUBLIC_KEY"),PrivateKeyPEM: string(pemBytes),Chain: fordefi.ChainSolanaDevnet,PushMode: fordefi.PushModeManual,})signed, err := signer.ModifyAndSignTransaction(ctx, tx)
블랙박스 모드: Chain을 설정하지 마세요. Fordefi는 사용자가 제공한 메시지 바이트를 그대로 서명하며 브로드캐스트하지 않습니다. Fordefi 블랙박스 볼트와 함께 사용하세요.
signer, err := fordefi.NewBlackBox(ctx, fordefi.Config{AccessToken: os.Getenv("FORDEFI_ACCESS_TOKEN"),VaultID: os.Getenv("FORDEFI_BB_VAULT_ID"),PublicKey: os.Getenv("FORDEFI_BB_PUBLIC_KEY"),PrivateKeyPEM: string(pemBytes),})
통합 패키지가 없는 이유
Rust에는 Signer 열거형이, TypeScript에는 createKeychainSigner가 있지만 Go에는 의도적으로 둘 다 없습니다. Go는 런타임 디스패치 switch를 가로지르는 데드 코드 제거를 하지 않으므로, 통합 패키지를 만들면 모든 백엔드의 벤더 SDK(AWS, GCP 등)가 모든 사용자의 빌드에 포함됩니다. 필요한 백엔드 패키지를 임포트하는 것이 곧 선택 방법입니다. 런타임에 백엔드를 선택하려면 지원하려는 백엔드만 대상으로 직접 switch를 작성하고, 그 결과를 core.SolanaSigner로 보관하세요.
오류 처리
모든 오류는 안정적인 Code(CodeConfigError, CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed, CodeNotInitialized, CodeBroadcastUnconfirmed 등)를 가진 *core.SignerError입니다. Error()는 코드별로 고정된 메시지만 반환하며, 키 자료나 공급자의 원시 응답은 절대 포함하지 않습니다. errors.As, errors.Is(err, &core.SignerError{Code: ...}) 또는 core.CodeOf(err)로 확인하세요.
Crossmint와 Fordefi 네이티브 자동 모드에서는 실패했다고 해서 아무것도 반영되지 않았다는 뜻은 아닙니다. 공급자가 트랜잭션을 수락했을 수 있는 경우 코드는 core.CodeBroadcastUnconfirmed("SIGNER_BROADCAST_UNCONFIRMED")입니다. 생성 요청이 수락되었다면 ProviderTxID가 설정되고, IdempotencyKey에는 메시지에서 파생된 키가 담깁니다. 바이트가 완전히 동일한 트랜잭션을 다시 전송하는 것은 안전하지만, 다시 구성한 트랜잭션(예: 새 블록해시 사용)은 새로운 전송으로 처리됩니다. core.SignAndSendTransaction도 사용자의 전송 함수가 실패하면 이 코드를 보고하며, 이때 수수료 지불자의 서명은 TransactionSignature에 담깁니다. Fireblocks PROGRAM_CALL은 서명 전용이며 키를 전송하지 않습니다. 폴링 결과 Fireblocks가 그래도 브로드캐스트한 것으로 확인된 경우에만 이 코드를 보고합니다.
sig, err := core.SignAndSendTransaction(ctx, signer, tx, send)var se *core.SignerErrorif errors.As(err, &se) && se.Code == core.CodeBroadcastUnconfirmed {// Reconcile se.ProviderTxID, se.IdempotencyKey or se.TransactionSignature// with the provider before retrying.}
각 서명 방식이 보장하는 내용은 보안 모델에서, 전체 모듈 목록은 Go README에서, API 레퍼런스는 pkg.go.dev에서 확인하세요.
사용자 정의 서명자 추가하기
core.SolanaSigner와 기능 인터페이스 정확히 하나를 구현하고, 백엔드가 지원할 수 없는 메서드는 추가하지 마세요. 호출자는 타입 어서션으로 기능을 감지합니다. 추가 키 관리 서비스를 통합하려면 서명자 추가 가이드를 참조하세요.
Is this page helpful?