# Yellowstone gRPC usage (/solana-rpc/grpc/usage)

> Connect, authenticate, subscribe, and reconnect to Corvus Solana streams with the @triton-one/yellowstone-grpc TypeScript client.



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

```bash
export CORVUS_GRPC_ENDPOINT='http://<region>.corvus-labs.io:10101'
```

Your endpoint is in the [dashboard](https://dashboard.corvus-labs.io). Keep the `http://` scheme; that's what makes this client pick insecure channel credentials ([transport security](/platform/access#transport-security)).

## Connect and subscribe to slots

```ts
import Client, { CommitmentLevel } from '@triton-one/yellowstone-grpc';

const endpoint = process.env.CORVUS_GRPC_ENDPOINT!;
const token = process.env.CORVUS_TOKEN || undefined;

const client = new Client(endpoint, token, {
  grpcMaxDecodingMessageSize: 64 * 1024 * 1024,
});

const stream = await client.subscribe();

stream.on('data', (update) => {
  if (update.slot) {
    console.log('slot', update.slot.slot, 'status', update.slot.status);
  }

  if (update.ping) {
    stream.write({ ping: { id: 1 } });
  }
});

stream.on('error', (error) => {
  console.error('Yellowstone stream failed', error.code, error.details);
});

stream.write({
  slots: {
    progress: {
      filterByCommitment: true,
      interslotUpdates: false,
    },
  },
  commitment: CommitmentLevel.PROCESSED,
});
```

* The second constructor argument is your `x-token`. Only dedicated deployments have one; on a shared endpoint pass `undefined`, and the registered egress IP is the credential.
* `progress` is a filter name you picked yourself. It comes back on matching updates, so one name per logical consumer pays off.
* `grpcMaxDecodingMessageSize` is raised above the client default here, because large account and block payloads can go past it. Channel options in client v5 are camelCase; the old grpc-js style keys (`'grpc.max_receive_message_length'`) are silently ignored.
* `error.details` carries the structured reason naming whichever limit or filter got rejected, so log it next to `error.code`; [Errors](/solana-rpc/errors) lists them.

## Subscribe to account updates

```ts
stream.write({
  accounts: {
    clock: {
      account: ['SysvarC1ock11111111111111111111111111111111'],
      owner: [],
      filters: [],
    },
  },
  accountsDataSlice: [{ offset: 0, length: 64 }],
  commitment: CommitmentLevel.PROCESSED,
});
```

`accountsDataSlice` returns only the bytes you named.

## Subscribe to transactions

```ts
stream.write({
  transactions: {
    activity: {
      vote: false,
      failed: false,
      accountInclude: ['<PUBKEY>'],
      accountExclude: [],
      accountRequired: [],
    },
  },
  commitment: CommitmentLevel.PROCESSED,
});
```

If you only need confirmation state rather than the full payload, use `transactionsStatus` instead.

<Callout type="info" title="Each write replaces everything">
  A `SubscribeRequest` is the whole desired state, not a delta. The account subscription above cancels the slot subscription unless you write both in the same message. The one exception is a ping-only write (`{ ping: { id } }`): it carries no filters, so it leaves your active configuration alone.
</Callout>

## Reconnect after a failure

On `error` or `end`, drop the stream and channel, open a new client, and write the complete filter set in one request; nothing is carried over. Then close the gap, one of two ways.

**Replay it.** Track the last slot you processed and pass it as `fromSlot`. The server re-sends everything it retains from that slot onward, in order, then continues live on the same stream:

```ts
stream.write({
  transactions: {
    activity: {
      vote: false,
      failed: false,
      accountInclude: ['<PUBKEY>'],
      accountExclude: [],
      accountRequired: [],
    },
  },
  commitment: CommitmentLevel.PROCESSED,
  fromSlot: String(lastProcessedSlot),
});
```

**Backfill it.** Subscribe without `fromSlot`, compare the first slot you receive against your persisted checkpoint, and reconcile the gap through [JSON-RPC](/solana-rpc/json-rpc) or your own store.

Replay's contract:

* **Processed commitment only.** Any other `commitment` combined with `fromSlot` is rejected: `INVALID_ARGUMENT` (3), `from_slot replay only supports processed commitment`.
* **Everything replays except full blocks.** Combining `fromSlot` with a `blocks` filter is rejected: `INVALID_ARGUMENT` (3), `blocks are not possible to replay`. `blocksMeta` replays fine.
* **Replay is inclusive.** It starts at `fromSlot` itself, so you'll see updates from that slot you already handled; dedupe on your side.
* **The window is bounded.** History is a fixed-size buffer evicted oldest-first, so its depth in slots moves with network traffic. A `fromSlot` older than what's retained is rejected: `INVALID_ARGUMENT` (3), `from_slot is older than the available replay window`.
* **An empty backend replays nothing.** Right after a restart there may be no history at all; the subscribe succeeds and the stream simply starts live. Replay narrows the gap, it doesn't replace checkpointing.
* **Reading too slowly mid-replay ends the stream** with `DATA_LOSS` (15), message `lagged`. Reconnect and ask again from your checkpoint.

Rate-limit the reconnects. Cancelling streams in quick succession ends the connection with an HTTP/2 `GOAWAY` carrying `ENHANCE_YOUR_CALM`, which isn't a gRPC status at all, so a handler matching on `error.code` never sees it. Rewriting the filter configuration too often gets you `RESOURCE_EXHAUSTED` with reason `subscribe_rate_limit`.

## Non-retryable errors

| Status                  | Meaning                                               | Fix                                                               |
| ----------------------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
| `UNAUTHENTICATED` (16)  | Wrong or missing `x-token`, or unregistered egress IP | Correct credentials, then open a new connection                   |
| `PERMISSION_DENIED` (7) | Your resource does not include the requested access   | Check product access on [Discord](https://discord.gg/corvus-labs) |
| `INVALID_ARGUMENT` (3)  | Malformed request or filter                           | Fix the request; retrying it unchanged fails identically          |

One you won't meet here: the PPS gate that closes a connection with `PERMISSION_DENIED` and the detail prefix `PPS limit requirement not met` belongs to the aRPC transport, so a client on port 10101 never hits it. See [aRPC limits](/arpc/limits) if you consume both. [Gateway & streaming errors](/solana-rpc/errors) has every status and reason string.
