Go

2.0.0 es la primera versión estable de los módulos de Go, así que no hay nada que migrar. La guía de migración a 2.0 cubre la misma división de capacidades para Rust y TypeScript.

Instalación

Cada backend es su propio módulo de Go, por lo que tu compilación y tu grafo de módulos contienen únicamente los backends que importas:

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

Los módulos se etiquetan como go/<module>/v2.0.0 (por ejemplo, go/signers/memory/v2.0.0). Todos los módulos requieren Go 1.25.0, excepto gcpkms, que requiere Go 1.25.8 debido al mínimo establecido por google.golang.org/api. Ledger es exclusivo de Rust y no tiene módulo de Go.

solana-go v2 y transacciones v1

Los módulos están construidos sobre github.com/solana-foundation/solana-go/v2 v2.0.0 y reciben su *solana.Transaction. Se admiten transacciones legacy, v0 y v1 (solana.MessageVersionV1), excepto en Fireblocks con UseProgramCall: true, que acepta solo legacy y v0.

Uso básico

Todos los firmantes implementan la interfaz base core.SolanaSigner (Pubkey, SignMessage, IsAvailable). Los métodos reciben un context.Context y son bloqueantes. Además, cada backend implementa exactamente una interfaz de capacidad:

  • core.TransactionSigner: SignTransaction firma tu transacción tal como se la das.
  • core.ModifyingSigner: ModifyAndSignTransaction permite que el proveedor reescriba la transacción y luego la firma.
  • core.SendingSigner: SignAndSendTransaction hace que el proveedor la firme y la difunda.

Cada paquete de backend expone una estructura Config y un constructor 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 y ModifyAndSignTransaction devuelven un core.SignedTransaction (EncodedTransaction, Signature, Completeness). Los backends HTTP remotos aceptan un HTTPClient y un HTTPClientConfig opcionales (tiempos de espera). El cliente predeterminado exige HTTPS; si proporcionas el tuyo, se omite esa restricción y asumes la responsabilidad de su postura de seguridad.

Capacidades de los firmantes

Un backend que no puede firmar una transacción no incluye ningún método SignTransaction, por lo que la capacidad es un hecho en tiempo de compilación para los tipos concretos y una aserción de tipo para un core.SolanaSigner:

BackendCapacidadSignMessage
memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfortTransactionSignerSí
cdpTransactionSignerSolo payloads UTF-8
utilaTransactionSignerFalla con CodeSigningFailed
fordefi black box (fordefi.BlackBoxSigner)TransactionSignerSí
fordefi native manual (fordefi.NativeManualSigner)ModifyingSignerSí
fordefi native auto (fordefi.NativeAutoSigner)SendingSignerSí
crossmintSendingSignerFalla 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)
}

Inicialización

No existe un paso Init independiente. Privy, Fireblocks, Dfns, Crossmint, Para, Openfort y Utila resuelven la dirección de la wallet dentro de New, y por eso sus constructores reciben un context.Context. Crea siempre los firmantes mediante New: un firmante de Crossmint, Para o Utila con valor cero falla con CodeNotInitialized en lugar de firmar para la dirección cero.

Firmar y enviar con cualquier firmante

core.SignAndSendTransaction envía una transacción a la cadena sin importar la forma del firmante. Un SendingSigner la difunde a través de su proveedor e ignora la función de envío; cualquier otra forma firma y tu función difunde el resultado en base64, que en el caso de un ModifyingSigner es la transacción que reescribió su proveedor. Core no tiene dependencia de 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 por lotes

La firma por lotes se ofrece como funciones auxiliares independientes, concurrentes y con un escalonamiento opcional por solicitud para las API con límite de velocidad:

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 recibe un core.TransactionSigner, por lo que los firmantes que gestionan la difusión (Crossmint, Fordefi native auto) y Fordefi native manual no pueden procesarse por lotes con ella; llámalos de una transacción en una. Un firmante cuyas llamadas generan trabajo en el lado del proveedor (Fireblocks con UseProgramCall) lo notifica mediante core.BatchServerSideEffects, y entonces el lote se ejecuta de forma secuencial.

Configuración de backends

Memory

Proporciona exactamente uno de estos: PrivateKey (bytes sin procesar), PrivateKeyString (base58 o un arreglo de bytes [1,2,...]) o PrivateKeyPath (archivo keypair de 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 no ofrece una forma fiable de poner la memoria a cero: el recolector de basura puede copiar y retener los bytes de las claves (keypairs en memoria y las claves que Crossmint y Openfort derivan localmente) hasta que se produzca la recolección. Trata toda la memoria del proceso como sensible cuando uses backends de claves locales.

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

El SignMessage de CDP solo acepta payloads UTF-8.

Crossmint

Crossmint es solo de envío: su API ejecuta cada transacción aprobada en el servidor, por lo que *crossmint.Signer tiene SignAndSendTransaction y no tiene SignTransaction. Tu transacción nunca se modifica; la firma devuelta identifica la transacción que Crossmint confirmó en la cadena, que puede diferir de la tuya si Crossmint patrocina el 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 solo firma transacciones; SignMessage falla y tú mismo difundes la transacción firmada.

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 tiene tres tipos de firmante, que se fijan en la construcción mediante Config.Chain y Config.PushMode. Los tres firman mensajes:

ConfigTipoPunto de entrada
Chain sin definir*fordefi.BlackBoxSignerSignTransaction
Chain definido, PushMode sin definir o PushModeAuto*fordefi.NativeAutoSignerSignAndSendTransaction
Chain definido, PushMode: PushModeManual*fordefi.NativeManualSignerModifyAndSignTransaction

fordefi.New selecciona el tipo según la configuración y devuelve un core.SolanaSigner; fordefi.NewBlackBox, fordefi.NewNativeAuto y fordefi.NewNativeManual devuelven el tipo concreto y rechazan las configuraciones de los otros modos. Las solicitudes a la API se autentican con una firma de solicitud ECDSA P-256: proporciona exactamente uno de PrivateKeyPEM o un RequestSigner para mantener esa clave en un KMS/HSM.

Modo native auto: Fordefi puede reemplazar el blockhash y las comisiones, y luego firma y difunde. Solo acepta una transacción sin firmar cuyo único firmante requerido sea la bóveda (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)

Modo native manual: define PushMode: fordefi.PushModeManual. Fordefi reescribe el mensaje (blockhash reciente, instrucciones de comisión de Compute Budget) y lo firma sin difundirlo. ModifyAndSignTransaction reemplaza tx por la transacción que Fordefi firmó: continúa siempre a partir de ella. Fordefi debe ser quien pague la comisión y debe firmar antes que cualquier otro firmante requerido, ya que la reescritura invalida las firmas existentes. La reescritura no se compara con el original (diff), así que inspecciona el resultado antes de difundir.

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)

Modo black box: deja Chain sin definir. Fordefi firma exactamente los bytes de tu mensaje y no los difunde. Úsalo con una bóveda (vault) black box de 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),
})

Por qué no hay un paquete general

Rust tiene un enum Signer y TypeScript tiene createKeychainSigner; Go no tiene ninguno de los dos, de forma deliberada. Go no elimina el código muerto a través de un switch de despacho en tiempo de ejecución, por lo que un paquete general (umbrella) obligaría a incluir el SDK de cada proveedor de cada backend (AWS, GCP y los demás) en la compilación de todos los consumidores. Importar el paquete del backend que necesitas es el selector. Para elegir un backend en tiempo de ejecución, escribe el switch en tu propio código solo con los backends que admites y guarda el resultado como un core.SolanaSigner.

Gestión de errores

Todo error es un *core.SignerError con un Code estable (CodeConfigError, CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed, CodeNotInitialized, CodeBroadcastUnconfirmed y otros). Error() devuelve solo un mensaje fijo por código, nunca material de claves ni respuestas sin procesar del proveedor. Compara con errors.As, errors.Is(err, &core.SignerError{Code: ...}) o core.CodeOf(err).

En Crossmint y en el modo native auto de Fordefi, un fallo no significa que no se haya confirmado nada en la cadena. Cuando el proveedor pudo haber aceptado la transacción, el código es core.CodeBroadcastUnconfirmed ("SIGNER_BROADCAST_UNCONFIRMED"). ProviderTxID se establece cuando se aceptó la creación, y IdempotencyKey contiene la clave derivada del mensaje. Reenviar la transacción idéntica byte a byte es seguro; una transacción reconstruida (por ejemplo, con un nuevo blockhash) es una nueva transferencia. core.SignAndSendTransaction también informa de este código cuando tu función de envío falla, con la firma del pagador de comisiones en TransactionSignature. El PROGRAM_CALL de Fireblocks es solo de firma y no envía ninguna clave; informa de este código únicamente cuando una consulta muestra que Fireblocks difundió la transacción de todos modos.

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 el modelo de seguridad para saber qué garantiza cada forma de firma, el README de Go para ver la lista completa de módulos y pkg.go.dev para la referencia de la API.

Cómo añadir firmantes personalizados

Implementa core.SolanaSigner más exactamente una interfaz de capacidad, y no añadas ningún método que tu backend no pueda cumplir: quienes lo llaman detectan la capacidad mediante aserción de tipo. Consulta la guía de cómo añadir firmantes para integrar servicios de gestión de claves adicionales.

Is this page helpful?