@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.
- package/CHANGELOG.md +18 -0
- package/dist/grpc/proto/sui/rpc/v2/ledger_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
- package/dist/transactions/Transaction.d.mts +16 -2
- package/dist/transactions/Transaction.d.mts.map +1 -1
- package/dist/transactions/Transaction.mjs +8 -3
- package/dist/transactions/Transaction.mjs.map +1 -1
- package/dist/transactions/index.d.mts +2 -2
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/dist/zklogin/jwt-utils.d.mts.map +1 -1
- package/dist/zklogin/jwt-utils.mjs.map +1 -1
- package/dist/zklogin/utils.d.mts.map +1 -1
- package/dist/zklogin/utils.mjs +9 -0
- package/dist/zklogin/utils.mjs.map +1 -1
- package/docs/bcs.md +11 -6
- package/docs/clients/core.md +10 -9
- package/docs/clients/grpc.md +1 -1
- package/docs/index.md +176 -22
- package/docs/llms-index.md +6 -9
- package/docs/migrations/0.38.md +2 -2
- package/docs/migrations/sui-1.0.md +2 -2
- package/docs/migrations/sui-2.0/json-rpc-migration.md +76 -33
- package/docs/plugins.md +29 -5
- package/docs/transactions/basics.md +279 -0
- package/docs/transactions/coins-and-balances.md +293 -0
- package/docs/transactions/offline.md +192 -0
- package/docs/transactions/reference.md +380 -0
- package/docs/transactions/signing-and-execution.md +401 -0
- package/package.json +26 -26
- package/src/transactions/Transaction.ts +36 -5
- package/src/transactions/index.ts +1 -0
- package/src/version.ts +1 -1
- package/src/zklogin/jwt-utils.ts +3 -0
- package/src/zklogin/utils.ts +17 -0
- package/docs/faucet.md +0 -26
- package/docs/hello-sui.md +0 -115
- package/docs/install.md +0 -61
- package/docs/transaction-building/basics.md +0 -299
- package/docs/transaction-building/gas.md +0 -61
- package/docs/transaction-building/intents.md +0 -62
- package/docs/transaction-building/offline.md +0 -73
- 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
|
+
> ```
|