@msafe/sui3-sdk 1.0.17 → 1.0.19
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/dist/index.cjs +55 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +58 -7
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/utils/gasFunding.ts +92 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msafe/sui3-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "SDK for MSafe SUI V3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@changesets/changelog-github": "^0.7.0",
|
|
33
33
|
"@changesets/cli": "^2.31.0",
|
|
34
34
|
"@msafe/mpay-sdk-sui": "^1.0.25",
|
|
35
|
-
"@msafe/sui-app-store": "0.0.
|
|
36
|
-
"@msafe/sui3-utils": "^5.0
|
|
35
|
+
"@msafe/sui-app-store": "^0.0.357",
|
|
36
|
+
"@msafe/sui3-utils": "^5.1.0",
|
|
37
37
|
"@mysten/sui": "^2.16.2",
|
|
38
38
|
"@mysten/wallet-standard": "^0.20.3",
|
|
39
39
|
"@types/jest": "^30.0.0",
|
package/src/utils/gasFunding.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SuiClientTypes } from '@mysten/sui/client';
|
|
2
2
|
import type { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
3
3
|
import { Transaction } from '@mysten/sui/transactions';
|
|
4
|
-
import { normalizeSuiObjectId } from '@mysten/sui/utils';
|
|
4
|
+
import { normalizeStructTag, normalizeSuiObjectId } from '@mysten/sui/utils';
|
|
5
5
|
|
|
6
6
|
import { createCoinReservationRef } from '@/utils/coinReservation';
|
|
7
7
|
import { getAllCoins, SUI_COIN } from '@/utils/sui';
|
|
@@ -73,6 +73,29 @@ function collectUsedObjectIds(tx: Transaction): Set<string> {
|
|
|
73
73
|
}, new Set<string>());
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Mysten CoinWithBalance with useGasCoin=false (type is 0x2::sui::SUI, not "gas")
|
|
78
|
+
* loads every unused SUI coin as a mutable input at resolve time. Those coins
|
|
79
|
+
* cannot also be gas payment or the node rejects:
|
|
80
|
+
* "Mutable object ... cannot appear more than one in one transaction".
|
|
81
|
+
*/
|
|
82
|
+
function txConsumesOwnedSuiCoins(tx: Transaction): boolean {
|
|
83
|
+
return tx.getData().commands.some((cmd) => {
|
|
84
|
+
if (cmd.$kind !== '$Intent' || cmd.$Intent.name !== 'CoinWithBalance') {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const type = (cmd.$Intent.data as { type?: string } | undefined)?.type;
|
|
88
|
+
if (!type || type === 'gas') {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return normalizeStructTag(type) === normalizeStructTag(SUI_COIN);
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
async function loadSuiGasFunding(suiClient: SuiGrpcClient, owner: string, tx: Transaction): Promise<SuiGasFunding> {
|
|
77
100
|
const usedObjectIds = collectUsedObjectIds(tx);
|
|
78
101
|
const [coins, balanceRes] = await Promise.all([
|
|
@@ -80,6 +103,18 @@ async function loadSuiGasFunding(suiClient: SuiGrpcClient, owner: string, tx: Tr
|
|
|
80
103
|
suiClient.getBalance({ owner, coinType: SUI_COIN }),
|
|
81
104
|
]);
|
|
82
105
|
|
|
106
|
+
const addressBalance = BigInt(balanceRes.balance.addressBalance);
|
|
107
|
+
|
|
108
|
+
// Intent will take every owned SUI coin at resolve time — leave them off gas payment.
|
|
109
|
+
if (txConsumesOwnedSuiCoins(tx)) {
|
|
110
|
+
return {
|
|
111
|
+
paymentCoins: [],
|
|
112
|
+
coinBalance: 0n,
|
|
113
|
+
addressBalance,
|
|
114
|
+
total: addressBalance,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
83
118
|
const paymentCoins = coins
|
|
84
119
|
.filter((coin) => !usedObjectIds.has(normalizeSuiObjectId(coin.objectId)) && BigInt(coin.balance) > 0n)
|
|
85
120
|
.map((coin) => ({
|
|
@@ -90,7 +125,6 @@ async function loadSuiGasFunding(suiClient: SuiGrpcClient, owner: string, tx: Tr
|
|
|
90
125
|
}));
|
|
91
126
|
|
|
92
127
|
const coinBalance = paymentCoins.reduce((sum, coin) => sum + coin.balance, 0n);
|
|
93
|
-
const addressBalance = BigInt(balanceRes.balance.addressBalance);
|
|
94
128
|
return {
|
|
95
129
|
paymentCoins,
|
|
96
130
|
coinBalance,
|
|
@@ -212,6 +246,44 @@ export async function selectGasFunding(input: {
|
|
|
212
246
|
* cheaper payment path. Budget is left unset so Mysten can dry-run with MAX_GAS
|
|
213
247
|
* overrides (avoids locking the full balance away from GasCoin splits).
|
|
214
248
|
*/
|
|
249
|
+
/**
|
|
250
|
+
* Clone a tx with gas budget/payment/expiration cleared.
|
|
251
|
+
* Needed because Mysten treats budget "0" as set (truthy) and skips re-estimation,
|
|
252
|
+
* and app-store helper.build() often pre-builds with payment:[] + budget:"0".
|
|
253
|
+
*/
|
|
254
|
+
function cloneWithClearedGasConfig(tx: Transaction): Transaction {
|
|
255
|
+
const data = structuredClone(tx.getData()) as {
|
|
256
|
+
version: number;
|
|
257
|
+
sender: string | null;
|
|
258
|
+
expiration: unknown;
|
|
259
|
+
gasData: {
|
|
260
|
+
budget: string | null;
|
|
261
|
+
price: string | null;
|
|
262
|
+
owner: string | null;
|
|
263
|
+
payment: unknown;
|
|
264
|
+
};
|
|
265
|
+
inputs: unknown;
|
|
266
|
+
commands: unknown;
|
|
267
|
+
};
|
|
268
|
+
data.gasData.budget = null;
|
|
269
|
+
data.gasData.price = null;
|
|
270
|
+
data.gasData.payment = null;
|
|
271
|
+
// Address-balance gas bakes ValidDuring; clear so classic gas probe is valid.
|
|
272
|
+
data.expiration = null;
|
|
273
|
+
return Transaction.from(JSON.stringify(data));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isPositiveBudget(budget: string | number | bigint | null | undefined): boolean {
|
|
277
|
+
if (budget == null) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
return BigInt(budget) > 0n;
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
215
287
|
export async function estimateGasBudget(input: {
|
|
216
288
|
tx: Transaction;
|
|
217
289
|
suiClient: SuiGrpcClient;
|
|
@@ -224,7 +296,11 @@ export async function estimateGasBudget(input: {
|
|
|
224
296
|
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
225
297
|
}
|
|
226
298
|
|
|
227
|
-
const
|
|
299
|
+
const { budget, payment } = tx.getData().gasData;
|
|
300
|
+
// app-store / Mysten pre-build can leave payment:[] with budget "0".
|
|
301
|
+
// Mysten's setGasBudget skips when budget is the string "0" (truthy), so clear first.
|
|
302
|
+
const mustResetGas = (payment != null && payment.length === 0) || (budget != null && !isPositiveBudget(budget));
|
|
303
|
+
const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : Transaction.from(tx);
|
|
228
304
|
probe.setSender(owner);
|
|
229
305
|
probe.setGasPrice(gasPrice);
|
|
230
306
|
|
|
@@ -273,16 +349,27 @@ export async function prepareGasFunding(input: {
|
|
|
273
349
|
// Never trust a budget baked into plain-tx / Mysten auto AB-gas txs.
|
|
274
350
|
// Those budgets are often under-estimated for payment: [] and would skip
|
|
275
351
|
// estimateGasBudget if we kept existingBudget.
|
|
352
|
+
// Also never trust budget "0" / 0n — Mysten treats "0" as set and skips resolve.
|
|
276
353
|
let { gasBudget } = input;
|
|
354
|
+
if (gasBudget != null && gasBudget <= 0n) {
|
|
355
|
+
gasBudget = undefined;
|
|
356
|
+
}
|
|
357
|
+
const existingPositive = isPositiveBudget(existingBudget) ? BigInt(existingBudget as string) : null;
|
|
358
|
+
|
|
277
359
|
if (gasBudget == null) {
|
|
278
|
-
if (bakedAddressBalanceGas ||
|
|
360
|
+
if (bakedAddressBalanceGas || existingPositive == null) {
|
|
279
361
|
gasBudget = await estimateGasBudget({ tx, suiClient, owner, gasPrice });
|
|
280
362
|
} else {
|
|
281
|
-
gasBudget =
|
|
363
|
+
gasBudget = existingPositive;
|
|
282
364
|
}
|
|
283
365
|
}
|
|
284
366
|
|
|
285
367
|
tx.setGasBudget(gasBudget);
|
|
368
|
+
// Drop ValidDuring from a prior address-balance pre-build; classic gas must not keep it,
|
|
369
|
+
// and AB gas will re-attach expiration during the final build when payment is [].
|
|
370
|
+
if (bakedAddressBalanceGas) {
|
|
371
|
+
tx.setExpiration(null);
|
|
372
|
+
}
|
|
286
373
|
return selectGasFunding({ tx, suiClient, owner, gasBudget });
|
|
287
374
|
}
|
|
288
375
|
|