@mysten/sui 2.23.2 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +9 -9
- package/dist/bcs/bcs.d.mts +6 -6
- package/dist/client/core.d.mts +1 -0
- package/dist/client/core.d.mts.map +1 -1
- package/dist/client/core.mjs +4 -1
- package/dist/client/core.mjs.map +1 -1
- package/dist/client/mvr.d.mts.map +1 -1
- package/dist/client/mvr.mjs +1 -0
- package/dist/client/mvr.mjs.map +1 -1
- package/dist/client/types.d.mts +7 -0
- package/dist/client/types.d.mts.map +1 -1
- package/dist/cryptography/signature.d.mts +6 -6
- package/dist/graphql/client.d.mts +6 -1
- package/dist/graphql/client.d.mts.map +1 -1
- package/dist/graphql/client.mjs +18 -2
- package/dist/graphql/client.mjs.map +1 -1
- package/dist/graphql/core.d.mts +5 -4
- package/dist/graphql/core.d.mts.map +1 -1
- package/dist/graphql/core.mjs +60 -14
- package/dist/graphql/core.mjs.map +1 -1
- package/dist/graphql/generated/queries.d.mts.map +1 -1
- package/dist/graphql/generated/queries.mjs +8 -1
- package/dist/graphql/generated/queries.mjs.map +1 -1
- package/dist/graphql/generated/tada-env.d.mts +16 -0
- package/dist/grpc/client.d.mts +6 -1
- package/dist/grpc/client.d.mts.map +1 -1
- package/dist/grpc/client.mjs +17 -2
- package/dist/grpc/client.mjs.map +1 -1
- package/dist/grpc/core.d.mts +1 -0
- package/dist/grpc/core.d.mts.map +1 -1
- package/dist/grpc/core.mjs +20 -0
- package/dist/grpc/core.mjs.map +1 -1
- package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
- package/dist/jsonRpc/client.d.mts.map +1 -1
- package/dist/jsonRpc/client.mjs +60 -15
- package/dist/jsonRpc/client.mjs.map +1 -1
- package/dist/jsonRpc/core.d.mts +6 -1
- package/dist/jsonRpc/core.d.mts.map +1 -1
- package/dist/jsonRpc/core.mjs +25 -7
- package/dist/jsonRpc/core.mjs.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/dist/zklogin/bcs.d.mts +14 -14
- package/docs/bcs.md +2 -2
- package/docs/clients/core.md +151 -690
- package/docs/clients/executing.md +113 -0
- package/docs/clients/graphql.md +91 -46
- package/docs/clients/grpc.md +239 -142
- package/docs/clients/index.md +87 -51
- package/docs/clients/querying.md +539 -0
- package/docs/executors.md +28 -13
- package/docs/index.md +1 -1
- package/docs/llms-index.md +18 -17
- package/docs/migrations/sui-2.0/dapp-kit.md +3 -3
- package/docs/migrations/sui-2.0/deepbook-v3.md +2 -2
- package/docs/migrations/sui-2.0/index.md +46 -25
- package/docs/migrations/sui-2.0/json-rpc-migration.md +462 -284
- package/docs/migrations/sui-2.0/kiosk.md +10 -9
- package/docs/migrations/sui-2.0/sdk-maintainers.md +17 -11
- package/docs/migrations/sui-2.0/sui.md +47 -29
- package/docs/migrations/sui-2.0/suins.md +2 -2
- package/docs/migrations/sui-2.0/walrus.md +3 -3
- package/docs/migrations/sui-2.0/zksend.md +9 -8
- package/docs/sdk-building.md +7 -5
- package/docs/transactions/signing-and-execution.md +8 -28
- package/package.json +1 -1
- package/src/client/core.ts +5 -0
- package/src/client/mvr.ts +6 -0
- package/src/client/types.ts +11 -0
- package/src/graphql/client.ts +35 -2
- package/src/graphql/core.ts +57 -10
- package/src/graphql/generated/queries.ts +14 -0
- package/src/graphql/generated/schema.graphql +11 -1
- package/src/graphql/generated/tada-env.ts +20 -0
- package/src/graphql/queries/nameService.graphql +6 -0
- package/src/grpc/client.ts +34 -2
- package/src/grpc/core.ts +35 -0
- package/src/jsonRpc/client.ts +15 -0
- package/src/jsonRpc/core.ts +31 -6
- package/src/version.ts +1 -1
- package/docs/clients/json-rpc.md +0 -239
package/docs/clients/grpc.md
CHANGED
|
@@ -1,46 +1,70 @@
|
|
|
1
1
|
# SuiGrpcClient
|
|
2
2
|
|
|
3
|
-
> Connect to Sui
|
|
3
|
+
> Connect to Sui over gRPC, with native service clients and real-time subscriptions
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[gRPC API docs](https://docs.sui.io/develop/accessing-data/grpc/what-is-grpc).
|
|
9
|
-
|
|
10
|
-
## Creating a gRPC client
|
|
11
|
-
|
|
12
|
-
To get started, create a `SuiGrpcClient` instance by specifying a network and base URL:
|
|
5
|
+
`SuiGrpcClient` talks to the full node gRPC API. It is the recommended default for application code
|
|
6
|
+
and SDK integrations: it reads directly from a full node, and it is the only client with real-time
|
|
7
|
+
subscriptions.
|
|
13
8
|
|
|
14
9
|
```typescript
|
|
15
10
|
|
|
16
|
-
const
|
|
17
|
-
network: '
|
|
18
|
-
baseUrl: 'https://fullnode.
|
|
11
|
+
const client = new SuiGrpcClient({
|
|
12
|
+
network: 'mainnet',
|
|
13
|
+
baseUrl: 'https://fullnode.mainnet.sui.io:443',
|
|
19
14
|
});
|
|
20
15
|
```
|
|
21
16
|
|
|
22
17
|
For local development:
|
|
23
18
|
|
|
24
19
|
```typescript
|
|
25
|
-
const
|
|
20
|
+
const client = new SuiGrpcClient({
|
|
26
21
|
network: 'localnet',
|
|
27
22
|
baseUrl: 'http://127.0.0.1:9000',
|
|
28
23
|
});
|
|
29
24
|
```
|
|
30
25
|
|
|
26
|
+
Reading data, executing transactions, and querying history all work the same way here as on any
|
|
27
|
+
client. See [Querying data](/sui/clients/querying) and
|
|
28
|
+
[Signing and execution](/sui/transactions/signing-and-execution). The rest of this page covers what
|
|
29
|
+
is specific to gRPC.
|
|
30
|
+
|
|
31
|
+
## gRPC-specific options
|
|
32
|
+
|
|
33
|
+
Top-level gRPC methods are a superset of the shared API, adding fields where the transport exposes
|
|
34
|
+
more data than the common shape can carry:
|
|
35
|
+
|
|
36
|
+
| Option | Available on |
|
|
37
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `include.protoJson` | `getTransaction`, `executeTransaction`, `signAndExecuteTransaction`, `waitForTransaction`, `simulateTransaction` |
|
|
39
|
+
|
|
40
|
+
`protoJson` returns the raw protobuf response alongside the parsed result, which is useful when you
|
|
41
|
+
need a field the unified shape does not expose yet:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const result = await client.getTransaction({
|
|
45
|
+
digest: 'ABC123...',
|
|
46
|
+
include: {
|
|
47
|
+
effects: true,
|
|
48
|
+
protoJson: true,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const transaction = result.Transaction ?? result.FailedTransaction;
|
|
53
|
+
console.log(transaction.digest, result.protoJson);
|
|
54
|
+
```
|
|
55
|
+
|
|
31
56
|
## Transport options
|
|
32
57
|
|
|
33
58
|
By default, `SuiGrpcClient` uses `GrpcWebFetchTransport` from
|
|
34
59
|
[protobuf-ts](https://github.com/timostamm/protobuf-ts), which works in browsers and Node.js through
|
|
35
|
-
the Fetch API.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
re-exported from `@mysten/sui/grpc` for convenience.
|
|
60
|
+
the Fetch API. The `GrpcWebFetchTransport` class, `GrpcWebOptions` type, and `RpcTransport` type are
|
|
61
|
+
re-exported from `@mysten/sui/grpc`, so you can configure a transport without adding
|
|
62
|
+
`@protobuf-ts/*` as a direct dependency.
|
|
39
63
|
|
|
40
64
|
### gRPC-web transport (default)
|
|
41
65
|
|
|
42
|
-
The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2.
|
|
43
|
-
|
|
66
|
+
The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2. Pass
|
|
67
|
+
`GrpcWebFetchTransport` options to customize it:
|
|
44
68
|
|
|
45
69
|
```typescript
|
|
46
70
|
|
|
@@ -50,7 +74,7 @@ const transport = new GrpcWebFetchTransport({
|
|
|
50
74
|
// Additional transport options like fetchInit
|
|
51
75
|
});
|
|
52
76
|
|
|
53
|
-
const
|
|
77
|
+
const client = new SuiGrpcClient({
|
|
54
78
|
network: 'testnet',
|
|
55
79
|
transport,
|
|
56
80
|
});
|
|
@@ -58,18 +82,14 @@ const grpcClient = new SuiGrpcClient({
|
|
|
58
82
|
|
|
59
83
|
### Native gRPC transport
|
|
60
84
|
|
|
61
|
-
For server-side applications (Node.js, Bun, and others),
|
|
62
|
-
`@protobuf-ts/grpc-transport` and `@grpc/grpc-js`. This
|
|
85
|
+
For server-side applications (Node.js, Bun, and others), use the native gRPC transport with
|
|
86
|
+
`@protobuf-ts/grpc-transport` and `@grpc/grpc-js`. This speaks HTTP/2 and the native gRPC protocol
|
|
63
87
|
rather than the gRPC-web translation layer.
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```bash
|
|
89
|
+
```npm
|
|
68
90
|
npm install @protobuf-ts/grpc-transport @grpc/grpc-js
|
|
69
91
|
```
|
|
70
92
|
|
|
71
|
-
Then create the client with a `GrpcTransport`:
|
|
72
|
-
|
|
73
93
|
```typescript
|
|
74
94
|
|
|
75
95
|
const transport = new GrpcTransport({
|
|
@@ -77,96 +97,71 @@ const transport = new GrpcTransport({
|
|
|
77
97
|
channelCredentials: ChannelCredentials.createSsl(),
|
|
78
98
|
});
|
|
79
99
|
|
|
80
|
-
const
|
|
100
|
+
const client = new SuiGrpcClient({
|
|
81
101
|
network: 'testnet',
|
|
82
102
|
transport,
|
|
83
103
|
});
|
|
84
104
|
```
|
|
85
105
|
|
|
86
|
-
For local development without TLS
|
|
106
|
+
For local development without TLS, use `ChannelCredentials.createInsecure()` and a plain
|
|
107
|
+
`host: '127.0.0.1:9000'`.
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
## Read masks
|
|
89
110
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
channelCredentials: ChannelCredentials.createInsecure(),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const grpcClient = new SuiGrpcClient({
|
|
96
|
-
network: 'localnet',
|
|
97
|
-
transport,
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Using service clients
|
|
102
|
-
|
|
103
|
-
The `SuiGrpcClient` exposes several service clients for lower-level access to the gRPC API. These
|
|
104
|
-
service clients are generated using [protobuf-ts](https://github.com/timostamm/protobuf-ts), which
|
|
105
|
-
provides type-safe gRPC clients for TypeScript. For more details on how to use gRPC with Sui, see
|
|
106
|
-
the [gRPC overview](https://docs.sui.io/develop/accessing-data/grpc/what-is-grpc).
|
|
107
|
-
|
|
108
|
-
### With the core API
|
|
109
|
-
|
|
110
|
-
The gRPC client implements all the [`core`](./core) API methods:
|
|
111
|
+
gRPC responses are opt-in. A request names the fields it wants in a `readMask`, and the server
|
|
112
|
+
returns only those. Paths are proto field names in `snake_case`, and nested fields are dotted:
|
|
111
113
|
|
|
112
114
|
```typescript
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
baseUrl: 'https://fullnode.testnet.sui.io:443',
|
|
117
|
-
});
|
|
118
|
-
// Get coins owned by an address
|
|
119
|
-
await grpcClient.getCoins({
|
|
120
|
-
owner: '<OWNER_ADDRESS>',
|
|
115
|
+
const { response } = await client.ledgerService.getTransaction({
|
|
116
|
+
digest: 'ABC123...',
|
|
117
|
+
readMask: { paths: ['digest', 'effects.status', 'transaction.sender'] },
|
|
121
118
|
});
|
|
122
119
|
```
|
|
123
120
|
|
|
124
|
-
|
|
121
|
+
A field you did not ask for comes back unset, which is the most common surprise when moving from a
|
|
122
|
+
JSON-RPC mindset: an empty field usually means it was not requested rather than that it has no
|
|
123
|
+
value.
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
The top-level methods build masks for you from their `include` options, adding the paths each option
|
|
126
|
+
needs on top of the handful every result carries. That is the main reason to prefer them: field
|
|
127
|
+
selection and the mapping back into the unified shape are handled for you.
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
const { response } = await grpcClient.transactionExecutionService.executeTransaction({
|
|
130
|
-
transaction: {
|
|
131
|
-
bcs: {
|
|
132
|
-
value: transactionBytes,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
signatures: signatures.map((sig) => ({
|
|
136
|
-
bcs: { value: fromBase64(sig) },
|
|
137
|
-
signature: { oneofKind: undefined },
|
|
138
|
-
})),
|
|
139
|
-
});
|
|
129
|
+
## Using service clients
|
|
140
130
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
131
|
+
`SuiGrpcClient` exposes the generated service clients as properties, so any RPC the node serves is
|
|
132
|
+
reachable even where the shared API has no method for it:
|
|
133
|
+
|
|
134
|
+
| Property | Service |
|
|
135
|
+
| ------------------------------ | ----------------------------------------- |
|
|
136
|
+
| `ledgerService` | Ledger reads, and the streaming list RPCs |
|
|
137
|
+
| `stateService` | Live object and balance state |
|
|
138
|
+
| `transactionExecutionService` | Transaction execution |
|
|
139
|
+
| `subscriptionService` | Real-time streams |
|
|
140
|
+
| `movePackageService` | Move package metadata |
|
|
141
|
+
| `nameService` | SuiNS lookup and reverse lookup |
|
|
142
|
+
| `signatureVerificationService` | Signature verification |
|
|
143
|
+
| `forkingService` | Admin APIs, for `sui-fork` instances only |
|
|
147
144
|
|
|
148
|
-
|
|
145
|
+
The clients are generated with [protobuf-ts](https://github.com/timostamm/protobuf-ts). Each call
|
|
146
|
+
takes the request message and an optional `RpcOptions`, where `abort` carries an `AbortSignal`:
|
|
149
147
|
|
|
150
148
|
```typescript
|
|
151
|
-
|
|
152
|
-
const { response } = await grpcClient.ledgerService.getTransaction({
|
|
153
|
-
digest: '0x123...',
|
|
154
|
-
});
|
|
149
|
+
const controller = new AbortController();
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
const { response } = await client.nameService.lookupName(
|
|
152
|
+
{ name: 'example.sui' },
|
|
153
|
+
{ abort: controller.signal },
|
|
154
|
+
);
|
|
158
155
|
```
|
|
159
156
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
filters (combined, negated, and additional predicates like `affected_address` and `package_write`)
|
|
165
|
-
and checkpoint range bounds that the core API does not expose:
|
|
157
|
+
Request and response shapes come from the proto definitions, so the generated types are the
|
|
158
|
+
reference for what each RPC accepts. Filters on the list and subscribe RPCs are one place worth
|
|
159
|
+
knowing the shape: a filter is a list of `terms` ORed together, each term a list of `literals` ANDed
|
|
160
|
+
together, and `negated: true` inverts a literal.
|
|
166
161
|
|
|
167
162
|
```typescript
|
|
168
|
-
// Transactions that affected an address but were not sent by it
|
|
169
|
-
const stream =
|
|
163
|
+
// Transactions that affected an address but were not sent by it
|
|
164
|
+
const stream = client.ledgerService.listTransactions({
|
|
170
165
|
filter: {
|
|
171
166
|
terms: [
|
|
172
167
|
{
|
|
@@ -188,21 +183,116 @@ const stream = grpcClient.ledgerService.listTransactions({
|
|
|
188
183
|
},
|
|
189
184
|
readMask: { paths: ['digest'] },
|
|
190
185
|
});
|
|
186
|
+
```
|
|
191
187
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
An absent filter matches everything; a present filter needs at least one term.
|
|
189
|
+
|
|
190
|
+
## Generated types and helpers
|
|
191
|
+
|
|
192
|
+
`@mysten/sui/grpc` re-exports everything you need to work with the generated API, so nothing has to
|
|
193
|
+
depend on `@protobuf-ts/*` or the proto files directly.
|
|
194
|
+
|
|
195
|
+
`GrpcTypes` is a namespace holding every generated message interface and enum. Use it to type values
|
|
196
|
+
you pass around, and to reference enums by name rather than by number:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
|
|
200
|
+
function describe(status: GrpcTypes.ExecutionStatus) {
|
|
201
|
+
return status.success ? 'succeeded' : status.error?.description;
|
|
196
202
|
}
|
|
203
|
+
|
|
204
|
+
const ordering = GrpcTypes.Ordering.DESCENDING;
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Two helpers map a raw protobuf response into the same shape the top-level methods return, which is
|
|
208
|
+
useful when you drop to a service client for the request but still want the unified result:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
|
|
212
|
+
const { response } = await client.ledgerService.getTransaction({
|
|
213
|
+
digest: 'ABC123...',
|
|
214
|
+
readMask: { paths: ['digest', 'effects'] },
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Same discriminated union that client.getTransaction() returns
|
|
218
|
+
const result = parseGrpcTransactionResponse(response.transaction!, {
|
|
219
|
+
include: { effects: true },
|
|
220
|
+
});
|
|
197
221
|
```
|
|
198
222
|
|
|
199
|
-
|
|
223
|
+
`parseGrpcSimulateTransactionResponse` does the same for `SimulateTransactionResponse`.
|
|
200
224
|
|
|
201
|
-
|
|
202
|
-
|
|
225
|
+
| Export | Use |
|
|
226
|
+
| --------------------------------------------------------- | ----------------------------------------------------- |
|
|
227
|
+
| `GrpcTypes` | Generated message interfaces and enums |
|
|
228
|
+
| `parseGrpcTransactionResponse` | Raw `ExecutedTransaction` to the unified result shape |
|
|
229
|
+
| `parseGrpcSimulateTransactionResponse` | Raw simulation response to the unified result shape |
|
|
230
|
+
| `GrpcWebFetchTransport`, `GrpcWebOptions`, `RpcTransport` | Configuring a [transport](#transport-options) |
|
|
231
|
+
| `SuiGrpcClientOptions`, `GrpcTransactionInclude`, … | Typing your own wrappers around the client |
|
|
232
|
+
| `isSuiGrpcClient` | Type guard for narrowing an unknown client |
|
|
233
|
+
|
|
234
|
+
## Streaming responses
|
|
235
|
+
|
|
236
|
+
The list and subscribe RPCs return server streams rather than a single response, consumed with
|
|
237
|
+
`for await` over `stream.responses`. Both use the same frame shape.
|
|
238
|
+
|
|
239
|
+
A list RPC is a stream of frames, not a single response. Each frame either delivers one item or just
|
|
240
|
+
reports progress, and every frame carries a `watermark` whose `cursor` is a safe resume point.
|
|
241
|
+
Exactly one frame of a successful stream carries `end`, reporting why the scan stopped.
|
|
242
|
+
|
|
243
|
+
This matters because a single request does not necessarily reach the end of the range you asked for:
|
|
244
|
+
the server bounds how much ledger a filtered scan reads, so a stream can stop early and report
|
|
245
|
+
`SCAN_LIMIT`. Reissue from the last watermark cursor until the reason says the scan is genuinely
|
|
246
|
+
finished:
|
|
203
247
|
|
|
204
248
|
```typescript
|
|
205
|
-
|
|
249
|
+
|
|
250
|
+
let resumeFrom: Uint8Array | undefined;
|
|
251
|
+
let reason: GrpcTypes.QueryEndReason | undefined;
|
|
252
|
+
|
|
253
|
+
// One request can stop before the range is exhausted, so scan until the range bound is reached
|
|
254
|
+
do {
|
|
255
|
+
const stream = client.ledgerService.listEvents({
|
|
256
|
+
readMask: { paths: ['event_type', 'transaction_digest', 'event_index'] },
|
|
257
|
+
// Bound the scan. Without an end, this walks the whole ledger to the current tip
|
|
258
|
+
startCheckpoint: 1_000_000n,
|
|
259
|
+
endCheckpoint: 1_000_100n,
|
|
260
|
+
options: {
|
|
261
|
+
after: resumeFrom,
|
|
262
|
+
limit: 100,
|
|
263
|
+
ordering: GrpcTypes.Ordering.ASCENDING,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
for await (const frame of stream.responses) {
|
|
268
|
+
// The latest watermark is always the safe place to resume from
|
|
269
|
+
resumeFrom = frame.watermark?.cursor ?? resumeFrom;
|
|
270
|
+
reason = frame.end?.reason ?? reason;
|
|
271
|
+
|
|
272
|
+
if (frame.event) {
|
|
273
|
+
console.log(frame.event.eventType, frame.event.transactionDigest);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} while (reason === GrpcTypes.QueryEndReason.SCAN_LIMIT);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`options.after` and `options.before` are ledger-position bounds that mean the same thing in both
|
|
280
|
+
directions (ordering only controls the order of items within the interval), and they intersect with
|
|
281
|
+
the checkpoint range when both are given.
|
|
282
|
+
|
|
283
|
+
## Subscriptions
|
|
284
|
+
|
|
285
|
+
`subscriptionService` provides filtered, real-time streams. Each subscription pairs with the list
|
|
286
|
+
RPC of the same name: same filter message, same item and watermark shapes, same cursor semantics.
|
|
287
|
+
|
|
288
|
+
| Method | Yields |
|
|
289
|
+
| ----------------------- | ----------------------------------------------- |
|
|
290
|
+
| `subscribeCheckpoints` | Checkpoints, and progress-only cursor frames |
|
|
291
|
+
| `subscribeTransactions` | Executed transactions, and progress-only frames |
|
|
292
|
+
| `subscribeEvents` | Emitted events, and progress-only frames |
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const stream = client.subscriptionService.subscribeTransactions({
|
|
206
296
|
filter: {
|
|
207
297
|
terms: [
|
|
208
298
|
{
|
|
@@ -220,59 +310,66 @@ const stream = grpcClient.subscriptionService.subscribeTransactions({
|
|
|
220
310
|
|
|
221
311
|
for await (const frame of stream.responses) {
|
|
222
312
|
if (frame.transaction) {
|
|
223
|
-
console.log(frame.transaction.digest);
|
|
313
|
+
console.log(frame.transaction.digest, frame.transaction.effects?.status?.success);
|
|
224
314
|
}
|
|
225
315
|
}
|
|
226
316
|
```
|
|
227
317
|
|
|
228
|
-
|
|
318
|
+
Omit `filter` to receive everything.
|
|
319
|
+
|
|
320
|
+
### Frames and watermarks
|
|
321
|
+
|
|
322
|
+
A subscription behaves like an unbounded ascending scan, so its frames work the same way as a
|
|
323
|
+
[list RPC's](#streaming-responses), with two differences: checkpoint frames are checkpoint-granular
|
|
324
|
+
and carry a `cursor` sequence number instead of a watermark, and the first frame of a filtered
|
|
325
|
+
subscription is always progress-only, establishing the start position. Progress also keeps advancing
|
|
326
|
+
with bounded staleness while nothing matches, which is what keeps a sparse filter alive. Track the
|
|
327
|
+
cursor on every frame, not just the ones with items:
|
|
229
328
|
|
|
230
329
|
```typescript
|
|
231
|
-
|
|
232
|
-
const { response } = await grpcClient.stateService.listOwnedObjects({
|
|
233
|
-
owner: '0xabc...',
|
|
234
|
-
objectType: '0x2::coin::Coin<0x2::sui::SUI>',
|
|
235
|
-
});
|
|
330
|
+
let lastCursor: Uint8Array | undefined;
|
|
236
331
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
332
|
+
for await (const frame of stream.responses) {
|
|
333
|
+
lastCursor = frame.watermark?.cursor ?? lastCursor;
|
|
334
|
+
|
|
335
|
+
if (frame.transaction) {
|
|
336
|
+
await handleTransaction(frame.transaction);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
241
339
|
```
|
|
242
340
|
|
|
243
|
-
###
|
|
341
|
+
### Cancelling a subscription
|
|
342
|
+
|
|
343
|
+
Subscription streams have no successful end. They run until the client cancels them, or until the
|
|
344
|
+
server terminates them with a gRPC status. Pass an `AbortSignal` through `RpcOptions`:
|
|
244
345
|
|
|
245
346
|
```typescript
|
|
246
|
-
|
|
247
|
-
const { response } = await grpcClient.movePackageService.getFunction({
|
|
248
|
-
packageId: '0x2',
|
|
249
|
-
moduleName: 'coin',
|
|
250
|
-
name: 'value',
|
|
251
|
-
});
|
|
252
|
-
```
|
|
347
|
+
const controller = new AbortController();
|
|
253
348
|
|
|
254
|
-
|
|
349
|
+
const stream = client.subscriptionService.subscribeEvents(
|
|
350
|
+
{ readMask: { paths: ['event_type', 'transaction_digest'] } },
|
|
351
|
+
{ abort: controller.signal },
|
|
352
|
+
);
|
|
255
353
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const { response } = await grpcClient.nameService.reverseLookupName({
|
|
259
|
-
address: '0xabc...',
|
|
260
|
-
});
|
|
354
|
+
// Later, to tear the stream down
|
|
355
|
+
controller.abort();
|
|
261
356
|
```
|
|
262
357
|
|
|
263
|
-
###
|
|
358
|
+
### Recovering missed data
|
|
264
359
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
360
|
+
Subscriptions do not resume: a new subscription starts at the current tip, so anything that happened
|
|
361
|
+
while you were disconnected is skipped. Close the gap with the paired list RPC, scanning between the
|
|
362
|
+
last cursor you processed and the cursor the new subscription reported in its first frame. Pass them
|
|
363
|
+
as `options.after` and `options.before`. The indexed tip the list RPC reads from can trail the
|
|
364
|
+
subscription's start position, so repeat the scan until a terminal frame reports `CURSOR_BOUND`
|
|
365
|
+
rather than `LEDGER_TIP`.
|
|
366
|
+
|
|
367
|
+
A durable consumer therefore keeps two pieces of state: the last cursor it processed, and the start
|
|
368
|
+
cursor of each new subscription. On reconnect, open the subscription first, buffer its frames,
|
|
369
|
+
replay the gap, then drain the buffer.
|
|
370
|
+
|
|
371
|
+
Checkpoint subscriptions recover differently. `subscribeCheckpoints` reports its position as a
|
|
372
|
+
checkpoint sequence number rather than a watermark cursor, and `listCheckpoints` bounds a scan with
|
|
373
|
+
`startCheckpoint` and `endCheckpoint` rather than `options.after` and `options.before`. Replay that
|
|
374
|
+
gap by listing from the sequence number after the last one you processed, up to the sequence number
|
|
375
|
+
the new subscription started at.
|
package/docs/clients/index.md
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
# Sui Clients
|
|
2
2
|
|
|
3
|
-
> Choose
|
|
3
|
+
> Choose between SuiGrpcClient and SuiGraphQLClient and understand their shared API
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
A client is how your code reads from and writes to the Sui network. There are two to choose from,
|
|
6
|
+
and they implement the same API (the same method names, options, and response shapes), so the choice
|
|
7
|
+
is about which transport suits your application, not about which features you get.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
| Client | Choose it for |
|
|
10
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
11
|
+
| [`SuiGrpcClient`](/sui/clients/grpc) | The default. Reads from a full node, and the only client with real-time subscriptions |
|
|
12
|
+
| [`SuiGraphQLClient`](/sui/clients/graphql) | Reads from the indexer, and custom queries written against the GraphQL schema |
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
Both are fully supported primary choices. Pick `SuiGrpcClient` if you have no particular reason to
|
|
15
|
+
prefer one, and `SuiGraphQLClient` when your application wants to query the GraphQL schema directly.
|
|
16
|
+
Because they share an API, moving between them is mostly a matter of changing the constructor.
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| [`SuiGrpcClient`](/sui/clients/grpc) (recommended) | [Full Node gRPC](https://docs.sui.io/references/fullnode-protocol) |
|
|
16
|
-
| [`SuiGraphQLClient`](/sui/clients/graphql) | [GraphQL](https://docs.sui.io/references/sui-graphql) |
|
|
17
|
-
| [`SuiJsonRpcClient`](/sui/clients/json-rpc) (deprecated) | [JSON-RPC (deprecated)](https://docs.sui.io/sui-api-ref) |
|
|
18
|
-
|
|
19
|
-
All clients are compatible with Mysten SDKs like `@mysten/walrus`, `@mysten/seal` and
|
|
20
|
-
`@mysten/suins`.
|
|
21
|
-
|
|
22
|
-
For most application gRPC is a good default. The JSON RPC API has been deprecated and will be
|
|
23
|
-
decommissioned soon. The GraphQL can be used for more advanced query patterns that can not be
|
|
24
|
-
supported directly on full nodes (for example, querying for transactions or events with various
|
|
25
|
-
filters).
|
|
18
|
+
> **Note:** JSON-RPC is deprecated. See the [migration guide](/sui/migrations/sui-2.0/json-rpc-migration) if
|
|
19
|
+
> you maintain existing JSON-RPC code.
|
|
26
20
|
|
|
27
21
|
## Quick start
|
|
28
22
|
|
|
@@ -33,26 +27,76 @@ const client = new SuiGrpcClient({
|
|
|
33
27
|
baseUrl: 'https://fullnode.mainnet.sui.io:443',
|
|
34
28
|
});
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
const { response } = await client.ledgerService.getTransaction({ digest: '0x...' });
|
|
30
|
+
const { balance } = await client.getBalance({ owner: '0x...' });
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
const { object } = await client.getObject({
|
|
33
|
+
objectId: '0x...',
|
|
34
|
+
include: { content: true },
|
|
35
|
+
});
|
|
41
36
|
```
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
The same code against GraphQL differs only in how the client is created:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
|
|
42
|
+
const client = new SuiGraphQLClient({
|
|
43
|
+
network: 'mainnet',
|
|
44
|
+
url: 'https://graphql.mainnet.sui.io/graphql',
|
|
45
|
+
});
|
|
46
|
+
```
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
## Three ways to call a client
|
|
46
49
|
|
|
47
|
-
Each client exposes the
|
|
48
|
-
|
|
50
|
+
Each client exposes the same functionality through three surfaces, aimed at different callers.
|
|
51
|
+
|
|
52
|
+
### Top-level methods
|
|
53
|
+
|
|
54
|
+
The main API for application code. Read data, execute transactions, and query history by calling
|
|
55
|
+
methods directly on the client:
|
|
49
56
|
|
|
50
57
|
```typescript
|
|
58
|
+
const { objects } = await client.listOwnedObjects({
|
|
59
|
+
owner: '0x...',
|
|
60
|
+
include: { display: true },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await client.signAndExecuteTransaction({
|
|
64
|
+
transaction,
|
|
65
|
+
signer,
|
|
66
|
+
include: { effects: true, balanceChanges: true },
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
These are documented in [Querying data](/sui/clients/querying) and
|
|
71
|
+
[Signing and execution](/sui/transactions/signing-and-execution). Where a transport can return more
|
|
72
|
+
than the shared shape carries, it adds options here, such as `include: { protoJson: true }` on gRPC.
|
|
73
|
+
|
|
74
|
+
### `client.core`
|
|
51
75
|
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
The transport-agnostic contract, for libraries that must work with whichever client their caller
|
|
77
|
+
supplies:
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
|
|
81
|
+
return client.core.getObject({
|
|
82
|
+
objectId,
|
|
83
|
+
include: { content: true },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See the [Core API](/sui/clients/core) for the contract and the cross-transport differences to
|
|
89
|
+
account for. Application code does not need this. Call the top-level methods instead.
|
|
90
|
+
|
|
91
|
+
### Native APIs
|
|
92
|
+
|
|
93
|
+
Each client also exposes its transport in full, for the cases the shared API does not cover:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// gRPC: generated service clients, read masks, and streams
|
|
97
|
+
const { response } = await grpcClient.ledgerService.getServiceInfo({});
|
|
98
|
+
|
|
99
|
+
// GraphQL: type-safe custom queries
|
|
56
100
|
const result = await graphqlClient.query({
|
|
57
101
|
query: graphql(`
|
|
58
102
|
query {
|
|
@@ -60,36 +104,28 @@ const result = await graphqlClient.query({
|
|
|
60
104
|
}
|
|
61
105
|
`),
|
|
62
106
|
});
|
|
63
|
-
|
|
64
|
-
// JSON-RPC - call any JSON-RPC method
|
|
65
|
-
const coins = await jsonRpcClient.getCoins({ owner: '0x...' });
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Core API
|
|
69
|
-
|
|
70
|
-
All clients also implement the [Core API](/sui/clients/core) through `client.core`. This provides a
|
|
71
|
-
consistent interface for common operations that works identically across all transports:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
// These methods work the same on any client
|
|
75
|
-
const { object } = await client.core.getObject({ objectId: '0x...' });
|
|
76
|
-
const balance = await client.core.getBalance({ owner: '0x...' });
|
|
77
|
-
await client.core.executeTransaction({ transaction, signatures });
|
|
78
107
|
```
|
|
79
108
|
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
gRPC adds [raw list RPCs](/sui/clients/grpc#using-service-clients) and
|
|
110
|
+
[subscriptions](/sui/clients/grpc#subscriptions); GraphQL adds
|
|
111
|
+
[custom queries](/sui/clients/graphql#writing-queries) against the schema.
|
|
82
112
|
|
|
83
113
|
## Client extensions
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
[@mysten/walrus](https://www.npmjs.com/package/@mysten/walrus) to
|
|
115
|
+
Both clients support extensions through `$extend`, which lets SDKs such as
|
|
116
|
+
[@mysten/walrus](https://www.npmjs.com/package/@mysten/walrus) add functionality to a client your
|
|
117
|
+
application already configured:
|
|
87
118
|
|
|
88
119
|
```typescript
|
|
89
120
|
|
|
90
121
|
const client = new SuiGrpcClient({ network: 'mainnet', baseUrl: '...' }).$extend(walrus());
|
|
91
122
|
|
|
92
|
-
await client.walrus.writeBlob({
|
|
123
|
+
const { blobId } = await client.walrus.writeBlob({
|
|
124
|
+
blob: file,
|
|
125
|
+
deletable: false,
|
|
126
|
+
epochs: 3,
|
|
127
|
+
signer: keypair,
|
|
128
|
+
});
|
|
93
129
|
```
|
|
94
130
|
|
|
95
|
-
See [Building SDKs](/sui/sdk-building) for
|
|
131
|
+
See [Building SDKs](/sui/sdk-building) for creating your own.
|