Go

2.0.0 — первый стабильный релиз модулей Go, поэтому переносить ничего не нужно. В руководстве по миграции на 2.0 описано такое же разделение возможностей для Rust и TypeScript.

Установка

Каждый бэкенд — это отдельный модуль Go, поэтому в вашу сборку и граф модулей попадают только те бэкенды, которые вы импортируете:

go get github.com/solana-foundation/solana-keychain/go/core/v2 # Interfaces, errors, helpers
go get github.com/solana-foundation/solana-keychain/go/signers/memory/v2 # Local keypair signer
go get github.com/solana-foundation/solana-keychain/go/signers/vault/v2 # HashiCorp Vault
go get github.com/solana-foundation/solana-keychain/go/signers/awskms/v2 # AWS KMS
go get github.com/solana-foundation/solana-keychain/go/signers/gcpkms/v2 # GCP KMS
go get github.com/solana-foundation/solana-keychain/go/signers/privy/v2 # Privy
go get github.com/solana-foundation/solana-keychain/go/signers/turnkey/v2 # Turnkey
go get github.com/solana-foundation/solana-keychain/go/signers/fireblocks/v2 # Fireblocks
go get github.com/solana-foundation/solana-keychain/go/signers/cdp/v2 # Coinbase Developer Platform
go get github.com/solana-foundation/solana-keychain/go/signers/crossmint/v2 # Crossmint
go get github.com/solana-foundation/solana-keychain/go/signers/dfns/v2 # Dfns
go get github.com/solana-foundation/solana-keychain/go/signers/openfort/v2 # Openfort
go get github.com/solana-foundation/solana-keychain/go/signers/para/v2 # Para
go get github.com/solana-foundation/solana-keychain/go/signers/utila/v2 # Utila
go 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, которому нужен Go 1.25.8 из-за минимальной версии, заданной в google.golang.org/api. Ledger существует только для Rust, модуля Go для него нет.

Транзакции solana-go v2 и v1

Модули построены на github.com/solana-foundation/solana-go/v2 v2.0.0 и принимают его *solana.Transaction. Поддерживаются транзакции Legacy, v0 и v1 (solana.MessageVersionV1), кроме Fireblocks с UseProgramCall: true: он принимает только 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 transaction
return 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, openfortTransactionSignerДа
cdpTransactionSignerТолько данные в UTF-8
utilaTransactionSignerЗавершается ошибкой CodeSigningFailed
fordefi black box (fordefi.BlackBoxSigner)TransactionSignerДа
fordefi native manual (fordefi.NativeManualSigner)ModifyingSignerДа
fordefi native auto (fordefi.NativeAutoSigner)SendingSignerДа
crossmintSendingSignerЗавершается ошибкой 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 = unbounded
RequestDelay: 50 * time.Millisecond,
})
signed, err := core.SignTransactions(ctx, signer, txs, core.BatchOptions{})

core.SignTransactions принимает core.TransactionSigner, поэтому подписанты, управляющие отправкой (Crossmint, Fordefi native auto), и Fordefi native manual нельзя использовать в пакетном режиме; вызывайте их для каждой транзакции по отдельности. Подписант, вызовы которого создают работу на стороне провайдера (Fireblocks с UseProgramCall), сообщает об этом через core.BatchServerSideEffects, и тогда пакет выполняется последовательно.

Конфигурация бэкендов

Memory

Укажите ровно одно из значений: PrivateKey (сырые байты), PrivateKeyString (base58 или массив байтов [1,2,...]) либо PrivateKeyPath (файл keypair из Solana CLI):

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 devnet
UseProgramCall: 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 Ed25519
WalletSecret: "wallet_secret", // base64 PKCS#8 DER
Address: "base58_address",
Network: cdp.NetworkMainnet, // or cdp.NetworkDevnet; required for address lookup tables
})

SignMessage в CDP принимает только данные в 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. Все три подписывают сообщения:

КонфигурацияТипТочка входа
Chain не задан*fordefi.BlackBoxSignerSignTransaction
Chain задан, PushMode не задан или PushModeAuto*fordefi.NativeAutoSignerSignAndSendTransaction
Chain задан, PushMode: PushModeManual*fordefi.NativeManualSignerModifyAndSignTransaction

fordefi.New выбирает тип по конфигурации и возвращает core.SolanaSigner; fordefi.NewBlackBox, fordefi.NewNativeAuto и fordefi.NewNativeManual возвращают конкретный тип и отклоняют конфигурации других режимов. API-запросы аутентифицируются подписью запроса ECDSA P-256: укажите ровно одно из значений — PrivateKeyPEM либо RequestSigner, чтобы хранить этот ключ в KMS/HSM.

Режим native auto: 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.ChainSolanaMainnet
Fee: &fordefi.Fee{
Type: fordefi.FeeTypePriority,
PriorityLevel: fordefi.PriorityMedium,
},
})
sig, err := signer.SignAndSendTransaction(ctx, tx)

Режим native manual: задайте PushMode: fordefi.PushModeManual. Fordefi переписывает сообщение (recent 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)

Режим 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),
})

Почему нет объединяющего пакета

В Rust есть enum Signer, а в TypeScript — createKeychainSigner; в Go намеренно нет ни того, ни другого. Go не удаляет мёртвый код при диспетчеризации через switch во время выполнения, поэтому объединяющий пакет затянул бы SDK вендоров всех бэкендов (AWS, GCP и остальных) в сборку каждого потребителя. Селектор — это импорт нужного вам пакета бэкенда. Чтобы выбирать бэкенд во время выполнения, напишите switch в своём коде только для поддерживаемых вами бэкендов и храните результат как core.SolanaSigner.

Обработка ошибок

Каждая ошибка — это *core.SignerError со стабильным Code (CodeConfigError, CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed, CodeNotInitialized, CodeBroadcastUnconfirmed и другие). Error() возвращает только фиксированное сообщение для каждого кода, но никогда не ключевой материал и не необработанные ответы провайдера. Для сопоставления используйте errors.As, errors.Is(err, &core.SignerError{Code: ...}) или core.CodeOf(err).

В Crossmint и в режиме Fordefi native auto сбой не означает, что транзакция не попала в сеть. Если провайдер мог принять транзакцию, код ошибки — 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.SignerError
if errors.As(err, &se) && se.Code == core.CodeBroadcastUnconfirmed {
// Reconcile se.ProviderTxID, se.IdempotencyKey or se.TransactionSignature
// with the provider before retrying.
}

Что гарантирует каждый вид подписи, описано в модели безопасности, полный список модулей — в README для Go, а справочник по API — на pkg.go.dev.

Добавление собственных подписантов

Реализуйте core.SolanaSigner и ровно один интерфейс возможностей и не добавляйте методы, которые ваш бэкенд не может поддерживать: вызывающий код определяет возможности приведением типа. См. руководство по добавлению подписантов, чтобы интегрировать дополнительные сервисы управления ключами.

Is this page helpful?