Go

La versione 2.0.0 è la prima release stabile dei moduli Go, quindi non c'è nulla da migrare. La guida alla migrazione 2.0 illustra la stessa suddivisione delle capacità per Rust e TypeScript.

Installazione

Ogni backend è un modulo Go a sé stante, quindi la tua build e il tuo grafo dei moduli contengono solo i backend che importi:

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

I moduli sono taggati go/<module>/v2.0.0 (ad esempio go/signers/memory/v2.0.0). Ogni modulo richiede Go 1.25.0, eccetto gcpkms, che richiede Go 1.25.8 a causa del requisito minimo imposto da google.golang.org/api. Ledger è disponibile solo per Rust e non ha un modulo Go.

solana-go v2 e transazioni v1

I moduli sono basati su github.com/solana-foundation/solana-go/v2 v2.0.0 e accettano il suo *solana.Transaction. Sono supportate le transazioni legacy, v0 e v1 (solana.MessageVersionV1), tranne che da Fireblocks con UseProgramCall: true, che accetta solo legacy e v0.

Utilizzo di base

Ogni signer implementa l'interfaccia base core.SolanaSigner (Pubkey, SignMessage, IsAvailable). I metodi accettano un context.Context e sono bloccanti. Ogni backend implementa inoltre esattamente un'interfaccia di capacità:

  • core.TransactionSigner: SignTransaction firma la transazione così com'è.
  • core.ModifyingSigner: ModifyAndSignTransaction consente al provider di riscrivere la transazione e poi la firma.
  • core.SendingSigner: SignAndSendTransaction fa firmare e trasmettere la transazione dal provider.

Ogni pacchetto backend espone una struct Config e un costruttore 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 e ModifyAndSignTransaction restituiscono un core.SignedTransaction (EncodedTransaction, Signature, Completeness). I backend HTTP remoti accettano un HTTPClient e un HTTPClientConfig opzionali (timeout). Il client predefinito impone HTTPS; fornirne uno proprio aggira questo vincolo, e la sua sicurezza resta sotto la tua responsabilità.

Capacità dei signer

Un backend che non può firmare una transazione non ha alcun metodo SignTransaction, quindi la capacità è un fatto noto in fase di compilazione per i tipi concreti e una type assertion per un core.SolanaSigner:

BackendCapacitàSignMessage
memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfortTransactionSignerSì
cdpTransactionSignerSolo payload UTF-8
utilaTransactionSignerFallisce con CodeSigningFailed
fordefi black box (fordefi.BlackBoxSigner)TransactionSignerSì
fordefi native manual (fordefi.NativeManualSigner)ModifyingSignerSì
fordefi native auto (fordefi.NativeAutoSigner)SendingSignerSì
crossmintSendingSignerFallisce con 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)
}

Inizializzazione

Non esiste un passaggio Init separato. Privy, Fireblocks, Dfns, Crossmint, Para, Openfort e Utila risolvono l'indirizzo del wallet all'interno di New, motivo per cui i loro costruttori accettano un context.Context. Crea sempre i signer tramite New: un signer Crossmint, Para o Utila a valore zero fallisce con CodeNotInitialized anziché firmare per l'indirizzo zero.

Firmare e inviare con qualsiasi signer

core.SignAndSendTransaction porta una transazione on-chain indipendentemente dalla forma del signer. Un SendingSigner trasmette tramite il proprio provider e ignora la funzione di invio; ogni altra forma firma e la tua funzione trasmette il risultato in base64, che per un ModifyingSigner è la transazione riscritta dal suo provider. Core non ha alcuna dipendenza 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)
})

Firma in batch

La firma in batch è fornita come helper liberi, concorrenti, con uno sfasamento opzionale per richiesta per le API soggette a rate limit:

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 accetta un core.TransactionSigner, quindi i signer con broadcast gestito (Crossmint, Fordefi native auto) e Fordefi native manual non possono essere usati in batch; chiamali una transazione alla volta. Un signer le cui chiamate creano lavoro lato provider (Fireblocks con UseProgramCall) lo segnala tramite core.BatchServerSideEffects, e il batch viene quindi eseguito in sequenza.

Configurazione dei backend

Memory

Fornisci esattamente uno tra PrivateKey (byte grezzi), PrivateKeyString (base58 o un array di byte [1,2,...]) o PrivateKeyPath (file keypair della 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 non offre alcun modo affidabile per azzerare la memoria: il garbage collector può copiare e conservare i byte delle chiavi (keypair in memoria e chiavi che Crossmint e Openfort derivano localmente) fino alla raccolta. Considera l'intera memoria del processo come sensibile quando usi backend con chiavi locali.

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 di CDP accetta solo payload UTF-8.

Crossmint

Crossmint è solo per l'invio: la sua API esegue ogni transazione approvata lato server, quindi *crossmint.Signer ha SignAndSendTransaction e non ha SignTransaction. La tua transazione non viene mai modificata; la firma restituita identifica la transazione che Crossmint ha effettivamente registrato, che può differire dalla tua se Crossmint sponsorizza il 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 firma solo le transazioni; SignMessage fallisce e la transazione firmata devi trasmetterla tu stesso.

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 è disponibile in tre tipi di signer, stabiliti in fase di costruzione da Config.Chain e Config.PushMode. Tutti e tre firmano messaggi:

ConfigTipoPunto di ingresso
Chain non impostato*fordefi.BlackBoxSignerSignTransaction
Chain impostato, PushMode non impostato o PushModeAuto*fordefi.NativeAutoSignerSignAndSendTransaction
Chain impostato, PushMode: PushModeManual*fordefi.NativeManualSignerModifyAndSignTransaction

fordefi.New smista in base alla configurazione e restituisce un core.SolanaSigner; fordefi.NewBlackBox, fordefi.NewNativeAuto e fordefi.NewNativeManual restituiscono il tipo concreto e rifiutano le configurazioni delle altre modalità. Le richieste API vengono autenticate con una firma della richiesta ECDSA P-256: fornisci esattamente uno tra PrivateKeyPEM oppure un RequestSigner per mantenere quella chiave in un KMS/HSM.

Modalità native auto: Fordefi può sostituire il blockhash e le commissioni, quindi firma e trasmette. Accetta solo una transazione non firmata il cui unico firmatario richiesto è il 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)

Modalità native manual: imposta PushMode: fordefi.PushModeManual. Fordefi riscrive il messaggio (recent blockhash, istruzioni di commissione del Compute Budget) e lo firma senza trasmetterlo. ModifyAndSignTransaction sostituisce tx con la transazione firmata da Fordefi: continua sempre da quella. Fordefi deve essere il fee payer e deve firmare prima di qualsiasi altro firmatario richiesto, poiché la riscrittura invalida le firme esistenti. La riscrittura non produce un diff, quindi ispeziona il risultato prima di trasmettere.

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)

Modalità black box: lascia Chain non impostato. Fordefi firma esattamente i byte del tuo messaggio e non trasmette. Usala con un vault black box di 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),
})

Perché non esiste un pacchetto ombrello

Rust ha un enum Signer e TypeScript ha createKeychainSigner; Go volutamente non ha né l'uno né l'altro. Go non elimina il codice morto attraverso un'istruzione switch di dispatch a runtime, quindi un pacchetto ombrello imporrebbe l'SDK del fornitore di ogni backend (AWS, GCP e gli altri) nella build di ogni consumatore. Importare il pacchetto del backend di cui hai bisogno è il selettore. Per scegliere un backend a runtime, scrivi lo switch nel tuo codice solo sui backend che supporti e conserva il risultato come core.SolanaSigner.

Gestione degli errori

Ogni errore è un *core.SignerError con un Code stabile (CodeConfigError, CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed, CodeNotInitialized, CodeBroadcastUnconfirmed e altri). Error() restituisce solo un messaggio fisso per codice, mai materiale delle chiavi né risposte grezze del provider. Esegui il confronto con errors.As, errors.Is(err, &core.SignerError{Code: ...}) oppure core.CodeOf(err).

Con Crossmint e Fordefi in modalità native auto, un errore non significa che nulla sia stato registrato. Quando il provider potrebbe aver accettato la transazione, il codice è core.CodeBroadcastUnconfirmed ("SIGNER_BROADCAST_UNCONFIRMED"). ProviderTxID è impostato quando la creazione è stata accettata, e IdempotencyKey contiene la chiave derivata dal messaggio. Reinviare la transazione identica byte per byte è sicuro; una ricostruita (ad esempio con un nuovo blockhash) è un nuovo trasferimento. core.SignAndSendTransaction segnala questo codice anche quando la tua funzione di invio fallisce, con la firma del fee payer in TransactionSignature. Fireblocks PROGRAM_CALL è solo di firma e non invia alcuna chiave; segnala questo codice solo quando un polling mostra che Fireblocks ha comunque trasmesso.

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.
}

Consulta il modello di sicurezza per sapere cosa garantisce ogni forma di firma, il README di Go per l'elenco completo dei moduli e pkg.go.dev per il riferimento API.

Aggiungere signer personalizzati

Implementa core.SolanaSigner più esattamente un'interfaccia di capacità, e non aggiungere alcun metodo che il tuo backend non possa onorare: i chiamanti rilevano la capacità tramite type assertion. Consulta la guida Adding Signers per integrare ulteriori servizi di gestione delle chiavi.

Is this page helpful?

© 2026 Solana Foundation. Tutti i diritti riservati.