@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.js
CHANGED
|
@@ -133,125 +133,90 @@ import {
|
|
|
133
133
|
buildRejectTxb,
|
|
134
134
|
isSameAddress as isSameAddress2
|
|
135
135
|
} from "@msafe/sui3-utils";
|
|
136
|
-
import { Transaction as
|
|
137
|
-
import { fromHex, normalizeStructTag as normalizeStructTag3, toHex } from "@mysten/sui/utils";
|
|
136
|
+
import { Transaction as Transaction4 } from "@mysten/sui/transactions";
|
|
137
|
+
import { fromHex as fromHex2, normalizeStructTag as normalizeStructTag3, toHex as toHex2 } from "@mysten/sui/utils";
|
|
138
138
|
import { SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN } from "@mysten/wallet-standard";
|
|
139
139
|
|
|
140
140
|
// src/simulate/simulator.ts
|
|
141
|
+
import { Transaction as Transaction2 } from "@mysten/sui/transactions";
|
|
142
|
+
|
|
143
|
+
// src/utils/gasFunding.ts
|
|
141
144
|
import { Transaction } from "@mysten/sui/transactions";
|
|
142
|
-
|
|
143
|
-
function formatExecutionError(error) {
|
|
144
|
-
return error.message;
|
|
145
|
-
}
|
|
146
|
-
function gasObjectToReference(gas) {
|
|
147
|
-
if (!gas?.objectId) {
|
|
148
|
-
throw new Error("Gas object missing from simulation effects");
|
|
149
|
-
}
|
|
150
|
-
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
151
|
-
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
152
|
-
return { objectId: gas.objectId, version, digest };
|
|
153
|
-
}
|
|
154
|
-
var Simulator = class {
|
|
155
|
-
constructor(globals) {
|
|
156
|
-
this.globals = globals;
|
|
157
|
-
}
|
|
158
|
-
async simulate(input) {
|
|
159
|
-
const tx = this.copyTransaction(input.txb);
|
|
160
|
-
const { suiClient } = this.globals;
|
|
161
|
-
const gasPrice = await this.getGasPrice();
|
|
162
|
-
tx.setGasPrice(gasPrice);
|
|
163
|
-
tx.setSender(input.sender);
|
|
164
|
-
let built;
|
|
165
|
-
try {
|
|
166
|
-
built = await tx.build({ client: suiClient });
|
|
167
|
-
} catch (e) {
|
|
168
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
169
|
-
return {
|
|
170
|
-
success: false,
|
|
171
|
-
gasPrice,
|
|
172
|
-
simulationError: message
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
let inspectResult;
|
|
176
|
-
try {
|
|
177
|
-
inspectResult = await suiClient.simulateTransaction({
|
|
178
|
-
transaction: built,
|
|
179
|
-
include: {
|
|
180
|
-
effects: true,
|
|
181
|
-
balanceChanges: true,
|
|
182
|
-
events: true,
|
|
183
|
-
objectTypes: true,
|
|
184
|
-
transaction: true,
|
|
185
|
-
bcs: true,
|
|
186
|
-
commandResults: true
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
} catch (e) {
|
|
190
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
191
|
-
return {
|
|
192
|
-
success: false,
|
|
193
|
-
gasPrice,
|
|
194
|
-
simulationError: message
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
198
|
-
const { effects } = txRow;
|
|
199
|
-
if (!effects) {
|
|
200
|
-
throw new Error("simulateTransaction did not return effects");
|
|
201
|
-
}
|
|
202
|
-
const success = effects.status.success === true;
|
|
203
|
-
if (!success) {
|
|
204
|
-
const err = effects.status.success === false ? effects.status.error : null;
|
|
205
|
-
return {
|
|
206
|
-
success,
|
|
207
|
-
gasPrice,
|
|
208
|
-
response: inspectResult,
|
|
209
|
-
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
213
|
-
return {
|
|
214
|
-
success,
|
|
215
|
-
gasPrice,
|
|
216
|
-
gasObject: gasObjectToReference(effects.gasObject),
|
|
217
|
-
gasUsed: effects.gasUsed,
|
|
218
|
-
gasBudget,
|
|
219
|
-
response: inspectResult
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
async getGasPrice() {
|
|
223
|
-
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
224
|
-
return BigInt(referenceGasPrice);
|
|
225
|
-
}
|
|
226
|
-
toGasBudget(gasUsed, gasPrice) {
|
|
227
|
-
const { computationCost, storageCost, storageRebate } = gasUsed;
|
|
228
|
-
const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
|
|
229
|
-
const baseComputationCostWithOverhead = BigInt(computationCost) + safeOverhead;
|
|
230
|
-
const gasBudget = baseComputationCostWithOverhead + BigInt(storageCost) - BigInt(storageRebate);
|
|
231
|
-
return gasBudget > baseComputationCostWithOverhead ? gasBudget : baseComputationCostWithOverhead;
|
|
232
|
-
}
|
|
233
|
-
copyTransaction(tx) {
|
|
234
|
-
return Transaction.from(tx);
|
|
235
|
-
}
|
|
236
|
-
};
|
|
145
|
+
import { normalizeSuiObjectId } from "@mysten/sui/utils";
|
|
237
146
|
|
|
238
|
-
// src/utils/
|
|
239
|
-
import {
|
|
240
|
-
|
|
241
|
-
|
|
147
|
+
// src/utils/coinReservation.ts
|
|
148
|
+
import { bcs, TypeTagSerializer } from "@mysten/sui/bcs";
|
|
149
|
+
import { deriveDynamicFieldID, fromBase58, fromHex, normalizeSuiAddress, toBase58, toHex } from "@mysten/sui/utils";
|
|
150
|
+
var SUI_ACCUMULATOR_ROOT_OBJECT_ID = normalizeSuiAddress("0xacc");
|
|
151
|
+
var ACCUMULATOR_KEY_TYPE_TAG = TypeTagSerializer.parseFromStr(
|
|
152
|
+
"0x2::accumulator::Key<0x2::balance::Balance<0x2::sui::SUI>>"
|
|
153
|
+
);
|
|
154
|
+
var COIN_RESERVATION_MAGIC = new Uint8Array([
|
|
155
|
+
172,
|
|
156
|
+
172,
|
|
157
|
+
172,
|
|
158
|
+
172,
|
|
159
|
+
172,
|
|
160
|
+
172,
|
|
161
|
+
172,
|
|
162
|
+
172,
|
|
163
|
+
172,
|
|
164
|
+
172,
|
|
165
|
+
172,
|
|
166
|
+
172,
|
|
167
|
+
172,
|
|
168
|
+
172,
|
|
169
|
+
172,
|
|
170
|
+
172,
|
|
171
|
+
172,
|
|
172
|
+
172,
|
|
173
|
+
172,
|
|
174
|
+
172
|
|
175
|
+
]);
|
|
176
|
+
function isCoinReservationDigest(digestBase58) {
|
|
177
|
+
const digestBytes = fromBase58(digestBase58);
|
|
178
|
+
const last20Bytes = digestBytes.slice(12, 32);
|
|
179
|
+
return last20Bytes.every((byte, i) => byte === COIN_RESERVATION_MAGIC[i]);
|
|
242
180
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
181
|
+
function deriveReservationObjectId(owner, chainIdentifier) {
|
|
182
|
+
const keyBcs = bcs.Address.serialize(owner).toBytes();
|
|
183
|
+
const accumulatorId = deriveDynamicFieldID(SUI_ACCUMULATOR_ROOT_OBJECT_ID, ACCUMULATOR_KEY_TYPE_TAG, keyBcs);
|
|
184
|
+
const accBytes = fromHex(accumulatorId.slice(2));
|
|
185
|
+
const chainBytes = fromBase58(chainIdentifier);
|
|
186
|
+
if (chainBytes.length !== 32) {
|
|
187
|
+
throw new Error(`Invalid chain identifier length: expected 32 bytes, got ${chainBytes.length}`);
|
|
188
|
+
}
|
|
189
|
+
const xored = new Uint8Array(32);
|
|
190
|
+
for (let i = 0; i < 32; i++) {
|
|
191
|
+
xored[i] = accBytes[i] ^ chainBytes[i];
|
|
192
|
+
}
|
|
193
|
+
return `0x${toHex(xored)}`;
|
|
245
194
|
}
|
|
246
|
-
function
|
|
247
|
-
|
|
195
|
+
function createCoinReservationRef(reservedBalance, owner, chainIdentifier, epoch) {
|
|
196
|
+
const digestBytes = new Uint8Array(32);
|
|
197
|
+
const view = new DataView(digestBytes.buffer);
|
|
198
|
+
view.setBigUint64(0, reservedBalance, true);
|
|
199
|
+
const epochNum = Number(epoch);
|
|
200
|
+
if (!Number.isSafeInteger(epochNum) || epochNum < 0 || epochNum > 4294967295) {
|
|
201
|
+
throw new Error(`Epoch ${epoch} out of u32 range for coin reservation digest`);
|
|
202
|
+
}
|
|
203
|
+
view.setUint32(8, epochNum, true);
|
|
204
|
+
digestBytes.set(COIN_RESERVATION_MAGIC, 12);
|
|
205
|
+
return {
|
|
206
|
+
objectId: deriveReservationObjectId(owner, chainIdentifier),
|
|
207
|
+
version: "0",
|
|
208
|
+
digest: toBase58(digestBytes)
|
|
209
|
+
};
|
|
248
210
|
}
|
|
249
211
|
|
|
250
|
-
// src/utils/
|
|
251
|
-
import {
|
|
212
|
+
// src/utils/sui.ts
|
|
213
|
+
import { PublicKeySerde as PublicKeySerde2 } from "@msafe/sui3-utils";
|
|
214
|
+
import { parseSerializedSignature } from "@mysten/sui/cryptography";
|
|
215
|
+
import { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
216
|
+
import { MultiSigPublicKey } from "@mysten/sui/multisig";
|
|
252
217
|
|
|
253
218
|
// src/utils/format.ts
|
|
254
|
-
import { normalizeSuiAddress, normalizeStructTag as normalizeStructTag2 } from "@mysten/sui/utils";
|
|
219
|
+
import { normalizeSuiAddress as normalizeSuiAddress2, normalizeStructTag as normalizeStructTag2 } from "@mysten/sui/utils";
|
|
255
220
|
|
|
256
221
|
// src/utils/coin.ts
|
|
257
222
|
import { normalizeStructTag } from "@mysten/sui/utils";
|
|
@@ -315,13 +280,13 @@ var Coin = class _Coin {
|
|
|
315
280
|
// src/utils/format.ts
|
|
316
281
|
var Formatter = class {
|
|
317
282
|
static normalizeSuiAddress(addr) {
|
|
318
|
-
return
|
|
283
|
+
return normalizeSuiAddress2(addr);
|
|
319
284
|
}
|
|
320
285
|
static normalizeStructTag(struct) {
|
|
321
286
|
return normalizeStructTag2(struct);
|
|
322
287
|
}
|
|
323
288
|
static isSuiAddressEqual(addr1, addr2) {
|
|
324
|
-
return
|
|
289
|
+
return normalizeSuiAddress2(addr1) === normalizeSuiAddress2(addr2);
|
|
325
290
|
}
|
|
326
291
|
static isSuiStructEqual(struct1, struct2) {
|
|
327
292
|
return normalizeStructTag2(struct1) === normalizeStructTag2(struct2);
|
|
@@ -337,50 +302,7 @@ function addPrefix(s, prefix) {
|
|
|
337
302
|
return prefix + s;
|
|
338
303
|
}
|
|
339
304
|
|
|
340
|
-
// src/utils/crypto.ts
|
|
341
|
-
var SignatureVerifier = class _SignatureVerifier {
|
|
342
|
-
static async getPublicKeyFromSignature(input) {
|
|
343
|
-
if (input.messageType === "TransactionBlock") {
|
|
344
|
-
return verifyTransactionSignature(input.message, input.signature);
|
|
345
|
-
}
|
|
346
|
-
return verifyPersonalMessageSignature(input.message, input.signature);
|
|
347
|
-
}
|
|
348
|
-
static async getPublicKeyFromPersonalSignature(input) {
|
|
349
|
-
const message = stringToBuffer(input.messageStr);
|
|
350
|
-
return this.getPublicKeyFromSignature({
|
|
351
|
-
message,
|
|
352
|
-
messageType: "Personal",
|
|
353
|
-
signature: input.signature
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
static async verifySignature(input) {
|
|
357
|
-
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
358
|
-
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
359
|
-
}
|
|
360
|
-
static async verifyPersonalSignature(input) {
|
|
361
|
-
const message = stringToBuffer(input.messageStr);
|
|
362
|
-
return this.verifySignature({
|
|
363
|
-
message,
|
|
364
|
-
messageType: "Personal",
|
|
365
|
-
signature: input.signature,
|
|
366
|
-
targetAddress: input.targetAddress
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
static async verifyTransactionSignature(input) {
|
|
370
|
-
return this.verifySignature({
|
|
371
|
-
messageType: "TransactionBlock",
|
|
372
|
-
message: input.payload,
|
|
373
|
-
signature: input.signature,
|
|
374
|
-
targetAddress: input.targetAddress
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
|
|
379
305
|
// src/utils/sui.ts
|
|
380
|
-
import { PublicKeySerde as PublicKeySerde2 } from "@msafe/sui3-utils";
|
|
381
|
-
import { parseSerializedSignature } from "@mysten/sui/cryptography";
|
|
382
|
-
import { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
383
|
-
import { MultiSigPublicKey } from "@mysten/sui/multisig";
|
|
384
306
|
var SUI_COIN = "0x2::sui::SUI";
|
|
385
307
|
var GRAPHQL_URL_BY_NETWORK = {
|
|
386
308
|
mainnet: "https://sui-mainnet.mystenlabs.com/graphql",
|
|
@@ -492,14 +414,382 @@ async function getAllCoins(input) {
|
|
|
492
414
|
return res;
|
|
493
415
|
}
|
|
494
416
|
|
|
417
|
+
// src/utils/gasFunding.ts
|
|
418
|
+
var GAS_SAFE_OVERHEAD = 1000n;
|
|
419
|
+
var InsufficientGasFundsError = class extends Error {
|
|
420
|
+
gasBudget;
|
|
421
|
+
coinBalance;
|
|
422
|
+
addressBalance;
|
|
423
|
+
constructor(gasBudget, coinBalance, addressBalance) {
|
|
424
|
+
const total = coinBalance + addressBalance;
|
|
425
|
+
super(
|
|
426
|
+
`Insufficient gas funds: need ${gasBudget}, have coinBalance=${coinBalance} + addressBalance=${addressBalance} = ${total}`
|
|
427
|
+
);
|
|
428
|
+
this.name = "InsufficientGasFundsError";
|
|
429
|
+
this.gasBudget = gasBudget;
|
|
430
|
+
this.coinBalance = coinBalance;
|
|
431
|
+
this.addressBalance = addressBalance;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
function computeGasBudget(gasUsed, gasPrice) {
|
|
435
|
+
const { computationCost, storageCost } = gasUsed;
|
|
436
|
+
const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
|
|
437
|
+
return BigInt(computationCost) + BigInt(storageCost) + safeOverhead;
|
|
438
|
+
}
|
|
439
|
+
function collectUsedObjectIds(tx) {
|
|
440
|
+
return tx.getData().inputs.reduce((used, input) => {
|
|
441
|
+
const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
|
|
442
|
+
if (immOrOwned) {
|
|
443
|
+
used.add(normalizeSuiObjectId(immOrOwned));
|
|
444
|
+
return used;
|
|
445
|
+
}
|
|
446
|
+
const unresolved = input.UnresolvedObject?.objectId;
|
|
447
|
+
if (unresolved) {
|
|
448
|
+
used.add(normalizeSuiObjectId(unresolved));
|
|
449
|
+
}
|
|
450
|
+
return used;
|
|
451
|
+
}, /* @__PURE__ */ new Set());
|
|
452
|
+
}
|
|
453
|
+
async function loadSuiGasFunding(suiClient, owner, tx) {
|
|
454
|
+
const usedObjectIds = collectUsedObjectIds(tx);
|
|
455
|
+
const [coins, balanceRes] = await Promise.all([
|
|
456
|
+
getAllCoins({ suiClient, owner, coinType: SUI_COIN }),
|
|
457
|
+
suiClient.getBalance({ owner, coinType: SUI_COIN })
|
|
458
|
+
]);
|
|
459
|
+
const paymentCoins = coins.filter((coin) => !usedObjectIds.has(normalizeSuiObjectId(coin.objectId)) && BigInt(coin.balance) > 0n).map((coin) => ({
|
|
460
|
+
objectId: coin.objectId,
|
|
461
|
+
version: coin.version,
|
|
462
|
+
digest: coin.digest,
|
|
463
|
+
balance: BigInt(coin.balance)
|
|
464
|
+
}));
|
|
465
|
+
const coinBalance = paymentCoins.reduce((sum, coin) => sum + coin.balance, 0n);
|
|
466
|
+
const addressBalance = BigInt(balanceRes.balance.addressBalance);
|
|
467
|
+
return {
|
|
468
|
+
paymentCoins,
|
|
469
|
+
coinBalance,
|
|
470
|
+
addressBalance,
|
|
471
|
+
total: coinBalance + addressBalance
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function toObjectRefs(coins) {
|
|
475
|
+
return coins.map(({ objectId, version, digest }) => ({ objectId, version, digest }));
|
|
476
|
+
}
|
|
477
|
+
async function applyMixedTopUp(input) {
|
|
478
|
+
const { tx, suiClient, owner, paymentCoins, topUpAmount } = input;
|
|
479
|
+
if (paymentCoins.length === 0) {
|
|
480
|
+
throw new Error("mixed gas top-up requires at least one SUI coin object");
|
|
481
|
+
}
|
|
482
|
+
if (topUpAmount <= 0n) {
|
|
483
|
+
tx.setGasPayment(toObjectRefs(paymentCoins));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const [{ chainIdentifier }, { systemState }] = await Promise.all([
|
|
487
|
+
suiClient.core.getChainIdentifier(),
|
|
488
|
+
suiClient.core.getCurrentSystemState()
|
|
489
|
+
]);
|
|
490
|
+
const reservation = createCoinReservationRef(topUpAmount, owner, chainIdentifier, systemState.epoch);
|
|
491
|
+
tx.setGasPayment([...toObjectRefs(paymentCoins), reservation]);
|
|
492
|
+
}
|
|
493
|
+
async function selectGasFunding(input) {
|
|
494
|
+
const { tx, suiClient, owner, gasBudget } = input;
|
|
495
|
+
const { payment } = tx.getData().gasData;
|
|
496
|
+
if (payment != null && payment.length > 0) {
|
|
497
|
+
const funding2 = await loadSuiGasFunding(suiClient, owner, tx);
|
|
498
|
+
return {
|
|
499
|
+
mode: "classic",
|
|
500
|
+
gasBudget,
|
|
501
|
+
coinBalance: funding2.coinBalance,
|
|
502
|
+
addressBalance: funding2.addressBalance
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
506
|
+
const { paymentCoins, coinBalance, addressBalance, total } = funding;
|
|
507
|
+
if (total < gasBudget) {
|
|
508
|
+
throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
|
|
509
|
+
}
|
|
510
|
+
if (paymentCoins.length > 0 && coinBalance >= gasBudget) {
|
|
511
|
+
tx.setGasPayment(toObjectRefs(paymentCoins));
|
|
512
|
+
return { mode: "classic", gasBudget, coinBalance, addressBalance };
|
|
513
|
+
}
|
|
514
|
+
if (paymentCoins.length > 0 && total >= gasBudget) {
|
|
515
|
+
const topUpAmount = gasBudget - coinBalance;
|
|
516
|
+
await applyMixedTopUp({
|
|
517
|
+
tx,
|
|
518
|
+
suiClient,
|
|
519
|
+
owner,
|
|
520
|
+
paymentCoins,
|
|
521
|
+
topUpAmount
|
|
522
|
+
});
|
|
523
|
+
return { mode: "mixedTopUp", gasBudget, coinBalance, addressBalance };
|
|
524
|
+
}
|
|
525
|
+
if (addressBalance >= gasBudget) {
|
|
526
|
+
tx.setGasPayment([]);
|
|
527
|
+
return { mode: "addressBalance", gasBudget, coinBalance, addressBalance };
|
|
528
|
+
}
|
|
529
|
+
throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
|
|
530
|
+
}
|
|
531
|
+
function cloneWithClearedGasConfig(tx) {
|
|
532
|
+
const data = structuredClone(tx.getData());
|
|
533
|
+
data.gasData.budget = null;
|
|
534
|
+
data.gasData.price = null;
|
|
535
|
+
data.gasData.payment = null;
|
|
536
|
+
data.expiration = null;
|
|
537
|
+
return Transaction.from(JSON.stringify(data));
|
|
538
|
+
}
|
|
539
|
+
function isPositiveBudget(budget) {
|
|
540
|
+
if (budget == null) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
return BigInt(budget) > 0n;
|
|
545
|
+
} catch {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async function estimateGasBudget(input) {
|
|
550
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
551
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
552
|
+
if (funding.total <= 0n) {
|
|
553
|
+
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
554
|
+
}
|
|
555
|
+
const { budget, payment } = tx.getData().gasData;
|
|
556
|
+
const mustResetGas = payment != null && payment.length === 0 || budget != null && !isPositiveBudget(budget);
|
|
557
|
+
const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : Transaction.from(tx);
|
|
558
|
+
probe.setSender(owner);
|
|
559
|
+
probe.setGasPrice(gasPrice);
|
|
560
|
+
if (funding.paymentCoins.length > 0) {
|
|
561
|
+
probe.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
562
|
+
}
|
|
563
|
+
let built;
|
|
564
|
+
try {
|
|
565
|
+
built = await probe.build({ client: suiClient });
|
|
566
|
+
} catch (e) {
|
|
567
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
568
|
+
throw new Error(`Failed to estimate gas budget: ${message}`);
|
|
569
|
+
}
|
|
570
|
+
const inspect = await suiClient.simulateTransaction({
|
|
571
|
+
transaction: built,
|
|
572
|
+
include: { effects: true }
|
|
573
|
+
});
|
|
574
|
+
const effects = (inspect.Transaction ?? inspect.FailedTransaction)?.effects;
|
|
575
|
+
if (!effects?.gasUsed) {
|
|
576
|
+
throw new Error("Failed to estimate gas budget: simulateTransaction returned no gasUsed");
|
|
577
|
+
}
|
|
578
|
+
if (effects.status.success !== true) {
|
|
579
|
+
const err = effects.status.success === false ? effects.status.error?.message : void 0;
|
|
580
|
+
throw new Error(`Failed to estimate gas budget: ${err ?? "simulation failed"}`);
|
|
581
|
+
}
|
|
582
|
+
return computeGasBudget(effects.gasUsed, gasPrice);
|
|
583
|
+
}
|
|
584
|
+
async function prepareGasFunding(input) {
|
|
585
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
586
|
+
const { payment, budget: existingBudget } = tx.getData().gasData;
|
|
587
|
+
const bakedAddressBalanceGas = payment != null && payment.length === 0;
|
|
588
|
+
let { gasBudget } = input;
|
|
589
|
+
if (gasBudget != null && gasBudget <= 0n) {
|
|
590
|
+
gasBudget = void 0;
|
|
591
|
+
}
|
|
592
|
+
const existingPositive = isPositiveBudget(existingBudget) ? BigInt(existingBudget) : null;
|
|
593
|
+
if (gasBudget == null) {
|
|
594
|
+
if (bakedAddressBalanceGas || existingPositive == null) {
|
|
595
|
+
gasBudget = await estimateGasBudget({ tx, suiClient, owner, gasPrice });
|
|
596
|
+
} else {
|
|
597
|
+
gasBudget = existingPositive;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
tx.setGasBudget(gasBudget);
|
|
601
|
+
if (bakedAddressBalanceGas) {
|
|
602
|
+
tx.setExpiration(null);
|
|
603
|
+
}
|
|
604
|
+
return selectGasFunding({ tx, suiClient, owner, gasBudget });
|
|
605
|
+
}
|
|
606
|
+
async function preferClassicGasPayment(input) {
|
|
607
|
+
const { tx, suiClient, owner } = input;
|
|
608
|
+
const { payment } = tx.getData().gasData;
|
|
609
|
+
if (payment != null) {
|
|
610
|
+
return payment.length > 0;
|
|
611
|
+
}
|
|
612
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
613
|
+
if (funding.paymentCoins.length === 0) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
tx.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/simulate/simulator.ts
|
|
621
|
+
function formatExecutionError(error) {
|
|
622
|
+
return error.message;
|
|
623
|
+
}
|
|
624
|
+
function gasObjectToReference(gas) {
|
|
625
|
+
if (!gas?.objectId) {
|
|
626
|
+
throw new Error("Gas object missing from simulation effects");
|
|
627
|
+
}
|
|
628
|
+
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
629
|
+
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
630
|
+
return { objectId: gas.objectId, version, digest };
|
|
631
|
+
}
|
|
632
|
+
var Simulator = class {
|
|
633
|
+
constructor(globals) {
|
|
634
|
+
this.globals = globals;
|
|
635
|
+
}
|
|
636
|
+
async simulate(input) {
|
|
637
|
+
const tx = this.copyTransaction(input.txb);
|
|
638
|
+
const { suiClient } = this.globals;
|
|
639
|
+
const gasPrice = await this.getGasPrice();
|
|
640
|
+
tx.setGasPrice(gasPrice);
|
|
641
|
+
tx.setSender(input.sender);
|
|
642
|
+
try {
|
|
643
|
+
await prepareGasFunding({
|
|
644
|
+
tx,
|
|
645
|
+
suiClient,
|
|
646
|
+
owner: input.sender,
|
|
647
|
+
gasPrice
|
|
648
|
+
});
|
|
649
|
+
} catch (e) {
|
|
650
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
gasPrice,
|
|
654
|
+
simulationError: message
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
let built;
|
|
658
|
+
try {
|
|
659
|
+
built = await tx.build({ client: suiClient });
|
|
660
|
+
} catch (e) {
|
|
661
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
gasPrice,
|
|
665
|
+
simulationError: message
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
let inspectResult;
|
|
669
|
+
try {
|
|
670
|
+
inspectResult = await suiClient.simulateTransaction({
|
|
671
|
+
transaction: built,
|
|
672
|
+
include: {
|
|
673
|
+
effects: true,
|
|
674
|
+
balanceChanges: true,
|
|
675
|
+
events: true,
|
|
676
|
+
objectTypes: true,
|
|
677
|
+
transaction: true,
|
|
678
|
+
bcs: true,
|
|
679
|
+
commandResults: true
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
} catch (e) {
|
|
683
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
684
|
+
return {
|
|
685
|
+
success: false,
|
|
686
|
+
gasPrice,
|
|
687
|
+
simulationError: message
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
691
|
+
const { effects } = txRow;
|
|
692
|
+
if (!effects) {
|
|
693
|
+
throw new Error("simulateTransaction did not return effects");
|
|
694
|
+
}
|
|
695
|
+
const success = effects.status.success === true;
|
|
696
|
+
if (!success) {
|
|
697
|
+
const err = effects.status.success === false ? effects.status.error : null;
|
|
698
|
+
return {
|
|
699
|
+
success,
|
|
700
|
+
gasPrice,
|
|
701
|
+
response: inspectResult,
|
|
702
|
+
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
706
|
+
return {
|
|
707
|
+
success,
|
|
708
|
+
gasPrice,
|
|
709
|
+
gasObject: gasObjectToReference(effects.gasObject),
|
|
710
|
+
gasUsed: effects.gasUsed,
|
|
711
|
+
gasBudget,
|
|
712
|
+
response: inspectResult
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async getGasPrice() {
|
|
716
|
+
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
717
|
+
return BigInt(referenceGasPrice);
|
|
718
|
+
}
|
|
719
|
+
toGasBudget(gasUsed, gasPrice) {
|
|
720
|
+
return computeGasBudget(gasUsed, gasPrice);
|
|
721
|
+
}
|
|
722
|
+
copyTransaction(tx) {
|
|
723
|
+
return Transaction2.from(tx);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/utils/buffer.ts
|
|
728
|
+
import { Buffer } from "buffer";
|
|
729
|
+
function stringToBuffer(s) {
|
|
730
|
+
return Buffer.from(s, "utf-8");
|
|
731
|
+
}
|
|
732
|
+
function Uint8ArrayToHex(b) {
|
|
733
|
+
return `0x${Array.prototype.map.call(b, (x) => `0${x.toString(16)}`.slice(-2)).join("")}`;
|
|
734
|
+
}
|
|
735
|
+
function HexToUint8Array(hex) {
|
|
736
|
+
return Uint8Array.from(Buffer.from(hex.startsWith("0x") ? hex.slice(2) : hex, "hex"));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/utils/crypto.ts
|
|
740
|
+
import { verifyPersonalMessageSignature, verifyTransactionSignature } from "@mysten/sui/verify";
|
|
741
|
+
var SignatureVerifier = class _SignatureVerifier {
|
|
742
|
+
static async getPublicKeyFromSignature(input) {
|
|
743
|
+
if (input.messageType === "TransactionBlock") {
|
|
744
|
+
return verifyTransactionSignature(input.message, input.signature);
|
|
745
|
+
}
|
|
746
|
+
return verifyPersonalMessageSignature(input.message, input.signature);
|
|
747
|
+
}
|
|
748
|
+
static async getPublicKeyFromPersonalSignature(input) {
|
|
749
|
+
const message = stringToBuffer(input.messageStr);
|
|
750
|
+
return this.getPublicKeyFromSignature({
|
|
751
|
+
message,
|
|
752
|
+
messageType: "Personal",
|
|
753
|
+
signature: input.signature
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
static async verifySignature(input) {
|
|
757
|
+
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
758
|
+
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
759
|
+
}
|
|
760
|
+
static async verifyPersonalSignature(input) {
|
|
761
|
+
const message = stringToBuffer(input.messageStr);
|
|
762
|
+
return this.verifySignature({
|
|
763
|
+
message,
|
|
764
|
+
messageType: "Personal",
|
|
765
|
+
signature: input.signature,
|
|
766
|
+
targetAddress: input.targetAddress
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
static async verifyTransactionSignature(input) {
|
|
770
|
+
return this.verifySignature({
|
|
771
|
+
messageType: "TransactionBlock",
|
|
772
|
+
message: input.payload,
|
|
773
|
+
signature: input.signature,
|
|
774
|
+
targetAddress: input.targetAddress
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
495
779
|
// src/utils/transaction.ts
|
|
496
|
-
import { Transaction as
|
|
780
|
+
import { Transaction as Transaction3, isTransaction } from "@mysten/sui/transactions";
|
|
497
781
|
function toSuiTransaction(txb) {
|
|
498
782
|
if (isTransaction(txb)) {
|
|
499
783
|
return txb;
|
|
500
784
|
}
|
|
785
|
+
if (typeof txb === "string" || txb instanceof Uint8Array) {
|
|
786
|
+
return Transaction3.from(txb);
|
|
787
|
+
}
|
|
501
788
|
const legacy = txb;
|
|
502
|
-
|
|
789
|
+
if (typeof legacy?.serialize === "function") {
|
|
790
|
+
return Transaction3.from(legacy.serialize());
|
|
791
|
+
}
|
|
792
|
+
throw new Error("Unsupported transaction value for toSuiTransaction");
|
|
503
793
|
}
|
|
504
794
|
|
|
505
795
|
// src/utils/iter/iterator.ts
|
|
@@ -740,7 +1030,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
740
1030
|
clientUrl: this.globals.config.suiClient.url,
|
|
741
1031
|
account: {
|
|
742
1032
|
address: this.address,
|
|
743
|
-
publicKey:
|
|
1033
|
+
publicKey: fromHex2(this.address),
|
|
744
1034
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
745
1035
|
features: []
|
|
746
1036
|
}
|
|
@@ -783,7 +1073,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
783
1073
|
clientUrl: this.globals.config.suiClient.url,
|
|
784
1074
|
account: {
|
|
785
1075
|
address: this.address,
|
|
786
|
-
publicKey:
|
|
1076
|
+
publicKey: fromHex2(this.address),
|
|
787
1077
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
788
1078
|
features: []
|
|
789
1079
|
}
|
|
@@ -791,14 +1081,20 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
791
1081
|
);
|
|
792
1082
|
}
|
|
793
1083
|
txb.setGasPrice(input.gasPrice);
|
|
794
|
-
txb.setGasBudget(input.gasBudget);
|
|
795
1084
|
txb.setSender(this.address);
|
|
1085
|
+
await prepareGasFunding({
|
|
1086
|
+
tx: txb,
|
|
1087
|
+
suiClient: this.globals.suiClient,
|
|
1088
|
+
owner: this.address,
|
|
1089
|
+
gasPrice: input.gasPrice,
|
|
1090
|
+
gasBudget: input.gasBudget
|
|
1091
|
+
});
|
|
796
1092
|
const payload = await txb.build({ client: this.globals.suiClient });
|
|
797
1093
|
const digest = await txb.getDigest({ client: this.globals.suiClient });
|
|
798
1094
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
799
1095
|
return this.backend.proposeIntentionAndBuildVote({
|
|
800
1096
|
...input,
|
|
801
|
-
payload:
|
|
1097
|
+
payload: toHex2(payload),
|
|
802
1098
|
digest,
|
|
803
1099
|
msafeAddress: this.address,
|
|
804
1100
|
signature: signature.signature
|
|
@@ -856,7 +1152,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
856
1152
|
}
|
|
857
1153
|
async proposePlainPayloadIntention(intention) {
|
|
858
1154
|
const sn = await this.nextSequenceNumber();
|
|
859
|
-
const tb =
|
|
1155
|
+
const tb = Transaction4.from(intention.payload);
|
|
860
1156
|
const data = tb.getData();
|
|
861
1157
|
if (!data.sender || !isSameAddress2(data.sender, this.address)) {
|
|
862
1158
|
throw new Error("Transaction sender is not same as the multisig address");
|
|
@@ -893,6 +1189,14 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
893
1189
|
throw new Error("Already rejected");
|
|
894
1190
|
}
|
|
895
1191
|
const rejectTxb = toSuiTransaction(buildRejectTxb(this.address));
|
|
1192
|
+
rejectTxb.setGasPrice(input.gasPrice);
|
|
1193
|
+
await prepareGasFunding({
|
|
1194
|
+
tx: rejectTxb,
|
|
1195
|
+
suiClient: this.suiClient,
|
|
1196
|
+
owner: this.address,
|
|
1197
|
+
gasPrice: input.gasPrice,
|
|
1198
|
+
gasBudget: input.gasBudget
|
|
1199
|
+
});
|
|
896
1200
|
const digest = await rejectTxb.getDigest({ client: this.suiClient });
|
|
897
1201
|
const payload = await rejectTxb.build({ client: this.suiClient });
|
|
898
1202
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
@@ -918,7 +1222,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
918
1222
|
clientUrl: this.globals.config.suiClient.url,
|
|
919
1223
|
account: {
|
|
920
1224
|
address: this.address,
|
|
921
|
-
publicKey:
|
|
1225
|
+
publicKey: fromHex2(this.address),
|
|
922
1226
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
923
1227
|
features: []
|
|
924
1228
|
}
|
|
@@ -1000,7 +1304,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
1000
1304
|
|
|
1001
1305
|
// src/core/PublicKeyHelper.ts
|
|
1002
1306
|
import { isSameAddress as isSameAddress3 } from "@msafe/sui3-utils";
|
|
1003
|
-
import { normalizeSuiAddress as
|
|
1307
|
+
import { normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
|
|
1004
1308
|
var PublicKeyHelper = class {
|
|
1005
1309
|
constructor(globals) {
|
|
1006
1310
|
this.globals = globals;
|
|
@@ -1049,7 +1353,7 @@ var PublicKeyHelper = class {
|
|
|
1049
1353
|
if (!isSameAddress3(address, publicKey.toSuiAddress())) {
|
|
1050
1354
|
throw new Error("Invalid public key");
|
|
1051
1355
|
}
|
|
1052
|
-
this.knownPublicKeys.set(
|
|
1356
|
+
this.knownPublicKeys.set(normalizeSuiAddress3(address), publicKey);
|
|
1053
1357
|
}
|
|
1054
1358
|
async _getPublicKey(address) {
|
|
1055
1359
|
const pkBackend = await this.getPublicKeyFromBackend(address);
|
|
@@ -1668,6 +1972,7 @@ var MSafeEnabledDto = class {
|
|
|
1668
1972
|
};
|
|
1669
1973
|
export {
|
|
1670
1974
|
AddressBookSDK,
|
|
1975
|
+
COIN_RESERVATION_MAGIC,
|
|
1671
1976
|
COIN_TYPE_ARG_REGEX,
|
|
1672
1977
|
Coin,
|
|
1673
1978
|
CoinHelper,
|
|
@@ -1679,6 +1984,7 @@ export {
|
|
|
1679
1984
|
HexToUint8Array,
|
|
1680
1985
|
InfoTypeEnabledDto,
|
|
1681
1986
|
InfoTypeKey,
|
|
1987
|
+
InsufficientGasFundsError,
|
|
1682
1988
|
LOCAL_API_URL,
|
|
1683
1989
|
LOCAL_SYNCING_URL,
|
|
1684
1990
|
MAINNET_RPC_URL,
|
|
@@ -1696,11 +2002,18 @@ export {
|
|
|
1696
2002
|
TESTNET_RPC_URL,
|
|
1697
2003
|
Uint8ArrayToHex,
|
|
1698
2004
|
addPrefix,
|
|
2005
|
+
computeGasBudget,
|
|
2006
|
+
createCoinReservationRef,
|
|
2007
|
+
estimateGasBudget,
|
|
1699
2008
|
getAllCoins,
|
|
1700
2009
|
getMSafeConfig,
|
|
1701
2010
|
getPublicKeyFromChain,
|
|
1702
2011
|
httpUrlToGrpcBaseUrl,
|
|
2012
|
+
isCoinReservationDigest,
|
|
1703
2013
|
msafeChainToSuiNetwork,
|
|
2014
|
+
preferClassicGasPayment,
|
|
2015
|
+
prepareGasFunding,
|
|
2016
|
+
selectGasFunding,
|
|
1704
2017
|
stringToBuffer,
|
|
1705
2018
|
toSuiTransaction
|
|
1706
2019
|
};
|