@mysten/sui 2.14.0 → 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 +24 -0
- package/dist/bcs/index.d.mts +20 -20
- package/dist/client/core-resolver.mjs +3 -5
- package/dist/client/core-resolver.mjs.map +1 -1
- package/dist/grpc/index.d.mts +3 -1
- package/dist/grpc/index.mjs +2 -1
- 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/signature_verification_service.client.d.mts +4 -4
- package/dist/transactions/Transaction.d.mts +3 -3
- 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/utils/sui-types.d.mts.map +1 -1
- package/dist/utils/sui-types.mjs +1 -0
- package/dist/utils/sui-types.mjs.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/docs/clients/grpc.md +55 -2
- package/package.json +4 -2
- package/src/client/core-resolver.ts +10 -21
- package/src/grpc/index.ts +6 -0
- package/src/transactions/intents/CoinWithBalance.ts +41 -6
- package/src/utils/sui-types.ts +6 -0
- 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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sui-types.d.mts","names":[],"sources":["../../src/utils/sui-types.ts"],"mappings":";;iBAUgB,wBAAA,CAAyB,KAAA,WAAgB,KAAA;AAAA,cAe5C,kBAAA;AAAA,iBACG,iBAAA,CAAkB,KAAA,WAAgB,KAAA;AAAA,iBAIlC,kBAAA,CAAmB,KAAA;AAAA,iBAoDnB,gBAAA,CAAiB,IAAA;AAAA,KASrB,SAAA;EACX,OAAA;EACA,MAAA;EACA,IAAA;EACA,UAAA,YAAsB,SAAA;AAAA;AAAA,iBAwBP,cAAA,CAAe,IAAA,WAAe,SAAA;AAAA,iBAoC9B,kBAAA,CAAmB,IAAA,WAAe,SAAA;;AA7HlD;;;;;AAoDA;;;;;
|
|
1
|
+
{"version":3,"file":"sui-types.d.mts","names":[],"sources":["../../src/utils/sui-types.ts"],"mappings":";;iBAUgB,wBAAA,CAAyB,KAAA,WAAgB,KAAA;AAAA,cAe5C,kBAAA;AAAA,iBACG,iBAAA,CAAkB,KAAA,WAAgB,KAAA;AAAA,iBAIlC,kBAAA,CAAmB,KAAA;AAAA,iBAoDnB,gBAAA,CAAiB,IAAA;AAAA,KASrB,SAAA;EACX,OAAA;EACA,MAAA;EACA,IAAA;EACA,UAAA,YAAsB,SAAA;AAAA;AAAA,iBAwBP,cAAA,CAAe,IAAA,WAAe,SAAA;AAAA,iBAoC9B,kBAAA,CAAmB,IAAA,WAAe,SAAA;;AA7HlD;;;;;AAoDA;;;;;iBA0GgB,mBAAA,CAAoB,KAAA,UAAe,UAAA;AAAA,iBAQnC,oBAAA,CAAqB,KAAA,UAAe,UAAA"}
|
package/dist/utils/sui-types.mjs
CHANGED
|
@@ -86,6 +86,7 @@ function parseStructTag(type) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
function normalizeStructTag(type) {
|
|
89
|
+
if (typeof type === "string" && type.startsWith("vector<")) throw new Error("normalizeStructTag does not support vector types. Use normalizeTypeTag instead.");
|
|
89
90
|
const { address, module, name, typeParams } = typeof type === "string" ? parseStructTag(type) : type;
|
|
90
91
|
return `${address}::${module}::${name}${typeParams?.length > 0 ? `<${typeParams.map((typeParam) => typeof typeParam === "string" ? typeParam : normalizeStructTag(typeParam)).join(",")}>` : ""}`;
|
|
91
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sui-types.mjs","names":[],"sources":["../../src/utils/sui-types.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { fromBase58, splitGenericParameters } from '@mysten/bcs';\n\nimport { isValidNamedPackage } from './move-registry.js';\n\nconst TX_DIGEST_LENGTH = 32;\n\n/** Returns whether the tx digest is valid based on the serialization format */\nexport function isValidTransactionDigest(value: string): value is string {\n\ttry {\n\t\tconst buffer = fromBase58(value);\n\t\treturn buffer.length === TX_DIGEST_LENGTH;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// TODO - can we automatically sync this with rust length definition?\n// Source of truth is\n// https://github.com/MystenLabs/sui/blob/acb2b97ae21f47600e05b0d28127d88d0725561d/crates/sui-types/src/base_types.rs#L67\n// which uses the Move account address length\n// https://github.com/move-language/move/blob/67ec40dc50c66c34fd73512fcc412f3b68d67235/language/move-core/types/src/account_address.rs#L23 .\n\nexport const SUI_ADDRESS_LENGTH = 32;\nexport function isValidSuiAddress(value: string): value is string {\n\treturn isHex(value) && getHexByteLength(value) === SUI_ADDRESS_LENGTH;\n}\n\nexport function isValidSuiObjectId(value: string): boolean {\n\treturn isValidSuiAddress(value);\n}\n\nconst MOVE_IDENTIFIER_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n\nexport function isValidMoveIdentifier(name: string): boolean {\n\treturn MOVE_IDENTIFIER_REGEX.test(name);\n}\n\nconst PRIMITIVE_TYPE_TAGS = [\n\t'bool',\n\t'u8',\n\t'u16',\n\t'u32',\n\t'u64',\n\t'u128',\n\t'u256',\n\t'address',\n\t'signer',\n];\n\nconst VECTOR_TYPE_REGEX = /^vector<(.+)>$/;\n\nfunction isValidTypeTag(type: string): boolean {\n\tif (PRIMITIVE_TYPE_TAGS.includes(type)) return true;\n\n\tconst vectorMatch = type.match(VECTOR_TYPE_REGEX);\n\tif (vectorMatch) return isValidTypeTag(vectorMatch[1]);\n\n\tif (type.includes('::')) return isValidStructTag(type);\n\n\treturn false;\n}\n\nfunction isValidParsedStructTag(tag: StructTag): boolean {\n\tif (!isValidSuiAddress(tag.address) && !isValidNamedPackage(tag.address)) {\n\t\treturn false;\n\t}\n\n\tif (!isValidMoveIdentifier(tag.module) || !isValidMoveIdentifier(tag.name)) {\n\t\treturn false;\n\t}\n\n\treturn tag.typeParams.every((param) => {\n\t\tif (typeof param === 'string') {\n\t\t\treturn isValidTypeTag(param);\n\t\t}\n\t\treturn isValidParsedStructTag(param);\n\t});\n}\n\nexport function isValidStructTag(type: string): boolean {\n\ttry {\n\t\tconst tag = parseStructTag(type);\n\t\treturn isValidParsedStructTag(tag);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport type StructTag = {\n\taddress: string;\n\tmodule: string;\n\tname: string;\n\ttypeParams: (string | StructTag)[];\n};\n\nfunction parseTypeTag(type: string): string | StructTag {\n\tif (type.startsWith('vector<')) {\n\t\tif (!type.endsWith('>')) {\n\t\t\tthrow new Error(`Invalid type tag: ${type}`);\n\t\t}\n\t\tconst inner = type.slice(7, -1);\n\t\tif (!inner) {\n\t\t\tthrow new Error(`Invalid type tag: ${type}`);\n\t\t}\n\t\tconst parsed = parseTypeTag(inner);\n\t\tif (typeof parsed === 'string') {\n\t\t\treturn `vector<${parsed}>`;\n\t\t}\n\t\treturn `vector<${normalizeStructTag(parsed)}>`;\n\t}\n\n\tif (!type.includes('::')) return type;\n\n\treturn parseStructTag(type);\n}\n\nexport function parseStructTag(type: string): StructTag {\n\tconst parts = type.split('::');\n\n\tif (parts.length < 3) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst [address, module] = parts;\n\n\tif (!address || !module) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst isMvrPackage = isValidNamedPackage(address);\n\n\tconst rest = type.slice(address.length + module.length + 4);\n\tconst name = rest.includes('<') ? rest.slice(0, rest.indexOf('<')) : rest;\n\n\tif (!name || (rest.includes('<') && !rest.endsWith('>'))) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst typeParams = rest.includes('<')\n\t\t? splitGenericParameters(rest.slice(rest.indexOf('<') + 1, rest.lastIndexOf('>'))).map(\n\t\t\t\t(typeParam) => parseTypeTag(typeParam.trim()),\n\t\t\t)\n\t\t: [];\n\n\treturn {\n\t\taddress: isMvrPackage ? address : normalizeSuiAddress(address),\n\t\tmodule,\n\t\tname,\n\t\ttypeParams,\n\t};\n}\n\nexport function normalizeStructTag(type: string | StructTag): string {\n\tconst { address, module, name, typeParams } =\n\t\ttypeof type === 'string' ? parseStructTag(type) : type;\n\n\tconst formattedTypeParams =\n\t\ttypeParams?.length > 0\n\t\t\t? `<${typeParams\n\t\t\t\t\t.map((typeParam) =>\n\t\t\t\t\t\ttypeof typeParam === 'string' ? typeParam : normalizeStructTag(typeParam),\n\t\t\t\t\t)\n\t\t\t\t\t.join(',')}>`\n\t\t\t: '';\n\n\treturn `${address}::${module}::${name}${formattedTypeParams}`;\n}\n\n/**\n * Perform the following operations:\n * 1. Make the address lower case\n * 2. Prepend `0x` if the string does not start with `0x`.\n * 3. Add more zeros if the length of the address(excluding `0x`) is less than `SUI_ADDRESS_LENGTH`\n *\n * WARNING: if the address value itself starts with `0x`, e.g., `0x0x`, the default behavior\n * is to treat the first `0x` not as part of the address. The default behavior can be overridden by\n * setting `forceAdd0x` to true\n *\n */\nexport function normalizeSuiAddress(value: string, forceAdd0x: boolean = false): string {\n\tlet address = value.toLowerCase();\n\tif (!forceAdd0x && address.startsWith('0x')) {\n\t\taddress = address.slice(2);\n\t}\n\treturn `0x${address.padStart(SUI_ADDRESS_LENGTH * 2, '0')}`;\n}\n\nexport function normalizeSuiObjectId(value: string, forceAdd0x: boolean = false): string {\n\treturn normalizeSuiAddress(value, forceAdd0x);\n}\n\nfunction isHex(value: string): boolean {\n\treturn /^(0x|0X)?[a-fA-F0-9]+$/.test(value) && value.length % 2 === 0;\n}\n\nfunction getHexByteLength(value: string): number {\n\treturn /^(0x|0X)/.test(value) ? (value.length - 2) / 2 : value.length / 2;\n}\n"],"mappings":";;;;AAOA,MAAM,mBAAmB;;AAGzB,SAAgB,yBAAyB,OAAgC;AACxE,KAAI;AAEH,SADe,WAAW,MAAM,CAClB,WAAW;SAClB;AACP,SAAO;;;AAUT,MAAa,qBAAqB;AAClC,SAAgB,kBAAkB,OAAgC;AACjE,QAAO,MAAM,MAAM,IAAI,iBAAiB,MAAM,KAAK;;AAGpD,SAAgB,mBAAmB,OAAwB;AAC1D,QAAO,kBAAkB,MAAM;;AAGhC,MAAM,wBAAwB;AAE9B,SAAgB,sBAAsB,MAAuB;AAC5D,QAAO,sBAAsB,KAAK,KAAK;;AAGxC,MAAM,sBAAsB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAED,MAAM,oBAAoB;AAE1B,SAAS,eAAe,MAAuB;AAC9C,KAAI,oBAAoB,SAAS,KAAK,CAAE,QAAO;CAE/C,MAAM,cAAc,KAAK,MAAM,kBAAkB;AACjD,KAAI,YAAa,QAAO,eAAe,YAAY,GAAG;AAEtD,KAAI,KAAK,SAAS,KAAK,CAAE,QAAO,iBAAiB,KAAK;AAEtD,QAAO;;AAGR,SAAS,uBAAuB,KAAyB;AACxD,KAAI,CAAC,kBAAkB,IAAI,QAAQ,IAAI,CAAC,oBAAoB,IAAI,QAAQ,CACvE,QAAO;AAGR,KAAI,CAAC,sBAAsB,IAAI,OAAO,IAAI,CAAC,sBAAsB,IAAI,KAAK,CACzE,QAAO;AAGR,QAAO,IAAI,WAAW,OAAO,UAAU;AACtC,MAAI,OAAO,UAAU,SACpB,QAAO,eAAe,MAAM;AAE7B,SAAO,uBAAuB,MAAM;GACnC;;AAGH,SAAgB,iBAAiB,MAAuB;AACvD,KAAI;AAEH,SAAO,uBADK,eAAe,KAAK,CACE;SAC3B;AACP,SAAO;;;AAWT,SAAS,aAAa,MAAkC;AACvD,KAAI,KAAK,WAAW,UAAU,EAAE;AAC/B,MAAI,CAAC,KAAK,SAAS,IAAI,CACtB,OAAM,IAAI,MAAM,qBAAqB,OAAO;EAE7C,MAAM,QAAQ,KAAK,MAAM,GAAG,GAAG;AAC/B,MAAI,CAAC,MACJ,OAAM,IAAI,MAAM,qBAAqB,OAAO;EAE7C,MAAM,SAAS,aAAa,MAAM;AAClC,MAAI,OAAO,WAAW,SACrB,QAAO,UAAU,OAAO;AAEzB,SAAO,UAAU,mBAAmB,OAAO,CAAC;;AAG7C,KAAI,CAAC,KAAK,SAAS,KAAK,CAAE,QAAO;AAEjC,QAAO,eAAe,KAAK;;AAG5B,SAAgB,eAAe,MAAyB;CACvD,MAAM,QAAQ,KAAK,MAAM,KAAK;AAE9B,KAAI,MAAM,SAAS,EAClB,OAAM,IAAI,MAAM,uBAAuB,OAAO;CAG/C,MAAM,CAAC,SAAS,UAAU;AAE1B,KAAI,CAAC,WAAW,CAAC,OAChB,OAAM,IAAI,MAAM,uBAAuB,OAAO;CAG/C,MAAM,eAAe,oBAAoB,QAAQ;CAEjD,MAAM,OAAO,KAAK,MAAM,QAAQ,SAAS,OAAO,SAAS,EAAE;CAC3D,MAAM,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG;AAErE,KAAI,CAAC,QAAS,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CACtD,OAAM,IAAI,MAAM,uBAAuB,OAAO;CAG/C,MAAM,aAAa,KAAK,SAAS,IAAI,GAClC,uBAAuB,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAG,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAChF,cAAc,aAAa,UAAU,MAAM,CAAC,CAC7C,GACA,EAAE;AAEL,QAAO;EACN,SAAS,eAAe,UAAU,oBAAoB,QAAQ;EAC9D;EACA;EACA;EACA;;AAGF,SAAgB,mBAAmB,MAAkC;
|
|
1
|
+
{"version":3,"file":"sui-types.mjs","names":[],"sources":["../../src/utils/sui-types.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { fromBase58, splitGenericParameters } from '@mysten/bcs';\n\nimport { isValidNamedPackage } from './move-registry.js';\n\nconst TX_DIGEST_LENGTH = 32;\n\n/** Returns whether the tx digest is valid based on the serialization format */\nexport function isValidTransactionDigest(value: string): value is string {\n\ttry {\n\t\tconst buffer = fromBase58(value);\n\t\treturn buffer.length === TX_DIGEST_LENGTH;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// TODO - can we automatically sync this with rust length definition?\n// Source of truth is\n// https://github.com/MystenLabs/sui/blob/acb2b97ae21f47600e05b0d28127d88d0725561d/crates/sui-types/src/base_types.rs#L67\n// which uses the Move account address length\n// https://github.com/move-language/move/blob/67ec40dc50c66c34fd73512fcc412f3b68d67235/language/move-core/types/src/account_address.rs#L23 .\n\nexport const SUI_ADDRESS_LENGTH = 32;\nexport function isValidSuiAddress(value: string): value is string {\n\treturn isHex(value) && getHexByteLength(value) === SUI_ADDRESS_LENGTH;\n}\n\nexport function isValidSuiObjectId(value: string): boolean {\n\treturn isValidSuiAddress(value);\n}\n\nconst MOVE_IDENTIFIER_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n\nexport function isValidMoveIdentifier(name: string): boolean {\n\treturn MOVE_IDENTIFIER_REGEX.test(name);\n}\n\nconst PRIMITIVE_TYPE_TAGS = [\n\t'bool',\n\t'u8',\n\t'u16',\n\t'u32',\n\t'u64',\n\t'u128',\n\t'u256',\n\t'address',\n\t'signer',\n];\n\nconst VECTOR_TYPE_REGEX = /^vector<(.+)>$/;\n\nfunction isValidTypeTag(type: string): boolean {\n\tif (PRIMITIVE_TYPE_TAGS.includes(type)) return true;\n\n\tconst vectorMatch = type.match(VECTOR_TYPE_REGEX);\n\tif (vectorMatch) return isValidTypeTag(vectorMatch[1]);\n\n\tif (type.includes('::')) return isValidStructTag(type);\n\n\treturn false;\n}\n\nfunction isValidParsedStructTag(tag: StructTag): boolean {\n\tif (!isValidSuiAddress(tag.address) && !isValidNamedPackage(tag.address)) {\n\t\treturn false;\n\t}\n\n\tif (!isValidMoveIdentifier(tag.module) || !isValidMoveIdentifier(tag.name)) {\n\t\treturn false;\n\t}\n\n\treturn tag.typeParams.every((param) => {\n\t\tif (typeof param === 'string') {\n\t\t\treturn isValidTypeTag(param);\n\t\t}\n\t\treturn isValidParsedStructTag(param);\n\t});\n}\n\nexport function isValidStructTag(type: string): boolean {\n\ttry {\n\t\tconst tag = parseStructTag(type);\n\t\treturn isValidParsedStructTag(tag);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport type StructTag = {\n\taddress: string;\n\tmodule: string;\n\tname: string;\n\ttypeParams: (string | StructTag)[];\n};\n\nfunction parseTypeTag(type: string): string | StructTag {\n\tif (type.startsWith('vector<')) {\n\t\tif (!type.endsWith('>')) {\n\t\t\tthrow new Error(`Invalid type tag: ${type}`);\n\t\t}\n\t\tconst inner = type.slice(7, -1);\n\t\tif (!inner) {\n\t\t\tthrow new Error(`Invalid type tag: ${type}`);\n\t\t}\n\t\tconst parsed = parseTypeTag(inner);\n\t\tif (typeof parsed === 'string') {\n\t\t\treturn `vector<${parsed}>`;\n\t\t}\n\t\treturn `vector<${normalizeStructTag(parsed)}>`;\n\t}\n\n\tif (!type.includes('::')) return type;\n\n\treturn parseStructTag(type);\n}\n\nexport function parseStructTag(type: string): StructTag {\n\tconst parts = type.split('::');\n\n\tif (parts.length < 3) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst [address, module] = parts;\n\n\tif (!address || !module) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst isMvrPackage = isValidNamedPackage(address);\n\n\tconst rest = type.slice(address.length + module.length + 4);\n\tconst name = rest.includes('<') ? rest.slice(0, rest.indexOf('<')) : rest;\n\n\tif (!name || (rest.includes('<') && !rest.endsWith('>'))) {\n\t\tthrow new Error(`Invalid struct tag: ${type}`);\n\t}\n\n\tconst typeParams = rest.includes('<')\n\t\t? splitGenericParameters(rest.slice(rest.indexOf('<') + 1, rest.lastIndexOf('>'))).map(\n\t\t\t\t(typeParam) => parseTypeTag(typeParam.trim()),\n\t\t\t)\n\t\t: [];\n\n\treturn {\n\t\taddress: isMvrPackage ? address : normalizeSuiAddress(address),\n\t\tmodule,\n\t\tname,\n\t\ttypeParams,\n\t};\n}\n\nexport function normalizeStructTag(type: string | StructTag): string {\n\tif (typeof type === 'string' && type.startsWith('vector<')) {\n\t\tthrow new Error(\n\t\t\t'normalizeStructTag does not support vector types. Use normalizeTypeTag instead.',\n\t\t);\n\t}\n\n\tconst { address, module, name, typeParams } =\n\t\ttypeof type === 'string' ? parseStructTag(type) : type;\n\n\tconst formattedTypeParams =\n\t\ttypeParams?.length > 0\n\t\t\t? `<${typeParams\n\t\t\t\t\t.map((typeParam) =>\n\t\t\t\t\t\ttypeof typeParam === 'string' ? typeParam : normalizeStructTag(typeParam),\n\t\t\t\t\t)\n\t\t\t\t\t.join(',')}>`\n\t\t\t: '';\n\n\treturn `${address}::${module}::${name}${formattedTypeParams}`;\n}\n\n/**\n * Perform the following operations:\n * 1. Make the address lower case\n * 2. Prepend `0x` if the string does not start with `0x`.\n * 3. Add more zeros if the length of the address(excluding `0x`) is less than `SUI_ADDRESS_LENGTH`\n *\n * WARNING: if the address value itself starts with `0x`, e.g., `0x0x`, the default behavior\n * is to treat the first `0x` not as part of the address. The default behavior can be overridden by\n * setting `forceAdd0x` to true\n *\n */\nexport function normalizeSuiAddress(value: string, forceAdd0x: boolean = false): string {\n\tlet address = value.toLowerCase();\n\tif (!forceAdd0x && address.startsWith('0x')) {\n\t\taddress = address.slice(2);\n\t}\n\treturn `0x${address.padStart(SUI_ADDRESS_LENGTH * 2, '0')}`;\n}\n\nexport function normalizeSuiObjectId(value: string, forceAdd0x: boolean = false): string {\n\treturn normalizeSuiAddress(value, forceAdd0x);\n}\n\nfunction isHex(value: string): boolean {\n\treturn /^(0x|0X)?[a-fA-F0-9]+$/.test(value) && value.length % 2 === 0;\n}\n\nfunction getHexByteLength(value: string): number {\n\treturn /^(0x|0X)/.test(value) ? (value.length - 2) / 2 : value.length / 2;\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/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"
|
|
@@ -85,17 +85,15 @@ export async function coreClientResolveTransactionPlugin(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const needsSystemState = needsGasPrice || (needsPayment && usesGasCoin);
|
|
88
|
-
const [, systemStateResult, balanceResult, coinsResult,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
needsPayment && usesGasCoin ? client.core.getChainIdentifier() : null,
|
|
98
|
-
]);
|
|
88
|
+
const [, systemStateResult, balanceResult, coinsResult, chainIdResult] = await Promise.all([
|
|
89
|
+
normalizeInputs(transactionData, client),
|
|
90
|
+
needsSystemState ? client.core.getCurrentSystemState() : null,
|
|
91
|
+
needsPayment && gasPayer ? client.core.getBalance({ owner: gasPayer }) : null,
|
|
92
|
+
needsPayment && gasPayer
|
|
93
|
+
? client.core.listCoins({ owner: gasPayer, coinType: SUI_TYPE_ARG })
|
|
94
|
+
: null,
|
|
95
|
+
needsPayment && usesGasCoin ? client.core.getChainIdentifier() : null,
|
|
96
|
+
]);
|
|
99
97
|
|
|
100
98
|
await resolveObjectReferences(transactionData, client);
|
|
101
99
|
|
|
@@ -120,7 +118,6 @@ export async function coreClientResolveTransactionPlugin(
|
|
|
120
118
|
coins: coinsResult,
|
|
121
119
|
usesGasCoin,
|
|
122
120
|
withdrawals,
|
|
123
|
-
protocolConfig: protocolConfigResult?.protocolConfig,
|
|
124
121
|
gasPayer: gasPayer!,
|
|
125
122
|
chainIdentifier: chainIdResult?.chainIdentifier ?? null,
|
|
126
123
|
epoch: systemState?.epoch ?? null,
|
|
@@ -178,7 +175,6 @@ function setGasPayment({
|
|
|
178
175
|
coins,
|
|
179
176
|
usesGasCoin,
|
|
180
177
|
withdrawals,
|
|
181
|
-
protocolConfig,
|
|
182
178
|
gasPayer,
|
|
183
179
|
chainIdentifier,
|
|
184
180
|
epoch,
|
|
@@ -188,7 +184,6 @@ function setGasPayment({
|
|
|
188
184
|
coins: SuiClientTypes.ListCoinsResponse;
|
|
189
185
|
usesGasCoin: boolean;
|
|
190
186
|
withdrawals: bigint;
|
|
191
|
-
protocolConfig: SuiClientTypes.ProtocolConfig | undefined;
|
|
192
187
|
gasPayer: string;
|
|
193
188
|
chainIdentifier: string | null;
|
|
194
189
|
epoch: string | null;
|
|
@@ -221,13 +216,7 @@ function setGasPayment({
|
|
|
221
216
|
|
|
222
217
|
const reservationAmount = addressBalance - withdrawals;
|
|
223
218
|
|
|
224
|
-
if (
|
|
225
|
-
usesGasCoin &&
|
|
226
|
-
reservationAmount > 0n &&
|
|
227
|
-
protocolConfig?.featureFlags?.['enable_coin_reservation_obj_refs'] &&
|
|
228
|
-
chainIdentifier &&
|
|
229
|
-
epoch
|
|
230
|
-
) {
|
|
219
|
+
if (usesGasCoin && reservationAmount > 0n && chainIdentifier && epoch) {
|
|
231
220
|
transactionData.gasData.payment = [
|
|
232
221
|
createCoinReservationRef(reservationAmount, gasPayer, chainIdentifier, epoch),
|
|
233
222
|
...paymentCoins,
|
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/utils/sui-types.ts
CHANGED
|
@@ -154,6 +154,12 @@ export function parseStructTag(type: string): StructTag {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
export function normalizeStructTag(type: string | StructTag): string {
|
|
157
|
+
if (typeof type === 'string' && type.startsWith('vector<')) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'normalizeStructTag does not support vector types. Use normalizeTypeTag instead.',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
const { address, module, name, typeParams } =
|
|
158
164
|
typeof type === 'string' ? parseStructTag(type) : type;
|
|
159
165
|
|
package/src/version.ts
CHANGED