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 должен быть плательщиком комиссии и подписывать раньше любого другого обязательного подписанта, поскольку перезапись аннулирует существующие подписи. Изменения не сравниваются (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 подписывает точные байты вашего сообщения и не рассылает его. Используйте этот режим с black box vault в 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 есть 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?