@mysten/sui 2.17.0 → 2.18.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 (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/grpc/proto/sui/rpc/v2/ledger_service.client.d.mts +4 -4
  3. package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
  4. package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
  5. package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
  6. package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
  7. package/dist/transactions/Transaction.d.mts +16 -2
  8. package/dist/transactions/Transaction.d.mts.map +1 -1
  9. package/dist/transactions/Transaction.mjs +8 -3
  10. package/dist/transactions/Transaction.mjs.map +1 -1
  11. package/dist/transactions/index.d.mts +2 -2
  12. package/dist/version.mjs +1 -1
  13. package/dist/version.mjs.map +1 -1
  14. package/dist/zklogin/jwt-utils.d.mts.map +1 -1
  15. package/dist/zklogin/jwt-utils.mjs.map +1 -1
  16. package/dist/zklogin/utils.d.mts.map +1 -1
  17. package/dist/zklogin/utils.mjs +9 -0
  18. package/dist/zklogin/utils.mjs.map +1 -1
  19. package/docs/bcs.md +11 -6
  20. package/docs/clients/core.md +10 -9
  21. package/docs/clients/grpc.md +1 -1
  22. package/docs/index.md +176 -22
  23. package/docs/llms-index.md +6 -9
  24. package/docs/migrations/0.38.md +2 -2
  25. package/docs/migrations/sui-1.0.md +2 -2
  26. package/docs/migrations/sui-2.0/json-rpc-migration.md +76 -33
  27. package/docs/plugins.md +29 -5
  28. package/docs/transactions/basics.md +279 -0
  29. package/docs/transactions/coins-and-balances.md +293 -0
  30. package/docs/transactions/offline.md +192 -0
  31. package/docs/transactions/reference.md +380 -0
  32. package/docs/transactions/signing-and-execution.md +401 -0
  33. package/package.json +26 -26
  34. package/src/transactions/Transaction.ts +36 -5
  35. package/src/transactions/index.ts +1 -0
  36. package/src/version.ts +1 -1
  37. package/src/zklogin/jwt-utils.ts +3 -0
  38. package/src/zklogin/utils.ts +17 -0
  39. package/docs/faucet.md +0 -26
  40. package/docs/hello-sui.md +0 -115
  41. package/docs/install.md +0 -61
  42. package/docs/transaction-building/basics.md +0 -299
  43. package/docs/transaction-building/gas.md +0 -61
  44. package/docs/transaction-building/intents.md +0 -62
  45. package/docs/transaction-building/offline.md +0 -73
  46. package/docs/transaction-building/sponsored-transactions.md +0 -22
@@ -0,0 +1,279 @@
1
+ # Building Transactions
2
+
3
+ > Construct programmable transaction blocks with the Transaction API
4
+
5
+ Every interaction with the Sui network goes through a transaction. Sui transactions are a sequence
6
+ of commands called
7
+ [programmable transaction blocks (PTBs)](https://docs.sui.io/guides/developer/transactions/prog-txn-blocks)
8
+ that execute on inputs to produce a result. Commands within a transaction can call Move functions,
9
+ transfer objects, split and merge coins, and more. You can chain the result of one command into a
10
+ subsequent command, composing complex operations in a single transaction.
11
+
12
+ All commands in a transaction execute atomically. If any command fails, the entire transaction is
13
+ rolled back and none of its effects are applied.
14
+
15
+ For more information on transaction model, see the
16
+ [Sui documentation on PTBs](https://docs.sui.io/guides/developer/transactions/prog-txn-blocks) and
17
+ [inputs and results](https://docs.sui.io/concepts/transactions/inputs-and-results).
18
+
19
+ ## Creating a transaction
20
+
21
+ Import the `Transaction` class and create a new instance:
22
+
23
+ ```typescript
24
+
25
+ const tx = new Transaction();
26
+ ```
27
+
28
+ ## Sending SUI
29
+
30
+ Use `tx.balance()` and `balance::send_funds` to send SUI; this deposits directly into the
31
+ recipient's address balance:
32
+
33
+ ```typescript
34
+
35
+ const tx = new Transaction();
36
+
37
+ tx.moveCall({
38
+ target: '0x2::balance::send_funds',
39
+ typeArguments: ['0x2::sui::SUI'],
40
+ arguments: [tx.balance({ balance: 1n * MIST_PER_SUI }), tx.pure.address('0xRecipientAddress')],
41
+ });
42
+ ```
43
+
44
+ If the recipient needs a `Coin<T>` object, use `tx.coin()` with `transferObjects` instead:
45
+
46
+ ```typescript
47
+ tx.transferObjects([tx.coin({ balance: 1n * MIST_PER_SUI })], '0xRecipientAddress');
48
+ ```
49
+
50
+ Both `tx.balance()` and `tx.coin()` automatically draw from your coin objects and address balances.
51
+ You don't need to manage individual coins yourself. See [Coins and Balances](./coins-and-balances)
52
+ for full details.
53
+
54
+ ## Sending other tokens
55
+
56
+ For non-SUI tokens, use the same patterns for passing the coin type:
57
+
58
+ ```typescript
59
+ const tx = new Transaction();
60
+
61
+ // Send to address balance (preferred)
62
+ tx.moveCall({
63
+ target: '0x2::balance::send_funds',
64
+ typeArguments: ['0xPackageId::module::CoinType'],
65
+ arguments: [
66
+ tx.balance({ balance: 1_000_000, type: '0xPackageId::module::CoinType' }),
67
+ tx.pure.address('0xRecipientAddress'),
68
+ ],
69
+ });
70
+
71
+ // Or send as a coin object
72
+ tx.transferObjects(
73
+ [tx.coin({ balance: 1_000_000, type: '0xPackageId::module::CoinType' })],
74
+ '0xRecipientAddress',
75
+ );
76
+ ```
77
+
78
+ ## Calling Move functions
79
+
80
+ Use `moveCall` to call any function in a published Move package. The Sui framework at `0x2` provides
81
+ many built-in functions you can call directly. For example, to split a coin:
82
+
83
+ ```typescript
84
+ const tx = new Transaction();
85
+
86
+ const [newCoin] = tx.moveCall({
87
+ target: '0x2::coin::split',
88
+ typeArguments: ['0x2::sui::SUI'],
89
+ arguments: [tx.object('0xCoinId'), tx.pure.u64(1000)],
90
+ });
91
+
92
+ tx.transferObjects([newCoin], '0xRecipientAddress');
93
+ ```
94
+
95
+ The `target` format is `packageId::moduleName::functionName`.
96
+
97
+ To call functions in your own published packages, use the same pattern with your package ID:
98
+
99
+ ```typescript
100
+ tx.moveCall({
101
+ target: '0xYourPackageId::module::function_name',
102
+ arguments: [tx.pure.string('hello'), tx.object('0xSomeObjectId')],
103
+ });
104
+ ```
105
+
106
+ ### Using codegen for type-safe calls
107
+
108
+ The [`@mysten/codegen`](/codegen) package generates type-safe TypeScript functions from your Move
109
+ packages. Instead of writing `moveCall` with string targets and manual argument construction, use
110
+ `tx.add()` with generated functions:
111
+
112
+ ```typescript
113
+
114
+ const tx = new Transaction();
115
+ tx.add(
116
+ counter.increment({
117
+ arguments: {
118
+ counter: '0x123...', // Counter object ID
119
+ },
120
+ }),
121
+ );
122
+ ```
123
+
124
+ This gives you IDE autocompletion, compile-time type checking for arguments, and eliminates
125
+ incorrect target strings. See the [codegen documentation](/codegen) for setup instructions.
126
+
127
+ ### Return values
128
+
129
+ Commands return results that you can use as input to subsequent commands. For example, `splitCoins`
130
+ returns the new coins it creates:
131
+
132
+ ```typescript
133
+ const tx = new Transaction();
134
+
135
+ // splitCoins returns one result per amount
136
+ const [coin] = tx.splitCoins(tx.gas, [1_000_000]);
137
+
138
+ // Use that result as input to another command
139
+ tx.transferObjects([coin], '0xRecipientAddress');
140
+ ```
141
+
142
+ `moveCall` works the same way. When a Move function returns a value, you can capture it and pass it
143
+ to the next command:
144
+
145
+ ```typescript
146
+ const [nft] = tx.moveCall({
147
+ target: '0xPackageId::nft::mint',
148
+ arguments: [tx.pure.string('My NFT'), tx.pure.string('Description')],
149
+ });
150
+
151
+ tx.transferObjects([nft], '0xRecipientAddress');
152
+ ```
153
+
154
+ When a command returns multiple values, destructure or index into the result:
155
+
156
+ ```typescript
157
+ // Destructuring
158
+ const [nft1, nft2] = tx.moveCall({ target: '0xPackageId::nft::mint_pair' });
159
+
160
+ // Or indexing
161
+ const result = tx.moveCall({ target: '0xPackageId::nft::mint_pair' });
162
+ const firstNft = result[0];
163
+ const secondNft = result[1];
164
+ ```
165
+
166
+ > **Warning:** Always access elements by index or destructuring. Do not use the spread operator (`...result`) or
167
+ > pass results to `Array.from()`, which causes an infinite loop.
168
+
169
+ ### Type arguments
170
+
171
+ Some Move functions have generic type parameters. Pass them with `typeArguments`:
172
+
173
+ ```typescript
174
+ tx.moveCall({
175
+ target: '0x2::coin::split',
176
+ typeArguments: ['0x2::sui::SUI'],
177
+ arguments: [tx.object('0xCoinId'), tx.pure.u64(1000)],
178
+ });
179
+ ```
180
+
181
+ ## Chaining commands
182
+
183
+ The result of any command can be used as input to a subsequent command. This is what makes
184
+ transactions **programmable**. You can compose multiple operations into a single atomic transaction:
185
+
186
+ ```typescript
187
+ const tx = new Transaction();
188
+
189
+ // Mint an NFT
190
+ const [nft] = tx.moveCall({
191
+ target: '0xPackageId::nft::mint',
192
+ arguments: [tx.pure.string('My NFT')],
193
+ });
194
+
195
+ // Set a property on it
196
+ tx.moveCall({
197
+ target: '0xPackageId::nft::set_description',
198
+ arguments: [nft, tx.pure.string('A nice NFT')],
199
+ });
200
+
201
+ // Transfer it to someone
202
+ tx.transferObjects([nft], '0xRecipientAddress');
203
+ ```
204
+
205
+ ## Batch transfers
206
+
207
+ Send tokens to multiple recipients in a single transaction:
208
+
209
+ ```typescript
210
+
211
+ const transfers = [
212
+ { to: '0xAlice', amount: 1_000_000_000 },
213
+ { to: '0xBob', amount: 2_000_000_000 },
214
+ { to: '0xCarol', amount: 500_000_000 },
215
+ ];
216
+
217
+ const tx = new Transaction();
218
+
219
+ for (const transfer of transfers) {
220
+ // Send to address balances (preferred)
221
+ tx.moveCall({
222
+ target: '0x2::balance::send_funds',
223
+ typeArguments: ['0x2::sui::SUI'],
224
+ arguments: [tx.balance({ balance: transfer.amount }), tx.pure.address(transfer.to)],
225
+ });
226
+ }
227
+
228
+ // Or send as coin objects
229
+ for (const transfer of transfers) {
230
+ tx.transferObjects([tx.coin({ balance: transfer.amount })], transfer.to);
231
+ }
232
+ ```
233
+
234
+ ## Composable transaction building with thunks
235
+
236
+ The `tx.add()` method accepts thunks, which are functions that receive the transaction and add
237
+ commands to it. This enables building reusable, composable transaction pieces:
238
+
239
+ ```typescript
240
+ function mintNft(name: string) {
241
+ return (tx: Transaction) => {
242
+ return tx.moveCall({
243
+ target: '0xPackage::nft::mint',
244
+ arguments: [tx.pure.string(name)],
245
+ });
246
+ };
247
+ }
248
+
249
+ const tx = new Transaction();
250
+ const [nft] = tx.add(mintNft('My NFT'));
251
+ tx.transferObjects([nft], '0xRecipientAddress');
252
+ ```
253
+
254
+ See [SDK Building](/sui/sdk-building) for more information on building composable transaction
255
+ libraries.
256
+
257
+ ## Serializing transactions
258
+
259
+ Serialize a transaction to JSON for storage, transmission, or later reconstruction:
260
+
261
+ ```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();
267
+
268
+ // Reconstruct from JSON
269
+ const tx = Transaction.from(json);
270
+ ```
271
+
272
+ This is useful for passing transactions between a frontend and backend, or for storing pre-built
273
+ transactions.
274
+
275
+ ## Executing transactions
276
+
277
+ Once you've built a transaction, see [Signing and Execution](./signing-and-execution) for all the
278
+ ways to sign and submit it, such as keypairs, [`dapp-kit`](/dapp-kit) hooks, sponsored transactions,
279
+ and more.
@@ -0,0 +1,293 @@
1
+ # Coins and Balances
2
+
3
+ > Work with coin objects and address balances in transactions
4
+
5
+ Sui has two systems for holding fungible token balances: coin objects and address balances.
6
+
7
+ **Coin objects** are individual onchain objects, each with its own ID, version, and balance. You own
8
+ specific coin objects and need to split, merge, and track them.
9
+
10
+ **Address balances** are an accumulator per address per coin type. There are no objects to manage
11
+ because deposits automatically merge into a single balance, and you withdraw from it as needed.
12
+
13
+ Both systems coexist. An address's total balance for a given coin type is the sum of its coin object
14
+ balances and its address balance.
15
+
16
+ Address balances have no object versions. When a transaction has no versioned object inputs (for
17
+ example, a balance transfer built entirely from address balance withdrawals), it won't be
18
+ invalidated by other transactions from the same address executing concurrently.
19
+
20
+ ## `tx.coin` and `tx.balance`
21
+
22
+ `tx.coin()` and `tx.balance()` are the recommended ways to get tokens in a transaction. They
23
+ automatically draw from both coin objects and address balances.
24
+
25
+ ### Getting a `Coin`
26
+
27
+ `tx.coin()` produces a `Coin<T>`. Use it for transfers and most operations:
28
+
29
+ ```tsx
30
+
31
+ const tx = new Transaction();
32
+
33
+ // SUI (balance is in MIST — 1 SUI = 1,000,000,000 MIST)
34
+ tx.transferObjects([tx.coin({ balance: 1_000_000_000n })], '0xRecipientAddress');
35
+
36
+ // Another coin type
37
+ tx.transferObjects(
38
+ [tx.coin({ balance: 1_000_000n, type: '0xPackageId::module::CoinType' })],
39
+ '0xRecipientAddress',
40
+ );
41
+ ```
42
+
43
+ ### Getting a `Balance`
44
+
45
+ `tx.balance()` produces a `Balance<T>`. Use it for Move functions that expect a balance directly, or
46
+ for gasless transactions:
47
+
48
+ ```tsx
49
+ const tx = new Transaction();
50
+
51
+ tx.moveCall({
52
+ target: '0xPackage::module::deposit',
53
+ arguments: [tx.object('0xPoolId'), tx.balance({ balance: 1_000_000_000n })],
54
+ });
55
+ ```
56
+
57
+ ### Sending to address balances
58
+
59
+ To deposit tokens into a recipient's address balance (instead of creating a coin object), use
60
+ `tx.balance()` with `balance::send_funds`:
61
+
62
+ ```tsx
63
+ const tx = new Transaction();
64
+
65
+ tx.moveCall({
66
+ target: '0x2::balance::send_funds',
67
+ typeArguments: ['0x2::sui::SUI'],
68
+ arguments: [tx.balance({ balance: 1_000_000_000n }), tx.pure.address('0xRecipientAddress')],
69
+ });
70
+ ```
71
+
72
+ > **Note:** Transactions built entirely from `tx.balance()` and gasless-eligible Move calls like `send_funds`
73
+ > and `redeem_funds` might qualify as [gasless transactions](#gasless-transactions).
74
+
75
+ ### Options
76
+
77
+ | Option | Type | Default | Description |
78
+ | ------------ | ------------------ | ---------- | ------------------------------------------------------------------------ |
79
+ | `balance` | `bigint \| number` | _required_ | Amount in base units (MIST for SUI) |
80
+ | `type` | `string` | SUI | Coin type. Defaults to `0x2::sui::SUI` |
81
+ | `useGasCoin` | `boolean` | `true` | For SUI, split from the gas coin. Set `false` for sponsored transactions |
82
+
83
+ For SUI, `tx.coin()` splits from the gas coin by default. For sponsored transactions where the gas
84
+ coin belongs to the sponsor, set `useGasCoin: false`:
85
+
86
+ ```tsx
87
+ tx.transferObjects([tx.coin({ balance: 100n, useGasCoin: false })], recipient);
88
+ ```
89
+
90
+ ### `coinWithBalance`
91
+
92
+ `coinWithBalance()` is a standalone alias for `tx.coin()`:
93
+
94
+ ```tsx
95
+
96
+ const tx = new Transaction();
97
+ tx.transferObjects([coinWithBalance({ balance: 1_000_000_000 })], recipient);
98
+ ```
99
+
100
+ ## How resolution works
101
+
102
+ When you call `tx.coin()` or `tx.balance()`, the SDK adds a placeholder intent to the transaction.
103
+ At build time, the resolver replaces it with concrete commands based on the sender's funds:
104
+
105
+ - **`tx.balance()` with sufficient address balance:** Uses a direct `FundsWithdrawal` through
106
+ `balance::redeem_funds`. No coin objects are used, so the transaction has no versioned object
107
+ inputs from this intent, keeping it eligible for parallel execution.
108
+
109
+ - **Otherwise, coins are needed:** Fetches the sender's coin objects and address balance in
110
+ parallel. Merges available coins (topping up from address balance if needed), then splits the
111
+ exact amounts. For `tx.balance()` intents, the split results are converted to `Balance<T>` through
112
+ `coin::into_balance`, and any remainder is returned to the sender's address balance through
113
+ `coin::send_funds`.
114
+
115
+ The resolver prefers address balances when possible to avoid introducing versioned object
116
+ dependencies.
117
+
118
+ Zero-balance requests resolve to `balance::zero` or `coin::zero` with no network lookups.
119
+
120
+ ## Checking balances
121
+
122
+ Use `getBalance` to see both coin objects and address balance:
123
+
124
+ ```tsx
125
+ const { balance } = await grpcClient.getBalance({
126
+ owner: '0xMyAddress',
127
+ });
128
+
129
+ console.log(balance.balance); // total balance as string (coin objects + address balance)
130
+ console.log(balance.coinBalance); // balance from coin objects only
131
+ console.log(balance.addressBalance); // balance from address balance only
132
+ console.log(balance.coinType); // e.g. "0x2::sui::SUI"
133
+ ```
134
+
135
+ All balance values are returned as strings. Use `BigInt(balance.balance)` for arithmetic.
136
+
137
+ ## Manual coin operations
138
+
139
+ For fine-grained control, you can split and merge coins manually.
140
+
141
+ ### Splitting coins
142
+
143
+ `splitCoins` creates new coins from an existing coin:
144
+
145
+ ```tsx
146
+ const tx = new Transaction();
147
+
148
+ // Split specific amounts from a coin you own
149
+ const [coin1, coin2] = tx.splitCoins('0xMyCoinId', [1_000_000, 2_000_000]);
150
+
151
+ tx.transferObjects([coin1], '0xAlice');
152
+ tx.transferObjects([coin2], '0xBob');
153
+ ```
154
+
155
+ Split the gas coin for SUI:
156
+
157
+ ```tsx
158
+ const [coin] = tx.splitCoins(tx.gas, [1_000_000_000]);
159
+ tx.transferObjects([coin], '0xRecipientAddress');
160
+ ```
161
+
162
+ ### Merging coins
163
+
164
+ `mergeCoins` combines multiple coins into one:
165
+
166
+ ```tsx
167
+ const tx = new Transaction();
168
+
169
+ tx.mergeCoins('0xCoin1', ['0xCoin2', '0xCoin3']);
170
+ ```
171
+
172
+ ## Working with address balances directly
173
+
174
+ ### Withdrawing from address balance
175
+
176
+ Create a withdrawal input and redeem it to get a `Coin<T>`:
177
+
178
+ ```tsx
179
+ const tx = new Transaction();
180
+
181
+ const [coin] = tx.moveCall({
182
+ target: '0x2::coin::redeem_funds',
183
+ typeArguments: ['0x2::sui::SUI'],
184
+ arguments: [tx.withdrawal({ amount: 1_000_000_000 })],
185
+ });
186
+
187
+ tx.transferObjects([coin], '0xRecipientAddress');
188
+ ```
189
+
190
+ Or get a `Balance<T>` directly:
191
+
192
+ ```tsx
193
+ const [balance] = tx.moveCall({
194
+ target: '0x2::balance::redeem_funds',
195
+ typeArguments: ['0x2::sui::SUI'],
196
+ arguments: [tx.withdrawal({ amount: 1_000_000_000 })],
197
+ });
198
+ ```
199
+
200
+ For non-SUI coin types, pass the `type` parameter:
201
+
202
+ ```tsx
203
+ const [coin] = tx.moveCall({
204
+ target: '0x2::coin::redeem_funds',
205
+ typeArguments: ['0xPackageId::module::USDC'],
206
+ arguments: [tx.withdrawal({ amount: 1_000_000, type: '0xPackageId::module::USDC' })],
207
+ });
208
+ ```
209
+
210
+ ### Depositing into address balances
211
+
212
+ Use `coin::send_funds` to deposit a coin into a recipient's address balance:
213
+
214
+ ```tsx
215
+ const tx = new Transaction();
216
+
217
+ tx.moveCall({
218
+ target: '0x2::coin::send_funds',
219
+ typeArguments: ['0x2::sui::SUI'],
220
+ arguments: [tx.object('0xMyCoinObjectId'), tx.pure.address('0xRecipientAddress')],
221
+ });
222
+ ```
223
+
224
+ ## Listing coin objects
225
+
226
+ Use `listCoins` to see specific coin objects:
227
+
228
+ ```tsx
229
+ const { objects, hasNextPage, cursor } = await grpcClient.listCoins({
230
+ owner: '0xMyAddress',
231
+ });
232
+
233
+ for (const coin of objects) {
234
+ console.log(coin.objectId, coin.balance);
235
+ }
236
+ ```
237
+
238
+ ## Gasless transactions
239
+
240
+ Gasless transactions enable peer-to-peer payments of qualified stablecoins to execute without paying
241
+ gas fees in SUI. The sender does not need to hold SUI in their wallet. Gasless transactions are an
242
+ extension of
243
+ [address balances](https://docs.sui.io/onchain-finance/asset-custody/address-balances/using-address-balances),
244
+ built around `0x2::balance::send_funds` with `gasPrice = 0` and `gasBudget = 0` on the transaction.
245
+ See the
246
+ [Sui gasless stablecoin transfers guide](https://docs.sui.io/develop/transaction-payment/gasless-stablecoin-transfers)
247
+ for full details, including the current
248
+ [allowlist of eligible stablecoins](https://docs.sui.io/develop/transaction-payment/gasless-stablecoin-transfers#eligible-stablecoins).
249
+
250
+ Gasless transactions are limited to transactions that send funds as balances for specific
251
+ allowlisted stablecoins. Using `0x2::balance::send_funds` with `tx.balance()` is the recommended way
252
+ to build gasless transactions with the TypeScript SDK.
253
+
254
+ ### SDK support (gRPC and GraphQL)
255
+
256
+ When using the gRPC or GraphQL transports, transactions that qualify are automatically detected, and
257
+ the gas price is set when the transaction is built.
258
+
259
+ ```tsx
260
+
261
+ const client = new SuiGrpcClient({ url: 'https://grpc.mainnet.sui.io:443' });
262
+
263
+ // Mainnet USDC. For the full set of allowlisted gasless stablecoins, see:
264
+ // https://docs.sui.io/develop/transaction-payment/gasless-stablecoin-transfers#eligible-stablecoins
265
+ const USDC = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
266
+
267
+ const tx = new Transaction();
268
+ tx.setSender(keypair.toSuiAddress());
269
+
270
+ tx.moveCall({
271
+ target: '0x2::balance::send_funds',
272
+ typeArguments: [USDC],
273
+ arguments: [tx.balance({ type: USDC, balance: 1_000_000 }), tx.pure.address(recipient)],
274
+ });
275
+
276
+ const result = await client.signAndExecuteTransaction({
277
+ transaction: tx,
278
+ signer: keypair,
279
+ });
280
+ ```
281
+
282
+ The JSON-RPC transport does not automatically detect gasless eligibility. You can opt in by setting
283
+ the gas price to zero with `tx.setGasPrice(0)`, but only after confirming the coin type is on the
284
+ [allowlist](https://docs.sui.io/develop/transaction-payment/gasless-stablecoin-transfers#eligible-stablecoins)
285
+ and the PTB shape is eligible. Otherwise the transaction fails at validation.
286
+
287
+ > **Warning:** If your wallet builds transactions with gRPC but executes through a different transport (for
288
+ > example, dapp-kit wallets that still execute over JSON-RPC), pass the gRPC client to `build` so
289
+ > the gas price still gets set correctly:
290
+ >
291
+ > ```tsx
292
+ > const bytes = await tx.build({ client: grpcClient });
293
+ > ```