Introducing fluxrpc/solana-go: the Solana Go SDK we needed

We're releasing fluxrpc/solana-go, the Solana Go SDK that powers our own infrastructure.

CloakdDev avatar CloakdDev · Technical Founder at FluxRPC · August 21, 2026 · 9 min read

We're releasing our internal Solana Go SDK as open-source. It contains several substantial performance optimizations to codecs, caching, and processing large RPC responses.

Five years ago, gagliardetto/solana-go helped get me into Solana development. It holds a special place in my heart. It gave Go developers a practical way to build on Solana and became foundational infrastructure for much of the ecosystem.

But foundational infrastructure needs to be predictable.

After the project moved to solana-foundation/solana-go, new maintainers introduced several breaking changes, including changing the SOL constant address in a minor release. Contribution cycles became difficult, and upgrading a dependency started to feel risky.

After discussions with the original maintainer about these issues, we decided to build the SDK we needed: github.com/fluxrpc/solana-go.

A lean, optimized and spec-complete Solana SDK for Go, built around performance and long-term API stability.

Built from production requirements

This is not an academic rewrite.

Go powers much of the infrastructure behind FluxRPC, RugCheck and FluxBeam. The existing Solana Go stack appeared consistently in our profiling: base58, JSON decoding, account deserialization, transaction handling and RPC calls.

These operations often sit directly in the hottest paths of a Solana application. A few allocations or microseconds might look insignificant in isolation, but not when they are repeated millions of times. So we rebuilt the stack around the workloads we actually run.

The SDK provides complete coverage across:

  • Solana JSON-RPC over HTTP
  • All nine WebSocket pubsub subscriptions
  • Yellowstone gRPC Geyser
  • Legacy, v0 and SIMD-0385 v1 transactions

That coverage sits on top of optimized core types, binary decoding, JSON handling, base58 operations, transaction construction and account access.

A deliberately small dependency surface

The root SDK has only five direct dependencies:

  • fluxrpc/base58
  • bytedance/sonic
  • oasisprotocol/curve25519-voi
  • klauspost/compress
  • gobwas/ws

Each one has a specific purpose.

The gRPC and protobuf stack is isolated inside the nested yellowstone module. If your application only needs core types, HTTP RPC and WebSockets, it does not inherit Yellowstone's dependency tree. That matters for performance and build size, but it also matters for security.

The ecosystem is dealing with a constant stream of supply-chain attacks. Every unnecessary package, transitive dependency and maintainer relationship expands the surface that needs to be trusted and audited.

The previous SDK accumulated a large import tree over time. We wanted the opposite: keep the core small enough to understand, audit and maintain. A small dependency tree does not eliminate supply-chain risk, but it makes that risk considerably easier to reason about.

The benchmarks speak for themselves

The repository contains reproducible benchmarks comparing identical operations with the previous Go SDK.

Some examples:

OperationImprovement
Base58 data marshaling14.3× faster
Base58 data unmarshaling23.7× faster
Token account binary decoding4.1× faster, zero allocations
Message JSON marshaling3.6× faster
Signature parsing4.9× faster, zero allocations
Parsed transaction decoding2.2× faster
Parsed-block WebSocket notifications3.6× faster

The full tables are published in the repository, including operations where the difference is small or performance is roughly equal.

The point is not to manufacture an impressive headline number. It is to remove work from the hot paths that Solana applications execute continuously.

getProgramAccounts should be a stream

A large getProgramAccounts response can contain thousands of accounts and many megabytes of JSON.

Most RPC clients handle that response like this:

  1. Download the entire HTTP response.
  2. Hold the complete body in memory.
  3. Decode the JSON envelope.
  4. Decode the complete account array.
  5. Finally return the first account to the application.

That design forces network transfer and decoding to happen sequentially. The application cannot begin useful work until the final byte arrives, and memory usage scales with the complete response.

fluxrpc/solana-go can process it differently: GetProgramAccountsStream reads directly from the HTTP response body. A small incremental JSON scanner identifies each complete account value as its bytes arrive.

Accounts are decoded in bounded batches (up to 32 accounts or 256KB) to amortize decoder setup on fast connections. When the current network buffer runs dry, the batch is flushed immediately rather than waiting for more data. Then, each decoded account is passed to the application through a callback while the remainder of the response is still being downloaded.

That means:

  • Network transfer and decoding overlap
  • Work can begin as soon as the first accounts arrive
  • Backpressure is naturally controlled by the callback
  • The caller can abort early by returning an error
  • Memory is bounded by the largest account or current batch, rather than the full response
  • Returned accounts own their decoded data and can safely be retained

In our paced network benchmark (2,000 accounts arriving in 32KB chunks) the results were:

MeasurementBufferedStreamed
Time to first account6.2 ms0.09 ms
Total wall time6.2 ms4.0 ms
Memory2.1 MB686 KB
Allocations14,1004,300

The ~68× improvement in time-to-first-account is what was most important to us.

For a massive program, the application can be decoding, indexing or analyzing accounts while another client is still waiting to receive the full response body. With a provider that streams the response continuously, processing can be complete before a buffered provider has even returned the body to its caller.

This is not simply faster JSON decoding, it changes the execution model.

A proper Yellowstone client for Go

Yellowstone is increasingly important for serious Solana infrastructure, but Go has lacked a complete, ergonomic client around it.

github.com/fluxrpc/solana-go/yellowstone is a separate module providing:

  • Account, slot, transaction, block, block metadata and entry filters
  • Authenticated TLS connections
  • Live subscription filter updates without reconnecting
  • Support for large block messages
  • Yellowstone unary methods
  • Conversion into the SDK's native account and transaction types
  • Allocation-conscious update conversion

Converted transactions reserialize byte-identically to the original on-chain wire form and pass signature verification.

The API is very small:

req := yellowstone.NewRequest(pb.CommitmentLevel_CONFIRMED).
    AccountsByOwner("usdc", tokenProgram.String()).
    AllSlots("slots")

stream, err := client.Subscribe(ctx, req)

for {
    update, err := stream.Recv()
    if err != nil {
        break
    }

    if account := update.Account(); account != nil {
        process(account)
    }
}

On our benchmark machine, the client processes roughly 600,000 Yellowstone updates per second, including account conversion.

And because Yellowstone is a nested module, none of its gRPC or protobuf dependencies leak into the core SDK.

RPC caching as part of the client

The RPC package has its own in-memory, sharded and slot-aware cache.

Once enabled, existing methods such as GetAccountInfo and GetMultipleAccounts automatically use it. Applications do not need to replace their normal RPC access layer with a separate cache abstraction.

client := rpc.New(endpoint)
client.EnableCache()

account, err := client.GetAccountInfo(ctx, address)

Regular RPC results are cached for a configurable freshness window. Immutable accounts can be stored without expiry, while real-time streamed accounts remain valid on the assumption that their feed continues delivering every update.

The cache handles more than individual lookups:

  • Writes are ordered by slot
  • Older RPC responses cannot overwrite newer streamed state
  • GetMultipleAccounts serves available entries locally
  • Missing accounts are deduplicated and fetched in one RPC call
  • Idle entries are evicted by a janitor
  • Cache hits and misses are observable through CacheStats
  • Explicit WithOpts methods bypass the cache when the requested representation may differ

It also caches chain-head data. This includes slots, block heights and recent blockhashes, with separate short freshness windows. If a feed dies, the client falls back to RPC rather than continuing to serve a stale slot or expiring blockhash.

The raw cache performance is:

OperationResult
GetAccountInfo cache hit96 ns
Streamed update ingestion76 ns, zero allocations
Ingestion capacityroughly 13 million updates per second
GetMultipleAccounts with 100 cached accounts3.6 µs

For comparison, even a localhost RPC round trip takes roughly 125 µs.

Where Yellowstone and the cache meet

Yellowstone and the RPC cache are useful independently. Together, they remove network requests from the read path.

A Yellowstone stream can be piped directly into the RPC client:

client.EnableCache()

req := yellowstone.NewRequest(pb.CommitmentLevel_CONFIRMED).
    AccountsByOwner("accounts", programID.String()).
    AllSlots("slots").
    AllBlocksMeta("blocks")

stream, err := ys.Subscribe(ctx, req)
go stream.Pipe(rpc.CommitmentConfirmed, client)

The stream mirrors account updates, slots and block metadata into the cache. Existing calls to GetAccountInfo, GetMultipleAccounts, GetSlot, GetBlockHeight, GetLatestBlockhash and IsBlockhashValid can then be served locally.

If subscriptions cover the programs your application depends on, around 90% of account reads in many workloads can become local cache hits. This architecture has run internally at scale for more than two years across RugCheck and FluxBeam.

A RugCheck report performs thousands of checks against a token's state. Moving that state into a Yellowstone-fed local cache took typical analysis time from roughly 400 ms to 15 ms.

Instead of repeatedly asking the network for state, the application already has it.

Stability is a feature

Performance is important, but trust is more important.

Developers should be able to upgrade a minor version without discovering that a constant changed, an API disappeared or their application no longer compiles. Our commitment is to maintain a stable and consistent package, preserve compatibility, and extend it carefully as Solana evolves.

FluxRPC has a full team supporting the repository because we use it throughout our own infrastructure. Improvements are driven by production profiling, and regressions affect us directly.

What comes next

This release is the foundation.

Next, we're working on:

  • More guides for developers entering Solana through Go
  • Lantern integration to reduce latency and bandwidth further
  • A repository of typed interfaces for programs across Solana, removing the need for every developer to handle IDLs themselves
  • The foundations of a broader open-source Solana aggregator

The long-term goal is a fast, stable and coherent open-source stack for building across Solana in Go.

Try it

Install the core SDK:

go get github.com/fluxrpc/solana-go

Add Yellowstone when you need it:

go get github.com/fluxrpc/solana-go/yellowstone

The SDK works with any RPC provider. Pair it with FluxRPC when you want the infrastructure and client stack designed and optimized together.

If you migrate a workload, feel free to share your benchmarks, or let us know what could work better.

Source code

If you build on Solana in Go, star the repository and follow FluxRPC on X.

This is already powering our production systems — and it is only going to get faster.