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, dzięki czemu Twój build i graf modułów zawierają tylko te backendy, które importujesz:
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
Moduły są otagowane jako 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 dla Rusta 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 oraz 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:SignTransactionpodpisuje transakcję w postaci, w jakiej została przekazana.core.ModifyingSigner:ModifyAndSignTransactionpozwala dostawcy przepisać transakcję, a następnie ją podpisuje.core.SendingSigner:SignAndSendTransactionpowoduje, że dostawca podpisuje transakcję i ją rozgłasza.
Każdy pakiet backendu udostępnia strukturę Config oraz 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 transactionreturn 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 omija to
ograniczenie, a odpowiedzialność za jego bezpieczeństwo spoczywa na Tobie.
Możliwości signerów
Backend, który nie potrafi podpisać transakcji, w ogóle nie ma metody SignTransaction,
dlatego możliwość ta jest faktem ustalanym w czasie kompilacji dla typów konkretnych
i asercją typu dla core.SolanaSigner:
| Backend | Możliwość | SignMessage |
|---|---|---|
| memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfort | TransactionSigner | Tak |
| cdp | TransactionSigner | Tylko dane UTF-8 |
| utila | TransactionSigner | Kończy się błędem CodeSigningFailed |
fordefi black box (fordefi.BlackBoxSigner) | TransactionSigner | Tak |
fordefi native manual (fordefi.NativeManualSigner) | ModifyingSigner | Tak |
fordefi native auto (fordefi.NativeAutoSigner) | SendingSigner | Tak |
| crossmint | SendingSigner | Koń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 zerowego adresu.
Podpisywanie i wysyłanie dowolnym signerem
core.SignAndSendTransaction umieszcza transakcję w łańcuchu niezależnie od
rodzaju signera. SendingSigner rozgłasza transakcję przez swojego dostawcę i ignoruje
funkcję wysyłania; każdy inny rodzaj podpisuje, a Twoja funkcja rozgłasza wynik
w base64, którym dla ModifyingSigner jest transakcja przepisana 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ą samodzielne funkcje pomocnicze, działające współbieżnie, z opcjonalnym opóźnieniem między żądaniami dla API z limitami szybkości:
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 przyjmuje core.TransactionSigner, więc signery zarządzające
rozgłaszaniem (Crossmint, Fordefi native auto) oraz Fordefi native
manual nie mogą być przetwarzane w ten sposób wsadowo; wywołuj je pojedynczo dla każdej transakcji.
Signer, którego wywołania tworzą pracę po stronie dostawcy (Fireblocks z UseProgramCall),
zgłasza to przez core.BatchServerSideEffects, a wtedy wsad jest 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: garbage collector może kopiować i przechowywać bajty klucza (keypair w pamięci oraz klucze, które Crossmint i Openfort wyprowadzają lokalnie) aż do momentu zwolnienia pamięci. Traktuj całą pamięć procesu jako wrażliwą, gdy używasz backendów z kluczami lokalnymi.
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 w CDP akceptuje wyłącznie dane UTF-8.
Crossmint
Crossmint działa wyłącznie w trybie wysyłania: jego API wykonuje każdą zatwierdzoną transakcję
po stronie serwera, więc *crossmint.Signer ma SignAndSendTransaction, a nie ma
SignTransaction. Twoja transakcja nigdy nie jest modyfikowana; zwrócona sygnatura
identyfikuje transakcję, którą Crossmint zapisał w łańcuchu, a która może różnić się od Twojej, jeśli
Crossmint sponsoruje opłaty 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 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 przy tworzeniu przez Config.Chain oraz
Config.PushMode. Wszystkie trzy podpisują wiadomości:
| Config | Typ | Punkt wejścia |
|---|---|---|
Chain nieustawione | *fordefi.BlackBoxSigner | SignTransaction |
Chain ustawione, PushMode nieustawione lub PushModeAuto | *fordefi.NativeAutoSigner | SignAndSendTransaction |
Chain ustawione, PushMode: PushModeManual | *fordefi.NativeManualSigner | ModifyAndSignTransaction |
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 sygnaturą żą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.ChainSolanaMainnetFee: &fordefi.Fee{Type: fordefi.FeeTypePriority,PriorityLevel: fordefi.PriorityMedium,},})sig, err := signer.SignAndSendTransaction(ctx, tx)
Tryb native manual: ustaw PushMode: fordefi.PushModeManual. Fordefi przepisuje
wiadomość (najnowszy blockhash, instrukcje opłat Compute Budget) i podpisuje ją
bez rozgłaszania. ModifyAndSignTransaction zastępuje tx transakcją podpisaną
przez 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 z oryginałem (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 Twoje bajty wiadomości
i nie rozgłasza. Używaj go z vaultem 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),})
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 przy dyspozytorze
opartym na switchu w czasie wykonania, więc pakiet zbiorczy wciągnąłby SDK dostawców wszystkich backendów (AWS, GCP
i pozostałych) do builda każdego konsumenta. Selektorem jest zaimportowanie pakietu
backendu, którego potrzebujesz. Aby wybrać backend w czasie działania, napisz switch 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 to *core.SignerError ze stabilnym Code (CodeConfigError,
CodeHTTPError, CodeRemoteAPIError, CodeSigningFailed,
CodeNotInitialized, CodeBroadcastUnconfirmed i inne). Error() zwraca
tylko stały komunikat przypisany do kodu, nigdy materiał klucza ani surowe odpowiedzi dostawcy.
Dopasowuj za pomocą errors.As, errors.Is(err, &core.SignerError{Code: ...}) lub
core.CodeOf(err).
W Crossmint i Fordefi w trybie native auto niepowodzenie 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 bajt po bajcie transakcji jest
bezpieczne; odbudowana transakcja (na przykład z nowym blockhash) to nowy transfer.
core.SignAndSendTransaction zgłasza ten kod również wtedy, gdy Twoja funkcja wysyłania
zawiedzie, z podpisem płatnika opłaty w TransactionSignature. Fireblocks
PROGRAM_CALL tylko podpisuje i niczego nie wysyła; zgłasza ten kod wyłącznie wtedy, gdy
odpytanie pokaże, że Fireblocks mimo to rozgłosił transakcję.
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.}
Zobacz model bezpieczeństwa, gdzie opisano, co gwarantuje każdy rodzaj podpisywania, README dla Go, gdzie znajdziesz pełną listę modułów, oraz pkg.go.dev, gdzie jest dokumentacja API.
Dodawanie własnych signerów
Zaimplementuj core.SolanaSigner oraz dokładnie jeden interfejs możliwości i nie dodawaj żadnej
metody, której Twój backend nie może obsłużyć: wywołujący wykrywają możliwości przez asercję typu.
Zobacz przewodnik dodawania signerów, aby zintegrować
dodatkowe usługi zarządzania kluczami.
Is this page helpful?