@msafe/sui3-sdk 1.0.15 → 1.0.18
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 +484 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -2
- package/dist/index.d.ts +70 -2
- package/dist/index.js +476 -163
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/core/MSafeAccount.ts +16 -1
- package/src/simulate/simulator.ts +20 -12
- package/src/utils/coinReservation.ts +65 -0
- package/src/utils/gasFunding.ts +362 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/transaction.ts +16 -3
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AddressBookSDK: () => AddressBookSDK,
|
|
34
|
+
COIN_RESERVATION_MAGIC: () => COIN_RESERVATION_MAGIC,
|
|
34
35
|
COIN_TYPE_ARG_REGEX: () => COIN_TYPE_ARG_REGEX,
|
|
35
36
|
Coin: () => Coin,
|
|
36
37
|
CoinHelper: () => CoinHelper,
|
|
@@ -42,6 +43,7 @@ __export(src_exports, {
|
|
|
42
43
|
HexToUint8Array: () => HexToUint8Array,
|
|
43
44
|
InfoTypeEnabledDto: () => InfoTypeEnabledDto,
|
|
44
45
|
InfoTypeKey: () => InfoTypeKey,
|
|
46
|
+
InsufficientGasFundsError: () => InsufficientGasFundsError,
|
|
45
47
|
LOCAL_API_URL: () => LOCAL_API_URL,
|
|
46
48
|
LOCAL_SYNCING_URL: () => LOCAL_SYNCING_URL,
|
|
47
49
|
MAINNET_RPC_URL: () => MAINNET_RPC_URL,
|
|
@@ -59,11 +61,18 @@ __export(src_exports, {
|
|
|
59
61
|
TESTNET_RPC_URL: () => TESTNET_RPC_URL,
|
|
60
62
|
Uint8ArrayToHex: () => Uint8ArrayToHex,
|
|
61
63
|
addPrefix: () => addPrefix,
|
|
64
|
+
computeGasBudget: () => computeGasBudget,
|
|
65
|
+
createCoinReservationRef: () => createCoinReservationRef,
|
|
66
|
+
estimateGasBudget: () => estimateGasBudget,
|
|
62
67
|
getAllCoins: () => getAllCoins,
|
|
63
68
|
getMSafeConfig: () => getMSafeConfig,
|
|
64
69
|
getPublicKeyFromChain: () => getPublicKeyFromChain,
|
|
65
70
|
httpUrlToGrpcBaseUrl: () => httpUrlToGrpcBaseUrl,
|
|
71
|
+
isCoinReservationDigest: () => isCoinReservationDigest,
|
|
66
72
|
msafeChainToSuiNetwork: () => msafeChainToSuiNetwork,
|
|
73
|
+
preferClassicGasPayment: () => preferClassicGasPayment,
|
|
74
|
+
prepareGasFunding: () => prepareGasFunding,
|
|
75
|
+
selectGasFunding: () => selectGasFunding,
|
|
67
76
|
stringToBuffer: () => stringToBuffer,
|
|
68
77
|
toSuiTransaction: () => toSuiTransaction
|
|
69
78
|
});
|
|
@@ -188,128 +197,93 @@ var InvitationSDK = class {
|
|
|
188
197
|
// src/core/MSafeAccount.ts
|
|
189
198
|
var import_sui_app_store = require("@msafe/sui-app-store");
|
|
190
199
|
var import_sui3_utils4 = require("@msafe/sui3-utils");
|
|
191
|
-
var
|
|
192
|
-
var
|
|
200
|
+
var import_transactions4 = require("@mysten/sui/transactions");
|
|
201
|
+
var import_utils5 = require("@mysten/sui/utils");
|
|
193
202
|
var import_wallet_standard = require("@mysten/wallet-standard");
|
|
194
203
|
|
|
195
204
|
// src/simulate/simulator.ts
|
|
205
|
+
var import_transactions2 = require("@mysten/sui/transactions");
|
|
206
|
+
|
|
207
|
+
// src/utils/gasFunding.ts
|
|
196
208
|
var import_transactions = require("@mysten/sui/transactions");
|
|
197
|
-
var
|
|
198
|
-
function formatExecutionError(error) {
|
|
199
|
-
return error.message;
|
|
200
|
-
}
|
|
201
|
-
function gasObjectToReference(gas) {
|
|
202
|
-
if (!gas?.objectId) {
|
|
203
|
-
throw new Error("Gas object missing from simulation effects");
|
|
204
|
-
}
|
|
205
|
-
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
206
|
-
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
207
|
-
return { objectId: gas.objectId, version, digest };
|
|
208
|
-
}
|
|
209
|
-
var Simulator = class {
|
|
210
|
-
constructor(globals) {
|
|
211
|
-
this.globals = globals;
|
|
212
|
-
}
|
|
213
|
-
async simulate(input) {
|
|
214
|
-
const tx = this.copyTransaction(input.txb);
|
|
215
|
-
const { suiClient } = this.globals;
|
|
216
|
-
const gasPrice = await this.getGasPrice();
|
|
217
|
-
tx.setGasPrice(gasPrice);
|
|
218
|
-
tx.setSender(input.sender);
|
|
219
|
-
let built;
|
|
220
|
-
try {
|
|
221
|
-
built = await tx.build({ client: suiClient });
|
|
222
|
-
} catch (e) {
|
|
223
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
224
|
-
return {
|
|
225
|
-
success: false,
|
|
226
|
-
gasPrice,
|
|
227
|
-
simulationError: message
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
let inspectResult;
|
|
231
|
-
try {
|
|
232
|
-
inspectResult = await suiClient.simulateTransaction({
|
|
233
|
-
transaction: built,
|
|
234
|
-
include: {
|
|
235
|
-
effects: true,
|
|
236
|
-
balanceChanges: true,
|
|
237
|
-
events: true,
|
|
238
|
-
objectTypes: true,
|
|
239
|
-
transaction: true,
|
|
240
|
-
bcs: true,
|
|
241
|
-
commandResults: true
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
} catch (e) {
|
|
245
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
246
|
-
return {
|
|
247
|
-
success: false,
|
|
248
|
-
gasPrice,
|
|
249
|
-
simulationError: message
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
253
|
-
const { effects } = txRow;
|
|
254
|
-
if (!effects) {
|
|
255
|
-
throw new Error("simulateTransaction did not return effects");
|
|
256
|
-
}
|
|
257
|
-
const success = effects.status.success === true;
|
|
258
|
-
if (!success) {
|
|
259
|
-
const err = effects.status.success === false ? effects.status.error : null;
|
|
260
|
-
return {
|
|
261
|
-
success,
|
|
262
|
-
gasPrice,
|
|
263
|
-
response: inspectResult,
|
|
264
|
-
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
268
|
-
return {
|
|
269
|
-
success,
|
|
270
|
-
gasPrice,
|
|
271
|
-
gasObject: gasObjectToReference(effects.gasObject),
|
|
272
|
-
gasUsed: effects.gasUsed,
|
|
273
|
-
gasBudget,
|
|
274
|
-
response: inspectResult
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
async getGasPrice() {
|
|
278
|
-
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
279
|
-
return BigInt(referenceGasPrice);
|
|
280
|
-
}
|
|
281
|
-
toGasBudget(gasUsed, gasPrice) {
|
|
282
|
-
const { computationCost, storageCost, storageRebate } = gasUsed;
|
|
283
|
-
const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
|
|
284
|
-
const baseComputationCostWithOverhead = BigInt(computationCost) + safeOverhead;
|
|
285
|
-
const gasBudget = baseComputationCostWithOverhead + BigInt(storageCost) - BigInt(storageRebate);
|
|
286
|
-
return gasBudget > baseComputationCostWithOverhead ? gasBudget : baseComputationCostWithOverhead;
|
|
287
|
-
}
|
|
288
|
-
copyTransaction(tx) {
|
|
289
|
-
return import_transactions.Transaction.from(tx);
|
|
290
|
-
}
|
|
291
|
-
};
|
|
209
|
+
var import_utils4 = require("@mysten/sui/utils");
|
|
292
210
|
|
|
293
|
-
// src/utils/
|
|
294
|
-
var
|
|
295
|
-
|
|
296
|
-
|
|
211
|
+
// src/utils/coinReservation.ts
|
|
212
|
+
var import_bcs = require("@mysten/sui/bcs");
|
|
213
|
+
var import_utils = require("@mysten/sui/utils");
|
|
214
|
+
var SUI_ACCUMULATOR_ROOT_OBJECT_ID = (0, import_utils.normalizeSuiAddress)("0xacc");
|
|
215
|
+
var ACCUMULATOR_KEY_TYPE_TAG = import_bcs.TypeTagSerializer.parseFromStr(
|
|
216
|
+
"0x2::accumulator::Key<0x2::balance::Balance<0x2::sui::SUI>>"
|
|
217
|
+
);
|
|
218
|
+
var COIN_RESERVATION_MAGIC = new Uint8Array([
|
|
219
|
+
172,
|
|
220
|
+
172,
|
|
221
|
+
172,
|
|
222
|
+
172,
|
|
223
|
+
172,
|
|
224
|
+
172,
|
|
225
|
+
172,
|
|
226
|
+
172,
|
|
227
|
+
172,
|
|
228
|
+
172,
|
|
229
|
+
172,
|
|
230
|
+
172,
|
|
231
|
+
172,
|
|
232
|
+
172,
|
|
233
|
+
172,
|
|
234
|
+
172,
|
|
235
|
+
172,
|
|
236
|
+
172,
|
|
237
|
+
172,
|
|
238
|
+
172
|
|
239
|
+
]);
|
|
240
|
+
function isCoinReservationDigest(digestBase58) {
|
|
241
|
+
const digestBytes = (0, import_utils.fromBase58)(digestBase58);
|
|
242
|
+
const last20Bytes = digestBytes.slice(12, 32);
|
|
243
|
+
return last20Bytes.every((byte, i) => byte === COIN_RESERVATION_MAGIC[i]);
|
|
297
244
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
245
|
+
function deriveReservationObjectId(owner, chainIdentifier) {
|
|
246
|
+
const keyBcs = import_bcs.bcs.Address.serialize(owner).toBytes();
|
|
247
|
+
const accumulatorId = (0, import_utils.deriveDynamicFieldID)(SUI_ACCUMULATOR_ROOT_OBJECT_ID, ACCUMULATOR_KEY_TYPE_TAG, keyBcs);
|
|
248
|
+
const accBytes = (0, import_utils.fromHex)(accumulatorId.slice(2));
|
|
249
|
+
const chainBytes = (0, import_utils.fromBase58)(chainIdentifier);
|
|
250
|
+
if (chainBytes.length !== 32) {
|
|
251
|
+
throw new Error(`Invalid chain identifier length: expected 32 bytes, got ${chainBytes.length}`);
|
|
252
|
+
}
|
|
253
|
+
const xored = new Uint8Array(32);
|
|
254
|
+
for (let i = 0; i < 32; i++) {
|
|
255
|
+
xored[i] = accBytes[i] ^ chainBytes[i];
|
|
256
|
+
}
|
|
257
|
+
return `0x${(0, import_utils.toHex)(xored)}`;
|
|
300
258
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
259
|
+
function createCoinReservationRef(reservedBalance, owner, chainIdentifier, epoch) {
|
|
260
|
+
const digestBytes = new Uint8Array(32);
|
|
261
|
+
const view = new DataView(digestBytes.buffer);
|
|
262
|
+
view.setBigUint64(0, reservedBalance, true);
|
|
263
|
+
const epochNum = Number(epoch);
|
|
264
|
+
if (!Number.isSafeInteger(epochNum) || epochNum < 0 || epochNum > 4294967295) {
|
|
265
|
+
throw new Error(`Epoch ${epoch} out of u32 range for coin reservation digest`);
|
|
266
|
+
}
|
|
267
|
+
view.setUint32(8, epochNum, true);
|
|
268
|
+
digestBytes.set(COIN_RESERVATION_MAGIC, 12);
|
|
269
|
+
return {
|
|
270
|
+
objectId: deriveReservationObjectId(owner, chainIdentifier),
|
|
271
|
+
version: "0",
|
|
272
|
+
digest: (0, import_utils.toBase58)(digestBytes)
|
|
273
|
+
};
|
|
303
274
|
}
|
|
304
275
|
|
|
305
|
-
// src/utils/
|
|
306
|
-
var
|
|
276
|
+
// src/utils/sui.ts
|
|
277
|
+
var import_sui3_utils3 = require("@msafe/sui3-utils");
|
|
278
|
+
var import_cryptography = require("@mysten/sui/cryptography");
|
|
279
|
+
var import_graphql = require("@mysten/sui/graphql");
|
|
280
|
+
var import_multisig = require("@mysten/sui/multisig");
|
|
307
281
|
|
|
308
282
|
// src/utils/format.ts
|
|
309
|
-
var
|
|
283
|
+
var import_utils3 = require("@mysten/sui/utils");
|
|
310
284
|
|
|
311
285
|
// src/utils/coin.ts
|
|
312
|
-
var
|
|
286
|
+
var import_utils2 = require("@mysten/sui/utils");
|
|
313
287
|
var CoinHelper = class {
|
|
314
288
|
_client;
|
|
315
289
|
_coinMetaReg;
|
|
@@ -318,7 +292,7 @@ var CoinHelper = class {
|
|
|
318
292
|
this._coinMetaReg = /* @__PURE__ */ new Map();
|
|
319
293
|
}
|
|
320
294
|
async getCoinMeta(coinType) {
|
|
321
|
-
const normalized = (0,
|
|
295
|
+
const normalized = (0, import_utils2.normalizeStructTag)(coinType);
|
|
322
296
|
if (this._coinMetaReg.has(normalized)) {
|
|
323
297
|
return this._coinMetaReg.get(normalized);
|
|
324
298
|
}
|
|
@@ -339,10 +313,10 @@ var Coin = class _Coin {
|
|
|
339
313
|
if (!type) {
|
|
340
314
|
return false;
|
|
341
315
|
}
|
|
342
|
-
return (0,
|
|
316
|
+
return (0, import_utils2.normalizeStructTag)(type).match(COIN_TYPE_ARG_REGEX) != null;
|
|
343
317
|
}
|
|
344
318
|
static getCoinType(type) {
|
|
345
|
-
const [, res] = (0,
|
|
319
|
+
const [, res] = (0, import_utils2.normalizeStructTag)(type).match(COIN_TYPE_ARG_REGEX) ?? [];
|
|
346
320
|
return res || null;
|
|
347
321
|
}
|
|
348
322
|
static getBalance(data) {
|
|
@@ -370,16 +344,16 @@ var Coin = class _Coin {
|
|
|
370
344
|
// src/utils/format.ts
|
|
371
345
|
var Formatter = class {
|
|
372
346
|
static normalizeSuiAddress(addr) {
|
|
373
|
-
return (0,
|
|
347
|
+
return (0, import_utils3.normalizeSuiAddress)(addr);
|
|
374
348
|
}
|
|
375
349
|
static normalizeStructTag(struct) {
|
|
376
|
-
return (0,
|
|
350
|
+
return (0, import_utils3.normalizeStructTag)(struct);
|
|
377
351
|
}
|
|
378
352
|
static isSuiAddressEqual(addr1, addr2) {
|
|
379
|
-
return (0,
|
|
353
|
+
return (0, import_utils3.normalizeSuiAddress)(addr1) === (0, import_utils3.normalizeSuiAddress)(addr2);
|
|
380
354
|
}
|
|
381
355
|
static isSuiStructEqual(struct1, struct2) {
|
|
382
|
-
return (0,
|
|
356
|
+
return (0, import_utils3.normalizeStructTag)(struct1) === (0, import_utils3.normalizeStructTag)(struct2);
|
|
383
357
|
}
|
|
384
358
|
static isCoinObjectType(struct) {
|
|
385
359
|
return Coin.isCoin(struct);
|
|
@@ -392,50 +366,7 @@ function addPrefix(s, prefix) {
|
|
|
392
366
|
return prefix + s;
|
|
393
367
|
}
|
|
394
368
|
|
|
395
|
-
// src/utils/crypto.ts
|
|
396
|
-
var SignatureVerifier = class _SignatureVerifier {
|
|
397
|
-
static async getPublicKeyFromSignature(input) {
|
|
398
|
-
if (input.messageType === "TransactionBlock") {
|
|
399
|
-
return (0, import_verify.verifyTransactionSignature)(input.message, input.signature);
|
|
400
|
-
}
|
|
401
|
-
return (0, import_verify.verifyPersonalMessageSignature)(input.message, input.signature);
|
|
402
|
-
}
|
|
403
|
-
static async getPublicKeyFromPersonalSignature(input) {
|
|
404
|
-
const message = stringToBuffer(input.messageStr);
|
|
405
|
-
return this.getPublicKeyFromSignature({
|
|
406
|
-
message,
|
|
407
|
-
messageType: "Personal",
|
|
408
|
-
signature: input.signature
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
static async verifySignature(input) {
|
|
412
|
-
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
413
|
-
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
414
|
-
}
|
|
415
|
-
static async verifyPersonalSignature(input) {
|
|
416
|
-
const message = stringToBuffer(input.messageStr);
|
|
417
|
-
return this.verifySignature({
|
|
418
|
-
message,
|
|
419
|
-
messageType: "Personal",
|
|
420
|
-
signature: input.signature,
|
|
421
|
-
targetAddress: input.targetAddress
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
static async verifyTransactionSignature(input) {
|
|
425
|
-
return this.verifySignature({
|
|
426
|
-
messageType: "TransactionBlock",
|
|
427
|
-
message: input.payload,
|
|
428
|
-
signature: input.signature,
|
|
429
|
-
targetAddress: input.targetAddress
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
|
|
434
369
|
// src/utils/sui.ts
|
|
435
|
-
var import_sui3_utils3 = require("@msafe/sui3-utils");
|
|
436
|
-
var import_cryptography = require("@mysten/sui/cryptography");
|
|
437
|
-
var import_graphql = require("@mysten/sui/graphql");
|
|
438
|
-
var import_multisig = require("@mysten/sui/multisig");
|
|
439
370
|
var SUI_COIN = "0x2::sui::SUI";
|
|
440
371
|
var GRAPHQL_URL_BY_NETWORK = {
|
|
441
372
|
mainnet: "https://sui-mainnet.mystenlabs.com/graphql",
|
|
@@ -547,14 +478,382 @@ async function getAllCoins(input) {
|
|
|
547
478
|
return res;
|
|
548
479
|
}
|
|
549
480
|
|
|
481
|
+
// src/utils/gasFunding.ts
|
|
482
|
+
var GAS_SAFE_OVERHEAD = 1000n;
|
|
483
|
+
var InsufficientGasFundsError = class extends Error {
|
|
484
|
+
gasBudget;
|
|
485
|
+
coinBalance;
|
|
486
|
+
addressBalance;
|
|
487
|
+
constructor(gasBudget, coinBalance, addressBalance) {
|
|
488
|
+
const total = coinBalance + addressBalance;
|
|
489
|
+
super(
|
|
490
|
+
`Insufficient gas funds: need ${gasBudget}, have coinBalance=${coinBalance} + addressBalance=${addressBalance} = ${total}`
|
|
491
|
+
);
|
|
492
|
+
this.name = "InsufficientGasFundsError";
|
|
493
|
+
this.gasBudget = gasBudget;
|
|
494
|
+
this.coinBalance = coinBalance;
|
|
495
|
+
this.addressBalance = addressBalance;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
function computeGasBudget(gasUsed, gasPrice) {
|
|
499
|
+
const { computationCost, storageCost } = gasUsed;
|
|
500
|
+
const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
|
|
501
|
+
return BigInt(computationCost) + BigInt(storageCost) + safeOverhead;
|
|
502
|
+
}
|
|
503
|
+
function collectUsedObjectIds(tx) {
|
|
504
|
+
return tx.getData().inputs.reduce((used, input) => {
|
|
505
|
+
const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
|
|
506
|
+
if (immOrOwned) {
|
|
507
|
+
used.add((0, import_utils4.normalizeSuiObjectId)(immOrOwned));
|
|
508
|
+
return used;
|
|
509
|
+
}
|
|
510
|
+
const unresolved = input.UnresolvedObject?.objectId;
|
|
511
|
+
if (unresolved) {
|
|
512
|
+
used.add((0, import_utils4.normalizeSuiObjectId)(unresolved));
|
|
513
|
+
}
|
|
514
|
+
return used;
|
|
515
|
+
}, /* @__PURE__ */ new Set());
|
|
516
|
+
}
|
|
517
|
+
async function loadSuiGasFunding(suiClient, owner, tx) {
|
|
518
|
+
const usedObjectIds = collectUsedObjectIds(tx);
|
|
519
|
+
const [coins, balanceRes] = await Promise.all([
|
|
520
|
+
getAllCoins({ suiClient, owner, coinType: SUI_COIN }),
|
|
521
|
+
suiClient.getBalance({ owner, coinType: SUI_COIN })
|
|
522
|
+
]);
|
|
523
|
+
const paymentCoins = coins.filter((coin) => !usedObjectIds.has((0, import_utils4.normalizeSuiObjectId)(coin.objectId)) && BigInt(coin.balance) > 0n).map((coin) => ({
|
|
524
|
+
objectId: coin.objectId,
|
|
525
|
+
version: coin.version,
|
|
526
|
+
digest: coin.digest,
|
|
527
|
+
balance: BigInt(coin.balance)
|
|
528
|
+
}));
|
|
529
|
+
const coinBalance = paymentCoins.reduce((sum, coin) => sum + coin.balance, 0n);
|
|
530
|
+
const addressBalance = BigInt(balanceRes.balance.addressBalance);
|
|
531
|
+
return {
|
|
532
|
+
paymentCoins,
|
|
533
|
+
coinBalance,
|
|
534
|
+
addressBalance,
|
|
535
|
+
total: coinBalance + addressBalance
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function toObjectRefs(coins) {
|
|
539
|
+
return coins.map(({ objectId, version, digest }) => ({ objectId, version, digest }));
|
|
540
|
+
}
|
|
541
|
+
async function applyMixedTopUp(input) {
|
|
542
|
+
const { tx, suiClient, owner, paymentCoins, topUpAmount } = input;
|
|
543
|
+
if (paymentCoins.length === 0) {
|
|
544
|
+
throw new Error("mixed gas top-up requires at least one SUI coin object");
|
|
545
|
+
}
|
|
546
|
+
if (topUpAmount <= 0n) {
|
|
547
|
+
tx.setGasPayment(toObjectRefs(paymentCoins));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const [{ chainIdentifier }, { systemState }] = await Promise.all([
|
|
551
|
+
suiClient.core.getChainIdentifier(),
|
|
552
|
+
suiClient.core.getCurrentSystemState()
|
|
553
|
+
]);
|
|
554
|
+
const reservation = createCoinReservationRef(topUpAmount, owner, chainIdentifier, systemState.epoch);
|
|
555
|
+
tx.setGasPayment([...toObjectRefs(paymentCoins), reservation]);
|
|
556
|
+
}
|
|
557
|
+
async function selectGasFunding(input) {
|
|
558
|
+
const { tx, suiClient, owner, gasBudget } = input;
|
|
559
|
+
const { payment } = tx.getData().gasData;
|
|
560
|
+
if (payment != null && payment.length > 0) {
|
|
561
|
+
const funding2 = await loadSuiGasFunding(suiClient, owner, tx);
|
|
562
|
+
return {
|
|
563
|
+
mode: "classic",
|
|
564
|
+
gasBudget,
|
|
565
|
+
coinBalance: funding2.coinBalance,
|
|
566
|
+
addressBalance: funding2.addressBalance
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
570
|
+
const { paymentCoins, coinBalance, addressBalance, total } = funding;
|
|
571
|
+
if (total < gasBudget) {
|
|
572
|
+
throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
|
|
573
|
+
}
|
|
574
|
+
if (paymentCoins.length > 0 && coinBalance >= gasBudget) {
|
|
575
|
+
tx.setGasPayment(toObjectRefs(paymentCoins));
|
|
576
|
+
return { mode: "classic", gasBudget, coinBalance, addressBalance };
|
|
577
|
+
}
|
|
578
|
+
if (paymentCoins.length > 0 && total >= gasBudget) {
|
|
579
|
+
const topUpAmount = gasBudget - coinBalance;
|
|
580
|
+
await applyMixedTopUp({
|
|
581
|
+
tx,
|
|
582
|
+
suiClient,
|
|
583
|
+
owner,
|
|
584
|
+
paymentCoins,
|
|
585
|
+
topUpAmount
|
|
586
|
+
});
|
|
587
|
+
return { mode: "mixedTopUp", gasBudget, coinBalance, addressBalance };
|
|
588
|
+
}
|
|
589
|
+
if (addressBalance >= gasBudget) {
|
|
590
|
+
tx.setGasPayment([]);
|
|
591
|
+
return { mode: "addressBalance", gasBudget, coinBalance, addressBalance };
|
|
592
|
+
}
|
|
593
|
+
throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
|
|
594
|
+
}
|
|
595
|
+
function cloneWithClearedGasConfig(tx) {
|
|
596
|
+
const data = structuredClone(tx.getData());
|
|
597
|
+
data.gasData.budget = null;
|
|
598
|
+
data.gasData.price = null;
|
|
599
|
+
data.gasData.payment = null;
|
|
600
|
+
data.expiration = null;
|
|
601
|
+
return import_transactions.Transaction.from(JSON.stringify(data));
|
|
602
|
+
}
|
|
603
|
+
function isPositiveBudget(budget) {
|
|
604
|
+
if (budget == null) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
return BigInt(budget) > 0n;
|
|
609
|
+
} catch {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function estimateGasBudget(input) {
|
|
614
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
615
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
616
|
+
if (funding.total <= 0n) {
|
|
617
|
+
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
618
|
+
}
|
|
619
|
+
const { budget, payment } = tx.getData().gasData;
|
|
620
|
+
const mustResetGas = payment != null && payment.length === 0 || budget != null && !isPositiveBudget(budget);
|
|
621
|
+
const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : import_transactions.Transaction.from(tx);
|
|
622
|
+
probe.setSender(owner);
|
|
623
|
+
probe.setGasPrice(gasPrice);
|
|
624
|
+
if (funding.paymentCoins.length > 0) {
|
|
625
|
+
probe.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
626
|
+
}
|
|
627
|
+
let built;
|
|
628
|
+
try {
|
|
629
|
+
built = await probe.build({ client: suiClient });
|
|
630
|
+
} catch (e) {
|
|
631
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
632
|
+
throw new Error(`Failed to estimate gas budget: ${message}`);
|
|
633
|
+
}
|
|
634
|
+
const inspect = await suiClient.simulateTransaction({
|
|
635
|
+
transaction: built,
|
|
636
|
+
include: { effects: true }
|
|
637
|
+
});
|
|
638
|
+
const effects = (inspect.Transaction ?? inspect.FailedTransaction)?.effects;
|
|
639
|
+
if (!effects?.gasUsed) {
|
|
640
|
+
throw new Error("Failed to estimate gas budget: simulateTransaction returned no gasUsed");
|
|
641
|
+
}
|
|
642
|
+
if (effects.status.success !== true) {
|
|
643
|
+
const err = effects.status.success === false ? effects.status.error?.message : void 0;
|
|
644
|
+
throw new Error(`Failed to estimate gas budget: ${err ?? "simulation failed"}`);
|
|
645
|
+
}
|
|
646
|
+
return computeGasBudget(effects.gasUsed, gasPrice);
|
|
647
|
+
}
|
|
648
|
+
async function prepareGasFunding(input) {
|
|
649
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
650
|
+
const { payment, budget: existingBudget } = tx.getData().gasData;
|
|
651
|
+
const bakedAddressBalanceGas = payment != null && payment.length === 0;
|
|
652
|
+
let { gasBudget } = input;
|
|
653
|
+
if (gasBudget != null && gasBudget <= 0n) {
|
|
654
|
+
gasBudget = void 0;
|
|
655
|
+
}
|
|
656
|
+
const existingPositive = isPositiveBudget(existingBudget) ? BigInt(existingBudget) : null;
|
|
657
|
+
if (gasBudget == null) {
|
|
658
|
+
if (bakedAddressBalanceGas || existingPositive == null) {
|
|
659
|
+
gasBudget = await estimateGasBudget({ tx, suiClient, owner, gasPrice });
|
|
660
|
+
} else {
|
|
661
|
+
gasBudget = existingPositive;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
tx.setGasBudget(gasBudget);
|
|
665
|
+
if (bakedAddressBalanceGas) {
|
|
666
|
+
tx.setExpiration(null);
|
|
667
|
+
}
|
|
668
|
+
return selectGasFunding({ tx, suiClient, owner, gasBudget });
|
|
669
|
+
}
|
|
670
|
+
async function preferClassicGasPayment(input) {
|
|
671
|
+
const { tx, suiClient, owner } = input;
|
|
672
|
+
const { payment } = tx.getData().gasData;
|
|
673
|
+
if (payment != null) {
|
|
674
|
+
return payment.length > 0;
|
|
675
|
+
}
|
|
676
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
677
|
+
if (funding.paymentCoins.length === 0) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
tx.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/simulate/simulator.ts
|
|
685
|
+
function formatExecutionError(error) {
|
|
686
|
+
return error.message;
|
|
687
|
+
}
|
|
688
|
+
function gasObjectToReference(gas) {
|
|
689
|
+
if (!gas?.objectId) {
|
|
690
|
+
throw new Error("Gas object missing from simulation effects");
|
|
691
|
+
}
|
|
692
|
+
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
693
|
+
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
694
|
+
return { objectId: gas.objectId, version, digest };
|
|
695
|
+
}
|
|
696
|
+
var Simulator = class {
|
|
697
|
+
constructor(globals) {
|
|
698
|
+
this.globals = globals;
|
|
699
|
+
}
|
|
700
|
+
async simulate(input) {
|
|
701
|
+
const tx = this.copyTransaction(input.txb);
|
|
702
|
+
const { suiClient } = this.globals;
|
|
703
|
+
const gasPrice = await this.getGasPrice();
|
|
704
|
+
tx.setGasPrice(gasPrice);
|
|
705
|
+
tx.setSender(input.sender);
|
|
706
|
+
try {
|
|
707
|
+
await prepareGasFunding({
|
|
708
|
+
tx,
|
|
709
|
+
suiClient,
|
|
710
|
+
owner: input.sender,
|
|
711
|
+
gasPrice
|
|
712
|
+
});
|
|
713
|
+
} catch (e) {
|
|
714
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
gasPrice,
|
|
718
|
+
simulationError: message
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
let built;
|
|
722
|
+
try {
|
|
723
|
+
built = await tx.build({ client: suiClient });
|
|
724
|
+
} catch (e) {
|
|
725
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
726
|
+
return {
|
|
727
|
+
success: false,
|
|
728
|
+
gasPrice,
|
|
729
|
+
simulationError: message
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
let inspectResult;
|
|
733
|
+
try {
|
|
734
|
+
inspectResult = await suiClient.simulateTransaction({
|
|
735
|
+
transaction: built,
|
|
736
|
+
include: {
|
|
737
|
+
effects: true,
|
|
738
|
+
balanceChanges: true,
|
|
739
|
+
events: true,
|
|
740
|
+
objectTypes: true,
|
|
741
|
+
transaction: true,
|
|
742
|
+
bcs: true,
|
|
743
|
+
commandResults: true
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
} catch (e) {
|
|
747
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
748
|
+
return {
|
|
749
|
+
success: false,
|
|
750
|
+
gasPrice,
|
|
751
|
+
simulationError: message
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
755
|
+
const { effects } = txRow;
|
|
756
|
+
if (!effects) {
|
|
757
|
+
throw new Error("simulateTransaction did not return effects");
|
|
758
|
+
}
|
|
759
|
+
const success = effects.status.success === true;
|
|
760
|
+
if (!success) {
|
|
761
|
+
const err = effects.status.success === false ? effects.status.error : null;
|
|
762
|
+
return {
|
|
763
|
+
success,
|
|
764
|
+
gasPrice,
|
|
765
|
+
response: inspectResult,
|
|
766
|
+
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
770
|
+
return {
|
|
771
|
+
success,
|
|
772
|
+
gasPrice,
|
|
773
|
+
gasObject: gasObjectToReference(effects.gasObject),
|
|
774
|
+
gasUsed: effects.gasUsed,
|
|
775
|
+
gasBudget,
|
|
776
|
+
response: inspectResult
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
async getGasPrice() {
|
|
780
|
+
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
781
|
+
return BigInt(referenceGasPrice);
|
|
782
|
+
}
|
|
783
|
+
toGasBudget(gasUsed, gasPrice) {
|
|
784
|
+
return computeGasBudget(gasUsed, gasPrice);
|
|
785
|
+
}
|
|
786
|
+
copyTransaction(tx) {
|
|
787
|
+
return import_transactions2.Transaction.from(tx);
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/utils/buffer.ts
|
|
792
|
+
var import_buffer = require("buffer");
|
|
793
|
+
function stringToBuffer(s) {
|
|
794
|
+
return import_buffer.Buffer.from(s, "utf-8");
|
|
795
|
+
}
|
|
796
|
+
function Uint8ArrayToHex(b) {
|
|
797
|
+
return `0x${Array.prototype.map.call(b, (x) => `0${x.toString(16)}`.slice(-2)).join("")}`;
|
|
798
|
+
}
|
|
799
|
+
function HexToUint8Array(hex) {
|
|
800
|
+
return Uint8Array.from(import_buffer.Buffer.from(hex.startsWith("0x") ? hex.slice(2) : hex, "hex"));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/utils/crypto.ts
|
|
804
|
+
var import_verify = require("@mysten/sui/verify");
|
|
805
|
+
var SignatureVerifier = class _SignatureVerifier {
|
|
806
|
+
static async getPublicKeyFromSignature(input) {
|
|
807
|
+
if (input.messageType === "TransactionBlock") {
|
|
808
|
+
return (0, import_verify.verifyTransactionSignature)(input.message, input.signature);
|
|
809
|
+
}
|
|
810
|
+
return (0, import_verify.verifyPersonalMessageSignature)(input.message, input.signature);
|
|
811
|
+
}
|
|
812
|
+
static async getPublicKeyFromPersonalSignature(input) {
|
|
813
|
+
const message = stringToBuffer(input.messageStr);
|
|
814
|
+
return this.getPublicKeyFromSignature({
|
|
815
|
+
message,
|
|
816
|
+
messageType: "Personal",
|
|
817
|
+
signature: input.signature
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
static async verifySignature(input) {
|
|
821
|
+
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
822
|
+
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
823
|
+
}
|
|
824
|
+
static async verifyPersonalSignature(input) {
|
|
825
|
+
const message = stringToBuffer(input.messageStr);
|
|
826
|
+
return this.verifySignature({
|
|
827
|
+
message,
|
|
828
|
+
messageType: "Personal",
|
|
829
|
+
signature: input.signature,
|
|
830
|
+
targetAddress: input.targetAddress
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
static async verifyTransactionSignature(input) {
|
|
834
|
+
return this.verifySignature({
|
|
835
|
+
messageType: "TransactionBlock",
|
|
836
|
+
message: input.payload,
|
|
837
|
+
signature: input.signature,
|
|
838
|
+
targetAddress: input.targetAddress
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
550
843
|
// src/utils/transaction.ts
|
|
551
|
-
var
|
|
844
|
+
var import_transactions3 = require("@mysten/sui/transactions");
|
|
552
845
|
function toSuiTransaction(txb) {
|
|
553
|
-
if ((0,
|
|
846
|
+
if ((0, import_transactions3.isTransaction)(txb)) {
|
|
554
847
|
return txb;
|
|
555
848
|
}
|
|
849
|
+
if (typeof txb === "string" || txb instanceof Uint8Array) {
|
|
850
|
+
return import_transactions3.Transaction.from(txb);
|
|
851
|
+
}
|
|
556
852
|
const legacy = txb;
|
|
557
|
-
|
|
853
|
+
if (typeof legacy?.serialize === "function") {
|
|
854
|
+
return import_transactions3.Transaction.from(legacy.serialize());
|
|
855
|
+
}
|
|
856
|
+
throw new Error("Unsupported transaction value for toSuiTransaction");
|
|
558
857
|
}
|
|
559
858
|
|
|
560
859
|
// src/utils/iter/iterator.ts
|
|
@@ -722,7 +1021,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
722
1021
|
balances.map(async (balance) => {
|
|
723
1022
|
const meta = await this.coinHelper.getCoinMeta(balance.coinType);
|
|
724
1023
|
return {
|
|
725
|
-
type: (0,
|
|
1024
|
+
type: (0, import_utils5.normalizeStructTag)(balance.coinType),
|
|
726
1025
|
balance: BigInt(balance.addressBalance),
|
|
727
1026
|
metadata: meta
|
|
728
1027
|
};
|
|
@@ -795,7 +1094,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
795
1094
|
clientUrl: this.globals.config.suiClient.url,
|
|
796
1095
|
account: {
|
|
797
1096
|
address: this.address,
|
|
798
|
-
publicKey: (0,
|
|
1097
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
799
1098
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
800
1099
|
features: []
|
|
801
1100
|
}
|
|
@@ -838,7 +1137,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
838
1137
|
clientUrl: this.globals.config.suiClient.url,
|
|
839
1138
|
account: {
|
|
840
1139
|
address: this.address,
|
|
841
|
-
publicKey: (0,
|
|
1140
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
842
1141
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
843
1142
|
features: []
|
|
844
1143
|
}
|
|
@@ -846,14 +1145,20 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
846
1145
|
);
|
|
847
1146
|
}
|
|
848
1147
|
txb.setGasPrice(input.gasPrice);
|
|
849
|
-
txb.setGasBudget(input.gasBudget);
|
|
850
1148
|
txb.setSender(this.address);
|
|
1149
|
+
await prepareGasFunding({
|
|
1150
|
+
tx: txb,
|
|
1151
|
+
suiClient: this.globals.suiClient,
|
|
1152
|
+
owner: this.address,
|
|
1153
|
+
gasPrice: input.gasPrice,
|
|
1154
|
+
gasBudget: input.gasBudget
|
|
1155
|
+
});
|
|
851
1156
|
const payload = await txb.build({ client: this.globals.suiClient });
|
|
852
1157
|
const digest = await txb.getDigest({ client: this.globals.suiClient });
|
|
853
1158
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
854
1159
|
return this.backend.proposeIntentionAndBuildVote({
|
|
855
1160
|
...input,
|
|
856
|
-
payload: (0,
|
|
1161
|
+
payload: (0, import_utils5.toHex)(payload),
|
|
857
1162
|
digest,
|
|
858
1163
|
msafeAddress: this.address,
|
|
859
1164
|
signature: signature.signature
|
|
@@ -911,7 +1216,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
911
1216
|
}
|
|
912
1217
|
async proposePlainPayloadIntention(intention) {
|
|
913
1218
|
const sn = await this.nextSequenceNumber();
|
|
914
|
-
const tb =
|
|
1219
|
+
const tb = import_transactions4.Transaction.from(intention.payload);
|
|
915
1220
|
const data = tb.getData();
|
|
916
1221
|
if (!data.sender || !(0, import_sui3_utils4.isSameAddress)(data.sender, this.address)) {
|
|
917
1222
|
throw new Error("Transaction sender is not same as the multisig address");
|
|
@@ -948,6 +1253,14 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
948
1253
|
throw new Error("Already rejected");
|
|
949
1254
|
}
|
|
950
1255
|
const rejectTxb = toSuiTransaction((0, import_sui3_utils4.buildRejectTxb)(this.address));
|
|
1256
|
+
rejectTxb.setGasPrice(input.gasPrice);
|
|
1257
|
+
await prepareGasFunding({
|
|
1258
|
+
tx: rejectTxb,
|
|
1259
|
+
suiClient: this.suiClient,
|
|
1260
|
+
owner: this.address,
|
|
1261
|
+
gasPrice: input.gasPrice,
|
|
1262
|
+
gasBudget: input.gasBudget
|
|
1263
|
+
});
|
|
951
1264
|
const digest = await rejectTxb.getDigest({ client: this.suiClient });
|
|
952
1265
|
const payload = await rejectTxb.build({ client: this.suiClient });
|
|
953
1266
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
@@ -973,7 +1286,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
973
1286
|
clientUrl: this.globals.config.suiClient.url,
|
|
974
1287
|
account: {
|
|
975
1288
|
address: this.address,
|
|
976
|
-
publicKey: (0,
|
|
1289
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
977
1290
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
978
1291
|
features: []
|
|
979
1292
|
}
|
|
@@ -1055,7 +1368,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
1055
1368
|
|
|
1056
1369
|
// src/core/PublicKeyHelper.ts
|
|
1057
1370
|
var import_sui3_utils5 = require("@msafe/sui3-utils");
|
|
1058
|
-
var
|
|
1371
|
+
var import_utils7 = require("@mysten/sui/utils");
|
|
1059
1372
|
var PublicKeyHelper = class {
|
|
1060
1373
|
constructor(globals) {
|
|
1061
1374
|
this.globals = globals;
|
|
@@ -1104,7 +1417,7 @@ var PublicKeyHelper = class {
|
|
|
1104
1417
|
if (!(0, import_sui3_utils5.isSameAddress)(address, publicKey.toSuiAddress())) {
|
|
1105
1418
|
throw new Error("Invalid public key");
|
|
1106
1419
|
}
|
|
1107
|
-
this.knownPublicKeys.set((0,
|
|
1420
|
+
this.knownPublicKeys.set((0, import_utils7.normalizeSuiAddress)(address), publicKey);
|
|
1108
1421
|
}
|
|
1109
1422
|
async _getPublicKey(address) {
|
|
1110
1423
|
const pkBackend = await this.getPublicKeyFromBackend(address);
|