> 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/reading-account-state/account-sync.md).

# Yellowstone Account Sync

Yellowstone Account Sync streams Solana account updates into your process, so reads of tracked accounts in the Triton SDKs resolve from memory, not RPC.

Yellowstone Account Sync (Account Sync for short) adds buffered account reads to the Triton SDKs, which keep the method names you already call:

* **TypeScript / JavaScript:** `@triton-one/triton-sdk`, a `@solana/web3.js` 1.x `Connection` with Account Sync-backed account reads.
* **Rust:** the `triton-sdk` crate, a wrapper around the asynchronous Solana `RpcClient` from `solana-client`.

## Read latency

| Method                | Median latency | p90 latency |
| --------------------- | :------------: | :---------: |
| `@solana/web3.js` RPC |    12.01 ms    |   14.94 ms  |
| Account Sync          |     1.65 ms    |   1.98 ms   |
| **Reduction**         |    **86.3%**   |  **86.8%**  |

Measured with Triton's [JavaScript `getMultipleAccountsInfo` benchmark](https://github.com/rpcpool/triton-js-sdk/blob/main/examples/account-sync/src/compare_get_multiple_accounts_info_latency.ts). It and the [Rust timing example](https://github.com/rpcpool/triton-rust-sdk/blob/main/triton-sdk/examples/compare_multiple_accounts_latency.rs) measure how long a read takes, not stream delivery latency, and a successful read alone doesn't prove the result came from the stream. Measure with your own accounts and network.

## When to use it

Account Sync trades continuous stream bandwidth for lower-latency repeated reads. It pays off when you read the same accounts more often than they change.

**A good fit** if your application:

* reads the same accounts repeatedly, for example by polling `getAccountInfo` or `getMultipleAccountsInfo`;
* needs the latest account state with low read latency; or
* makes enough account reads that request volume or RPC cost matters.

**Use something else** when:

* you read accounts only occasionally, or only after a user action: use standard JSON-RPC. The stream delivers every update to every tracked account whether or not you read it, so for occasional reads its cost can outweigh the savings;
* you need to react to every account change or need intermediate history: Account Sync keeps only the newest value per account. Use [Dragon's Mouth gRPC](/solana/real-time-streaming/dragon-s-mouth-grpc.md), or [Fumarole](/solana/real-time-streaming/fumarole-persistent-streams.md) if you can't miss updates;
* you need minimum latency per update, such as for liquidations or market making: stream directly with [Dragon's Mouth gRPC](/solana/real-time-streaming/dragon-s-mouth-grpc.md). Account Sync adds a fan-out hop and a local buffer; or
* you need RPC methods other than account reads: every other method still goes to JSON-RPC.

## SDKs and transports

| SDK and environment                  | WebSocket     | gRPC |
| ------------------------------------ | ------------- | ---- |
| TypeScript / JavaScript in Node.js   | Yes (default) | Yes  |
| TypeScript / JavaScript in a browser | Yes           | No   |
| Rust with Tokio                      | No            | Yes  |

## How a read works

1. **The account is in the buffer.** The SDK returns the value from memory. No RPC request is made. The buffer holds the newest observed value for every tracked account.
2. **The account is not in the buffer.** The SDK sends a one-time RPC request for it and, by default, adds a temporary stream subscription so later reads hit the buffer. Whichever answers first, the stream or RPC, completes the read.
3. **RPC reports the account doesn't exist.** The read reports it as missing:
   * TypeScript returns `null`.
   * Rust `get_account_with_commitment` and `get_multiple_accounts*` return `None`.
   * Rust `get_account` and `get_account_data` return an `AccountNotFound` error.
   * A single-account context read carries the slot at which RPC observed the absence. A multiple-account context read carries the lowest slot among its results.
   * A timeout, a network failure or an RPC error is never returned as missing: it rejects or returns an error.
4. **Startup and set changes.** When accounts are tracked, the SDK loads their current values over RPC so it doesn't have to wait for the next change. Stream updates that arrive meanwhile are reconciled by slot and write order, and the newest value wins.
5. **The stream disconnects.** The SDK reconnects with exponential backoff and then reloads the tracked accounts. Meanwhile the TypeScript SDK polls every tracked account over RPC (once a second by default), so reads stay fresh. In 0.1.0 the Rust SDK doesn't poll: a value it fetches through RPC during the outage is served from the buffer, unrefreshed, until the stream is back (see the [Rust SDK reference](/solana/reading-account-state/account-sync/rust-sdk-reference.md#stream-lifecycle)).

A buffered value's context slot is the slot of that account's last update, which can be well behind the cluster if the account hasn't changed. Don't use it as a measure of cluster progress.

Each commitment level (`processed`, `confirmed`, `finalized`) has its own stream and buffer.

## Quick start

Account Sync works with your existing Triton endpoint. Nothing new is needed: use the same endpoint URL, ending in `/<your-token>`, for both the JSON-RPC URL and the Account Sync stream URL. The Rust SDK requires the stream URL. The TypeScript SDK uses the RPC URL for the stream when you omit `subscriptionEndpoint`.

```bash
export RPC_URL='https://<your-endpoint>.mainnet.rpcpool.com/<your-token>'
export ACCOUNT_SYNC_URL='https://<your-endpoint>.mainnet.rpcpool.com/<your-token>'
```

{% hint style="warning" %}
Keep these URLs on your backend. They contain your token. Browser apps use the WebSocket transport; see the [TypeScript SDK reference](/solana/reading-account-state/account-sync/typescript-sdk-reference.md#browser-apps).
{% endhint %}

{% tabs %}
{% tab title="TypeScript" %}
Install the SDK. It needs Node.js 18 or newer (for global `fetch`) and is an ES module. The example uses top-level `await`, so save it as `.mts` (or set `"type": "module"` in `package.json`) and run it with `tsx` or another ESM-aware runner.

```bash
npm install @triton-one/triton-sdk
# or: pnpm add @triton-one/triton-sdk
# or: yarn add @triton-one/triton-sdk
```

Save as `quickstart.mts` and run `npx tsx quickstart.mts`:

```ts
import {
  AccountSyncTransports,
  Connection,
  PublicKey,
} from "@triton-one/triton-sdk";

const endpoint = process.env.RPC_URL;
const subscriptionEndpoint = process.env.ACCOUNT_SYNC_URL;

if (!endpoint || !subscriptionEndpoint) {
  throw new Error("Set RPC_URL and ACCOUNT_SYNC_URL");
}

const address = new PublicKey("So11111111111111111111111111111111111111112");

const connection = new Connection(endpoint, {
  accountSync: {
    transport: AccountSyncTransports.GRPC,
    subscriptionEndpoint,
    commitment: "confirmed",
    initialAccounts: [address],
  },
});

try {
  const response = await connection.getAccountInfoAndContext(address);
  console.log("Observed at slot:", response.context.slot);
  console.log("Account:", response.value);
} finally {
  await connection.close();
}
```

Set `transport` with the `AccountSyncTransports` enum. The string literals `"grpc"` and `"ws"` don't type-check in TypeScript.
{% endtab %}

{% tab title="Rust" %}
Add the SDK and Tokio. The crate requires Rust 1.97.1 or newer, the minimum Rust version of its `solana-client` 4.3.0 dependency.

```bash
cargo add triton-sdk
cargo add tokio --features rt,macros
```

Save as `src/main.rs` and run `cargo run`:

```rust
use std::{env, error::Error};

use triton_sdk::{AccountSyncConfig, CommitmentConfig, Pubkey, RpcClient};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn Error>> {
    let address: Pubkey = "So11111111111111111111111111111111111111112".parse()?;

    let client =
        RpcClient::new_with_commitment(env::var("RPC_URL")?, CommitmentConfig::confirmed())
            .with_account_sync(AccountSyncConfig {
                endpoint: env::var("ACCOUNT_SYNC_URL")?,
                pinned_accounts: [address].into(),
                ..Default::default()
            })?;

    let result = client
        .get_account_with_commitment(&address, client.commitment())
        .await;
    let closed = client.close().await;
    let response = result?;
    closed?;

    println!("Observed at slot: {}", response.context.slot);
    println!("Account: {:?}", response.value);
    Ok(())
}
```

`with_account_sync` turns a plain client into one with buffered reads. Account Sync starts on the first supported read, not at construction.
{% endtab %}
{% endtabs %}

Always close the connection or client when you're done. It owns streams, timers and retries.

Runnable examples: [TypeScript](https://github.com/rpcpool/triton-js-sdk/tree/main/examples/account-sync) and [Rust](https://github.com/rpcpool/triton-rust-sdk/tree/main/triton-sdk/examples).

## Pricing

Account Sync usage is billed under your plan. See [Plans and billing](/account-and-billing/plans-and-billing.md) for how billing works and the [pricing page](https://triton.one/pricing) for current rates.

Your usage has two parts:

* **Stream traffic**, billed as bandwidth. Every update to every tracked account streams to your application, whether or not you read it. Its volume is roughly the size of each tracked account multiplied by how often it changes. Reads served from the buffer send no RPC request.
* **RPC requests the SDK makes for you:** loading initial values, cache misses, reloading tracked accounts after the tracked set changes or the stream reconnects, polling during stream outages (TypeScript), reads that need a newer slot than the buffer has, and mint lookups for parsed token accounts.

## 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-js">:js:</i> <strong>TypeScript SDK reference</strong></td><td>Configuration, read semantics, tracked accounts, errors, production guidance and the full API for <code>@triton-one/triton-sdk</code>.</td><td><a href="/solana/reading-account-state/account-sync/typescript-sdk-reference.md">TypeScript SDK reference</a></td></tr><tr><td><i class="fa-rust">:rust:</i> <strong>Rust SDK reference</strong></td><td>Configuration, read semantics, pinned and temporary accounts, errors and the full API for the <code>triton-sdk</code> crate.</td><td><a href="/solana/reading-account-state/account-sync/rust-sdk-reference.md">Rust SDK reference</a></td></tr><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-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></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)
