@msafe/sui3-sdk 1.0.23 → 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,
@@ -47,6 +48,7 @@ __export(src_exports, {
47
48
  LOCAL_API_URL: () => LOCAL_API_URL,
48
49
  LOCAL_SYNCING_URL: () => LOCAL_SYNCING_URL,
49
50
  MAINNET_RPC_URL: () => MAINNET_RPC_URL,
51
+ MAX_GAS_PAYMENT_OBJECTS: () => MAX_GAS_PAYMENT_OBJECTS,
50
52
  MSafeAccount: () => MSafeAccount,
51
53
  MSafeClient: () => MSafeClient,
52
54
  MSafeEnabledDto: () => MSafeEnabledDto,
@@ -66,6 +68,8 @@ __export(src_exports, {
66
68
  computeGasBudget: () => computeGasBudget,
67
69
  createCoinReservationRef: () => createCoinReservationRef,
68
70
  estimateGasBudget: () => estimateGasBudget,
71
+ gasCoinWithdrawal: () => gasCoinWithdrawal,
72
+ gasPaymentCover: () => gasPaymentCover,
69
73
  getAllCoins: () => getAllCoins,
70
74
  getIntentionContent: () => getIntentionContent,
71
75
  getMSafeConfig: () => getMSafeConfig,
@@ -74,6 +78,7 @@ __export(src_exports, {
74
78
  isCoinReservationDigest: () => isCoinReservationDigest,
75
79
  msafeChainToSuiNetwork: () => msafeChainToSuiNetwork,
76
80
  parkedPayloadHex: () => parkedPayloadHex,
81
+ pickGasPaymentCoins: () => pickGasPaymentCoins,
77
82
  preferClassicGasPayment: () => preferClassicGasPayment,
78
83
  prepareGasFunding: () => prepareGasFunding,
79
84
  selectGasFunding: () => selectGasFunding,
@@ -485,6 +490,10 @@ async function getAllCoins(input) {
485
490
 
486
491
  // src/utils/gasFunding.ts
487
492
  var GAS_SAFE_OVERHEAD = 1000n;
493
+ var MAX_GAS_PAYMENT_OBJECTS = 256;
494
+ var ESTIMATE_GAS_COVER = 1000000000n;
495
+ var MAX_PROBE_GAS_BUDGET = 50000000000n;
496
+ var COIN_WITH_BALANCE_INTENT = "CoinWithBalance";
488
497
  var InsufficientGasFundsError = class extends Error {
489
498
  gasBudget;
490
499
  coinBalance;
@@ -500,11 +509,105 @@ var InsufficientGasFundsError = class extends Error {
500
509
  this.addressBalance = addressBalance;
501
510
  }
502
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
+ }
503
540
  function computeGasBudget(gasUsed, gasPrice) {
504
541
  const { computationCost, storageCost } = gasUsed;
505
542
  const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
506
543
  return BigInt(computationCost) + BigInt(storageCost) + safeOverhead;
507
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
+ }
551
+ function pickGasPaymentCoins(coins, cover) {
552
+ if (cover <= 0n) {
553
+ return [];
554
+ }
555
+ const sorted = [...coins].filter((coin) => coin.balance > 0n).sort(compareBalanceDescending);
556
+ const selected = [];
557
+ let sum = 0n;
558
+ for (let index = 0; index < sorted.length; index += 1) {
559
+ if (selected.length >= MAX_GAS_PAYMENT_OBJECTS) {
560
+ break;
561
+ }
562
+ const coin = sorted[index];
563
+ selected.push(coin);
564
+ sum += coin.balance;
565
+ if (sum >= cover) {
566
+ break;
567
+ }
568
+ }
569
+ return selected;
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
+ }
508
611
  function collectUsedObjectIds(tx) {
509
612
  const used = tx.getData().inputs.reduce((acc, input) => {
510
613
  const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
@@ -648,8 +751,14 @@ async function selectGasFunding(input) {
648
751
  if (total < gasBudget) {
649
752
  throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
650
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
+ }
651
760
  if (paymentCoins.length > 0 && coinBalance >= gasBudget) {
652
- tx.setGasPayment(toObjectRefs(paymentCoins));
761
+ tx.setGasPayment(toObjectRefs(pickGasPaymentCoins(paymentCoins, gasBudget)));
653
762
  return { mode: "classic", gasBudget, coinBalance, addressBalance };
654
763
  }
655
764
  if (paymentCoins.length > 0 && total >= gasBudget) {
@@ -658,7 +767,7 @@ async function selectGasFunding(input) {
658
767
  tx,
659
768
  suiClient,
660
769
  owner,
661
- paymentCoins,
770
+ paymentCoins: pickGasPaymentCoins(paymentCoins, 1n),
662
771
  topUpAmount
663
772
  });
664
773
  return { mode: "mixedTopUp", gasBudget, coinBalance, addressBalance };
@@ -703,8 +812,19 @@ async function estimateGasBudget(input) {
703
812
  const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : import_transactions.Transaction.from(tx);
704
813
  probe.setSender(owner);
705
814
  probe.setGasPrice(gasPrice);
706
- if (funding.paymentCoins.length > 0) {
707
- probe.setGasPayment(toObjectRefs(funding.paymentCoins));
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) {
827
+ probe.setGasPayment(toObjectRefs(pickGasPaymentCoins(funding.paymentCoins, ESTIMATE_GAS_COVER)));
708
828
  }
709
829
  let built;
710
830
  try {
@@ -779,7 +899,7 @@ async function preferClassicGasPayment(input) {
779
899
  if (funding.paymentCoins.length === 0) {
780
900
  return false;
781
901
  }
782
- tx.setGasPayment(toObjectRefs(funding.paymentCoins));
902
+ tx.setGasPayment(toObjectRefs(pickGasPaymentCoins(funding.paymentCoins, ESTIMATE_GAS_COVER)));
783
903
  return true;
784
904
  }
785
905