@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.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,355 @@ 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
|
+
async function estimateGasBudget(input) {
|
|
532
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
533
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
534
|
+
if (funding.total <= 0n) {
|
|
535
|
+
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
536
|
+
}
|
|
537
|
+
const probe = Transaction.from(tx);
|
|
538
|
+
probe.setSender(owner);
|
|
539
|
+
probe.setGasPrice(gasPrice);
|
|
540
|
+
if (funding.paymentCoins.length > 0) {
|
|
541
|
+
probe.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
542
|
+
}
|
|
543
|
+
let built;
|
|
544
|
+
try {
|
|
545
|
+
built = await probe.build({ client: suiClient });
|
|
546
|
+
} catch (e) {
|
|
547
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
548
|
+
throw new Error(`Failed to estimate gas budget: ${message}`);
|
|
549
|
+
}
|
|
550
|
+
const inspect = await suiClient.simulateTransaction({
|
|
551
|
+
transaction: built,
|
|
552
|
+
include: { effects: true }
|
|
553
|
+
});
|
|
554
|
+
const effects = (inspect.Transaction ?? inspect.FailedTransaction)?.effects;
|
|
555
|
+
if (!effects?.gasUsed) {
|
|
556
|
+
throw new Error("Failed to estimate gas budget: simulateTransaction returned no gasUsed");
|
|
557
|
+
}
|
|
558
|
+
if (effects.status.success !== true) {
|
|
559
|
+
const err = effects.status.success === false ? effects.status.error?.message : void 0;
|
|
560
|
+
throw new Error(`Failed to estimate gas budget: ${err ?? "simulation failed"}`);
|
|
561
|
+
}
|
|
562
|
+
return computeGasBudget(effects.gasUsed, gasPrice);
|
|
563
|
+
}
|
|
564
|
+
async function prepareGasFunding(input) {
|
|
565
|
+
const { tx, suiClient, owner, gasPrice } = input;
|
|
566
|
+
const { payment, budget: existingBudget } = tx.getData().gasData;
|
|
567
|
+
const bakedAddressBalanceGas = payment != null && payment.length === 0;
|
|
568
|
+
let { gasBudget } = input;
|
|
569
|
+
if (gasBudget == null) {
|
|
570
|
+
if (bakedAddressBalanceGas || existingBudget == null) {
|
|
571
|
+
gasBudget = await estimateGasBudget({ tx, suiClient, owner, gasPrice });
|
|
572
|
+
} else {
|
|
573
|
+
gasBudget = BigInt(existingBudget);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
tx.setGasBudget(gasBudget);
|
|
577
|
+
return selectGasFunding({ tx, suiClient, owner, gasBudget });
|
|
578
|
+
}
|
|
579
|
+
async function preferClassicGasPayment(input) {
|
|
580
|
+
const { tx, suiClient, owner } = input;
|
|
581
|
+
const { payment } = tx.getData().gasData;
|
|
582
|
+
if (payment != null) {
|
|
583
|
+
return payment.length > 0;
|
|
584
|
+
}
|
|
585
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
586
|
+
if (funding.paymentCoins.length === 0) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
tx.setGasPayment(toObjectRefs(funding.paymentCoins));
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/simulate/simulator.ts
|
|
594
|
+
function formatExecutionError(error) {
|
|
595
|
+
return error.message;
|
|
596
|
+
}
|
|
597
|
+
function gasObjectToReference(gas) {
|
|
598
|
+
if (!gas?.objectId) {
|
|
599
|
+
throw new Error("Gas object missing from simulation effects");
|
|
600
|
+
}
|
|
601
|
+
const version = gas.outputVersion ?? gas.inputVersion ?? "0";
|
|
602
|
+
const digest = gas.outputDigest ?? gas.inputDigest ?? "";
|
|
603
|
+
return { objectId: gas.objectId, version, digest };
|
|
604
|
+
}
|
|
605
|
+
var Simulator = class {
|
|
606
|
+
constructor(globals) {
|
|
607
|
+
this.globals = globals;
|
|
608
|
+
}
|
|
609
|
+
async simulate(input) {
|
|
610
|
+
const tx = this.copyTransaction(input.txb);
|
|
611
|
+
const { suiClient } = this.globals;
|
|
612
|
+
const gasPrice = await this.getGasPrice();
|
|
613
|
+
tx.setGasPrice(gasPrice);
|
|
614
|
+
tx.setSender(input.sender);
|
|
615
|
+
try {
|
|
616
|
+
await prepareGasFunding({
|
|
617
|
+
tx,
|
|
618
|
+
suiClient,
|
|
619
|
+
owner: input.sender,
|
|
620
|
+
gasPrice
|
|
621
|
+
});
|
|
622
|
+
} catch (e) {
|
|
623
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
624
|
+
return {
|
|
625
|
+
success: false,
|
|
626
|
+
gasPrice,
|
|
627
|
+
simulationError: message
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
let built;
|
|
631
|
+
try {
|
|
632
|
+
built = await tx.build({ client: suiClient });
|
|
633
|
+
} catch (e) {
|
|
634
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
635
|
+
return {
|
|
636
|
+
success: false,
|
|
637
|
+
gasPrice,
|
|
638
|
+
simulationError: message
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
let inspectResult;
|
|
642
|
+
try {
|
|
643
|
+
inspectResult = await suiClient.simulateTransaction({
|
|
644
|
+
transaction: built,
|
|
645
|
+
include: {
|
|
646
|
+
effects: true,
|
|
647
|
+
balanceChanges: true,
|
|
648
|
+
events: true,
|
|
649
|
+
objectTypes: true,
|
|
650
|
+
transaction: true,
|
|
651
|
+
bcs: true,
|
|
652
|
+
commandResults: true
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
} catch (e) {
|
|
656
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
657
|
+
return {
|
|
658
|
+
success: false,
|
|
659
|
+
gasPrice,
|
|
660
|
+
simulationError: message
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
const txRow = inspectResult.Transaction ?? inspectResult.FailedTransaction;
|
|
664
|
+
const { effects } = txRow;
|
|
665
|
+
if (!effects) {
|
|
666
|
+
throw new Error("simulateTransaction did not return effects");
|
|
667
|
+
}
|
|
668
|
+
const success = effects.status.success === true;
|
|
669
|
+
if (!success) {
|
|
670
|
+
const err = effects.status.success === false ? effects.status.error : null;
|
|
671
|
+
return {
|
|
672
|
+
success,
|
|
673
|
+
gasPrice,
|
|
674
|
+
response: inspectResult,
|
|
675
|
+
simulationError: err ? formatExecutionError(err) : "Simulation failed"
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const gasBudget = this.toGasBudget(effects.gasUsed, gasPrice);
|
|
679
|
+
return {
|
|
680
|
+
success,
|
|
681
|
+
gasPrice,
|
|
682
|
+
gasObject: gasObjectToReference(effects.gasObject),
|
|
683
|
+
gasUsed: effects.gasUsed,
|
|
684
|
+
gasBudget,
|
|
685
|
+
response: inspectResult
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async getGasPrice() {
|
|
689
|
+
const { referenceGasPrice } = await this.globals.suiClient.getReferenceGasPrice();
|
|
690
|
+
return BigInt(referenceGasPrice);
|
|
691
|
+
}
|
|
692
|
+
toGasBudget(gasUsed, gasPrice) {
|
|
693
|
+
return computeGasBudget(gasUsed, gasPrice);
|
|
694
|
+
}
|
|
695
|
+
copyTransaction(tx) {
|
|
696
|
+
return Transaction2.from(tx);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/utils/buffer.ts
|
|
701
|
+
import { Buffer } from "buffer";
|
|
702
|
+
function stringToBuffer(s) {
|
|
703
|
+
return Buffer.from(s, "utf-8");
|
|
704
|
+
}
|
|
705
|
+
function Uint8ArrayToHex(b) {
|
|
706
|
+
return `0x${Array.prototype.map.call(b, (x) => `0${x.toString(16)}`.slice(-2)).join("")}`;
|
|
707
|
+
}
|
|
708
|
+
function HexToUint8Array(hex) {
|
|
709
|
+
return Uint8Array.from(Buffer.from(hex.startsWith("0x") ? hex.slice(2) : hex, "hex"));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/utils/crypto.ts
|
|
713
|
+
import { verifyPersonalMessageSignature, verifyTransactionSignature } from "@mysten/sui/verify";
|
|
714
|
+
var SignatureVerifier = class _SignatureVerifier {
|
|
715
|
+
static async getPublicKeyFromSignature(input) {
|
|
716
|
+
if (input.messageType === "TransactionBlock") {
|
|
717
|
+
return verifyTransactionSignature(input.message, input.signature);
|
|
718
|
+
}
|
|
719
|
+
return verifyPersonalMessageSignature(input.message, input.signature);
|
|
720
|
+
}
|
|
721
|
+
static async getPublicKeyFromPersonalSignature(input) {
|
|
722
|
+
const message = stringToBuffer(input.messageStr);
|
|
723
|
+
return this.getPublicKeyFromSignature({
|
|
724
|
+
message,
|
|
725
|
+
messageType: "Personal",
|
|
726
|
+
signature: input.signature
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
static async verifySignature(input) {
|
|
730
|
+
const publicKey = await _SignatureVerifier.getPublicKeyFromSignature(input);
|
|
731
|
+
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
732
|
+
}
|
|
733
|
+
static async verifyPersonalSignature(input) {
|
|
734
|
+
const message = stringToBuffer(input.messageStr);
|
|
735
|
+
return this.verifySignature({
|
|
736
|
+
message,
|
|
737
|
+
messageType: "Personal",
|
|
738
|
+
signature: input.signature,
|
|
739
|
+
targetAddress: input.targetAddress
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
static async verifyTransactionSignature(input) {
|
|
743
|
+
return this.verifySignature({
|
|
744
|
+
messageType: "TransactionBlock",
|
|
745
|
+
message: input.payload,
|
|
746
|
+
signature: input.signature,
|
|
747
|
+
targetAddress: input.targetAddress
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
495
752
|
// src/utils/transaction.ts
|
|
496
|
-
import { Transaction as
|
|
753
|
+
import { Transaction as Transaction3, isTransaction } from "@mysten/sui/transactions";
|
|
497
754
|
function toSuiTransaction(txb) {
|
|
498
755
|
if (isTransaction(txb)) {
|
|
499
756
|
return txb;
|
|
500
757
|
}
|
|
758
|
+
if (typeof txb === "string" || txb instanceof Uint8Array) {
|
|
759
|
+
return Transaction3.from(txb);
|
|
760
|
+
}
|
|
501
761
|
const legacy = txb;
|
|
502
|
-
|
|
762
|
+
if (typeof legacy?.serialize === "function") {
|
|
763
|
+
return Transaction3.from(legacy.serialize());
|
|
764
|
+
}
|
|
765
|
+
throw new Error("Unsupported transaction value for toSuiTransaction");
|
|
503
766
|
}
|
|
504
767
|
|
|
505
768
|
// src/utils/iter/iterator.ts
|
|
@@ -740,7 +1003,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
740
1003
|
clientUrl: this.globals.config.suiClient.url,
|
|
741
1004
|
account: {
|
|
742
1005
|
address: this.address,
|
|
743
|
-
publicKey:
|
|
1006
|
+
publicKey: fromHex2(this.address),
|
|
744
1007
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
745
1008
|
features: []
|
|
746
1009
|
}
|
|
@@ -783,7 +1046,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
783
1046
|
clientUrl: this.globals.config.suiClient.url,
|
|
784
1047
|
account: {
|
|
785
1048
|
address: this.address,
|
|
786
|
-
publicKey:
|
|
1049
|
+
publicKey: fromHex2(this.address),
|
|
787
1050
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
788
1051
|
features: []
|
|
789
1052
|
}
|
|
@@ -791,14 +1054,20 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
791
1054
|
);
|
|
792
1055
|
}
|
|
793
1056
|
txb.setGasPrice(input.gasPrice);
|
|
794
|
-
txb.setGasBudget(input.gasBudget);
|
|
795
1057
|
txb.setSender(this.address);
|
|
1058
|
+
await prepareGasFunding({
|
|
1059
|
+
tx: txb,
|
|
1060
|
+
suiClient: this.globals.suiClient,
|
|
1061
|
+
owner: this.address,
|
|
1062
|
+
gasPrice: input.gasPrice,
|
|
1063
|
+
gasBudget: input.gasBudget
|
|
1064
|
+
});
|
|
796
1065
|
const payload = await txb.build({ client: this.globals.suiClient });
|
|
797
1066
|
const digest = await txb.getDigest({ client: this.globals.suiClient });
|
|
798
1067
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
799
1068
|
return this.backend.proposeIntentionAndBuildVote({
|
|
800
1069
|
...input,
|
|
801
|
-
payload:
|
|
1070
|
+
payload: toHex2(payload),
|
|
802
1071
|
digest,
|
|
803
1072
|
msafeAddress: this.address,
|
|
804
1073
|
signature: signature.signature
|
|
@@ -856,7 +1125,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
856
1125
|
}
|
|
857
1126
|
async proposePlainPayloadIntention(intention) {
|
|
858
1127
|
const sn = await this.nextSequenceNumber();
|
|
859
|
-
const tb =
|
|
1128
|
+
const tb = Transaction4.from(intention.payload);
|
|
860
1129
|
const data = tb.getData();
|
|
861
1130
|
if (!data.sender || !isSameAddress2(data.sender, this.address)) {
|
|
862
1131
|
throw new Error("Transaction sender is not same as the multisig address");
|
|
@@ -893,6 +1162,14 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
893
1162
|
throw new Error("Already rejected");
|
|
894
1163
|
}
|
|
895
1164
|
const rejectTxb = toSuiTransaction(buildRejectTxb(this.address));
|
|
1165
|
+
rejectTxb.setGasPrice(input.gasPrice);
|
|
1166
|
+
await prepareGasFunding({
|
|
1167
|
+
tx: rejectTxb,
|
|
1168
|
+
suiClient: this.suiClient,
|
|
1169
|
+
owner: this.address,
|
|
1170
|
+
gasPrice: input.gasPrice,
|
|
1171
|
+
gasBudget: input.gasBudget
|
|
1172
|
+
});
|
|
896
1173
|
const digest = await rejectTxb.getDigest({ client: this.suiClient });
|
|
897
1174
|
const payload = await rejectTxb.build({ client: this.suiClient });
|
|
898
1175
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
@@ -918,7 +1195,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
918
1195
|
clientUrl: this.globals.config.suiClient.url,
|
|
919
1196
|
account: {
|
|
920
1197
|
address: this.address,
|
|
921
|
-
publicKey:
|
|
1198
|
+
publicKey: fromHex2(this.address),
|
|
922
1199
|
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
923
1200
|
features: []
|
|
924
1201
|
}
|
|
@@ -1000,7 +1277,7 @@ var MSafeAccount = class _MSafeAccount {
|
|
|
1000
1277
|
|
|
1001
1278
|
// src/core/PublicKeyHelper.ts
|
|
1002
1279
|
import { isSameAddress as isSameAddress3 } from "@msafe/sui3-utils";
|
|
1003
|
-
import { normalizeSuiAddress as
|
|
1280
|
+
import { normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
|
|
1004
1281
|
var PublicKeyHelper = class {
|
|
1005
1282
|
constructor(globals) {
|
|
1006
1283
|
this.globals = globals;
|
|
@@ -1049,7 +1326,7 @@ var PublicKeyHelper = class {
|
|
|
1049
1326
|
if (!isSameAddress3(address, publicKey.toSuiAddress())) {
|
|
1050
1327
|
throw new Error("Invalid public key");
|
|
1051
1328
|
}
|
|
1052
|
-
this.knownPublicKeys.set(
|
|
1329
|
+
this.knownPublicKeys.set(normalizeSuiAddress3(address), publicKey);
|
|
1053
1330
|
}
|
|
1054
1331
|
async _getPublicKey(address) {
|
|
1055
1332
|
const pkBackend = await this.getPublicKeyFromBackend(address);
|
|
@@ -1668,6 +1945,7 @@ var MSafeEnabledDto = class {
|
|
|
1668
1945
|
};
|
|
1669
1946
|
export {
|
|
1670
1947
|
AddressBookSDK,
|
|
1948
|
+
COIN_RESERVATION_MAGIC,
|
|
1671
1949
|
COIN_TYPE_ARG_REGEX,
|
|
1672
1950
|
Coin,
|
|
1673
1951
|
CoinHelper,
|
|
@@ -1679,6 +1957,7 @@ export {
|
|
|
1679
1957
|
HexToUint8Array,
|
|
1680
1958
|
InfoTypeEnabledDto,
|
|
1681
1959
|
InfoTypeKey,
|
|
1960
|
+
InsufficientGasFundsError,
|
|
1682
1961
|
LOCAL_API_URL,
|
|
1683
1962
|
LOCAL_SYNCING_URL,
|
|
1684
1963
|
MAINNET_RPC_URL,
|
|
@@ -1696,11 +1975,18 @@ export {
|
|
|
1696
1975
|
TESTNET_RPC_URL,
|
|
1697
1976
|
Uint8ArrayToHex,
|
|
1698
1977
|
addPrefix,
|
|
1978
|
+
computeGasBudget,
|
|
1979
|
+
createCoinReservationRef,
|
|
1980
|
+
estimateGasBudget,
|
|
1699
1981
|
getAllCoins,
|
|
1700
1982
|
getMSafeConfig,
|
|
1701
1983
|
getPublicKeyFromChain,
|
|
1702
1984
|
httpUrlToGrpcBaseUrl,
|
|
1985
|
+
isCoinReservationDigest,
|
|
1703
1986
|
msafeChainToSuiNetwork,
|
|
1987
|
+
preferClassicGasPayment,
|
|
1988
|
+
prepareGasFunding,
|
|
1989
|
+
selectGasFunding,
|
|
1704
1990
|
stringToBuffer,
|
|
1705
1991
|
toSuiTransaction
|
|
1706
1992
|
};
|