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)。除 gcpkms 外,所有模块都要求 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。支持 Legacy、v0 和 v1(solana.MessageVersionV1)交易,但启用 UseProgramCall: true 的 Fireblocks 除外,它只接受 Legacy 和 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 没有可靠的内存清零方式:垃圾回收器可能会复制并保留密钥字节(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 代付 gas,则可能与你的交易不同。
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 确定。三者都支持消息签名:
| 配置 | 类型 | 入口方法 |
|---|---|---|
未设置 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 和 RequestSigner 中恰好提供一个,后者可将该密钥保存在 KMS/HSM 中。
原生自动模式:Fordefi 可能会替换 blockhash 和手续费,然后签名并广播。它只接受唯一必需签名者为该 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 会改写消息(最近的 blockhash、Compute Budget 手续费指令)并签名,但不广播。ModifyAndSignTransaction 会将 tx 替换为 Fordefi 签名后的交易:请始终基于它继续操作。Fordefi 必须是手续费支付方,且必须先于任何其他必需签名者签名,因为改写会使已有签名失效。改写前后不会生成差异对比,因此广播前请检查结果。
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 黑盒 vault 搭配使用。
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 Package)
Rust 有 Signer 枚举,TypeScript 有 createKeychainSigner;Go 则有意两者皆无。Go 不会跨运行时分派 switch 消除死代码,因此总包会把每个后端的供应商 SDK(AWS、GCP 等)都带入每个使用者的构建中。导入你所需的后端包本身就是选择方式。若要在运行时选择后端,请在你自己的代码中仅针对你支持的后端编写 switch,并将结果保存为 core.SolanaSigner。
错误处理
每个错误都是带有稳定 Code 的 *core.SignerError(CodeConfigError、CodeHTTPError、CodeRemoteAPIError、CodeSigningFailed、CodeNotInitialized、CodeBroadcastUnconfirmed 等)。Error() 每个错误码只返回固定消息,绝不包含密钥材料或服务提供方的原始响应。可使用 errors.As、errors.Is(err, &core.SignerError{Code: ...}) 或 core.CodeOf(err) 进行匹配。
在 Crossmint 和 Fordefi 原生自动模式下,失败并不意味着交易没有上链。当服务提供方可能已接受该交易时,错误码为 core.CodeBroadcastUnconfirmed("SIGNER_BROADCAST_UNCONFIRMED")。创建请求被接受时会设置 ProviderTxID,IdempotencyKey 则携带由消息派生的密钥。重新发送字节完全相同的交易是安全的;重新构建的交易(例如使用新的 blockhash)则属于新的转账。当你的发送函数失败时,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?