Go

Wersja 2.0.0 to pierwsze stabilne wydanie modułów Go, więc nie ma czego migrować. Przewodnik migracji do wersji 2.0 opisuje ten sam podział możliwości dla Rusta i TypeScriptu.

Instalacja

Każdy backend jest osobnym modułem Go, więc w Twoim buildzie i grafie modułów znajdą się wyłącznie te backendy, które importujesz:

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

Moduły są oznaczane tagami go/<module>/v2.0.0 (na przykład go/signers/memory/v2.0.0). Każdy moduł wymaga Go 1.25.0, z wyjątkiem gcpkms, który wymaga Go 1.25.8 ze względu na minimalną wersję ustaloną przez google.golang.org/api. Ledger jest dostępny wyłącznie w Rust i nie ma modułu Go.

Transakcje solana-go v2 i v1

Moduły są zbudowane na github.com/solana-foundation/solana-go/v2 v2.0.0 i przyjmują jego *solana.Transaction. Obsługiwane są transakcje legacy, v0 i v1 (solana.MessageVersionV1), z wyjątkiem Fireblocks z UseProgramCall: true, który akceptuje wyłącznie legacy i v0.

Podstawowe użycie

Każdy signer implementuje bazowy interfejs core.SolanaSigner (Pubkey, SignMessage, IsAvailable). Metody przyjmują context.Context i są blokujące. Każdy backend implementuje też dokładnie jeden interfejs możliwości:

  • core.TransactionSigner: SignTransaction podpisuje transakcję w takiej postaci, w jakiej została przekazana.
  • core.ModifyingSigner: ModifyAndSignTransaction pozwala dostawcy przepisać transakcję, a następnie ją podpisuje.
  • core.SendingSigner: SignAndSendTransaction powoduje, że dostawca podpisuje i rozgłasza transakcję.

Pakiet każdego backendu udostępnia strukturę Config i konstruktor 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 i ModifyAndSignTransaction zwracają core.SignedTransaction (EncodedTransaction, Signature, Completeness). Zdalne backendy HTTP akceptują opcjonalne HTTPClient i HTTPClientConfig (limity czasu). Domyślny klient wymusza HTTPS; podanie własnego klienta to omija, a za jego bezpieczeństwo odpowiadasz Ty.

Możliwości signerów

Backend, który nie może podpisać transakcji, w ogóle nie ma metody SignTransaction, więc możliwość jest faktem znanym w czasie kompilacji dla typów konkretnych i asercją typu dla core.SolanaSigner:

BackendMożliwośćSignMessage
memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfortTransactionSignerTak
cdpTransactionSignerTylko ładunki UTF-8
utilaTransactionSignerKończy się błędem CodeSigningFailed
fordefi black box (fordefi.BlackBoxSigner)TransactionSignerTak
fordefi native manual (fordefi.NativeManualSigner)ModifyingSignerTak
fordefi native auto (fordefi.NativeAutoSigner)SendingSignerTak
crossmintSendingSignerKończy się błędem 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)
}

Inicjalizacja

Nie ma osobnego kroku Init. Privy, Fireblocks, Dfns, Crossmint, Para, Openfort i Utila ustalają adres portfela wewnątrz New, dlatego ich konstruktory przyjmują context.Context. Zawsze twórz signery przez New: signer Crossmint, Para lub Utila o wartości zerowej kończy się błędem CodeNotInitialized, zamiast podpisywać dla adresu zerowego.

Podpisywanie i wysyłanie dowolnym signerem

core.SignAndSendTransaction umieszcza transakcję w łańcuchu niezależnie od rodzaju signera. SendingSigner rozgłasza ją przez swojego dostawcę i ignoruje funkcję wysyłania; każdy inny rodzaj podpisuje, a Twoja funkcja rozgłasza wynik w base64, który dla ModifyingSigner jest transakcją przepisaną przez jego dostawcę. Core nie ma zależności od 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)
})

Podpisywanie wsadowe

Podpisywanie wsadowe udostępniają darmowe funkcje pomocnicze, działające współbieżnie, z opcjonalnym odstępem między żądaniami dla API z ograniczeniem liczby żądań:

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 przyjmuje core.TransactionSigner, więc signerów zarządzających rozgłaszaniem (Crossmint, Fordefi native auto) oraz Fordefi native manual nie można przetwarzać wsadowo; wywołuj je po jednej transakcji. Signer, którego wywołania tworzą pracę po stronie dostawcy (Fireblocks z UseProgramCall), zgłasza to przez core.BatchServerSideEffects, a wsad jest wtedy wykonywany sekwencyjnie.

Konfiguracja backendów

Memory

Podaj dokładnie jedno z: PrivateKey (surowe bajty), PrivateKeyString (base58 lub tablica bajtów [1,2,...]) albo PrivateKeyPath (plik 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 nie oferuje niezawodnego sposobu zerowania pamięci: odśmiecacz pamięci może kopiować i przechowywać bajty kluczy (keypair w pamięci oraz klucze, które Crossmint i Openfort wyprowadzają lokalnie) aż do zwolnienia pamięci. Przy korzystaniu z backendów z kluczem lokalnym traktuj całą pamięć procesu jako wrażliwą.

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 w CDP akceptuje wyłącznie ładunki UTF-8.

Crossmint

Crossmint obsługuje tylko wysyłanie: jego API wykonuje każdą zatwierdzoną transakcję po stronie serwera, więc *crossmint.Signer ma SignAndSendTransaction, ale nie ma SignTransaction. Twoja transakcja nigdy nie jest modyfikowana; zwrócona sygnatura identyfikuje transakcję, którą Crossmint umieścił w łańcuchu, a która może różnić się od Twojej, jeśli Crossmint sponsoruje opłaty za gaz.

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 tylko podpisuje transakcje; SignMessage kończy się błędem, a podpisaną transakcję rozgłaszasz samodzielnie.

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 występuje w trzech typach signerów, ustalanych podczas tworzenia przez Config.Chain i Config.PushMode. Wszystkie trzy podpisują wiadomości:

ConfigTypPunkt wejścia
Chain nieustawione*fordefi.BlackBoxSignerSignTransaction
Chain ustawione, PushMode nieustawione lub PushModeAuto*fordefi.NativeAutoSignerSignAndSendTransaction
Chain ustawione, PushMode: PushModeManual*fordefi.NativeManualSignerModifyAndSignTransaction

fordefi.New wybiera typ na podstawie konfiguracji i zwraca core.SolanaSigner; fordefi.NewBlackBox, fordefi.NewNativeAuto i fordefi.NewNativeManual zwracają typ konkretny i odrzucają konfiguracje pozostałych trybów. Żądania API są uwierzytelniane podpisem żądania ECDSA P-256: podaj dokładnie jedno z: PrivateKeyPEM albo RequestSigner, aby przechowywać ten klucz w KMS/HSM.

Tryb native auto: Fordefi może zastąpić blockhash i opłaty, a następnie podpisuje i rozgłasza. Akceptuje wyłącznie niepodpisaną transakcję, w której jedynym wymaganym podpisującym jest 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)

Tryb native manual: ustaw PushMode: fordefi.PushModeManual. Fordefi przepisuje wiadomość (recent blockhash, instrukcje opłat Compute Budget) i podpisuje ją bez rozgłaszania. ModifyAndSignTransaction zastępuje tx transakcją, którą podpisał Fordefi: zawsze kontynuuj od niej. Fordefi musi być płatnikiem opłaty i musi podpisać przed każdym innym wymaganym podpisującym, ponieważ przepisanie unieważnia istniejące podpisy. Przepisanie nie jest porównywane (diff), więc sprawdź wynik przed rozgłoszeniem.

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)

Tryb black box: pozostaw Chain nieustawione. Fordefi podpisuje dokładnie te bajty wiadomości, które przekażesz, i nie rozgłasza. Używaj tego z vaultem black box 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),
})

Dlaczego nie ma pakietu zbiorczego

Rust ma enum Signer, a TypeScript ma createKeychainSigner; Go celowo nie ma żadnego z nich. Go nie eliminuje martwego kodu w ramach przełącznika dispatchu w czasie wykonania, więc pakiet zbiorczy wciągałby SDK dostawców wszystkich backendów (AWS, GCP i pozostałe) do builda każdego konsumenta. Selektorem jest zaimportowanie pakietu potrzebnego backendu. Aby wybrać backend w czasie działania, napisz przełącznik we własnym kodzie, obejmujący tylko obsługiwane przez Ciebie backendy, i przechowuj wynik jako core.SolanaSigner.

Obsługa błędów

Każdy błąd jest typu *core.SignerError ze stabilnym Code (CodeConfigError, CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed, CodeNotInitialized, CodeBroadcastUnconfirmed i inne). Error() zwraca tylko stały komunikat dla każdego kodu, nigdy materiał kluczowy ani surowe odpowiedzi dostawcy. Dopasowuj przez errors.As, errors.Is(err, &core.SignerError{Code: ...}) lub core.CodeOf(err).

W Crossmint i Fordefi w trybie native auto błąd nie oznacza, że nic nie trafiło do łańcucha. Gdy dostawca mógł zaakceptować transakcję, kodem jest core.CodeBroadcastUnconfirmed ("SIGNER_BROADCAST_UNCONFIRMED"). ProviderTxID jest ustawiane, gdy utworzenie zostało zaakceptowane, a IdempotencyKey zawiera klucz wyprowadzony z wiadomości. Ponowne wysłanie identycznej co do bajta transakcji jest bezpieczne; przebudowana (na przykład z nowym blockhash) to nowy przelew. core.SignAndSendTransaction zgłasza ten kod także wtedy, gdy Twoja funkcja wysyłania zawiedzie, z podpisem płatnika opłaty w TransactionSignature. Fireblocks PROGRAM_CALL tylko podpisuje i nie wysyła klucza; zgłasza ten kod wyłącznie wtedy, gdy odpytywanie pokaże, że Fireblocks mimo to rozgłosił transakcję.

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

Zobacz model bezpieczeństwa, aby dowiedzieć się, co gwarantuje każdy rodzaj podpisywania, README Go — pełną listę modułów, oraz pkg.go.dev — dokumentację API.

Dodawanie własnych signerów

Zaimplementuj core.SolanaSigner oraz dokładnie jeden interfejs możliwości i nie dodawaj metody, której Twój backend nie może obsłużyć: wywołujący wykrywają możliwości przez asercję typu. Zobacz przewodnik Dodawanie signerów, aby zintegrować dodatkowe usługi zarządzania kluczami.

Is this page helpful?