diff --git a/.changeset/olive-pandas-repeat.md b/.changeset/olive-pandas-repeat.md
new file mode 100644
index 000000000..09257c2ba
--- /dev/null
+++ b/.changeset/olive-pandas-repeat.md
@@ -0,0 +1,5 @@
+---
+'@mysten/sui': minor
+---
+
+Add `listTransactions` and `listEvents` core API methods for querying transactions and events with filters, pagination, and ordering. The methods behave identically across the gRPC, GraphQL, and JSON-RPC transports. The regenerated gRPC protos also add the new `SubscribeTransactions` and `SubscribeEvents` subscription APIs.
diff --git a/packages/docs/content/sui/clients/core.mdx b/packages/docs/content/sui/clients/core.mdx
index b3871e748..d277dd277 100644
--- a/packages/docs/content/sui/clients/core.mdx
+++ b/packages/docs/content/sui/clients/core.mdx
@@ -460,6 +460,114 @@ const finalResult = await client.core.waitForTransaction({
});
```
+## Query methods
+
+Query methods return pages of transactions or events. They behave identically on every transport,
+and share a common set of options:
+
+| Option | Type | Description |
+| -------- | ---------------------------------- | ----------------------------------------------------- |
+| `filter` | `TransactionFilter \| EventFilter` | Filter to apply (transactions and events) |
+| `limit` | `number` | Maximum number of items to return (defaults to 50) |
+| `after` | `string \| null` | Return items strictly after this cursor (ascending) |
+| `before` | `string \| null` | Return items strictly before this cursor (descending) |
+| `order` | `'ascending' \| 'descending'` | Order of results (defaults to `ascending`) |
+
+A filter specifies exactly one predicate. MVR names in `function` and `eventType` predicates are
+resolved automatically.
+
+`after` and `before` are exclusive ledger-position bounds, and each page reports the positions of
+its first and last items as `startCursor` and `endCursor`. You can provide at most one bound per
+query, and the bound implies the traversal direction (`after` reads ascending, `before` reads
+descending), so a feed of recent activity can page in both directions from any point:
+
+```typescript
+// Fetch the most recent transactions
+const latest = await client.core.listTransactions({
+ filter: { sender: '0xabc...' },
+ order: 'descending',
+ limit: 10,
+});
+
+// Load older transactions
+const older = await client.core.listTransactions({
+ filter: { sender: '0xabc...' },
+ before: latest.endCursor,
+ limit: 10,
+});
+
+// Poll for transactions executed since
+const newer = await client.core.listTransactions({
+ filter: { sender: '0xabc...' },
+ after: latest.startCursor,
+});
+```
+
+
+ Richer filtering (combined predicates, additional predicates like affected addresses and objects,
+ and checkpoint ranges on transaction and event queries) is available through the [raw gRPC ledger
+ service](/sui/clients/grpc#ledger-service) and [raw GraphQL queries](/sui/clients/graphql).
+
+
+### `listTransactions`
+
+Query transactions matching a filter. Results support the same `include` options as
+[`getTransaction`](#gettransaction).
+
+```typescript
+const result = await client.core.listTransactions({
+ filter: { function: '0x2::coin::transfer' },
+ limit: 10,
+ include: { effects: true },
+});
+
+for (const tx of result.transactions) {
+ const transaction = tx.Transaction ?? tx.FailedTransaction;
+ console.log(transaction.digest, tx.$kind);
+}
+
+// Paginate
+if (result.hasNextPage) {
+ const nextPage = await client.core.listTransactions({
+ filter: { function: '0x2::coin::transfer' },
+ after: result.endCursor,
+ });
+}
+```
+
+Transaction filters support one of the following predicates:
+
+| Predicate | Description |
+| ---------- | --------------------------------------------------------------------------- |
+| `sender` | Transactions sent by an address |
+| `function` | Transactions calling a Move function (`pkg`, `pkg::mod`, or `pkg::mod::fn`) |
+
+### `listEvents`
+
+Query events matching a filter. Each event includes its position in the ledger (`transactionDigest`
+and `eventIndex`, plus `checkpoint` on transports that can provide it; `checkpoint` is `null` on
+JSON-RPC).
+
+```typescript
+const result = await client.core.listEvents({
+ filter: { eventType: '0x2::coin::CoinCreated' },
+ order: 'descending',
+ limit: 10,
+});
+
+for (const event of result.events) {
+ console.log(event.eventType, event.transactionDigest, event.json);
+}
+```
+
+Event filters support one of the following predicates:
+
+| Predicate | Description |
+| ------------ | --------------------------------------------------------------------------------- |
+| `sender` | Events from transactions sent by an address |
+| `emitModule` | Events emitted by a module (`pkg::mod`) |
+| `eventType` | Events with types defined in a module (`pkg::mod`) or a fully qualified type name |
+
## System methods
### `getReferenceGasPrice`
@@ -613,6 +721,9 @@ const options: SuiClientTypes.GetObjectOptions<{ content: true }> = {
| `TransactionResult` | Success or failure result from execution |
| `TransactionEffects` | Detailed effects from transaction execution |
| `Event` | Emitted event from a transaction |
+| `EventEntry` | Queried event with its ledger position |
+| `TransactionFilter` | Filter for transaction queries |
+| `EventFilter` | Filter for event queries |
| `ObjectOwner` | Union of all owner types |
| `ExecutionStatus` | Success/failure status with error details |
| `DynamicFieldName` | Name identifier for dynamic fields |
@@ -643,3 +754,5 @@ const options: SuiClientTypes.GetObjectOptions<{ content: true }> = {
| `SignAndExecuteTransactionOptions` | Options for `signAndExecuteTransaction` |
| `GetTransactionOptions` | Options for `getTransaction` |
| `WaitForTransactionOptions` | Options for `waitForTransaction` |
+| `ListTransactionsOptions` | Options for `listTransactions` |
+| `ListEventsOptions` | Options for `listEvents` |
diff --git a/packages/docs/content/sui/clients/grpc.mdx b/packages/docs/content/sui/clients/grpc.mdx
index 138235f23..de1f72c7c 100644
--- a/packages/docs/content/sui/clients/grpc.mdx
+++ b/packages/docs/content/sui/clients/grpc.mdx
@@ -176,6 +176,74 @@ const { response } = await grpcClient.ledgerService.getTransaction({
const { response: epochInfo } = await grpcClient.ledgerService.getEpoch({});
```
+The ledger service also provides streaming `listCheckpoints`, `listTransactions`, and `listEvents`
+RPCs. For most use cases, prefer the corresponding
+[core API query methods](/sui/clients/core#query-methods), which handle pagination and filter
+construction for you. The raw RPCs additionally support DNF filters (combined, negated, and
+additional predicates like `affected_address` and `package_write`) and checkpoint range bounds that
+the core API does not expose:
+
+```typescript
+// Transactions that affected an address but were not sent by it:
+const stream = grpcClient.ledgerService.listTransactions({
+ filter: {
+ terms: [
+ {
+ literals: [
+ {
+ negated: false,
+ predicate: {
+ oneofKind: 'affectedAddress',
+ affectedAddress: { address: '0xabc...' },
+ },
+ },
+ {
+ negated: true,
+ predicate: { oneofKind: 'sender', sender: { address: '0xabc...' } },
+ },
+ ],
+ },
+ ],
+ },
+ readMask: { paths: ['digest'] },
+});
+
+for await (const frame of stream.responses) {
+ if (frame.transaction) {
+ console.log(frame.transaction.digest);
+ }
+}
+```
+
+### Subscription service
+
+Subscribe to filtered, real-time streams of checkpoints, transactions, or events. Streams begin at
+the current tip of the chain:
+
+```typescript
+const stream = grpcClient.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);
+ }
+}
+```
+
### State service
```typescript
diff --git a/packages/docs/content/sui/migrations/sui-2.0/json-rpc-migration.mdx b/packages/docs/content/sui/migrations/sui-2.0/json-rpc-migration.mdx
index cb4bc96ea..3278b7cb5 100644
--- a/packages/docs/content/sui/migrations/sui-2.0/json-rpc-migration.mdx
+++ b/packages/docs/content/sui/migrations/sui-2.0/json-rpc-migration.mdx
@@ -18,16 +18,17 @@ This guide covers migrating from `SuiJsonRpcClient` to the new client APIs. The
being deprecated in favor of `SuiGrpcClient` and `SuiGraphQLClient`.
- We recommend using `SuiGrpcClient` for most operations and `SuiGraphQLClient` for complex queries
- like filtering transactions and events.
+ Use `SuiGrpcClient` for most operations, including filtering transactions and events with the core
+ query API. `SuiGraphQLClient` remains useful for complex queries that aren't covered by the core
+ API.
## Choosing a client
-| Client | Best For |
-| ------------------ | --------------------------------------------------------------- |
-| `SuiGrpcClient` | Most operations, SDK integrations, real-time data |
-| `SuiGraphQLClient` | Complex queries, filtering transactions/events, historical data |
+| Client | Best For |
+| ------------------ | -------------------------------------------------------------------- |
+| `SuiGrpcClient` | Most operations, SDK integrations, queries, real-time data |
+| `SuiGraphQLClient` | Complex queries not covered by the core API, like historical objects |
## Quick migration to gRPC
@@ -72,6 +73,60 @@ These JSON-RPC methods have direct replacements in the core API:
| `dryRunTransactionBlock` | `simulateTransaction` |
| `getNormalizedMoveFunction` | `getMoveFunction` |
| `getMoveFunctionArgTypes` | `getMoveFunction` |
+| `queryTransactionBlocks` | `listTransactions` |
+| `queryEvents` | `listEvents` |
+
+
+ The query methods (`listTransactions` and `listEvents`) behave identically on every client,
+ including `SuiJsonRpcClient`. See [Core API query methods](/sui/clients/core#query-methods) for
+ details.
+
+
+### Example: Migrating queryTransactionBlocks
+
+```diff
+- const result = await jsonRpcClient.queryTransactionBlocks({
+- filter: { FromAddress: '0xabc...' },
+- options: { showEffects: true },
+- limit: 10,
+- });
+- const digests = result.data.map((tx) => tx.digest);
++ const result = await client.core.listTransactions({
++ filter: { sender: '0xabc...' },
++ include: { effects: true },
++ limit: 10,
++ });
++ const digests = result.transactions.map(
++ (tx) => (tx.Transaction ?? tx.FailedTransaction).digest,
++ );
+```
+
+The core API filters map to the JSON-RPC `TransactionFilter` variants:
+
+| JSON-RPC filter | Core API filter predicate |
+| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
+| `FromAddress` | `sender` |
+| `MoveFunction` | `function` |
+| `ToAddress` / `FromOrToAddress` / `ChangedObject` / `AffectedObject` | No core equivalent. Use raw [gRPC](/sui/clients/grpc#ledger-service) or [GraphQL](/sui/clients/graphql) queries |
+
+### Example: Migrating queryEvents
+
+```diff
+- const result = await jsonRpcClient.queryEvents({
+- query: { MoveEventType: '0x2::coin::CoinCreated' },
+- limit: 10,
+- order: 'descending',
+- });
++ const result = await client.core.listEvents({
++ filter: { eventType: '0x2::coin::CoinCreated' },
++ limit: 10,
++ order: 'descending',
++ });
+```
+
+The JSON-RPC `EventFilter` variants map to `sender`, `emitModule` (replaces `MoveModule` /
+`MoveEventModule`), and `eventType` (replaces `MoveEventType`). Each returned event includes its
+ledger position (`checkpoint`, `transactionDigest`, and `eventIndex`).
### Example: Migrating devInspectTransactionBlock
@@ -109,6 +164,7 @@ These JSON-RPC methods can be replaced by calling gRPC service clients directly:
| JSON-RPC Method | gRPC Service Replacement |
| ----------------------------------- | ---------------------------------------------------------------- |
| `getCheckpoint` | `ledgerService.getCheckpoint` |
+| `getCheckpoints` | `ledgerService.listCheckpoints` |
| `getLatestCheckpointSequenceNumber` | `ledgerService.getServiceInfo` (read `checkpointHeight`) |
| `getCurrentEpoch` | `ledgerService.getEpoch` (omit `epoch` for the current) |
| `getLatestSuiSystemState` | `ledgerService.getEpoch` with `system_state` in the read mask |
@@ -123,10 +179,10 @@ These JSON-RPC methods can be replaced by calling gRPC service clients directly:
| `resolveNameServiceNames` | `nameService.reverseLookupName` |
- `getCheckpoints` and `getEpochs` (paginated listings) have no gRPC equivalent — use the
- `checkpoints` / `epochs` GraphQL queries instead. `getValidatorsApy` also has no replacement:
- there is no canonical definition of validator APY, so it must be computed client-side from
- validator exchange rates (see [Validator APY](#validator-apy) below).
+ `getEpochs` (paginated listing) has no gRPC equivalent. Use the `epochs` GraphQL query instead.
+ `getValidatorsApy` also has no replacement: there is no canonical definition of validator APY, so
+ it must be computed client-side from validator exchange rates (see [Validator APY](#validator-apy)
+ below).
### Example: using gRPC service clients
@@ -184,16 +240,53 @@ const { response: address } = await client.nameService.lookupName({
});
```
+## Methods replaced by gRPC subscriptions
+
+Replace the deprecated JSON-RPC websocket subscriptions with the gRPC `subscriptionService`, which
+provides filtered, real-time streams:
+
+| JSON-RPC Method | gRPC Service Replacement |
+| ---------------------- | ------------------------------------------- |
+| `subscribeTransaction` | `subscriptionService.subscribeTransactions` |
+| `subscribeEvent` | `subscriptionService.subscribeEvents` |
+
+```typescript
+const stream = client.subscriptionService.subscribeEvents({
+ filter: {
+ terms: [
+ {
+ literals: [
+ {
+ negated: false,
+ predicate: {
+ oneofKind: 'eventType',
+ eventType: { eventType: '0x2::coin::CoinCreated' },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ readMask: { paths: ['event_type', 'contents', 'json', 'checkpoint', 'transaction_digest'] },
+});
+
+for await (const frame of stream.responses) {
+ if (frame.event) {
+ console.log(frame.event.eventType, frame.event.json);
+ }
+}
+```
+
+Subscriptions always begin at the current tip of the chain. To recover events you missed between
+subscriptions, replay the gap with `client.core.listEvents` using the same filter.
+
## Methods requiring GraphQL
Some JSON-RPC methods don't have gRPC equivalents and require using `SuiGraphQLClient` instead:
| JSON-RPC Method | GraphQL Alternative |
| --------------------------- | --------------------------------------------------------- |
-| `queryTransactionBlocks` | `transactions` query |
| `multiGetTransactionBlocks` | `multiGetTransactionEffects` query |
-| `queryEvents` | `events` query |
-| `getCheckpoints` | `checkpoints` query |
| `getEpochs` | `epochs` query |
| `getCoinMetadata` | `coinMetadata` query (or gRPC `stateService.getCoinInfo`) |
| `getTotalSupply` | `coinMetadata` query (or gRPC `stateService.getCoinInfo`) |
@@ -215,94 +308,6 @@ const graphqlClient = new SuiGraphQLClient({
});
```
-### Querying transactions
-
-Replace `queryTransactionBlocks` with a GraphQL query:
-
-```typescript
-const result = await graphqlClient.query({
- query: `
- query QueryTransactions($sender: SuiAddress, $first: Int, $after: String) {
- transactions(
- first: $first
- after: $after
- filter: { sentAddress: $sender }
- ) {
- pageInfo {
- hasNextPage
- endCursor
- }
- nodes {
- digest
- effects {
- status
- epoch { epochId }
- }
- }
- }
- }
- `,
- variables: {
- sender: '0xabc...',
- first: 10,
- },
-});
-```
-
-**Available transaction filters:**
-
-- `sentAddress`: Filter by sender address
-- `affectedAddress`: Filter by any address involved in the transaction
-- `affectedObject`: Filter by object ID that was affected
-- `function`: Filter by Move function called (for example, `0x2::coin::transfer`)
-- `kind`: Filter by transaction kind (`SYSTEM` or `PROGRAMMABLE`)
-- `atCheckpoint` / `beforeCheckpoint` / `afterCheckpoint` - Filter by checkpoint
-
-### Querying events
-
-Replace `queryEvents` with a GraphQL query:
-
-```typescript
-const result = await graphqlClient.query({
- query: `
- query QueryEvents($type: String, $first: Int, $after: String) {
- events(
- first: $first
- after: $after
- filter: { type: $type }
- ) {
- pageInfo {
- hasNextPage
- endCursor
- }
- nodes {
- transactionModule {
- package { address }
- name
- }
- sender { address }
- contents {
- type { repr }
- bcs
- }
- }
- }
- }
- `,
- variables: {
- type: '0x2::coin::CoinCreated',
- first: 10,
- },
-});
-```
-
-**Available event filters:**
-
-- `type`: Filter by event type (package, package::module, or full type)
-- `module`: Filter by emitting module
-- `sender`: Filter by transaction sender
-- `atCheckpoint` / `beforeCheckpoint` / `afterCheckpoint`: Filter by checkpoint
-
### Fetching multiple transactions
Replace `multiGetTransactionBlocks` with a GraphQL query:
diff --git a/packages/sui/JSON_RPC_SUNSET_FOLLOW_UPS.md b/packages/sui/JSON_RPC_SUNSET_FOLLOW_UPS.md
new file mode 100644
index 000000000..6cdfdfa83
--- /dev/null
+++ b/packages/sui/JSON_RPC_SUNSET_FOLLOW_UPS.md
@@ -0,0 +1,76 @@
+# Core query API follow-ups gated on the JSON-RPC sunset
+
+The core query APIs (`listTransactions`, `listEvents`, `listCheckpoints`) are intentionally
+restricted to what all three transports (gRPC, GraphQL, JSON-RPC) can support with identical
+behavior, verified by the parity tests in `test/e2e/clients/core/queries.test.ts`. Several
+capabilities were left out of the core API only because JSON-RPC cannot express them. Once JSON-RPC
+is fully deprecated and removed, they can be promoted into the core API.
+
+Until then, advanced filtering is available through the raw gRPC ledger service
+(`client.ledgerService.listTransactions` and friends take full DNF filters), and through raw GraphQL
+queries where the schema supports it. Promoting a capability means extending the core filter types
+in `src/client/types.ts` and the shared resolution helpers in `src/client/query-filters.ts`,
+implementing the gRPC/GraphQL mappings, and extending the parity tests.
+
+## Blocked only by JSON-RPC
+
+- **`affectedAddress` transaction predicate** — no JSON-RPC equivalent: `FromOrToAddress` is
+ rejected server-side ("CURRENTLY NOT SUPPORTED"), and emulating it with `FromAddress` +
+ `ToAddress` queries silently misses sponsored transactions and address-balance deposits, and
+ cannot reproduce exact intra-checkpoint ordering.
+- **`affectedObject` transaction predicate** — the JSON-RPC `AffectedObject` filter is feature-gated
+ server-side and rejected by default (`Feature is not supported`), and the older `ChangedObject`
+ filter has narrower semantics (misses deleted/wrapped/read objects).
+- **`startCheckpoint`/`endCheckpoint` on `listTransactions` and `listEvents`** — JSON-RPC has no
+ checkpoint range support, and its single-checkpoint `Checkpoint` filter cannot be combined with
+ other filters.
+- **A `listCheckpoints` core method** — a summaries-only version (sequence number, digest, epoch,
+ timestamp) was prototyped and dropped from the initial release because listing checkpoints is
+ mostly useful for getting the transactions they contain, which is better served by
+ `listTransactions` with checkpoint ranges once those are promoted. Including per-checkpoint
+ transaction digests has exact parity on gRPC and JSON-RPC, but GraphQL exposes them as a nested
+ connection capped at the server page size, which would silently truncate busy checkpoints. If a
+ core method is added, checkpoint ranges are implementable exactly on every transport (JSON-RPC's
+ sequence-number cursor emulates them).
+- **Combining `after` and `before` bounds, and mixing bounds with either order** — JSON-RPC cursors
+ are direction-relative, so a query currently accepts at most one bound and the bound must match
+ the traversal direction (`after` implies ascending, `before` descending). gRPC treats both as
+ order-independent canonical position bounds and GraphQL accepts both Relay bounds together, so
+ windowed queries (`after` + `before`) and reversed reads within a bound can be enabled by relaxing
+ `resolvePagination` once JSON-RPC is gone.
+- **Combining multiple predicates in one filter (AND)** — JSON-RPC accepts exactly one filter per
+ query. GraphQL supports one instance of each predicate ANDed together, so this can be promoted to
+ that level once JSON-RPC is gone.
+- **Pagination bounds with partial function filters** — JSON-RPC rejects cursors on package- or
+ module-level `MoveFunction` filters ("Cannot supply cursor without supplying module and
+ function"), so `validateTransactionQuery` requires a fully qualified `package::module::function`
+ when `after`/`before` is provided. gRPC and GraphQL paginate partial function filters without
+ restriction.
+- **Package-only prefixes for event predicates** — core `emitModule` requires `package::module` and
+ `eventType` requires at least a module because JSON-RPC's `MoveModule`/`MoveEventModule` filters
+ require a module name. GraphQL and gRPC both support package-only prefixes.
+- **Non-nullable `EventEntry.checkpoint`** — JSON-RPC `queryEvents` responses carry no checkpoint
+ information, so the field is `string | null` and `null` on JSON-RPC.
+
+## Blocked by GraphQL as well
+
+- **OR of filters, negated predicates, and repeated predicate values** — the gRPC DNF filter model
+ (`terms` of ANDed, optionally negated literals). GraphQL currently has no equivalent.
+- **`emitModule`/`eventType`/`eventStreamHead`/`packageWrite` as _transaction_ predicates** — not
+ present on the GraphQL `TransactionFilter` input.
+- **Transaction filters on `listCheckpoints`** — the GraphQL `checkpoints` query only supports
+ checkpoint/epoch bounds, not transaction predicates.
+- **`eventStreamHead` event predicate** — not present on the GraphQL `EventFilter` input.
+
+## Other tracked items
+
+- The gRPC parity e2e tests in `test/e2e/clients/core/queries.test.ts` are excluded via the
+ `EXCLUDE` constant until `SUI_TOOLS_TAG` points at an image that serves the
+ `ListCheckpoints`/`ListTransactions`/`ListEvents` RPCs.
+- `MoveEventType` matching semantics for generic types differ between JSON-RPC (exact struct tag
+ match) and gRPC/GraphQL (a bare `pkg::mod::Name` matches any instantiation). The parity tests only
+ cover non-generic event types; revisit when promoting package-only prefixes.
+- The JSON-RPC `MoveFunction` filter matches aborted transactions, while the GraphQL `function`
+ filter only matches successful ones (gRPC behavior is unverified until the List RPCs are
+ testable). The parity tests avoid this by querying a freshly published package whose calls all
+ succeed; revisit once JSON-RPC is gone.
diff --git a/packages/sui/src/bcs/bcs.ts b/packages/sui/src/bcs/bcs.ts
index 250e982d6..31178a608 100644
--- a/packages/sui/src/bcs/bcs.ts
+++ b/packages/sui/src/bcs/bcs.ts
@@ -228,13 +228,6 @@ export const ProgrammableTransaction = bcs.struct('ProgrammableTransaction', {
commands: bcs.vector(Command),
});
-export const TransactionKind = bcs.enum('TransactionKind', {
- ProgrammableTransaction: ProgrammableTransaction,
- ChangeEpoch: null,
- Genesis: null,
- ConsensusCommitPrologue: null,
-});
-
// Rust: crates/sui-types/src/transaction.rs
export const ValidDuring = bcs.struct('ValidDuring', {
minEpoch: bcs.option(bcs.u64()),
@@ -265,17 +258,6 @@ export const GasData = bcs.struct('GasData', {
budget: bcs.u64(),
});
-export const TransactionDataV1 = bcs.struct('TransactionDataV1', {
- kind: TransactionKind,
- sender: Address,
- gasData: GasData,
- expiration: TransactionExpiration,
-});
-
-export const TransactionData = bcs.enum('TransactionData', {
- V1: TransactionDataV1,
-});
-
export const IntentScope = bcs.enum('IntentScope', {
TransactionData: null,
TransactionEffects: null,
@@ -341,15 +323,6 @@ export const base64String = bcs.byteVector().transform({
output: (val) => toBase64(new Uint8Array(val)),
});
-export const SenderSignedTransaction = bcs.struct('SenderSignedTransaction', {
- intentMessage: IntentMessage(TransactionData),
- txSignatures: bcs.vector(base64String),
-});
-
-export const SenderSignedData = bcs.vector(SenderSignedTransaction, {
- name: 'SenderSignedData',
-});
-
export const PasskeyAuthenticator = bcs.struct('PasskeyAuthenticator', {
authenticatorData: bcs.byteVector(),
clientDataJson: bcs.string(),
diff --git a/packages/sui/src/bcs/index.ts b/packages/sui/src/bcs/index.ts
index 616d7582b..d26c55054 100644
--- a/packages/sui/src/bcs/index.ts
+++ b/packages/sui/src/bcs/index.ts
@@ -30,19 +30,21 @@ import {
ProgrammableMoveCall,
ProgrammableTransaction,
PublicKey,
- SenderSignedData,
- SenderSignedTransaction,
SharedObjectRef,
StructTag,
SuiObjectRef,
- TransactionData,
- TransactionDataV1,
TransactionExpiration,
- TransactionKind,
TypeOrigin,
TypeTag,
UpgradeInfo,
} from './bcs.js';
+import {
+ SenderSignedData,
+ SenderSignedTransaction,
+ TransactionData,
+ TransactionDataV1,
+ TransactionKind,
+} from './transactions.js';
import { TransactionEffects } from './effects.js';
export type { TypeTag } from './types.js';
diff --git a/packages/sui/src/bcs/transactions.ts b/packages/sui/src/bcs/transactions.ts
new file mode 100644
index 000000000..feb3f5a4d
--- /dev/null
+++ b/packages/sui/src/bcs/transactions.ts
@@ -0,0 +1,226 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { bcs } from '@mysten/bcs';
+
+import {
+ Address,
+ base64String,
+ Data,
+ GasData,
+ IntentMessage,
+ ObjectDigest,
+ Owner,
+ ProgrammableTransaction,
+ TransactionExpiration,
+ TypeTag,
+} from './bcs.js';
+
+// Rust: crates/sui-types/src/transaction.rs
+export const ChangeEpoch = bcs.struct('ChangeEpoch', {
+ epoch: bcs.u64(),
+ protocolVersion: bcs.u64(),
+ storageCharge: bcs.u64(),
+ computationCharge: bcs.u64(),
+ storageRebate: bcs.u64(),
+ nonRefundableStorageFee: bcs.u64(),
+ epochStartTimestampMs: bcs.u64(),
+ systemPackages: bcs.vector(
+ bcs.tuple([bcs.u64(), bcs.vector(bcs.byteVector()), bcs.vector(Address)]),
+ ),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const GenesisObject = bcs.enum('GenesisObject', {
+ RawObject: bcs.struct('RawObject', {
+ data: Data,
+ owner: Owner,
+ }),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const GenesisTransaction = bcs.struct('GenesisTransaction', {
+ objects: bcs.vector(GenesisObject),
+});
+
+// Rust: crates/sui-types/src/messages_consensus.rs
+export const ConsensusCommitPrologue = bcs.struct('ConsensusCommitPrologue', {
+ epoch: bcs.u64(),
+ round: bcs.u64(),
+ commitTimestampMs: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/messages_consensus.rs
+export const ConsensusCommitPrologueV2 = bcs.struct('ConsensusCommitPrologueV2', {
+ epoch: bcs.u64(),
+ round: bcs.u64(),
+ commitTimestampMs: bcs.u64(),
+ consensusCommitDigest: ObjectDigest,
+});
+
+// Rust: crates/sui-types/src/messages_consensus.rs
+export const ConsensusDeterminedVersionAssignments = bcs.enum(
+ 'ConsensusDeterminedVersionAssignments',
+ {
+ CancelledTransactions: bcs.vector(
+ bcs.tuple([ObjectDigest, bcs.vector(bcs.tuple([Address, bcs.u64()]))]),
+ ),
+ CancelledTransactionsV2: bcs.vector(
+ bcs.tuple([
+ ObjectDigest,
+ bcs.vector(bcs.tuple([bcs.tuple([Address, bcs.u64()]), bcs.u64()])),
+ ]),
+ ),
+ },
+);
+
+// Rust: crates/sui-types/src/messages_consensus.rs
+export const ConsensusCommitPrologueV3 = bcs.struct('ConsensusCommitPrologueV3', {
+ epoch: bcs.u64(),
+ round: bcs.u64(),
+ subDagIndex: bcs.option(bcs.u64()),
+ commitTimestampMs: bcs.u64(),
+ consensusCommitDigest: ObjectDigest,
+ consensusDeterminedVersionAssignments: ConsensusDeterminedVersionAssignments,
+});
+
+// Rust: crates/sui-types/src/messages_consensus.rs
+export const ConsensusCommitPrologueV4 = bcs.struct('ConsensusCommitPrologueV4', {
+ epoch: bcs.u64(),
+ round: bcs.u64(),
+ subDagIndex: bcs.option(bcs.u64()),
+ commitTimestampMs: bcs.u64(),
+ consensusCommitDigest: ObjectDigest,
+ consensusDeterminedVersionAssignments: ConsensusDeterminedVersionAssignments,
+ additionalStateDigest: ObjectDigest,
+});
+
+// Rust: crates/sui-types/src/authenticator_state.rs
+export const ActiveJwk = bcs.struct('ActiveJwk', {
+ jwkId: bcs.struct('JwkId', {
+ iss: bcs.string(),
+ kid: bcs.string(),
+ }),
+ jwk: bcs.struct('JWK', {
+ kty: bcs.string(),
+ e: bcs.string(),
+ n: bcs.string(),
+ alg: bcs.string(),
+ }),
+ epoch: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const AuthenticatorStateUpdate = bcs.struct('AuthenticatorStateUpdate', {
+ epoch: bcs.u64(),
+ round: bcs.u64(),
+ newActiveJwks: bcs.vector(ActiveJwk),
+ authenticatorObjInitialSharedVersion: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const RandomnessStateUpdate = bcs.struct('RandomnessStateUpdate', {
+ epoch: bcs.u64(),
+ randomnessRound: bcs.u64(),
+ randomBytes: bcs.byteVector(),
+ randomnessObjInitialSharedVersion: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const AuthenticatorStateExpire = bcs.struct('AuthenticatorStateExpire', {
+ minEpoch: bcs.u64(),
+ authenticatorObjInitialSharedVersion: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/execution.rs
+export const ExecutionTimeObservationKey = bcs.enum('ExecutionTimeObservationKey', {
+ MoveEntryPoint: bcs.struct('MoveEntryPoint', {
+ package: Address,
+ module: bcs.string(),
+ function: bcs.string(),
+ typeArguments: bcs.vector(TypeTag),
+ }),
+ TransferObjects: null,
+ SplitCoins: null,
+ MergeCoins: null,
+ Publish: null,
+ MakeMoveVec: null,
+ Upgrade: null,
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const StoredExecutionTimeObservations = bcs.enum('StoredExecutionTimeObservations', {
+ V1: bcs.vector(
+ bcs.tuple([
+ ExecutionTimeObservationKey,
+ bcs.vector(
+ bcs.tuple([
+ // AuthorityName (BLS public key bytes)
+ bcs.byteVector(),
+ bcs.struct('Duration', {
+ secs: bcs.u64(),
+ nanos: bcs.u32(),
+ }),
+ ]),
+ ),
+ ]),
+ ),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const WriteAccumulatorStorageCost = bcs.struct('WriteAccumulatorStorageCost', {
+ storageCost: bcs.u64(),
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const EndOfEpochTransactionKind = bcs.enum('EndOfEpochTransactionKind', {
+ ChangeEpoch: ChangeEpoch,
+ AuthenticatorStateCreate: null,
+ AuthenticatorStateExpire: AuthenticatorStateExpire,
+ RandomnessStateCreate: null,
+ DenyListStateCreate: null,
+ // ChainIdentifier (the genesis checkpoint digest)
+ BridgeStateCreate: ObjectDigest,
+ BridgeCommitteeInit: bcs.u64(),
+ StoreExecutionTimeObservations: StoredExecutionTimeObservations,
+ AccumulatorRootCreate: null,
+ CoinRegistryCreate: null,
+ DisplayRegistryCreate: null,
+ AddressAliasStateCreate: null,
+ WriteAccumulatorStorageCost: WriteAccumulatorStorageCost,
+});
+
+// Rust: crates/sui-types/src/transaction.rs
+export const TransactionKind = bcs.enum('TransactionKind', {
+ ProgrammableTransaction: ProgrammableTransaction,
+ ChangeEpoch: ChangeEpoch,
+ Genesis: GenesisTransaction,
+ ConsensusCommitPrologue: ConsensusCommitPrologue,
+ AuthenticatorStateUpdate: AuthenticatorStateUpdate,
+ EndOfEpochTransaction: bcs.vector(EndOfEpochTransactionKind),
+ RandomnessStateUpdate: RandomnessStateUpdate,
+ ConsensusCommitPrologueV2: ConsensusCommitPrologueV2,
+ ConsensusCommitPrologueV3: ConsensusCommitPrologueV3,
+ ConsensusCommitPrologueV4: ConsensusCommitPrologueV4,
+ ProgrammableSystemTransaction: ProgrammableTransaction,
+});
+
+export const TransactionDataV1 = bcs.struct('TransactionDataV1', {
+ kind: TransactionKind,
+ sender: Address,
+ gasData: GasData,
+ expiration: TransactionExpiration,
+});
+
+export const TransactionData = bcs.enum('TransactionData', {
+ V1: TransactionDataV1,
+});
+
+export const SenderSignedTransaction = bcs.struct('SenderSignedTransaction', {
+ intentMessage: IntentMessage(TransactionData),
+ txSignatures: bcs.vector(base64String),
+});
+
+export const SenderSignedData = bcs.vector(SenderSignedTransaction, {
+ name: 'SenderSignedData',
+});
diff --git a/packages/sui/src/client/core.ts b/packages/sui/src/client/core.ts
index bb1139a19..332f21418 100644
--- a/packages/sui/src/client/core.ts
+++ b/packages/sui/src/client/core.ts
@@ -112,6 +112,14 @@ export abstract class CoreClient extends BaseClient implements SuiClientTypes.Tr
options: SuiClientTypes.ListDynamicFieldsOptions,
): Promise;
+ abstract listTransactions(
+ options: SuiClientTypes.ListTransactionsOptions,
+ ): Promise>;
+
+ abstract listEvents(
+ options: SuiClientTypes.ListEventsOptions,
+ ): Promise;
+
abstract resolveTransactionPlugin(): TransactionPlugin;
abstract verifyZkLoginSignature(
diff --git a/packages/sui/src/client/query-filters.ts b/packages/sui/src/client/query-filters.ts
new file mode 100644
index 000000000..1d0a85400
--- /dev/null
+++ b/packages/sui/src/client/query-filters.ts
@@ -0,0 +1,191 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { normalizeSuiAddress } from '../utils/sui-types.js';
+import type { SuiClientTypes } from './types.js';
+
+export interface ResolvedPagination {
+ descending: boolean;
+ after: string | undefined;
+ before: string | undefined;
+ limit: number;
+}
+
+/**
+ * Default page size applied when `limit` is omitted, so every transport
+ * requests the same page size instead of falling back to differing server
+ * defaults.
+ */
+const DEFAULT_PAGE_SIZE = 50;
+
+/**
+ * Validates pagination bounds and resolves the traversal direction.
+ *
+ * `after` and `before` are exclusive ledger-position bounds. Until every
+ * transport can combine bounds freely, a query accepts at most one bound, and
+ * the bound must match the traversal direction (`after` implies ascending,
+ * `before` implies descending).
+ */
+export function resolvePagination(options: {
+ after?: string | null;
+ before?: string | null;
+ order?: 'ascending' | 'descending';
+ limit?: number;
+}): ResolvedPagination {
+ if (options.after != null && options.before != null) {
+ throw new Error('Only one of `after` or `before` may be provided');
+ }
+
+ const descending = options.order ? options.order === 'descending' : options.before != null;
+
+ if (options.after != null && descending) {
+ throw new Error('`after` can not be combined with descending queries');
+ }
+
+ if (options.before != null && !descending) {
+ throw new Error('`before` can not be combined with ascending queries');
+ }
+
+ return {
+ descending,
+ after: options.after ?? undefined,
+ before: options.before ?? undefined,
+ limit: options.limit ?? DEFAULT_PAGE_SIZE,
+ };
+}
+
+export type ResolvedTransactionFilter =
+ | { $kind: 'sender'; sender: string }
+ | { $kind: 'function'; package: string; module?: string; function?: string };
+
+/**
+ * Validates that a transaction query's filter supports pagination bounds.
+ *
+ * Function filters can only be paginated when fully qualified, since JSON-RPC
+ * rejects cursors on package- or module-level `MoveFunction` filters.
+ */
+export function validateTransactionQuery(
+ filter: ResolvedTransactionFilter | undefined,
+ pagination: ResolvedPagination,
+): void {
+ if (
+ filter?.$kind === 'function' &&
+ (pagination.after != null || pagination.before != null) &&
+ (!filter.module || !filter.function)
+ ) {
+ throw new Error(
+ 'Paginating transactions filtered by function requires a fully qualified `package::module::function`',
+ );
+ }
+}
+
+export type ResolvedEventFilter =
+ | { $kind: 'sender'; sender: string }
+ | { $kind: 'emitModule'; package: string; module: string }
+ | { $kind: 'eventTypeModule'; package: string; module: string }
+ | { $kind: 'eventType'; eventType: string };
+
+/**
+ * Validates a transaction filter and resolves any MVR names it contains.
+ *
+ * Every transport resolves filters through this function so that validation
+ * errors and MVR handling are identical across clients.
+ */
+export async function resolveTransactionFilter(
+ mvr: SuiClientTypes.MvrMethods,
+ filter: SuiClientTypes.TransactionFilter,
+): Promise {
+ assertSinglePredicate(filter, ['sender', 'function'], 'transaction');
+
+ if (filter.sender != null) {
+ return { $kind: 'sender', sender: normalizeSuiAddress(filter.sender) };
+ }
+
+ const [pkg, module, fn, ...rest] = filter.function!.split('::');
+
+ if (!pkg || rest.length > 0 || module === '' || fn === '') {
+ throw new Error(
+ `Invalid function filter "${filter.function}": expected "package", "package::module", or "package::module::function"`,
+ );
+ }
+
+ return {
+ $kind: 'function',
+ package: normalizeSuiAddress((await mvr.resolvePackage({ package: pkg })).package),
+ module,
+ function: fn,
+ };
+}
+
+/**
+ * Validates an event filter and resolves any MVR names it contains.
+ *
+ * `emitModule` must name a module (`package::module`), and `eventType` must be
+ * at least module-qualified (`package::module` or a full type). Every transport
+ * resolves filters through this function so that validation errors and MVR
+ * handling are identical across clients.
+ */
+export async function resolveEventFilter(
+ mvr: SuiClientTypes.MvrMethods,
+ filter: SuiClientTypes.EventFilter,
+): Promise {
+ assertSinglePredicate(filter, ['sender', 'emitModule', 'eventType'], 'event');
+
+ if (filter.sender != null) {
+ return { $kind: 'sender', sender: normalizeSuiAddress(filter.sender) };
+ }
+
+ if (filter.emitModule != null) {
+ const [pkg, module, ...rest] = filter.emitModule.split('::');
+
+ if (!pkg || !module || rest.length > 0) {
+ throw new Error(
+ `Invalid emitModule filter "${filter.emitModule}": expected "package::module"`,
+ );
+ }
+
+ return {
+ $kind: 'emitModule',
+ package: normalizeSuiAddress((await mvr.resolvePackage({ package: pkg })).package),
+ module,
+ };
+ }
+
+ const eventType = filter.eventType!;
+ const segments = eventType.split('::');
+
+ if (segments.length === 2 && segments.every((segment) => segment !== '')) {
+ return {
+ $kind: 'eventTypeModule',
+ package: normalizeSuiAddress((await mvr.resolvePackage({ package: segments[0] })).package),
+ module: segments[1],
+ };
+ }
+
+ if (segments.length < 3 || segments.some((segment) => segment === '')) {
+ throw new Error(
+ `Invalid eventType filter "${eventType}": expected "package::module" or a fully qualified type name`,
+ );
+ }
+
+ return {
+ $kind: 'eventType',
+ eventType: (await mvr.resolveType({ type: eventType })).type,
+ };
+}
+
+function assertSinglePredicate(
+ filter: Record,
+ predicates: string[],
+ kind: string,
+): void {
+ const present = predicates.filter((predicate) => filter[predicate] != null);
+
+ if (present.length !== 1) {
+ throw new Error(
+ `A ${kind} filter must specify exactly one of ${predicates.join(', ')}${
+ present.length ? ` (got ${present.join(', ')})` : ''
+ }`,
+ );
+ }
+}
diff --git a/packages/sui/src/client/types.ts b/packages/sui/src/client/types.ts
index f9635bc31..a00780e84 100644
--- a/packages/sui/src/client/types.ts
+++ b/packages/sui/src/client/types.ts
@@ -461,6 +461,150 @@ export namespace SuiClientTypes {
checksEnabled?: boolean;
}
+ /** Query methods */
+ export interface TransportMethods {
+ listTransactions?: (
+ options: ListTransactionsOptions,
+ ) => Promise>;
+ listEvents?: (options: ListEventsOptions) => Promise;
+ }
+
+ /**
+ * A filter matching transactions. Exactly one predicate must be specified.
+ *
+ * All predicates are supported identically by every transport.
+ */
+ export type TransactionFilter =
+ | {
+ /** Match transactions sent by this address. */
+ sender: string;
+ function?: never;
+ }
+ | {
+ /**
+ * Match transactions that called a Move function, specified as a
+ * `::`-delimited Move path: `package`, `package::module`, or
+ * `package::module::function`.
+ */
+ function: string;
+ sender?: never;
+ };
+
+ /**
+ * A filter matching events. Exactly one predicate must be specified.
+ *
+ * All predicates are supported identically by every transport.
+ */
+ export type EventFilter =
+ | {
+ /** Match events emitted by transactions sent by this address. */
+ sender: string;
+ emitModule?: never;
+ eventType?: never;
+ }
+ | {
+ /** Match events emitted by a module, specified as `package::module`. */
+ emitModule: string;
+ sender?: never;
+ eventType?: never;
+ }
+ | {
+ /**
+ * Match events by type: either every event whose type is defined in a
+ * module (`package::module`), or events with a fully qualified type
+ * name (`package::module::Name` or `package::module::Name`).
+ */
+ eventType: string;
+ sender?: never;
+ emitModule?: never;
+ };
+
+ export interface ListTransactionsOptions<
+ Include extends TransactionInclude = {},
+ > extends CoreClientMethodOptions {
+ filter?: TransactionFilter;
+ limit?: number;
+ /**
+ * Return items strictly after this cursor in ledger order (usually the `endCursor` of
+ * an ascending page, or the `startCursor` of a descending page to poll for new items).
+ * Implies `order: 'ascending'`, and cannot be combined with `before`.
+ */
+ after?: string | null;
+ /**
+ * Return items strictly before this cursor in ledger order (usually the `endCursor` of
+ * a descending page). Implies `order: 'descending'`, and cannot be combined with
+ * `after`.
+ */
+ before?: string | null;
+ /** Order of returned results. Defaults to `ascending` (oldest first). */
+ order?: 'ascending' | 'descending';
+ include?: Include & TransactionInclude;
+ }
+
+ export interface ListTransactionsResponse {
+ transactions: TransactionResult[];
+ hasNextPage: boolean;
+ /**
+ * Cursor at the first returned item. After a descending read of the most recent
+ * items, pass as `after` to poll for items added since.
+ */
+ startCursor: string | null;
+ /**
+ * Cursor at the last returned item. Continue the query by passing it as `after`
+ * (ascending) or `before` (descending).
+ */
+ endCursor: string | null;
+ }
+
+ export interface ListEventsOptions extends CoreClientMethodOptions {
+ filter?: EventFilter;
+ limit?: number;
+ /**
+ * Return items strictly after this cursor in ledger order (usually the `endCursor` of
+ * an ascending page, or the `startCursor` of a descending page to poll for new items).
+ * Implies `order: 'ascending'`, and cannot be combined with `before`.
+ */
+ after?: string | null;
+ /**
+ * Return items strictly before this cursor in ledger order (usually the `endCursor` of
+ * a descending page). Implies `order: 'descending'`, and cannot be combined with
+ * `after`.
+ */
+ before?: string | null;
+ /** Order of returned results. Defaults to `ascending` (oldest first). */
+ order?: 'ascending' | 'descending';
+ }
+
+ /** An event returned from a query, along with its position in the ledger. */
+ export interface EventEntry extends Event {
+ /**
+ * The sequence number of the checkpoint containing the emitting transaction.
+ *
+ * `null` on the JSON-RPC transport, which does not expose checkpoint
+ * information for queried events.
+ */
+ checkpoint: string | null;
+ /** The digest of the transaction that emitted this event. */
+ transactionDigest: string;
+ /** The index of this event within its transaction's event list. */
+ eventIndex: number;
+ }
+
+ export interface ListEventsResponse {
+ events: EventEntry[];
+ hasNextPage: boolean;
+ /**
+ * Cursor at the first returned item. After a descending read of the most recent
+ * items, pass as `after` to poll for items added since.
+ */
+ startCursor: string | null;
+ /**
+ * Cursor at the last returned item. Continue the query by passing it as `after`
+ * (ascending) or `before` (descending).
+ */
+ endCursor: string | null;
+ }
+
export interface GetReferenceGasPriceOptions extends CoreClientMethodOptions {}
export interface TransportMethods {
diff --git a/packages/sui/src/graphql/client.ts b/packages/sui/src/graphql/client.ts
index cb90a2f4a..fed517a78 100644
--- a/packages/sui/src/graphql/client.ts
+++ b/packages/sui/src/graphql/client.ts
@@ -324,6 +324,16 @@ export class SuiGraphQLClient =
return this.core.getDynamicField(input);
}
+ listTransactions(
+ input: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ return this.core.listTransactions(input);
+ }
+
+ listEvents(input: SuiClientTypes.ListEventsOptions): Promise {
+ return this.core.listEvents(input);
+ }
+
getMoveFunction(
input: SuiClientTypes.GetMoveFunctionOptions,
): Promise {
diff --git a/packages/sui/src/graphql/core.ts b/packages/sui/src/graphql/core.ts
index 34745decc..2d467babc 100644
--- a/packages/sui/src/graphql/core.ts
+++ b/packages/sui/src/graphql/core.ts
@@ -28,6 +28,8 @@ import {
GetOwnedObjectsDocument,
GetReferenceGasPriceDocument,
GetTransactionBlockDocument,
+ ListEventsDocument,
+ ListTransactionsDocument,
MultiGetObjectsDocument,
ResolveTransactionDocument,
SimulateTransactionDocument,
@@ -36,7 +38,7 @@ import {
} from './generated/queries.js';
import { ObjectError, SimulationError } from '../client/errors.js';
import { chunk, fromBase64, toBase64 } from '@mysten/utils';
-import { normalizeSuiAddress } from '../utils/sui-types.js';
+import { normalizeStructTag, normalizeSuiAddress } from '../utils/sui-types.js';
import { formatMoveAbortMessage, parseTransactionEffectsBcs } from '../client/utils.js';
import type { OpenMoveTypeSignatureBody, OpenMoveTypeSignature } from './types.js';
import {
@@ -49,6 +51,12 @@ import { TransactionEffects as TransactionEffectsType } from '../grpc/proto/sui/
import { Transaction as GrpcTransactionType } from '../grpc/proto/sui/rpc/v2/transaction.js';
import { TransactionDataBuilder } from '../transactions/TransactionData.js';
import type { BuildTransactionOptions } from '../transactions/index.js';
+import {
+ resolveEventFilter,
+ resolvePagination,
+ resolveTransactionFilter,
+ validateTransactionQuery,
+} from '../client/query-filters.js';
export class GraphQLCoreClient extends CoreClient {
#graphqlClient: SuiGraphQLClient;
@@ -592,6 +600,112 @@ export class GraphQLCoreClient extends CoreClient {
return this.#graphqlClient.listDynamicFields(options);
}
+ async listTransactions(
+ options: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ const pagination = resolvePagination(options);
+ const { descending, after, before, limit } = pagination;
+ const filter = options.filter
+ ? await resolveTransactionFilter(this.mvr, options.filter)
+ : undefined;
+ validateTransactionQuery(filter, pagination);
+
+ const transactions = await this.#graphqlQuery(
+ {
+ query: ListTransactionsDocument,
+ signal: options.signal,
+ variables: {
+ filter: filter && {
+ sentAddress: filter.$kind === 'sender' ? filter.sender : undefined,
+ function:
+ filter.$kind === 'function'
+ ? [filter.package, filter.module, filter.function].filter(Boolean).join('::')
+ : undefined,
+ },
+ first: descending ? undefined : limit,
+ after,
+ last: descending ? limit : undefined,
+ before,
+ includeTransaction: options.include?.transaction ?? false,
+ includeEffects: options.include?.effects ?? false,
+ includeEvents: options.include?.events ?? false,
+ includeBalanceChanges: options.include?.balanceChanges ?? false,
+ includeObjectTypes: options.include?.objectTypes ?? false,
+ includeBcs: options.include?.bcs ?? false,
+ },
+ },
+ (result) => result.transactions,
+ );
+
+ // Backwards pagination returns nodes in ascending order, so reverse them for descending reads
+ const nodes = descending ? [...transactions.nodes].reverse() : transactions.nodes;
+
+ return {
+ transactions: nodes.map((transaction) => parseTransaction(transaction, options.include)),
+ hasNextPage: descending
+ ? transactions.pageInfo.hasPreviousPage
+ : transactions.pageInfo.hasNextPage,
+ startCursor:
+ (descending ? transactions.pageInfo.endCursor : transactions.pageInfo.startCursor) ?? null,
+ endCursor:
+ (descending ? transactions.pageInfo.startCursor : transactions.pageInfo.endCursor) ?? null,
+ };
+ }
+
+ async listEvents(
+ options: SuiClientTypes.ListEventsOptions,
+ ): Promise {
+ const { descending, after, before, limit } = resolvePagination(options);
+ const filter = options.filter ? await resolveEventFilter(this.mvr, options.filter) : undefined;
+
+ const events = await this.#graphqlQuery(
+ {
+ query: ListEventsDocument,
+ signal: options.signal,
+ variables: {
+ filter: filter && {
+ sender: filter.$kind === 'sender' ? filter.sender : undefined,
+ module:
+ filter.$kind === 'emitModule' ? `${filter.package}::${filter.module}` : undefined,
+ type:
+ filter.$kind === 'eventTypeModule'
+ ? `${filter.package}::${filter.module}`
+ : filter.$kind === 'eventType'
+ ? filter.eventType
+ : undefined,
+ },
+ first: descending ? undefined : limit,
+ after,
+ last: descending ? limit : undefined,
+ before,
+ },
+ },
+ (result) => result.events,
+ );
+
+ // Backwards pagination returns nodes in ascending order, so reverse them for descending reads
+ const nodes = descending ? [...events.nodes].reverse() : events.nodes;
+
+ return {
+ events: nodes.map(
+ (event): SuiClientTypes.EventEntry => ({
+ packageId: normalizeSuiAddress(event.transactionModule?.package?.address!),
+ module: event.transactionModule?.name!,
+ sender: normalizeSuiAddress(event.sender?.address!),
+ eventType: normalizeStructTag(event.contents?.type?.repr!),
+ bcs: event.contents?.bcs ? fromBase64(event.contents.bcs) : new Uint8Array(),
+ json: (event.contents?.json as Record) ?? null,
+ checkpoint: event.transaction?.effects?.checkpoint?.sequenceNumber?.toString() ?? null,
+ transactionDigest: event.transaction?.digest!,
+ eventIndex: event.sequenceNumber,
+ }),
+ ),
+ hasNextPage: descending ? events.pageInfo.hasPreviousPage : events.pageInfo.hasNextPage,
+ startCursor: (descending ? events.pageInfo.endCursor : events.pageInfo.startCursor) ?? null,
+ endCursor: (descending ? events.pageInfo.startCursor : events.pageInfo.endCursor) ?? null,
+ };
+ }
+
async verifyZkLoginSignature(
options: SuiClientTypes.VerifyZkLoginSignatureOptions,
): Promise {
diff --git a/packages/sui/src/graphql/generated/queries.ts b/packages/sui/src/graphql/generated/queries.ts
index ebc1679f7..23152f43f 100644
--- a/packages/sui/src/graphql/generated/queries.ts
+++ b/packages/sui/src/graphql/generated/queries.ts
@@ -8,6 +8,31 @@ type Exact = { [K in keyof T]: T[K] };
export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
import { OpenMoveTypeSignature } from '../types.js';
import { DocumentTypeDecoration } from '@graphql-typed-document-node/core';
+export type EventFilter = {
+ /** Limit to events that occured strictly after the given checkpoint. */
+ afterCheckpoint?: number | null | undefined;
+ /** Limit to events in the given checkpoint. */
+ atCheckpoint?: number | null | undefined;
+ /** Limit to event that occured strictly before the given checkpoint. */
+ beforeCheckpoint?: number | null | undefined;
+ /**
+ * Events emitted by a particular module. An event is emitted by a particular module if some function in the module is called by a PTB and emits an event.
+ *
+ * Modules can be filtered by their package, or package::module. We currently do not support filtering by emitting module and event type at the same time so if both are provided in one filter, the query will error.
+ */
+ module?: string | null | undefined;
+ /** Filter on events by transaction sender address. */
+ sender?: string | null | undefined;
+ /**
+ * This field is used to specify the type of event emitted.
+ *
+ * Events can be filtered by their type's package, package::module, or their fully qualified type name.
+ *
+ * Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`.
+ */
+ type?: string | null | undefined;
+};
+
/** The execution status of this transaction: success or failure. */
export enum ExecutionStatus {
/** The transaction could not be executed. */
@@ -116,6 +141,43 @@ export enum OwnerKind {
Shared = 'SHARED'
}
+export type TransactionFilter = {
+ /**
+ * Limit to transactions that interacted with the given address.
+ * The address could be a sender, sponsor, or recipient of the transaction.
+ */
+ affectedAddress?: string | null | undefined;
+ /**
+ * Limit to transactions that interacted with the given object.
+ * The object could have been created, read, modified, deleted, wrapped, or unwrapped by the transaction.
+ * Objects that were passed as a `Receiving` input are not considered to have been affected by a transaction unless they were actually received.
+ */
+ affectedObject?: string | null | undefined;
+ /** Filter to transactions that occurred strictly after the given checkpoint. */
+ afterCheckpoint?: number | null | undefined;
+ /** Filter to transactions in the given checkpoint. */
+ atCheckpoint?: number | null | undefined;
+ /** Filter to transaction that occurred strictly before the given checkpoint. */
+ beforeCheckpoint?: number | null | undefined;
+ /** Filter transactions by move function called. Calls can be filtered by the `package`, `package::module`, or the `package::module::name` of their function. */
+ function?: string | null | undefined;
+ /** An input filter selecting for either system or programmable transactions. */
+ kind?: TransactionKindInput | null | undefined;
+ /** Limit to transactions that were sent by the given address. */
+ sentAddress?: string | null | undefined;
+};
+
+/** An input filter selecting for either system or programmable transactions. */
+export enum TransactionKindInput {
+ /** A user submitted transaction block. */
+ ProgrammableTx = 'PROGRAMMABLE_TX',
+ /**
+ * A system transaction can be one of several types of transactions.
+ * See [unions/transaction-block-kind] for more details.
+ */
+ SystemTx = 'SYSTEM_TX'
+}
+
/** An enum that specifies the intent scope to be used to parse the bytes for signature verification. */
export enum ZkLoginIntentScope {
/** Indicates that the bytes are to be parsed as a personal message. */
@@ -206,6 +268,17 @@ export type GetReferenceGasPriceQueryVariables = Exact<{ [key: string]: never; }
export type GetReferenceGasPriceQuery = { epoch: { referenceGasPrice: string | null } | null };
+export type ListEventsQueryVariables = Exact<{
+ filter?: EventFilter | null | undefined;
+ first?: number | null | undefined;
+ after?: string | null | undefined;
+ last?: number | null | undefined;
+ before?: string | null | undefined;
+}>;
+
+
+export type ListEventsQuery = { events: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null, endCursor: string | null }, nodes: Array<{ sequenceNumber: number, transaction: { digest: string, effects: { checkpoint: { sequenceNumber: number } | null } | null } | null, transactionModule: { name: string, package: { address: string } | null } | null, sender: { address: string } | null, contents: { bcs: string | null, json: unknown, type: { repr: string } | null } | null }> } | null };
+
export type DefaultSuinsNameQueryVariables = Exact<{
address: string;
}>;
@@ -329,6 +402,23 @@ export type GetTransactionBlockQueryVariables = Exact<{
export type GetTransactionBlockQuery = { transaction: { digest: string, transactionJson?: unknown, transactionBcs?: string | null, signatures: Array<{ signatureBytes: string | null }>, effects: { status: ExecutionStatus | null, effectsBcs?: string | null, effectsJson?: unknown, balanceChangesJson?: unknown, executionError: { message: string, abortCode: string | null, identifier: string | null, constant: string | null, sourceLineNumber: number | null, instructionOffset: number | null, module: { name: string, package: { address: string } | null } | null, function: { name: string } | null } | null, epoch: { epochId: number } | null, objectChanges?: { nodes: Array<{ address: string, outputState: { asMoveObject: { contents: { type: { repr: string } | null } | null } | null } | null }> } | null, events?: { pageInfo: { hasNextPage: boolean }, nodes: Array<{ transactionModule: { name: string, package: { address: string } | null } | null, sender: { address: string } | null, contents: { bcs: string | null, json: unknown, type: { repr: string } | null } | null }> } | null } | null } | null };
+export type ListTransactionsQueryVariables = Exact<{
+ filter?: TransactionFilter | null | undefined;
+ first?: number | null | undefined;
+ after?: string | null | undefined;
+ last?: number | null | undefined;
+ before?: string | null | undefined;
+ includeTransaction?: boolean | null | undefined;
+ includeEffects?: boolean | null | undefined;
+ includeEvents?: boolean | null | undefined;
+ includeBalanceChanges?: boolean | null | undefined;
+ includeObjectTypes?: boolean | null | undefined;
+ includeBcs?: boolean | null | undefined;
+}>;
+
+
+export type ListTransactionsQuery = { transactions: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null, endCursor: string | null }, nodes: Array<{ digest: string, transactionJson?: unknown, transactionBcs?: string | null, signatures: Array<{ signatureBytes: string | null }>, effects: { status: ExecutionStatus | null, effectsBcs?: string | null, effectsJson?: unknown, balanceChangesJson?: unknown, executionError: { message: string, abortCode: string | null, identifier: string | null, constant: string | null, sourceLineNumber: number | null, instructionOffset: number | null, module: { name: string, package: { address: string } | null } | null, function: { name: string } | null } | null, epoch: { epochId: number } | null, objectChanges?: { nodes: Array<{ address: string, outputState: { asMoveObject: { contents: { type: { repr: string } | null } | null } | null } | null }> } | null, events?: { pageInfo: { hasNextPage: boolean }, nodes: Array<{ transactionModule: { name: string, package: { address: string } | null } | null, sender: { address: string } | null, contents: { bcs: string | null, json: unknown, type: { repr: string } | null } | null }> } | null } | null }> } | null };
+
export type Transaction_FieldsFragment = { digest: string, transactionJson?: unknown, transactionBcs?: string | null, signatures: Array<{ signatureBytes: string | null }>, effects: { status: ExecutionStatus | null, effectsBcs?: string | null, effectsJson?: unknown, balanceChangesJson?: unknown, executionError: { message: string, abortCode: string | null, identifier: string | null, constant: string | null, sourceLineNumber: number | null, instructionOffset: number | null, module: { name: string, package: { address: string } | null } | null, function: { name: string } | null } | null, epoch: { epochId: number } | null, objectChanges?: { nodes: Array<{ address: string, outputState: { asMoveObject: { contents: { type: { repr: string } | null } | null } | null } | null }> } | null, events?: { pageInfo: { hasNextPage: boolean }, nodes: Array<{ transactionModule: { name: string, package: { address: string } | null } | null, sender: { address: string } | null, contents: { bcs: string | null, json: unknown, type: { repr: string } | null } | null }> } | null } | null };
export type ResolveTransactionQueryVariables = Exact<{
@@ -760,6 +850,51 @@ export const GetReferenceGasPriceDocument = new TypedDocumentString(`
}
}
`) as unknown as TypedDocumentString;
+export const ListEventsDocument = new TypedDocumentString(`
+ query listEvents($filter: EventFilter, $first: Int, $after: String, $last: Int, $before: String) {
+ events(
+ filter: $filter
+ first: $first
+ after: $after
+ last: $last
+ before: $before
+ ) {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ nodes {
+ sequenceNumber
+ transaction {
+ digest
+ effects {
+ checkpoint {
+ sequenceNumber
+ }
+ }
+ }
+ transactionModule {
+ package {
+ address
+ }
+ name
+ }
+ sender {
+ address
+ }
+ contents {
+ type {
+ repr
+ }
+ bcs
+ json
+ }
+ }
+ }
+}
+ `) as unknown as TypedDocumentString;
export const DefaultSuinsNameDocument = new TypedDocumentString(`
query defaultSuinsName($address: SuiAddress!) {
address(address: $address) {
@@ -1142,6 +1277,97 @@ export const GetTransactionBlockDocument = new TypedDocumentString(`
}
}
}`) as unknown as TypedDocumentString;
+export const ListTransactionsDocument = new TypedDocumentString(`
+ query listTransactions($filter: TransactionFilter, $first: Int, $after: String, $last: Int, $before: String, $includeTransaction: Boolean = false, $includeEffects: Boolean = false, $includeEvents: Boolean = false, $includeBalanceChanges: Boolean = false, $includeObjectTypes: Boolean = false, $includeBcs: Boolean = false) {
+ transactions(
+ filter: $filter
+ first: $first
+ after: $after
+ last: $last
+ before: $before
+ ) {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ nodes {
+ ...TRANSACTION_FIELDS
+ }
+ }
+}
+ fragment TRANSACTION_FIELDS on Transaction {
+ digest
+ transactionJson @include(if: $includeTransaction)
+ transactionBcs @include(if: $includeBcs)
+ signatures {
+ signatureBytes
+ }
+ effects {
+ status
+ executionError {
+ message
+ abortCode
+ identifier
+ constant
+ sourceLineNumber
+ instructionOffset
+ module {
+ name
+ package {
+ address
+ }
+ }
+ function {
+ name
+ }
+ }
+ epoch {
+ epochId
+ }
+ effectsBcs @include(if: $includeEffects)
+ effectsJson @include(if: $includeObjectTypes)
+ objectChanges(first: 50) @include(if: $includeObjectTypes) {
+ nodes {
+ address
+ outputState {
+ asMoveObject {
+ contents {
+ type {
+ repr
+ }
+ }
+ }
+ }
+ }
+ }
+ balanceChangesJson @include(if: $includeBalanceChanges)
+ events(first: 50) @include(if: $includeEvents) {
+ pageInfo {
+ hasNextPage
+ }
+ nodes {
+ transactionModule {
+ package {
+ address
+ }
+ name
+ }
+ sender {
+ address
+ }
+ contents {
+ type {
+ repr
+ }
+ bcs
+ json
+ }
+ }
+ }
+ }
+}`) as unknown as TypedDocumentString;
export const ResolveTransactionDocument = new TypedDocumentString(`
query resolveTransaction($transaction: JSON!, $doGasSelection: Boolean = true, $checksEnabled: Boolean = true) {
simulateTransaction(
diff --git a/packages/sui/src/graphql/generated/schema.graphql b/packages/sui/src/graphql/generated/schema.graphql
index 603669b41..13e23960d 100644
--- a/packages/sui/src/graphql/generated/schema.graphql
+++ b/packages/sui/src/graphql/generated/schema.graphql
@@ -84,6 +84,16 @@ type Address implements Node & IAddressable {
"""
asObject: Object
"""
+ How this address (interpreted as an object ID) was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
Returns `None` when no checkpoint is set in scope (e.g. execution scope).
@@ -617,6 +627,16 @@ type CoinMetadata implements IAddressable & IMoveObject & IObject {
"""
allowGlobalPause: Boolean
"""
+ How this object was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -986,6 +1006,16 @@ type DynamicField implements Node & IAddressable & IMoveObject & IObject {
"""
addressAt(rootVersion: UInt53, checkpoint: UInt53): Address
"""
+ How this object was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -1641,6 +1671,16 @@ interface IAddressable {
"""
addressAt(rootVersion: UInt53, checkpoint: UInt53): Address
"""
+ How this addressable entity was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -2275,6 +2315,16 @@ type MoveObject implements Node & IAddressable & IMoveObject & IObject {
"""
asDynamicField: DynamicField
"""
+ How this object was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -2434,6 +2484,16 @@ type MovePackage implements Node & IAddressable & IObject {
"""
addressAt(rootVersion: UInt53, checkpoint: UInt53): Address
"""
+ How this object was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -2955,6 +3015,16 @@ type Object implements Node & IAddressable & IObject {
"""
asMovePackage: MovePackage
"""
+ How this object was referenced by a specific transaction.
+
+ Returns `null` if the object was not referenced, or was present only as a non-object marker variant of unchanged consensus input (e.g. cancelled, stream-ended, per-epoch).
+
+ The `transactionDigest` argument may be omitted when the query is scoped under a transaction context (e.g. a parent `Transaction`, `TransactionEffects`, or `Event`); the field then resolves against the in-scope transaction.
+
+ Passing an explicit `transactionDigest` other than the in-scope transaction in subscription context is not supported; for arbitrary transaction lookups, use the indexed Query API.
+ """
+ asTransactionObject(transactionDigest: String): TransactionObject
+ """
Fetch the total balance for coins with marker type `coinType` (e.g. `0x2::sui::SUI`), owned by this address.
If the address does not own any coins of that type, a balance of zero is returned.
@@ -4334,6 +4404,11 @@ enum TransactionKindInput {
PROGRAMMABLE_TX
}
+"""
+An object as it appears in a specific transaction. The variant discriminates whether the object was changed by the transaction or read as an unchanged consensus (shared) input.
+"""
+union TransactionObject = ObjectChange | ConsensusObjectRead
+
"""
Transfers `inputs` to `address`. All inputs must have the `store` ability (allows public transfer) and must not be previously immutable or shared.
"""
diff --git a/packages/sui/src/graphql/generated/tada-env.ts b/packages/sui/src/graphql/generated/tada-env.ts
index 6a3489a3a..bf239e326 100644
--- a/packages/sui/src/graphql/generated/tada-env.ts
+++ b/packages/sui/src/graphql/generated/tada-env.ts
@@ -253,6 +253,23 @@ const introspection = {
"args": [],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -1739,6 +1756,23 @@ const introspection = {
"args": [],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -2805,6 +2839,23 @@ const introspection = {
],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -4741,6 +4792,23 @@ const introspection = {
],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -6808,6 +6876,23 @@ const introspection = {
"args": [],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -7499,6 +7584,23 @@ const introspection = {
],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -9041,6 +9143,23 @@ const introspection = {
"args": [],
"isDeprecated": false
},
+ {
+ "name": "asTransactionObject",
+ "type": {
+ "kind": "UNION",
+ "name": "TransactionObject"
+ },
+ "args": [
+ {
+ "name": "transactionDigest",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String"
+ }
+ }
+ ],
+ "isDeprecated": false
+ },
{
"name": "balance",
"type": {
@@ -13053,6 +13172,20 @@ const introspection = {
}
]
},
+ {
+ "kind": "UNION",
+ "name": "TransactionObject",
+ "possibleTypes": [
+ {
+ "kind": "OBJECT",
+ "name": "ConsensusObjectRead"
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ObjectChange"
+ }
+ ]
+ },
{
"kind": "OBJECT",
"name": "TransferObjectsCommand",
diff --git a/packages/sui/src/graphql/queries/listEvents.graphql b/packages/sui/src/graphql/queries/listEvents.graphql
new file mode 100644
index 000000000..520c35931
--- /dev/null
+++ b/packages/sui/src/graphql/queries/listEvents.graphql
@@ -0,0 +1,40 @@
+# Copyright (c) Mysten Labs, Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+query listEvents($filter: EventFilter, $first: Int, $after: String, $last: Int, $before: String) {
+ events(filter: $filter, first: $first, after: $after, last: $last, before: $before) {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ nodes {
+ sequenceNumber
+ transaction {
+ digest
+ effects {
+ checkpoint {
+ sequenceNumber
+ }
+ }
+ }
+ transactionModule {
+ package {
+ address
+ }
+ name
+ }
+ sender {
+ address
+ }
+ contents {
+ type {
+ repr
+ }
+ bcs
+ json
+ }
+ }
+ }
+}
diff --git a/packages/sui/src/graphql/queries/transactions.graphql b/packages/sui/src/graphql/queries/transactions.graphql
index 76bb67332..fd5dd745c 100644
--- a/packages/sui/src/graphql/queries/transactions.graphql
+++ b/packages/sui/src/graphql/queries/transactions.graphql
@@ -71,6 +71,32 @@ query getTransactionBlock(
}
}
+query listTransactions(
+ $filter: TransactionFilter
+ $first: Int
+ $after: String
+ $last: Int
+ $before: String
+ $includeTransaction: Boolean = false
+ $includeEffects: Boolean = false
+ $includeEvents: Boolean = false
+ $includeBalanceChanges: Boolean = false
+ $includeObjectTypes: Boolean = false
+ $includeBcs: Boolean = false
+) {
+ transactions(filter: $filter, first: $first, after: $after, last: $last, before: $before) {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ nodes {
+ ...TRANSACTION_FIELDS
+ }
+ }
+}
+
fragment TRANSACTION_FIELDS on Transaction {
digest
transactionJson @include(if: $includeTransaction)
diff --git a/packages/sui/src/grpc/client.ts b/packages/sui/src/grpc/client.ts
index f85dd63af..683746653 100644
--- a/packages/sui/src/grpc/client.ts
+++ b/packages/sui/src/grpc/client.ts
@@ -221,6 +221,16 @@ export class SuiGrpcClient extends BaseClient implements SuiClientTypes.Transpor
return this.core.getDynamicField(input);
}
+ listTransactions(
+ input: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ return this.core.listTransactions(input);
+ }
+
+ listEvents(input: SuiClientTypes.ListEventsOptions): Promise {
+ return this.core.listEvents(input);
+ }
+
getMoveFunction(
input: SuiClientTypes.GetMoveFunctionOptions,
): Promise {
diff --git a/packages/sui/src/grpc/core.ts b/packages/sui/src/grpc/core.ts
index 94bdf1988..3dbfb18f1 100644
--- a/packages/sui/src/grpc/core.ts
+++ b/packages/sui/src/grpc/core.ts
@@ -45,6 +45,16 @@ import {
} from '../client/transaction-resolver.js';
import { Value } from './proto/google/protobuf/struct.js';
import { SimulateTransactionRequest_TransactionChecks } from './proto/sui/rpc/v2/transaction_execution_service.js';
+import type { QueryEnd, QueryOptions } from './proto/sui/rpc/v2/query_options.js';
+import { Ordering, QueryEndReason } from './proto/sui/rpc/v2/query_options.js';
+import type { ResolvedPagination } from '../client/query-filters.js';
+import {
+ resolveEventFilter,
+ resolvePagination,
+ resolveTransactionFilter,
+ validateTransactionQuery,
+} from '../client/query-filters.js';
+import { toGrpcEventFilter, toGrpcTransactionFilter } from './filters.js';
export interface GrpcCoreClientOptions extends CoreClientOptions {
client: SuiGrpcClient;
@@ -305,31 +315,7 @@ export class GrpcCoreClient extends CoreClient {
async getTransaction(
options: SuiClientTypes.GetTransactionOptions,
): Promise> {
- const paths = ['digest', 'transaction.digest', 'signatures', 'effects.status'];
- if (options.include?.transaction) {
- paths.push(
- 'transaction.sender',
- 'transaction.gas_payment',
- 'transaction.expiration',
- 'transaction.kind',
- );
- }
- if (options.include?.bcs) {
- paths.push('transaction.bcs');
- }
- if (options.include?.balanceChanges) {
- paths.push('balance_changes');
- }
- if (options.include?.effects) {
- paths.push('effects');
- }
- if (options.include?.events) {
- paths.push('events');
- }
- if (options.include?.objectTypes) {
- paths.push('effects.changed_objects.object_type');
- paths.push('effects.changed_objects.object_id');
- }
+ const paths = transactionReadMaskPaths(options.include);
const { response } = await this.#client.ledgerService.getTransaction({
digest: options.digest,
@@ -347,31 +333,7 @@ export class GrpcCoreClient extends CoreClient {
async executeTransaction(
options: SuiClientTypes.ExecuteTransactionOptions,
): Promise> {
- const paths = ['digest', 'transaction.digest', 'signatures', 'effects.status'];
- if (options.include?.transaction) {
- paths.push(
- 'transaction.sender',
- 'transaction.gas_payment',
- 'transaction.expiration',
- 'transaction.kind',
- );
- }
- if (options.include?.bcs) {
- paths.push('transaction.bcs');
- }
- if (options.include?.balanceChanges) {
- paths.push('balance_changes');
- }
- if (options.include?.effects) {
- paths.push('effects');
- }
- if (options.include?.events) {
- paths.push('events');
- }
- if (options.include?.objectTypes) {
- paths.push('effects.changed_objects.object_type');
- paths.push('effects.changed_objects.object_id');
- }
+ const paths = transactionReadMaskPaths(options.include);
const { response } = await this.#client.transactionExecutionService.executeTransaction({
transaction: {
@@ -397,37 +359,8 @@ export class GrpcCoreClient extends CoreClient {
async simulateTransaction(
options: SuiClientTypes.SimulateTransactionOptions,
): Promise> {
- const paths = [
- 'transaction.digest',
- 'transaction.transaction.digest',
- 'transaction.signatures',
- 'transaction.effects.status',
- ];
- if (options.include?.transaction) {
- paths.push(
- 'transaction.transaction.sender',
- 'transaction.transaction.gas_payment',
- 'transaction.transaction.expiration',
- 'transaction.transaction.kind',
- );
- }
- if (options.include?.bcs) {
- paths.push('transaction.transaction.bcs');
- }
- if (options.include?.balanceChanges) {
- paths.push('transaction.balance_changes');
- }
- if (options.include?.effects) {
- paths.push('transaction.effects');
- }
- if (options.include?.events) {
- paths.push('transaction.events');
- }
- if (options.include?.objectTypes) {
- // Use effects.changed_objects to match JSON-RPC behavior (which uses objectChanges)
- paths.push('transaction.effects.changed_objects.object_type');
- paths.push('transaction.effects.changed_objects.object_id');
- }
+ // The simulated transaction is nested one level deeper in the response
+ const paths = transactionReadMaskPaths(options.include, 'transaction.');
if (options.include?.commandResults) {
paths.push('command_outputs');
}
@@ -612,6 +545,127 @@ export class GrpcCoreClient extends CoreClient {
return this.#client.listDynamicFields(options);
}
+ async listTransactions(
+ options: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ const paths = transactionReadMaskPaths(options.include);
+
+ const filter = options.filter
+ ? await resolveTransactionFilter(this.mvr, options.filter)
+ : undefined;
+ const pagination = resolvePagination(options);
+ validateTransactionQuery(filter, pagination);
+
+ const call = this.#client.ledgerService.listTransactions(
+ {
+ readMask: { paths },
+ filter: filter && toGrpcTransactionFilter(filter),
+ options: toGrpcQueryOptions(pagination),
+ },
+ { abort: options.signal },
+ );
+
+ const transactions: SuiClientTypes.TransactionResult[] = [];
+ let startCursor: string | null = null;
+ let endCursor: string | null = null;
+ let frontier: string | null = null;
+ let end: QueryEnd | undefined;
+
+ for await (const frame of call.responses) {
+ if (frame.watermark?.cursor) {
+ frontier = toBase64(frame.watermark.cursor);
+ }
+ if (frame.transaction) {
+ startCursor ??= frontier;
+ endCursor = frontier;
+ transactions.push(parseTransaction(frame.transaction, options.include));
+ }
+ if (frame.end) {
+ end = frame.end;
+ }
+ }
+
+ const hasNextPage = queryHasNextPage(end);
+
+ return {
+ transactions,
+ hasNextPage,
+ startCursor,
+ // Item-less scans that stopped early continue from the scan frontier,
+ // while terminal empty pages have no positions to continue from
+ endCursor: endCursor ?? (hasNextPage ? frontier : null),
+ };
+ }
+
+ async listEvents(
+ options: SuiClientTypes.ListEventsOptions,
+ ): Promise {
+ const call = this.#client.ledgerService.listEvents(
+ {
+ readMask: {
+ paths: [
+ 'package_id',
+ 'module',
+ 'sender',
+ 'event_type',
+ 'contents',
+ 'json',
+ 'checkpoint',
+ 'transaction_digest',
+ 'event_index',
+ ],
+ },
+ filter: options.filter
+ ? toGrpcEventFilter(await resolveEventFilter(this.mvr, options.filter))
+ : undefined,
+ options: toGrpcQueryOptions(resolvePagination(options)),
+ },
+ { abort: options.signal },
+ );
+
+ const events: SuiClientTypes.EventEntry[] = [];
+ let startCursor: string | null = null;
+ let endCursor: string | null = null;
+ let frontier: string | null = null;
+ let end: QueryEnd | undefined;
+
+ for await (const frame of call.responses) {
+ if (frame.watermark?.cursor) {
+ frontier = toBase64(frame.watermark.cursor);
+ }
+ if (frame.event) {
+ startCursor ??= frontier;
+ endCursor = frontier;
+ const event = frame.event;
+ events.push({
+ packageId: normalizeSuiAddress(event.packageId!),
+ module: event.module!,
+ sender: normalizeSuiAddress(event.sender!),
+ eventType: normalizeStructTag(event.eventType!),
+ bcs: event.contents?.value ?? new Uint8Array(),
+ json: event.json ? (Value.toJson(event.json) as Record) : null,
+ checkpoint: event.checkpoint?.toString() ?? null,
+ transactionDigest: event.transactionDigest!,
+ eventIndex: event.eventIndex!,
+ });
+ }
+ if (frame.end) {
+ end = frame.end;
+ }
+ }
+
+ const hasNextPage = queryHasNextPage(end);
+
+ return {
+ events,
+ hasNextPage,
+ startCursor,
+ // Item-less scans that stopped early continue from the scan frontier,
+ // while terminal empty pages have no positions to continue from
+ endCursor: endCursor ?? (hasNextPage ? frontier : null),
+ };
+ }
+
async verifyZkLoginSignature(
options: SuiClientTypes.VerifyZkLoginSignatureOptions,
): Promise {
@@ -805,6 +859,54 @@ export class GrpcCoreClient extends CoreClient {
}
}
+function toGrpcQueryOptions(pagination: ResolvedPagination): QueryOptions {
+ return {
+ limit: pagination.limit,
+ ordering: pagination.descending ? Ordering.DESCENDING : Ordering.ASCENDING,
+ after: pagination.after ? fromBase64(pagination.after) : undefined,
+ before: pagination.before ? fromBase64(pagination.before) : undefined,
+ };
+}
+
+function queryHasNextPage(end: QueryEnd | undefined): boolean {
+ return end?.reason === QueryEndReason.ITEM_LIMIT || end?.reason === QueryEndReason.SCAN_LIMIT;
+}
+
+function transactionReadMaskPaths(
+ include: SuiClientTypes.TransactionInclude | undefined,
+ prefix = '',
+): string[] {
+ const paths = ['digest', 'transaction.digest', 'signatures', 'effects.status'];
+
+ if (include?.transaction) {
+ paths.push(
+ 'transaction.sender',
+ 'transaction.gas_payment',
+ 'transaction.expiration',
+ 'transaction.kind',
+ );
+ }
+ if (include?.bcs) {
+ paths.push('transaction.bcs');
+ }
+ if (include?.balanceChanges) {
+ paths.push('balance_changes');
+ }
+ if (include?.effects) {
+ paths.push('effects');
+ }
+ if (include?.events) {
+ paths.push('events');
+ }
+ if (include?.objectTypes) {
+ // Use effects.changed_objects to match JSON-RPC behavior (which uses objectChanges)
+ paths.push('effects.changed_objects.object_type');
+ paths.push('effects.changed_objects.object_id');
+ }
+
+ return prefix ? paths.map((path) => prefix + path) : paths;
+}
+
function mapDisplayProto(
include: boolean | undefined,
display: { output?: Value; errors?: Value } | undefined,
diff --git a/packages/sui/src/grpc/filters.ts b/packages/sui/src/grpc/filters.ts
new file mode 100644
index 000000000..e51938935
--- /dev/null
+++ b/packages/sui/src/grpc/filters.ts
@@ -0,0 +1,68 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import type { ResolvedEventFilter, ResolvedTransactionFilter } from '../client/query-filters.js';
+import type { EventFilter, TransactionFilter } from './proto/sui/rpc/v2/filter.js';
+
+export function toGrpcTransactionFilter(filter: ResolvedTransactionFilter): TransactionFilter {
+ return {
+ terms: [
+ {
+ literals: [
+ filter.$kind === 'sender'
+ ? {
+ negated: false,
+ predicate: { oneofKind: 'sender', sender: { address: filter.sender } },
+ }
+ : {
+ negated: false,
+ predicate: {
+ oneofKind: 'moveCall',
+ moveCall: {
+ function: [filter.package, filter.module, filter.function]
+ .filter(Boolean)
+ .join('::'),
+ },
+ },
+ },
+ ],
+ },
+ ],
+ };
+}
+
+export function toGrpcEventFilter(filter: ResolvedEventFilter): EventFilter {
+ return {
+ terms: [
+ {
+ literals: [
+ filter.$kind === 'sender'
+ ? {
+ negated: false,
+ predicate: { oneofKind: 'sender', sender: { address: filter.sender } },
+ }
+ : filter.$kind === 'emitModule'
+ ? {
+ negated: false,
+ predicate: {
+ oneofKind: 'emitModule',
+ emitModule: { module: `${filter.package}::${filter.module}` },
+ },
+ }
+ : {
+ negated: false,
+ predicate: {
+ oneofKind: 'eventType',
+ eventType: {
+ eventType:
+ filter.$kind === 'eventTypeModule'
+ ? `${filter.package}::${filter.module}`
+ : filter.eventType,
+ },
+ },
+ },
+ ],
+ },
+ ],
+ };
+}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/event.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/event.ts
index 473a7a129..2e0e17843 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/event.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/event.ts
@@ -81,6 +81,37 @@ export interface Event {
* @generated from protobuf field: optional google.protobuf.Value json = 6;
*/
json?: Value;
+ /**
+ * The sequence number of the checkpoint that includes the transaction
+ * that emitted this event. Populated when the event is delivered on its
+ * own (for example via `LedgerService.ListEvents`); left unset when the
+ * event is carried inside its transaction's `events` list, where the
+ * enclosing `ExecutedTransaction` already provides this context.
+ *
+ * @generated from protobuf field: optional uint64 checkpoint = 7;
+ */
+ checkpoint?: bigint;
+ /**
+ * The digest of the transaction that emitted this event.
+ *
+ * @generated from protobuf field: optional string transaction_digest = 8;
+ */
+ transactionDigest?: string;
+ /**
+ * Zero-based position of the emitting transaction within its containing
+ * checkpoint. For clients verifying authenticated event streams this
+ * index is part of the BCS-encoded `EventCommitment` leaf used to
+ * construct the per-checkpoint merkle root.
+ *
+ * @generated from protobuf field: optional uint64 transaction_index = 9;
+ */
+ transactionIndex?: bigint;
+ /**
+ * Zero-based index of this event within its transaction's event list.
+ *
+ * @generated from protobuf field: optional uint32 event_index = 10;
+ */
+ eventIndex?: number;
}
// @generated message type with reflection information, may provide speed optimized methods
class TransactionEvents$Type extends MessageType {
@@ -106,6 +137,24 @@ class Event$Type extends MessageType {
{ no: 4, name: 'event_type', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: 'contents', kind: 'message', T: () => Bcs },
{ no: 6, name: 'json', kind: 'message', T: () => Value },
+ {
+ no: 7,
+ name: 'checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ { no: 8, name: 'transaction_digest', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ {
+ no: 9,
+ name: 'transaction_index',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ { no: 10, name: 'event_index', kind: 'scalar', opt: true, T: 13 /*ScalarType.UINT32*/ },
]);
}
}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/executed_transaction.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/executed_transaction.ts
index 5a3c117fa..86e7471a0 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/executed_transaction.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/executed_transaction.ts
@@ -80,6 +80,13 @@ export interface ExecutedTransaction {
* @generated from protobuf field: optional sui.rpc.v2.ObjectSet objects = 9;
*/
objects?: ObjectSet;
+ /**
+ * Zero-based position of this transaction within the checkpoint that
+ * includes it.
+ *
+ * @generated from protobuf field: optional uint64 transaction_index = 10;
+ */
+ transactionIndex?: bigint;
}
// @generated message type with reflection information, may provide speed optimized methods
class ExecutedTransaction$Type extends MessageType {
@@ -113,6 +120,14 @@ class ExecutedTransaction$Type extends MessageType {
T: () => BalanceChange,
},
{ no: 9, name: 'objects', kind: 'message', T: () => ObjectSet },
+ {
+ no: 10,
+ name: 'transaction_index',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
]);
}
}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/filter.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/filter.ts
new file mode 100644
index 000000000..3f74eadf6
--- /dev/null
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/filter.ts
@@ -0,0 +1,589 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+// @generated by protobuf-ts 2.9.6 with parameter force_server_none,optimize_code_size,ts_nocheck
+// @generated from protobuf file "sui/rpc/v2/filter.proto" (package "sui.rpc.v2", syntax proto3)
+// tslint:disable
+// @ts-nocheck
+//
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+//
+import { MessageType } from '@protobuf-ts/runtime';
+/**
+ * DNF filter for transactions: any term may match, and each term is an AND
+ * of signed literals.
+ * An absent filter matches everything. A present filter must have at least one
+ * term.
+ *
+ * @generated from protobuf message sui.rpc.v2.TransactionFilter
+ */
+export interface TransactionFilter {
+ /**
+ * Terms are ORed together.
+ *
+ * @generated from protobuf field: repeated sui.rpc.v2.TransactionTerm terms = 1;
+ */
+ terms: TransactionTerm[];
+}
+/**
+ * One conjunction in a transaction DNF filter.
+ *
+ * @generated from protobuf message sui.rpc.v2.TransactionTerm
+ */
+export interface TransactionTerm {
+ /**
+ * Literals are ANDed together.
+ *
+ * @generated from protobuf field: repeated sui.rpc.v2.TransactionLiteral literals = 1;
+ */
+ literals: TransactionLiteral[];
+}
+/**
+ * One signed transaction predicate literal: a predicate, optionally negated.
+ *
+ * @generated from protobuf message sui.rpc.v2.TransactionLiteral
+ */
+export interface TransactionLiteral {
+ /**
+ * When true, the literal matches transactions that the predicate does *not*
+ * match.
+ *
+ * @generated from protobuf field: bool negated = 1;
+ */
+ negated: boolean;
+ /**
+ * @generated from protobuf oneof: predicate
+ */
+ predicate:
+ | {
+ oneofKind: 'sender';
+ /**
+ * Match transactions sent by the specified address.
+ *
+ * @generated from protobuf field: sui.rpc.v2.SenderFilter sender = 2;
+ */
+ sender: SenderFilter;
+ }
+ | {
+ oneofKind: 'affectedAddress';
+ /**
+ * Match transactions where the specified address's state moved as a side
+ * effect: it owns an object after the txn, owned an object before the
+ * txn that was mutated/transferred away/deleted/wrapped, or its
+ * address-balance changed via an accumulator event.
+ *
+ * @generated from protobuf field: sui.rpc.v2.AffectedAddressFilter affected_address = 3;
+ */
+ affectedAddress: AffectedAddressFilter;
+ }
+ | {
+ oneofKind: 'affectedObject';
+ /**
+ * Match transactions whose effects include a change for the specified
+ * object.
+ *
+ * @generated from protobuf field: sui.rpc.v2.AffectedObjectFilter affected_object = 4;
+ */
+ affectedObject: AffectedObjectFilter;
+ }
+ | {
+ oneofKind: 'moveCall';
+ /**
+ * Match transactions that made a Move call matching the specified filter.
+ *
+ * @generated from protobuf field: sui.rpc.v2.MoveCallFilter move_call = 5;
+ */
+ moveCall: MoveCallFilter;
+ }
+ | {
+ oneofKind: 'emitModule';
+ /**
+ * Match transactions that emitted an event whose package/module fields
+ * match the specified filter.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EmitModuleFilter emit_module = 6;
+ */
+ emitModule: EmitModuleFilter;
+ }
+ | {
+ oneofKind: 'eventType';
+ /**
+ * Match transactions that emitted an event with a type matching the
+ * specified filter.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EventTypeFilter event_type = 7;
+ */
+ eventType: EventTypeFilter;
+ }
+ | {
+ oneofKind: 'eventStreamHead';
+ /**
+ * Match transactions that wrote to the specified authenticated event
+ * stream head.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EventStreamHeadFilter event_stream_head = 8;
+ */
+ eventStreamHead: EventStreamHeadFilter;
+ }
+ | {
+ oneofKind: 'packageWrite';
+ /**
+ * Match transactions that wrote a Move package — a first publish or an
+ * upgrade, of any package.
+ *
+ * @generated from protobuf field: sui.rpc.v2.PackageWriteFilter package_write = 9;
+ */
+ packageWrite: PackageWriteFilter;
+ }
+ | {
+ oneofKind: undefined;
+ };
+}
+/**
+ * DNF filter for events: any term may match, and each term is an AND of
+ * signed literals. Sender predicates match all events from matching
+ * transactions; emit-module, event-type, and event-stream-head predicates match
+ * individual event-space dimensions. An absent filter matches everything. A
+ * present filter must have at least one term.
+ *
+ * @generated from protobuf message sui.rpc.v2.EventFilter
+ */
+export interface EventFilter {
+ /**
+ * Terms are ORed together.
+ *
+ * @generated from protobuf field: repeated sui.rpc.v2.EventTerm terms = 1;
+ */
+ terms: EventTerm[];
+}
+/**
+ * One conjunction in an event DNF filter.
+ *
+ * @generated from protobuf message sui.rpc.v2.EventTerm
+ */
+export interface EventTerm {
+ /**
+ * Literals are ANDed together.
+ *
+ * @generated from protobuf field: repeated sui.rpc.v2.EventLiteral literals = 1;
+ */
+ literals: EventLiteral[];
+}
+/**
+ * One signed event predicate literal: a predicate, optionally negated.
+ *
+ * @generated from protobuf message sui.rpc.v2.EventLiteral
+ */
+export interface EventLiteral {
+ /**
+ * When true, the literal matches events that the predicate does *not* match.
+ *
+ * @generated from protobuf field: bool negated = 1;
+ */
+ negated: boolean;
+ /**
+ * @generated from protobuf oneof: predicate
+ */
+ predicate:
+ | {
+ oneofKind: 'sender';
+ /**
+ * Match events from transactions sent by the specified address.
+ *
+ * @generated from protobuf field: sui.rpc.v2.SenderFilter sender = 2;
+ */
+ sender: SenderFilter;
+ }
+ | {
+ oneofKind: 'emitModule';
+ /**
+ * Match events whose package/module fields match the specified filter.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EmitModuleFilter emit_module = 3;
+ */
+ emitModule: EmitModuleFilter;
+ }
+ | {
+ oneofKind: 'eventType';
+ /**
+ * Match events whose type matches the specified filter.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EventTypeFilter event_type = 4;
+ */
+ eventType: EventTypeFilter;
+ }
+ | {
+ oneofKind: 'eventStreamHead';
+ /**
+ * Match events committed to the specified authenticated event stream head.
+ *
+ * @generated from protobuf field: sui.rpc.v2.EventStreamHeadFilter event_stream_head = 5;
+ */
+ eventStreamHead: EventStreamHeadFilter;
+ }
+ | {
+ oneofKind: undefined;
+ };
+}
+/**
+ * Match by transaction sender address.
+ *
+ * @generated from protobuf message sui.rpc.v2.SenderFilter
+ */
+export interface SenderFilter {
+ /**
+ * The sender address (hex-encoded).
+ *
+ * @generated from protobuf field: optional string address = 1;
+ */
+ address?: string;
+}
+/**
+ * Match by any address whose state moved as a side effect of the
+ * transaction: object ownership changes (in either direction), prior
+ * owners of removed/wrapped objects, and address-balance changes via
+ * accumulator events.
+ *
+ * @generated from protobuf message sui.rpc.v2.AffectedAddressFilter
+ */
+export interface AffectedAddressFilter {
+ /**
+ * The affected address (hex-encoded).
+ *
+ * @generated from protobuf field: optional string address = 1;
+ */
+ address?: string;
+}
+/**
+ * Match by changed object ID.
+ *
+ * @generated from protobuf message sui.rpc.v2.AffectedObjectFilter
+ */
+export interface AffectedObjectFilter {
+ /**
+ * The changed object ID (hex-encoded).
+ *
+ * @generated from protobuf field: optional string object_id = 1;
+ */
+ objectId?: string;
+}
+/**
+ * Match by Move function call, specified as a `::`-delimited Move path.
+ *
+ * Specificity levels:
+ * "0xpkg" -> matches any call in the package
+ * "0xpkg::module" -> matches any call in the module
+ * "0xpkg::module::function" -> matches calls to the exact function
+ *
+ * @generated from protobuf message sui.rpc.v2.MoveCallFilter
+ */
+export interface MoveCallFilter {
+ /**
+ * Required. Move path of the form `package[::module[::function]]`.
+ *
+ * @generated from protobuf field: optional string function = 1;
+ */
+ function?: string;
+}
+/**
+ * Match by an event's package/module fields, specified as a `::`-delimited
+ * Move path. These identify the top-level Move call that triggered the event.
+ *
+ * Specificity levels:
+ * "0xpkg" -> matches events with this package_id
+ * "0xpkg::module" -> matches events with this package_id and module
+ *
+ * @generated from protobuf message sui.rpc.v2.EmitModuleFilter
+ */
+export interface EmitModuleFilter {
+ /**
+ * Required. Move path of the form `package[::module]`.
+ *
+ * @generated from protobuf field: optional string module = 1;
+ */
+ module?: string;
+}
+/**
+ * Match by event struct type, specified as a Move type string.
+ *
+ * Specificity levels:
+ * "0xaddr" -> matches events whose type is defined at this address
+ * "0xaddr::module" -> matches events whose type is in this module
+ * "0xaddr::module::Name" -> matches events with this type name (any instantiation)
+ * "0xaddr::module::Name" -> matches events with this exact generic instantiation
+ *
+ * @generated from protobuf message sui.rpc.v2.EventTypeFilter
+ */
+export interface EventTypeFilter {
+ /**
+ * Required. Move type string of the form
+ * `address[::module[::Name[]]]`.
+ *
+ * @generated from protobuf field: optional string event_type = 1;
+ */
+ eventType?: string;
+}
+/**
+ * Match by authenticated event stream head.
+ *
+ * @generated from protobuf message sui.rpc.v2.EventStreamHeadFilter
+ */
+export interface EventStreamHeadFilter {
+ /**
+ * The stream id address (hex-encoded).
+ *
+ * @generated from protobuf field: optional string stream_id = 1;
+ */
+ streamId?: string;
+}
+/**
+ * Match transactions that wrote a Move package.
+ *
+ * @generated from protobuf message sui.rpc.v2.PackageWriteFilter
+ */
+export interface PackageWriteFilter {}
+// @generated message type with reflection information, may provide speed optimized methods
+class TransactionFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.TransactionFilter', [
+ {
+ no: 1,
+ name: 'terms',
+ kind: 'message',
+ repeat: 2 /*RepeatType.UNPACKED*/,
+ T: () => TransactionTerm,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.TransactionFilter
+ */
+export const TransactionFilter = new TransactionFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class TransactionTerm$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.TransactionTerm', [
+ {
+ no: 1,
+ name: 'literals',
+ kind: 'message',
+ repeat: 2 /*RepeatType.UNPACKED*/,
+ T: () => TransactionLiteral,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.TransactionTerm
+ */
+export const TransactionTerm = new TransactionTerm$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class TransactionLiteral$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.TransactionLiteral', [
+ { no: 1, name: 'negated', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
+ { no: 2, name: 'sender', kind: 'message', oneof: 'predicate', T: () => SenderFilter },
+ {
+ no: 3,
+ name: 'affected_address',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => AffectedAddressFilter,
+ },
+ {
+ no: 4,
+ name: 'affected_object',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => AffectedObjectFilter,
+ },
+ { no: 5, name: 'move_call', kind: 'message', oneof: 'predicate', T: () => MoveCallFilter },
+ {
+ no: 6,
+ name: 'emit_module',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => EmitModuleFilter,
+ },
+ { no: 7, name: 'event_type', kind: 'message', oneof: 'predicate', T: () => EventTypeFilter },
+ {
+ no: 8,
+ name: 'event_stream_head',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => EventStreamHeadFilter,
+ },
+ {
+ no: 9,
+ name: 'package_write',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => PackageWriteFilter,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.TransactionLiteral
+ */
+export const TransactionLiteral = new TransactionLiteral$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EventFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EventFilter', [
+ {
+ no: 1,
+ name: 'terms',
+ kind: 'message',
+ repeat: 2 /*RepeatType.UNPACKED*/,
+ T: () => EventTerm,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EventFilter
+ */
+export const EventFilter = new EventFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EventTerm$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EventTerm', [
+ {
+ no: 1,
+ name: 'literals',
+ kind: 'message',
+ repeat: 2 /*RepeatType.UNPACKED*/,
+ T: () => EventLiteral,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EventTerm
+ */
+export const EventTerm = new EventTerm$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EventLiteral$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EventLiteral', [
+ { no: 1, name: 'negated', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
+ { no: 2, name: 'sender', kind: 'message', oneof: 'predicate', T: () => SenderFilter },
+ {
+ no: 3,
+ name: 'emit_module',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => EmitModuleFilter,
+ },
+ { no: 4, name: 'event_type', kind: 'message', oneof: 'predicate', T: () => EventTypeFilter },
+ {
+ no: 5,
+ name: 'event_stream_head',
+ kind: 'message',
+ oneof: 'predicate',
+ T: () => EventStreamHeadFilter,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EventLiteral
+ */
+export const EventLiteral = new EventLiteral$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class SenderFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.SenderFilter', [
+ { no: 1, name: 'address', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.SenderFilter
+ */
+export const SenderFilter = new SenderFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class AffectedAddressFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.AffectedAddressFilter', [
+ { no: 1, name: 'address', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.AffectedAddressFilter
+ */
+export const AffectedAddressFilter = new AffectedAddressFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class AffectedObjectFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.AffectedObjectFilter', [
+ { no: 1, name: 'object_id', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.AffectedObjectFilter
+ */
+export const AffectedObjectFilter = new AffectedObjectFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class MoveCallFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.MoveCallFilter', [
+ { no: 1, name: 'function', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.MoveCallFilter
+ */
+export const MoveCallFilter = new MoveCallFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EmitModuleFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EmitModuleFilter', [
+ { no: 1, name: 'module', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EmitModuleFilter
+ */
+export const EmitModuleFilter = new EmitModuleFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EventTypeFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EventTypeFilter', [
+ { no: 1, name: 'event_type', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EventTypeFilter
+ */
+export const EventTypeFilter = new EventTypeFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class EventStreamHeadFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.EventStreamHeadFilter', [
+ { no: 1, name: 'stream_id', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.EventStreamHeadFilter
+ */
+export const EventStreamHeadFilter = new EventStreamHeadFilter$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class PackageWriteFilter$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.PackageWriteFilter', []);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.PackageWriteFilter
+ */
+export const PackageWriteFilter = new PackageWriteFilter$Type();
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.client.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.client.ts
index ad2342d98..fa61bcc55 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.client.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.client.ts
@@ -12,6 +12,13 @@
import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
import type { ServiceInfo } from '@protobuf-ts/runtime-rpc';
import { LedgerService } from './ledger_service.js';
+import type { ListEventsResponse } from './ledger_service.js';
+import type { ListEventsRequest } from './ledger_service.js';
+import type { ListTransactionsResponse } from './ledger_service.js';
+import type { ListTransactionsRequest } from './ledger_service.js';
+import type { ListCheckpointsResponse } from './ledger_service.js';
+import type { ListCheckpointsRequest } from './ledger_service.js';
+import type { ServerStreamingCall } from '@protobuf-ts/runtime-rpc';
import type { GetEpochResponse } from './ledger_service.js';
import type { GetEpochRequest } from './ledger_service.js';
import type { GetCheckpointResponse } from './ledger_service.js';
@@ -84,6 +91,43 @@ export interface ILedgerServiceClient {
input: GetEpochRequest,
options?: RpcOptions,
): UnaryCall;
+ /**
+ * List checkpoints matching the provided filters.
+ *
+ * Checkpoints are returned in ascending or descending checkpoint sequence
+ * number order according to the query options ordering.
+ * A checkpoint matches if any transaction it contains satisfies the filter.
+ *
+ * @generated from protobuf rpc: ListCheckpoints(sui.rpc.v2.ListCheckpointsRequest) returns (stream sui.rpc.v2.ListCheckpointsResponse);
+ */
+ listCheckpoints(
+ input: ListCheckpointsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall;
+ /**
+ * List transactions matching the provided filters.
+ *
+ * Transactions are returned in ascending or descending transaction sequence
+ * order according to the query options ordering.
+ *
+ * @generated from protobuf rpc: ListTransactions(sui.rpc.v2.ListTransactionsRequest) returns (stream sui.rpc.v2.ListTransactionsResponse);
+ */
+ listTransactions(
+ input: ListTransactionsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall;
+ /**
+ * List events matching the provided filters.
+ *
+ * Events are returned in ascending or descending packed event sequence order
+ * according to the query options ordering.
+ *
+ * @generated from protobuf rpc: ListEvents(sui.rpc.v2.ListEventsRequest) returns (stream sui.rpc.v2.ListEventsResponse);
+ */
+ listEvents(
+ input: ListEventsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall;
}
/**
* @generated from protobuf service sui.rpc.v2.LedgerService
@@ -214,4 +258,71 @@ export class LedgerServiceClient implements ILedgerServiceClient, ServiceInfo {
input,
);
}
+ /**
+ * List checkpoints matching the provided filters.
+ *
+ * Checkpoints are returned in ascending or descending checkpoint sequence
+ * number order according to the query options ordering.
+ * A checkpoint matches if any transaction it contains satisfies the filter.
+ *
+ * @generated from protobuf rpc: ListCheckpoints(sui.rpc.v2.ListCheckpointsRequest) returns (stream sui.rpc.v2.ListCheckpointsResponse);
+ */
+ listCheckpoints(
+ input: ListCheckpointsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall {
+ const method = this.methods[7],
+ opt = this._transport.mergeOptions(options);
+ return stackIntercept(
+ 'serverStreaming',
+ this._transport,
+ method,
+ opt,
+ input,
+ );
+ }
+ /**
+ * List transactions matching the provided filters.
+ *
+ * Transactions are returned in ascending or descending transaction sequence
+ * order according to the query options ordering.
+ *
+ * @generated from protobuf rpc: ListTransactions(sui.rpc.v2.ListTransactionsRequest) returns (stream sui.rpc.v2.ListTransactionsResponse);
+ */
+ listTransactions(
+ input: ListTransactionsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall {
+ const method = this.methods[8],
+ opt = this._transport.mergeOptions(options);
+ return stackIntercept(
+ 'serverStreaming',
+ this._transport,
+ method,
+ opt,
+ input,
+ );
+ }
+ /**
+ * List events matching the provided filters.
+ *
+ * Events are returned in ascending or descending packed event sequence order
+ * according to the query options ordering.
+ *
+ * @generated from protobuf rpc: ListEvents(sui.rpc.v2.ListEventsRequest) returns (stream sui.rpc.v2.ListEventsResponse);
+ */
+ listEvents(
+ input: ListEventsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall {
+ const method = this.methods[9],
+ opt = this._transport.mergeOptions(options);
+ return stackIntercept(
+ 'serverStreaming',
+ this._transport,
+ method,
+ opt,
+ input,
+ );
+ }
}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.ts
index e0520375b..1ca74db92 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/ledger_service.ts
@@ -11,6 +11,12 @@
//
import { ServiceType } from '@protobuf-ts/runtime-rpc';
import { MessageType } from '@protobuf-ts/runtime';
+import { Event } from './event.js';
+import { EventFilter } from './filter.js';
+import { QueryEnd } from './query_options.js';
+import { Watermark } from './query_options.js';
+import { QueryOptions } from './query_options.js';
+import { TransactionFilter } from './filter.js';
import { Epoch } from './epoch.js';
import { Checkpoint } from './checkpoint.js';
import { ExecutedTransaction } from './executed_transaction.js';
@@ -320,6 +326,253 @@ export interface GetEpochResponse {
*/
epoch?: Epoch;
}
+/**
+ * Request message for LedgerService.ListCheckpoints.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListCheckpointsRequest
+ */
+export interface ListCheckpointsRequest {
+ /**
+ * Optional. Mask for specifying which parts of the Checkpoint should be
+ * returned (e.g. summary, contents, signatures).
+ *
+ * @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
+ */
+ readMask?: FieldMask;
+ /**
+ * Optional. Start of the checkpoint range to query (inclusive). Defaults to
+ * genesis.
+ *
+ * @generated from protobuf field: optional uint64 start_checkpoint = 2;
+ */
+ startCheckpoint?: bigint;
+ /**
+ * Optional. End of the checkpoint range to query (exclusive). Defaults to the
+ * current indexed ledger tip.
+ *
+ * @generated from protobuf field: optional uint64 end_checkpoint = 3;
+ */
+ endCheckpoint?: bigint;
+ /**
+ * Optional. DNF filter over indexed transaction dimensions. A checkpoint
+ * matches if any transaction it contains satisfies the filter. If absent,
+ * all checkpoints in the range are returned.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.TransactionFilter filter = 4;
+ */
+ filter?: TransactionFilter;
+ /**
+ * Optional cursor-bounded query options. If unspecified, reads in ascending
+ * order with the default item limit. The server enforces a maximum item
+ * limit and silently coerces larger values down to it. To paginate, pass
+ * the last received `Watermark.cursor` as `options.after` (ascending) or
+ * `options.before` (descending) on the next request.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryOptions options = 5;
+ */
+ options?: QueryOptions;
+}
+/**
+ * Response message for LedgerService.ListCheckpoints.
+ *
+ * Every frame carries a `watermark` with a safe resume cursor. A frame
+ * with `checkpoint` set delivers one matching item; a frame without it reports
+ * scan progress or terminal completion. Watermarks never regress in the
+ * requested ordering but may repeat.
+ *
+ * `end` is set exactly once, on the final frame of a successful stream. For
+ * `QUERY_END_REASON_ITEM_LIMIT`, that frame also carries the final item. For
+ * every other end reason, the final frame has no `checkpoint` payload.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListCheckpointsResponse
+ */
+export interface ListCheckpointsResponse {
+ /**
+ * One matching checkpoint.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Checkpoint checkpoint = 1;
+ */
+ checkpoint?: Checkpoint;
+ /**
+ * Progress watermark as of this frame. Present on every frame. A
+ * ScanLimit terminal watermark may repeat the previous frame's cursor when
+ * its authoritative scan frontier was already emitted.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Watermark watermark = 2;
+ */
+ watermark?: Watermark;
+ /**
+ * Set exactly once, on the final frame of a successful query stream.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryEnd end = 3;
+ */
+ end?: QueryEnd;
+}
+/**
+ * Request message for LedgerService.ListTransactions.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListTransactionsRequest
+ */
+export interface ListTransactionsRequest {
+ /**
+ * Optional. Mask for specifying which parts of the ExecutedTransaction
+ * should be returned.
+ *
+ * @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
+ */
+ readMask?: FieldMask;
+ /**
+ * Optional. Start of the checkpoint range to query (inclusive). Defaults to
+ * genesis.
+ *
+ * @generated from protobuf field: optional uint64 start_checkpoint = 2;
+ */
+ startCheckpoint?: bigint;
+ /**
+ * Optional. End of the checkpoint range to query (exclusive). Defaults to the
+ * current indexed ledger tip.
+ *
+ * @generated from protobuf field: optional uint64 end_checkpoint = 3;
+ */
+ endCheckpoint?: bigint;
+ /**
+ * Optional. DNF filter over indexed dimensions.
+ * If absent, all transactions in the range are returned.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.TransactionFilter filter = 4;
+ */
+ filter?: TransactionFilter;
+ /**
+ * Optional cursor-bounded query options. If unspecified, reads in ascending
+ * order with the default item limit. The server enforces a maximum item
+ * limit and silently coerces larger values down to it. To paginate, pass
+ * the last received `Watermark.cursor` as `options.after` (ascending) or
+ * `options.before` (descending) on the next request.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryOptions options = 5;
+ */
+ options?: QueryOptions;
+}
+/**
+ * Response message for LedgerService.ListTransactions.
+ *
+ * Every frame carries a `watermark` with a safe resume cursor. A frame
+ * with `transaction` set delivers one matching item; a frame without it reports
+ * scan progress or terminal completion. Watermarks never regress in the
+ * requested ordering but may repeat.
+ *
+ * `end` is set exactly once, on the final frame of a successful stream. For
+ * `QUERY_END_REASON_ITEM_LIMIT`, that frame also carries the final item. For
+ * every other end reason, the final frame has no `transaction` payload.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListTransactionsResponse
+ */
+export interface ListTransactionsResponse {
+ /**
+ * One matching transaction. Its position within the containing checkpoint
+ * is reported by `ExecutedTransaction.transaction_index`.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.ExecutedTransaction transaction = 1;
+ */
+ transaction?: ExecutedTransaction;
+ /**
+ * Progress watermark as of this frame. Present on every frame. A
+ * ScanLimit terminal watermark may repeat the previous frame's cursor when
+ * its authoritative scan frontier was already emitted.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Watermark watermark = 2;
+ */
+ watermark?: Watermark;
+ /**
+ * Set exactly once, on the final frame of a successful query stream.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryEnd end = 3;
+ */
+ end?: QueryEnd;
+}
+/**
+ * Request message for LedgerService.ListEvents.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListEventsRequest
+ */
+export interface ListEventsRequest {
+ /**
+ * Optional. Mask for specifying which parts of the Event should be returned.
+ *
+ * @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
+ */
+ readMask?: FieldMask;
+ /**
+ * Optional. Start of the checkpoint range to query (inclusive). Defaults to
+ * genesis.
+ *
+ * @generated from protobuf field: optional uint64 start_checkpoint = 2;
+ */
+ startCheckpoint?: bigint;
+ /**
+ * Optional. End of the checkpoint range to query (exclusive). Defaults to the
+ * current indexed ledger tip.
+ *
+ * @generated from protobuf field: optional uint64 end_checkpoint = 3;
+ */
+ endCheckpoint?: bigint;
+ /**
+ * Optional. DNF filter over indexed dimensions.
+ * If absent, all events in the range are returned.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.EventFilter filter = 4;
+ */
+ filter?: EventFilter;
+ /**
+ * Optional cursor-bounded query options. If unspecified, reads in ascending
+ * order with the default item limit. The server enforces a maximum item
+ * limit and silently coerces larger values down to it. To paginate, pass
+ * the last received `Watermark.cursor` as `options.after` (ascending) or
+ * `options.before` (descending) on the next request.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryOptions options = 5;
+ */
+ options?: QueryOptions;
+}
+/**
+ * Response message for LedgerService.ListEvents.
+ *
+ * Every frame carries a `watermark` with a safe resume cursor. A frame
+ * with `event` set delivers one matching item; a frame without it reports scan
+ * progress or terminal completion. Watermarks never regress in the requested
+ * ordering but may repeat.
+ *
+ * `end` is set exactly once, on the final frame of a successful stream. For
+ * `QUERY_END_REASON_ITEM_LIMIT`, that frame also carries the final item. For
+ * every other end reason, the final frame has no `event` payload.
+ *
+ * @generated from protobuf message sui.rpc.v2.ListEventsResponse
+ */
+export interface ListEventsResponse {
+ /**
+ * One matching event. Its ledger position -- containing checkpoint,
+ * emitting transaction digest and offset, and index within that
+ * transaction's event list -- is reported by the corresponding fields on
+ * `Event`.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Event event = 1;
+ */
+ event?: Event;
+ /**
+ * Progress watermark as of this frame. Present on every frame. A
+ * ScanLimit terminal watermark may repeat the previous frame's cursor when
+ * its authoritative scan frontier was already emitted.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Watermark watermark = 2;
+ */
+ watermark?: Watermark;
+ /**
+ * Set exactly once, on the final frame of a successful query stream.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryEnd end = 3;
+ */
+ end?: QueryEnd;
+}
// @generated message type with reflection information, may provide speed optimized methods
class GetServiceInfoRequest$Type extends MessageType {
constructor() {
@@ -606,6 +859,138 @@ class GetEpochResponse$Type extends MessageType {
* @generated MessageType for protobuf message sui.rpc.v2.GetEpochResponse
*/
export const GetEpochResponse = new GetEpochResponse$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListCheckpointsRequest$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListCheckpointsRequest', [
+ { no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ {
+ no: 2,
+ name: 'start_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ {
+ no: 3,
+ name: 'end_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ { no: 4, name: 'filter', kind: 'message', T: () => TransactionFilter },
+ { no: 5, name: 'options', kind: 'message', T: () => QueryOptions },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListCheckpointsRequest
+ */
+export const ListCheckpointsRequest = new ListCheckpointsRequest$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListCheckpointsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListCheckpointsResponse', [
+ { no: 1, name: 'checkpoint', kind: 'message', T: () => Checkpoint },
+ { no: 2, name: 'watermark', kind: 'message', T: () => Watermark },
+ { no: 3, name: 'end', kind: 'message', T: () => QueryEnd },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListCheckpointsResponse
+ */
+export const ListCheckpointsResponse = new ListCheckpointsResponse$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListTransactionsRequest$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListTransactionsRequest', [
+ { no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ {
+ no: 2,
+ name: 'start_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ {
+ no: 3,
+ name: 'end_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ { no: 4, name: 'filter', kind: 'message', T: () => TransactionFilter },
+ { no: 5, name: 'options', kind: 'message', T: () => QueryOptions },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListTransactionsRequest
+ */
+export const ListTransactionsRequest = new ListTransactionsRequest$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListTransactionsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListTransactionsResponse', [
+ { no: 1, name: 'transaction', kind: 'message', T: () => ExecutedTransaction },
+ { no: 2, name: 'watermark', kind: 'message', T: () => Watermark },
+ { no: 3, name: 'end', kind: 'message', T: () => QueryEnd },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListTransactionsResponse
+ */
+export const ListTransactionsResponse = new ListTransactionsResponse$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListEventsRequest$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListEventsRequest', [
+ { no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ {
+ no: 2,
+ name: 'start_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ {
+ no: 3,
+ name: 'end_checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ { no: 4, name: 'filter', kind: 'message', T: () => EventFilter },
+ { no: 5, name: 'options', kind: 'message', T: () => QueryOptions },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListEventsRequest
+ */
+export const ListEventsRequest = new ListEventsRequest$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class ListEventsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.ListEventsResponse', [
+ { no: 1, name: 'event', kind: 'message', T: () => Event },
+ { no: 2, name: 'watermark', kind: 'message', T: () => Watermark },
+ { no: 3, name: 'end', kind: 'message', T: () => QueryEnd },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.ListEventsResponse
+ */
+export const ListEventsResponse = new ListEventsResponse$Type();
/**
* @generated ServiceType for protobuf service sui.rpc.v2.LedgerService
*/
@@ -622,4 +1007,25 @@ export const LedgerService = new ServiceType('sui.rpc.v2.LedgerService', [
},
{ name: 'GetCheckpoint', options: {}, I: GetCheckpointRequest, O: GetCheckpointResponse },
{ name: 'GetEpoch', options: {}, I: GetEpochRequest, O: GetEpochResponse },
+ {
+ name: 'ListCheckpoints',
+ serverStreaming: true,
+ options: {},
+ I: ListCheckpointsRequest,
+ O: ListCheckpointsResponse,
+ },
+ {
+ name: 'ListTransactions',
+ serverStreaming: true,
+ options: {},
+ I: ListTransactionsRequest,
+ O: ListTransactionsResponse,
+ },
+ {
+ name: 'ListEvents',
+ serverStreaming: true,
+ options: {},
+ I: ListEventsRequest,
+ O: ListEventsResponse,
+ },
]);
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/protocol_config.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/protocol_config.ts
index 1c9db5a72..807a098e4 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/protocol_config.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/protocol_config.ts
@@ -10,6 +10,7 @@
// SPDX-License-Identifier: Apache-2.0
//
import { MessageType } from '@protobuf-ts/runtime';
+import { Value } from '../../../google/protobuf/struct.js';
/**
* @generated from protobuf message sui.rpc.v2.ProtocolConfig
*/
@@ -19,17 +20,29 @@ export interface ProtocolConfig {
*/
protocolVersion?: bigint;
/**
- * @generated from protobuf field: map feature_flags = 2;
+ * Deprecated in favor of the lossless `configs` field.
+ *
+ * @deprecated
+ * @generated from protobuf field: map feature_flags = 2 [deprecated = true];
*/
featureFlags: {
[key: string]: boolean;
};
/**
- * @generated from protobuf field: map attributes = 3;
+ * Deprecated in favor of the lossless `configs` field.
+ *
+ * @deprecated
+ * @generated from protobuf field: map attributes = 3 [deprecated = true];
*/
attributes: {
[key: string]: string;
};
+ /**
+ * @generated from protobuf field: map configs = 4;
+ */
+ configs: {
+ [key: string]: Value;
+ };
}
// @generated message type with reflection information, may provide speed optimized methods
class ProtocolConfig$Type extends MessageType {
@@ -57,6 +70,13 @@ class ProtocolConfig$Type extends MessageType {
K: 9 /*ScalarType.STRING*/,
V: { kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
},
+ {
+ no: 4,
+ name: 'configs',
+ kind: 'map',
+ K: 9 /*ScalarType.STRING*/,
+ V: { kind: 'message', T: () => Value },
+ },
]);
}
}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/query_options.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/query_options.ts
new file mode 100644
index 000000000..1ac1374ae
--- /dev/null
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/query_options.ts
@@ -0,0 +1,255 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+// @generated by protobuf-ts 2.9.6 with parameter force_server_none,optimize_code_size,ts_nocheck
+// @generated from protobuf file "sui/rpc/v2/query_options.proto" (package "sui.rpc.v2", syntax proto3)
+// tslint:disable
+// @ts-nocheck
+//
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+//
+import { MessageType } from '@protobuf-ts/runtime';
+/**
+ * Cursor-bounded query options.
+ *
+ * `after` and `before` are canonical ledger-position bounds, not
+ * ordering-relative cursors. `after` always excludes items at or below that
+ * cursor, and `before` always excludes items at or above that cursor. Ordering
+ * only controls the order of returned items within the resulting open interval.
+ *
+ * When a request also specifies a checkpoint range, cursor bounds and
+ * checkpoint bounds compose by intersection: results come only from ledger
+ * positions inside both. Checkpoint bounds are likewise canonical and
+ * ordering-independent.
+ *
+ * For example, with `after = A`, `before = B`, `ordering = DESCENDING`, and
+ * `limit = N`, the response contains up to N matching items in descending
+ * order from the interval `(A, B)`. If the response ends with
+ * `QUERY_END_REASON_ITEM_LIMIT`, resume by keeping `after = A` and setting
+ * `before` to the last `Watermark.cursor` received. That cursor is the
+ * lowest position reached in ledger order, so it becomes the next exclusive
+ * upper bound.
+ *
+ * @generated from protobuf message sui.rpc.v2.QueryOptions
+ */
+export interface QueryOptions {
+ /**
+ * The maximum number of items to return. Each method applies its own default
+ * and maximum. QueryEnd does not count against this limit.
+ *
+ * @generated from protobuf field: optional uint32 limit = 1;
+ */
+ limit?: number;
+ /**
+ * Opaque exclusive lower bound. Results must be strictly after this cursor in
+ * canonical ledger order.
+ *
+ * @generated from protobuf field: optional bytes after = 2;
+ */
+ after?: Uint8Array;
+ /**
+ * Opaque exclusive upper bound. Results must be strictly before this cursor
+ * in canonical ledger order.
+ *
+ * @generated from protobuf field: optional bytes before = 3;
+ */
+ before?: Uint8Array;
+ /**
+ * Ordering for returned results. Defaults to ASCENDING.
+ *
+ * Ordering only controls the order of results within the bounded interval;
+ * cursor bounds keep the same meaning for ascending and descending reads.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Ordering ordering = 4;
+ */
+ ordering?: Ordering;
+}
+/**
+ * Progress marker for a query scan. Carried on every response frame, whether or
+ * not the frame delivers a matching item. Watermarks never regress in the
+ * requested ordering, but consecutive frames may carry the same watermark when
+ * additional work does not advance the safe resume frontier.
+ *
+ * @generated from protobuf message sui.rpc.v2.Watermark
+ */
+export interface Watermark {
+ /**
+ * Opaque cursor at this scan position. Set on every watermark. Use as
+ * `options.after` (ascending) or `options.before` (descending) on the next
+ * request to resume from here. The most recently received cursor is always
+ * the safe resume point.
+ *
+ * @generated from protobuf field: optional bytes cursor = 1;
+ */
+ cursor?: Uint8Array;
+ /**
+ * The inclusive boundary checkpoint that the scan has fully covered within
+ * the request's effective interval, in the request's ordering direction: an
+ * ascending scan has emitted every matching item in the interval at
+ * checkpoints `<= checkpoint` (strictly greater ones may still hold
+ * matches); a descending scan has emitted every matching item in the
+ * interval at checkpoints `>= checkpoint`. This boundary never regresses in
+ * the scan direction, but it may repeat while the cursor advances.
+ *
+ * Unset until the scan's first checkpoint is fully covered. For example, a
+ * scan resumed from a cursor that lands mid-checkpoint leaves this unset
+ * until the next checkpoint boundary in the scan direction is fully covered.
+ * A watermark still has a valid resume cursor while this field is unset.
+ *
+ * @generated from protobuf field: optional uint64 checkpoint = 2;
+ */
+ checkpoint?: bigint;
+}
+/**
+ * Marker for the final frame of a successful query stream. Every successful
+ * stream sets `QueryEnd` on exactly one frame, after which no further frames
+ * are sent. That frame always carries the final watermark. For `ItemLimit`, it
+ * also carries the final matching item; for every other reason it carries no
+ * item. A ScanLimit terminal watermark may repeat the previous frame's cursor
+ * when its authoritative scan frontier was already emitted; this does not
+ * repeat an item.
+ *
+ * A stream that fails or is cancelled terminates with a gRPC status and does
+ * not send `QueryEnd`.
+ *
+ * @generated from protobuf message sui.rpc.v2.QueryEnd
+ */
+export interface QueryEnd {
+ /**
+ * Reason this response stopped.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.QueryEndReason reason = 1;
+ */
+ reason?: QueryEndReason;
+}
+/**
+ * Ordering for the returned result set.
+ *
+ * @generated from protobuf enum sui.rpc.v2.Ordering
+ */
+export enum Ordering {
+ /**
+ * Return results in increasing cursor order.
+ *
+ * @generated from protobuf enum value: ORDERING_ASCENDING = 0;
+ */
+ ASCENDING = 0,
+ /**
+ * Return results in decreasing cursor order.
+ *
+ * @generated from protobuf enum value: ORDERING_DESCENDING = 1;
+ */
+ DESCENDING = 1,
+}
+/**
+ * Reason the server stopped this query response.
+ *
+ * @generated from protobuf enum sui.rpc.v2.QueryEndReason
+ */
+export enum QueryEndReason {
+ /**
+ * The stop reason was not specified.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_UNKNOWN = 0;
+ */
+ UNKNOWN = 0,
+ /**
+ * The response reached the requested item limit. The final matching item's
+ * frame carries `QueryEnd` and the final watermark. Resume from that frame's
+ * `Watermark.cursor` to continue reading the same effective interval.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_ITEM_LIMIT = 1;
+ */
+ ITEM_LIMIT = 1,
+ /**
+ * The response reached the server's per-request bucket-fetch budget for
+ * filtered scans before reaching the effective interval bound. The terminal
+ * frame carries no item. Its watermark cursor is the authoritative scan
+ * frontier from which to resume.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_SCAN_LIMIT = 2;
+ */
+ SCAN_LIMIT = 2,
+ /**
+ * The scan reached a requested checkpoint range bound. The terminal frame
+ * carries no item.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_CHECKPOINT_BOUND = 3;
+ */
+ CHECKPOINT_BOUND = 3,
+ /**
+ * The scan reached an exclusive cursor bound. The terminal frame carries no
+ * item. Its watermark cursor represents that resolved bound without claiming
+ * that its containing checkpoint was fully covered.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_CURSOR_BOUND = 4;
+ */
+ CURSOR_BOUND = 4,
+ /**
+ * The scan reached the currently indexed ledger tip. The terminal frame
+ * carries no item.
+ *
+ * @generated from protobuf enum value: QUERY_END_REASON_LEDGER_TIP = 5;
+ */
+ LEDGER_TIP = 5,
+}
+// @generated message type with reflection information, may provide speed optimized methods
+class QueryOptions$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.QueryOptions', [
+ { no: 1, name: 'limit', kind: 'scalar', opt: true, T: 13 /*ScalarType.UINT32*/ },
+ { no: 2, name: 'after', kind: 'scalar', opt: true, T: 12 /*ScalarType.BYTES*/ },
+ { no: 3, name: 'before', kind: 'scalar', opt: true, T: 12 /*ScalarType.BYTES*/ },
+ {
+ no: 4,
+ name: 'ordering',
+ kind: 'enum',
+ opt: true,
+ T: () => ['sui.rpc.v2.Ordering', Ordering, 'ORDERING_'],
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.QueryOptions
+ */
+export const QueryOptions = new QueryOptions$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class Watermark$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.Watermark', [
+ { no: 1, name: 'cursor', kind: 'scalar', opt: true, T: 12 /*ScalarType.BYTES*/ },
+ {
+ no: 2,
+ name: 'checkpoint',
+ kind: 'scalar',
+ opt: true,
+ T: 4 /*ScalarType.UINT64*/,
+ L: 0 /*LongType.BIGINT*/,
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.Watermark
+ */
+export const Watermark = new Watermark$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class QueryEnd$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.QueryEnd', [
+ {
+ no: 1,
+ name: 'reason',
+ kind: 'enum',
+ opt: true,
+ T: () => ['sui.rpc.v2.QueryEndReason', QueryEndReason, 'QUERY_END_REASON_'],
+ },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.QueryEnd
+ */
+export const QueryEnd = new QueryEnd$Type();
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.client.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.client.ts
index f84cd81b9..21c617918 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.client.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.client.ts
@@ -12,27 +12,54 @@
import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
import type { ServiceInfo } from '@protobuf-ts/runtime-rpc';
import { SubscriptionService } from './subscription_service.js';
+import type { SubscribeEventsResponse } from './subscription_service.js';
+import type { SubscribeEventsRequest } from './subscription_service.js';
+import type { SubscribeTransactionsResponse } from './subscription_service.js';
+import type { SubscribeTransactionsRequest } from './subscription_service.js';
import { stackIntercept } from '@protobuf-ts/runtime-rpc';
import type { SubscribeCheckpointsResponse } from './subscription_service.js';
import type { SubscribeCheckpointsRequest } from './subscription_service.js';
import type { ServerStreamingCall } from '@protobuf-ts/runtime-rpc';
import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
/**
+ * SubscriptionService provides filtered, real-time streams of checkpoints,
+ * transactions, and events.
+ *
+ * Each Subscribe API pairs with the LedgerService List API of the same name:
+ * requests take the same filter message, and responses carry the same item
+ * and watermark shapes with identical cursor semantics.
+ *
+ * Subscriptions do not support resumption. A new subscription always begins
+ * at the current tip of the chain as seen by the server (the latest executed
+ * checkpoint). To recover data missed between subscriptions, replay the gap
+ * with the paired List API: pass the last received `Watermark.cursor` as
+ * `options.after` on the List request (for checkpoints, pass the last
+ * received `cursor + 1` as `start_checkpoint`). The List scan reads from the
+ * indexed tip, which may trail the subscription's start position; repeat the
+ * List call as the index advances until the replay reaches the position
+ * established by the subscription's first frame.
+ *
+ * A subscription behaves like an unbounded ascending scan: every frame
+ * carries the subscriber's resume point, and progress advances as
+ * checkpoints are fully covered. Two delivery guarantees keep sparse
+ * filters live: the first frame on a filtered subscription is a
+ * progress-only frame establishing the stream's start position, and
+ * progress continues to advance with bounded staleness even when no item
+ * matches.
+ *
+ * Subscription streams have no successful end: they run until cancelled by
+ * the client or terminated by the server with a gRPC status.
+ *
* @generated from protobuf service sui.rpc.v2.SubscriptionService
*/
export interface ISubscriptionServiceClient {
/**
* Subscribe to the stream of checkpoints.
*
- * This API provides a subscription to the checkpoint stream for the Sui
- * blockchain. When a subscription is initialized the stream will begin with
- * the latest executed checkpoint as seen by the server. Responses are
- * guaranteed to return checkpoints in-order and without gaps. This enables
- * clients to know exactly the last checkpoint they have processed and in the
- * event the subscription terminates (either by the client/server or by the
- * connection breaking), clients will be able to reinitialize a subscription
- * and then leverage other APIs in order to request data for the checkpoints
- * they missed.
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields checkpoints matching the filter as they are executed.
+ * A checkpoint matches if any transaction it contains satisfies the
+ * filter.
*
* @generated from protobuf rpc: SubscribeCheckpoints(sui.rpc.v2.SubscribeCheckpointsRequest) returns (stream sui.rpc.v2.SubscribeCheckpointsResponse);
*/
@@ -40,8 +67,60 @@ export interface ISubscriptionServiceClient {
input: SubscribeCheckpointsRequest,
options?: RpcOptions,
): ServerStreamingCall;
+ /**
+ * Subscribe to the stream of transactions.
+ *
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields transactions matching the filter as they are executed.
+ *
+ * @generated from protobuf rpc: SubscribeTransactions(sui.rpc.v2.SubscribeTransactionsRequest) returns (stream sui.rpc.v2.SubscribeTransactionsResponse);
+ */
+ subscribeTransactions(
+ input: SubscribeTransactionsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall;
+ /**
+ * Subscribe to the stream of events.
+ *
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields events matching the filter as they are emitted.
+ *
+ * @generated from protobuf rpc: SubscribeEvents(sui.rpc.v2.SubscribeEventsRequest) returns (stream sui.rpc.v2.SubscribeEventsResponse);
+ */
+ subscribeEvents(
+ input: SubscribeEventsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall;
}
/**
+ * SubscriptionService provides filtered, real-time streams of checkpoints,
+ * transactions, and events.
+ *
+ * Each Subscribe API pairs with the LedgerService List API of the same name:
+ * requests take the same filter message, and responses carry the same item
+ * and watermark shapes with identical cursor semantics.
+ *
+ * Subscriptions do not support resumption. A new subscription always begins
+ * at the current tip of the chain as seen by the server (the latest executed
+ * checkpoint). To recover data missed between subscriptions, replay the gap
+ * with the paired List API: pass the last received `Watermark.cursor` as
+ * `options.after` on the List request (for checkpoints, pass the last
+ * received `cursor + 1` as `start_checkpoint`). The List scan reads from the
+ * indexed tip, which may trail the subscription's start position; repeat the
+ * List call as the index advances until the replay reaches the position
+ * established by the subscription's first frame.
+ *
+ * A subscription behaves like an unbounded ascending scan: every frame
+ * carries the subscriber's resume point, and progress advances as
+ * checkpoints are fully covered. Two delivery guarantees keep sparse
+ * filters live: the first frame on a filtered subscription is a
+ * progress-only frame establishing the stream's start position, and
+ * progress continues to advance with bounded staleness even when no item
+ * matches.
+ *
+ * Subscription streams have no successful end: they run until cancelled by
+ * the client or terminated by the server with a gRPC status.
+ *
* @generated from protobuf service sui.rpc.v2.SubscriptionService
*/
export class SubscriptionServiceClient implements ISubscriptionServiceClient, ServiceInfo {
@@ -52,15 +131,10 @@ export class SubscriptionServiceClient implements ISubscriptionServiceClient, Se
/**
* Subscribe to the stream of checkpoints.
*
- * This API provides a subscription to the checkpoint stream for the Sui
- * blockchain. When a subscription is initialized the stream will begin with
- * the latest executed checkpoint as seen by the server. Responses are
- * guaranteed to return checkpoints in-order and without gaps. This enables
- * clients to know exactly the last checkpoint they have processed and in the
- * event the subscription terminates (either by the client/server or by the
- * connection breaking), clients will be able to reinitialize a subscription
- * and then leverage other APIs in order to request data for the checkpoints
- * they missed.
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields checkpoints matching the filter as they are executed.
+ * A checkpoint matches if any transaction it contains satisfies the
+ * filter.
*
* @generated from protobuf rpc: SubscribeCheckpoints(sui.rpc.v2.SubscribeCheckpointsRequest) returns (stream sui.rpc.v2.SubscribeCheckpointsResponse);
*/
@@ -78,4 +152,48 @@ export class SubscriptionServiceClient implements ISubscriptionServiceClient, Se
input,
);
}
+ /**
+ * Subscribe to the stream of transactions.
+ *
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields transactions matching the filter as they are executed.
+ *
+ * @generated from protobuf rpc: SubscribeTransactions(sui.rpc.v2.SubscribeTransactionsRequest) returns (stream sui.rpc.v2.SubscribeTransactionsResponse);
+ */
+ subscribeTransactions(
+ input: SubscribeTransactionsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall {
+ const method = this.methods[1],
+ opt = this._transport.mergeOptions(options);
+ return stackIntercept(
+ 'serverStreaming',
+ this._transport,
+ method,
+ opt,
+ input,
+ );
+ }
+ /**
+ * Subscribe to the stream of events.
+ *
+ * The stream begins at the latest executed checkpoint as seen by the
+ * server and yields events matching the filter as they are emitted.
+ *
+ * @generated from protobuf rpc: SubscribeEvents(sui.rpc.v2.SubscribeEventsRequest) returns (stream sui.rpc.v2.SubscribeEventsResponse);
+ */
+ subscribeEvents(
+ input: SubscribeEventsRequest,
+ options?: RpcOptions,
+ ): ServerStreamingCall {
+ const method = this.methods[2],
+ opt = this._transport.mergeOptions(options);
+ return stackIntercept(
+ 'serverStreaming',
+ this._transport,
+ method,
+ opt,
+ input,
+ );
+ }
}
diff --git a/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.ts b/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.ts
index 3af8258d2..ceef1fae6 100644
--- a/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.ts
+++ b/packages/sui/src/grpc/proto/sui/rpc/v2/subscription_service.ts
@@ -11,47 +11,161 @@
//
import { ServiceType } from '@protobuf-ts/runtime-rpc';
import { MessageType } from '@protobuf-ts/runtime';
+import { Event } from './event.js';
+import { EventFilter } from './filter.js';
+import { Watermark } from './query_options.js';
+import { ExecutedTransaction } from './executed_transaction.js';
import { Checkpoint } from './checkpoint.js';
+import { TransactionFilter } from './filter.js';
import { FieldMask } from '../../../google/protobuf/field_mask.js';
/**
- * Request message for SubscriptionService.SubscribeCheckpoints
+ * Request message for SubscriptionService.SubscribeCheckpoints.
*
* @generated from protobuf message sui.rpc.v2.SubscribeCheckpointsRequest
*/
export interface SubscribeCheckpointsRequest {
/**
- * Optional. Mask for specifying which parts of the
- * SubscribeCheckpointsResponse should be returned.
+ * Optional. Mask for specifying which parts of the Checkpoint should be
+ * returned (e.g. summary, contents, signatures). `cursor` is always
+ * populated and is not subject to the mask.
*
* @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
*/
readMask?: FieldMask;
+ /**
+ * Optional. DNF filter over indexed transaction dimensions. A checkpoint
+ * matches if any transaction it contains satisfies the filter. If absent,
+ * every checkpoint is streamed.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.TransactionFilter filter = 2;
+ */
+ filter?: TransactionFilter;
}
/**
- * Response message for SubscriptionService.SubscribeCheckpoints
+ * Response message for SubscriptionService.SubscribeCheckpoints.
+ *
+ * A checkpoint stream's position is checkpoint-granular, so the `cursor`
+ * sequence number stands in for the `Watermark` message the other
+ * subscription responses carry. Progress-only frames (with `checkpoint`
+ * unset) occur only on filtered streams: on an unfiltered stream every frame
+ * carries both fields, in order and without gaps.
*
* @generated from protobuf message sui.rpc.v2.SubscribeCheckpointsResponse
*/
export interface SubscribeCheckpointsResponse {
/**
- * Required. The checkpoint sequence number and value of the current cursor
- * into the checkpoint stream
+ * Required. The checkpoint sequence number the stream has fully covered,
+ * inclusive: every matching checkpoint from the stream's start position
+ * through `cursor` has been delivered. Present on every frame and
+ * advances monotonically.
*
* @generated from protobuf field: optional uint64 cursor = 1;
*/
cursor?: bigint;
/**
- * The requested data for this checkpoint
+ * The matching checkpoint. Unset when this frame only advances the
+ * cursor past non-matching checkpoints.
*
* @generated from protobuf field: optional sui.rpc.v2.Checkpoint checkpoint = 2;
*/
checkpoint?: Checkpoint;
}
+/**
+ * Request message for SubscriptionService.SubscribeTransactions.
+ *
+ * @generated from protobuf message sui.rpc.v2.SubscribeTransactionsRequest
+ */
+export interface SubscribeTransactionsRequest {
+ /**
+ * Optional. Mask for specifying which parts of the ExecutedTransaction
+ * should be returned.
+ *
+ * @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
+ */
+ readMask?: FieldMask;
+ /**
+ * Optional. DNF filter over indexed dimensions. If absent, every
+ * transaction is streamed.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.TransactionFilter filter = 2;
+ */
+ filter?: TransactionFilter;
+}
+/**
+ * Response message for SubscriptionService.SubscribeTransactions.
+ *
+ * Mirrors ListTransactionsResponse, except there is no `end` field: a
+ * subscription stream has no successful end.
+ *
+ * @generated from protobuf message sui.rpc.v2.SubscribeTransactionsResponse
+ */
+export interface SubscribeTransactionsResponse {
+ /**
+ * One matching transaction. Its position within the containing checkpoint
+ * is reported by `ExecutedTransaction.transaction_index`.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.ExecutedTransaction transaction = 1;
+ */
+ transaction?: ExecutedTransaction;
+ /**
+ * Progress watermark as of this frame. Present on every frame.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Watermark watermark = 2;
+ */
+ watermark?: Watermark;
+}
+/**
+ * Request message for SubscriptionService.SubscribeEvents.
+ *
+ * @generated from protobuf message sui.rpc.v2.SubscribeEventsRequest
+ */
+export interface SubscribeEventsRequest {
+ /**
+ * Optional. Mask for specifying which parts of the Event should be
+ * returned.
+ *
+ * @generated from protobuf field: optional google.protobuf.FieldMask read_mask = 1;
+ */
+ readMask?: FieldMask;
+ /**
+ * Optional. DNF filter over indexed dimensions. If absent, every event is
+ * streamed.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.EventFilter filter = 2;
+ */
+ filter?: EventFilter;
+}
+/**
+ * Response message for SubscriptionService.SubscribeEvents.
+ *
+ * Mirrors ListEventsResponse, except there is no `end` field: a subscription
+ * stream has no successful end.
+ *
+ * @generated from protobuf message sui.rpc.v2.SubscribeEventsResponse
+ */
+export interface SubscribeEventsResponse {
+ /**
+ * One matching event. Its ledger position -- containing checkpoint,
+ * emitting transaction digest and offset, and index within that
+ * transaction's event list -- is reported by the corresponding fields on
+ * `Event`.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Event event = 1;
+ */
+ event?: Event;
+ /**
+ * Progress watermark as of this frame. Present on every frame.
+ *
+ * @generated from protobuf field: optional sui.rpc.v2.Watermark watermark = 2;
+ */
+ watermark?: Watermark;
+}
// @generated message type with reflection information, may provide speed optimized methods
class SubscribeCheckpointsRequest$Type extends MessageType {
constructor() {
super('sui.rpc.v2.SubscribeCheckpointsRequest', [
{ no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ { no: 2, name: 'filter', kind: 'message', T: () => TransactionFilter },
]);
}
}
@@ -79,6 +193,58 @@ class SubscribeCheckpointsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.SubscribeTransactionsRequest', [
+ { no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ { no: 2, name: 'filter', kind: 'message', T: () => TransactionFilter },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.SubscribeTransactionsRequest
+ */
+export const SubscribeTransactionsRequest = new SubscribeTransactionsRequest$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class SubscribeTransactionsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.SubscribeTransactionsResponse', [
+ { no: 1, name: 'transaction', kind: 'message', T: () => ExecutedTransaction },
+ { no: 2, name: 'watermark', kind: 'message', T: () => Watermark },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.SubscribeTransactionsResponse
+ */
+export const SubscribeTransactionsResponse = new SubscribeTransactionsResponse$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class SubscribeEventsRequest$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.SubscribeEventsRequest', [
+ { no: 1, name: 'read_mask', kind: 'message', T: () => FieldMask },
+ { no: 2, name: 'filter', kind: 'message', T: () => EventFilter },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.SubscribeEventsRequest
+ */
+export const SubscribeEventsRequest = new SubscribeEventsRequest$Type();
+// @generated message type with reflection information, may provide speed optimized methods
+class SubscribeEventsResponse$Type extends MessageType {
+ constructor() {
+ super('sui.rpc.v2.SubscribeEventsResponse', [
+ { no: 1, name: 'event', kind: 'message', T: () => Event },
+ { no: 2, name: 'watermark', kind: 'message', T: () => Watermark },
+ ]);
+ }
+}
+/**
+ * @generated MessageType for protobuf message sui.rpc.v2.SubscribeEventsResponse
+ */
+export const SubscribeEventsResponse = new SubscribeEventsResponse$Type();
/**
* @generated ServiceType for protobuf service sui.rpc.v2.SubscriptionService
*/
@@ -90,4 +256,18 @@ export const SubscriptionService = new ServiceType('sui.rpc.v2.SubscriptionServi
I: SubscribeCheckpointsRequest,
O: SubscribeCheckpointsResponse,
},
+ {
+ name: 'SubscribeTransactions',
+ serverStreaming: true,
+ options: {},
+ I: SubscribeTransactionsRequest,
+ O: SubscribeTransactionsResponse,
+ },
+ {
+ name: 'SubscribeEvents',
+ serverStreaming: true,
+ options: {},
+ I: SubscribeEventsRequest,
+ O: SubscribeEventsResponse,
+ },
]);
diff --git a/packages/sui/src/grpc/proto/types.ts b/packages/sui/src/grpc/proto/types.ts
index 7fab4f7c9..85e287be3 100644
--- a/packages/sui/src/grpc/proto/types.ts
+++ b/packages/sui/src/grpc/proto/types.ts
@@ -23,6 +23,7 @@ export * from './sui/rpc/v2/error_reason.js';
export * from './sui/rpc/v2/event.js';
export * from './sui/rpc/v2/executed_transaction.js';
export * from './sui/rpc/v2/execution_status.js';
+export * from './sui/rpc/v2/filter.js';
export * from './sui/rpc/v2/gas_cost_summary.js';
export * from './sui/rpc/v2/input.js';
export * from './sui/rpc/v2/jwk.js';
@@ -34,6 +35,7 @@ export * from './sui/rpc/v2/object.js';
export * from './sui/rpc/v2/object_reference.js';
export * from './sui/rpc/v2/owner.js';
export * from './sui/rpc/v2/protocol_config.js';
+export * from './sui/rpc/v2/query_options.js';
export * from './sui/rpc/v2/signature.js';
export * from './sui/rpc/v2/signature_scheme.js';
export * from './sui/rpc/v2/signature_verification_service.js';
diff --git a/packages/sui/src/jsonRpc/core.ts b/packages/sui/src/jsonRpc/core.ts
index 0cf875a2e..9e2f3bb00 100644
--- a/packages/sui/src/jsonRpc/core.ts
+++ b/packages/sui/src/jsonRpc/core.ts
@@ -1,12 +1,13 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
-import { fromBase64, type InferBcsInput } from '@mysten/bcs';
+import { fromBase58, fromBase64, type InferBcsInput } from '@mysten/bcs';
import { bcs, TypeTagSerializer } from '../bcs/index.js';
import type {
DevInspectResults,
DryRunTransactionBlockResponse,
+ EventId,
ExecutionStatus as JsonRpcExecutionStatus,
ObjectOwner,
SuiMoveAbilitySet,
@@ -19,6 +20,12 @@ import type {
SuiTransactionBlockResponse,
TransactionEffects,
} from './types/index.js';
+import {
+ resolveEventFilter,
+ resolvePagination,
+ resolveTransactionFilter,
+ validateTransactionQuery,
+} from '../client/query-filters.js';
import { Transaction } from '../transactions/Transaction.js';
import { computeGasBudget, coreClientResolveTransactionPlugin } from '../client/core-resolver.js';
import { TransactionDataBuilder } from '../transactions/TransactionData.js';
@@ -650,6 +657,116 @@ export class JSONRpcCoreClient extends CoreClient {
};
}
+ /**
+ * @deprecated JSON-RPC APIs are deprecated in the Sui TypeScript SDK. Use `SuiGrpcClient`
+ * from `@mysten/sui/grpc` or `SuiGraphQLClient` from `@mysten/sui/graphql` instead.
+ */
+ async listTransactions(
+ options: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ const filter = options.filter
+ ? await resolveTransactionFilter(this.mvr, options.filter)
+ : undefined;
+
+ // Transaction cursors are digests, and bounds are interpreted relative to the
+ // traversal direction
+ const pagination = resolvePagination(options);
+ const { descending, after, before } = pagination;
+ validateTransactionQuery(filter, pagination);
+
+ const page = await this.#jsonRpcClient.queryTransactionBlocks({
+ filter:
+ filter &&
+ (filter.$kind === 'sender'
+ ? { FromAddress: filter.sender }
+ : {
+ MoveFunction: {
+ package: filter.package,
+ module: filter.module ?? null,
+ function: filter.function ?? null,
+ },
+ }),
+ cursor: after ?? before,
+ limit: pagination.limit,
+ order: descending ? 'descending' : 'ascending',
+ options: {
+ // showRawInput is always needed to extract signatures from SenderSignedData
+ showRawInput: true,
+ // showEffects is always needed to get status
+ showEffects: true,
+ showObjectChanges: options.include?.objectTypes ?? false,
+ showRawEffects: options.include?.effects ?? false,
+ showEvents: options.include?.events ?? false,
+ showBalanceChanges: options.include?.balanceChanges ?? false,
+ },
+ signal: options.signal,
+ });
+
+ return {
+ transactions: page.data.map((transaction) => parseTransaction(transaction, options.include)),
+ hasNextPage: page.hasNextPage,
+ startCursor: page.data[0]?.digest ?? null,
+ endCursor: page.data.length
+ ? (page.nextCursor ?? page.data[page.data.length - 1].digest)
+ : null,
+ };
+ }
+
+ /**
+ * @deprecated JSON-RPC APIs are deprecated in the Sui TypeScript SDK. Use `SuiGrpcClient`
+ * from `@mysten/sui/grpc` or `SuiGraphQLClient` from `@mysten/sui/graphql` instead.
+ */
+ async listEvents(
+ options: SuiClientTypes.ListEventsOptions,
+ ): Promise {
+ const filter = options.filter ? await resolveEventFilter(this.mvr, options.filter) : undefined;
+ // Event cursors are event ids, and bounds are interpreted relative to the
+ // traversal direction
+ const { descending, after, before, limit } = resolvePagination(options);
+ const cursor = after ?? before;
+
+ const page = await this.#jsonRpcClient.queryEvents({
+ query: !filter
+ ? { All: [] }
+ : filter.$kind === 'sender'
+ ? { Sender: filter.sender }
+ : filter.$kind === 'emitModule'
+ ? { MoveModule: { package: filter.package, module: filter.module } }
+ : filter.$kind === 'eventTypeModule'
+ ? { MoveEventModule: { package: filter.package, module: filter.module } }
+ : { MoveEventType: filter.eventType },
+ cursor: cursor ? parseEventCursor(cursor) : undefined,
+ limit,
+ order: descending ? 'descending' : 'ascending',
+ signal: options.signal,
+ });
+
+ return {
+ events: page.data.map(
+ (event): SuiClientTypes.EventEntry => ({
+ packageId: normalizeSuiAddress(event.packageId),
+ module: event.transactionModule,
+ sender: normalizeSuiAddress(event.sender),
+ eventType: normalizeStructTag(event.type),
+ bcs: event.bcsEncoding === 'base58' ? fromBase58(event.bcs) : fromBase64(event.bcs),
+ json: (event.parsedJson as Record) ?? null,
+ // queryEvents responses do not include checkpoint information
+ checkpoint: null,
+ transactionDigest: event.id.txDigest,
+ eventIndex: Number(event.id.eventSeq),
+ }),
+ ),
+ hasNextPage: page.hasNextPage,
+ startCursor: page.data.length ? JSON.stringify(page.data[0].id) : null,
+ endCursor:
+ page.data.length && page.nextCursor
+ ? JSON.stringify(page.nextCursor)
+ : page.data.length
+ ? JSON.stringify(page.data[page.data.length - 1].id)
+ : null,
+ };
+ }
+
/**
* @deprecated JSON-RPC APIs are deprecated in the Sui TypeScript SDK. Use `SuiGrpcClient`
* from `@mysten/sui/grpc` or `SuiGraphQLClient` from `@mysten/sui/graphql` instead.
@@ -948,6 +1065,18 @@ function parseOwnerAddress(owner: ObjectOwner): string | null {
throw new Error(`Unknown owner type: ${JSON.stringify(owner)}`);
}
+function parseEventCursor(cursor: string): EventId {
+ try {
+ const parsed = JSON.parse(cursor) as EventId;
+ if (typeof parsed?.txDigest !== 'string' || typeof parsed?.eventSeq !== 'string') {
+ throw new Error('malformed event cursor');
+ }
+ return parsed;
+ } catch {
+ throw new Error(`Invalid event cursor: ${cursor}`);
+ }
+}
+
function parseTransaction(
transaction: SuiTransactionBlockResponse,
include?: Include,
@@ -968,7 +1097,10 @@ function parseTransaction = ['grpc'];
+
+describe('Core API - Queries', () => {
+ let toolbox: TestToolbox;
+ let senderAddress: string;
+ let senderKeypair: Awaited>['keypair'];
+ let packageId: string;
+ let digests: string[];
+
+ const testWithAllClients = createTestWithAllClients(() => toolbox);
+
+ function stripCursors(
+ result: T,
+ ) {
+ return {
+ ...result,
+ startCursor: result.startCursor === null ? null : '',
+ endCursor: result.endCursor === null ? null : '',
+ };
+ }
+
+ function normalizeEvents(result: SuiClientTypes.ListEventsResponse) {
+ return {
+ ...stripCursors(result),
+ // JSON-RPC does not expose checkpoint information for queried events
+ events: result.events.map((event) => ({ ...event, checkpoint: null })),
+ };
+ }
+
+ beforeAll(async () => {
+ toolbox = await setup();
+
+ // Publish a fresh copy of the test package so package-scoped filters only
+ // match this file's transactions, keeping results deterministic while other
+ // test files execute transactions against the same localnet
+ const published = await publishPackage('shared/test_data', toolbox);
+ packageId = published.packageId;
+ await toolbox.waitForTransaction({ digest: published.publishTxn.digest });
+
+ const { keypair, address } = await toolbox.getSigner({ coins: [1_000_000_000n] });
+ senderAddress = address;
+ senderKeypair = keypair;
+
+ digests = [];
+ for (let i = 0; i < 3; i++) {
+ const tx = new Transaction();
+ tx.moveCall({
+ target: `${packageId}::test_objects::create_object_with_event`,
+ arguments: [tx.pure.u64(i)],
+ });
+
+ const result = await toolbox.signAndExecuteTransaction({
+ transaction: tx,
+ signer: keypair,
+ include: { effects: true },
+ });
+
+ if (result.$kind !== 'Transaction') {
+ throw new Error(
+ `Setup tx failed: ${result.FailedTransaction.status.error?.message ?? 'unknown error'}`,
+ );
+ }
+
+ digests.push(result.Transaction.digest);
+ }
+ });
+
+ describe('listTransactions', () => {
+ it('all clients return same data: no filter', async () => {
+ await toolbox.expectAllClientsReturnSameData(
+ (client) => client.core.listTransactions({ limit: 3 }),
+ stripCursors,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ it('all clients return same data: filter by sender with all includes', async () => {
+ await toolbox.expectAllClientsReturnSameData(
+ (client) =>
+ client.core.listTransactions({
+ filter: { sender: senderAddress },
+ limit: 10,
+ include: {
+ transaction: true,
+ effects: true,
+ events: true,
+ balanceChanges: true,
+ objectTypes: true,
+ },
+ }),
+ stripCursors,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ it('all clients return same data: filter by function at each specificity', async () => {
+ for (const fn of [
+ packageId,
+ `${packageId}::test_objects`,
+ `${packageId}::test_objects::create_object_with_event`,
+ ]) {
+ await toolbox.expectAllClientsReturnSameData(
+ (client) => client.core.listTransactions({ filter: { function: fn }, limit: 10 }),
+ stripCursors,
+ { exclude: EXCLUDE },
+ );
+ }
+
+ // Fully qualified function filters also support pagination bounds
+ await toolbox.expectAllClientsReturnSameData(
+ async (client) => {
+ const fn = `${packageId}::test_objects::create_object_with_event`;
+ const page = await client.core.listTransactions({ filter: { function: fn }, limit: 3 });
+ return client.core.listTransactions({
+ filter: { function: fn },
+ before: page.endCursor,
+ });
+ },
+ stripCursors,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ it('all clients return same data: descending order and pagination', async () => {
+ await toolbox.expectAllClientsReturnSameData(
+ async (client) => {
+ const firstPage = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ limit: 2,
+ order: 'descending',
+ });
+ const secondPage = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ limit: 2,
+ before: firstPage.endCursor,
+ });
+ return { firstPage: stripCursors(firstPage), secondPage: stripCursors(secondPage) };
+ },
+ undefined,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ testWithAllClients(
+ 'should list transactions filtered by sender',
+ async (client) => {
+ const result = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ include: { events: true },
+ });
+
+ expect(result.transactions.map((tx) => tx.Transaction?.digest)).toEqual(digests);
+ expect(result.hasNextPage).toBe(false);
+ expect(result.transactions[0].Transaction?.events?.length).toBeGreaterThan(0);
+ },
+ { skip: EXCLUDE },
+ );
+
+ testWithAllClients(
+ 'should paginate transactions',
+ async (client) => {
+ const firstPage = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ limit: 2,
+ });
+
+ expect(firstPage.transactions.map((tx) => tx.Transaction?.digest)).toEqual(
+ digests.slice(0, 2),
+ );
+ expect(firstPage.hasNextPage).toBe(true);
+
+ const secondPage = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ limit: 2,
+ after: firstPage.endCursor,
+ });
+
+ expect(secondPage.transactions.map((tx) => tx.Transaction?.digest)).toEqual(
+ digests.slice(2),
+ );
+ },
+ { skip: EXCLUDE },
+ );
+
+ testWithAllClients(
+ 'should list transactions in descending order',
+ async (client) => {
+ const result = await client.core.listTransactions({
+ filter: { sender: senderAddress },
+ order: 'descending',
+ });
+
+ expect(result.transactions.map((tx) => tx.Transaction?.digest)).toEqual(
+ [...digests].reverse(),
+ );
+ },
+ { skip: EXCLUDE },
+ );
+
+ it('all clients return same data: polling for new transactions', async () => {
+ const clients = (
+ [
+ ['jsonrpc', toolbox.jsonRpcClient],
+ ['grpc', toolbox.grpcClient],
+ ['graphql', toolbox.graphqlClient],
+ ] as const
+ ).filter(([kind]) => !EXCLUDE.includes(kind));
+
+ // Anchor at the most recent transaction from the sender on every client
+ const anchors = await Promise.all(
+ clients.map(([, client]) =>
+ client.core.listTransactions({
+ filter: { sender: senderAddress },
+ order: 'descending',
+ limit: 1,
+ }),
+ ),
+ );
+
+ // Execute a new transaction after the anchors were established
+ const tx = new Transaction();
+ const [coin] = tx.splitCoins(tx.gas, [1n]);
+ tx.transferObjects([coin], senderAddress);
+ const result = await toolbox.signAndExecuteTransaction({
+ transaction: tx,
+ signer: senderKeypair,
+ });
+
+ // Polling after each anchor's startCursor returns exactly the new transaction
+ const polls = await Promise.all(
+ clients.map(([, client], index) =>
+ client.core.listTransactions({
+ filter: { sender: senderAddress },
+ after: anchors[index].startCursor,
+ }),
+ ),
+ );
+
+ for (const poll of polls) {
+ expect(poll.transactions.map((transaction) => transaction.Transaction?.digest)).toEqual([
+ result.Transaction?.digest,
+ ]);
+ expect(poll.hasNextPage).toBe(false);
+ }
+ });
+
+ testWithAllClients('should reject conflicting pagination bounds', async (client) => {
+ await expect(client.core.listTransactions({ after: 'A', before: 'B' })).rejects.toThrow(
+ 'Only one of `after` or `before` may be provided',
+ );
+ await expect(
+ client.core.listTransactions({ after: 'A', order: 'descending' }),
+ ).rejects.toThrow('`after` can not be combined with descending queries');
+ await expect(
+ client.core.listTransactions({ before: 'B', order: 'ascending' }),
+ ).rejects.toThrow('`before` can not be combined with ascending queries');
+ });
+
+ testWithAllClients('should reject filters with multiple predicates', async (client) => {
+ await expect(
+ client.core.listTransactions({
+ filter: { sender: senderAddress, function: `${packageId}::test_objects` } as never,
+ }),
+ ).rejects.toThrow('exactly one of sender, function');
+ });
+
+ testWithAllClients('should reject paginated partial function filters', async (client) => {
+ await expect(
+ client.core.listTransactions({ filter: { function: packageId }, after: 'A' }),
+ ).rejects.toThrow('requires a fully qualified');
+ await expect(
+ client.core.listTransactions({
+ filter: { function: `${packageId}::test_objects` },
+ before: 'B',
+ }),
+ ).rejects.toThrow('requires a fully qualified');
+ });
+
+ testWithAllClients('should reject invalid function filters', async (client) => {
+ await expect(
+ client.core.listTransactions({ filter: { function: '0x2::a::b::c' } }),
+ ).rejects.toThrow('Invalid function filter');
+ });
+ });
+
+ describe('listEvents', () => {
+ it('all clients return same data: no filter', async () => {
+ await toolbox.expectAllClientsReturnSameData(
+ (client) => client.core.listEvents({ limit: 5 }),
+ normalizeEvents,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ it('all clients return same data: each filter predicate', async () => {
+ const filters: SuiClientTypes.EventFilter[] = [
+ { sender: senderAddress },
+ { emitModule: `${packageId}::test_objects` },
+ { eventType: `${packageId}::test_objects` },
+ { eventType: `${packageId}::test_objects::ObjectCreated` },
+ ];
+
+ for (const filter of filters) {
+ await toolbox.expectAllClientsReturnSameData(
+ (client) => client.core.listEvents({ filter, limit: 10 }),
+ normalizeEvents,
+ { exclude: EXCLUDE },
+ );
+ }
+ });
+
+ it('all clients return same data: descending order and pagination', async () => {
+ await toolbox.expectAllClientsReturnSameData(
+ async (client) => {
+ const firstPage = await client.core.listEvents({
+ filter: { sender: senderAddress },
+ limit: 2,
+ order: 'descending',
+ });
+ const secondPage = await client.core.listEvents({
+ filter: { sender: senderAddress },
+ limit: 2,
+ before: firstPage.endCursor,
+ });
+ return {
+ firstPage: normalizeEvents(firstPage),
+ secondPage: normalizeEvents(secondPage),
+ };
+ },
+ undefined,
+ { exclude: EXCLUDE },
+ );
+ });
+
+ it('all clients return same data: event checkpoints', async () => {
+ // JSON-RPC cannot provide checkpoint information for queried events, so
+ // checkpoint parity only applies to the other transports
+ await toolbox.expectAllClientsReturnSameData(
+ async (client) => {
+ const result = await client.core.listEvents({
+ filter: { sender: senderAddress },
+ limit: 10,
+ });
+ return result.events.map((event) => event.checkpoint);
+ },
+ undefined,
+ { exclude: [...EXCLUDE, 'jsonrpc'] },
+ );
+ });
+
+ testWithAllClients(
+ 'should list events with ledger positions',
+ async (client, kind) => {
+ const result = await client.core.listEvents({
+ filter: { eventType: `${packageId}::test_objects::ObjectCreated` },
+ });
+
+ expect(result.events.length).toBe(digests.length);
+ expect(result.events.map((event) => event.transactionDigest)).toEqual(digests);
+
+ for (const event of result.events) {
+ expect(event.packageId).toBe(packageId);
+ expect(event.module).toBe('test_objects');
+ expect(event.sender).toBe(senderAddress);
+ expect(event.eventType).toBe(`${packageId}::test_objects::ObjectCreated`);
+ expect(event.bcs).toBeInstanceOf(Uint8Array);
+ expect(event.eventIndex).toBe(0);
+
+ if (kind === 'jsonrpc') {
+ expect(event.checkpoint).toBeNull();
+ } else {
+ expect(event.checkpoint).toMatch(/^\d+$/);
+ }
+ }
+ },
+ { skip: EXCLUDE },
+ );
+
+ testWithAllClients(
+ 'should paginate events in descending order',
+ async (client) => {
+ const firstPage = await client.core.listEvents({
+ filter: { sender: senderAddress },
+ limit: 2,
+ order: 'descending',
+ });
+
+ expect(firstPage.events.map((event) => event.transactionDigest)).toEqual(
+ [...digests].reverse().slice(0, 2),
+ );
+ expect(firstPage.hasNextPage).toBe(true);
+
+ const secondPage = await client.core.listEvents({
+ filter: { sender: senderAddress },
+ limit: 2,
+ before: firstPage.endCursor,
+ });
+
+ expect(secondPage.events.map((event) => event.transactionDigest)).toEqual(
+ [...digests].reverse().slice(2),
+ );
+ },
+ { skip: EXCLUDE },
+ );
+
+ testWithAllClients('should reject module-less event filters', async (client) => {
+ await expect(client.core.listEvents({ filter: { emitModule: packageId } })).rejects.toThrow(
+ 'Invalid emitModule filter',
+ );
+ await expect(client.core.listEvents({ filter: { eventType: packageId } })).rejects.toThrow(
+ 'Invalid eventType filter',
+ );
+ });
+ });
+});
diff --git a/packages/sui/test/e2e/utils/setup.ts b/packages/sui/test/e2e/utils/setup.ts
index 39e585d2d..86d2b337e 100644
--- a/packages/sui/test/e2e/utils/setup.ts
+++ b/packages/sui/test/e2e/utils/setup.ts
@@ -234,25 +234,32 @@ export class TestToolbox {
async expectAllClientsReturnSameData(
queryFn: (client: ClientWithCoreApi, kind: 'jsonrpc' | 'grpc' | 'graphql') => Promise,
normalize?: (result: T) => N,
- options?: { skip?: boolean },
+ options?: { skip?: boolean; exclude?: Array<'jsonrpc' | 'grpc' | 'graphql'> },
) {
if (options?.skip) {
test.skip('all clients return same data', () => {});
return;
}
- const [jsonRpcResult, grpcResult, graphqlResult] = await Promise.all([
- queryFn(this.jsonRpcClient, 'jsonrpc'),
- queryFn(this.grpcClient, 'grpc'),
- queryFn(this.graphqlClient, 'graphql'),
- ]);
-
- const normalizedJson = normalize ? normalize(jsonRpcResult) : jsonRpcResult;
- const normalizedGrpc = normalize ? normalize(grpcResult) : grpcResult;
- const normalizedGraphql = normalize ? normalize(graphqlResult) : graphqlResult;
+ const clients = (
+ [
+ ['jsonrpc', this.jsonRpcClient],
+ ['grpc', this.grpcClient],
+ ['graphql', this.graphqlClient],
+ ] as const
+ ).filter(([kind]) => !options?.exclude?.includes(kind));
+
+ const results = await Promise.all(
+ clients.map(async ([kind, client]) => {
+ const result = await queryFn(client, kind);
+ return [kind, normalize ? normalize(result) : result] as const;
+ }),
+ );
- expect(normalizedJson).toEqual(normalizedGrpc);
- expect(normalizedJson).toEqual(normalizedGraphql);
+ const [[, first]] = results;
+ for (const [, result] of results.slice(1)) {
+ expect(result).toEqual(first);
+ }
}
}
diff --git a/packages/sui/test/unit/client/query-filters.test.ts b/packages/sui/test/unit/client/query-filters.test.ts
new file mode 100644
index 000000000..abfdbbdc6
--- /dev/null
+++ b/packages/sui/test/unit/client/query-filters.test.ts
@@ -0,0 +1,281 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { describe, expect, it } from 'vitest';
+
+import {
+ resolveEventFilter,
+ resolvePagination,
+ resolveTransactionFilter,
+ validateTransactionQuery,
+} from '../../../src/client/query-filters.js';
+import type { SuiClientTypes } from '../../../src/client/types.js';
+import { toGrpcEventFilter, toGrpcTransactionFilter } from '../../../src/grpc/filters.js';
+
+const mvr: SuiClientTypes.MvrMethods = {
+ resolvePackage: async ({ package: pkg }) => ({
+ package: pkg === '@mysten/demo' ? '0x123' : pkg,
+ }),
+ resolveType: async ({ type }) => ({ type: type.replaceAll('@mysten/demo', '0x123') }),
+ resolve: async () => ({ packages: {}, types: {} }),
+};
+
+describe('resolvePagination', () => {
+ it('defaults to ascending without bounds', () => {
+ expect(resolvePagination({})).toEqual({
+ descending: false,
+ after: undefined,
+ before: undefined,
+ limit: 50,
+ });
+ expect(resolvePagination({ order: 'descending' })).toEqual({
+ descending: true,
+ after: undefined,
+ before: undefined,
+ limit: 50,
+ });
+ });
+
+ it('applies a shared default page size', () => {
+ expect(resolvePagination({}).limit).toBe(50);
+ expect(resolvePagination({ limit: 7 }).limit).toBe(7);
+ });
+
+ it('infers direction from the provided bound', () => {
+ expect(resolvePagination({ after: 'A' })).toEqual({
+ descending: false,
+ after: 'A',
+ before: undefined,
+ limit: 50,
+ });
+ expect(resolvePagination({ before: 'B' })).toEqual({
+ descending: true,
+ after: undefined,
+ before: 'B',
+ limit: 50,
+ });
+ });
+
+ it('accepts bounds with a matching explicit order', () => {
+ expect(resolvePagination({ after: 'A', order: 'ascending' }).after).toBe('A');
+ expect(resolvePagination({ before: 'B', order: 'descending' }).before).toBe('B');
+ });
+
+ it('rejects conflicting bounds and orders', () => {
+ expect(() => resolvePagination({ after: 'A', before: 'B' })).toThrowError(
+ 'Only one of `after` or `before` may be provided',
+ );
+ expect(() => resolvePagination({ after: 'A', order: 'descending' })).toThrowError(
+ '`after` can not be combined with descending queries',
+ );
+ expect(() => resolvePagination({ before: 'B', order: 'ascending' })).toThrowError(
+ '`before` can not be combined with ascending queries',
+ );
+ });
+});
+
+describe('validateTransactionQuery', () => {
+ it('allows bounds with sender and fully qualified function filters', async () => {
+ const sender = await resolveTransactionFilter(mvr, { sender: '0x1' });
+ const fn = await resolveTransactionFilter(mvr, { function: '0x2::coin::transfer' });
+
+ expect(() => validateTransactionQuery(sender, resolvePagination({ after: 'A' }))).not.toThrow();
+ expect(() => validateTransactionQuery(fn, resolvePagination({ before: 'B' }))).not.toThrow();
+ expect(() =>
+ validateTransactionQuery(undefined, resolvePagination({ after: 'A' })),
+ ).not.toThrow();
+ });
+
+ it('rejects bounds with partial function filters', async () => {
+ const pkg = await resolveTransactionFilter(mvr, { function: '0x2' });
+ const mod = await resolveTransactionFilter(mvr, { function: '0x2::coin' });
+
+ expect(() => validateTransactionQuery(pkg, resolvePagination({ after: 'A' }))).toThrowError(
+ 'requires a fully qualified',
+ );
+ expect(() => validateTransactionQuery(mod, resolvePagination({ before: 'B' }))).toThrowError(
+ 'requires a fully qualified',
+ );
+ expect(() => validateTransactionQuery(pkg, resolvePagination({}))).not.toThrow();
+ });
+});
+
+describe('resolveTransactionFilter', () => {
+ it('normalizes sender addresses', async () => {
+ expect(await resolveTransactionFilter(mvr, { sender: '0x2' })).toEqual({
+ $kind: 'sender',
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000002',
+ });
+ });
+
+ it('parses function paths at each specificity level', async () => {
+ const pkg = '0x0000000000000000000000000000000000000000000000000000000000000002';
+
+ expect(await resolveTransactionFilter(mvr, { function: '0x2' })).toEqual({
+ $kind: 'function',
+ package: pkg,
+ module: undefined,
+ function: undefined,
+ });
+ expect(await resolveTransactionFilter(mvr, { function: '0x2::coin' })).toEqual({
+ $kind: 'function',
+ package: pkg,
+ module: 'coin',
+ function: undefined,
+ });
+ expect(await resolveTransactionFilter(mvr, { function: '0x2::coin::transfer' })).toEqual({
+ $kind: 'function',
+ package: pkg,
+ module: 'coin',
+ function: 'transfer',
+ });
+ });
+
+ it('resolves MVR names in function paths', async () => {
+ expect(await resolveTransactionFilter(mvr, { function: '@mysten/demo::foo::bar' })).toEqual({
+ $kind: 'function',
+ package: '0x0000000000000000000000000000000000000000000000000000000000000123',
+ module: 'foo',
+ function: 'bar',
+ });
+ });
+
+ it('throws for invalid function paths', async () => {
+ await expect(resolveTransactionFilter(mvr, { function: '0x2::a::b::c' })).rejects.toThrowError(
+ 'Invalid function filter',
+ );
+ await expect(resolveTransactionFilter(mvr, { function: '0x2::' })).rejects.toThrowError(
+ 'Invalid function filter',
+ );
+ });
+
+ it('throws unless exactly one predicate is specified', async () => {
+ await expect(
+ resolveTransactionFilter(mvr, {} as SuiClientTypes.TransactionFilter),
+ ).rejects.toThrowError('exactly one of sender, function');
+ await expect(
+ resolveTransactionFilter(mvr, {
+ sender: '0x1',
+ function: '0x2::coin',
+ } as never),
+ ).rejects.toThrowError('exactly one of sender, function');
+ });
+});
+
+describe('resolveEventFilter', () => {
+ it('normalizes sender addresses', async () => {
+ expect(await resolveEventFilter(mvr, { sender: '0x2' })).toEqual({
+ $kind: 'sender',
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000002',
+ });
+ });
+
+ it('parses emitModule filters', async () => {
+ expect(await resolveEventFilter(mvr, { emitModule: '0x2::coin' })).toEqual({
+ $kind: 'emitModule',
+ package: '0x0000000000000000000000000000000000000000000000000000000000000002',
+ module: 'coin',
+ });
+ });
+
+ it('requires emitModule to name a module', async () => {
+ await expect(resolveEventFilter(mvr, { emitModule: '0x2' })).rejects.toThrowError(
+ 'Invalid emitModule filter',
+ );
+ await expect(resolveEventFilter(mvr, { emitModule: '0x2::a::b' })).rejects.toThrowError(
+ 'Invalid emitModule filter',
+ );
+ });
+
+ it('parses module-level eventType filters', async () => {
+ expect(await resolveEventFilter(mvr, { eventType: '0x2::coin' })).toEqual({
+ $kind: 'eventTypeModule',
+ package: '0x0000000000000000000000000000000000000000000000000000000000000002',
+ module: 'coin',
+ });
+ });
+
+ it('parses fully qualified eventType filters', async () => {
+ expect(await resolveEventFilter(mvr, { eventType: '0x2::coin::CoinCreated' })).toEqual({
+ $kind: 'eventType',
+ eventType: '0x2::coin::CoinCreated',
+ });
+ });
+
+ it('resolves MVR names in eventType filters', async () => {
+ expect(await resolveEventFilter(mvr, { eventType: '@mysten/demo::foo::Bar' })).toEqual({
+ $kind: 'eventType',
+ eventType: '0x123::foo::Bar',
+ });
+ });
+
+ it('requires eventType to be at least module-qualified', async () => {
+ await expect(resolveEventFilter(mvr, { eventType: '0x2' })).rejects.toThrowError(
+ 'Invalid eventType filter',
+ );
+ });
+
+ it('throws unless exactly one predicate is specified', async () => {
+ await expect(resolveEventFilter(mvr, {} as SuiClientTypes.EventFilter)).rejects.toThrowError(
+ 'exactly one of sender, emitModule, eventType',
+ );
+ });
+});
+
+describe('gRPC filter conversion', () => {
+ it('converts sender filters', async () => {
+ expect(toGrpcTransactionFilter(await resolveTransactionFilter(mvr, { sender: '0x1' }))).toEqual(
+ {
+ terms: [
+ {
+ literals: [
+ {
+ negated: false,
+ predicate: {
+ oneofKind: 'sender',
+ sender: {
+ address: '0x0000000000000000000000000000000000000000000000000000000000000001',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ );
+ });
+
+ it('converts function filters at each specificity', async () => {
+ const pkg = '0x0000000000000000000000000000000000000000000000000000000000000002';
+
+ for (const [input, expected] of [
+ ['0x2', pkg],
+ ['0x2::coin', `${pkg}::coin`],
+ ['0x2::coin::transfer', `${pkg}::coin::transfer`],
+ ]) {
+ expect(
+ toGrpcTransactionFilter(await resolveTransactionFilter(mvr, { function: input })).terms[0]
+ .literals[0].predicate,
+ ).toEqual({ oneofKind: 'moveCall', moveCall: { function: expected } });
+ }
+ });
+
+ it('converts event filters for each predicate', async () => {
+ const pkg = '0x0000000000000000000000000000000000000000000000000000000000000002';
+
+ expect(
+ toGrpcEventFilter(await resolveEventFilter(mvr, { emitModule: '0x2::coin' })).terms[0]
+ .literals[0].predicate,
+ ).toEqual({ oneofKind: 'emitModule', emitModule: { module: `${pkg}::coin` } });
+
+ expect(
+ toGrpcEventFilter(await resolveEventFilter(mvr, { eventType: '0x2::coin' })).terms[0]
+ .literals[0].predicate,
+ ).toEqual({ oneofKind: 'eventType', eventType: { eventType: `${pkg}::coin` } });
+
+ expect(
+ toGrpcEventFilter(await resolveEventFilter(mvr, { eventType: '0x2::coin::CoinCreated' }))
+ .terms[0].literals[0].predicate,
+ ).toEqual({ oneofKind: 'eventType', eventType: { eventType: '0x2::coin::CoinCreated' } });
+ });
+});
diff --git a/packages/wallet-sdk/test/mocks/MockSuiClient.ts b/packages/wallet-sdk/test/mocks/MockSuiClient.ts
index 68208c4aa..f083359c5 100644
--- a/packages/wallet-sdk/test/mocks/MockSuiClient.ts
+++ b/packages/wallet-sdk/test/mocks/MockSuiClient.ts
@@ -341,6 +341,18 @@ export class MockSuiClient extends CoreClient {
throw new Error('defaultNameServiceName not implemented in MockSuiClient');
}
+ async listTransactions(
+ _options: SuiClientTypes.ListTransactionsOptions,
+ ): Promise> {
+ throw new Error('listTransactions not implemented in MockSuiClient');
+ }
+
+ async listEvents(
+ _options: SuiClientTypes.ListEventsOptions,
+ ): Promise {
+ throw new Error('listEvents not implemented in MockSuiClient');
+ }
+
async simulateTransaction(
_options: SuiClientTypes.SimulateTransactionOptions,
): Promise> {