@mysten/sui 2.14.1 → 2.15.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 +14 -0
- package/dist/bcs/bcs.d.mts +6 -6
- package/dist/grpc/index.d.mts +3 -1
- package/dist/grpc/index.mjs +2 -1
- package/dist/grpc/proto/sui/rpc/v2/ledger_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/signature_verification_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/grpc/proto/sui/rpc/v2/transaction_execution_service.client.d.mts +4 -4
- package/dist/transactions/Transaction.d.mts +6 -6
- package/dist/transactions/Transaction.d.mts.map +1 -1
- package/dist/transactions/data/internal.d.mts +109 -109
- package/dist/transactions/data/internal.d.mts.map +1 -1
- package/dist/transactions/data/v1.d.mts +220 -220
- package/dist/transactions/data/v1.d.mts.map +1 -1
- package/dist/transactions/data/v2.d.mts +16 -16
- package/dist/transactions/data/v2.d.mts.map +1 -1
- package/dist/transactions/intents/CoinWithBalance.mjs +35 -5
- package/dist/transactions/intents/CoinWithBalance.mjs.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/dist/zklogin/bcs.d.mts +14 -14
- package/docs/clients/grpc.md +55 -2
- package/package.json +4 -2
- package/src/grpc/index.ts +6 -0
- package/src/transactions/intents/CoinWithBalance.ts +41 -6
- package/src/version.ts +1 -1
|
@@ -72,6 +72,7 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
72
72
|
outputKind: outputKind ?? "coin"
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
if (totalByType.has("gas") && totalByType.has(SUI_TYPE)) throw new Error("Cannot mix SUI CoinWithBalance intents that use the gas coin with ones that do not (useGasCoin: false). Use one or the other.");
|
|
75
76
|
const usedIds = /* @__PURE__ */ new Set();
|
|
76
77
|
for (const input of transactionData.inputs) {
|
|
77
78
|
if (input.Object?.ImmOrOwnedObject) usedIds.add(input.Object.ImmOrOwnedObject.objectId);
|
|
@@ -99,6 +100,7 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
99
100
|
}) : null]);
|
|
100
101
|
const mergedCoins = /* @__PURE__ */ new Map();
|
|
101
102
|
const exactBalanceByType = /* @__PURE__ */ new Map();
|
|
103
|
+
const usedAddressBalance = /* @__PURE__ */ new Set();
|
|
102
104
|
const typeState = /* @__PURE__ */ new Map();
|
|
103
105
|
let index = 0;
|
|
104
106
|
while (index < transactionData.commands.length) {
|
|
@@ -141,7 +143,31 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
141
143
|
if (!typeState.has(type)) {
|
|
142
144
|
const intents = intentsForType;
|
|
143
145
|
const sources = [];
|
|
144
|
-
if (
|
|
146
|
+
if (addressBalance >= totalRequired) {
|
|
147
|
+
usedAddressBalance.add(type);
|
|
148
|
+
commands.push(TransactionCommands.MoveCall({
|
|
149
|
+
target: "0x2::coin::redeem_funds",
|
|
150
|
+
typeArguments: [coinType],
|
|
151
|
+
arguments: [transactionData.addInput("withdrawal", Inputs.FundsWithdrawal({
|
|
152
|
+
reservation: {
|
|
153
|
+
$kind: "MaxAmountU64",
|
|
154
|
+
MaxAmountU64: String(totalRequired)
|
|
155
|
+
},
|
|
156
|
+
typeArg: {
|
|
157
|
+
$kind: "Balance",
|
|
158
|
+
Balance: coinType
|
|
159
|
+
},
|
|
160
|
+
withdrawFrom: {
|
|
161
|
+
$kind: "Sender",
|
|
162
|
+
Sender: true
|
|
163
|
+
}
|
|
164
|
+
}))]
|
|
165
|
+
}));
|
|
166
|
+
sources.push({
|
|
167
|
+
$kind: "Result",
|
|
168
|
+
Result: index + commands.length - 1
|
|
169
|
+
});
|
|
170
|
+
} else if (type === "gas") sources.push({
|
|
145
171
|
$kind: "GasCoin",
|
|
146
172
|
GasCoin: true
|
|
147
173
|
});
|
|
@@ -156,6 +182,7 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
156
182
|
version: coin.version
|
|
157
183
|
})));
|
|
158
184
|
if (abNeeded > 0n) {
|
|
185
|
+
usedAddressBalance.add(type);
|
|
159
186
|
commands.push(TransactionCommands.MoveCall({
|
|
160
187
|
target: "0x2::coin::redeem_funds",
|
|
161
188
|
typeArguments: [coinType],
|
|
@@ -216,15 +243,18 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
216
243
|
index += commands.length;
|
|
217
244
|
}
|
|
218
245
|
for (const [type, mergedCoin] of mergedCoins) {
|
|
219
|
-
if (type === "gas") continue;
|
|
220
|
-
|
|
246
|
+
if (type === "gas" && !usedAddressBalance.has(type)) continue;
|
|
247
|
+
const coinType = type === "gas" ? SUI_TYPE : type;
|
|
248
|
+
const hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === "balance");
|
|
249
|
+
const sourcedFromAB = usedAddressBalance.has(type);
|
|
250
|
+
if (hasBalanceIntent || sourcedFromAB) transactionData.commands.push(TransactionCommands.MoveCall({
|
|
221
251
|
target: "0x2::coin::send_funds",
|
|
222
|
-
typeArguments: [
|
|
252
|
+
typeArguments: [coinType],
|
|
223
253
|
arguments: [mergedCoin, transactionData.addInput("pure", Inputs.Pure(suiBcs.Address.serialize(transactionData.sender)))]
|
|
224
254
|
}));
|
|
225
255
|
else if (exactBalanceByType.get(type)) transactionData.commands.push(TransactionCommands.MoveCall({
|
|
226
256
|
target: "0x2::coin::destroy_zero",
|
|
227
|
-
typeArguments: [
|
|
257
|
+
typeArguments: [coinType],
|
|
228
258
|
arguments: [mergedCoin]
|
|
229
259
|
}));
|
|
230
260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoinWithBalance.mjs","names":["bcs","balance"],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { InferInput } from 'valibot';\nimport { bigint, object, optional, parse, picklist, string } from 'valibot';\n\nimport { bcs } from '../../bcs/index.js';\nimport { normalizeStructTag } from '../../utils/sui-types.js';\nimport { TransactionCommands } from '../Commands.js';\nimport type { Argument } from '../data/internal.js';\nimport { Inputs } from '../Inputs.js';\nimport type { BuildTransactionOptions } from '../resolve.js';\nimport type { Transaction, TransactionResult } from '../Transaction.js';\nimport type { TransactionDataBuilder } from '../TransactionData.js';\nimport type { ClientWithCoreApi, SuiClientTypes } from '../../client/index.js';\n\nexport const COIN_WITH_BALANCE = 'CoinWithBalance';\nconst SUI_TYPE = normalizeStructTag('0x2::sui::SUI');\n\nexport function coinWithBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet coinResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (coinResult) {\n\t\t\treturn coinResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tcoinResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'coin',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn coinResult;\n\t};\n}\n\nexport function createBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet balanceResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (balanceResult) {\n\t\t\treturn balanceResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tbalanceResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'balance',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn balanceResult;\n\t};\n}\n\nconst CoinWithBalanceData = object({\n\ttype: string(),\n\tbalance: bigint(),\n\toutputKind: optional(picklist(['coin', 'balance'])),\n});\n\nexport async function resolveCoinBalance(\n\ttransactionData: TransactionDataBuilder,\n\tbuildOptions: BuildTransactionOptions,\n\tnext: () => Promise<void>,\n) {\n\ttype IntentInfo = { balance: bigint; outputKind: 'coin' | 'balance' };\n\n\tconst coinTypes = new Set<string>();\n\tconst totalByType = new Map<string, bigint>();\n\tconst intentsByType = new Map<string, IntentInfo[]>();\n\n\tif (!transactionData.sender) {\n\t\tthrow new Error('Sender must be set to resolve CoinWithBalance');\n\t}\n\n\t// First pass: scan intents, collect per-type data, and resolve zero-balance intents in place.\n\tfor (const [i, command] of transactionData.commands.entries()) {\n\t\tif (command.$kind !== '$Intent' || command.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance, outputKind } = parse(CoinWithBalanceData, command.$Intent.data);\n\n\t\t// Zero-balance intents are resolved immediately — no coins or AB needed.\n\t\t// This is a 1:1 replacement so indices don't shift.\n\t\tif (balance === 0n) {\n\t\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\t\ttransactionData.replaceCommand(\n\t\t\t\ti,\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: (outputKind ?? 'coin') === 'balance' ? '0x2::balance::zero' : '0x2::coin::zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (type !== 'gas') {\n\t\t\tcoinTypes.add(type);\n\t\t}\n\n\t\ttotalByType.set(type, (totalByType.get(type) ?? 0n) + balance);\n\n\t\tif (!intentsByType.has(type)) intentsByType.set(type, []);\n\t\tintentsByType.get(type)!.push({ balance, outputKind: outputKind ?? 'coin' });\n\t}\n\n\tconst usedIds = new Set<string>();\n\n\tfor (const input of transactionData.inputs) {\n\t\tif (input.Object?.ImmOrOwnedObject) {\n\t\t\tusedIds.add(input.Object.ImmOrOwnedObject.objectId);\n\t\t}\n\t\tif (input.UnresolvedObject?.objectId) {\n\t\t\tusedIds.add(input.UnresolvedObject.objectId);\n\t\t}\n\t}\n\n\tconst coinsByType = new Map<string, SuiClientTypes.Coin[]>();\n\tconst addressBalanceByType = new Map<string, bigint>();\n\tconst client = buildOptions.client;\n\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'Client must be provided to build or serialize transactions with CoinWithBalance intents',\n\t\t);\n\t}\n\n\tawait Promise.all([\n\t\t...[...coinTypes].map(async (coinType) => {\n\t\t\tconst { coins, addressBalance } = await getCoinsAndBalanceOfType({\n\t\t\t\tcoinType,\n\t\t\t\tbalance: totalByType.get(coinType)!,\n\t\t\t\tclient,\n\t\t\t\towner: transactionData.sender!,\n\t\t\t\tusedIds,\n\t\t\t});\n\n\t\t\tcoinsByType.set(coinType, coins);\n\t\t\taddressBalanceByType.set(coinType, addressBalance);\n\t\t}),\n\t\ttotalByType.has('gas')\n\t\t\t? await client.core\n\t\t\t\t\t.getBalance({\n\t\t\t\t\t\towner: transactionData.sender!,\n\t\t\t\t\t\tcoinType: SUI_TYPE,\n\t\t\t\t\t})\n\t\t\t\t\t.then(({ balance }) => {\n\t\t\t\t\t\taddressBalanceByType.set('gas', BigInt(balance.addressBalance));\n\t\t\t\t\t})\n\t\t\t: null,\n\t]);\n\n\tconst mergedCoins = new Map<string, Argument>();\n\tconst exactBalanceByType = new Map<string, boolean>();\n\n\t// Per-type state for Path 2 combined splits\n\ttype TypeState = { results: Argument[]; nextIntent: number };\n\tconst typeState = new Map<string, TypeState>();\n\n\tlet index = 0;\n\twhile (index < transactionData.commands.length) {\n\t\tconst transaction = transactionData.commands[index];\n\t\tif (transaction.$kind !== '$Intent' || transaction.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance } = transaction.$Intent.data as {\n\t\t\ttype: string;\n\t\t\tbalance: bigint;\n\t\t};\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst totalRequired = totalByType.get(type)!;\n\t\tconst addressBalance = addressBalanceByType.get(type) ?? 0n;\n\n\t\tconst commands = [];\n\t\tlet intentResult: Argument;\n\n\t\tconst intentsForType = intentsByType.get(type) ?? [];\n\t\tconst allBalance = intentsForType.every((i) => i.outputKind === 'balance');\n\n\t\tif (allBalance && addressBalance >= totalRequired) {\n\t\t\t// Path 1: All balance intents and AB sufficient — direct per-intent withdrawal.\n\t\t\t// No coins touched, enables parallel execution.\n\t\t\tcommands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::balance::redeem_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\tMaxAmountU64: String(balance),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tintentResult = {\n\t\t\t\t$kind: 'NestedResult',\n\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t};\n\t\t} else {\n\t\t\t// Path 2: Merge and Split — build a merged coin, split all intents at once.\n\n\t\t\tif (!typeState.has(type)) {\n\t\t\t\tconst intents = intentsForType;\n\n\t\t\t\t// Step 1: Build sources and merge\n\t\t\t\tconst sources: Argument[] = [];\n\n\t\t\t\tif (type === 'gas') {\n\t\t\t\t\tsources.push({ $kind: 'GasCoin', GasCoin: true });\n\t\t\t\t} else {\n\t\t\t\t\tconst coins = coinsByType.get(type)!;\n\t\t\t\t\tconst loadedCoinBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);\n\t\t\t\t\tconst abNeeded =\n\t\t\t\t\t\ttotalRequired > loadedCoinBalance ? totalRequired - loadedCoinBalance : 0n;\n\n\t\t\t\t\texactBalanceByType.set(type, loadedCoinBalance + abNeeded === totalRequired);\n\n\t\t\t\t\tfor (const coin of coins) {\n\t\t\t\t\t\tsources.push(\n\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t'object',\n\t\t\t\t\t\t\t\tInputs.ObjectRef({\n\t\t\t\t\t\t\t\t\tobjectId: coin.objectId,\n\t\t\t\t\t\t\t\t\tdigest: coin.digest,\n\t\t\t\t\t\t\t\t\tversion: coin.version,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (abNeeded > 0n) {\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(abNeeded),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst baseCoin = sources[0];\n\t\t\t\tconst rest = sources.slice(1);\n\t\t\t\tfor (let i = 0; i < rest.length; i += 500) {\n\t\t\t\t\tcommands.push(TransactionCommands.MergeCoins(baseCoin, rest.slice(i, i + 500)));\n\t\t\t\t}\n\n\t\t\t\tmergedCoins.set(type, baseCoin);\n\n\t\t\t\t// Step 2: Combined SplitCoins for all intents of this type\n\t\t\t\tconst splitCmdIndex = index + commands.length;\n\t\t\t\tcommands.push(\n\t\t\t\t\tTransactionCommands.SplitCoins(\n\t\t\t\t\t\tbaseCoin,\n\t\t\t\t\t\tintents.map((i) =>\n\t\t\t\t\t\t\ttransactionData.addInput('pure', Inputs.Pure(bcs.u64().serialize(i.balance))),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// Build per-intent results, adding into_balance conversions for balance intents\n\t\t\t\tconst results: Argument[] = [];\n\t\t\t\tfor (let i = 0; i < intents.length; i++) {\n\t\t\t\t\tconst splitResult: Argument = {\n\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\tNestedResult: [splitCmdIndex, i],\n\t\t\t\t\t};\n\n\t\t\t\t\tif (intents[i].outputKind === 'balance') {\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::into_balance',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [splitResult],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresults.push(splitResult);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttypeState.set(type, { results, nextIntent: 0 });\n\t\t\t}\n\n\t\t\tconst state = typeState.get(type)!;\n\t\t\tintentResult = state.results[state.nextIntent++];\n\t\t}\n\n\t\ttransactionData.replaceCommand(\n\t\t\tindex,\n\t\t\tcommands,\n\t\t\tintentResult as { NestedResult: [number, number] },\n\t\t);\n\n\t\t// Advance past the replacement. When commands is empty (subsequent intents\n\t\t// of a combined split), the command was removed and the next command shifted\n\t\t// into this position — so we stay at the same index.\n\t\tindex += commands.length;\n\t}\n\n\t// Step 3: Remainder handling\n\tfor (const [type, mergedCoin] of mergedCoins) {\n\t\tif (type === 'gas') continue;\n\n\t\tconst hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === 'balance');\n\n\t\tif (hasBalanceIntent) {\n\t\t\t// Balance intents exist: send remainder coin back to sender's address balance.\n\t\t\t// coin::send_funds is gasless-eligible and handles zero amounts.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::send_funds',\n\t\t\t\t\ttypeArguments: [type],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\tmergedCoin,\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'pure',\n\t\t\t\t\t\t\tInputs.Pure(bcs.Address.serialize(transactionData.sender!)),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (exactBalanceByType.get(type)) {\n\t\t\t// Coin-only with exact match: destroy the zero-value dust coin.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::destroy_zero',\n\t\t\t\t\ttypeArguments: [type],\n\t\t\t\t\targuments: [mergedCoin],\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Coin-only with surplus: merged coin stays with sender as an owned object\n\t}\n\n\treturn next();\n}\n\nasync function getCoinsAndBalanceOfType({\n\tcoinType,\n\tbalance,\n\tclient,\n\towner,\n\tusedIds,\n}: {\n\tcoinType: string;\n\tbalance: bigint;\n\tclient: ClientWithCoreApi;\n\towner: string;\n\tusedIds: Set<string>;\n}): Promise<{\n\tcoins: SuiClientTypes.Coin[];\n\tbalance: bigint;\n\taddressBalance: bigint;\n\tcoinBalance: bigint;\n}> {\n\tlet remainingBalance = balance;\n\tconst coins: SuiClientTypes.Coin[] = [];\n\tconst balanceRequest = client.core.getBalance({ owner, coinType }).then(({ balance }) => {\n\t\tremainingBalance -= BigInt(balance.addressBalance);\n\n\t\treturn balance;\n\t});\n\n\tconst [allCoins, balanceResponse] = await Promise.all([loadMoreCoins(), balanceRequest]);\n\n\tif (BigInt(balanceResponse.balance) < balance) {\n\t\tthrow new Error(\n\t\t\t`Insufficient balance of ${coinType} for owner ${owner}. Required: ${balance}, Available: ${\n\t\t\t\tbalance - remainingBalance\n\t\t\t}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tcoins: allCoins,\n\t\tbalance: BigInt(balanceResponse.coinBalance),\n\t\taddressBalance: BigInt(balanceResponse.addressBalance),\n\t\tcoinBalance: BigInt(balanceResponse.coinBalance),\n\t};\n\n\tasync function loadMoreCoins(cursor: string | null = null): Promise<SuiClientTypes.Coin[]> {\n\t\tconst {\n\t\t\tobjects,\n\t\t\thasNextPage,\n\t\t\tcursor: nextCursor,\n\t\t} = await client.core.listCoins({\n\t\t\towner,\n\t\t\tcoinType,\n\t\t\tcursor,\n\t\t});\n\n\t\tawait balanceRequest;\n\n\t\t// Always load all coins from the page (except already-used ones).\n\t\t// This merges all available coins rather than leaving dust.\n\t\tfor (const coin of objects) {\n\t\t\tif (usedIds.has(coin.objectId)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcoins.push(coin);\n\t\t\tremainingBalance -= BigInt(coin.balance);\n\t\t}\n\n\t\t// Only paginate if loaded coins + AB are still insufficient\n\t\tif (remainingBalance > 0n && hasNextPage) {\n\t\t\treturn loadMoreCoins(nextCursor);\n\t\t}\n\n\t\treturn coins;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoinWithBalance.mjs","names":["bcs","balance"],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { InferInput } from 'valibot';\nimport { bigint, object, optional, parse, picklist, string } from 'valibot';\n\nimport { bcs } from '../../bcs/index.js';\nimport { normalizeStructTag } from '../../utils/sui-types.js';\nimport { TransactionCommands } from '../Commands.js';\nimport type { Argument } from '../data/internal.js';\nimport { Inputs } from '../Inputs.js';\nimport type { BuildTransactionOptions } from '../resolve.js';\nimport type { Transaction, TransactionResult } from '../Transaction.js';\nimport type { TransactionDataBuilder } from '../TransactionData.js';\nimport type { ClientWithCoreApi, SuiClientTypes } from '../../client/index.js';\n\nexport const COIN_WITH_BALANCE = 'CoinWithBalance';\nconst SUI_TYPE = normalizeStructTag('0x2::sui::SUI');\n\nexport function coinWithBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet coinResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (coinResult) {\n\t\t\treturn coinResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tcoinResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'coin',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn coinResult;\n\t};\n}\n\nexport function createBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet balanceResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (balanceResult) {\n\t\t\treturn balanceResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tbalanceResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'balance',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn balanceResult;\n\t};\n}\n\nconst CoinWithBalanceData = object({\n\ttype: string(),\n\tbalance: bigint(),\n\toutputKind: optional(picklist(['coin', 'balance'])),\n});\n\nexport async function resolveCoinBalance(\n\ttransactionData: TransactionDataBuilder,\n\tbuildOptions: BuildTransactionOptions,\n\tnext: () => Promise<void>,\n) {\n\ttype IntentInfo = { balance: bigint; outputKind: 'coin' | 'balance' };\n\n\tconst coinTypes = new Set<string>();\n\tconst totalByType = new Map<string, bigint>();\n\tconst intentsByType = new Map<string, IntentInfo[]>();\n\n\tif (!transactionData.sender) {\n\t\tthrow new Error('Sender must be set to resolve CoinWithBalance');\n\t}\n\n\t// First pass: scan intents, collect per-type data, and resolve zero-balance intents in place.\n\tfor (const [i, command] of transactionData.commands.entries()) {\n\t\tif (command.$kind !== '$Intent' || command.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance, outputKind } = parse(CoinWithBalanceData, command.$Intent.data);\n\n\t\t// Zero-balance intents are resolved immediately — no coins or AB needed.\n\t\t// This is a 1:1 replacement so indices don't shift.\n\t\tif (balance === 0n) {\n\t\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\t\ttransactionData.replaceCommand(\n\t\t\t\ti,\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: (outputKind ?? 'coin') === 'balance' ? '0x2::balance::zero' : '0x2::coin::zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (type !== 'gas') {\n\t\t\tcoinTypes.add(type);\n\t\t}\n\n\t\ttotalByType.set(type, (totalByType.get(type) ?? 0n) + balance);\n\n\t\tif (!intentsByType.has(type)) intentsByType.set(type, []);\n\t\tintentsByType.get(type)!.push({ balance, outputKind: outputKind ?? 'coin' });\n\t}\n\n\tif (totalByType.has('gas') && totalByType.has(SUI_TYPE)) {\n\t\tthrow new Error(\n\t\t\t'Cannot mix SUI CoinWithBalance intents that use the gas coin with ones that do not (useGasCoin: false). Use one or the other.',\n\t\t);\n\t}\n\n\tconst usedIds = new Set<string>();\n\n\tfor (const input of transactionData.inputs) {\n\t\tif (input.Object?.ImmOrOwnedObject) {\n\t\t\tusedIds.add(input.Object.ImmOrOwnedObject.objectId);\n\t\t}\n\t\tif (input.UnresolvedObject?.objectId) {\n\t\t\tusedIds.add(input.UnresolvedObject.objectId);\n\t\t}\n\t}\n\n\tconst coinsByType = new Map<string, SuiClientTypes.Coin[]>();\n\tconst addressBalanceByType = new Map<string, bigint>();\n\tconst client = buildOptions.client;\n\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'Client must be provided to build or serialize transactions with CoinWithBalance intents',\n\t\t);\n\t}\n\n\tawait Promise.all([\n\t\t...[...coinTypes].map(async (coinType) => {\n\t\t\tconst { coins, addressBalance } = await getCoinsAndBalanceOfType({\n\t\t\t\tcoinType,\n\t\t\t\tbalance: totalByType.get(coinType)!,\n\t\t\t\tclient,\n\t\t\t\towner: transactionData.sender!,\n\t\t\t\tusedIds,\n\t\t\t});\n\n\t\t\tcoinsByType.set(coinType, coins);\n\t\t\taddressBalanceByType.set(coinType, addressBalance);\n\t\t}),\n\t\ttotalByType.has('gas')\n\t\t\t? await client.core\n\t\t\t\t\t.getBalance({\n\t\t\t\t\t\towner: transactionData.sender!,\n\t\t\t\t\t\tcoinType: SUI_TYPE,\n\t\t\t\t\t})\n\t\t\t\t\t.then(({ balance }) => {\n\t\t\t\t\t\taddressBalanceByType.set('gas', BigInt(balance.addressBalance));\n\t\t\t\t\t})\n\t\t\t: null,\n\t]);\n\n\tconst mergedCoins = new Map<string, Argument>();\n\tconst exactBalanceByType = new Map<string, boolean>();\n\tconst usedAddressBalance = new Set<string>();\n\n\t// Per-type state for Path 2 combined splits\n\ttype TypeState = { results: Argument[]; nextIntent: number };\n\tconst typeState = new Map<string, TypeState>();\n\n\tlet index = 0;\n\twhile (index < transactionData.commands.length) {\n\t\tconst transaction = transactionData.commands[index];\n\t\tif (transaction.$kind !== '$Intent' || transaction.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance } = transaction.$Intent.data as {\n\t\t\ttype: string;\n\t\t\tbalance: bigint;\n\t\t};\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst totalRequired = totalByType.get(type)!;\n\t\tconst addressBalance = addressBalanceByType.get(type) ?? 0n;\n\n\t\tconst commands = [];\n\t\tlet intentResult: Argument;\n\n\t\tconst intentsForType = intentsByType.get(type) ?? [];\n\t\tconst allBalance = intentsForType.every((i) => i.outputKind === 'balance');\n\n\t\tif (allBalance && addressBalance >= totalRequired) {\n\t\t\t// Path 1: All balance intents and AB sufficient — direct per-intent withdrawal.\n\t\t\t// No coins touched, enables parallel execution.\n\t\t\tcommands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::balance::redeem_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\tMaxAmountU64: String(balance),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tintentResult = {\n\t\t\t\t$kind: 'NestedResult',\n\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t};\n\t\t} else {\n\t\t\t// Path 2: Merge and Split — build a merged coin, split all intents at once.\n\n\t\t\tif (!typeState.has(type)) {\n\t\t\t\tconst intents = intentsForType;\n\n\t\t\t\t// Step 1: Build sources and merge\n\t\t\t\tconst sources: Argument[] = [];\n\n\t\t\t\tif (addressBalance >= totalRequired) {\n\t\t\t\t\t// AB sufficient — source entirely from address balance, no coins needed.\n\t\t\t\t\tusedAddressBalance.add(type);\n\n\t\t\t\t\tcommands.push(\n\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(totalRequired),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t} else if (type === 'gas') {\n\t\t\t\t\tsources.push({ $kind: 'GasCoin', GasCoin: true });\n\t\t\t\t} else {\n\t\t\t\t\tconst coins = coinsByType.get(type)!;\n\t\t\t\t\tconst loadedCoinBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);\n\t\t\t\t\tconst abNeeded =\n\t\t\t\t\t\ttotalRequired > loadedCoinBalance ? totalRequired - loadedCoinBalance : 0n;\n\n\t\t\t\t\texactBalanceByType.set(type, loadedCoinBalance + abNeeded === totalRequired);\n\n\t\t\t\t\tfor (const coin of coins) {\n\t\t\t\t\t\tsources.push(\n\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t'object',\n\t\t\t\t\t\t\t\tInputs.ObjectRef({\n\t\t\t\t\t\t\t\t\tobjectId: coin.objectId,\n\t\t\t\t\t\t\t\t\tdigest: coin.digest,\n\t\t\t\t\t\t\t\t\tversion: coin.version,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (abNeeded > 0n) {\n\t\t\t\t\t\tusedAddressBalance.add(type);\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(abNeeded),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst baseCoin = sources[0];\n\t\t\t\tconst rest = sources.slice(1);\n\t\t\t\tfor (let i = 0; i < rest.length; i += 500) {\n\t\t\t\t\tcommands.push(TransactionCommands.MergeCoins(baseCoin, rest.slice(i, i + 500)));\n\t\t\t\t}\n\n\t\t\t\tmergedCoins.set(type, baseCoin);\n\n\t\t\t\t// Step 2: Combined SplitCoins for all intents of this type\n\t\t\t\tconst splitCmdIndex = index + commands.length;\n\t\t\t\tcommands.push(\n\t\t\t\t\tTransactionCommands.SplitCoins(\n\t\t\t\t\t\tbaseCoin,\n\t\t\t\t\t\tintents.map((i) =>\n\t\t\t\t\t\t\ttransactionData.addInput('pure', Inputs.Pure(bcs.u64().serialize(i.balance))),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// Build per-intent results, adding into_balance conversions for balance intents\n\t\t\t\tconst results: Argument[] = [];\n\t\t\t\tfor (let i = 0; i < intents.length; i++) {\n\t\t\t\t\tconst splitResult: Argument = {\n\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\tNestedResult: [splitCmdIndex, i],\n\t\t\t\t\t};\n\n\t\t\t\t\tif (intents[i].outputKind === 'balance') {\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::into_balance',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [splitResult],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresults.push(splitResult);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttypeState.set(type, { results, nextIntent: 0 });\n\t\t\t}\n\n\t\t\tconst state = typeState.get(type)!;\n\t\t\tintentResult = state.results[state.nextIntent++];\n\t\t}\n\n\t\ttransactionData.replaceCommand(\n\t\t\tindex,\n\t\t\tcommands,\n\t\t\tintentResult as { NestedResult: [number, number] },\n\t\t);\n\n\t\t// Advance past the replacement. When commands is empty (subsequent intents\n\t\t// of a combined split), the command was removed and the next command shifted\n\t\t// into this position — so we stay at the same index.\n\t\tindex += commands.length;\n\t}\n\n\t// Step 3: Remainder handling\n\tfor (const [type, mergedCoin] of mergedCoins) {\n\t\t// When gas type used GasCoin (not AB), leftover stays in the gas coin — no remainder needed.\n\t\tif (type === 'gas' && !usedAddressBalance.has(type)) continue;\n\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === 'balance');\n\t\tconst sourcedFromAB = usedAddressBalance.has(type);\n\n\t\tif (hasBalanceIntent || sourcedFromAB) {\n\t\t\t// Sourced from AB or balance intents exist: send remainder back to sender's address balance.\n\t\t\t// coin::send_funds is gasless-eligible and handles zero amounts.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::send_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\tmergedCoin,\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'pure',\n\t\t\t\t\t\t\tInputs.Pure(bcs.Address.serialize(transactionData.sender!)),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (exactBalanceByType.get(type)) {\n\t\t\t// Coin-only with exact match: destroy the zero-value dust coin.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::destroy_zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [mergedCoin],\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Coin-only with surplus: merged coin stays with sender as an owned object\n\t}\n\n\treturn next();\n}\n\nasync function getCoinsAndBalanceOfType({\n\tcoinType,\n\tbalance,\n\tclient,\n\towner,\n\tusedIds,\n}: {\n\tcoinType: string;\n\tbalance: bigint;\n\tclient: ClientWithCoreApi;\n\towner: string;\n\tusedIds: Set<string>;\n}): Promise<{\n\tcoins: SuiClientTypes.Coin[];\n\tbalance: bigint;\n\taddressBalance: bigint;\n\tcoinBalance: bigint;\n}> {\n\tlet remainingBalance = balance;\n\tconst coins: SuiClientTypes.Coin[] = [];\n\tconst balanceRequest = client.core.getBalance({ owner, coinType }).then(({ balance }) => {\n\t\tremainingBalance -= BigInt(balance.addressBalance);\n\n\t\treturn balance;\n\t});\n\n\tconst [allCoins, balanceResponse] = await Promise.all([loadMoreCoins(), balanceRequest]);\n\n\tif (BigInt(balanceResponse.balance) < balance) {\n\t\tthrow new Error(\n\t\t\t`Insufficient balance of ${coinType} for owner ${owner}. Required: ${balance}, Available: ${\n\t\t\t\tbalance - remainingBalance\n\t\t\t}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tcoins: allCoins,\n\t\tbalance: BigInt(balanceResponse.coinBalance),\n\t\taddressBalance: BigInt(balanceResponse.addressBalance),\n\t\tcoinBalance: BigInt(balanceResponse.coinBalance),\n\t};\n\n\tasync function loadMoreCoins(cursor: string | null = null): Promise<SuiClientTypes.Coin[]> {\n\t\tconst {\n\t\t\tobjects,\n\t\t\thasNextPage,\n\t\t\tcursor: nextCursor,\n\t\t} = await client.core.listCoins({\n\t\t\towner,\n\t\t\tcoinType,\n\t\t\tcursor,\n\t\t});\n\n\t\tawait balanceRequest;\n\n\t\t// Always load all coins from the page (except already-used ones).\n\t\t// This merges all available coins rather than leaving dust.\n\t\tfor (const coin of objects) {\n\t\t\tif (usedIds.has(coin.objectId)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcoins.push(coin);\n\t\t\tremainingBalance -= BigInt(coin.balance);\n\t\t}\n\n\t\t// Only paginate if loaded coins + AB are still insufficient\n\t\tif (remainingBalance > 0n && hasNextPage) {\n\t\t\treturn loadMoreCoins(nextCursor);\n\t\t}\n\n\t\treturn coins;\n\t}\n}\n"],"mappings":"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"}
|
package/dist/version.mjs
CHANGED
package/dist/version.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.
|
|
1
|
+
{"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.15.0';\nexport const TARGETED_RPC_VERSION = '1.70.0';\n"],"mappings":";AAKA,MAAa,kBAAkB;AAC/B,MAAa,uBAAuB"}
|
package/dist/zklogin/bcs.d.mts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _mysten_bcs810 from "@mysten/bcs";
|
|
2
2
|
import { InferBcsInput } from "@mysten/bcs";
|
|
3
3
|
|
|
4
4
|
//#region src/zklogin/bcs.d.ts
|
|
5
|
-
declare const zkLoginSignature:
|
|
6
|
-
inputs:
|
|
7
|
-
proofPoints:
|
|
8
|
-
a:
|
|
5
|
+
declare const zkLoginSignature: _mysten_bcs810.BcsStruct<{
|
|
6
|
+
inputs: _mysten_bcs810.BcsStruct<{
|
|
7
|
+
proofPoints: _mysten_bcs810.BcsStruct<{
|
|
8
|
+
a: _mysten_bcs810.BcsType<string[], Iterable<string> & {
|
|
9
9
|
length: number;
|
|
10
10
|
}, string>;
|
|
11
|
-
b:
|
|
11
|
+
b: _mysten_bcs810.BcsType<string[][], Iterable<Iterable<string> & {
|
|
12
12
|
length: number;
|
|
13
13
|
}> & {
|
|
14
14
|
length: number;
|
|
15
15
|
}, string>;
|
|
16
|
-
c:
|
|
16
|
+
c: _mysten_bcs810.BcsType<string[], Iterable<string> & {
|
|
17
17
|
length: number;
|
|
18
18
|
}, string>;
|
|
19
19
|
}, string>;
|
|
20
|
-
issBase64Details:
|
|
21
|
-
value:
|
|
22
|
-
indexMod4:
|
|
20
|
+
issBase64Details: _mysten_bcs810.BcsStruct<{
|
|
21
|
+
value: _mysten_bcs810.BcsType<string, string, "string">;
|
|
22
|
+
indexMod4: _mysten_bcs810.BcsType<number, number, "u8">;
|
|
23
23
|
}, string>;
|
|
24
|
-
headerBase64:
|
|
25
|
-
addressSeed:
|
|
24
|
+
headerBase64: _mysten_bcs810.BcsType<string, string, "string">;
|
|
25
|
+
addressSeed: _mysten_bcs810.BcsType<string, string, "string">;
|
|
26
26
|
}, string>;
|
|
27
|
-
maxEpoch:
|
|
28
|
-
userSignature:
|
|
27
|
+
maxEpoch: _mysten_bcs810.BcsType<string, string | number | bigint, "u64">;
|
|
28
|
+
userSignature: _mysten_bcs810.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
|
|
29
29
|
}, string>;
|
|
30
30
|
type ZkLoginSignature = InferBcsInput<typeof zkLoginSignature>;
|
|
31
31
|
type ZkLoginSignatureInputs = ZkLoginSignature['inputs'];
|
package/docs/clients/grpc.md
CHANGED
|
@@ -27,12 +27,25 @@ const grpcClient = new SuiGrpcClient({
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
## Transport options
|
|
31
|
+
|
|
32
|
+
By default, `SuiGrpcClient` uses `GrpcWebFetchTransport` from
|
|
33
|
+
[protobuf-ts](https://github.com/timostamm/protobuf-ts), which works in browsers and Node.js via the
|
|
34
|
+
Fetch API. You can also provide a custom transport for advanced use cases.
|
|
35
|
+
|
|
36
|
+
The `GrpcWebFetchTransport` class, `GrpcWebOptions` type, and `RpcTransport` type are all
|
|
37
|
+
re-exported from `@mysten/sui/grpc` for convenience.
|
|
38
|
+
|
|
39
|
+
### gRPC-web transport (default)
|
|
40
|
+
|
|
41
|
+
The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2. You can customize it by
|
|
42
|
+
passing `GrpcWebFetchTransport` options directly:
|
|
31
43
|
|
|
32
44
|
```typescript
|
|
33
45
|
const transport = new GrpcWebFetchTransport({
|
|
34
46
|
baseUrl: 'https://your-custom-grpc-endpoint.com',
|
|
35
|
-
//
|
|
47
|
+
format: 'binary', // default is 'text' (base64-encoded)
|
|
48
|
+
// Additional transport options like fetchInit
|
|
36
49
|
});
|
|
37
50
|
|
|
38
51
|
const grpcClient = new SuiGrpcClient({
|
|
@@ -41,6 +54,46 @@ const grpcClient = new SuiGrpcClient({
|
|
|
41
54
|
});
|
|
42
55
|
```
|
|
43
56
|
|
|
57
|
+
### Native gRPC transport
|
|
58
|
+
|
|
59
|
+
For server-side applications (Node.js, Bun, etc.), you can use the native gRPC transport with
|
|
60
|
+
`@protobuf-ts/grpc-transport` and `@grpc/grpc-js`. This uses HTTP/2 with the native gRPC protocol
|
|
61
|
+
rather than the gRPC-web translation layer.
|
|
62
|
+
|
|
63
|
+
Install the required packages:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install @protobuf-ts/grpc-transport @grpc/grpc-js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then create the client with a `GrpcTransport`:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const transport = new GrpcTransport({
|
|
73
|
+
host: 'fullnode.testnet.sui.io:443',
|
|
74
|
+
channelCredentials: ChannelCredentials.createSsl(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const grpcClient = new SuiGrpcClient({
|
|
78
|
+
network: 'testnet',
|
|
79
|
+
transport,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For local development without TLS:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const transport = new GrpcTransport({
|
|
87
|
+
host: '127.0.0.1:9000',
|
|
88
|
+
channelCredentials: ChannelCredentials.createInsecure(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const grpcClient = new SuiGrpcClient({
|
|
92
|
+
network: 'localnet',
|
|
93
|
+
transport,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
44
97
|
## Using service clients
|
|
45
98
|
|
|
46
99
|
The `SuiGrpcClient` exposes several service clients for lower-level access to the gRPC API. These
|
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.
|
|
6
|
+
"version": "2.15.0",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"files": [
|
|
@@ -138,8 +138,10 @@
|
|
|
138
138
|
"@graphql-codegen/typescript": "5.0.7",
|
|
139
139
|
"@graphql-codegen/typescript-document-nodes": "5.0.7",
|
|
140
140
|
"@graphql-codegen/typescript-operations": "^5.0.7",
|
|
141
|
+
"@grpc/grpc-js": "^1.13.3",
|
|
141
142
|
"@iarna/toml": "^2.2.5",
|
|
142
143
|
"@parcel/watcher": "^2.5.4",
|
|
144
|
+
"@protobuf-ts/grpc-transport": "^2.11.1",
|
|
143
145
|
"@types/node": "^25.0.8",
|
|
144
146
|
"@types/tmp": "^0.2.6",
|
|
145
147
|
"cross-env": "^10.1.0",
|
|
@@ -148,7 +150,7 @@
|
|
|
148
150
|
"tmp": "^0.2.5",
|
|
149
151
|
"ts-retry-promise": "^0.8.1",
|
|
150
152
|
"typescript": "^5.9.3",
|
|
151
|
-
"vite": "^
|
|
153
|
+
"vite": "^8.0.5",
|
|
152
154
|
"vite-tsconfig-paths": "^6.0.4",
|
|
153
155
|
"vitest": "^4.0.17",
|
|
154
156
|
"wait-on": "^9.0.3"
|
package/src/grpc/index.ts
CHANGED
|
@@ -6,6 +6,12 @@ export { GrpcCoreClient } from './core.js';
|
|
|
6
6
|
export type { SuiGrpcClientOptions } from './client.js';
|
|
7
7
|
export type { GrpcCoreClientOptions } from './core.js';
|
|
8
8
|
|
|
9
|
+
// Re-export transports and types so users can configure custom transports
|
|
10
|
+
// without adding @protobuf-ts/* as direct dependencies.
|
|
11
|
+
export { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
|
|
12
|
+
export type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
|
|
13
|
+
export type { RpcTransport } from '@protobuf-ts/runtime-rpc';
|
|
14
|
+
|
|
9
15
|
// Export all gRPC proto types as a namespace
|
|
10
16
|
import * as GrpcTypes from './proto/types.js';
|
|
11
17
|
export { GrpcTypes };
|
|
@@ -140,6 +140,12 @@ export async function resolveCoinBalance(
|
|
|
140
140
|
intentsByType.get(type)!.push({ balance, outputKind: outputKind ?? 'coin' });
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
if (totalByType.has('gas') && totalByType.has(SUI_TYPE)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'Cannot mix SUI CoinWithBalance intents that use the gas coin with ones that do not (useGasCoin: false). Use one or the other.',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
143
149
|
const usedIds = new Set<string>();
|
|
144
150
|
|
|
145
151
|
for (const input of transactionData.inputs) {
|
|
@@ -188,6 +194,7 @@ export async function resolveCoinBalance(
|
|
|
188
194
|
|
|
189
195
|
const mergedCoins = new Map<string, Argument>();
|
|
190
196
|
const exactBalanceByType = new Map<string, boolean>();
|
|
197
|
+
const usedAddressBalance = new Set<string>();
|
|
191
198
|
|
|
192
199
|
// Per-type state for Path 2 combined splits
|
|
193
200
|
type TypeState = { results: Argument[]; nextIntent: number };
|
|
@@ -251,7 +258,31 @@ export async function resolveCoinBalance(
|
|
|
251
258
|
// Step 1: Build sources and merge
|
|
252
259
|
const sources: Argument[] = [];
|
|
253
260
|
|
|
254
|
-
if (
|
|
261
|
+
if (addressBalance >= totalRequired) {
|
|
262
|
+
// AB sufficient — source entirely from address balance, no coins needed.
|
|
263
|
+
usedAddressBalance.add(type);
|
|
264
|
+
|
|
265
|
+
commands.push(
|
|
266
|
+
TransactionCommands.MoveCall({
|
|
267
|
+
target: '0x2::coin::redeem_funds',
|
|
268
|
+
typeArguments: [coinType],
|
|
269
|
+
arguments: [
|
|
270
|
+
transactionData.addInput(
|
|
271
|
+
'withdrawal',
|
|
272
|
+
Inputs.FundsWithdrawal({
|
|
273
|
+
reservation: {
|
|
274
|
+
$kind: 'MaxAmountU64',
|
|
275
|
+
MaxAmountU64: String(totalRequired),
|
|
276
|
+
},
|
|
277
|
+
typeArg: { $kind: 'Balance', Balance: coinType },
|
|
278
|
+
withdrawFrom: { $kind: 'Sender', Sender: true },
|
|
279
|
+
}),
|
|
280
|
+
),
|
|
281
|
+
],
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
sources.push({ $kind: 'Result', Result: index + commands.length - 1 });
|
|
285
|
+
} else if (type === 'gas') {
|
|
255
286
|
sources.push({ $kind: 'GasCoin', GasCoin: true });
|
|
256
287
|
} else {
|
|
257
288
|
const coins = coinsByType.get(type)!;
|
|
@@ -275,6 +306,7 @@ export async function resolveCoinBalance(
|
|
|
275
306
|
}
|
|
276
307
|
|
|
277
308
|
if (abNeeded > 0n) {
|
|
309
|
+
usedAddressBalance.add(type);
|
|
278
310
|
commands.push(
|
|
279
311
|
TransactionCommands.MoveCall({
|
|
280
312
|
target: '0x2::coin::redeem_funds',
|
|
@@ -363,17 +395,20 @@ export async function resolveCoinBalance(
|
|
|
363
395
|
|
|
364
396
|
// Step 3: Remainder handling
|
|
365
397
|
for (const [type, mergedCoin] of mergedCoins) {
|
|
366
|
-
|
|
398
|
+
// When gas type used GasCoin (not AB), leftover stays in the gas coin — no remainder needed.
|
|
399
|
+
if (type === 'gas' && !usedAddressBalance.has(type)) continue;
|
|
367
400
|
|
|
401
|
+
const coinType = type === 'gas' ? SUI_TYPE : type;
|
|
368
402
|
const hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === 'balance');
|
|
403
|
+
const sourcedFromAB = usedAddressBalance.has(type);
|
|
369
404
|
|
|
370
|
-
if (hasBalanceIntent) {
|
|
371
|
-
//
|
|
405
|
+
if (hasBalanceIntent || sourcedFromAB) {
|
|
406
|
+
// Sourced from AB or balance intents exist: send remainder back to sender's address balance.
|
|
372
407
|
// coin::send_funds is gasless-eligible and handles zero amounts.
|
|
373
408
|
transactionData.commands.push(
|
|
374
409
|
TransactionCommands.MoveCall({
|
|
375
410
|
target: '0x2::coin::send_funds',
|
|
376
|
-
typeArguments: [
|
|
411
|
+
typeArguments: [coinType],
|
|
377
412
|
arguments: [
|
|
378
413
|
mergedCoin,
|
|
379
414
|
transactionData.addInput(
|
|
@@ -388,7 +423,7 @@ export async function resolveCoinBalance(
|
|
|
388
423
|
transactionData.commands.push(
|
|
389
424
|
TransactionCommands.MoveCall({
|
|
390
425
|
target: '0x2::coin::destroy_zero',
|
|
391
|
-
typeArguments: [
|
|
426
|
+
typeArguments: [coinType],
|
|
392
427
|
arguments: [mergedCoin],
|
|
393
428
|
}),
|
|
394
429
|
);
|
package/src/version.ts
CHANGED