> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitflare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# gRPC Client (Yellowstone)

> Yellowstone Geyser streaming with automatic reconnection, ping/pong liveness detection, and typed filters.

## Install

```bash theme={null}
go get github.com/orbitflare/orbitflare-sdk-go
```

```go theme={null}
import "github.com/orbitflare/orbitflare-sdk-go/geyser"
```

## Building the client

Here's a client with every option set:

```go theme={null}
import (
    "time"

    "github.com/orbitflare/orbitflare-sdk-go/geyser"
    "github.com/orbitflare/orbitflare-sdk-go/orbitflare"
)

client, err := geyser.NewBuilder().
    URL("http://ny.rpc.orbitflare.com:10000").
    FallbackURL("http://fra.rpc.orbitflare.com:10000").
    Retry(orbitflare.RetryPolicy{
        InitialDelay: 100 * time.Millisecond,
        MaxDelay:     30 * time.Second,
        Multiplier:   2.0,
        MaxAttempts:  0,
    }).
    Timeout(30 * time.Second).
    Keepalive(60 * time.Second).
    PingInterval(10 * time.Second).
    MaxMissedPongs(3).
    ChannelCapacity(4096).
    Build()
```

Minimal setup:

```go theme={null}
client, err := geyser.NewBuilder().
    URL("http://ny.rpc.orbitflare.com:10000").
    Build()
```

gRPC needs no API key. The URL falls back to the `ORBITFLARE_GRPC_URL` environment variable.

### Builder methods

**`.URL(url)`** - The primary gRPC endpoint. Falls back to `ORBITFLARE_GRPC_URL`. Use `http://` for plaintext HTTP/2 and `https://` for TLS.

```go theme={null}
.URL("http://ny.rpc.orbitflare.com:10000")
```

**`.URLs(urls...)`** - Primary + fallbacks in one call. First element is primary.

```go theme={null}
.URLs("http://ny.rpc.orbitflare.com:10000", "http://fra.rpc.orbitflare.com:10000")
```

**`.FallbackURL(url)` / `.FallbackURLs(urls...)`** - Add fallback endpoints. On connection failure, the SDK rotates through them.

```go theme={null}
.FallbackURL("http://fra.rpc.orbitflare.com:10000")
```

**`.Retry(policy)`** - Controls reconnection backoff. When a connection drops, the SDK waits `InitialDelay`, then multiplies it each attempt (capped at `MaxDelay`). Set `MaxAttempts` to 0 for infinite retries. Default: 100ms initial, 30s max, 2x multiplier, infinite.

```go theme={null}
.Retry(orbitflare.RetryPolicy{
    InitialDelay: 200 * time.Millisecond,
    MaxDelay:     15 * time.Second,
    Multiplier:   2.0,
    MaxAttempts:  0,
})
```

**`.Timeout(duration)`** - Connection/dial timeout. Default: 30s.

```go theme={null}
.Timeout(15 * time.Second)
```

**`.Keepalive(duration)`** - TCP/HTTP2 keepalive interval, used to detect dead connections at the transport level. Default: 60s.

```go theme={null}
.Keepalive(30 * time.Second)
```

**`.PingInterval(duration)`** - How often the SDK sends proto-level `Ping` messages to the server. The server should respond with a `Pong`. Default: 10s.

```go theme={null}
.PingInterval(15 * time.Second)
```

**`.MaxMissedPongs(n)`** - How many consecutive pings can go unanswered before the SDK considers the connection dead and reconnects. Default: 3. With the defaults, a dead connection is detected within 30 seconds.

```go theme={null}
.MaxMissedPongs(5)
```

**`.ChannelCapacity(n)`** - The bounded channel between the background goroutine and your code. If your code is slow to consume events, the background goroutine blocks when this fills up instead of eating unlimited memory. Default: 4096.

```go theme={null}
.ChannelCapacity(8192)
```

**`.Logger(logger)`** - A `*slog.Logger` for connection lifecycle events. When unset, logging is controlled by `ORBITFLARE_LOG`.

## Building a subscription

Filters are built programmatically with `SubscribeRequestBuilder` and the typed filter types. A transaction matches if it involves any `AccountInclude` address; `AccountExclude` removes matches; `AccountRequired` means every listed address must be present.

```go theme={null}
request := geyser.NewSubscribeRequestBuilder().
    Transactions("pumpfun", geyser.NewTransactionFilter().
        Vote(false).
        Failed(false).
        AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")).
    Accounts("usdc-holders", geyser.NewAccountFilter().
        Owner("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").
        Datasize(165).
        Memcmp(geyser.MemcmpBase58(0, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")).
        Lamports(geyser.LamportsGt(0))).
    Slots("slots", geyser.NewSlotFilter().FilterByCommitment(true)).
    Commitment(geyser.Confirmed).
    Build()
```

### Filter types

* **`NewTransactionFilter()`** - `Vote(bool)`, `Failed(bool)`, `Signature(string)`, `AccountInclude(...)`, `AccountExclude(...)`, `AccountRequired(...)`.
* **`NewAccountFilter()`** - `Account(...)`, `Owner(...)`, `Datasize(uint64)`, `TokenAccountState(bool)`, `Memcmp(...)`, `Lamports(...)`, `NonemptyTxnSignature(bool)`.
* **`NewSlotFilter()`** - `FilterByCommitment(bool)`, `InterslotUpdates(bool)`.
* **`NewBlockFilter()`** - `AccountInclude(...)`, `IncludeTransactions(bool)`, `IncludeAccounts(bool)`, `IncludeEntries(bool)`.
* **`NewDeshredTransactionFilter()`** - `Vote(bool)`, `AccountInclude(...)`, `AccountExclude(...)`, `AccountRequired(...)`.

`Memcmp` is built with `geyser.MemcmpBytes(offset, []byte)`, `geyser.MemcmpBase58(offset, string)`, or `geyser.MemcmpBase64(offset, string)`. `Lamports` with `geyser.LamportsEq/Ne/Lt/Gt(uint64)`. `Commitment` is one of `geyser.Processed`, `geyser.Confirmed`, `geyser.Finalized`.

`Subscribe` also accepts a raw `*geyser.SubscribeRequest` (a re-export of the proto type) if you'd rather build it by hand.

## Reading the stream

`Subscribe` returns a `*geyser.Stream`. Call `Next(ctx)` to get the next update:

```go theme={null}
stream := client.Subscribe(request)
defer stream.Close()

for {
    update, err := stream.Next(ctx)
    if errors.Is(err, io.EOF) {
        break // stream closed
    }
    if err != nil {
        break // fatal: all retries exhausted
    }

    switch {
    case update.GetTransaction() != nil:
        tx := update.GetTransaction()
        // tx.GetSlot(), tx.GetTransaction() (signature, accounts, instructions, meta)
    case update.GetAccount() != nil:
        acct := update.GetAccount()
        // acct.GetSlot(), acct.GetAccount() (pubkey, lamports, owner, data), acct.GetIsStartup()
    case update.GetSlot() != nil:
        slot := update.GetSlot()
        // slot.GetSlot(), slot.GetStatus()
    case update.GetBlockMeta() != nil:
        meta := update.GetBlockMeta()
        // meta.GetSlot(), meta.GetBlockhash(), meta.GetParentSlot()
    }
}
```

`Next(ctx)` blocks until an event arrives. It returns `io.EOF` once the stream has ended (including after `Close`); any other non-nil error is fatal for that stream. It also returns `ctx.Err()` if the context is cancelled. Pong messages are consumed internally and never appear in your stream.

### Closing

```go theme={null}
stream.Close()
```

Stops the background goroutine immediately. `client.Close()` stops every stream the client started.

## Multiple streams

One client can run many streams at once. Each gets its own background connection:

```go theme={null}
pumpfun := client.Subscribe(pumpfunReq)
raydium := client.Subscribe(raydiumReq)
slots := client.Subscribe(slotsReq)
```

They share endpoint health state - if one stream quarantines a failing endpoint, the others skip it on their next reconnect. But their connections and lifecycles are fully independent. You can spin up new streams at any time.

## Full example

A stream that watches pump.fun transactions and prints a summary for each one.

```go theme={null}
package main

import (
    "context"
    "errors"
    "fmt"
    "io"
    "log"

    "github.com/orbitflare/orbitflare-sdk-go/geyser"
)

func main() {
    client, err := geyser.NewBuilder().
        URL("http://ny.rpc.orbitflare.com:10000").
        FallbackURL("http://fra.rpc.orbitflare.com:10000").
        Build()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    request := geyser.NewSubscribeRequestBuilder().
        Transactions("pumpfun", geyser.NewTransactionFilter().
            Vote(false).
            Failed(false).
            AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")).
        Slots("slots", geyser.NewSlotFilter().FilterByCommitment(true)).
        Commitment(geyser.Confirmed).
        Build()

    stream := client.Subscribe(request)
    defer stream.Close()

    ctx := context.Background()
    var count int
    fmt.Println("streaming...")

    for {
        update, err := stream.Next(ctx)
        if errors.Is(err, io.EOF) {
            return
        }
        if err != nil {
            log.Fatal(err)
        }

        switch {
        case update.GetTransaction() != nil:
            count++
            tx := update.GetTransaction()
            fmt.Printf("#%d slot=%d\n", count, tx.GetSlot())
        case update.GetSlot() != nil:
            s := update.GetSlot()
            fmt.Printf("slot %d (%s)\n", s.GetSlot(), s.GetStatus())
        }
    }
}
```
