2.0.0은 Go 모듈의 첫 번째 안정 릴리스이므로 마이그레이션할 내용이 없습니다. 2.0 마이그레이션 가이드에서는 Rust와 TypeScript에 적용된 것과 동일한 기능 분리를 다룹니다.
설치
모든 백엔드는 각각 별도의 Go 모듈이므로, 빌드와 모듈 그래프에는 import한 백엔드만 포함됩니다:
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만 허용합니다.
기본 사용법
모든 서명자(signer)는 기본 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 black box (fordefi.BlackBoxSigner) | TransactionSigner | 예 |
fordefi native manual (fordefi.NativeManualSigner) | ModifyingSigner | 예 |
fordefi native auto (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를 위해 요청별 지연 간격(stagger)을 선택적으로 지정할 수 있습니다:
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 native auto)와 Fordefi native manual은 이 함수로 배치 처리할 수 없습니다. 이들은 트랜잭션을 한 번에 하나씩 호출하세요. 호출 시 공급자 측에서 작업이 생성되는 서명자(UseProgramCall을 사용하는 Fireblocks)는 core.BatchServerSideEffects를 통해 이를 알리며, 이 경우 배치는 순차적으로 실행됩니다.
백엔드 구성
Memory
PrivateKey(raw 바이트), 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는 전송 전용입니다. 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 중 정확히 하나를 제공하세요.
Native auto 모드: Fordefi가 블록해시와 수수료를 교체한 뒤 서명하고 브로드캐스트합니다. 필요한 서명자가 볼트뿐인, 서명되지 않은 트랜잭션만 허용합니다.
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)
Native manual 모드: PushMode: fordefi.PushModeManual로 설정합니다. Fordefi는 메시지(최근 블록해시, Compute Budget 수수료 명령어)를 재작성하고 브로드캐스트 없이 서명합니다. ModifyAndSignTransaction은 tx를 Fordefi가 서명한 트랜잭션으로 교체하므로, 반드시 그 트랜잭션에서 이어서 진행하세요. 재작성하면 기존 서명이 무효화되므로 Fordefi가 수수료 지불자(fee payer)여야 하며 다른 필수 서명자보다 먼저 서명해야 합니다. 재작성 내용의 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)
Black box 모드: Chain을 설정하지 않습니다. Fordefi는 사용자가 보낸 메시지 바이트를 그대로 서명하며 브로드캐스트하지 않습니다. Fordefi black box 볼트와 함께 사용하세요.
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),})
통합(umbrella) 패키지가 없는 이유
Rust에는 Signer enum이, TypeScript에는 createKeychainSigner가 있지만 Go에는 의도적으로 둘 다 없습니다. Go는 런타임 디스패치 switch 전반에 걸쳐 데드 코드를 제거하지 않기 때문에, umbrella 패키지를 두면 모든 백엔드의 벤더 SDK(AWS, GCP 등)가 모든 사용자의 빌드에 포함되고 맙니다. 필요한 백엔드 패키지를 import하는 것이 곧 선택 방법입니다. 런타임에 백엔드를 고르려면 지원하려는 백엔드만 대상으로 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 native auto 모드에서는 실패했다고 해서 아무것도 반영되지 않았다는 뜻이 아닙니다. 공급자가 트랜잭션을 수락했을 가능성이 있는 경우, 코드는 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?