@msafe/sui3-sdk 1.0.24 → 1.0.25

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 CHANGED
@@ -40,6 +40,7 @@ __export(src_exports, {
40
40
  DEV_SYNCING_URL: () => DEV_SYNCING_URL,
41
41
  ENV_CONFIGS: () => ENV_CONFIGS,
42
42
  Formatter: () => Formatter,
43
+ GasPaymentLimitError: () => GasPaymentLimitError,
43
44
  HexToUint8Array: () => HexToUint8Array,
44
45
  InfoTypeEnabledDto: () => InfoTypeEnabledDto,
45
46
  InfoTypeKey: () => InfoTypeKey,
@@ -67,6 +68,8 @@ __export(src_exports, {
67
68
  computeGasBudget: () => computeGasBudget,
68
69
  createCoinReservationRef: () => createCoinReservationRef,
69
70
  estimateGasBudget: () => estimateGasBudget,
71
+ gasCoinWithdrawal: () => gasCoinWithdrawal,
72
+ gasPaymentCover: () => gasPaymentCover,
70
73
  getAllCoins: () => getAllCoins,
71
74
  getIntentionContent: () => getIntentionContent,
72
75
  getMSafeConfig: () => getMSafeConfig,
@@ -489,6 +492,8 @@ async function getAllCoins(input) {
489
492
  var GAS_SAFE_OVERHEAD = 1000n;
490
493
  var MAX_GAS_PAYMENT_OBJECTS = 256;
491
494
  var ESTIMATE_GAS_COVER = 1000000000n;
495
+ var MAX_PROBE_GAS_BUDGET = 50000000000n;
496
+ var COIN_WITH_BALANCE_INTENT = "CoinWithBalance";
492
497
  var InsufficientGasFundsError = class extends Error {
493
498
  gasBudget;
494
499
  coinBalance;
@@ -504,22 +509,57 @@ var InsufficientGasFundsError = class extends Error {
504
509
  this.addressBalance = addressBalance;
505
510
  }
506
511
  };
512
+ var GasPaymentLimitError = class extends Error {
513
+ required;
514
+ covered;
515
+ coinCount;
516
+ constructor(required, covered, coinCount) {
517
+ super(gasPaymentLimitMessage(required, covered, coinCount));
518
+ this.name = "GasPaymentLimitError";
519
+ this.required = required;
520
+ this.covered = covered;
521
+ this.coinCount = coinCount;
522
+ }
523
+ };
524
+ function formatSui(mist) {
525
+ const negative = mist < 0n;
526
+ const value = negative ? -mist : mist;
527
+ const whole = value / 1000000000n;
528
+ const fraction = (value % 1000000000n).toString().padStart(9, "0").replace(/0+$/, "");
529
+ const text = fraction.length > 0 ? `${whole}.${fraction}` : `${whole}`;
530
+ return negative ? `-${text}` : text;
531
+ }
532
+ function gasPaymentLimitMessage(required, covered, coinCount) {
533
+ const requiredSui = formatSui(required);
534
+ const coveredSui = formatSui(covered);
535
+ if (coinCount >= MAX_GAS_PAYMENT_OBJECTS) {
536
+ return `This transfer needs ${requiredSui} SUI from gas coins, but the largest ${MAX_GAS_PAYMENT_OBJECTS} coins only cover ${coveredSui} SUI. Send a smaller amount, or consolidate coins into fewer objects first.`;
537
+ }
538
+ return `This transfer needs ${requiredSui} SUI from gas coins, but the available SUI coin objects only cover ${coveredSui} SUI.`;
539
+ }
507
540
  function computeGasBudget(gasUsed, gasPrice) {
508
541
  const { computationCost, storageCost } = gasUsed;
509
542
  const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
510
543
  return BigInt(computationCost) + BigInt(storageCost) + safeOverhead;
511
544
  }
545
+ function compareBalanceDescending(a, b) {
546
+ if (a.balance === b.balance) {
547
+ return 0;
548
+ }
549
+ return a.balance < b.balance ? 1 : -1;
550
+ }
512
551
  function pickGasPaymentCoins(coins, cover) {
513
552
  if (cover <= 0n) {
514
553
  return [];
515
554
  }
516
- const sorted = [...coins].filter((coin) => coin.balance > 0n).sort((a, b) => a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1);
555
+ const sorted = [...coins].filter((coin) => coin.balance > 0n).sort(compareBalanceDescending);
517
556
  const selected = [];
518
557
  let sum = 0n;
519
- for (const coin of sorted) {
558
+ for (let index = 0; index < sorted.length; index += 1) {
520
559
  if (selected.length >= MAX_GAS_PAYMENT_OBJECTS) {
521
560
  break;
522
561
  }
562
+ const coin = sorted[index];
523
563
  selected.push(coin);
524
564
  sum += coin.balance;
525
565
  if (sum >= cover) {
@@ -528,6 +568,46 @@ function pickGasPaymentCoins(coins, cover) {
528
568
  }
529
569
  return selected;
530
570
  }
571
+ function readBigInt(value) {
572
+ if (typeof value === "bigint") {
573
+ return value;
574
+ }
575
+ if (typeof value === "number" && Number.isFinite(value)) {
576
+ return BigInt(Math.trunc(value));
577
+ }
578
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
579
+ return BigInt(value);
580
+ }
581
+ return 0n;
582
+ }
583
+ function gasCoinWithdrawal(tx) {
584
+ return tx.getData().commands.reduce((sum, command) => {
585
+ if (command.$kind !== "$Intent" || command.$Intent.name !== COIN_WITH_BALANCE_INTENT) {
586
+ return sum;
587
+ }
588
+ const data = command.$Intent.data;
589
+ if (data?.type !== "gas") {
590
+ return sum;
591
+ }
592
+ return sum + readBigInt(data.balance);
593
+ }, 0n);
594
+ }
595
+ function gasPaymentCover(tx, addressBalance, gasBudget) {
596
+ const withdrawal = gasCoinWithdrawal(tx);
597
+ const fromGasCoin = withdrawal > addressBalance ? withdrawal : 0n;
598
+ return gasBudget + fromGasCoin;
599
+ }
600
+ function sumBalances(coins) {
601
+ return coins.reduce((sum, coin) => sum + coin.balance, 0n);
602
+ }
603
+ function pickGasPaymentCoinsForCover(coins, cover) {
604
+ const picked = pickGasPaymentCoins(coins, cover);
605
+ const covered = sumBalances(picked);
606
+ if (cover > 0n && covered < cover) {
607
+ throw new GasPaymentLimitError(cover, covered, picked.length);
608
+ }
609
+ return picked;
610
+ }
531
611
  function collectUsedObjectIds(tx) {
532
612
  const used = tx.getData().inputs.reduce((acc, input) => {
533
613
  const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
@@ -671,6 +751,12 @@ async function selectGasFunding(input) {
671
751
  if (total < gasBudget) {
672
752
  throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
673
753
  }
754
+ const cover = gasPaymentCover(tx, addressBalance, gasBudget);
755
+ if (cover > gasBudget) {
756
+ const picked = pickGasPaymentCoinsForCover(paymentCoins, cover);
757
+ tx.setGasPayment(toObjectRefs(picked));
758
+ return { mode: "classic", gasBudget, coinBalance, addressBalance };
759
+ }
674
760
  if (paymentCoins.length > 0 && coinBalance >= gasBudget) {
675
761
  tx.setGasPayment(toObjectRefs(pickGasPaymentCoins(paymentCoins, gasBudget)));
676
762
  return { mode: "classic", gasBudget, coinBalance, addressBalance };
@@ -726,7 +812,18 @@ async function estimateGasBudget(input) {
726
812
  const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : import_transactions.Transaction.from(tx);
727
813
  probe.setSender(owner);
728
814
  probe.setGasPrice(gasPrice);
729
- if (funding.paymentCoins.length > 0) {
815
+ const fromGasCoin = gasPaymentCover(tx, funding.addressBalance, 0n);
816
+ if (fromGasCoin > 0n) {
817
+ const picked = pickGasPaymentCoins(funding.paymentCoins, fromGasCoin + ESTIMATE_GAS_COVER);
818
+ const covered = sumBalances(picked);
819
+ if (covered <= fromGasCoin) {
820
+ throw new GasPaymentLimitError(fromGasCoin, covered, picked.length);
821
+ }
822
+ probe.setGasPayment(toObjectRefs(picked));
823
+ const remainder = covered - fromGasCoin;
824
+ const probeBudget = remainder > MAX_PROBE_GAS_BUDGET ? MAX_PROBE_GAS_BUDGET : remainder;
825
+ probe.setGasBudget(probeBudget);
826
+ } else if (funding.paymentCoins.length > 0) {
730
827
  probe.setGasPayment(toObjectRefs(pickGasPaymentCoins(funding.paymentCoins, ESTIMATE_GAS_COVER)));
731
828
  }
732
829
  let built;