Documentation
¶
Overview ¶
Package rpc is a minimal, clean-room JSON-RPC 2.0 client for the Solana HTTP endpoint.
The package is deliberately small and has no third-party dependencies. It does not import the root solana package; it speaks pure JSON over HTTP so the higher-level typed wrappers can live one layer up without a dependency cycle.
A caller wanting a faster JSON library can swap stdlib encoding/json out at construction time via WithCodec, without touching the transport. See StdCodec for the default.
Index ¶
- Constants
- Variables
- func CallContext[T any](ctx context.Context, c *Client, method string, args ...any) (T, error)
- func IsAccountNotFound(err error) bool
- func IsBlockCleanedUp(err error) bool
- func IsBlockhashExpired(err error) bool
- func IsInsufficientFunds(err error) bool
- func IsNodeBehind(err error) bool
- func IsRateLimited(err error) bool
- func IsSignatureNotFound(err error) bool
- func IsSlotSkipped(err error) bool
- func IsTransactionExpired(err error) bool
- func NewContextWithHeaders(ctx context.Context, h http.Header) context.Context
- func NewDefaultTransport(maxIdleConns, maxIdleConnsPerHost int) *http.Transport
- type BatchElem
- type Client
- type ClientOption
- func WithCodec(codec Codec) ClientOption
- func WithHTTPAuth(a HTTPAuth) ClientOption
- func WithHTTPClient(hc *http.Client) ClientOption
- func WithHeader(key, value string) ClientOption
- func WithHeaders(headers http.Header) ClientOption
- func WithMaxIdleConns(n int) ClientOption
- func WithMaxIdleConnsPerHost(n int) ClientOption
- func WithRetryPolicy(p RetryPolicy) ClientOption
- type Codec
- type Config
- type ContextValue
- type ErrRPC
- type Error
- type HTTPAuth
- type RawJSON
- type Request
- type Response
- type RetryPolicy
Constants ¶
const ( RPCErrCodeBlockCleanedUp = -32001 RPCErrCodeTransactionSimulationFail = -32002 RPCErrCodeSigVerifyFailed = -32003 RPCErrCodeBlockNotAvailable = -32004 RPCErrCodeNodeUnhealthy = -32005 RPCErrCodeSlotSkipped = -32007 RPCErrCodeNoSnapshot = -32008 RPCErrCodeLongTermStorageSlotSkipped = -32009 RPCErrCodeKeyExcluded = -32010 RPCErrCodeTransactionPrecompileFail = -32011 RPCErrCodeScanError = -32012 RPCErrCodeTransactionHistoryOff = -32013 RPCErrCodeMinContextSlotNotReached = -32016 )
Solana JSON-RPC error codes. These are the numeric codes the server returns in the JSON-RPC error object; some are documented, most are folklore. The Is* classifiers below match against these codes first and fall back to message substring matching when the code is ambiguous or missing.
const DefaultMaxIdleConns = 100
DefaultMaxIdleConns is the total idle connection pool size used by NewClient when no WithMaxIdleConns option is provided.
const DefaultMaxIdleConnsPerHost = 10
DefaultMaxIdleConnsPerHost is the per-host idle connection pool size used by NewClient when no WithMaxIdleConnsPerHost option is provided. Go's built-in default (http.DefaultMaxIdleConnsPerHost) is 2, which causes TCP+TLS re-dials as soon as a third goroutine calls the same endpoint concurrently. 10 is a conservative but practical default: it covers small applications and typical bot workloads without over-reserving file descriptors. Increase it with WithMaxIdleConnsPerHost for indexers or high-frequency trading systems (50–200 is typical).
Variables ¶
var ( // ErrBlockhashExpired indicates that a transaction's recent // blockhash is no longer valid. Refresh via GetLatestBlockhash // and rebuild the transaction. ErrBlockhashExpired = errors.New("solana: blockhash expired") // ErrInsufficientFunds indicates the payer's balance was too low // to cover the transaction fee or an instruction's lamport transfer. ErrInsufficientFunds = errors.New("solana: insufficient funds") // ErrRateLimited indicates the RPC node is throttling the caller. // Back off and retry, or switch endpoints. ErrRateLimited = errors.New("solana: rate limited") // ErrNodeBehind indicates the RPC node is behind the current // cluster slot and cannot satisfy the query at the requested // commitment level. ErrNodeBehind = errors.New("solana: node is behind the cluster") // ErrTransactionExpired indicates the transaction's blockhash // passed its LastValidBlockHeight before landing, and the cluster // will not process it. This is distinct from ErrBlockhashExpired // in timing: ErrBlockhashExpired is returned during send, // ErrTransactionExpired during confirm. ErrTransactionExpired = errors.New("solana: transaction expired") // ErrAccountNotFound indicates the queried account does not exist // at the requested commitment level. ErrAccountNotFound = errors.New("solana: account not found") // ErrSignatureNotFound indicates the queried transaction signature // is not known to the node. This can mean the transaction never // landed, or that the node does not have transaction history enabled. ErrSignatureNotFound = errors.New("solana: signature not found") // ErrSlotSkipped indicates the requested slot was skipped in the // leader schedule and no block was produced for it. ErrSlotSkipped = errors.New("solana: slot skipped") // ErrBlockCleanedUp indicates the requested block has been pruned // from the node's local storage and is no longer available. ErrBlockCleanedUp = errors.New("solana: block cleaned up") )
Sentinel errors for common Solana RPC failure modes. Use the Is* classifiers below rather than comparing errors.Is directly, because the classifiers also match *ErrRPC values and substring patterns.
var ErrMissingResponse = errors.New("no response for batch element")
ErrMissingResponse is the per-element Error populated when the server's batch response omitted the element's ID. It indicates a broken or misbehaving RPC endpoint — the JSON-RPC 2.0 spec requires one response per request ID (except for notifications, which this client does not issue).
Functions ¶
func CallContext ¶ added in v0.1.1
CallContext issues a JSON-RPC 2.0 request and returns the decoded typed result in a single Unmarshal pass.
CallContext is a free generic function (Go does not allow type parameters on methods) that takes the *Client explicitly:
balance, err := jsonrpc.CallContext[uint64](ctx, c, "getBalance", addr)
CallContext uses the configured RetryPolicy to transparently retry transient failures. The caller's context controls deadlines and cancellation; a cancelled or expired context returns immediately with the context error wrapped in a method-labelled message.
On a JSON-RPC error response, CallContext returns *ErrRPC wrapping the code, message, data and raw body. Use errors.As to recover it. The zero value of T is returned on any error.
For methods that return Solana's {context:{slot}, value} envelope, instantiate T as ContextValue[X]; the slot is then on result.Context.Slot and the payload on result.Value.
func IsAccountNotFound ¶
IsAccountNotFound reports whether err indicates the queried account does not exist.
func IsBlockCleanedUp ¶
IsBlockCleanedUp reports whether err indicates the requested block has been pruned from the node's storage.
func IsBlockhashExpired ¶
IsBlockhashExpired reports whether err indicates a stale blockhash. Use this in retry loops to decide whether to refresh the blockhash and rebuild the transaction.
func IsInsufficientFunds ¶
IsInsufficientFunds reports whether err indicates the payer lacked the lamports required to cover fees or an instruction.
func IsNodeBehind ¶
IsNodeBehind reports whether err indicates the RPC node is behind the current cluster slot. This usually means the caller should pick a different endpoint or retry after a short wait.
func IsRateLimited ¶
IsRateLimited reports whether err indicates the server is throttling the caller. HTTP 429 at the transport layer is also treated as rate-limiting for the classifier's purposes.
func IsSignatureNotFound ¶
IsSignatureNotFound reports whether err indicates the queried signature is not known to the node.
func IsSlotSkipped ¶
IsSlotSkipped reports whether err indicates the requested slot was skipped in the leader schedule.
func IsTransactionExpired ¶
IsTransactionExpired reports whether err indicates a transaction expired (passed its LastValidBlockHeight before landing). This is distinct from IsBlockhashExpired: the former happens during the confirm loop, the latter during the send call.
func NewContextWithHeaders ¶
NewContextWithHeaders returns a copy of ctx with the given HTTP headers attached. When a Client makes a request using this context, the headers are merged into the request on top of any client-level headers set via WithHeader / WithHeaders. Headers set here take precedence over client-level headers but are overridden by the HTTPAuth callback.
This is useful for per-call authentication or tracing headers without creating a separate Client per call.
func NewDefaultTransport ¶
NewDefaultTransport returns an *http.Transport with Solana-optimised defaults. maxIdleConns is the global idle connection cap; maxIdleConnsPerHost sets the per-host keep-alive pool.
Use this when you need to layer a custom RoundTripper (e.g. for metrics or mTLS) on top of the optimised transport rather than starting from http.DefaultTransport:
tr := rpc.NewDefaultTransport(rpc.DefaultMaxIdleConns, rpc.DefaultMaxIdleConnsPerHost)
tr.TLSClientConfig = &tls.Config{...}
c := rpc.NewClient(url, rpc.WithHTTPClient(&http.Client{Transport: tr}))
Types ¶
type BatchElem ¶
BatchElem is one call inside a BatchCallContext invocation.
- Method and Args describe the request (Args may be nil for no-arg methods; the client will serialise it as []).
- Result is a pointer into which the decoded result is written; set to nil to discard. For context-wrapped methods, point it at a *ContextValue[T] to receive the full envelope.
- Error is populated by BatchCallContext on per-element failure: an *ErrRPC for JSON-RPC error objects, a plain error for result decode failures, or ErrMissingResponse when the server omitted the response. Zero on success.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a JSON-RPC 2.0 client for the Solana HTTP endpoint. It is safe for concurrent use by multiple goroutines.
A zero Client is not usable; always construct one with NewClient.
func NewClient ¶
NewClient constructs a Client from a Config struct. Zero-value fields in cfg fall back to library defaults.
c := rpc.NewClient("https://api.mainnet-beta.solana.com", rpc.Config{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
})
func NewClientWith ¶
func NewClientWith(endpoint string, opts ...ClientOption) *Client
NewClientWith constructs a Client using functional options.
c := rpc.NewClientWith("https://api.mainnet-beta.solana.com",
rpc.WithMaxIdleConnsPerHost(20),
rpc.WithHeader("X-API-Key", "secret"),
)
func (*Client) BatchCallContext ¶
BatchCallContext sends b as a single JSON-RPC 2.0 batch request. The returned error is non-nil only on transport-level failures (HTTP failure, malformed response array, context cancellation). Per-element errors — including JSON-RPC error objects, result decode failures, and missing responses — are written into each BatchElem.Error so the caller can observe partial success.
Each element is tagged with a fresh monotonic ID; the server's responses are matched back to the input by ID (JSON-RPC does not guarantee response order). The entire batch is retried as a unit under the configured retry policy; Solana's RPC server is stateless so reusing IDs on retry is safe.
An empty b is a no-op and returns nil without issuing a request.
type ClientOption ¶
type ClientOption interface {
// contains filtered or unexported methods
}
ClientOption configures a Client at construction time. Both Config and the WithXxx helper functions implement this interface, so either style (or a mix) can be passed to NewClient.
func WithCodec ¶
func WithCodec(codec Codec) ClientOption
WithCodec replaces the default JSON codec. The default is GoJSONCodec; use WithCodec(StdCodec()) to opt out of the goccy/go-json dependency in favour of stdlib encoding/json.
func WithHTTPAuth ¶
func WithHTTPAuth(a HTTPAuth) ClientOption
WithHTTPAuth sets a callback invoked before each request to inject authentication headers dynamically.
func WithHTTPClient ¶
func WithHTTPClient(hc *http.Client) ClientOption
WithHTTPClient replaces the default *http.Client. When set, the client is used as-is and MaxIdleConns/MaxIdleConnsPerHost have no effect. Callers who want to start from the library's transport defaults and only change one setting should use NewDefaultTransport instead:
tr := rpc.NewDefaultTransport(rpc.DefaultMaxIdleConns, 50)
c := rpc.NewClient(url, rpc.WithHTTPClient(&http.Client{Transport: tr}))
func WithHeader ¶
func WithHeader(key, value string) ClientOption
WithHeader sets a single HTTP header on every request.
func WithHeaders ¶
func WithHeaders(headers http.Header) ClientOption
WithHeaders sets multiple HTTP headers on every request.
func WithMaxIdleConns ¶
func WithMaxIdleConns(n int) ClientOption
WithMaxIdleConns sets the total number of idle (keep-alive) connections across all hosts for the default transport. The default is DefaultMaxIdleConns (100). Has no effect when WithHTTPClient is also passed.
func WithMaxIdleConnsPerHost ¶
func WithMaxIdleConnsPerHost(n int) ClientOption
WithMaxIdleConnsPerHost sets the per-host idle connection pool size for the default transport. The default is DefaultMaxIdleConnsPerHost (10). Has no effect when WithHTTPClient is also passed.
func WithRetryPolicy ¶
func WithRetryPolicy(p RetryPolicy) ClientOption
WithRetryPolicy replaces the default retry policy.
type Codec ¶
Codec encodes and decodes JSON for the RPC client. The default implementation is StdCodec, which uses stdlib encoding/json. For allocation-sensitive workloads, swap in a faster library (such as github.com/goccy/go-json) via the WithCodec client option.
Implementations must be safe for concurrent use.
func GoJSONCodec ¶
func GoJSONCodec() Codec
GoJSONCodec returns a Codec backed by github.com/goccy/go-json, the fastest general-purpose JSON library in the Go ecosystem.
It is API-compatible with stdlib encoding/json and honours the json.Marshaler and json.Unmarshaler interfaces, so every type in this module that implements either (PublicKey, Hash, Signature, Uint8Slice, RawJSON, ...) round-trips correctly through it without any additional plumbing.
GoJSONCodec is the default Codec used by NewClient. StdCodec is kept available for environments where a hard dependency on goccy/go-json is not acceptable; pass WithCodec(StdCodec()) to opt out.
func StdCodec ¶
func StdCodec() Codec
StdCodec returns a Codec backed by stdlib encoding/json. It is kept available as an opt-in alternative to GoJSONCodec for environments where a hard dependency on github.com/goccy/go-json is not acceptable; pass it explicitly via WithCodec(StdCodec()).
This file is the only place in the rpc package that imports encoding/json at the type level, so swapping out JSON libraries is a single-file change.
type Config ¶
type Config struct {
// HTTPClient replaces the default http.Client entirely.
// When set, MaxIdleConns and MaxIdleConnsPerHost have no effect.
HTTPClient *http.Client
// MaxIdleConns is the total idle connection pool size across all hosts.
// Default: DefaultMaxIdleConns (100).
MaxIdleConns int
// MaxIdleConnsPerHost is the per-host idle connection pool size.
// Default: DefaultMaxIdleConnsPerHost (10).
MaxIdleConnsPerHost int
// Codec replaces the default GoJSONCodec.
Codec Codec
// RetryPolicy replaces the default exponential-backoff retry policy.
RetryPolicy RetryPolicy
// Headers are added to every request. Use for static auth headers.
Headers http.Header
// HTTPAuth is called before each request to inject auth headers dynamically.
HTTPAuth HTTPAuth
}
Config is a struct-based alternative to the WithXxx functional options. Zero values are ignored; the client defaults are used for unset fields.
rpc.NewClient(url, rpc.Config{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
Codec: rpc.StdCodec(),
})
type ContextValue ¶
type ContextValue[T any] struct { Context struct { Slot uint64 `json:"slot"` } `json:"context"` Value T `json:"value"` }
ContextValue is the generic shape every Solana RPC method that returns a context-wrapped value takes on the wire:
{"context": {"slot": <slot>}, "value": <T>}
To decode this envelope, instantiate CallContext with ContextValue[T] as the type argument:
resp, err := jsonrpc.CallContext[jsonrpc.ContextValue[*AccountInfo]](
ctx, c, "getAccountInfo", addr, cfg)
// resp.Context.Slot, resp.Value
The type is also reused by ws.Client notification dispatchers, which decode the same envelope shape from raw JSON bytes.
type ErrRPC ¶
ErrRPC is the error type returned when the Solana RPC server responded with a JSON-RPC 2.0 error object. It carries the method name, the numeric code, the human-readable message, any server-attached Data payload, and the raw response body for diagnostics.
Use errors.As to recover *ErrRPC from a wrapped error:
var rpcErr *rpc.ErrRPC
if errors.As(err, &rpcErr) {
// inspect rpcErr.Code, rpcErr.Data, etc.
}
type Error ¶
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data RawJSON `json:"data,omitempty"`
}
Error is a JSON-RPC 2.0 error object as defined by the JSON-RPC 2.0 specification. Solana's server populates Code and Message and may include a Data payload for method-specific context (for example, a decoded instruction error from simulateTransaction).
type HTTPAuth ¶
HTTPAuth is a function called before each HTTP request to inject authentication headers. It is safe for concurrent use. Example usage: API key authentication for RPC providers.
type RawJSON ¶
type RawJSON []byte
RawJSON holds a fragment of JSON bytes without decoding them. It implements json.Marshaler and json.Unmarshaler so it round-trips through any JSON library that honours those interfaces (stdlib encoding/json, goccy/go-json, jsoniter, ...).
RawJSON is behaviourally equivalent to encoding/json.RawMessage but is defined here so the rpc package does not need to import encoding/json at the type level.
func (RawJSON) MarshalJSON ¶
MarshalJSON implements json.Marshaler by returning the raw bytes. A zero-length RawJSON marshals to the JSON token "null" so that absent optional fields are rendered correctly.
func (RawJSON) String ¶
String returns the underlying bytes as a Go string. It is intended for logging and debugging; do not parse the result back into JSON via string operations.
func (*RawJSON) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler by copying the input bytes. The copy is required: JSON decoders typically reuse a scratch buffer across tokens, so the argument bytes are not guaranteed to remain valid after the call returns.
type Request ¶
type Request struct {
Version string `json:"jsonrpc"`
ID uint64 `json:"id"`
Method string `json:"method"`
Params any `json:"params"`
}
Request is a JSON-RPC 2.0 request. Version is always "2.0" for Solana RPC. Callers do not usually construct Request directly; use Client.Call instead.
type Response ¶
type Response struct {
Version string `json:"jsonrpc"`
ID uint64 `json:"id"`
Result RawJSON `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
Response is a JSON-RPC 2.0 response envelope. Exactly one of Result or Error is populated on a well-formed response. Result is kept as raw JSON so the client can decode it into an arbitrary typed value without this package needing to know its shape in advance.
type RetryPolicy ¶
type RetryPolicy interface {
// ShouldRetry is invoked after each failed attempt. attempt is
// the 1-based count of attempts so far. err is the reason the
// last attempt failed. When retry is true, the caller waits for
// delay before trying again; when false, delay is ignored and
// the error is returned to the user.
ShouldRetry(attempt int, err error) (delay time.Duration, retry bool)
}
RetryPolicy decides whether a failed attempt should be retried and how long to wait before the next attempt. Implementations are called once per failure; they should be pure with respect to the inputs (except for jitter) and safe for concurrent use.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
DefaultRetryPolicy returns the policy used by NewClient when the caller does not supply one: up to 5 attempts, exponential backoff starting at 200ms (capped at 10s), with full jitter on the delay.
Only transient errors are retried: HTTP 5xx and 429 responses, and non-context network errors. Context cancellation and deadline errors are never retried.