---
title: Go
description: Install and use solana-keychain in Go applications
---

2.0.0 is the first stable release of the Go modules, so there is nothing to
migrate. The
[2.0 migration guide](https://github.com/solana-foundation/solana-keychain/blob/main/docs/MIGRATING_V2.md)
covers the same capability split for Rust and TypeScript.

## Installation

Every backend is its own Go module, so your build and your module graph contain
only the backends you import:

```shell
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
```

Modules are tagged `go/<module>/v2.0.0` (for example
`go/signers/memory/v2.0.0`). Every module requires Go 1.25.0, except `gcpkms`,
which requires Go 1.25.8 because of the floor set by `google.golang.org/api`.
Ledger is Rust-only and has no Go module.

### solana-go v2 and v1 Transactions

The modules are built on
[`github.com/solana-foundation/solana-go/v2`](https://github.com/solana-foundation/solana-go)
v2.0.0 and take its `*solana.Transaction`. Legacy, v0 and v1
(`solana.MessageVersionV1`) transactions are supported, except by Fireblocks
with `UseProgramCall: true`, which accepts legacy and v0 only.

## Basic Usage

Every signer implements the base
[`core.SolanaSigner` interface](https://pkg.go.dev/github.com/solana-foundation/solana-keychain/go/core/v2#SolanaSigner)
(`Pubkey`, `SignMessage`, `IsAvailable`). Methods take a `context.Context` and
block. Each backend also implements exactly one capability interface:

- `core.TransactionSigner`: `SignTransaction` signs your transaction as given.
- `core.ModifyingSigner`: `ModifyAndSignTransaction` lets the provider rewrite
  the transaction, then signs it.
- `core.SendingSigner`: `SignAndSendTransaction` has the provider sign and
  broadcast it.

Each backend package exposes a `Config` struct and a `New` constructor:

```go
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` and `ModifyAndSignTransaction` return a
`core.SignedTransaction` (`EncodedTransaction`, `Signature`, `Completeness`).
Remote HTTP backends accept an optional `HTTPClient` and `HTTPClientConfig`
(timeouts). The default client enforces HTTPS; supplying your own bypasses that,
and you own its security posture.

### Signer Capabilities

A backend that cannot sign a transaction carries no `SignTransaction` method at
all, so capability is a compile-time fact for concrete types and a type
assertion for a `core.SolanaSigner`:

| Backend                                                                         | Capability          | `SignMessage`                  |
| ------------------------------------------------------------------------------- | ------------------- | ------------------------------ |
| memory, vault, privy, turnkey, awskms, gcpkms, fireblocks, dfns, para, openfort | `TransactionSigner` | Yes                            |
| cdp                                                                             | `TransactionSigner` | UTF-8 payloads only            |
| utila                                                                           | `TransactionSigner` | Fails with `CodeSigningFailed` |
| fordefi black box (`fordefi.BlackBoxSigner`)                                    | `TransactionSigner` | Yes                            |
| fordefi native manual (`fordefi.NativeManualSigner`)                            | `ModifyingSigner`   | Yes                            |
| fordefi native auto (`fordefi.NativeAutoSigner`)                                | `SendingSigner`     | Yes                            |
| crossmint                                                                       | `SendingSigner`     | Fails with `CodeSigningFailed` |

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

### Initialization

There is no separate `Init` step. Privy, Fireblocks, Dfns, Crossmint, Para,
Openfort and Utila resolve the wallet address inside `New`, which is why their
constructors take a `context.Context`. Always build signers through `New`: a
zero-value Crossmint, Para or Utila signer fails with `CodeNotInitialized`
rather than signing for the zero address.

### Sign and Send with Any Signer

`core.SignAndSendTransaction` gets a transaction on chain whatever the signer's
shape. A `SendingSigner` broadcasts through its provider and ignores the send
function; every other shape signs and your function broadcasts the base64
result, which for a `ModifyingSigner` is the transaction its provider rewrote.
Core has no RPC dependency:

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

### Batch Signing

Batch signing is provided as free helpers, concurrent with an optional
per-request stagger for rate-limited APIs:

```go
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` takes a `core.TransactionSigner`, so the
broadcast-managed signers (Crossmint, Fordefi native auto) and Fordefi native
manual cannot be batched through it; call them one transaction at a time. A
signer whose calls create provider-side work (Fireblocks with `UseProgramCall`)
reports it through `core.BatchServerSideEffects`, and the batch then runs
sequentially.

## Backend Configuration

### Memory

Provide exactly one of `PrivateKey` (raw bytes), `PrivateKeyString` (base58 or a
`[1,2,...]` byte array) or `PrivateKeyPath` (Solana CLI keypair file):

```go
import "github.com/solana-foundation/solana-keychain/go/signers/memory/v2"

signer, err := memory.New(memory.Config{
	PrivateKeyPath: "/path/to/id.json",
})
```

Go offers no reliable way to zero memory: the garbage collector may copy and
retain key bytes (memory keypairs, and the keys Crossmint and Openfort derive
locally) until collection. Treat the whole process memory as sensitive when
using local-key backends.

### HashiCorp Vault

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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)

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

CDP's `SignMessage` accepts UTF-8 payloads only.

### Crossmint

Crossmint is sending-only: its API executes every approved transaction
server-side, so `*crossmint.Signer` has `SignAndSendTransaction` and no
`SignTransaction`. Your transaction is never modified; the returned signature
identifies the transaction Crossmint landed, which may differ from yours if
Crossmint sponsors gas.

```go
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

```go
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

```go
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

```go
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 signs transactions only; `SignMessage` fails, and you broadcast the signed
transaction yourself.

```go
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 comes as three signer types, fixed at construction by `Config.Chain` and
`Config.PushMode`. All three sign messages:

| Config                                          | Type                          | Entry point                |
| ----------------------------------------------- | ----------------------------- | -------------------------- |
| `Chain` unset                                   | `*fordefi.BlackBoxSigner`     | `SignTransaction`          |
| `Chain` set, `PushMode` unset or `PushModeAuto` | `*fordefi.NativeAutoSigner`   | `SignAndSendTransaction`   |
| `Chain` set, `PushMode: PushModeManual`         | `*fordefi.NativeManualSigner` | `ModifyAndSignTransaction` |

`fordefi.New` dispatches on the config and returns a `core.SolanaSigner`;
`fordefi.NewBlackBox`, `fordefi.NewNativeAuto` and `fordefi.NewNativeManual`
return the concrete type and reject the other modes' configs. API requests are
authenticated with an ECDSA P-256 request signature: provide exactly one of
`PrivateKeyPEM`, or a `RequestSigner` to keep that key in a KMS/HSM.

**Native auto mode**: Fordefi may replace the blockhash and fees, then signs and
broadcasts. It accepts only an unsigned transaction whose sole required signer
is the vault.

```go
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)
```

**Native manual mode**: set `PushMode: fordefi.PushModeManual`. Fordefi rewrites
the message (recent blockhash, Compute Budget fee instructions) and signs it
without broadcasting. `ModifyAndSignTransaction` replaces `tx` with the
transaction Fordefi signed: always continue from it. Fordefi must be the fee
payer and must sign before any other required signer, since the rewrite voids
existing signatures. The rewrite is not diffed, so inspect the result before
broadcasting.

```go
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)
```

**Black box mode**: leave `Chain` unset. Fordefi signs your exact message bytes
and does not broadcast. Use this with a Fordefi black box vault.

```go
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),
})
```

## Why There Is No Umbrella Package

Rust has a `Signer` enum and TypeScript has `createKeychainSigner`; Go
deliberately has neither. Go does not dead-code-eliminate across a runtime
dispatch switch, so an umbrella would force every backend's vendor SDK (AWS, GCP
and the rest) into every consumer's build. Importing the backend package you
need is the selector. To pick a backend at runtime, write the switch in your own
code over just the backends you support, and hold the result as a
`core.SolanaSigner`.

## Error Handling

Every error is a `*core.SignerError` with a stable `Code` (`CodeConfigError`,
`CodeHTTPError`, `CodeRemoteAPIError`, `CodeSigningFailed`,
`CodeNotInitialized`, `CodeBroadcastUnconfirmed` and others). `Error()` returns
only a fixed message per code, never key material or raw provider responses.
Match with `errors.As`, `errors.Is(err, &core.SignerError{Code: ...})` or
`core.CodeOf(err)`.

On Crossmint and Fordefi native auto mode, a failure does not mean nothing
landed. When the provider may have accepted the transaction, the code is
`core.CodeBroadcastUnconfirmed` (`"SIGNER_BROADCAST_UNCONFIRMED"`).
`ProviderTxID` is set when the create was accepted, and `IdempotencyKey` carries
the key derived from the message. Resending the byte-identical transaction is
safe; a rebuilt one (for example with a new blockhash) is a new transfer.
`core.SignAndSendTransaction` also reports this code when your send function
fails, with the fee-payer signature in `TransactionSignature`. Fireblocks
PROGRAM_CALL is sign-only and sends no key; it reports this code only when a
poll shows Fireblocks broadcast anyway.

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

See the
[security model](https://github.com/solana-foundation/solana-keychain/blob/main/docs/SECURITY_MODEL.md)
for what each signing shape guarantees, the
[Go README](https://github.com/solana-foundation/solana-keychain/blob/main/go/README.md)
for the full module list, and
[pkg.go.dev](https://pkg.go.dev/github.com/solana-foundation/solana-keychain/go/core/v2)
for the API reference.

## Adding Custom Signers

Implement `core.SolanaSigner` plus exactly one capability interface, and add no
method your backend cannot honor: callers detect capability by type assertion.
See the [Adding Signers guide](/docs/tools/keychain/adding-signers) to integrate
additional key management services.
