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, 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
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:SignTransactionfirma tu transacción tal como se la das.core.ModifyingSigner:ModifyAndSignTransactionpermite que el proveedor reescriba la transacción y luego la firma.core.SendingSigner:SignAndSendTransactionhace 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 transactionreturn 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:
| Backend | Capacidad | SignMessage |
|---|---|---|
| memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfort | TransactionSigner | Sí |
| cdp | TransactionSigner | Solo payloads UTF-8 |
| utila | TransactionSigner | Falla con CodeSigningFailed |
fordefi black box (fordefi.BlackBoxSigner) | TransactionSigner | Sí |
fordefi native manual (fordefi.NativeManualSigner) | ModifyingSigner | Sí |
fordefi native auto (fordefi.NativeAutoSigner) | SendingSigner | Sí |
| crossmint | SendingSigner | Falla 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 = unboundedRequestDelay: 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 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})
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:
| Config | Tipo | Punto de entrada |
|---|---|---|
Chain sin definir | *fordefi.BlackBoxSigner | SignTransaction |
Chain definido, PushMode sin definir o PushModeAuto | *fordefi.NativeAutoSigner | SignAndSendTransaction |
Chain definido, PushMode: PushModeManual | *fordefi.NativeManualSigner | ModifyAndSignTransaction |
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.ChainSolanaMainnetFee: &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.SignerErrorif 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?