@msafe/sui3-sdk 1.0.15 → 1.0.17
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 +457 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -2
- package/dist/index.d.ts +76 -2
- package/dist/index.js +449 -163
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- 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 +309 -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,355 @@ 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
|
+
async function estimateGasBudget(input) {
|
|
596
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
597
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
598
|
+
if (funding.total <= 0n) {
|
|
599
|
+
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
600
|
+
}
|
|
601
|
+
const probe = import_transactions.Transaction.from(tx);
|
|
602
|
+
probe.setSender(owner);
|
|
603
|
+
probe.setGasPrice(gasPrice);
|
|
604
|
+
if (funding.paymentCoins.length > 0) {
|
|
605
|
+
probe.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
606
|
+
}
|
|
607
|
+
let built;
|
|
608
|
+
try {
|
|
609
|
+
built = await probe.build({ client: suiClient });
|
|
610
|
+
} catch (e) {
|
|
611
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
612
|
+
throw new Error(`Failed to estimate gas budget: ${message}`);
|
|
613
|
+
}
|
|
614
|
+
const inspect = await suiClient.simulateTransaction({
|
|
615
|
+
transaction: built,
|
|
616
|
+
include: { effects: true }
|
|
617
|
+
});
|
|
618
|
+
const effects = (inspect.Transaction ?? inspect.FailedTransaction)?.effects;
|
|
619
|
+
if (!effects?.gasUsed) {
|
|
620
|
+
throw new Error("Failed to estimate gas budget: simulateTransaction returned no gasUsed");
|
|
621
|
+
}
|
|
622
|
+
if (effects.status.success !== true) {
|
|
623
|
+
const err = effects.status.success === false ? effects.status.error?.message : void 0;
|
|
624
|
+
throw new Error(`Failed to estimate gas budget: ${err ?? "simulation failed"}`);
|
|
625
|
+
}
|
|
626
|
+
return computeGasBudget(effects.gasUsed, gasPrice);
|
|
627
|
+
}
|
|
628
|
+
async function prepareGasFunding(input) {
|
|
629
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
630
|
+
const { payment, budget: existingBudget } = tx.getData().gasData;
|
|
631
|
+
const bakedAddressBalanceGas = payment != null && payment.length === 0;
|
|
632
|
+
let { gasBudget } = input;
|
|
633
|
+
if (gasBudget == null) {
|
|
634
|
+
if (bakedAddressBalanceGas || existingBudget == null) {
|
|
635
|
+
gasBudget = await estimateGasBudget({ tx, suiClient, owner, gasPrice });
|
|
636
|
+
} else {
|
|
637
|
+
gasBudget = BigInt(existingBudget);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
tx.setGasBudget(gasBudget);
|
|
641
|
+
return selectGasFunding({ tx, suiClient, owner, gasBudget });
|
|
642
|
+
}
|
|
643
|
+
async function preferClassicGasPayment(input) {
|
|
644
|
+
const { tx, suiClient, owner } = input;
|
|
645
|
+
const { payment } = tx.getData().gasData;
|
|
646
|
+
if (payment != null) {
|
|
647
|
+
return payment.length > 0;
|
|
648
|
+
}
|
|
649
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
650
|
+
if (funding.paymentCoins.length === 0) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
tx.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/simulate/simulator.ts
|
|
658
|
+
function formatExecutionError(error) {
|
|
659
|
+
return error.message;
|
|
660
|
+
}
|
|
661
|
+
function gasObjectToReference(gas) {
|
|
662
|
+
if (!gas?.objectId) {
|
|
663
|
+
throw new Error("Gas object missing from simulation effects");
|
|
664
|
+
}
|
|
665
|
+
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
666
|
+
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
667
|
+
return { objectId: gas.objectId, version, digest };
|
|
668
|
+
}
|
|
669
|
+
var Simulator = class {
|
|
670
|
+
constructor(globals) {
|
|
671
|
+
this.globals = globals;
|
|
672
|
+
}
|
|
673
|
+
async simulate(input) {
|
|
674
|
+
const tx = this.copyTransaction(input.txb);
|
|
675
|
+
const { suiClient } = this.globals;
|
|
676
|
+
const gasPrice = await this.getGasPrice();
|
|
677
|
+
tx.setGasPrice(gasPrice);
|
|
678
|
+
tx.setSender(input.sender);
|
|
679
|
+
try {
|
|
680
|
+
await prepareGasFunding({
|
|
681
|
+
tx,
|
|
682
|
+
suiClient,
|
|
683
|
+
owner: input.sender,
|
|
684
|
+
gasPrice
|
|
685
|
+
});
|
|
686
|
+
} catch (e) {
|
|
687
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
688
|
+
return {
|
|
689
|
+
success: false,
|
|
690
|
+
gasPrice,
|
|
691
|
+
simulationError: message
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
let built;
|
|
695
|
+
try {
|
|
696
|
+
built = await tx.build({ client: suiClient });
|
|
697
|
+
} catch (e) {
|
|
698
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
699
|
+
return {
|
|
700
|
+
success: false,
|
|
701
|
+
gasPrice,
|
|
702
|
+
simulationError: message
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
let inspectResult;
|
|
706
|
+
try {
|
|
707
|
+
inspectResult = await suiClient.simulateTransaction({
|
|
708
|
+
transaction: built,
|
|
709
|
+
include: {
|
|
710
|
+
effects: true,
|
|
711
|
+
balanceChanges: true,
|
|
712
|
+
events: true,
|
|
713
|
+
objectTypes: true,
|
|
714
|
+
transaction: true,
|
|
715
|
+
bcs: true,
|
|
716
|
+
commandResults: true
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
} catch (e) {
|
|
720
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
721
|
+
return {
|
|
722
|
+
success: false,
|
|
723
|
+
gasPrice,
|
|
724
|
+
simulationError: message
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
728
|
+
const { effects } = txRow;
|
|
729
|
+
if (!effects) {
|
|
730
|
+
throw new Error("simulateTransaction did not return effects");
|
|
731
|
+
}
|
|
732
|
+
const success = effects.status.success === true;
|
|
733
|
+
if (!success) {
|
|
734
|
+
const err = effects.status.success === false ? effects.status.error : null;
|
|
735
|
+
return {
|
|
736
|
+
success,
|
|
737
|
+
gasPrice,
|
|
738
|
+
response: inspectResult,
|
|
739
|
+
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
743
|
+
return {
|
|
744
|
+
success,
|
|
745
|
+
gasPrice,
|
|
746
|
+
gasObject: gasObjectToReference(effects.gasObject),
|
|
747
|
+
gasUsed: effects.gasUsed,
|
|
748
|
+
gasBudget,
|
|
749
|
+
response: inspectResult
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
async getGasPrice() {
|
|
753
|
+
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
754
|
+
return BigInt(referenceGasPrice);
|
|
755
|
+
}
|
|
756
|
+
toGasBudget(gasUsed, gasPrice) {
|
|
757
|
+
return computeGasBudget(gasUsed, gasPrice);
|
|
758
|
+
}
|
|
759
|
+
copyTransaction(tx) {
|
|
760
|
+
return import_transactions2.Transaction.from(tx);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// src/utils/buffer.ts
|
|
765
|
+
var import_buffer = require("buffer");
|
|
766
|
+
function stringToBuffer(s) {
|
|
767
|
+
return import_buffer.Buffer.from(s, "utf-8");
|
|
768
|
+
}
|
|
769
|
+
function Uint8ArrayToHex(b) {
|
|
770
|
+
return `0x${Array.prototype.map.call(b, (x) => `0${x.toString(16)}`.slice(-2)).join("")}`;
|
|
771
|
+
}
|
|
772
|
+
function HexToUint8Array(hex) {
|
|
773
|
+
return Uint8Array.from(import_buffer.Buffer.from(hex.startsWith("0x") ? hex.slice(2) : hex, "hex"));
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/utils/crypto.ts
|
|
777
|
+
var import_verify = require("@mysten/sui/verify");
|
|
778
|
+
var SignatureVerifier = class _SignatureVerifier {
|
|
779
|
+
static async getPublicKeyFromSignature(input) {
|
|
780
|
+
if (input.messageType === "TransactionBlock") {
|
|
781
|
+
return (0, import_verify.verifyTransactionSignature)(input.message, input.signature);
|
|
782
|
+
}
|
|
783
|
+
return (0, import_verify.verifyPersonalMessageSignature)(input.message, input.signature);
|
|
784
|
+
}
|
|
785
|
+
static async getPublicKeyFromPersonalSignature(input) {
|
|
786
|
+
const message = stringToBuffer(input.messageStr);
|
|
787
|
+
return this.getPublicKeyFromSignature({
|
|
788
|
+
message,
|
|
789
|
+
messageType: "Personal",
|
|
790
|
+
signature: input.signature
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
static async verifySignature(input) {
|
|
794
|
+
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
795
|
+
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
796
|
+
}
|
|
797
|
+
static async verifyPersonalSignature(input) {
|
|
798
|
+
const message = stringToBuffer(input.messageStr);
|
|
799
|
+
return this.verifySignature({
|
|
800
|
+
message,
|
|
801
|
+
messageType: "Personal",
|
|
802
|
+
signature: input.signature,
|
|
803
|
+
targetAddress: input.targetAddress
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
static async verifyTransactionSignature(input) {
|
|
807
|
+
return this.verifySignature({
|
|
808
|
+
messageType: "TransactionBlock",
|
|
809
|
+
message: input.payload,
|
|
810
|
+
signature: input.signature,
|
|
811
|
+
targetAddress: input.targetAddress
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
|
|
550
816
|
// src/utils/transaction.ts
|
|
551
|
-
var
|
|
817
|
+
var import_transactions3 = require("@mysten/sui/transactions");
|
|
552
818
|
function toSuiTransaction(txb) {
|
|
553
|
-
if ((0,
|
|
819
|
+
if ((0, import_transactions3.isTransaction)(txb)) {
|
|
554
820
|
return txb;
|
|
555
821
|
}
|
|
822
|
+
if (typeof txb === "string" || txb instanceof Uint8Array) {
|
|
823
|
+
return import_transactions3.Transaction.from(txb);
|
|
824
|
+
}
|
|
556
825
|
const legacy = txb;
|
|
557
|
-
|
|
826
|
+
if (typeof legacy?.serialize === "function") {
|
|
827
|
+
return import_transactions3.Transaction.from(legacy.serialize());
|
|
828
|
+
}
|
|
829
|
+
throw new Error("Unsupported transaction value for toSuiTransaction");
|
|
558
830
|
}
|
|
559
831
|
|
|
560
832
|
// src/utils/iter/iterator.ts
|
|
@@ -722,7 +994,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
722
994
|
balances.map(async (balance) => {
|
|
723
995
|
const meta = await this.coinHelper.getCoinMeta(balance.coinType);
|
|
724
996
|
return {
|
|
725
|
-
type: (0,
|
|
997
|
+
type: (0, import_utils5.normalizeStructTag)(balance.coinType),
|
|
726
998
|
balance: BigInt(balance.addressBalance),
|
|
727
999
|
metadata: meta
|
|
728
1000
|
};
|
|
@@ -795,7 +1067,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
795
1067
|
clientUrl: this.globals.config.suiClient.url,
|
|
796
1068
|
account: {
|
|
797
1069
|
address: this.address,
|
|
798
|
-
publicKey: (0,
|
|
1070
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
799
1071
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
800
1072
|
features: []
|
|
801
1073
|
}
|
|
@@ -838,7 +1110,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
838
1110
|
clientUrl: this.globals.config.suiClient.url,
|
|
839
1111
|
account: {
|
|
840
1112
|
address: this.address,
|
|
841
|
-
publicKey: (0,
|
|
1113
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
842
1114
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
843
1115
|
features: []
|
|
844
1116
|
}
|
|
@@ -846,14 +1118,20 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
846
1118
|
);
|
|
847
1119
|
}
|
|
848
1120
|
txb.setGasPrice(input.gasPrice);
|
|
849
|
-
txb.setGasBudget(input.gasBudget);
|
|
850
1121
|
txb.setSender(this.address);
|
|
1122
|
+
await prepareGasFunding({
|
|
1123
|
+
tx: txb,
|
|
1124
|
+
suiClient: this.globals.suiClient,
|
|
1125
|
+
owner: this.address,
|
|
1126
|
+
gasPrice: input.gasPrice,
|
|
1127
|
+
gasBudget: input.gasBudget
|
|
1128
|
+
});
|
|
851
1129
|
const payload = await txb.build({ client: this.globals.suiClient });
|
|
852
1130
|
const digest = await txb.getDigest({ client: this.globals.suiClient });
|
|
853
1131
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
854
1132
|
return this.backend.proposeIntentionAndBuildVote({
|
|
855
1133
|
...input,
|
|
856
|
-
payload: (0,
|
|
1134
|
+
payload: (0, import_utils5.toHex)(payload),
|
|
857
1135
|
digest,
|
|
858
1136
|
msafeAddress: this.address,
|
|
859
1137
|
signature: signature.signature
|
|
@@ -911,7 +1189,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
911
1189
|
}
|
|
912
1190
|
async proposePlainPayloadIntention(intention) {
|
|
913
1191
|
const sn = await this.nextSequenceNumber();
|
|
914
|
-
const tb =
|
|
1192
|
+
const tb = import_transactions4.Transaction.from(intention.payload);
|
|
915
1193
|
const data = tb.getData();
|
|
916
1194
|
if (!data.sender || !(0, import_sui3_utils4.isSameAddress)(data.sender, this.address)) {
|
|
917
1195
|
throw new Error("Transaction sender is not same as the multisig address");
|
|
@@ -948,6 +1226,14 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
948
1226
|
throw new Error("Already rejected");
|
|
949
1227
|
}
|
|
950
1228
|
const rejectTxb = toSuiTransaction((0, import_sui3_utils4.buildRejectTxb)(this.address));
|
|
1229
|
+
rejectTxb.setGasPrice(input.gasPrice);
|
|
1230
|
+
await prepareGasFunding({
|
|
1231
|
+
tx: rejectTxb,
|
|
1232
|
+
suiClient: this.suiClient,
|
|
1233
|
+
owner: this.address,
|
|
1234
|
+
gasPrice: input.gasPrice,
|
|
1235
|
+
gasBudget: input.gasBudget
|
|
1236
|
+
});
|
|
951
1237
|
const digest = await rejectTxb.getDigest({ client: this.suiClient });
|
|
952
1238
|
const payload = await rejectTxb.build({ client: this.suiClient });
|
|
953
1239
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
@@ -973,7 +1259,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
973
1259
|
clientUrl: this.globals.config.suiClient.url,
|
|
974
1260
|
account: {
|
|
975
1261
|
address: this.address,
|
|
976
|
-
publicKey: (0,
|
|
1262
|
+
publicKey: (0, import_utils5.fromHex)(this.address),
|
|
977
1263
|
chains: [import_wallet_standard.SUI_MAINNET_CHAIN, import_wallet_standard.SUI_TESTNET_CHAIN],
|
|
978
1264
|
features: []
|
|
979
1265
|
}
|
|
@@ -1055,7 +1341,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
1055
1341
|
|
|
1056
1342
|
// src/core/PublicKeyHelper.ts
|
|
1057
1343
|
var import_sui3_utils5 = require("@msafe/sui3-utils");
|
|
1058
|
-
var
|
|
1344
|
+
var import_utils7 = require("@mysten/sui/utils");
|
|
1059
1345
|
var PublicKeyHelper = class {
|
|
1060
1346
|
constructor(globals) {
|
|
1061
1347
|
this.globals = globals;
|
|
@@ -1104,7 +1390,7 @@ var PublicKeyHelper = class {
|
|
|
1104
1390
|
if (!(0, import_sui3_utils5.isSameAddress)(address, publicKey.toSuiAddress())) {
|
|
1105
1391
|
throw new Error("Invalid public key");
|
|
1106
1392
|
}
|
|
1107
|
-
this.knownPublicKeys.set((0,
|
|
1393
|
+
this.knownPublicKeys.set((0, import_utils7.normalizeSuiAddress)(address), publicKey);
|
|
1108
1394
|
}
|
|
1109
1395
|
async _getPublicKey(address) {
|
|
1110
1396
|
const pkBackend = await this.getPublicKeyFromBackend(address);
|