in-solana by Supanode

Solana gRPC in Go — Examples

TL;DR

  • - Generate Go gRPC stubs from Yellowstone protobuf definitions
  • - Use bidirectional streaming to subscribe to accounts, transactions, and slots
  • - Go's goroutines handle concurrent streams efficiently

Go is a natural fit for Solana gRPC streaming: its goroutine-based concurrency model handles multiple streams efficiently, and the compiled binary deploys without runtime dependencies.

Setup

Install the gRPC and protobuf tools:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Generate Go stubs from the Yellowstone protobuf:

git clone https://github.com/rpcpool/yellowstone-grpc
protoc --go_out=. --go-grpc_out=. proto/geyser.proto

Add dependencies:

go get google.golang.org/grpc
go get google.golang.org/grpc/credentials

Connecting to the gRPC endpoint

package main

import (
    "context"
    "crypto/tls"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"

    pb "your-module/geyser"
)

func connect(endpoint, token string) (pb.GeyserClient, error) {
    tlsCreds := credentials.NewTLS(&tls.Config{})
    conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(tlsCreds))
    if err != nil {
        return nil, err
    }
    return pb.NewGeyserClient(conn), nil
}

func authContext(ctx context.Context, token string) context.Context {
    return metadata.AppendToOutgoingContext(ctx, "x-token", token)
}

Subscribe to account updates

func subscribeAccounts(client pb.GeyserClient, token, owner string) error {
    ctx := authContext(context.Background(), token)
    stream, err := client.Subscribe(ctx)
    if err != nil {
        return err
    }

    req := &pb.SubscribeRequest{
        Accounts: map[string]*pb.SubscribeRequestFilterAccounts{
            "my_accounts": {
                Owner: []string{owner},
            },
        },
        Commitment: pb.CommitmentLevel_CONFIRMED.Enum(),
    }

    if err := stream.Send(req); err != nil {
        return err
    }

    for {
        resp, err := stream.Recv()
        if err != nil {
            return err
        }

        if acct := resp.GetAccount(); acct != nil {
            log.Printf("Account update: slot=%d pubkey=%x",
                acct.Slot, acct.Account.Pubkey)
        }
    }
}

Subscribe to transactions

func subscribeTransactions(client pb.GeyserClient, token, programID string) error {
    ctx := authContext(context.Background(), token)
    stream, err := client.Subscribe(ctx)
    if err != nil {
        return err
    }

    req := &pb.SubscribeRequest{
        Transactions: map[string]*pb.SubscribeRequestFilterTransactions{
            "program_txs": {
                AccountInclude: []string{programID},
            },
        },
        Commitment: pb.CommitmentLevel_CONFIRMED.Enum(),
    }

    if err := stream.Send(req); err != nil {
        return err
    }

    for {
        resp, err := stream.Recv()
        if err != nil {
            return err
        }

        if tx := resp.GetTransaction(); tx != nil {
            log.Printf("Transaction: slot=%d sig=%x",
                tx.Slot, tx.Transaction.Signature)
        }
    }
}

Concurrent subscriptions

Go’s goroutines make it easy to run multiple subscriptions in parallel:

func main() {
    client, err := connect("your-endpoint:443", "your-token")
    if err != nil {
        log.Fatal(err)
    }

    token := "your-token"

    go func() {
        for {
            err := subscribeAccounts(client, token, "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            log.Printf("Account stream disconnected: %v. Reconnecting...", err)
            time.Sleep(5 * time.Second)
        }
    }()

    go func() {
        for {
            err := subscribeTransactions(client, token, "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
            log.Printf("Transaction stream disconnected: %v. Reconnecting...", err)
            time.Sleep(5 * time.Second)
        }
    }()

    select {} // block forever
}

FAQ

Is there an official Go client library? No official high-level Go SDK exists. Use the generated gRPC stubs from the Yellowstone protobuf definitions.

How do I handle backpressure? If your processing is slower than the stream, use a buffered channel between the gRPC receiver goroutine and the processing goroutine. Drop or batch updates when the buffer is full.

What Go version is required? Go 1.21+ is recommended for the latest gRPC library features. The generated protobuf code requires the google.golang.org/protobuf module (not the deprecated github.com/golang/protobuf).

Updated: