> 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/quickstart.md).

# Real-time streaming quickstart

Install a client, subscribe, and verify a live Solana stream with Yellowstone gRPC (Dragon's Mouth), WebSocket, Fumarole, or Deshred Transactions.

| Service                                                                                    | What it is                                                                                                                                                                                 |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [**Dragon's Mouth gRPC**](/solana/real-time-streaming/dragon-s-mouth-grpc.md)              | Also called Yellowstone gRPC. Sub-slot real-time updates for accounts, transactions, slots, and blocks over gRPC. Use for backends. Browsers can't use gRPC.                               |
| [**WebSocket**](/solana/real-time-streaming/whirligig-websockets.md)                       | Solana WebSocket API served from Dragon's Mouth gRPC. Use for browsers and frontends.                                                                                                      |
| [**Fumarole Persistent gRPC**](/solana/real-time-streaming/fumarole-persistent-streams.md) | A redundant streaming layer with a server-side cursor, auto-backfill on reconnect (4 days of stored data), and at-least-once delivery, for indexers and pipelines that need complete data. |
| [**Deshred Transactions gRPC**](/solana/real-time-streaming/deshred-transactions.md)       | Pre-execution transactions reconstructed from raw shreds before the node's replay stage.                                                                                                   |

## 0. Prerequisites

{% hint style="warning" %}
The samples below authenticate with a secret token. Never put the secret token in frontend code: browser apps use the endpoint URL alone, authorized by [allowed origins](/security/authentication.md#allowed-origins-frontend).
{% endhint %}

* An active Triton subscription
* Your endpoint URL and secret token from the [customer dashboard](https://customers.triton.one/)
* An environment in TypeScript or Rust. For Go, Python, and other languages, see [yellowstone-grpc/examples](https://github.com/rpcpool/yellowstone-grpc/tree/master/examples).

## 1. Install

{% tabs %}
{% tab title="Dragon’s Mouth" %}
{% tabs %}
{% tab title="TypeScript" %}

```bash
mkdir solana-grpc-stream && cd solana-grpc-stream
npm init --yes && npm pkg set type=module
npm install @triton-one/yellowstone-grpc
npm install --save-dev typescript tsx @types/node
npx tsc --init --module esnext --moduleResolution bundler --types node
```

{% endtab %}

{% tab title="Rust" %}

```bash
cargo new solana-grpc-stream && cd solana-grpc-stream
```

{% 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"] }
anyhow = "1"
```

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

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

```bash
mkdir websocket-stream && cd websocket-stream
npm init --yes && npm pkg set type=module
npm install ws
npm install --save-dev typescript tsx @types/node @types/ws
npx tsc --init --module esnext --moduleResolution bundler --types node
```

{% endtab %}

{% tab title="Rust" %}
Uses Solana's `PubsubClient` from `solana-pubsub-client`. `solana-pubsub-client` 4.3 needs Rust 1.97.1 or later.

```bash
cargo new websocket-stream && cd websocket-stream
```

{% code title="Cargo.toml" %}

```toml
[dependencies]
solana-pubsub-client = "4"
solana-rpc-client-types = "4"
solana-account-decoder-client-types = "4"
solana-pubkey = "4"
solana-commitment-config = "3"
anyhow = "1"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
rustls = { version = "0.23", default-features = false, features = ["ring"] } # TLS crypto provider for wss://
```

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

{% tab title="Fumarole" %}
{% tabs %}
{% tab title="TypeScript" %}
The Fumarole TypeScript client requires Node.js 22 or later.

```bash
mkdir fumarole-stream && cd fumarole-stream
npm init --yes && npm pkg set type=module
npm install @triton-one/yellowstone-fumarole-client
npm install --save-dev typescript tsx @types/node
npx tsc --init --module esnext --moduleResolution bundler --types node
```

{% endtab %}

{% tab title="Rust" %}

```bash
cargo new fumarole-stream && cd fumarole-stream
```

{% code title="Cargo.toml" %}

```toml
[dependencies]
yellowstone-fumarole-client = "0.9"
serde_yaml = "0.9"
anyhow = "1"
yellowstone-grpc-proto = "14"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
```

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

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

```bash
mkdir deshred-stream && cd deshred-stream
npm init --yes && npm pkg set type=module
npm install @triton-one/yellowstone-grpc
npm install --save-dev typescript tsx @types/node
npx tsc --init --module esnext --moduleResolution bundler --types node
```

{% endtab %}

{% tab title="Rust" %}

```bash
cargo new deshred-stream && cd deshred-stream
```

{% code title="Cargo.toml" %}

```toml
[dependencies]
yellowstone-grpc-client = "17"
yellowstone-grpc-proto = "14"
solana-signature = "3"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
tonic = { version = "0.14", features = ["tls-native-roots", "tls-ring"] }
anyhow = "1"
```

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

## 2. Connect and subscribe

{% tabs %}
{% tab title="Dragon’s Mouth" %}
**What we're doing:** connect to the endpoint, stream at the `processed` commitment level, subscribe to slot updates and to all writes on a specific account (the USDC mint), and log each Protobuf-decoded update.

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

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

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

await client.connect();

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

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    futures::stream::StreamExt,
    std::collections::HashMap,
    tonic::transport::channel::ClientTlsConfig,
    yellowstone_grpc_client::GeyserGrpcClient,
    yellowstone_grpc_proto::prelude::{
        CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts,
        SubscribeRequestFilterSlots,
    },
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint = "https://<your-endpoint>.mainnet.rpcpool.com:443";
    let x_token = "<your-token>";

    let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
        .x_token(Some(x_token))?
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .connect()
        .await?;

    let version = client.get_version().await?;
    println!("{:?}", version);
```

{% endtab %}
{% endtabs %}

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

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

const stream = await client.subscribe();

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

const request: SubscribeRequest = {
  slots: { slots: {} },
  accounts: {
    "usdc": {
      account: ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"],
      owner: [],
      filters: [],
    },
  },
  transactions: {},
  transactionsStatus: {},
  entry: {},
  blocks: {},
  blocksMeta: {},
  blockFooter: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
  ping: undefined,
};

await new Promise<void>((resolve, reject) => {
  stream.write(request, (err) => {
    if (err === null || err === undefined) {
      resolve();
    } else {
      reject(err);
    }
  });
}).catch((reason) => {
  console.error(reason);
  throw reason;
});
```

{% endtab %}

{% tab title="Rust" %}

```rust
    let mut accounts = HashMap::new();
    accounts.insert(
        "usdc".to_string(),
        SubscribeRequestFilterAccounts {
            account: vec!["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".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()
    };

    let (_sink, mut stream) = client.subscribe_with_request(Some(request)).await?;

    while let Some(message) = stream.next().await {
        println!("data: {:?}", message?);
    }

    Ok(())
}
```

{% endtab %}

{% tab title="grpcurl" %}

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

Download `geyser.proto` and `solana-storage.proto` from the [proto directory](https://github.com/rpcpool/yellowstone-grpc/tree/master/yellowstone-grpc-proto/proto) into the current folder.
{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="WebSocket" %}
**What we're doing:** open a WebSocket connection to your endpoint and subscribe to changes on the system clock sysvar.

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

```typescript
import WebSocket from "ws";

const ws = new WebSocket(
  "wss://<your-endpoint>.mainnet.rpcpool.com/<your-token>"
);

ws.on("open", () => {
  ws.send(
    JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "accountSubscribe",
      params: [
        "SysvarC1ock11111111111111111111111111111111",
        { commitment: "processed", encoding: "base58" },
      ],
    })
  );
});

ws.on("message", (data) => {
  console.log("Update:", JSON.parse(data.toString()));
});
```

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    futures::StreamExt,
    solana_account_decoder_client_types::UiAccountEncoding,
    solana_commitment_config::CommitmentConfig,
    solana_pubkey::Pubkey,
    solana_pubsub_client::nonblocking::pubsub_client::PubsubClient,
    solana_rpc_client_types::config::RpcAccountInfoConfig,
    std::str::FromStr,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint =
        "wss://<your-endpoint>.mainnet.rpcpool.com/<your-token>";

    let client = PubsubClient::new(endpoint).await?;

    let pubkey = Pubkey::from_str("SysvarC1ock11111111111111111111111111111111")?;
    let config = RpcAccountInfoConfig {
        encoding: Some(UiAccountEncoding::Base58),
        commitment: Some(CommitmentConfig::processed()),
        ..Default::default()
    };

    let (mut stream, _unsubscribe) =
        client.account_subscribe(&pubkey, Some(config)).await?;

    while let Some(account) = stream.next().await {
        println!("Update: {:?}", account);
    }

    Ok(())
}
```

{% endtab %}
{% endtabs %}

The WebSocket endpoint follows the [standard Solana WebSocket API](https://solana.com/docs/rpc/websocket) and adds `transactionSubscribe`, a Triton extension. Full method reference: [WebSocket](/solana/real-time-streaming/whirligig-websockets.md).
{% endtab %}

{% tab title="Fumarole" %}
**What we're doing:** connect to a regional Fumarole endpoint (`ams.rpcpool.com` for Europe, `nyc.rpcpool.com` for the US), use a persistent subscriber, and stream account writes and transactions for the SPL Token program (the TypeScript sample creates the subscriber; the Rust sample streams account writes only and expects the subscriber to exist).

* Don't call Fumarole's gRPC methods directly. Connect with the Rust or TypeScript client.
* Reconnecting with the same subscriber name resumes from where the cursor left off.

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

```typescript
import {
  FumaroleClient,
  CommitmentLevel,
  SubscribeRequest,
} from "@triton-one/yellowstone-fumarole-client";

const TOKEN_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const subscriberName = "helloworld";

const client = await FumaroleClient.connect({
  endpoint: process.env.FUMAROLE_ENDPOINT!, // e.g. https://ams.rpcpool.com
  xToken: process.env.FUMAROLE_X_TOKEN!,
  maxDecodingMessageSizeBytes: 100 * 1024 * 1024,
});
```

{% endtab %}
{% endtabs %}

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

```typescript
const request: SubscribeRequest = {
  commitment: CommitmentLevel.PROCESSED,
  accounts: {
    token: {
      account: [],
      owner: [TOKEN_ADDRESS],
      filters: [],
    },
  },
  transactions: {
    token: {
      accountInclude: [TOKEN_ADDRESS],
      accountExclude: [],
      accountRequired: [],
    },
  },
  slots: { test: {} },
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  blockFooter: {},
  ping: undefined,
  accountsDataSlice: [],
  fromSlot: undefined,
};
```

{% endtab %}
{% endtabs %}

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

```typescript
// Create the persistent subscriber on the first run; later runs resume from its cursor.
if ((await client.getConsumerGroupInfo(subscriberName)) === null) {
  await client.createConsumerGroup(subscriberName);
}

const subscription = await client.subscribe(subscriberName, request);

for await (const event of subscription) {
  if (event.type === "data") {
    console.log("Update:", event.update);
  }
}
```

{% endtab %}

{% tab title="Rust" %}
This sample does not create the persistent subscriber. Create `helloworld` first, for example with the Fume CLI (see [Get started](/solana/real-time-streaming/fumarole-persistent-streams.md#get-started)). `subscribe` fails with `NOT_FOUND` if the subscriber does not exist.

```rust
use {
    futures::StreamExt,
    yellowstone_fumarole_client::{config::FumaroleConfig, FumaroleClient},
    yellowstone_grpc_proto::geyser::{
        CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts,
        subscribe_update::UpdateOneof,
    },
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config: FumaroleConfig = serde_yaml::from_str(&format!(
        "endpoint: {}\nx-token: {}",
        std::env::var("FUMAROLE_ENDPOINT")?,
        std::env::var("FUMAROLE_X_TOKEN")?,
    ))?;
    let mut client = FumaroleClient::connect(config).await?;

    let mut accounts = std::collections::HashMap::new();
    accounts.insert(
        "token".to_string(),
        SubscribeRequestFilterAccounts {
            account: vec![],
            owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".into()],
            filters: vec![],
            ..Default::default()
        },
    );

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

    let subscription = client
        .subscribe("helloworld", request)
        .await?;

    let (_sink, source) = subscription.split();
    let mut source = source.like_dragonsmouth();
    while let Some(update) = source.next().await {
        if let Some(UpdateOneof::Account(acc)) = update?.update_oneof {
            println!("Account update at slot {}", acc.slot);
        }
    }

    Ok(())
}
```

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

{% tab title="Deshred" %}
**What we're doing:** subscribe to the `SubscribeDeshred` gRPC stream (a separate method in the same client library as Dragon's Mouth), filter out vote transactions, and log every transaction signature with its slot.

Deshred delivers transactions reconstructed from shreds ***before*** the node executes them, so you see them earlier than on a Dragon's Mouth `transactions` stream at processed.

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

```typescript
import Client, {
  SubscribeDeshredRequest,
} from "@triton-one/yellowstone-grpc";

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

await client.connect();
const stream = await client.subscribeDeshred();

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

const request: SubscribeDeshredRequest = {
  slots: {},
  deshredTransactions: {
    client: {
      vote: false,
      accountInclude: [],
      accountExclude: [],
      accountRequired: [],
    },
  },
  ping: undefined,
};

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

{% endtab %}

{% tab title="Rust" %}

```rust
use {
    futures::{sink::SinkExt, stream::StreamExt},
    solana_signature::Signature,
    std::collections::HashMap,
    tonic::transport::channel::ClientTlsConfig,
    yellowstone_grpc_client::GeyserGrpcClient,
    yellowstone_grpc_proto::prelude::{
        subscribe_update_deshred::UpdateOneof, SubscribeDeshredRequest,
        SubscribeRequestFilterDeshredTransactions, SubscribeRequestPing,
    },
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint = std::env::var("ENDPOINT")
        .unwrap_or("https://<your-endpoint>".into());
    let x_token = std::env::var("X_TOKEN").ok();

    let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
        .x_token(x_token)?
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .http2_adaptive_window(true)
        .initial_connection_window_size(8 * 1024 * 1024)
        .initial_stream_window_size(4 * 1024 * 1024)
        .connect()
        .await?;

    let request = SubscribeDeshredRequest {
        deshred_transactions: HashMap::from([(
            "deshred".into(),
            SubscribeRequestFilterDeshredTransactions {
                vote: Some(false),
                account_include: vec![],
                account_exclude: vec![],
                account_required: vec![],
                ..Default::default()
            },
        )]),
        ping: None,
        ..Default::default()
    };

    let (mut tx, mut stream) =
        client.subscribe_deshred_with_request(Some(request)).await?;

    while let Some(msg) = stream.next().await {
        match msg?.update_oneof {
            Some(UpdateOneof::DeshredTransaction(update)) => {
                let info = update.transaction.as_ref().unwrap();
                let sig = Signature::try_from(info.signature.as_slice())?;
                println!("slot={} sig={sig} vote={}", update.slot, info.is_vote);
            }
            Some(UpdateOneof::Ping(_)) => {
                tx.send(SubscribeDeshredRequest {
                    ping: Some(SubscribeRequestPing { id: 1 }),
                    ..Default::default()
                })
                .await?;
            }
            Some(UpdateOneof::Pong(_)) => {}
            Some(UpdateOneof::Slot(_)) => {}
            // Other update types, such as update parents, arrive only if you opt in
            Some(_) => {}
            None => break,
        }
    }

    Ok(())
}
```

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

## 3. Verify your stream

{% tabs %}
{% tab title="TypeScript" %}
Save the TypeScript code blocks for your service from step 2, in order, as `main.ts`.

```bash
npx tsx main.ts
```

{% endtab %}

{% tab title="Rust" %}
Save the Rust code blocks for your service from step 2, in order, as `src/main.rs`.

```bash
cargo run
```

{% endtab %}
{% endtabs %}

Then walk through this checklist:

1. **Confirm the stream is open.** Dragon's Mouth sends a slot update within a few slots; other filters send an update when a match occurs. For WebSocket, you'll see a subscription-ID confirmation first: `{ "jsonrpc": "2.0", "result": 42, "id": 1 }`.
2. **Watch matching events flow.** If nothing arrives, your filter is probably too narrow.
3. **Keep long-lived connections alive.** Idle WebSocket connections are closed after about 60 seconds. Send `{"jsonrpc":"2.0","method":"ping"}` (no `id`) periodically. Dragon's Mouth streams can also be closed when idle, since some cloud providers, such as Cloudflare, close idle streams, so send a periodic ping. See [Sending pings](/solana/real-time-streaming/dragon-s-mouth-grpc.md#sending-pings).
4. **Widen the filter to test, then tighten back.** Subscribe to all transactions or all writes for a busy program (e.g. SPL Token) to confirm data is reaching you. Once you see traffic, narrow the filter to what your app actually needs.

### Expected first message

{% tabs %}
{% tab title="Dragon’s Mouth" %}
A Protobuf-decoded `SubscribeUpdate`. Each message has the matching filter labels and one payload, such as `account`, `slot`, `transaction`, `transactionStatus`, `block`, `blockMeta`, `entry`, `blockFooter`, `ping`, or `pong`. With this request, expect `slot` and `account` updates. Slot updates carry a `status` (`SLOT_PROCESSED`, `SLOT_CONFIRMED`, or `SLOT_FINALIZED`), so stream at processed and track confirmed and finalized from these updates in your client. The Rust sample prints the same fields in Debug format. Account-update example:

```
data {
  filters: [ 'usdc' ],
  account: {
    account: {
      pubkey: <Buffer ...>,
      lamports: '...',
      owner: <Buffer ...>,
      executable: false,
      rentEpoch: '18446744073709551615',
      data: <Buffer ...>,
      writeVersion: '...'
    },
    slot: '275123456',
    isStartup: false
  }
}
```

{% endtab %}

{% tab title="WebSocket" %}
First, a subscription-ID confirmation:

```json
{ "jsonrpc": "2.0", "result": 42, "id": 1 }
```

Then JSON-RPC notifications matching the standard Solana WebSocket API. Account-notification example:

```json
{
  "jsonrpc": "2.0",
  "method": "accountNotification",
  "params": {
    "result": {
      "context": { "slot": 275123456 },
      "value": {
        "lamports": 1169280,
        "owner": "Sysvar1111111111111111111111111111111111111",
        "data": ["9AAG2zomYcUVKQfondw13yz193crQ5R5...", "base58"],
        "executable": false,
        "rentEpoch": 18446744073709551615
      }
    },
    "subscription": 42
  }
}
```

{% endtab %}

{% tab title="Fumarole" %}
A Protobuf `SubscribeUpdate` delivered through the persistent subscriber. The first event is whatever's at the subscriber's cursor (latest, in this example). Reconnecting with the same subscriber name resumes from your last committed position. Delivery is at-least-once, so you can receive recent events again, and event order across reconnects is not guaranteed. The TypeScript sample logs each update, for example a transaction update; the Rust sample prints one `Account update at slot <slot>` line per account write:

```
Update: {
  filters: [ 'token' ],
  transaction: {
    transaction: {
      signature: <Buffer ...>,
      isVote: false,
      transaction: {...},
      meta: {...}
    },
    slot: '275123456'
  }
}
```

{% endtab %}

{% tab title="Deshred" %}
The Rust sample prints one line per pre-execution transaction, with slot, signature, and vote flag. The TypeScript sample logs each raw update object. These transactions have not yet been executed by the validator; they may still fail or land on a dead fork. For status, logs, balance changes, or compute units, run a normal Dragon's Mouth `transactions` subscription at processed, on a separate connection:

```
slot=275123456 sig=5j7s4Hk3...QmZ9xT vote=false
slot=275123456 sig=2pK8nLm9...VbY3wQ vote=false
slot=275123457 sig=8aRf6dGj...XwE5pN vote=false
```

{% endtab %}
{% endtabs %}

## 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-tower-broadcast">:tower-broadcast:</i> <strong>Dragon's Mouth gRPC</strong></td><td>Sub-slot real-time updates for accounts, transactions, slots, and blocks via gRPC.</td><td><a href="/solana/real-time-streaming/dragon-s-mouth-grpc.md">Dragon's Mouth gRPC</a></td></tr><tr><td><i class="fa-fire">:fire:</i> <strong>Deshred Transactions gRPC</strong></td><td>Pre-execution transactions reconstructed from raw shreds.</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></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)
