> For the complete documentation index, see [llms.txt](/llms.txt)

# SuiGrpcClient

Connect to Sui over gRPC, with native service clients and real-time subscriptions



`SuiGrpcClient` talks to the full node gRPC API. It is the recommended default for application code
and SDK integrations: it reads directly from a full node, and it is the only client with real-time
subscriptions.

```typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';

const client = new SuiGrpcClient({
	network: 'mainnet',
	baseUrl: 'https://fullnode.mainnet.sui.io:443',
});
```

For local development:

```typescript
const client = new SuiGrpcClient({
	network: 'localnet',
	baseUrl: 'http://127.0.0.1:9000',
});
```

Reading data, executing transactions, and querying history all work the same way here as on any
client. See [Querying data](/sui/clients/querying) and
[Signing and execution](/sui/transactions/signing-and-execution). The rest of this page covers what
is specific to gRPC.

## gRPC-specific options [#grpc-specific-options]

Top-level gRPC methods are a superset of the shared API, adding fields where the transport exposes
more data than the common shape can carry:

| Option              | Available on                                                                                                     |
| ------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `include.protoJson` | `getTransaction`, `executeTransaction`, `signAndExecuteTransaction`, `waitForTransaction`, `simulateTransaction` |

`protoJson` returns the raw protobuf response alongside the parsed result, which is useful when you
need a field the unified shape does not expose yet:

```typescript
const result = await client.getTransaction({
	digest: 'ABC123...',
	include: {
		effects: true,
		protoJson: true,
	},
});

const transaction = result.Transaction ?? result.FailedTransaction;
console.log(transaction.digest, result.protoJson);
```

## Transport options [#transport-options]

By default, `SuiGrpcClient` uses `GrpcWebFetchTransport` from
[protobuf-ts](https://github.com/timostamm/protobuf-ts), which works in browsers and Node.js through
the Fetch API. The `GrpcWebFetchTransport` class, `GrpcWebOptions` type, and `RpcTransport` type are
re-exported from `@mysten/sui/grpc`, so you can configure a transport without adding
`@protobuf-ts/*` as a direct dependency.

### gRPC-web transport (default) [#grpc-web-transport-default]

The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2. Pass
`GrpcWebFetchTransport` options to customize it:

```typescript
import { SuiGrpcClient, GrpcWebFetchTransport } from '@mysten/sui/grpc';

const transport = new GrpcWebFetchTransport({
	baseUrl: 'https://your-custom-grpc-endpoint.com',
	format: 'binary', // default is 'text' (base64-encoded)
	// Additional transport options like fetchInit
});

const client = new SuiGrpcClient({
	network: 'testnet',
	transport,
});
```

### Native gRPC transport [#native-grpc-transport]

For server-side applications (Node.js, Bun, and others), use the native gRPC transport with
`@protobuf-ts/grpc-transport` and `@grpc/grpc-js`. This speaks HTTP/2 and the native gRPC protocol
rather than the gRPC-web translation layer.

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install @protobuf-ts/grpc-transport @grpc/grpc-js
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add @protobuf-ts/grpc-transport @grpc/grpc-js
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add @protobuf-ts/grpc-transport @grpc/grpc-js
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add @protobuf-ts/grpc-transport @grpc/grpc-js
    ```
  </CodeBlockTab>
</CodeBlockTabs>

```typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { GrpcTransport } from '@protobuf-ts/grpc-transport';
import { ChannelCredentials } from '@grpc/grpc-js';

const transport = new GrpcTransport({
	host: 'fullnode.testnet.sui.io:443',
	channelCredentials: ChannelCredentials.createSsl(),
});

const client = new SuiGrpcClient({
	network: 'testnet',
	transport,
});
```

For local development without TLS, use `ChannelCredentials.createInsecure()` and a plain
`host: '127.0.0.1:9000'`.

## Read masks [#read-masks]

gRPC responses are opt-in. A request names the fields it wants in a `readMask`, and the server
returns only those. Paths are proto field names in `snake_case`, and nested fields are dotted:

```typescript
const { response } = await client.ledgerService.getTransaction({
	digest: 'ABC123...',
	readMask: { paths: ['digest', 'effects.status', 'transaction.sender'] },
});
```

A field you did not ask for comes back unset, which is the most common surprise when moving from a
JSON-RPC mindset: an empty field usually means it was not requested rather than that it has no
value.

The top-level methods build masks for you from their `include` options, adding the paths each option
needs on top of the handful every result carries. That is the main reason to prefer them: field
selection and the mapping back into the unified shape are handled for you.

## Using service clients [#using-service-clients]

`SuiGrpcClient` exposes the generated service clients as properties, so any RPC the node serves is
reachable even where the shared API has no method for it:

| Property                       | Service                                   |
| ------------------------------ | ----------------------------------------- |
| `ledgerService`                | Ledger reads, and the streaming list RPCs |
| `stateService`                 | Live object and balance state             |
| `transactionExecutionService`  | Transaction execution                     |
| `subscriptionService`          | Real-time streams                         |
| `movePackageService`           | Move package metadata                     |
| `nameService`                  | SuiNS lookup and reverse lookup           |
| `signatureVerificationService` | Signature verification                    |
| `forkingService`               | Admin APIs, for `sui-fork` instances only |

The clients are generated with [protobuf-ts](https://github.com/timostamm/protobuf-ts). Each call
takes the request message and an optional `RpcOptions`, where `abort` carries an `AbortSignal`:

```typescript
const controller = new AbortController();

const { response } = await client.nameService.lookupName(
	{ name: 'example.sui' },
	{ abort: controller.signal },
);
```

Request and response shapes come from the proto definitions, so the generated types are the
reference for what each RPC accepts. Filters on the list and subscribe RPCs are one place worth
knowing the shape: a filter is a list of `terms` ORed together, each term a list of `literals` ANDed
together, and `negated: true` inverts a literal.

```typescript
// Transactions that affected an address but were not sent by it
const stream = client.ledgerService.listTransactions({
	filter: {
		terms: [
			{
				literals: [
					{
						negated: false,
						predicate: {
							oneofKind: 'affectedAddress',
							affectedAddress: { address: '0xabc...' },
						},
					},
					{
						negated: true,
						predicate: { oneofKind: 'sender', sender: { address: '0xabc...' } },
					},
				],
			},
		],
	},
	readMask: { paths: ['digest'] },
});
```

An absent filter matches everything; a present filter needs at least one term.

## Generated types and helpers [#generated-types-and-helpers]

`@mysten/sui/grpc` re-exports everything you need to work with the generated API, so nothing has to
depend on `@protobuf-ts/*` or the proto files directly.

`GrpcTypes` is a namespace holding every generated message interface and enum. Use it to type values
you pass around, and to reference enums by name rather than by number:

```typescript
import { GrpcTypes } from '@mysten/sui/grpc';

function describe(status: GrpcTypes.ExecutionStatus) {
	return status.success ? 'succeeded' : status.error?.description;
}

const ordering = GrpcTypes.Ordering.DESCENDING;
```

Two helpers map a raw protobuf response into the same shape the top-level methods return, which is
useful when you drop to a service client for the request but still want the unified result:

```typescript
import { parseGrpcTransactionResponse } from '@mysten/sui/grpc';

const { response } = await client.ledgerService.getTransaction({
	digest: 'ABC123...',
	readMask: { paths: ['digest', 'effects'] },
});

// Same discriminated union that client.getTransaction() returns
const result = parseGrpcTransactionResponse(response.transaction!, {
	include: { effects: true },
});
```

`parseGrpcSimulateTransactionResponse` does the same for `SimulateTransactionResponse`.

| Export                                                    | Use                                                   |
| --------------------------------------------------------- | ----------------------------------------------------- |
| `GrpcTypes`                                               | Generated message interfaces and enums                |
| `parseGrpcTransactionResponse`                            | Raw `ExecutedTransaction` to the unified result shape |
| `parseGrpcSimulateTransactionResponse`                    | Raw simulation response to the unified result shape   |
| `GrpcWebFetchTransport`, `GrpcWebOptions`, `RpcTransport` | Configuring a [transport](#transport-options)         |
| `SuiGrpcClientOptions`, `GrpcTransactionInclude`, …       | Typing your own wrappers around the client            |
| `isSuiGrpcClient`                                         | Type guard for narrowing an unknown client            |

## Streaming responses [#streaming-responses]

The list and subscribe RPCs return server streams rather than a single response, consumed with
`for await` over `stream.responses`. Both use the same frame shape.

A list RPC is a stream of frames, not a single response. Each frame either delivers one item or just
reports progress, and every frame carries a `watermark` whose `cursor` is a safe resume point.
Exactly one frame of a successful stream carries `end`, reporting why the scan stopped.

This matters because a single request does not necessarily reach the end of the range you asked for:
the server bounds how much ledger a filtered scan reads, so a stream can stop early and report
`SCAN_LIMIT`. Reissue from the last watermark cursor until the reason says the scan is genuinely
finished:

```typescript
import { GrpcTypes } from '@mysten/sui/grpc';

let resumeFrom: Uint8Array | undefined;
let reason: GrpcTypes.QueryEndReason | undefined;

// One request can stop before the range is exhausted, so scan until the range bound is reached
do {
	const stream = client.ledgerService.listEvents({
		readMask: { paths: ['event_type', 'transaction_digest', 'event_index'] },
		// Bound the scan. Without an end, this walks the whole ledger to the current tip
		startCheckpoint: 1_000_000n,
		endCheckpoint: 1_000_100n,
		options: {
			after: resumeFrom,
			limit: 100,
			ordering: GrpcTypes.Ordering.ASCENDING,
		},
	});

	for await (const frame of stream.responses) {
		// The latest watermark is always the safe place to resume from
		resumeFrom = frame.watermark?.cursor ?? resumeFrom;
		reason = frame.end?.reason ?? reason;

		if (frame.event) {
			console.log(frame.event.eventType, frame.event.transactionDigest);
		}
	}
} while (reason === GrpcTypes.QueryEndReason.SCAN_LIMIT);
```

`options.after` and `options.before` are ledger-position bounds that mean the same thing in both
directions (ordering only controls the order of items within the interval), and they intersect with
the checkpoint range when both are given.

## Subscriptions [#subscriptions]

`subscriptionService` provides filtered, real-time streams. Each subscription pairs with the list
RPC of the same name: same filter message, same item and watermark shapes, same cursor semantics.

| Method                  | Yields                                          |
| ----------------------- | ----------------------------------------------- |
| `subscribeCheckpoints`  | Checkpoints, and progress-only cursor frames    |
| `subscribeTransactions` | Executed transactions, and progress-only frames |
| `subscribeEvents`       | Emitted events, and progress-only frames        |

```typescript
const stream = client.subscriptionService.subscribeTransactions({
	filter: {
		terms: [
			{
				literals: [
					{
						negated: false,
						predicate: { oneofKind: 'sender', sender: { address: '0xabc...' } },
					},
				],
			},
		],
	},
	readMask: { paths: ['digest', 'effects.status'] },
});

for await (const frame of stream.responses) {
	if (frame.transaction) {
		console.log(frame.transaction.digest, frame.transaction.effects?.status?.success);
	}
}
```

Omit `filter` to receive everything.

### Frames and watermarks [#frames-and-watermarks]

A subscription behaves like an unbounded ascending scan, so its frames work the same way as a
[list RPC's](#streaming-responses), with two differences: checkpoint frames are checkpoint-granular
and carry a `cursor` sequence number instead of a watermark, and the first frame of a filtered
subscription is always progress-only, establishing the start position. Progress also keeps advancing
with bounded staleness while nothing matches, which is what keeps a sparse filter alive. Track the
cursor on every frame, not just the ones with items:

```typescript
let lastCursor: Uint8Array | undefined;

for await (const frame of stream.responses) {
	lastCursor = frame.watermark?.cursor ?? lastCursor;

	if (frame.transaction) {
		await handleTransaction(frame.transaction);
	}
}
```

### Cancelling a subscription [#cancelling-a-subscription]

Subscription streams have no successful end. They run until the client cancels them, or until the
server terminates them with a gRPC status. Pass an `AbortSignal` through `RpcOptions`:

```typescript
const controller = new AbortController();

const stream = client.subscriptionService.subscribeEvents(
	{ readMask: { paths: ['event_type', 'transaction_digest'] } },
	{ abort: controller.signal },
);

// Later, to tear the stream down
controller.abort();
```

### Recovering missed data [#recovering-missed-data]

Subscriptions do not resume: a new subscription starts at the current tip, so anything that happened
while you were disconnected is skipped. Close the gap with the paired list RPC, scanning between the
last cursor you processed and the cursor the new subscription reported in its first frame. Pass them
as `options.after` and `options.before`. The indexed tip the list RPC reads from can trail the
subscription's start position, so repeat the scan until a terminal frame reports `CURSOR_BOUND`
rather than `LEDGER_TIP`.

A durable consumer therefore keeps two pieces of state: the last cursor it processed, and the start
cursor of each new subscription. On reconnect, open the subscription first, buffer its frames,
replay the gap, then drain the buffer.

Checkpoint subscriptions recover differently. `subscribeCheckpoints` reports its position as a
checkpoint sequence number rather than a watermark cursor, and `listCheckpoints` bounds a scan with
`startCheckpoint` and `endCheckpoint` rather than `options.after` and `options.before`. Replay that
gap by listing from the sequence number after the last one you processed, up to the sequence number
the new subscription started at.
