@mysten/sui 2.29.0 → 2.30.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 (36) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/bcs/bcs.d.mts +6 -6
  3. package/dist/bcs/index.d.mts +36 -36
  4. package/dist/client/core-resolver.d.mts.map +1 -1
  5. package/dist/client/core-resolver.mjs +2 -5
  6. package/dist/client/core-resolver.mjs.map +1 -1
  7. package/dist/cryptography/signature.d.mts +14 -14
  8. package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
  9. package/dist/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.mts +4 -4
  10. package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
  11. package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
  12. package/dist/grpc/proto/sui/rpc/v2/transaction_execution_service.client.d.mts +4 -4
  13. package/dist/transactions/Transaction.d.mts +10 -13
  14. package/dist/transactions/Transaction.d.mts.map +1 -1
  15. package/dist/transactions/Transaction.mjs.map +1 -1
  16. package/dist/transactions/intents/CoinWithBalance.mjs +4 -2
  17. package/dist/transactions/intents/CoinWithBalance.mjs.map +1 -1
  18. package/dist/transactions/resolution-utils.mjs +13 -0
  19. package/dist/transactions/resolution-utils.mjs.map +1 -0
  20. package/dist/transactions/resolve.d.mts +8 -0
  21. package/dist/transactions/resolve.d.mts.map +1 -1
  22. package/dist/transactions/resolve.mjs +6 -2
  23. package/dist/transactions/resolve.mjs.map +1 -1
  24. package/dist/version.mjs +1 -1
  25. package/dist/version.mjs.map +1 -1
  26. package/dist/zklogin/bcs.d.mts +14 -14
  27. package/docs/migrations/sui-2.0/json-rpc-migration.md +50 -1
  28. package/docs/transactions/basics.md +19 -14
  29. package/docs/transactions/offline.md +137 -88
  30. package/package.json +2 -2
  31. package/src/client/core-resolver.ts +2 -7
  32. package/src/transactions/Transaction.ts +1 -4
  33. package/src/transactions/intents/CoinWithBalance.ts +32 -25
  34. package/src/transactions/resolution-utils.ts +18 -0
  35. package/src/transactions/resolve.ts +29 -2
  36. package/src/version.ts +1 -1
@@ -1,31 +1,31 @@
1
- import * as _mysten_bcs1378 from "@mysten/bcs";
1
+ import * as _mysten_bcs1238 from "@mysten/bcs";
2
2
  import { InferBcsInput } from "@mysten/bcs";
3
3
 
4
4
  //#region src/zklogin/bcs.d.ts
5
- declare const zkLoginSignature: _mysten_bcs1378.BcsStruct<{
6
- inputs: _mysten_bcs1378.BcsStruct<{
7
- proofPoints: _mysten_bcs1378.BcsStruct<{
8
- a: _mysten_bcs1378.BcsType<string[], Iterable<string> & {
5
+ declare const zkLoginSignature: _mysten_bcs1238.BcsStruct<{
6
+ inputs: _mysten_bcs1238.BcsStruct<{
7
+ proofPoints: _mysten_bcs1238.BcsStruct<{
8
+ a: _mysten_bcs1238.BcsType<string[], Iterable<string> & {
9
9
  length: number;
10
10
  }, string>;
11
- b: _mysten_bcs1378.BcsType<string[][], Iterable<Iterable<string> & {
11
+ b: _mysten_bcs1238.BcsType<string[][], Iterable<Iterable<string> & {
12
12
  length: number;
13
13
  }> & {
14
14
  length: number;
15
15
  }, string>;
16
- c: _mysten_bcs1378.BcsType<string[], Iterable<string> & {
16
+ c: _mysten_bcs1238.BcsType<string[], Iterable<string> & {
17
17
  length: number;
18
18
  }, string>;
19
19
  }, string>;
20
- issBase64Details: _mysten_bcs1378.BcsStruct<{
21
- value: _mysten_bcs1378.BcsType<string, string, "string">;
22
- indexMod4: _mysten_bcs1378.BcsType<number, number, "u8">;
20
+ issBase64Details: _mysten_bcs1238.BcsStruct<{
21
+ value: _mysten_bcs1238.BcsType<string, string, "string">;
22
+ indexMod4: _mysten_bcs1238.BcsType<number, number, "u8">;
23
23
  }, string>;
24
- headerBase64: _mysten_bcs1378.BcsType<string, string, "string">;
25
- addressSeed: _mysten_bcs1378.BcsType<string, string, "string">;
24
+ headerBase64: _mysten_bcs1238.BcsType<string, string, "string">;
25
+ addressSeed: _mysten_bcs1238.BcsType<string, string, "string">;
26
26
  }, string>;
27
- maxEpoch: _mysten_bcs1378.BcsType<string, string | number | bigint, "u64">;
28
- userSignature: _mysten_bcs1378.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
27
+ maxEpoch: _mysten_bcs1238.BcsType<string, string | number | bigint, "u64">;
28
+ userSignature: _mysten_bcs1238.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
29
29
  }, string>;
30
30
  type ZkLoginSignature = InferBcsInput<typeof zkLoginSignature>;
31
31
  type ZkLoginSignatureInputs = ZkLoginSignature['inputs'];
@@ -95,7 +95,7 @@ use the same replacement under `client.core`.
95
95
  | `getBalance` | `getBalance` |
96
96
  | `getCoinMetadata` | `getCoinMetadata` |
97
97
  | `getDynamicFields` | `listDynamicFields` |
98
- | `getDynamicFieldObject` | `getDynamicField` or `client.core.getDynamicObjectField` |
98
+ | `getDynamicFieldObject` | `getDynamicField` or `getDynamicObjectField`, depending on field kind |
99
99
  | `getTransactionBlock` | `getTransaction` |
100
100
  | `multiGetTransactionBlocks` | Multiple `getTransaction` calls |
101
101
  | `executeTransactionBlock` | `executeTransaction` |
@@ -109,6 +109,55 @@ use the same replacement under `client.core`.
109
109
  | `resolveNameServiceAddress` | `resolveNameServiceAddress` (returns `{ address }`) |
110
110
  | `resolveNameServiceNames` | No direct equivalent for listing every name assigned to an address |
111
111
 
112
+ The JSON-RPC `getDynamicFieldObject` method returned an object for both field kinds. For a regular
113
+ dynamic field, it returned the `0x2::dynamic_field::Field<Name, Value>` object. For a dynamic object
114
+ field, it derived the wrapper field, extracted its child ID, and returned the referenced child
115
+ object instead of the wrapper.
116
+
117
+ The replacement APIs expose these operations separately. `getDynamicField` returns a normalized
118
+ field entry and its BCS-encoded value. `getDynamicObjectField` derives the wrapper, extracts the
119
+ child ID, and loads the referenced object. If the field kind is not known in advance, call
120
+ `listDynamicFields` and check whether its `$kind` is `DynamicField` or `DynamicObject`. SDKs can
121
+ call the same methods through `client.core`.
122
+
123
+ ```typescript
124
+ const page = await client.listDynamicFields({ parentId });
125
+
126
+ for (const field of page.dynamicFields) {
127
+ if (field.$kind === 'DynamicObject') {
128
+ const { object } = await client.getDynamicObjectField({
129
+ parentId,
130
+ name: field.name,
131
+ include: { content: true },
132
+ });
133
+
134
+ console.log(object.objectId, object.content);
135
+ } else {
136
+ const { dynamicField } = await client.getDynamicField({
137
+ parentId,
138
+ name: field.name,
139
+ });
140
+
141
+ console.log(dynamicField.value.type, dynamicField.value.bcs);
142
+ }
143
+ }
144
+ ```
145
+
146
+ The dynamic object field wrapper remains accessible through `getDynamicField`. Wrap the name type
147
+ explicitly and pass the original name BCS bytes:
148
+
149
+ ```typescript
150
+ const { dynamicField } = await client.getDynamicField({
151
+ parentId,
152
+ name: {
153
+ type: `0x2::dynamic_object_field::Wrapper<${fieldName.type}>`,
154
+ bcs: fieldName.bcs,
155
+ },
156
+ });
157
+
158
+ console.log(dynamicField.fieldId, dynamicField.childId);
159
+ ```
160
+
112
161
  `getMoveFunction` exposes normalized parameter signatures, but it does not reproduce the legacy
113
162
  `Pure`, `Object`, and object-access classifications from `getMoveFunctionArgTypes`. Likewise,
114
163
  `defaultNameServiceName` and raw gRPC `nameService.reverseLookupName` return only the configured
@@ -126,14 +126,13 @@ incorrect target strings. See the [codegen documentation](/codegen) for setup in
126
126
 
127
127
  ### Return values
128
128
 
129
- Commands return results that you can use as input to subsequent commands. For example, `splitCoins`
130
- returns the new coins it creates:
129
+ Helpers and commands return results that you can use as input to subsequent commands. For example,
130
+ `tx.coin()` creates a coin with the requested balance:
131
131
 
132
132
  ```typescript
133
133
  const tx = new Transaction();
134
134
 
135
- // splitCoins returns one result per amount
136
- const [coin] = tx.splitCoins(tx.gas, [1_000_000]);
135
+ const coin = tx.coin({ balance: 1_000_000 });
137
136
 
138
137
  // Use that result as input to another command
139
138
  tx.transferObjects([coin], '0xRecipientAddress');
@@ -256,21 +255,27 @@ libraries.
256
255
 
257
256
  ## Serializing transactions
258
257
 
259
- Serialize a transaction to JSON for storage, transmission, or later reconstruction:
258
+ `build()` produces BCS bytes for signing. With a client it resolves object versions, gas, and
259
+ intents like `tx.coin()` first. If everything is already set, no client is needed (see
260
+ [Building Offline](./offline)):
260
261
 
261
262
  ```typescript
262
- // Serialize to JSON with a client, resolves any unresolved data first
263
- const json = await tx.toJSON({ client: grpcClient });
264
-
265
- // Serialize without a client — intents like tx.coin() are preserved as-is
266
- const json = await tx.toJSON();
263
+ const bytes = await tx.build({ client: grpcClient });
267
264
 
268
- // Reconstruct from JSON
269
- const tx = Transaction.from(json);
265
+ // Only the inputs and commands, without sender or gas data
266
+ const kindBytes = await tx.build({ client: grpcClient, onlyTransactionKind: true });
270
267
  ```
271
268
 
272
- This is useful for passing transactions between a frontend and backend, or for storing pre-built
273
- transactions.
269
+ `toJSON()` produces an editable representation for storage or for passing between a frontend and
270
+ backend. It resolves intents like `tx.coin()` but leaves sender and gas data as they are:
271
+
272
+ ```typescript
273
+ const json = await tx.toJSON({ client: grpcClient });
274
+ const restored = Transaction.from(json);
275
+
276
+ // Or keep tx.coin() intents for the receiver to resolve
277
+ const jsonWithIntents = await tx.toJSON({ supportedIntents: ['CoinWithBalance'] });
278
+ ```
274
279
 
275
280
  ## Executing transactions
276
281
 
@@ -2,69 +2,132 @@
2
2
 
3
3
  > Build transactions without a network connection
4
4
 
5
- Normally the SDK resolves object versions, estimates gas, and fills in other details by querying the
6
- network. For offline building of backend services, air-gapped signing, or pre-built transactions,
7
- you must provide this information yourself. See also the Sui documentation on
5
+ Normally `build()` queries the network to resolve object versions, resolve intents like `tx.coin()`,
6
+ fetch the gas price, estimate the gas budget, pick gas coins, and set an expiration when gas is paid
7
+ from address balance. To build without a client, provide that information yourself. See also the Sui
8
+ documentation on
8
9
  [offline signing](https://docs.sui.io/guides/developer/transactions/transaction-auth/offline-signing)
9
10
  for the protocol-level details.
10
11
 
11
- ## Transactions without owned object inputs
12
+ ## Building only the transaction kind
12
13
 
13
- When your transaction only uses shared objects, party objects, and/or address balance withdrawals,
14
- there are no owned object versions to look up. Use `tx.withdrawal()` directly instead of `tx.coin()`
15
- or `coinWithBalance` because they require a client to resolve coin objects at build time.
14
+ If the next step only needs the inputs and commands (for example, a sponsor or a backend that fills
15
+ in gas), build with `onlyTransactionKind`. Sender, gas data, and expiration are left out:
16
16
 
17
17
  ```typescript
18
18
 
19
19
  const tx = new Transaction();
20
-
21
- // Use tx.withdrawal() + coin::redeem_funds to withdraw from address balance.
22
- // No coin object lookups needed — fully offline.
23
- const [coin] = tx.moveCall({
24
- target: '0x2::coin::redeem_funds',
25
- typeArguments: ['0x2::sui::SUI'],
26
- arguments: [tx.withdrawal({ amount: 1_000_000_000 })],
20
+ tx.moveCall({
21
+ target: '0xPackage::module::function',
22
+ arguments: [tx.pure.u64(100)],
27
23
  });
28
- tx.transferObjects([coin], '0xRecipientAddress');
29
24
 
30
- // Shared/party objects only need objectId + initialSharedVersion (both stable)
25
+ const kindBytes = await tx.build({ onlyTransactionKind: true });
26
+ ```
27
+
28
+ Inputs and intents still need to be resolved. Use full object references, `tx.withdrawal()`, or
29
+ [`assumeSufficientAddressBalances`](#coin-and-balance-intents) so nothing needs a lookup.
30
+
31
+ ## Building full transaction bytes
32
+
33
+ A full offline build needs the sender and all gas data:
34
+
35
+ | Method | Description |
36
+ | ----------------- | --------------------------------------------------------------- |
37
+ | `setSender()` | The address executing the transaction |
38
+ | `setGasPrice()` | Reference gas price (query `getReferenceGasPrice()` beforehand) |
39
+ | `setGasBudget()` | Maximum gas to spend (in MIST). Estimating it requires a client |
40
+ | `setGasPayment()` | Coin object references, or `[]` to pay gas from address balance |
41
+ | `setGasOwner()` | Only for sponsored transactions. Defaults to the sender |
42
+
43
+ ### Paying gas from address balance
44
+
45
+ With `setGasPayment([])`, gas is paid from the gas owner's SUI address balance. Nothing ties the
46
+ transaction to a specific object version, so it also needs a `ValidDuring` expiration for replay
47
+ protection:
48
+
49
+ ```typescript
50
+
51
+ // Look these up before going offline
52
+ const referenceGasPrice = 1000n;
53
+ const currentEpoch = 100;
54
+ const chainIdentifier = 'Base58ChainIdentifier'; // from getChainIdentifier()
55
+
56
+ const tx = new Transaction();
57
+
58
+ // FundsWithdrawal inputs contain the amount and type, so no object lookup is needed
31
59
  tx.moveCall({
32
- target: '0xPackage::module::function',
33
- arguments: [
34
- tx.sharedObjectRef({
35
- objectId: '0xSharedObjectId',
36
- initialSharedVersion: '1',
37
- mutable: true,
38
- }),
39
- ],
60
+ target: '0x2::balance::send_funds',
61
+ typeArguments: ['0x2::sui::SUI'],
62
+ arguments: [tx.withdrawal({ amount: 1_000_000_000 }), tx.pure.address('0xRecipientAddress')],
40
63
  });
41
64
 
42
- // Required configuration for all offline builds
43
65
  tx.setSender('0xSenderAddress');
44
- tx.setGasPrice(1000); // query getReferenceGasPrice() beforehand, or use a known value
66
+ tx.setGasPrice(referenceGasPrice);
45
67
  tx.setGasBudget(50_000_000);
46
- tx.setGasPayment([]); // empty array = pay gas from address balance
68
+ tx.setGasPayment([]);
47
69
 
48
- // Expiration is required when there are no owned objects for gas or inputs
49
70
  tx.setExpiration({
50
71
  ValidDuring: {
51
- minEpoch: 100, // current epoch
52
- maxEpoch: 101, // current epoch + 1
72
+ minEpoch: currentEpoch,
73
+ maxEpoch: currentEpoch + 1,
53
74
  minTimestamp: null,
54
75
  maxTimestamp: null,
55
- chain: 'mainnet', // or 'testnet', 'devnet'
56
- nonce: 0,
76
+ chain: chainIdentifier,
77
+ // Must be unique for each transaction in the validity window, including across restarts
78
+ nonce: await nonceStore.next(),
57
79
  },
58
80
  });
59
81
 
60
- // Build without a client
61
82
  const bytes = await tx.build();
62
83
  ```
63
84
 
64
- This enables fully stateless construction. You only need the sender address, reference gas price,
65
- epoch, and chain identifier.
85
+ Two otherwise-identical transactions with the same nonce have the same digest, and the second one is
86
+ rejected as a duplicate. Use a counter that survives restarts, not a hard-coded value.
87
+
88
+ The address balance needs to cover the gas budget on top of any withdrawals. Don't add the budget to
89
+ the withdrawal amount.
90
+
91
+ `{ Epoch: n }` expiration does not provide replay protection. Use `ValidDuring`.
66
92
 
67
- ## Party objects
93
+ ### Paying gas with coin objects
94
+
95
+ Gas coins need an exact version and digest. Other SUI can still come from address balance:
96
+
97
+ ```typescript
98
+
99
+ const tx = new Transaction();
100
+ tx.moveCall({
101
+ target: '0x2::balance::send_funds',
102
+ typeArguments: ['0x2::sui::SUI'],
103
+ arguments: [
104
+ tx.balance({ balance: 1_000_000, useGasCoin: false }),
105
+ tx.pure.address('0xRecipientAddress'),
106
+ ],
107
+ });
108
+
109
+ tx.setSender('0xSenderAddress');
110
+ tx.setGasPrice(1000);
111
+ tx.setGasBudget(50_000_000);
112
+ tx.setGasPayment([{ objectId: '0xGasCoinId', version: '3', digest: 'Base58GasCoinDigest' }]);
113
+
114
+ // assumeSufficientAddressBalances resolves tx.balance() without a client (see below)
115
+ const bytes = await tx.build({ assumeSufficientAddressBalances: true });
116
+ ```
117
+
118
+ ## Object inputs
119
+
120
+ ### Shared and party objects
121
+
122
+ Shared objects only need `objectId` and `initialSharedVersion`, both of which are stable:
123
+
124
+ ```typescript
125
+ tx.sharedObjectRef({
126
+ objectId: '0xSharedObjectId',
127
+ initialSharedVersion: '1',
128
+ mutable: true,
129
+ });
130
+ ```
68
131
 
69
132
  Party objects are address-owned but consensus-versioned, with per-address permissions. They are
70
133
  referenced the same way as shared objects:
@@ -83,12 +146,11 @@ Key properties for offline building:
83
146
  becomes a party object
84
147
  - **Enable pipelining**: Submit multiple transactions on the same party object without waiting for
85
148
  each one to finalize
86
- - **Cannot be used for gas**: Use address balance for gas payment (`setGasPayment([])`)
149
+ - **Cannot be used for gas**: Pay gas from address balance or with a SUI coin object
87
150
 
88
- ## Transactions with owned object inputs
151
+ ### Owned and immutable objects
89
152
 
90
- When your transaction uses owned or immutable objects, you must provide the exact version and digest
91
- for each one:
153
+ Owned and immutable objects need the exact version and digest:
92
154
 
93
155
  ```typescript
94
156
 
@@ -122,71 +184,58 @@ tx.moveCall({
122
184
  }),
123
185
  ],
124
186
  });
125
-
126
- // Gas payment with specific coin objects
127
- tx.setGasPayment([{ objectId: '0xGasCoinId', version: '3', digest: 'jkl012...' }]);
128
-
129
- tx.setSender('0xSenderAddress');
130
- tx.setGasPrice(1000);
131
- tx.setGasBudget(50_000_000);
132
-
133
- const bytes = await tx.build();
134
187
  ```
135
188
 
136
- ## Required configuration for all offline builds
189
+ ## Coin and balance intents
137
190
 
138
- Every offline-built transaction must have the following:
191
+ `tx.coin()` and `tx.balance()` normally look up the sender's address balance and coin objects. Pass
192
+ `assumeSufficientAddressBalances` to skip the lookups and withdraw from address balance instead:
139
193
 
140
- | Method | Description |
141
- | ----------------- | --------------------------------------------------------------- |
142
- | `setSender()` | The address executing the transaction |
143
- | `setGasPrice()` | Reference gas price (query `getReferenceGasPrice()` beforehand) |
144
- | `setGasBudget()` | Maximum gas to spend (in MIST) |
145
- | `setGasPayment()` | Coin object references, or `[]` for address balance |
194
+ ```typescript
146
195
 
147
- ### Expiration
196
+ const tx = new Transaction();
197
+ tx.moveCall({
198
+ target: '0x2::balance::send_funds',
199
+ typeArguments: ['0xPackage::module::TOKEN'],
200
+ arguments: [
201
+ tx.balance({ type: '0xPackage::module::TOKEN', balance: 1_000_000 }),
202
+ tx.pure.address('0xRecipientAddress'),
203
+ ],
204
+ });
148
205
 
149
- Set an expiration when your transaction uses no owned objects for gas or inputs. This applies when
150
- you use address balances for gas (`setGasPayment([])`) with only shared and party object inputs:
206
+ // The sender is still required, since the withdrawal comes from its address balance
207
+ tx.setSender('0xSenderAddress');
151
208
 
152
- ```typescript
153
- tx.setExpiration({
154
- ValidDuring: {
155
- minEpoch: 100, // current epoch
156
- maxEpoch: 101, // typically current epoch + 1
157
- minTimestamp: null,
158
- maxTimestamp: null,
159
- chain: 'mainnet',
160
- nonce: 0, // increment for multiple transactions in the same epoch
161
- },
209
+ const kindBytes = await tx.build({
210
+ onlyTransactionKind: true,
211
+ assumeSufficientAddressBalances: true,
162
212
  });
163
213
  ```
164
214
 
165
- You can also use epoch-based expiration:
215
+ > **Warning:** Nothing is checked. The transaction builds, then fails at execution if the address balance doesn't
216
+ > cover it.
166
217
 
167
- ```typescript
168
- tx.setExpiration({ Epoch: 100 });
169
- ```
218
+ SUI intents withdraw from address balance too, whether or not the transaction also uses `tx.gas`.
219
+ This matches an online build when the address balance is sufficient. Mixing default and
220
+ `useGasCoin: false` SUI intents in one transaction is still an error.
170
221
 
171
- > **Note:** When building with a client, the SDK sets expiration automatically. You only need the manual
172
- > configuration above for fully offline builds.
222
+ On a full build, the option also sets an unset gas payment to `[]`, but only when nothing else needs
223
+ a client, the transaction doesn't use `tx.gas`, and a `ValidDuring` or `Validity` expiration is
224
+ already set. Sender, gas price, and gas budget still need to be provided. Once set, the empty
225
+ payment is part of the transaction, the same as calling `setGasPayment([])`.
173
226
 
174
227
  ## Serialization
175
228
 
176
- ### Building to bytes
229
+ `toJSON()` resolves async thunks and intents but does not fill in gas or object versions. Pass
230
+ `supportedIntents` to keep an intent for another system to resolve:
177
231
 
178
232
  ```typescript
179
- // Build to BCS bytes (Uint8Array) fully offline, all data must be provided
180
- const bytes = await tx.build();
181
-
182
- // Build with a client — only makes network requests when there is unresolved data to look up
183
- const bytes = await tx.build({ client: grpcClient });
184
- ```
233
+ // Intents are resolved, so tx.coin() needs a client or assumeSufficientAddressBalances
234
+ const json = await tx.toJSON({ assumeSufficientAddressBalances: true });
235
+ const restored = Transaction.from(json);
185
236
 
186
- ### Converting bytes back to a Transaction
187
-
188
- ```typescript
189
- const tx = Transaction.from(bytes);
237
+ // Or keep tx.coin() intents for the receiver to resolve
238
+ const jsonWithIntents = await tx.toJSON({ supportedIntents: ['CoinWithBalance'] });
190
239
  ```
191
240
 
192
- This works with BCS byte arrays, base64-encoded strings, and JSON strings (from `toJSON()`).
241
+ `Transaction.from()` accepts JSON strings, BCS bytes, and base64-encoded BCS.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Mysten Labs <build@mystenlabs.com>",
4
4
  "description": "Sui TypeScript API",
5
5
  "homepage": "https://sdk.mystenlabs.com",
6
- "version": "2.29.0",
6
+ "version": "2.30.0",
7
7
  "license": "Apache-2.0",
8
8
  "sideEffects": false,
9
9
  "files": [
@@ -151,7 +151,7 @@
151
151
  "typescript": "^7.0.2",
152
152
  "vite": "^8.2.1",
153
153
  "vite-tsconfig-paths": "^6.0.4",
154
- "vitest": "^4.1.10",
154
+ "vitest": "^4.1.11",
155
155
  "wait-on": "^9.1.0"
156
156
  },
157
157
  "dependencies": {
@@ -17,6 +17,7 @@ import { getPureBcsSchema, isTxContext } from '../transactions/serializer.js';
17
17
  import type { TransactionDataBuilder } from '../transactions/TransactionData.js';
18
18
  import { chunk } from '@mysten/utils';
19
19
  import type { BuildTransactionOptions } from '../transactions/index.js';
20
+ import { transactionUsesGasCoin } from '../transactions/resolution-utils.js';
20
21
 
21
22
  // The maximum objects that can be fetched at once using multiGetObjects.
22
23
  const MAX_OBJECTS_PER_FETCH = 50;
@@ -54,19 +55,13 @@ export async function coreClientResolveTransactionPlugin(
54
55
  next: () => Promise<void>,
55
56
  ) {
56
57
  const client = getClient(options);
57
-
58
58
  const needsGasPrice = !options.onlyTransactionKind && !transactionData.gasData.price;
59
59
  const needsPayment = !options.onlyTransactionKind && !transactionData.gasData.payment;
60
60
  const gasPayer = transactionData.gasData.owner ?? transactionData.sender;
61
61
 
62
- let usesGasCoin = false;
62
+ const usesGasCoin = transactionUsesGasCoin(transactionData);
63
63
  let withdrawals = 0n;
64
64
 
65
- transactionData.mapArguments((arg) => {
66
- if (arg.$kind === 'GasCoin') usesGasCoin = true;
67
- return arg;
68
- });
69
-
70
65
  const normalizedGasPayer = gasPayer ? normalizeSuiAddress(gasPayer) : null;
71
66
  for (const input of transactionData.inputs) {
72
67
  if (input.$kind !== 'FundsWithdrawal' || !normalizedGasPayer) continue;
@@ -37,7 +37,6 @@ import {
37
37
  coinWithBalance,
38
38
  createBalance,
39
39
  } from './intents/CoinWithBalance.js';
40
- import type { ClientWithCoreApi } from '../client/core.js';
41
40
 
42
41
  export type TransactionObjectArgument =
43
42
  | Exclude<InferInput<typeof ArgumentSchema>, { Input: unknown; type?: 'pure' }>
@@ -802,9 +801,7 @@ export class Transaction {
802
801
 
803
802
  /** Derive transaction digest */
804
803
  async getDigest(
805
- options: {
806
- client?: ClientWithCoreApi;
807
- } = {},
804
+ options: Pick<BuildTransactionOptions, 'client' | 'assumeSufficientAddressBalances'> = {},
808
805
  ): Promise<string> {
809
806
  await this.prepareForSerialization(options);
810
807
  await this.#prepareBuild(options);
@@ -178,37 +178,44 @@ export async function resolveCoinBalance(
178
178
  const coinsByType = new Map<string, SuiClientTypes.Coin[]>();
179
179
  const addressBalanceByType = new Map<string, bigint>();
180
180
  const client = buildOptions.client;
181
+ const assumeSufficientAddressBalances = buildOptions.assumeSufficientAddressBalances;
181
182
 
182
- if (!client) {
183
+ if (!client && !assumeSufficientAddressBalances) {
183
184
  throw new Error(
184
185
  'Client must be provided to build or serialize transactions with CoinWithBalance intents',
185
186
  );
186
187
  }
187
188
 
188
- await Promise.all([
189
- ...[...coinTypes].map(async (coinType) => {
190
- const { coins, addressBalance } = await getCoinsAndBalanceOfType({
191
- coinType,
192
- balance: totalByType.get(coinType)!,
193
- client,
194
- owner: transactionData.sender!,
195
- usedIds,
196
- });
197
-
198
- coinsByType.set(coinType, coins);
199
- addressBalanceByType.set(coinType, addressBalance);
200
- }),
201
- totalByType.has('gas')
202
- ? await client.core
203
- .getBalance({
204
- owner: transactionData.sender!,
205
- coinType: SUI_TYPE,
206
- })
207
- .then(({ balance }) => {
208
- addressBalanceByType.set('gas', BigInt(balance.addressBalance));
209
- })
210
- : null,
211
- ]);
189
+ if (assumeSufficientAddressBalances) {
190
+ for (const [coinType, balance] of totalByType) {
191
+ addressBalanceByType.set(coinType, balance);
192
+ }
193
+ } else {
194
+ await Promise.all([
195
+ ...[...coinTypes].map(async (coinType) => {
196
+ const { coins, addressBalance } = await getCoinsAndBalanceOfType({
197
+ coinType,
198
+ balance: totalByType.get(coinType)!,
199
+ client: client!,
200
+ owner: transactionData.sender!,
201
+ usedIds,
202
+ });
203
+
204
+ coinsByType.set(coinType, coins);
205
+ addressBalanceByType.set(coinType, addressBalance);
206
+ }),
207
+ totalByType.has('gas')
208
+ ? await client!.core
209
+ .getBalance({
210
+ owner: transactionData.sender!,
211
+ coinType: SUI_TYPE,
212
+ })
213
+ .then(({ balance }) => {
214
+ addressBalanceByType.set('gas', BigInt(balance.addressBalance));
215
+ })
216
+ : null,
217
+ ]);
218
+ }
212
219
 
213
220
  const exactBalanceByType = new Map<string, boolean>();
214
221
  const usedAddressBalance = new Set<string>();
@@ -0,0 +1,18 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { TransactionDataBuilder } from './TransactionData.js';
5
+
6
+ export function transactionUsesGasCoin(transactionData: TransactionDataBuilder) {
7
+ let usesGasCoin = false;
8
+
9
+ transactionData.mapArguments((arg) => {
10
+ if (arg.$kind === 'GasCoin') {
11
+ usesGasCoin = true;
12
+ }
13
+
14
+ return arg;
15
+ });
16
+
17
+ return usesGasCoin;
18
+ }