2.0.0 est la première version stable des modules Go, il n'y a donc rien à migrer. Le guide de migration vers la version 2.0 couvre la même répartition des capacités pour Rust et TypeScript.
Installation
Chaque backend est son propre module Go, de sorte que votre build et votre graphe de modules ne contiennent que les backends que vous importez :
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
Les modules sont taggés go/<module>/v2.0.0 (par exemple
go/signers/memory/v2.0.0). Chaque module requiert Go 1.25.0, sauf gcpkms,
qui requiert Go 1.25.8 en raison de la version minimale imposée par google.golang.org/api.
Ledger est uniquement disponible en Rust et n'a pas de module Go.
Transactions solana-go v2 et v1
Les modules sont construits sur
github.com/solana-foundation/solana-go/v2
v2.0.0 et prennent en charge son type *solana.Transaction. Les transactions legacy, v0 et v1
(solana.MessageVersionV1) sont prises en charge, sauf par Fireblocks
avec UseProgramCall: true, qui n'accepte que les transactions legacy et v0.
Utilisation de base
Chaque signataire implémente l'interface de base
core.SolanaSigner
(Pubkey, SignMessage, IsAvailable). Les méthodes prennent un context.Context et
sont bloquantes. Chaque backend implémente en outre exactement une interface de capacité :
core.TransactionSigner:SignTransactionsigne votre transaction telle qu'elle est fournie.core.ModifyingSigner:ModifyAndSignTransactionpermet au fournisseur de réécrire la transaction, puis la signe.core.SendingSigner:SignAndSendTransactiondemande au fournisseur de signer et de diffuser la transaction.
Chaque package de backend expose une struct Config et un constructeur 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 et ModifyAndSignTransaction renvoient une
core.SignedTransaction (EncodedTransaction, Signature, Completeness).
Les backends HTTP distants acceptent un HTTPClient et un HTTPClientConfig facultatifs
(délais d'expiration). Le client par défaut impose HTTPS ; si vous fournissez le vôtre, cette contrainte est contournée
et vous êtes responsable de sa posture de sécurité.
Capacités des signataires
Un backend qui ne peut pas signer de transaction ne possède aucune méthode SignTransaction,
donc la capacité est un fait connu à la compilation pour les types concrets et une assertion
de type pour un core.SolanaSigner :
| Backend | Capacité | SignMessage |
|---|---|---|
| memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfort | TransactionSigner | Oui |
| cdp | TransactionSigner | Charges utiles UTF-8 uniquement |
| utila | TransactionSigner | Échoue avec CodeSigningFailed |
fordefi black box (fordefi.BlackBoxSigner) | TransactionSigner | Oui |
fordefi native manual (fordefi.NativeManualSigner) | ModifyingSigner | Oui |
fordefi native auto (fordefi.NativeAutoSigner) | SendingSigner | Oui |
| crossmint | SendingSigner | Échoue avec 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)}
Initialisation
Il n'y a pas d'étape Init distincte. Privy, Fireblocks, Dfns, Crossmint, Para,
Openfort et Utila résolvent l'adresse du portefeuille dans New, c'est pourquoi leurs
constructeurs prennent un context.Context. Construisez toujours les signataires via New : un
signataire Crossmint, Para ou Utila à valeur zéro échoue avec CodeNotInitialized
plutôt que de signer pour l'adresse zéro.
Signer et envoyer avec n'importe quel signataire
core.SignAndSendTransaction met une transaction on-chain quelle que soit la forme
du signataire. Un SendingSigner diffuse via son fournisseur et ignore la fonction
d'envoi ; tout autre type signe et votre fonction diffuse le résultat en base64,
qui, pour un ModifyingSigner, est la transaction réécrite par son fournisseur.
Core n'a aucune dépendance 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)})
Signature par lots
La signature par lots est fournie sous forme de fonctions utilitaires libres, exécutées en parallèle, avec un décalage facultatif par requête pour les API soumises à des limites de débit :
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 prend un core.TransactionSigner, donc les
signataires gérant eux-mêmes la diffusion (Crossmint, Fordefi native auto) et Fordefi native
manual ne peuvent pas être traités par lots ; appelez-les une transaction à la fois. Un
signataire dont les appels créent du travail côté fournisseur (Fireblocks avec UseProgramCall)
le signale via core.BatchServerSideEffects, et le lot s'exécute alors
séquentiellement.
Configuration des backends
Memory
Fournissez exactement l'un des champs suivants : PrivateKey (octets bruts), PrivateKeyString (base58 ou un
tableau d'octets [1,2,...]) ou PrivateKeyPath (fichier keypair de la CLI Solana) :
import "github.com/solana-foundation/solana-keychain/go/signers/memory/v2"signer, err := memory.New(memory.Config{PrivateKeyPath: "/path/to/id.json",})
Go n'offre aucun moyen fiable d'effacer la mémoire : le ramasse-miettes peut copier et conserver les octets des clés (keypairs en mémoire, et clés que Crossmint et Openfort dérivent localement) jusqu'à la collecte. Considérez toute la mémoire du processus comme sensible lorsque vous utilisez des backends à clé locale.
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})
SignMessage de CDP n'accepte que les charges utiles UTF-8.
Crossmint
Crossmint est en envoi uniquement : son API exécute chaque transaction approuvée
côté serveur, donc *crossmint.Signer dispose de SignAndSendTransaction mais pas de
SignTransaction. Votre transaction n'est jamais modifiée ; la signature renvoyée
identifie la transaction que Crossmint a effectivement enregistrée, qui peut différer de la vôtre si
Crossmint prend en charge les frais de 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 ne fait que signer les transactions ; SignMessage échoue, et vous diffusez vous-même la
transaction signée.
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 se décline en trois types de signataires, fixés à la construction par Config.Chain et
Config.PushMode. Les trois signent des messages :
| Config | Type | Point d'entrée |
|---|---|---|
Chain non défini | *fordefi.BlackBoxSigner | SignTransaction |
Chain défini, PushMode non défini ou PushModeAuto | *fordefi.NativeAutoSigner | SignAndSendTransaction |
Chain défini, PushMode: PushModeManual | *fordefi.NativeManualSigner | ModifyAndSignTransaction |
fordefi.New choisit selon la configuration et renvoie un core.SolanaSigner ;
fordefi.NewBlackBox, fordefi.NewNativeAuto et fordefi.NewNativeManual
renvoient le type concret et rejettent les configurations des autres modes. Les requêtes API sont
authentifiées par une signature de requête ECDSA P-256 : fournissez exactement l'un des champs
PrivateKeyPEM ou RequestSigner pour conserver cette clé dans un KMS/HSM.
Mode native auto : Fordefi peut remplacer le blockhash et les frais, puis signe et diffuse. Il n'accepte qu'une transaction non signée dont le seul signataire requis est le 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)
Mode native manual : définissez PushMode: fordefi.PushModeManual. Fordefi réécrit
le message (blockhash récent, instructions de frais Compute Budget) et le signe
sans le diffuser. ModifyAndSignTransaction remplace tx par la
transaction signée par Fordefi : poursuivez toujours à partir de celle-ci. Fordefi doit être le
payeur des frais et doit signer avant tout autre signataire requis, car la réécriture invalide
les signatures existantes. La réécriture n'est pas comparée (diff), inspectez donc le résultat avant
de le diffuser.
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)
Mode black box : laissez Chain non défini. Fordefi signe exactement les octets de votre message
et ne diffuse pas. Utilisez ce mode avec un vault 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),})
Pourquoi il n'existe pas de package global
Rust dispose d'un enum Signer et TypeScript de createKeychainSigner ; Go
n'a volontairement ni l'un ni l'autre. Go n'élimine pas le code mort à travers un switch de
dispatch à l'exécution, donc un package global imposerait le SDK fournisseur de chaque backend (AWS, GCP
et les autres) dans le build de chaque consommateur. Importer le package de backend dont vous
avez besoin fait office de sélecteur. Pour choisir un backend à l'exécution, écrivez le switch dans votre propre
code, uniquement pour les backends que vous prenez en charge, et conservez le résultat sous forme de
core.SolanaSigner.
Gestion des erreurs
Chaque erreur est une *core.SignerError avec un Code stable (CodeConfigError,
CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed,
CodeNotInitialized, CodeBroadcastUnconfirmed et d'autres). Error() ne renvoie
qu'un message fixe par code, jamais de matériel de clé ni de réponses brutes du fournisseur.
Faites la correspondance avec errors.As, errors.Is(err, &core.SignerError{Code: ...}) ou
core.CodeOf(err).
Avec Crossmint et Fordefi en mode native auto, un échec ne signifie pas que rien
n'a été enregistré. Lorsque le fournisseur a pu accepter la transaction, le code est
core.CodeBroadcastUnconfirmed ("SIGNER_BROADCAST_UNCONFIRMED").
ProviderTxID est renseigné lorsque la création a été acceptée, et IdempotencyKey contient
la clé dérivée du message. Renvoyer la transaction strictement identique octet par octet est
sans risque ; une transaction reconstruite (par exemple avec un nouveau blockhash) constitue un nouveau transfert.
core.SignAndSendTransaction signale également ce code lorsque votre fonction d'envoi
échoue, avec la signature du payeur des frais dans TransactionSignature. Fireblocks
PROGRAM_CALL est en signature seule et n'envoie aucune clé ; il signale ce code uniquement lorsqu'une
interrogation montre que Fireblocks a diffusé malgré tout.
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.}
Consultez le modèle de sécurité pour connaître les garanties de chaque type de signature, le README Go pour la liste complète des modules, et pkg.go.dev pour la référence de l'API.
Ajouter des signataires personnalisés
Implémentez core.SolanaSigner ainsi qu'exactement une interface de capacité, et n'ajoutez aucune
méthode que votre backend ne peut pas honorer : les appelants détectent la capacité par assertion de type.
Consultez le guide d'ajout de signataires pour intégrer
des services de gestion de clés supplémentaires.
Is this page helpful?