> For the complete documentation index, see [llms.txt](https://docs.triton.one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.triton.one/solana/real-time-streaming/dragon-s-mouth-grpc.md).

# Dragon's Mouth gRPC

Dragon's Mouth is Triton's Yellowstone gRPC stream, with Subscribe filters, replay, and reconnect for Solana account, transaction, slot, and block updates.

Dragon's Mouth (or Yellowstone gRPC) taps the validator's Geyser plugin directly, so you get account, transaction, slot, and block updates as the validator processes them, ahead of JSON-RPC polling.

## Use cases

Dragon's Mouth is the gRPC stream for backend services that need low-latency `processed` updates.

* **Trading, MEV, market making, and bots.** Low-latency reads of pool state, oracles, AMM accounts, and price-impacting transactions.
* **DeFi backends.** Live pool, order-book, and account state for services that react to the chain in real time.
* **Real-time UIs (backend).** Live balances, transaction feeds, and account state for backends that proxy to wallets and explorers.

{% hint style="info" %}
**When not to use it:**

* Browsers and frontends cannot speak gRPC. Use [WebSocket](/solana/real-time-streaming/whirligig-websockets.md) instead.
* If your pipeline cannot miss a block (indexing, analytics, compliance), use [Fumarole](/solana/real-time-streaming/fumarole-persistent-streams.md), which guarantees at-least-once delivery and auto-backfills up to 4 days of data on reconnect.
  {% endhint %}

## Features and benefits

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-timer">:timer:</i> <strong>Sub-slot latency</strong></td><td>Intra-slot updates arrive ahead of JSON-RPC polling. You receive multiple account updates within the current slot.</td><td></td></tr><tr><td><i class="fa-filter">:filter:</i> <strong>Server-side filtering</strong></td><td>Filter by pubkey, program owner, signature, memcmp, datasize, lamports, or token-account state, all server-side.</td><td></td></tr><tr><td><i class="fa-right-left">:right-left:</i> <strong>Bi-directional streams</strong></td><td>Modify subscriptions on the fly without reconnecting. Send a new request, server swaps your filter set.</td><td></td></tr><tr><td><i class="fa-feather">:feather:</i> <strong>Compact Protobuf payloads</strong></td><td>Binary serialization cuts bandwidth and CPU.</td><td></td></tr><tr><td><i class="fa-compress">:compress:</i> <strong>Compressed account filters</strong></td><td>Track millions of accounts with a Cuckoo-filter set instead of a raw pubkey list, cutting subscription payloads ~10x.</td><td></td></tr><tr><td><i class="fa-arrows-rotate">:arrows-rotate:</i> <strong>Reconnect with bank recovery</strong></td><td>The Rust and TS clients can reconnect a stream for you, replay from the last confirmed slot seen, and flag partly delivered banks to discard.</td><td></td></tr></tbody></table>

## How it works

You open a gRPC stream, send a `SubscribeRequest` describing the accounts, transactions, slots, blocks, or entries you want, and matching updates arrive as protobuf messages at your chosen commitment level (`processed`, `confirmed`, or `finalized`).

The same stream accepts updated requests, the service answers one-shot unary calls, and on reconnect you can replay from a recent slot (`from_slot`) within the server's replay window.

```mermaid
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#F2EDF6','primaryBorderColor':'#7A4BA0','primaryTextColor':'#171717','lineColor':'#956FB3','secondaryColor':'#E4DBEC','tertiaryColor':'#D7C9E3','edgeLabelBackground':'#F2EDF6'},'flowchart':{'nodeSpacing':20,'rankSpacing':35,'curve':'linear'}}}%%
flowchart LR
    v["Validator<br/>(Geyser plugin)"] --> dm["Dragon's Mouth<br/>Yellowstone gRPC"] --> app["Your app"]
    app -.->|"SubscribeRequest (filters)"| dm
    style app fill:#D6EAF8,stroke:#259DD0
```

## Supported methods

{% tabs %}
{% tab title="Streaming subscriptions" %}

| Stream                    | What you receive                                                                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Account writes            | Updates whenever a matching account's data, lamports, or owner changes                                                                                                               |
| Transactions              | Every transaction matching your filter, with full `meta` (logs, status, balance deltas)                                                                                              |
| Deshred Transactions gRPC | Transactions reconstructed from shreds *before* execution. Separate `SubscribeDeshred` method. See [Deshred Transactions gRPC](/solana/real-time-streaming/deshred-transactions.md). |
| Entries                   | Solana ledger entries (low-level, rare use case)                                                                                                                                     |
| Block notifications       | Full blocks as they're produced, optionally with their transactions and accounts                                                                                                     |
| Block metadata            | Block headers only, without the transaction payload                                                                                                                                  |
| Slot notifications        | Slot-status events (processed/confirmed/finalized plus intra-slot lifecycle)                                                                                                         |
| {% endtab %}              |                                                                                                                                                                                      |

{% tab title="Unary operations" %}

| Method                | Returns                                                              |
| --------------------- | -------------------------------------------------------------------- |
| `getLatestBlockhash`  | The latest blockhash + last valid block height at a commitment level |
| `getBlockHeight`      | The current block height at a commitment level                       |
| `getSlot`             | The current slot at a commitment level                               |
| `isBlockhashValid`    | Whether a given blockhash is still valid                             |
| `subscribeReplayInfo` | Earliest slot still available for stream replay                      |
| `getVersion`          | The Geyser gRPC plugin and Solana version                            |
| `ping`                | Liveness check; the server replies with a `pong`                     |
| {% endtab %}          |                                                                      |
| {% endtabs %}         |                                                                      |

## Subscribe request

Every stream subscription is a single `SubscribeRequest` message. It has fields that apply across all stream types, as well as a per-stream filter map.

You send it once when you open the stream; then you can send it again at any time to modify it (the new request fully replaces the old one).

{% tabs %}
{% tab title="commitment" %}
Commitment level for buffering. Optional. Defaults to `processed`.

| Value           | Meaning                                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `PROCESSED` (0) | Highest slot seen, possibly on a fork. Lowest latency.                                                      |
| `CONFIRMED` (1) | Same state as `FINALIZED` on Alpenglow.                                                                     |
| `FINALIZED` (2) | On Alpenglow there are no vote towers or lockouts: a slot is finalized by certificates of aggregated votes. |

Alpenglow has been active on Solana mainnet since epoch 1064 (21 Oct 2026). `CONFIRMED` and `FINALIZED` updates arrive together.

Stream at `processed` and track `confirmed` and `finalized` yourself, rather than asking the server to buffer by commitment. The pattern:

1. Subscribe to [slot notifications](#slots) alongside your data stream, and leave `filter_by_commitment` unset so all three statuses arrive.
2. Buffer incoming events by slot and `bank_id`.
3. When you see a slot status change to `confirmed` or `finalized`, release the buffer with the same slot and `bank_id`. On `finalized`, also drop the buffered events of the slot's other banks.
4. A bank's events always arrive *before* its commitment update. See [event order per bank](#event-order-per-bank).

{% hint style="info" %}
**The server fills in missing commitment updates.** The validator does not send a commitment status for every slot. When a slot reaches `confirmed` or `finalized`, Dragon's Mouth also sends that status for each ancestor slot that has not reached it yet.
{% endhint %}
{% endtab %}

{% tab title="ping" %}
Sends a periodic keepalive to prevent idle-stream drops by upstream cloud providers (e.g. Cloudflare). Server replies with `pong`. Optional but recommended for long-running streams.

See [pings](#sending-pings).
{% endtab %}

{% tab title="accountsDataSlice" %}
Truncates account data payloads to a byte range, reducing bandwidth. Optional. Empty array means full payload.

Example: `[{ "offset": 32, "length": 40 }]` returns 40 bytes starting at byte 32.
{% endtab %}

{% tab title="fromSlot" %}
Replay buffered updates starting from this slot, then continue live on the same stream. Optional. Used for reconnection after short disconnections.

See [replay from a slot](#replay-from-a-slot).
{% endtab %}
{% endtabs %}

### Multiplexing in one connection

{% hint style="warning" %}
**Multiplexing over a single gRPC connection is not ideal for performance.** Concurrent streams on one TCP connection fragment and contend through head-of-line blocking.
{% endhint %}

Several named filters in one `SubscribeRequest` share one stream. The warning above is about opening several streams on one connection.

A single gRPC connection can carry many subscriptions. Add multiple named entries to any filter map (for example `accounts`, `transactions`, `slots`, `blocks`, `blocksMeta`, `entry`) and they all stream over the same connection. Each match is tagged with the filter name(s) that produced it, so you can route updates downstream.

The canonical multiplex example is the [Multiple programs Accounts subscription](#accounts), two named owner filters under `accounts`, one connection. The same pattern works across stream types: subscribe to accounts AND transactions AND slots in one request.

### Filter configuration

Each `SubscribeRequest` has a map per stream type. The key is a label you choose (returned with each match so you can route it); the value is the filter spec.

<details>

<summary>Account filters</summary>

| Parameter | Type               | Required | Description                                                                                                                                          |
| --------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account` | `string[]`         | -        | List of pubkeys to subscribe to directly. Each must be a base58-encoded address.                                                                     |
| `owner`   | `string[]`         | -        | List of program owners. Subscribes to every account owned by these programs.                                                                         |
| `filters` | `AccountsFilter[]` | -        | Content-based filters on the account data. Each filter is one of: `memcmp`, `datasize`, `tokenAccountState`, or `lamports`. Combined as logical AND. |

</details>

<details>

<summary>Transaction filters</summary>

| Parameter          | Type       | Required | Description                                                                               |
| ------------------ | ---------- | -------- | ----------------------------------------------------------------------------------------- |
| `vote`             | `bool`     | -        | Include or exclude vote transactions. `false` excludes votes. Alpenglow blocks have none. |
| `failed`           | `bool`     | -        | Include or exclude failed transactions. `false` excludes failures.                        |
| `signature`        | `string`   | -        | Match one transaction by its signature.                                                   |
| `account_include`  | `string[]` | -        | Accounts mentioned anywhere in the transaction.                                           |
| `account_exclude`  | `string[]` | -        | Exclude transactions mentioning these accounts.                                           |
| `account_required` | `string[]` | -        | Accounts that MUST all be mentioned (every one of them).                                  |

</details>

<details>

<summary>Block filters</summary>

| Parameter              | Type       | Required | Description                                                             |
| ---------------------- | ---------- | -------- | ----------------------------------------------------------------------- |
| `account_include`      | `string[]` | -        | Only deliver transactions and accounts in the block that mention these. |
| `include_transactions` | `bool`     | -        | Include the block's transactions.                                       |
| `include_accounts`     | `bool`     | -        | Include accounts updated during the block.                              |
| `include_entries`      | `bool`     | -        | Include the block's entries.                                            |

</details>

<details>

<summary>Slot filters</summary>

| Parameter              | Type   | Required | Description                                                                                                                                          |
| ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `filter_by_commitment` | `bool` | -        | If `true`, only receive updates matching the request's `commitment`. If `false`, receive `processed`, `confirmed`, and `finalized` updates.          |
| `interslot_updates`    | `bool` | -        | If `true`, also receive the intra-slot statuses. Has no effect when `filter_by_commitment` is `true`. See [intra-slot updates](#intra-slot-updates). |

</details>

### Filter logic

Within a single filter category (e.g. one transaction filter), values are combined as follows:

* **Within an array, OR**, except `account_required`. `account_include: [A, B]` matches transactions mentioning A *or* B.
* **`account_required` needs every listed account.** `account_required: [A, B]` matches transactions mentioning both A *and* B.
* **Across fields, AND.** `vote: false, account_include: [A]` matches non-vote transactions mentioning A.

For transactions specifically:

```
match = (accountInclude OR empty) AND (accountRequired AND all) AND NOT accountExclude
```

Across multiple named filters in the same category (e.g. two transaction filters), you receive matches from any of them. Each match comes tagged with the filter name(s) that produced it, so you can route updates downstream.

### Excluded programs

Account updates owned by Light Protocol / ZK Compression (`compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq`) are not delivered on any Dragon's Mouth stream. To consume that data, use the [Photon indexer](https://github.com/helius-labs/photon).

## Subscribing to each stream type

The examples below use gRPC JSON for the request body and TypeScript for the client. Other languages use the same shape. Each example assumes you've already created and connected a `Client` (see [clients and SDKs](#clients-and-sdks)).

The latest protobuf files live in the [yellowstone-grpc repo](https://github.com/rpcpool/yellowstone-grpc/tree/master/yellowstone-grpc-proto/proto). For Rust, use the [yellowstone-grpc-proto crate](https://crates.io/crates/yellowstone-grpc-proto).

### Accounts

{% tabs %}
{% tab title="By pubkey" %}
Subscribe to one or more specific accounts. The label `wsol/usdc` is yours, it comes back on each match for routing.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": { "slots": {} },
  "accounts": {
    "wsol/usdc": {
      "account": ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": [],
  "commitment": 0
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  slots: { slots: {} },
  accounts: {
    "wsol/usdc": {
      account: ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
};
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{
        CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts,
        SubscribeRequestFilterSlots,
    },
};

let mut accounts = HashMap::new();
accounts.insert(
    "wsol/usdc".to_string(),
    SubscribeRequestFilterAccounts {
        account: vec!["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6".to_string()],
        owner: vec![],
        filters: vec![],
        ..Default::default()
    },
);

let mut slots = HashMap::new();
slots.insert("client".to_string(), SubscribeRequestFilterSlots::default());

let request = SubscribeRequest {
    accounts,
    slots,
    commitment: Some(CommitmentLevel::Processed.into()),
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="By owner" %}
Subscribe to every account owned by a program. The example tracks all Solend accounts.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": { "slots": {} },
  "accounts": {
    "solend": {
      "owner": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": [],
  "commitment": 0
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  slots: { slots: {} },
  accounts: {
    solend: {
      owner: ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
};
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{
        CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts,
        SubscribeRequestFilterSlots,
    },
};

let mut accounts = HashMap::new();
accounts.insert(
    "solend".to_string(),
    SubscribeRequestFilterAccounts {
        account: vec![],
        owner: vec!["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo".to_string()],
        filters: vec![],
        ..Default::default()
    },
);

let mut slots = HashMap::new();
slots.insert("client".to_string(), SubscribeRequestFilterSlots::default());

let request = SubscribeRequest {
    accounts,
    slots,
    commitment: Some(CommitmentLevel::Processed.into()),
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Multiple programs" %}
Combine multiple programs in one filter, or use separate filters for different routing tags.

Both programs in one filter:

```json
{
  "slots": { "slots": {} },
  "accounts": {
    "programs": {
      "owner": [
        "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo",
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
      ]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": []
}
```

Separate filters with different tags:

```json
{
  "slots": { "slots": {} },
  "accounts": {
    "solend": { "owner": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"] },
    "serum":  { "owner": ["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"] }
  },
  "transactions": {},
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": []
}
```

{% endtab %}

{% tab title="Advanced filters + data slice" %}
Filter by content (memcmp, token account state) and trim the returned payload to save bandwidth. The example tracks USDC token accounts and returns only 40 bytes starting at offset 32 (owner + amount).

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "accounts": {
    "usdc": {
      "owner": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
      "filters": [
        { "token_account_state": true },
        {
          "memcmp": {
            "offset": 0,
            "base58": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
          }
        }
      ]
    }
  },
  "accounts_data_slice": [{ "offset": 32, "length": 40 }]
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  slots: {},
  accounts: {
    usdc: {
      owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
      filters: [
        { tokenAccountState: true },
        {
          memcmp: {
            offset: "0",
            base58: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          },
        },
      ],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  commitment: CommitmentLevel.PROCESSED,
  accountsDataSlice: [{ offset: "32", length: "40" }],
};
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{
        subscribe_request_filter_accounts_filter::Filter,
        subscribe_request_filter_accounts_filter_memcmp::Data,
        CommitmentLevel, SubscribeRequest, SubscribeRequestAccountsDataSlice,
        SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter,
        SubscribeRequestFilterAccountsFilterMemcmp,
    },
};

let mut accounts = HashMap::new();
accounts.insert(
    "usdc".to_string(),
    SubscribeRequestFilterAccounts {
        account: vec![],
        owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
        filters: vec![
            SubscribeRequestFilterAccountsFilter {
                filter: Some(Filter::TokenAccountState(true)),
            },
            SubscribeRequestFilterAccountsFilter {
                filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp {
                    offset: 0,
                    data: Some(Data::Base58(
                        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
                    )),
                })),
            },
        ],
        ..Default::default()
    },
);

let request = SubscribeRequest {
    accounts,
    accounts_data_slice: vec![SubscribeRequestAccountsDataSlice {
        offset: 32,
        length: 40,
    }],
    commitment: Some(CommitmentLevel::Processed.into()),
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Compressed filters" %}
For subscriptions tracking thousands of accounts, the explicit pubkey list dominates the payload (1M pubkeys = \~44 MB per resend). Compressed account filters carry your tracked set as a [Cuckoo filter](https://en.wikipedia.org/wiki/Cuckoo_filter), a probabilistic data structure storing small fingerprints instead of full 32-byte pubkeys, cutting payload size \~10x.

| Tracked accounts | Compressed filter | Explicit pubkey list |
| ---------------: | ----------------: | -------------------: |
|            1,000 |           \~4 KiB |              \~44 KB |
|           10,000 |          \~32 KiB |             \~440 KB |
|          100,000 |         \~256 KiB |             \~4.4 MB |
|        1,000,000 |           \~4 MiB |              \~44 MB |
|        2,000,000 |           \~8 MiB |              \~84 MB |

Inserts and removes on the filter are O(1), so account-set mutations skip a full filter rebuild. And because it uses SipHash-2-4, a TypeScript client and a Rust client emit identical filter bytes for the same pubkey set, so one tracked set works across languages and Rust compiler versions.

**Tradeoffs:**

* **False positives** stay under 1%. Your client should keep an exact tracked set and drop incoming updates whose pubkey isn't in it. A `HashSet` check is the standard pattern; the probabilistic part lives only on the wire.
* **One-time build cost.** A 2M-account filter takes \~390 ms on a release build. Every subsequent insert, remove, and resend is cheap.
* **Request size limit on shared mainnet endpoints.** The server rejects a `SubscribeRequest` larger than 8 MiB (8,388,608 bytes). A filter built for 2,000,000 accounts is exactly 8 MiB before the rest of the request, so it is rejected. A filter built for about 1.99 million accounts or fewer is 4 MiB or smaller and fits.

Available in the Rust and TypeScript clients (TypeScript from `@triton-one/yellowstone-grpc` v5.0.9). The Rust API builds a `CompressedAccountFilterSet` and attaches it to your `SubscribeRequest`:

```rust
use yellowstone_grpc_proto::cuckoo::CompressedAccountFilterSet;

let mut accounts = CompressedAccountFilterSet::with_capacity(2_000_000)?;
for pk in my_tracked_pubkeys() {
    accounts.insert(*pk)?;
}

let mut req = SubscribeRequest::default();
accounts.insert_into_subscribe_request(&mut req, "tracked");
```

Mutate and resend when the set changes:

```rust
accounts.insert(new_pubkey)?;
accounts.remove(old_pubkey);
accounts.insert_into_subscribe_request(&mut req, "tracked");
```

Drop false positives against your local exact set:

```rust
if accounts.contains(incoming_pubkey) {
    // one of your tracked accounts
}
```

Full deep-dive: [Compressed filters for Yellowstone gRPC](https://blog.triton.one/compressed-filters-for-yellowstone-grpc-track-millions-of-accounts-with-10x-less-overhead).
{% endtab %}
{% endtabs %}

### Transactions

Use the `transactions` subscription to receive executed transactions at your chosen commitment level; `processed` delivers them as soon as the node processes them, while `confirmed` and `finalized` add latency. To receive transactions before the node executes them, use [Deshred Transactions gRPC](/solana/real-time-streaming/deshred-transactions.md), a separate gRPC method on the same service. It delivers transactions reconstructed from shreds.

{% tabs %}
{% tab title="All non-vote, non-failed" %}
Subscribe to every successful, non-vote transaction at `processed` commitment.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": { "slots": {} },
  "accounts": {},
  "transactions": {
    "alltxs": { "vote": false, "failed": false }
  },
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": [],
  "commitment": 0
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  slots: { slots: {} },
  accounts: {},
  transactions: {
    alltxs: { vote: false, failed: false },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
};
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{
        CommitmentLevel, SubscribeRequest, SubscribeRequestFilterTransactions,
    },
};

let mut transactions = HashMap::new();
transactions.insert(
    "alltxs".to_string(),
    SubscribeRequestFilterTransactions {
        vote: Some(false),
        failed: Some(false),
        ..Default::default()
    },
);

let request = SubscribeRequest {
    transactions,
    commitment: Some(CommitmentLevel::Processed.into()),
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Mentioning an account" %}
Subscribe to non-vote transactions that mention a specific account anywhere.

{% code title="gRPC" %}

```json
{
  "transactions": {
    "serum": {
      "vote": false,
      "account_include": ["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Excluding accounts" %}
Drop transactions that mention any of the listed accounts.

{% code title="gRPC" %}

```json
{
  "transactions": {
    "serum": {
      "account_exclude": [
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      ]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Combined include + exclude" %}
Combine include + exclude in one filter. The example matches transactions mentioning a Serum program but excludes any that also touch a specific account.

{% code title="gRPC" %}

```json
{
  "transactions": {
    "serum": {
      "account_include": ["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"],
      "account_exclude": ["9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="By signature" %}
Subscribe to one transaction by its signature.

{% code title="gRPC" %}

```json
{
  "transactions": {
    "sign": {
      "signature": "5rp2hL9b6kexex11Mugfs3vfU9GhieKruj4CkFFSnu52WLxiGn4VcLLwsB62XURhMmT1j4CZiXT6FFtYbXsLq2Zs"
    }
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

For the wire format of each transaction update, see [`geyser.proto`](https://github.com/rpcpool/yellowstone-grpc/blob/master/yellowstone-grpc-proto/proto/geyser.proto) (search for `SubscribeUpdateTransaction`).

### Slots

Subscribe to slot status changes. No filter parameters needed beyond a label.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": { "incoming_slots": {} },
  "accounts": {},
  "transactions": {},
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": []
}
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{SubscribeRequest, SubscribeRequestFilterSlots},
};

let mut slots = HashMap::new();
slots.insert(
    "incoming_slots".to_string(),
    SubscribeRequestFilterSlots::default(),
);

let request = SubscribeRequest {
    slots,
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}

Each update carries a `SlotStatus` enum. See [intra-slot updates](#intra-slot-updates) for all statuses and [event order per bank](#event-order-per-bank).

### Blocks

{% tabs %}
{% tab title="All blocks" %}
Receive every block as it's produced, with full transaction payload.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": { "blocks": {} },
  "blocks_meta": {},
  "accounts_data_slice": []
}
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{SubscribeRequest, SubscribeRequestFilterBlocks},
};

let mut blocks = HashMap::new();
blocks.insert(
    "blocks".to_string(),
    SubscribeRequestFilterBlocks::default(),
);

let request = SubscribeRequest {
    blocks,
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Customize payload" %}
Trim the block payload by toggling `include_transactions` / `include_accounts`.

{% code title="gRPC" %}

```json
{
  "blocks": {
    "blocks": {
      "include_transactions": false,
      "include_accounts": true
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Filtered by account" %}
Restrict the block's transactions and accounts to those mentioning specific addresses.

{% code title="gRPC" %}

```json
{
  "blocks": {
    "blocks": {
      "account_include": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Block metadata only" %}
Get block headers without the transaction payload (much smaller messages).

{% code title="gRPC" %}

```json
{
  "blocks": {},
  "blocks_meta": { "blockmetadata": {} }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Entries

Subscribe to ledger entries (groups of transactions that make up a slot). No filter parameters are required; subscribe by adding any named entry filter.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "entry": { "all": {} }
}
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    std::collections::HashMap,
    yellowstone_grpc_proto::prelude::{SubscribeRequest, SubscribeRequestFilterEntry},
};

let mut entry = HashMap::new();
entry.insert("all".to_string(), SubscribeRequestFilterEntry::default());

let request = SubscribeRequest {
    entry,
    ..Default::default()
};
```

{% endtab %}
{% endtabs %}

## Modifying and unsubscribing

The subscribe stream is bi-directional. Send a new `SubscribeRequest` at any time to update filters. It fully replaces the previous subscription, so your client must keep a local copy of the full config it wants.

To unsubscribe from everything but keep the connection open:

{% code title="gRPC" %}

```json
{
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {},
  "blocks_meta": {}
}
```

{% endcode %}

## Sending pings

Some cloud providers (e.g. Cloudflare) close idle streams. To avoid this, you need to keep sending pings to the server. The server sends its own `ping` every 10 seconds; when you send a ping with an `id`, it replies with a `pong` carrying the same `id`.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
const PING_INTERVAL_MS = 10_000;

const pingRequest = {
  ping: { id: 1 },
  accounts: {},
  accountsDataSlice: [],
  transactions: {},
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  blockFooter: {},
  entry: {},
  slots: {},
};

setInterval(async () => {
  await new Promise<void>((resolve, reject) => {
    stream.write(pingRequest, (err) =>
      err ? reject(err) : resolve()
    );
  });
}, PING_INTERVAL_MS);

stream.on("data", (data) => {
  if (data.pong) {
    console.log("Received pong");
  }
});
```

{% endtab %}

{% tab title="Rust" %}
A maintained Rust example that sends pings and handles the server's ping and pong updates: [subscribe-ping.rs in rpcpool/yellowstone-grpc](https://github.com/rpcpool/yellowstone-grpc/blob/master/examples/rust/src/bin/subscribe-ping.rs).
{% endtab %}
{% endtabs %}

## Replay from a slot

Dragon's Mouth can replay recently buffered updates by setting `from_slot` on `SubscribeRequest`. The server replays buffered updates from that slot, then continues live on the same stream.

Use it for short-disconnection recovery:

1. Track the latest slot your application has processed.
2. On disconnect, reconnect and resubscribe with `from_slot` set to that slot.
3. If the slot is no longer available, fall back to a fresh live subscription or your own backfill path.

{% tabs %}
{% tab title="gRPC" %}

```json
{
  "slots": { "incoming_slots": {} },
  "accounts": {},
  "transactions": {
    "alltxs": { "vote": false, "failed": false }
  },
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": [],
  "from_slot": 382001234
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const request = {
  slots: { incoming_slots: {} },
  accounts: {},
  transactions: {
    alltxs: { vote: false, failed: false, accountInclude: [], accountExclude: [], accountRequired: [] },
  },
  transactionsStatus: {},
  entry: {},
  blocks: {},
  blocksMeta: {},
  blockFooter: {},
  accountsDataSlice: [],
  fromSlot: "382001234",
};
```

{% endtab %}
{% endtabs %}

To discover the earliest replayable slot, call the unary `subscribeReplayInfo` RPC:

{% code title="TypeScript" %}

```typescript
const info = await client.subscribeReplayInfo();
console.log(info.firstAvailable);
```

{% endcode %}

{% hint style="info" %}

* Replay only covers the server's retained replay window: about 1,000 slots on shared mainnet endpoints and about 300 slots on shared devnet. On dedicated nodes the window varies, so contact support for yours.
* If `from_slot` is older than the earliest available, the request fails. Retry with a newer slot.
* Replay uses the same filters and commitment level as the live subscription.
* Replay starts at a slot boundary, so you may receive duplicate updates from your last-processed slot. Dedupe them.
  {% endhint %}

{% hint style="info" %}
For teams self-hosting a Yellowstone gRPC server, replay is on by default (`replay_stored_slots` defaults to `150`); set it to `0` to disable replay.
{% endhint %}

### Reconnect with bank recovery

Ordinary subscriptions don't reconnect. The Rust methods `subscribe`, `subscribe_with_request`, and `subscribe_once`, and the TypeScript methods `subscribe()` and `subscribeDeshred()`, end at the first disconnect, even if you set a reconnect config.

To have the client reconnect for you, open the stream with `subscribe_with_reconnect` (Rust, `yellowstone-grpc-client` 17.0.0 and later) or `subscribeWithReconnect()` (TypeScript, `@triton-one/yellowstone-grpc` 10.0.0 and later). Earlier versions have a different reconnect API.

The request:

* can use any commitment level;
* can set `from_slot`: the first connection replays from it, and the server ignores `from_slot` in later requests;
* must not name a filter `__reconn` (`RECONNECT_FILTER_KEY`). The client adds its own block meta and slot filters under that name and hides updates that only they match. The Rust client overwrites a filter with that name; the TypeScript client rejects the request.

The client must not request a startup snapshot (`x-request-snapshot`); otherwise opening the stream fails with `FAILED_PRECONDITION`.

To change the filters, send a new `SubscribeRequest` on the sink (Rust) or write it to the stream (TypeScript). The change applies to the current connection and to every reconnect.

When the connection fails with a retryable status (`UNAVAILABLE`, `INTERNAL`, `UNKNOWN`, `CANCELLED`, `DEADLINE_EXCEEDED`, `RESOURCE_EXHAUSTED`, `ABORTED`, or `DATA_LOSS`), or the server ends it, the stream:

1. Emits one `Disconnected` event that lists the banks you received only in part.
2. Waits the backoff delay, then resubscribes with the latest request and the `from_slot` the reconnect policy picks.
3. Drops whatever a later connection sends of a listed bank that the replay doesn't resend.

The reconnect policy (`ReconnectConfig::policy` in Rust, `policy` in TypeScript) picks where the new connection starts:

* **`Replay`** (the default) replays from `slots_behind` slots before the last confirmed slot the stream saw. `slots_behind` defaults to `0`.
  * Before the stream sees a confirmed slot, it replays from the first request's `from_slot`, or resumes at the live tip if there is none.
  * The replay resends banks you already received whole. Skip those duplicates.
  * A replayed bank carries only the latest write of each account, so it can have fewer account updates than the same bank delivered live. The final state of each account is the same.
  * The replay slot must be inside the server's [replay window](#replay-from-a-slot); otherwise the reconnect fails and the stream ends with an error.
* **`NoReplay`** resumes at the live tip. Updates sent during the outage are lost.

The backoff (`ReconnectConfig::backoff` in Rust, `backoff` in TypeScript) is a retry budget:

* The default is 10 ms, doubling, with 3 retries.
* Each reconnect spends one retry and waits first: the initial interval for the first reconnect, multiplied by the multiplier for each next one.
* A connection that delivers a Block, block footer, or block meta, including one from the client's own `__reconn` filter, refills the budget. A ping or a slot status alone doesn't.
* With the budget spent, the next disconnect ends the stream with its error. With 0 retries, the stream never reconnects.
* Within one reconnect, the client retries a failed connect up to the same number of times, with the same delays.

The stream returns an error and ends when the status isn't retryable, a reconnect fails, or the retry budget is spent. Open a new subscription.

Your code handles two event types:

* **`Update`** carries a `SubscribeUpdate`, as an ordinary subscription delivers it.
* **`Disconnected`** marks a dropped connection. Every update after it comes from the new connection.
  * `partial_banks` (`partialBanks` in TypeScript) lists, in slot order and by `slot` and `bank_id`, the banks you received content of (account writes, transactions, transaction statuses, entries, or `CreatedBank`) but not their Block, block footer, or block meta. Discard their state.
  * A replay resends the listed banks at or above `replay_from_slot` whole.
  * `replay_from_slot` (`replayFromSlot`) is the slot the new connection replays from, or `None` (`null`) when it resumes at the live tip.

{% tabs %}
{% tab title="Rust" %}
{% code title="Cargo.toml" %}

```toml
[dependencies]
yellowstone-grpc-client = "17"
yellowstone-grpc-proto = "14"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
tonic = { version = "0.14", features = ["tls-native-roots", "tls-ring"] } # TLS roots and crypto provider
anyhow = "1"
```

{% endcode %}

{% code title="src/main.rs" %}

```rust
use {
    futures::StreamExt,
    std::{
        collections::{BTreeSet, HashMap},
        time::Duration,
    },
    yellowstone_grpc_client::{
        Backoff, BankRef, ClientTlsConfig, GeyserGrpcClient, ReconnectConfig, ReconnectEvent,
        ReconnectPolicy,
    },
    yellowstone_grpc_proto::prelude::{
        subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest,
        SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterTransactions,
    },
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut client =
        GeyserGrpcClient::build_from_shared("https://<your-endpoint>.mainnet.rpcpool.com:443")?
            .x_token(Some("<your-token>"))?
            .tls_config(ClientTlsConfig::new().with_native_roots())?
            .http2_adaptive_window(true)
            // Optional. The defaults are 10 ms, doubling, 3 retries, and Replay { slots_behind: 0 }.
            .set_reconnect_config(
                ReconnectConfig::default()
                    .with_backoff(Backoff::new(Duration::from_millis(100), 2.0, 10))
                    .with_policy(ReconnectPolicy::Replay { slots_behind: 0 }),
            )
            .connect()
            .await?;

    let request = SubscribeRequest {
        transactions: HashMap::from([(
            "pool".to_string(),
            SubscribeRequestFilterTransactions {
                vote: Some(false),
                failed: Some(false),
                account_include: vec!["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6".to_string()],
                ..Default::default()
            },
        )]),
        // On one connection, a bank's block meta arrives after all of its content
        blocks_meta: HashMap::from([(
            "blocks".to_string(),
            SubscribeRequestFilterBlocksMeta::default(),
        )]),
        commitment: Some(CommitmentLevel::Processed.into()),
        ..Default::default()
    };

    // A request sent on the sink changes the filters, now and after every reconnect
    let (_sink, mut stream) = client.subscribe_with_reconnect(Some(request)).await?;

    // Transactions per bank in progress, and the banks already complete
    let mut in_progress: HashMap<BankRef, usize> = HashMap::new();
    let mut complete: BTreeSet<BankRef> = BTreeSet::new();

    while let Some(event) = stream.next().await {
        match event {
            Ok(ReconnectEvent::Update(update)) => match update.update_oneof {
                Some(UpdateOneof::Transaction(tx)) => {
                    let bank = BankRef { slot: tx.slot, bank_id: tx.bank_id };
                    // A replay resends complete banks: skip them
                    if !complete.contains(&bank) {
                        *in_progress.entry(bank).or_default() += 1;
                    }
                }
                Some(UpdateOneof::BlockMeta(meta)) => {
                    let bank = BankRef { slot: meta.slot, bank_id: meta.bank_id };
                    if complete.insert(bank) {
                        let count = in_progress.remove(&bank).unwrap_or_default();
                        println!("slot={} bank_id={} complete: {count} transactions", bank.slot, bank.bank_id);
                    }
                    while complete.len() > 4096 {
                        complete.pop_first();
                    }
                }
                _ => {}
            },
            Ok(ReconnectEvent::Disconnected(disconnected)) => {
                // Drop the banks received in part
                for bank in &disconnected.partial_banks {
                    in_progress.remove(bank);
                }
                println!("reconnecting, replay from slot {:?}", disconnected.replay_from_slot);
            }
            Err(status) => {
                // Not retryable, the reconnect failed, or the retry budget is spent.
                // The stream has ended: open a new subscription.
                eprintln!("stream ended: {status}");
                break;
            }
        }
    }

    Ok(())
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}

```typescript
import Client, {
  CommitmentLevel,
  type ReconnectEvent,
  type SubscribeRequest,
} from "@triton-one/yellowstone-grpc";

async function main() {
  // The optional fourth argument configures subscribeWithReconnect().
  // The defaults are 10 ms, doubling, 3 retries, and policy "Replay" with slotsBehind 0.
  const client = new Client(
    "https://<your-endpoint>.mainnet.rpcpool.com",
    "<your-token>",
    undefined,
    {
      backoff: { initialIntervalMs: 100, multiplier: 2, maxRetries: 10 },
      policy: "Replay",
      slotsBehind: 0,
    },
  );
  await client.connect();

  const request: SubscribeRequest = {
    accounts: {},
    slots: {},
    transactions: {
      pool: {
        vote: false,
        failed: false,
        accountInclude: ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"],
        accountExclude: [],
        accountRequired: [],
      },
    },
    transactionsStatus: {},
    blocks: {},
    // On one connection, a bank's block meta arrives after all of its content
    blocksMeta: { blocks: {} },
    entry: {},
    blockFooter: {},
    accountsDataSlice: [],
    commitment: CommitmentLevel.PROCESSED,
  };

  // Write a SubscribeRequest to the stream to change the filters, now and after every reconnect
  const stream = await client.subscribeWithReconnect(request);

  // Transactions per bank in progress, and the banks already complete, keyed "slot/bankId"
  const inProgress = new Map<string, number>();
  const complete = new Set<string>();

  stream.on("data", (event: ReconnectEvent) => {
    if (event.type === "Disconnected") {
      // Drop the banks received in part
      for (const bank of event.partialBanks) {
        inProgress.delete(`${bank.slot}/${bank.bankId}`);
      }
      console.log(`reconnecting, replay from slot ${event.replayFromSlot ?? "none (live tip)"}`);
      return;
    }
    const { transaction, blockMeta } = event.update;
    if (transaction) {
      const bank = `${transaction.slot}/${transaction.bankId}`;
      // A replay resends complete banks: skip them
      if (!complete.has(bank)) {
        inProgress.set(bank, (inProgress.get(bank) ?? 0) + 1);
      }
    } else if (blockMeta) {
      const bank = `${blockMeta.slot}/${blockMeta.bankId}`;
      if (!complete.has(bank)) {
        complete.add(bank);
        console.log(`bank ${bank} complete: ${inProgress.get(bank) ?? 0} transactions`);
        inProgress.delete(bank);
      }
      for (const old of complete) {
        if (complete.size <= 4096) break;
        complete.delete(old);
      }
    }
  });

  // Not retryable, the reconnect failed, or the retry budget is spent.
  // The stream has ended: open a new subscription.
  stream.on("error", (error) => console.error("stream ended:", error));
}

main().catch(console.error);
```

`slot`, `bankId`, and `replayFromSlot` are decimal strings. The fourth constructor argument takes `backoff` (`initialIntervalMs`, `multiplier`, `maxRetries`), `policy` (`"Replay"` or `"NoReplay"`), and `slotsBehind`. The client rejects `slotsBehind` with `"NoReplay"`, and rejects `enabled` and `slotRetention`.
{% endtab %}
{% endtabs %}

For a startup snapshot, use an ordinary subscription and resume it yourself: track the last slot you processed, and resubscribe with `from_slot` as described in [Replay from a slot](#replay-from-a-slot).

## Intra-slot updates

Subscribers can listen for "intra-slot" lifecycle events that show what's happening inside a slot, from the first shred received to a fully replayed bank.

These statuses are only delivered when your slots filter sets `interslot_updates: true`. Without it, a slots subscription receives only the processed, confirmed, and finalized statuses.

```protobuf
enum SlotStatus {
  // ... standard commitment statuses ...
  SLOT_FIRST_SHRED_RECEIVED = 3;
  SLOT_COMPLETED            = 4;
  SLOT_CREATED_BANK         = 5;
  SLOT_DEAD                 = 6;
}
```

| Status                      | What it means                                                                                                                                                     |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SLOT_FIRST_SHRED_RECEIVED` | The RPC node has received the first shred of this slot. Not yet replayed. Occurs in the [retransmit stage](https://docs.anza.xyz/validator/tvu#retransmit-stage). |
| `SLOT_CREATED_BANK`         | A bank for this slot has been created on the node. Banks are isolated execution contexts in a fork graph. A slot can have several competing banks.                |
| `SLOT_COMPLETED`            | All shreds for this slot received. Not necessarily replayed yet.                                                                                                  |
| `SLOT_DEAD`                 | The slot was rejected (invalid signature, bad PoH, wrong entry count). It's discarded by the network. Can happen at any point, even after `SLOT_COMPLETED`.       |

### Event order per bank

Every block is produced by a bank, identified by a `bank_id`. A slot usually has one bank, but it can have several competing ones.

Order within one bank on a `processed` stream:

```mermaid
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#F2EDF6','primaryBorderColor':'#7A4BA0','primaryTextColor':'#171717','lineColor':'#956FB3','secondaryColor':'#E4DBEC','tertiaryColor':'#D7C9E3','edgeLabelBackground':'#F2EDF6'},'flowchart':{'nodeSpacing':20,'rankSpacing':35,'curve':'linear'}}}%%
flowchart LR
    C["<b>1. Content</b><br/>any order<br/><br/>CreatedBank, entries,<br/>transactions,<br/>account updates"] --> F["<b>2. Block footer</b><br/><br/>at most one per bank<br/>Alpenglow clusters only"] --> M["<b>3. BlockMeta</b><br/><br/>end of the<br/>bank's block"] --> U["<b>4. Commitment updates</b><br/><br/>Processed → Confirmed<br/>→ Finalized"]
```

1. **Content**, in any order: the `SLOT_CREATED_BANK` status (CreatedBank), entries (`SubscribeUpdateEntry`), transactions (`SubscribeUpdateTransaction`), and account updates (`SubscribeUpdateAccount`), including the sysvars Clock, SlotHashes, SlotHistory, and RecentBlockhashes. Clock and SlotHashes can arrive before `SLOT_CREATED_BANK`.
2. **Block footer** (`SubscribeUpdateBlockFooter`): at most one per bank, on Alpenglow clusters only.
3. **BlockMeta** (`SubscribeUpdateBlockMeta`): marks the end of the bank's block.
4. **Commitment updates** (`SubscribeUpdateSlot`): `SLOT_PROCESSED`, then `SLOT_CONFIRMED`, then `SLOT_FINALIZED`. On Alpenglow clusters, `SLOT_CONFIRMED` and `SLOT_FINALIZED` arrive back to back.

On a `processed` subscription, nothing about a bank's content arrives after its BlockMeta, and no commitment update for that bank arrives before it. Besides the server's own `ping` updates, you receive only the update types your request subscribes to.

## Clients and SDKs

We provide SDKs in Rust and TypeScript, plus example clients in Go and Python. Sample clients for each live in the [yellowstone-grpc/examples](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples) directory; match your client to the current proto version.

{% tabs %}
{% tab title="Rust" %}
The repo's [yellowstone-grpc/examples/rust](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/rust) directory ships a `client` binary that exercises the subscribe and unary methods against any endpoint.

Subscribe to account updates:

```shell
cargo run --bin client -- \
  -e https://<your-endpoint>.mainnet.rpcpool.com \
  --x-token <your-token> \
  subscribe \
  --accounts \
  --accounts-account <Pubkey>
```

Subscribe to slots (with commitment override):

```shell
cargo run --bin client -- \
  -e https://<your-endpoint>.mainnet.rpcpool.com \
  --x-token <your-token> \
  --commitment processed \
  subscribe \
  --slots
```

Subscribe to non-vote, non-failed transactions touching an account:

```shell
cargo run --bin client -- \
  -e https://<your-endpoint>.mainnet.rpcpool.com \
  --x-token <your-token> \
  subscribe \
  --transactions \
  --transactions-vote false \
  --transactions-failed false \
  --transactions-account-include <Pubkey>
```

Subscribe to deshred transactions:

```shell
cargo run --bin client -- \
  -e https://<your-endpoint>.mainnet.rpcpool.com \
  --x-token <your-token> \
  subscribe-deshred \
  --vote false \
  --account-include <Pubkey>
```

Unary calls (`ping`, `get-latest-blockhash`, `get-block-height`, `get-slot`, `is-blockhash-valid`, `get-version`):

```shell
cargo run --bin client -- \
  -e https://<your-endpoint>.mainnet.rpcpool.com \
  --x-token <your-token> \
  get-slot
# response: GetSlotResponse { slot: 196214563 }
```

{% hint style="info" %}
Triton endpoints serve `subscribe-deshred`. Self-hosted, it needs `yellowstone-grpc-geyser` 14.1.1 or later on a validator that emits deshred transaction notifications. Versions before 14.0.0 return `UNIMPLEMENTED`. Versions 14.0.0 and 14.1.0 do not resolve ALT addresses.
{% endhint %}
{% endtab %}

{% tab title="TypeScript / NodeJS" %}
Since `5.0.1`, the TypeScript SDK runs on a Rust NAPI backend. Triton's benchmark measured about 400% more data throughput than the grpc-js version. [More on the NAPI rewrite](https://blog.triton.one/grpc-js-alternative-napi-rust/).

Install:

```bash
npm install --save @triton-one/yellowstone-grpc
```

The examples use top-level `await`. Set up the project as in the [streaming quickstart](/solana/real-time-streaming/quickstart.md) (`type=module`, `moduleResolution` bundler).

Initialize:

```typescript
import Client from "@triton-one/yellowstone-grpc";

const client = new Client(
  "https://<your-endpoint>.mainnet.rpcpool.com:443",
  "<your-token>",
  undefined
);

await client.connect();

const version = await client.getVersion();
console.log(version);
```

Open a subscription stream:

```typescript
import { SubscribeRequest } from "@triton-one/yellowstone-grpc";

const stream = await client.subscribe();

stream.on("data", (data) => {
  console.log("data", data);
});

const request: SubscribeRequest = { /* see Subscribe request */ };

await new Promise<void>((resolve, reject) => {
  stream.write(request, (err) =>
    err == null ? resolve() : reject(err)
  );
});
```

Full example: [yellowstone-grpc/examples/typescript](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/typescript).
{% endtab %}

{% tab title="grpcurl" %}
`grpcurl` is good for testing. You need the corresponding Protobuf `.proto` files (starting with `geyser.proto` and its dependencies) so `grpcurl` can describe the protocol.

```shell
./grpcurl \
  -proto geyser.proto \
  -d '{"slots": { "slots": {} }, "accounts": { "usdc": { "account": ["9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}}' \
  -H "x-token: <your-token>" \
  <your-endpoint>.mainnet.rpcpool.com:443 \
  geyser.Geyser/Subscribe
```

Self-hosters can drop the `x-token` header.
{% endtab %}

{% tab title="Go" %}
Requires Go 1.24. Clone the repo and run the sample client from `examples/golang`:

```shell
git clone https://github.com/rpcpool/yellowstone-grpc
cd yellowstone-grpc/examples/golang
go run ./cmd/grpc-client/ \
  -endpoint https://<your-endpoint>.mainnet.rpcpool.com:443 \
  -x-token <your-token> \
  -slots
```

Updating proto files needs `protoc` plus the Go plugins:

```shell
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.35.1
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
```

Run `make` in `examples/golang` to regenerate. Full source: [yellowstone-grpc/examples/golang](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/golang).

{% hint style="info" %}
The Go example may lag the latest stable proto version. For production-ready code, the Rust client is the reference implementation.
{% endhint %}
{% endtab %}

{% tab title="Python" %}
The repo's [yellowstone-grpc/examples/python](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/python) directory ships `helloworld_geyser.py`. It sends the `x-token` as call metadata over TLS, then calls a unary method:

```python
import grpc
import geyser_pb2, geyser_pb2_grpc

class TritonAuth(grpc.AuthMetadataPlugin):
    def __init__(self, x_token):
        self.x_token = x_token

    def __call__(self, context, callback):
        # metadata is a 1-tuple; the trailing comma is required
        callback((("x-token", self.x_token),), None)

ssl_creds = grpc.ssl_channel_credentials()
call_creds = grpc.metadata_call_credentials(TritonAuth("<your-token>"))
creds = grpc.composite_channel_credentials(ssl_creds, call_creds)

with grpc.secure_channel("<your-endpoint>.mainnet.rpcpool.com:443", creds) as channel:
    stub = geyser_pb2_grpc.GeyserStub(channel)
    print(stub.GetSlot(geyser_pb2.GetSlotRequest()))
```

Install `grpcio`, `grpcio-tools`, and `protobuf`, and generate `geyser_pb2` from the proto files first.
{% endtab %}
{% endtabs %}

{% hint style="info" %}
If you're experiencing errors, you can sanity-check your endpoint with Triton's prebuilt `client-linux-amd64` test binary (x86-64 Linux), no code required. See [Verify your gRPC endpoint](/solana/faq/error-handling/verify-your-grpc-endpoint.md).
{% endhint %}

## Pricing

On shared infrastructure, Dragon's Mouth is billed by the bandwidth you stream. See [pricing](https://triton.one/pricing).

## What's next

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-fire">:fire:</i> <strong>Deshred Transactions gRPC</strong></td><td>Transactions reconstructed from shreds before execution.</td><td><a href="/solana/real-time-streaming/deshred-transactions.md">Deshred Transactions gRPC</a></td></tr><tr><td><i class="fa-rotate-right">:rotate-right:</i> <strong>WebSocket</strong></td><td>Triton's Solana WebSocket API for browsers and frontends, served from Dragon's Mouth gRPC, with <code>transactionSubscribe</code>.</td><td><a href="/solana/real-time-streaming/whirligig-websockets.md">WebSocket</a></td></tr><tr><td><i class="fa-layer-group">:layer-group:</i> <strong>Fumarole Persistent gRPC</strong></td><td>Redundant streaming layer with 4 days of stored data and built-in cursor resume.</td><td><a href="/solana/real-time-streaming/fumarole-persistent-streams.md">Fumarole Persistent gRPC</a></td></tr><tr><td><i class="fa-compass">:compass:</i> <strong>Streaming overview</strong></td><td>Compare Dragon's Mouth, Riptide, Shred Streaming, Deshred, Preconfirmations, WebSocket, and Fumarole.</td><td><a href="/solana/real-time-streaming.md">Real-time streaming</a></td></tr></tbody></table>

***

<i class="fa-life-ring">:life-ring:</i> Contact support by clicking the chat icon in your [customer dashboard](https://customers.triton.one)\ <i class="fa-briefcase">:briefcase:</i> Sales questions? [Contact us](https://triton.one/contact)\ <i class="fa-sparkles">:sparkles:</i> AI agent? Read [llms.txt](https://docs.triton.one/llms.txt)\ <i class="fa-rss">:rss:</i> Follow updates: [Blog](https://blog.triton.one) · [X](https://x.com/triton_one) · [YouTube](https://www.youtube.com/@triton_one_ltd) · [Telegram](https://t.me/tritonone) · [GitHub](https://github.com/rpcpool)
