@mysten/sui 2.27.1 → 2.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/bcs/bcs.d.mts +6 -6
  3. package/dist/bcs/bcs.d.mts.map +1 -1
  4. package/dist/bcs/bcs.mjs +29 -2
  5. package/dist/bcs/bcs.mjs.map +1 -1
  6. package/dist/bcs/effects.mjs +2 -1
  7. package/dist/bcs/effects.mjs.map +1 -1
  8. package/dist/bcs/index.d.mts +102 -6
  9. package/dist/bcs/index.d.mts.map +1 -1
  10. package/dist/bcs/transactions.mjs +2 -1
  11. package/dist/bcs/transactions.mjs.map +1 -1
  12. package/dist/client/transaction-resolver.mjs +45 -9
  13. package/dist/client/transaction-resolver.mjs.map +1 -1
  14. package/dist/cryptography/signature.d.mts +14 -14
  15. package/dist/grpc/client.d.mts +1 -1
  16. package/dist/grpc/client.d.mts.map +1 -1
  17. package/dist/grpc/client.mjs +3 -5
  18. package/dist/grpc/client.mjs.map +1 -1
  19. package/dist/grpc/core.d.mts.map +1 -1
  20. package/dist/grpc/core.mjs +10 -10
  21. package/dist/grpc/core.mjs.map +1 -1
  22. package/dist/grpc/index.d.mts +4 -3
  23. package/dist/grpc/index.mjs +4 -2
  24. package/dist/grpc/proto/sui/forking/v1alpha/forking_service.client.d.mts +4 -4
  25. package/dist/grpc/proto/sui/rpc/v2/execution_status.d.mts +9 -1
  26. package/dist/grpc/proto/sui/rpc/v2/execution_status.d.mts.map +1 -1
  27. package/dist/grpc/proto/sui/rpc/v2/execution_status.mjs +8 -0
  28. package/dist/grpc/proto/sui/rpc/v2/execution_status.mjs.map +1 -1
  29. package/dist/grpc/proto/sui/rpc/v2/ledger_service.client.d.mts +4 -4
  30. package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
  31. package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
  32. package/dist/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.mts +4 -4
  33. package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
  34. package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
  35. package/dist/grpc/proto/sui/rpc/v2/transaction.d.mts +50 -2
  36. package/dist/grpc/proto/sui/rpc/v2/transaction.d.mts.map +1 -1
  37. package/dist/grpc/proto/sui/rpc/v2/transaction.mjs +36 -1
  38. package/dist/grpc/proto/sui/rpc/v2/transaction.mjs.map +1 -1
  39. package/dist/grpc/proto/sui/rpc/v2/transaction_execution_service.client.d.mts +4 -4
  40. package/dist/grpc/proto/types.d.mts +2 -2
  41. package/dist/grpc/proto/types.mjs +2 -1
  42. package/dist/grpc/transport.d.mts +18 -0
  43. package/dist/grpc/transport.d.mts.map +1 -0
  44. package/dist/grpc/transport.mjs +97 -0
  45. package/dist/grpc/transport.mjs.map +1 -0
  46. package/dist/transactions/Transaction.d.mts +19 -7
  47. package/dist/transactions/Transaction.d.mts.map +1 -1
  48. package/dist/transactions/data/internal.d.mts +133 -109
  49. package/dist/transactions/data/internal.d.mts.map +1 -1
  50. package/dist/transactions/data/internal.mjs +11 -2
  51. package/dist/transactions/data/internal.mjs.map +1 -1
  52. package/dist/transactions/data/v1.d.mts +22 -0
  53. package/dist/transactions/data/v1.d.mts.map +1 -1
  54. package/dist/transactions/data/v1.mjs +27 -4
  55. package/dist/transactions/data/v1.mjs.map +1 -1
  56. package/dist/transactions/data/v2.d.mts +12 -0
  57. package/dist/transactions/data/v2.d.mts.map +1 -1
  58. package/dist/transactions/data/v2.mjs +3 -2
  59. package/dist/transactions/data/v2.mjs.map +1 -1
  60. package/dist/version.mjs +1 -1
  61. package/dist/version.mjs.map +1 -1
  62. package/dist/zklogin/bcs.d.mts +14 -14
  63. package/docs/clients/grpc.md +31 -7
  64. package/package.json +1 -1
  65. package/src/bcs/bcs.ts +45 -0
  66. package/src/bcs/effects.ts +3 -0
  67. package/src/bcs/transactions.ts +1 -0
  68. package/src/bcs/types.ts +10 -1
  69. package/src/client/transaction-resolver.ts +79 -9
  70. package/src/grpc/client.ts +12 -4
  71. package/src/grpc/core.ts +18 -14
  72. package/src/grpc/index.ts +7 -3
  73. package/src/grpc/proto/sui/rpc/v2/execution_status.ts +8 -0
  74. package/src/grpc/proto/sui/rpc/v2/transaction.ts +68 -0
  75. package/src/grpc/transport.ts +160 -0
  76. package/src/transactions/data/internal.ts +19 -0
  77. package/src/transactions/data/v1.ts +51 -12
  78. package/src/transactions/data/v2.ts +2 -0
  79. package/src/version.ts +1 -1
@@ -188,6 +188,7 @@ export const EndOfEpochTransactionKind = bcs.enum('EndOfEpochTransactionKind', {
188
188
  DisplayRegistryCreate: null,
189
189
  AddressAliasStateCreate: null,
190
190
  WriteAccumulatorStorageCost: WriteAccumulatorStorageCost,
191
+ ForwardingAddressRegistryCreate: null,
191
192
  });
192
193
 
193
194
  // Rust: crates/sui-types/src/transaction.rs
package/src/bcs/types.ts CHANGED
@@ -126,10 +126,19 @@ type ValidDuring = {
126
126
  nonce: number;
127
127
  };
128
128
 
129
+ type AllowedProposers = {
130
+ epoch: number | string;
131
+ proposers: number[];
132
+ };
133
+
134
+ type Validity = ValidDuring & {
135
+ allowedProposers: { None: null } | { Some: AllowedProposers };
136
+ };
137
+
129
138
  /**
130
139
  * TransactionExpiration
131
140
  *
132
141
  * Indications the expiration time for a transaction.
133
142
  */
134
143
  export type TransactionExpiration =
135
- { None: null } | { Epoch: number } | { ValidDuring: ValidDuring };
144
+ { None: null } | { Epoch: number } | { ValidDuring: ValidDuring } | { Validity: Validity };
@@ -4,6 +4,10 @@
4
4
  import { fromBase64, toBase64 } from '@mysten/bcs';
5
5
 
6
6
  import type { bcs } from '../bcs/index.js';
7
+ import {
8
+ assertAllowedProposersNotEmpty,
9
+ assertAllowedProposersStrictlyIncreasing,
10
+ } from '../bcs/bcs.js';
7
11
  import { TransactionDataBuilder } from '../transactions/TransactionData.js';
8
12
  import type {
9
13
  CallArg,
@@ -23,6 +27,18 @@ import type { Argument } from '../grpc/proto/sui/rpc/v2/argument.js';
23
27
  import { Argument_ArgumentKind } from '../grpc/proto/sui/rpc/v2/argument.js';
24
28
  import { Transaction } from '../transactions/Transaction.js';
25
29
 
30
+ function millisecondsToGrpcTimestamp(value: string | number) {
31
+ const milliseconds = BigInt(value);
32
+ return {
33
+ seconds: milliseconds / 1000n,
34
+ nanos: Number(milliseconds % 1000n) * 1_000_000,
35
+ };
36
+ }
37
+
38
+ function grpcTimestampToMilliseconds(timestamp: { seconds: bigint; nanos: number }) {
39
+ return (timestamp.seconds * 1000n + BigInt(Math.floor(timestamp.nanos / 1_000_000))).toString();
40
+ }
41
+
26
42
  /**
27
43
  * Converts CallArg (TypeScript internal format) to gRPC Input format
28
44
  */
@@ -263,14 +279,36 @@ export function transactionDataToGrpcTransaction(data: TransactionData): GrpcTra
263
279
  kind: TransactionExpiration_TransactionExpirationKind.EPOCH,
264
280
  epoch: BigInt(data.expiration.Epoch),
265
281
  };
266
- } else if (data.expiration.$kind === 'ValidDuring') {
267
- const validDuring = data.expiration.ValidDuring;
282
+ } else if (data.expiration.$kind === 'ValidDuring' || data.expiration.$kind === 'Validity') {
283
+ const validity =
284
+ data.expiration.$kind === 'ValidDuring'
285
+ ? data.expiration.ValidDuring
286
+ : data.expiration.Validity;
287
+ const allowedProposers =
288
+ data.expiration.$kind === 'Validity' ? data.expiration.Validity.allowedProposers : null;
268
289
  transaction.expiration = {
269
- kind: TransactionExpiration_TransactionExpirationKind.VALID_DURING,
270
- minEpoch: validDuring.minEpoch != null ? BigInt(validDuring.minEpoch) : undefined,
271
- epoch: validDuring.maxEpoch != null ? BigInt(validDuring.maxEpoch) : undefined,
272
- chain: validDuring.chain,
273
- nonce: validDuring.nonce,
290
+ kind:
291
+ data.expiration.$kind === 'ValidDuring'
292
+ ? TransactionExpiration_TransactionExpirationKind.VALID_DURING
293
+ : TransactionExpiration_TransactionExpirationKind.VALIDITY,
294
+ minEpoch: validity.minEpoch != null ? BigInt(validity.minEpoch) : undefined,
295
+ epoch: validity.maxEpoch != null ? BigInt(validity.maxEpoch) : undefined,
296
+ minTimestamp:
297
+ validity.minTimestamp != null
298
+ ? millisecondsToGrpcTimestamp(validity.minTimestamp)
299
+ : undefined,
300
+ maxTimestamp:
301
+ validity.maxTimestamp != null
302
+ ? millisecondsToGrpcTimestamp(validity.maxTimestamp)
303
+ : undefined,
304
+ chain: validity.chain,
305
+ nonce: validity.nonce,
306
+ allowedProposers: allowedProposers
307
+ ? {
308
+ epoch: BigInt(allowedProposers.epoch),
309
+ proposers: assertAllowedProposersStrictlyIncreasing(allowedProposers.proposers),
310
+ }
311
+ : undefined,
274
312
  };
275
313
  }
276
314
  }
@@ -529,13 +567,45 @@ export function grpcTransactionToTransactionData(grpcTx: GrpcTransaction): Trans
529
567
  ValidDuring: {
530
568
  minEpoch: grpcTx.expiration.minEpoch?.toString() ?? null,
531
569
  maxEpoch: grpcTx.expiration.epoch?.toString() ?? null,
532
- minTimestamp: null,
533
- maxTimestamp: null,
570
+ minTimestamp: grpcTx.expiration.minTimestamp
571
+ ? grpcTimestampToMilliseconds(grpcTx.expiration.minTimestamp)
572
+ : null,
573
+ maxTimestamp: grpcTx.expiration.maxTimestamp
574
+ ? grpcTimestampToMilliseconds(grpcTx.expiration.maxTimestamp)
575
+ : null,
576
+ chain: grpcTx.expiration.chain ?? '',
577
+ nonce: grpcTx.expiration.nonce ?? 0,
578
+ },
579
+ };
580
+ break;
581
+ case TransactionExpiration_TransactionExpirationKind.VALIDITY:
582
+ expiration = {
583
+ $kind: 'Validity',
584
+ Validity: {
585
+ minEpoch: grpcTx.expiration.minEpoch?.toString() ?? null,
586
+ maxEpoch: grpcTx.expiration.epoch?.toString() ?? null,
587
+ minTimestamp: grpcTx.expiration.minTimestamp
588
+ ? grpcTimestampToMilliseconds(grpcTx.expiration.minTimestamp)
589
+ : null,
590
+ maxTimestamp: grpcTx.expiration.maxTimestamp
591
+ ? grpcTimestampToMilliseconds(grpcTx.expiration.maxTimestamp)
592
+ : null,
534
593
  chain: grpcTx.expiration.chain ?? '',
535
594
  nonce: grpcTx.expiration.nonce ?? 0,
595
+ allowedProposers: grpcTx.expiration.allowedProposers
596
+ ? {
597
+ epoch: grpcTx.expiration.allowedProposers.epoch?.toString() ?? '0',
598
+ proposers: assertAllowedProposersNotEmpty(
599
+ grpcTx.expiration.allowedProposers.proposers,
600
+ ),
601
+ }
602
+ : null,
536
603
  },
537
604
  };
538
605
  break;
606
+ default:
607
+ // Leaving `expiration` null here would drop a restriction the server did send.
608
+ throw new Error(`Unknown transaction expiration kind: ${grpcTx.expiration.kind}`);
539
609
  }
540
610
  }
541
611
 
@@ -2,7 +2,6 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
5
- import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
6
5
  import { TransactionExecutionServiceClient } from './proto/sui/rpc/v2/transaction_execution_service.client.js';
7
6
  import { LedgerServiceClient } from './proto/sui/rpc/v2/ledger_service.client.js';
8
7
  import { MovePackageServiceClient } from './proto/sui/rpc/v2/move_package_service.client.js';
@@ -19,6 +18,7 @@ import { fromBase64, toBase64 } from '@mysten/utils';
19
18
  import { NameServiceClient } from './proto/sui/rpc/v2/name_service.client.js';
20
19
  import { ForkingServiceClient } from './proto/sui/forking/v1alpha/forking_service.client.js';
21
20
  import type { TransactionPlugin } from '../transactions/index.js';
21
+ import { GrpcWebFetchTransport } from './transport.js';
22
22
 
23
23
  interface SuiGrpcTransportOptions extends GrpcWebOptions {
24
24
  transport?: never;
@@ -157,9 +157,17 @@ export class SuiGrpcClient extends BaseClient implements SuiClientTypes.Transpor
157
157
 
158
158
  constructor(options: SuiGrpcClientOptions) {
159
159
  super({ network: options.network });
160
- const transport =
161
- options.transport ??
162
- new GrpcWebFetchTransport({ baseUrl: options.baseUrl, fetchInit: options.fetchInit });
160
+ const {
161
+ network: _network,
162
+ mvr: _mvr,
163
+ // Not forwarded: every Core API call passes its own `signal`, which would overwrite it.
164
+ abort: _abort,
165
+ transport: providedTransport,
166
+ ...transportOptions
167
+ } = options as SuiGrpcClientOptions & SuiGrpcTransportOptions & { transport?: RpcTransport };
168
+
169
+ // A caller-supplied transport is used as given. See ./transport.ts for the default.
170
+ const transport = providedTransport ?? new GrpcWebFetchTransport(transportOptions);
163
171
  this.transactionExecutionService = new TransactionExecutionServiceClient(transport);
164
172
  this.ledgerService = new LedgerServiceClient(transport);
165
173
  this.stateService = new StateServiceClient(transport);
package/src/grpc/core.ts CHANGED
@@ -69,24 +69,28 @@ import {
69
69
  validateTransactionQuery,
70
70
  } from '../client/query-filters.js';
71
71
  import { toGrpcEventFilter, toGrpcTransactionFilter } from './filters.js';
72
+ import { hasDecodedStatusMessage } from './transport.js';
72
73
 
73
74
  export interface GrpcCoreClientOptions extends CoreClientOptions {
74
75
  client: SuiGrpcClient;
75
76
  }
76
77
 
77
78
  function isNameServiceResolutionMiss(error: unknown): boolean {
78
- if (!(error instanceof RpcError)) return false;
79
- if (error.code === GrpcStatusCode[GrpcStatusCode.NOT_FOUND]) return true;
80
- if (error.code !== GrpcStatusCode[GrpcStatusCode.RESOURCE_EXHAUSTED]) return false;
81
-
82
- try {
83
- // The gRPC service currently reports expired names as RESOURCE_EXHAUSTED without a
84
- // structured reason. grpc-web URI-encodes status messages, so decode before matching while
85
- // preserving unrelated RESOURCE_EXHAUSTED failures such as capacity limits.
86
- return decodeURIComponent(error.message) === 'name has expired';
87
- } catch {
88
- return false;
89
- }
79
+ // Read by shape, so an error from another copy of runtime-rpc is still recognised.
80
+ if (typeof error !== 'object' || error === null) return false;
81
+
82
+ const { code, message } = error as { code?: unknown; message?: unknown };
83
+ if (typeof code !== 'string' || typeof message !== 'string') return false;
84
+
85
+ if (code === GrpcStatusCode[GrpcStatusCode.NOT_FOUND]) return true;
86
+ if (code !== GrpcStatusCode[GrpcStatusCode.RESOURCE_EXHAUSTED]) return false;
87
+
88
+ // The service reports an expired name as RESOURCE_EXHAUSTED with no structured reason, so match
89
+ // the status text and leave other RESOURCE_EXHAUSTED failures alone. A transport this package did
90
+ // not build leaves `grpc-message` encoded, hence the second form.
91
+ if (message === 'name has expired') return true;
92
+
93
+ return !hasDecodedStatusMessage(error) && message === 'name%20has%20expired';
90
94
  }
91
95
 
92
96
  export class GrpcCoreClient extends CoreClient {
@@ -987,9 +991,9 @@ export class GrpcCoreClient extends CoreClient {
987
991
  });
988
992
  response = result.response;
989
993
  } catch (error) {
990
- // https://github.com/timostamm/protobuf-ts/pull/739
994
+ // The transport owns the status text. See ./transport.ts.
991
995
  if (error instanceof Error && error.message) {
992
- throw new SimulationError(decodeURIComponent(error.message), { cause: error });
996
+ throw new SimulationError(error.message, { cause: error });
993
997
  }
994
998
  throw error;
995
999
  }
package/src/grpc/index.ts CHANGED
@@ -23,10 +23,14 @@ export type {
23
23
  } from './client.js';
24
24
  export type { GrpcCoreClientOptions } from './core.js';
25
25
 
26
- // Re-export transports and types so users can configure custom transports
27
- // without adding @protobuf-ts/* as direct dependencies.
28
- export { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
26
+ // Subclasses `@protobuf-ts/grpcweb-transport`'s transport to fix its error handling.
27
+ export { GrpcWebFetchTransport } from './transport.js';
28
+
29
+ // Re-exported so users can configure a transport, and narrow and code the errors it produces,
30
+ // without adding @protobuf-ts/* as a dependency.
31
+ export { GrpcStatusCode } from '@protobuf-ts/grpcweb-transport';
29
32
  export type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
33
+ export { RpcError } from '@protobuf-ts/runtime-rpc';
30
34
  export type { RpcTransport } from '@protobuf-ts/runtime-rpc';
31
35
 
32
36
  // Export all gRPC proto types as a namespace
@@ -727,6 +727,14 @@ export enum CommandArgumentError_CommandArgumentErrorKind {
727
727
  * @generated from protobuf enum value: INVALID_REFERENCE_ARGUMENT = 19;
728
728
  */
729
729
  INVALID_REFERENCE_ARGUMENT = 19,
730
+ /**
731
+ * Invalid usage of TxContext in the function signature. TxContext can only be used by
732
+ * reference, `&TxContext` or `&mut TxContext`. If used mutably, it must be the only
733
+ * TxContext parameter, and TxContext can never be returned from a Move call.
734
+ *
735
+ * @generated from protobuf enum value: INVALID_TX_CONTEXT = 20;
736
+ */
737
+ INVALID_TX_CONTEXT = 20,
730
738
  }
731
739
  /**
732
740
  * An error with upgrading a package.
@@ -145,6 +145,13 @@ export interface TransactionExpiration {
145
145
  * @generated from protobuf field: optional uint32 nonce = 7;
146
146
  */
147
147
  nonce?: number;
148
+ /**
149
+ * The validators allowed to propose this transaction in consensus. Only set when `kind`
150
+ * is `VALIDITY`. Leave unset to let any validator propose the transaction.
151
+ *
152
+ * @generated from protobuf field: optional sui.rpc.v2.AllowedProposers allowed_proposers = 8;
153
+ */
154
+ allowedProposers?: AllowedProposers;
148
155
  }
149
156
  /**
150
157
  * @generated from protobuf enum sui.rpc.v2.TransactionExpiration.TransactionExpirationKind
@@ -181,6 +188,40 @@ export enum TransactionExpiration_TransactionExpirationKind {
181
188
  * @generated from protobuf enum value: VALID_DURING = 3;
182
189
  */
183
190
  VALID_DURING = 3,
191
+ /**
192
+ * Everything in VALID_DURING, plus a restriction on which validators may
193
+ * propose the transaction in consensus.
194
+ *
195
+ * @generated from protobuf enum value: VALIDITY = 4;
196
+ */
197
+ VALIDITY = 4,
198
+ }
199
+ /**
200
+ * The validators allowed to propose a transaction in consensus.
201
+ *
202
+ * Proposal by any other validator is byzantine behavior and invalidates the whole block.
203
+ *
204
+ * @generated from protobuf message sui.rpc.v2.AllowedProposers
205
+ */
206
+ export interface AllowedProposers {
207
+ /**
208
+ * The epoch whose committee `proposers` indexes into.
209
+ *
210
+ * Committee indices are only meaningful against one committee, so a set recorded for any
211
+ * other epoch is ignored and the transaction is treated as naming no proposers.
212
+ *
213
+ * @generated from protobuf field: optional uint64 epoch = 1;
214
+ */
215
+ epoch?: bigint;
216
+ /**
217
+ * Committee indices of the allowed proposers, strictly increasing and non-empty.
218
+ *
219
+ * An empty list names no validator and is rejected; omit `allowed_proposers` entirely to
220
+ * let any validator propose the transaction.
221
+ *
222
+ * @generated from protobuf field: repeated uint32 proposers = 2;
223
+ */
224
+ proposers: number[];
184
225
  }
185
226
  /**
186
227
  * Transaction type.
@@ -1314,6 +1355,7 @@ class TransactionExpiration$Type extends MessageType<TransactionExpiration> {
1314
1355
  { no: 5, name: 'max_timestamp', kind: 'message', T: () => Timestamp },
1315
1356
  { no: 6, name: 'chain', kind: 'scalar', opt: true, T: 9 /*ScalarType.STRING*/ },
1316
1357
  { no: 7, name: 'nonce', kind: 'scalar', opt: true, T: 13 /*ScalarType.UINT32*/ },
1358
+ { no: 8, name: 'allowed_proposers', kind: 'message', T: () => AllowedProposers },
1317
1359
  ]);
1318
1360
  }
1319
1361
  }
@@ -1322,6 +1364,32 @@ class TransactionExpiration$Type extends MessageType<TransactionExpiration> {
1322
1364
  */
1323
1365
  export const TransactionExpiration = new TransactionExpiration$Type();
1324
1366
  // @generated message type with reflection information, may provide speed optimized methods
1367
+ class AllowedProposers$Type extends MessageType<AllowedProposers> {
1368
+ constructor() {
1369
+ super('sui.rpc.v2.AllowedProposers', [
1370
+ {
1371
+ no: 1,
1372
+ name: 'epoch',
1373
+ kind: 'scalar',
1374
+ opt: true,
1375
+ T: 4 /*ScalarType.UINT64*/,
1376
+ L: 0 /*LongType.BIGINT*/,
1377
+ },
1378
+ {
1379
+ no: 2,
1380
+ name: 'proposers',
1381
+ kind: 'scalar',
1382
+ repeat: 1 /*RepeatType.PACKED*/,
1383
+ T: 13 /*ScalarType.UINT32*/,
1384
+ },
1385
+ ]);
1386
+ }
1387
+ }
1388
+ /**
1389
+ * @generated MessageType for protobuf message sui.rpc.v2.AllowedProposers
1390
+ */
1391
+ export const AllowedProposers = new AllowedProposers$Type();
1392
+ // @generated message type with reflection information, may provide speed optimized methods
1325
1393
  class TransactionKind$Type extends MessageType<TransactionKind> {
1326
1394
  constructor() {
1327
1395
  super('sui.rpc.v2.TransactionKind', [
@@ -0,0 +1,160 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { GrpcStatusCode } from '@protobuf-ts/grpcweb-transport';
5
+ import { GrpcWebFetchTransport as UpstreamGrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
6
+ import type {
7
+ MethodInfo,
8
+ RpcOptions,
9
+ ServerStreamingCall,
10
+ UnaryCall,
11
+ } from '@protobuf-ts/runtime-rpc';
12
+ import { RpcError } from '@protobuf-ts/runtime-rpc';
13
+
14
+ // A failed call rejects four promises with the same error, so it is only decoded once. Registered
15
+ // globally so another installed copy of this package sees the same marker.
16
+ const MESSAGE_DECODED = Symbol.for('@mysten/sui/grpc/decoded-status-message');
17
+
18
+ // `grpc-message` is percent-encoded on the wire and the upstream transport does not decode it:
19
+ // https://github.com/timostamm/protobuf-ts/pull/739
20
+ function decodeGrpcStatusMessage(message: string): string {
21
+ if (!message.includes('%')) return message;
22
+
23
+ try {
24
+ return decodeURIComponent(message);
25
+ } catch {
26
+ return message;
27
+ }
28
+ }
29
+
30
+ function markStatusMessageRead(error: RpcError): boolean {
31
+ if (MESSAGE_DECODED in error) return false;
32
+ Object.defineProperty(error, MESSAGE_DECODED, { value: true, configurable: true });
33
+
34
+ return true;
35
+ }
36
+
37
+ function decodeStatusMessageOnce(error: RpcError): void {
38
+ if (markStatusMessageRead(error)) error.message = decodeGrpcStatusMessage(error.message);
39
+ }
40
+
41
+ /** Whether this package's transport decoded the error's status text. */
42
+ export function hasDecodedStatusMessage(error: object): boolean {
43
+ return MESSAGE_DECODED in error;
44
+ }
45
+
46
+ // An abort reason is arbitrary, and reading a property on it runs code the caller wrote. A read
47
+ // that throws counts as absent, so one bad accessor does not decide the status.
48
+ function readProperty(value: unknown, key: 'code' | 'name' | 'message'): unknown {
49
+ if (typeof value !== 'object' || value === null) return undefined;
50
+
51
+ try {
52
+ return (value as Record<string, unknown>)[key];
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ // The reason is read by shape, since another copy of runtime-rpc or another realm fails
59
+ // `instanceof`. `AbortSignal.timeout` aborts with a `TimeoutError`.
60
+ function abortStatus(reason: unknown): string {
61
+ const code = readProperty(reason, 'code');
62
+
63
+ // A status name maps to a number; `in` would also match the enum's reverse mapping.
64
+ if (
65
+ typeof code === 'string' &&
66
+ typeof GrpcStatusCode[code as keyof typeof GrpcStatusCode] === 'number'
67
+ ) {
68
+ return code;
69
+ }
70
+
71
+ return readProperty(reason, 'name') === 'TimeoutError'
72
+ ? GrpcStatusCode[GrpcStatusCode.DEADLINE_EXCEEDED]
73
+ : GrpcStatusCode[GrpcStatusCode.CANCELLED];
74
+ }
75
+
76
+ // The transport reuses the abort reason's message, so matching it says the text is local. A fetch
77
+ // that substitutes its own error (node-fetch) gives text of its own, which holds nothing to decode.
78
+ // The reason is never coerced: a symbol or a throwing hook would throw here.
79
+ function isAbortReasonText(message: string, reason: unknown): boolean {
80
+ if (typeof reason === 'string') return message === reason;
81
+
82
+ const reasonMessage = readProperty(reason, 'message');
83
+
84
+ return typeof reasonMessage === 'string' && message === reasonMessage;
85
+ }
86
+
87
+ // Rewritten in place, so every promise a call rejects surfaces the same error. Only the two codes
88
+ // upstream uses for an abort are re-coded; a failure that raced the abort is relabelled with it.
89
+ function normalizeGrpcError(error: unknown, signal: AbortSignal | undefined): void {
90
+ try {
91
+ normalize(error, signal);
92
+ } catch {
93
+ // Never an unhandled rejection from a discarded handler: the call reports what it was going
94
+ // to report.
95
+ }
96
+ }
97
+
98
+ function normalize(error: unknown, signal: AbortSignal | undefined): void {
99
+ if (!(error instanceof RpcError)) return;
100
+
101
+ if (
102
+ signal?.aborted &&
103
+ (error.code === GrpcStatusCode[GrpcStatusCode.INTERNAL] ||
104
+ error.code === GrpcStatusCode[GrpcStatusCode.CANCELLED])
105
+ ) {
106
+ error.code = abortStatus(signal.reason);
107
+
108
+ // Text the abort reason supplied is local and holds nothing to decode; anything else came off
109
+ // the wire, even though the abort is what the caller sees. Settled here for the other promises
110
+ // the call rejects, which no longer match the codes above.
111
+ if (isAbortReasonText(error.message, signal.reason)) markStatusMessageRead(error);
112
+ else decodeStatusMessageOnce(error);
113
+
114
+ return;
115
+ }
116
+
117
+ decodeStatusMessageOnce(error);
118
+ }
119
+
120
+ function normalizeRejections(promises: Promise<unknown>[], signal: AbortSignal | undefined) {
121
+ for (const promise of promises) {
122
+ // Registered before the call is returned, so it runs before the consumer's own await.
123
+ promise.then(undefined, (error: unknown) => normalizeGrpcError(error, signal));
124
+ }
125
+ }
126
+
127
+ /**
128
+ * `GrpcWebFetchTransport` from `@protobuf-ts/grpcweb-transport`, subclassed to decode status
129
+ * messages and to code an aborted call from its reason rather than as `INTERNAL`.
130
+ *
131
+ * `SuiGrpcClient` builds one by default. A transport imported from `@protobuf-ts/grpcweb-transport`
132
+ * keeps that package's behaviour.
133
+ */
134
+ export class GrpcWebFetchTransport extends UpstreamGrpcWebFetchTransport {
135
+ override unary<I extends object, O extends object>(
136
+ method: MethodInfo<I, O>,
137
+ input: I,
138
+ options: RpcOptions,
139
+ ): UnaryCall<I, O> {
140
+ const call = super.unary(method, input, options);
141
+
142
+ normalizeRejections([call.headers, call.response, call.status, call.trailers], options.abort);
143
+
144
+ return call;
145
+ }
146
+
147
+ override serverStreaming<I extends object, O extends object>(
148
+ method: MethodInfo<I, O>,
149
+ input: I,
150
+ options: RpcOptions,
151
+ ): ServerStreamingCall<I, O> {
152
+ const call = super.serverStreaming(method, input, options);
153
+
154
+ // A finite stream is often read through `responses` without awaiting `status`.
155
+ call.responses.onError((error) => normalizeGrpcError(error, options.abort));
156
+ normalizeRejections([call.headers, call.status, call.trailers], options.abort);
157
+
158
+ return call;
159
+ }
160
+ }
@@ -343,10 +343,29 @@ export const ValidDuringSchema = object({
343
343
  });
344
344
  export type ValidDuring = InferOutput<typeof ValidDuringSchema>;
345
345
 
346
+ // Rust: crates/sui-types/src/transaction.rs
347
+ export const AllowedProposersSchema = object({
348
+ epoch: JsonU64,
349
+ // Shared by `setExpiration` and `restore`, so this can only carry the invariant true on both.
350
+ proposers: pipe(
351
+ array(U32),
352
+ check((proposers) => proposers.length > 0, 'Allowed proposers must not be empty'),
353
+ ),
354
+ });
355
+ export type AllowedProposers = InferOutput<typeof AllowedProposersSchema>;
356
+
357
+ // Rust: crates/sui-types/src/transaction.rs
358
+ export const ValiditySchema = object({
359
+ ...ValidDuringSchema.entries,
360
+ allowedProposers: nullable(AllowedProposersSchema),
361
+ });
362
+ export type Validity = InferOutput<typeof ValiditySchema>;
363
+
346
364
  export const TransactionExpiration = safeEnum({
347
365
  None: literal(true),
348
366
  Epoch: JsonU64,
349
367
  ValidDuring: ValidDuringSchema,
368
+ Validity: ValiditySchema,
350
369
  });
351
370
 
352
371
  export type TransactionExpiration = InferOutput<typeof TransactionExpiration>;
@@ -26,7 +26,14 @@ import {
26
26
 
27
27
  import { TypeTagSerializer } from '../../bcs/index.js';
28
28
  import type { StructTag as StructTagType, TypeTag as TypeTagType } from '../../bcs/types.js';
29
- import { JsonU64, ObjectID, safeEnum, TransactionDataSchema } from './internal.js';
29
+ import {
30
+ JsonU64,
31
+ ObjectID,
32
+ safeEnum,
33
+ TransactionDataSchema,
34
+ ValidDuringSchema,
35
+ ValiditySchema,
36
+ } from './internal.js';
30
37
  import type { Argument, ArgumentSchema, TransactionData } from './internal.js';
31
38
 
32
39
  export const ObjectRef = object({
@@ -65,11 +72,52 @@ const TransactionInput = union([
65
72
  }),
66
73
  ]);
67
74
 
75
+ // Carried here rather than collapsed to `{ None: true }`, which silently dropped the expiration
76
+ // (and its allowed proposers) from a `serialize()` -> `from()` round trip before signing.
68
77
  const TransactionExpiration = union([
69
78
  object({ Epoch: pipe(number(), integer()) }),
70
79
  object({ None: nullable(literal(true)) }),
80
+ object({ ValidDuring: ValidDuringSchema }),
81
+ object({ Validity: ValiditySchema }),
71
82
  ]);
72
83
 
84
+ function expirationToV1(
85
+ expiration: TransactionData['expiration'],
86
+ ): InferOutput<typeof TransactionExpiration> | null {
87
+ if (!expiration) return null;
88
+
89
+ switch (expiration.$kind) {
90
+ case 'Epoch':
91
+ return { Epoch: Number(expiration.Epoch) };
92
+ case 'ValidDuring':
93
+ return { ValidDuring: expiration.ValidDuring };
94
+ case 'Validity':
95
+ return { Validity: expiration.Validity };
96
+ case 'None':
97
+ return { None: true };
98
+ default:
99
+ // Representing a new variant here is not optional: this fails to compile without it.
100
+ expiration satisfies never;
101
+ throw new Error(`Unknown transaction expiration: ${JSON.stringify(expiration)}`);
102
+ }
103
+ }
104
+
105
+ function expirationFromV1(
106
+ expiration: SerializedTransactionDataV1['expiration'],
107
+ ): InferInput<typeof TransactionDataSchema>['expiration'] {
108
+ if (!expiration) return null;
109
+ if ('Epoch' in expiration) return { Epoch: expiration.Epoch };
110
+ if ('ValidDuring' in expiration) return { ValidDuring: expiration.ValidDuring };
111
+ if ('Validity' in expiration) return { Validity: expiration.Validity };
112
+ if ('None' in expiration) return { None: true };
113
+
114
+ // `restore` does not validate v1 payloads against the schema, so this is the only place an
115
+ // unrecognised expiration can be caught rather than silently becoming `{ None: true }`.
116
+ throw new Error(
117
+ `Unknown transaction expiration in v1 transaction data: ${Object.keys(expiration).join(', ')}`,
118
+ );
119
+ }
120
+
73
121
  const StringEncodedBigint = pipe(
74
122
  union([number(), string(), bigint()]),
75
123
  check((val) => {
@@ -267,12 +315,7 @@ export function serializeV1TransactionData(
267
315
  return {
268
316
  version: 1,
269
317
  sender: transactionData.sender ?? undefined,
270
- expiration:
271
- transactionData.expiration?.$kind === 'Epoch'
272
- ? { Epoch: Number(transactionData.expiration.Epoch) }
273
- : transactionData.expiration
274
- ? { None: true }
275
- : null,
318
+ expiration: expirationToV1(transactionData.expiration),
276
319
  gasConfig: {
277
320
  owner: transactionData.gasData.owner ?? undefined,
278
321
  budget: transactionData.gasData.budget ?? undefined,
@@ -373,11 +416,7 @@ export function transactionDataFromV1(data: SerializedTransactionDataV1): Transa
373
416
  return parse(TransactionDataSchema, {
374
417
  version: 2,
375
418
  sender: data.sender ?? null,
376
- expiration: data.expiration
377
- ? 'Epoch' in data.expiration
378
- ? { Epoch: data.expiration.Epoch }
379
- : { None: true }
380
- : null,
419
+ expiration: expirationFromV1(data.expiration),
381
420
  gasData: {
382
421
  owner: data.gasConfig.owner ?? null,
383
422
  budget: data.gasConfig.budget?.toString() ?? null,
@@ -29,6 +29,7 @@ import {
29
29
  ObjectRefSchema,
30
30
  SuiAddress,
31
31
  ValidDuringSchema,
32
+ ValiditySchema,
32
33
  } from './internal.js';
33
34
  import type { Simplify } from '@mysten/utils';
34
35
 
@@ -143,6 +144,7 @@ const TransactionExpiration = enumUnion({
143
144
  None: literal(true),
144
145
  Epoch: JsonU64,
145
146
  ValidDuring: ValidDuringSchema,
147
+ Validity: ValiditySchema,
146
148
  });
147
149
 
148
150
  export const SerializedTransactionDataV2Schema = object({