@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.d.cts CHANGED
@@ -349,6 +349,16 @@ declare class InsufficientGasFundsError extends Error {
349
349
  readonly addressBalance: bigint;
350
350
  constructor(gasBudget: bigint, coinBalance: bigint, addressBalance: bigint);
351
351
  }
352
+ /**
353
+ * Gas coins cannot cover a SUI amount that is split from the gas coin.
354
+ * Happens when the largest 256 coins are still short of amount + gas budget.
355
+ */
356
+ declare class GasPaymentLimitError extends Error {
357
+ readonly required: bigint;
358
+ readonly covered: bigint;
359
+ readonly coinCount: number;
360
+ constructor(required: bigint, covered: bigint, coinCount: number);
361
+ }
352
362
  /**
353
363
  * Gas budget must cover pre-rebate costs (computation + storage + overhead).
354
364
  * storageRebate is refunded after execution and must not reduce the budget.
@@ -367,6 +377,15 @@ type PaymentCoinRef = {
367
377
  * ("gas_budget must be 0 for gasless transactions").
368
378
  */
369
379
  declare function pickGasPaymentCoins(coins: PaymentCoinRef[], cover: bigint): PaymentCoinRef[];
380
+ /** SUI that CoinWithBalance intents will split out of the gas coin. */
381
+ declare function gasCoinWithdrawal(tx: Transaction): bigint;
382
+ /**
383
+ * Balance gas payment coins must hold.
384
+ * A SUI withdrawal is taken from the gas coin only when address balance cannot
385
+ * cover that withdrawal by itself. Mysten redeems address balance for the whole
386
+ * amount in that case, and gas payment only has to pay the gas budget.
387
+ */
388
+ declare function gasPaymentCover(tx: Transaction, addressBalance: bigint, gasBudget: bigint): bigint;
370
389
  /** Object IDs a built payload already pins (inputs + gas payment). */
371
390
  declare function collectPayloadObjectIds(payload: string | Uint8Array): string[];
372
391
  /**
@@ -658,4 +677,4 @@ declare function getIntentionContent(intention: unknown): unknown;
658
677
  */
659
678
  declare function transactionFromContent(content: unknown): Transaction;
660
679
 
661
- export { AddressBookSDK, COIN_RESERVATION_MAGIC, COIN_TYPE_ARG_REGEX, Coin, CoinHelper, CreateHelper, type CreateMSafeInfo, DEV_API_URL, DEV_SYNCING_URL, ENV_CONFIGS, Formatter, type FutureIntention, type GasFundingMode, type GasFundingResult, type GasObjectReference, HexToUint8Array, type HistorySendTx, type HistoryVote, type IWallet, InfoTypeEnabledDto, InfoTypeKey, InsufficientGasFundsError, LOCAL_API_URL, LOCAL_SYNCING_URL, MAINNET_RPC_URL, MAX_GAS_PAYMENT_OBJECTS, MSafeAccount, MSafeClient, type MSafeConfig, type MSafeConfigOptions, MSafeEnabledDto, MSafeEnv, MSafeGlobals, type NotificationData, type NotificationInfo, NotificationSubscribeDto, NotificationUpdateDto, PREV_API_URL, PROD_API_URL, type Pagination, type PaymentCoinRef, type PendingTx, type PendingVote, type ProposeIntention, type ReceiveTransaction, SUI_COIN, SignatureVerifier, type SimulateDryRunResponse, type SimulationFailedResult, type SimulationOption, type SimulationResult, type SimulationSuccessResult, TESTNET_RPC_URL, Uint8ArrayToHex, addPrefix, collectPayloadObjectIds, collectQueueExcludeObjectIds, computeGasBudget, createCoinReservationRef, estimateGasBudget, getAllCoins, getIntentionContent, getMSafeConfig, getPublicKeyFromChain, httpUrlToGrpcBaseUrl, isCoinReservationDigest, msafeChainToSuiNetwork, parkedPayloadHex, pickGasPaymentCoins, preferClassicGasPayment, prepareGasFunding, selectGasFunding, stringToBuffer, toSuiTransaction, transactionFromContent };
680
+ export { AddressBookSDK, COIN_RESERVATION_MAGIC, COIN_TYPE_ARG_REGEX, Coin, CoinHelper, CreateHelper, type CreateMSafeInfo, DEV_API_URL, DEV_SYNCING_URL, ENV_CONFIGS, Formatter, type FutureIntention, type GasFundingMode, type GasFundingResult, type GasObjectReference, GasPaymentLimitError, HexToUint8Array, type HistorySendTx, type HistoryVote, type IWallet, InfoTypeEnabledDto, InfoTypeKey, InsufficientGasFundsError, LOCAL_API_URL, LOCAL_SYNCING_URL, MAINNET_RPC_URL, MAX_GAS_PAYMENT_OBJECTS, MSafeAccount, MSafeClient, type MSafeConfig, type MSafeConfigOptions, MSafeEnabledDto, MSafeEnv, MSafeGlobals, type NotificationData, type NotificationInfo, NotificationSubscribeDto, NotificationUpdateDto, PREV_API_URL, PROD_API_URL, type Pagination, type PaymentCoinRef, type PendingTx, type PendingVote, type ProposeIntention, type ReceiveTransaction, SUI_COIN, SignatureVerifier, type SimulateDryRunResponse, type SimulationFailedResult, type SimulationOption, type SimulationResult, type SimulationSuccessResult, TESTNET_RPC_URL, Uint8ArrayToHex, addPrefix, collectPayloadObjectIds, collectQueueExcludeObjectIds, computeGasBudget, createCoinReservationRef, estimateGasBudget, gasCoinWithdrawal, gasPaymentCover, getAllCoins, getIntentionContent, getMSafeConfig, getPublicKeyFromChain, httpUrlToGrpcBaseUrl, isCoinReservationDigest, msafeChainToSuiNetwork, parkedPayloadHex, pickGasPaymentCoins, preferClassicGasPayment, prepareGasFunding, selectGasFunding, stringToBuffer, toSuiTransaction, transactionFromContent };
package/dist/index.d.ts CHANGED
@@ -349,6 +349,16 @@ declare class InsufficientGasFundsError extends Error {
349
349
  readonly addressBalance: bigint;
350
350
  constructor(gasBudget: bigint, coinBalance: bigint, addressBalance: bigint);
351
351
  }
352
+ /**
353
+ * Gas coins cannot cover a SUI amount that is split from the gas coin.
354
+ * Happens when the largest 256 coins are still short of amount + gas budget.
355
+ */
356
+ declare class GasPaymentLimitError extends Error {
357
+ readonly required: bigint;
358
+ readonly covered: bigint;
359
+ readonly coinCount: number;
360
+ constructor(required: bigint, covered: bigint, coinCount: number);
361
+ }
352
362
  /**
353
363
  * Gas budget must cover pre-rebate costs (computation + storage + overhead).
354
364
  * storageRebate is refunded after execution and must not reduce the budget.
@@ -367,6 +377,15 @@ type PaymentCoinRef = {
367
377
  * ("gas_budget must be 0 for gasless transactions").
368
378
  */
369
379
  declare function pickGasPaymentCoins(coins: PaymentCoinRef[], cover: bigint): PaymentCoinRef[];
380
+ /** SUI that CoinWithBalance intents will split out of the gas coin. */
381
+ declare function gasCoinWithdrawal(tx: Transaction): bigint;
382
+ /**
383
+ * Balance gas payment coins must hold.
384
+ * A SUI withdrawal is taken from the gas coin only when address balance cannot
385
+ * cover that withdrawal by itself. Mysten redeems address balance for the whole
386
+ * amount in that case, and gas payment only has to pay the gas budget.
387
+ */
388
+ declare function gasPaymentCover(tx: Transaction, addressBalance: bigint, gasBudget: bigint): bigint;
370
389
  /** Object IDs a built payload already pins (inputs + gas payment). */
371
390
  declare function collectPayloadObjectIds(payload: string | Uint8Array): string[];
372
391
  /**
@@ -658,4 +677,4 @@ declare function getIntentionContent(intention: unknown): unknown;
658
677
  */
659
678
  declare function transactionFromContent(content: unknown): Transaction;
660
679
 
661
- export { AddressBookSDK, COIN_RESERVATION_MAGIC, COIN_TYPE_ARG_REGEX, Coin, CoinHelper, CreateHelper, type CreateMSafeInfo, DEV_API_URL, DEV_SYNCING_URL, ENV_CONFIGS, Formatter, type FutureIntention, type GasFundingMode, type GasFundingResult, type GasObjectReference, HexToUint8Array, type HistorySendTx, type HistoryVote, type IWallet, InfoTypeEnabledDto, InfoTypeKey, InsufficientGasFundsError, LOCAL_API_URL, LOCAL_SYNCING_URL, MAINNET_RPC_URL, MAX_GAS_PAYMENT_OBJECTS, MSafeAccount, MSafeClient, type MSafeConfig, type MSafeConfigOptions, MSafeEnabledDto, MSafeEnv, MSafeGlobals, type NotificationData, type NotificationInfo, NotificationSubscribeDto, NotificationUpdateDto, PREV_API_URL, PROD_API_URL, type Pagination, type PaymentCoinRef, type PendingTx, type PendingVote, type ProposeIntention, type ReceiveTransaction, SUI_COIN, SignatureVerifier, type SimulateDryRunResponse, type SimulationFailedResult, type SimulationOption, type SimulationResult, type SimulationSuccessResult, TESTNET_RPC_URL, Uint8ArrayToHex, addPrefix, collectPayloadObjectIds, collectQueueExcludeObjectIds, computeGasBudget, createCoinReservationRef, estimateGasBudget, getAllCoins, getIntentionContent, getMSafeConfig, getPublicKeyFromChain, httpUrlToGrpcBaseUrl, isCoinReservationDigest, msafeChainToSuiNetwork, parkedPayloadHex, pickGasPaymentCoins, preferClassicGasPayment, prepareGasFunding, selectGasFunding, stringToBuffer, toSuiTransaction, transactionFromContent };
680
+ export { AddressBookSDK, COIN_RESERVATION_MAGIC, COIN_TYPE_ARG_REGEX, Coin, CoinHelper, CreateHelper, type CreateMSafeInfo, DEV_API_URL, DEV_SYNCING_URL, ENV_CONFIGS, Formatter, type FutureIntention, type GasFundingMode, type GasFundingResult, type GasObjectReference, GasPaymentLimitError, HexToUint8Array, type HistorySendTx, type HistoryVote, type IWallet, InfoTypeEnabledDto, InfoTypeKey, InsufficientGasFundsError, LOCAL_API_URL, LOCAL_SYNCING_URL, MAINNET_RPC_URL, MAX_GAS_PAYMENT_OBJECTS, MSafeAccount, MSafeClient, type MSafeConfig, type MSafeConfigOptions, MSafeEnabledDto, MSafeEnv, MSafeGlobals, type NotificationData, type NotificationInfo, NotificationSubscribeDto, NotificationUpdateDto, PREV_API_URL, PROD_API_URL, type Pagination, type PaymentCoinRef, type PendingTx, type PendingVote, type ProposeIntention, type ReceiveTransaction, SUI_COIN, SignatureVerifier, type SimulateDryRunResponse, type SimulationFailedResult, type SimulationOption, type SimulationResult, type SimulationSuccessResult, TESTNET_RPC_URL, Uint8ArrayToHex, addPrefix, collectPayloadObjectIds, collectQueueExcludeObjectIds, computeGasBudget, createCoinReservationRef, estimateGasBudget, gasCoinWithdrawal, gasPaymentCover, getAllCoins, getIntentionContent, getMSafeConfig, getPublicKeyFromChain, httpUrlToGrpcBaseUrl, isCoinReservationDigest, msafeChainToSuiNetwork, parkedPayloadHex, pickGasPaymentCoins, preferClassicGasPayment, prepareGasFunding, selectGasFunding, stringToBuffer, toSuiTransaction, transactionFromContent };
package/dist/index.js CHANGED
@@ -418,6 +418,8 @@ async function getAllCoins(input) {
418
418
  var GAS_SAFE_OVERHEAD = 1000n;
419
419
  var MAX_GAS_PAYMENT_OBJECTS = 256;
420
420
  var ESTIMATE_GAS_COVER = 1000000000n;
421
+ var MAX_PROBE_GAS_BUDGET = 50000000000n;
422
+ var COIN_WITH_BALANCE_INTENT = "CoinWithBalance";
421
423
  var InsufficientGasFundsError = class extends Error {
422
424
  gasBudget;
423
425
  coinBalance;
@@ -433,22 +435,57 @@ var InsufficientGasFundsError = class extends Error {
433
435
  this.addressBalance = addressBalance;
434
436
  }
435
437
  };
438
+ var GasPaymentLimitError = class extends Error {
439
+ required;
440
+ covered;
441
+ coinCount;
442
+ constructor(required, covered, coinCount) {
443
+ super(gasPaymentLimitMessage(required, covered, coinCount));
444
+ this.name = "GasPaymentLimitError";
445
+ this.required = required;
446
+ this.covered = covered;
447
+ this.coinCount = coinCount;
448
+ }
449
+ };
450
+ function formatSui(mist) {
451
+ const negative = mist < 0n;
452
+ const value = negative ? -mist : mist;
453
+ const whole = value / 1000000000n;
454
+ const fraction = (value % 1000000000n).toString().padStart(9, "0").replace(/0+$/, "");
455
+ const text = fraction.length > 0 ? `${whole}.${fraction}` : `${whole}`;
456
+ return negative ? `-${text}` : text;
457
+ }
458
+ function gasPaymentLimitMessage(required, covered, coinCount) {
459
+ const requiredSui = formatSui(required);
460
+ const coveredSui = formatSui(covered);
461
+ if (coinCount >= MAX_GAS_PAYMENT_OBJECTS) {
462
+ 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.`;
463
+ }
464
+ return `This transfer needs ${requiredSui} SUI from gas coins, but the available SUI coin objects only cover ${coveredSui} SUI.`;
465
+ }
436
466
  function computeGasBudget(gasUsed, gasPrice) {
437
467
  const { computationCost, storageCost } = gasUsed;
438
468
  const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
439
469
  return BigInt(computationCost) + BigInt(storageCost) + safeOverhead;
440
470
  }
471
+ function compareBalanceDescending(a, b) {
472
+ if (a.balance === b.balance) {
473
+ return 0;
474
+ }
475
+ return a.balance < b.balance ? 1 : -1;
476
+ }
441
477
  function pickGasPaymentCoins(coins, cover) {
442
478
  if (cover <= 0n) {
443
479
  return [];
444
480
  }
445
- const sorted = [...coins].filter((coin) => coin.balance > 0n).sort((a, b) => a.balance === b.balance ? 0 : a.balance < b.balance ? 1 : -1);
481
+ const sorted = [...coins].filter((coin) => coin.balance > 0n).sort(compareBalanceDescending);
446
482
  const selected = [];
447
483
  let sum = 0n;
448
- for (const coin of sorted) {
484
+ for (let index = 0; index < sorted.length; index += 1) {
449
485
  if (selected.length >= MAX_GAS_PAYMENT_OBJECTS) {
450
486
  break;
451
487
  }
488
+ const coin = sorted[index];
452
489
  selected.push(coin);
453
490
  sum += coin.balance;
454
491
  if (sum >= cover) {
@@ -457,6 +494,46 @@ function pickGasPaymentCoins(coins, cover) {
457
494
  }
458
495
  return selected;
459
496
  }
497
+ function readBigInt(value) {
498
+ if (typeof value === "bigint") {
499
+ return value;
500
+ }
501
+ if (typeof value === "number" && Number.isFinite(value)) {
502
+ return BigInt(Math.trunc(value));
503
+ }
504
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
505
+ return BigInt(value);
506
+ }
507
+ return 0n;
508
+ }
509
+ function gasCoinWithdrawal(tx) {
510
+ return tx.getData().commands.reduce((sum, command) => {
511
+ if (command.$kind !== "$Intent" || command.$Intent.name !== COIN_WITH_BALANCE_INTENT) {
512
+ return sum;
513
+ }
514
+ const data = command.$Intent.data;
515
+ if (data?.type !== "gas") {
516
+ return sum;
517
+ }
518
+ return sum + readBigInt(data.balance);
519
+ }, 0n);
520
+ }
521
+ function gasPaymentCover(tx, addressBalance, gasBudget) {
522
+ const withdrawal = gasCoinWithdrawal(tx);
523
+ const fromGasCoin = withdrawal > addressBalance ? withdrawal : 0n;
524
+ return gasBudget + fromGasCoin;
525
+ }
526
+ function sumBalances(coins) {
527
+ return coins.reduce((sum, coin) => sum + coin.balance, 0n);
528
+ }
529
+ function pickGasPaymentCoinsForCover(coins, cover) {
530
+ const picked = pickGasPaymentCoins(coins, cover);
531
+ const covered = sumBalances(picked);
532
+ if (cover > 0n && covered < cover) {
533
+ throw new GasPaymentLimitError(cover, covered, picked.length);
534
+ }
535
+ return picked;
536
+ }
460
537
  function collectUsedObjectIds(tx) {
461
538
  const used = tx.getData().inputs.reduce((acc, input) => {
462
539
  const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
@@ -600,6 +677,12 @@ async function selectGasFunding(input) {
600
677
  if (total < gasBudget) {
601
678
  throw new InsufficientGasFundsError(gasBudget, coinBalance, addressBalance);
602
679
  }
680
+ const cover = gasPaymentCover(tx, addressBalance, gasBudget);
681
+ if (cover > gasBudget) {
682
+ const picked = pickGasPaymentCoinsForCover(paymentCoins, cover);
683
+ tx.setGasPayment(toObjectRefs(picked));
684
+ return { mode: "classic", gasBudget, coinBalance, addressBalance };
685
+ }
603
686
  if (paymentCoins.length > 0 && coinBalance >= gasBudget) {
604
687
  tx.setGasPayment(toObjectRefs(pickGasPaymentCoins(paymentCoins, gasBudget)));
605
688
  return { mode: "classic", gasBudget, coinBalance, addressBalance };
@@ -655,7 +738,18 @@ async function estimateGasBudget(input) {
655
738
  const probe = mustResetGas ? cloneWithClearedGasConfig(tx) : Transaction.from(tx);
656
739
  probe.setSender(owner);
657
740
  probe.setGasPrice(gasPrice);
658
- if (funding.paymentCoins.length > 0) {
741
+ const fromGasCoin = gasPaymentCover(tx, funding.addressBalance, 0n);
742
+ if (fromGasCoin > 0n) {
743
+ const picked = pickGasPaymentCoins(funding.paymentCoins, fromGasCoin + ESTIMATE_GAS_COVER);
744
+ const covered = sumBalances(picked);
745
+ if (covered <= fromGasCoin) {
746
+ throw new GasPaymentLimitError(fromGasCoin, covered, picked.length);
747
+ }
748
+ probe.setGasPayment(toObjectRefs(picked));
749
+ const remainder = covered - fromGasCoin;
750
+ const probeBudget = remainder > MAX_PROBE_GAS_BUDGET ? MAX_PROBE_GAS_BUDGET : remainder;
751
+ probe.setGasBudget(probeBudget);
752
+ } else if (funding.paymentCoins.length > 0) {
659
753
  probe.setGasPayment(toObjectRefs(pickGasPaymentCoins(funding.paymentCoins, ESTIMATE_GAS_COVER)));
660
754
  }
661
755
  let built;
@@ -2121,6 +2215,7 @@ export {
2121
2215
  DEV_SYNCING_URL,
2122
2216
  ENV_CONFIGS,
2123
2217
  Formatter,
2218
+ GasPaymentLimitError,
2124
2219
  HexToUint8Array,
2125
2220
  InfoTypeEnabledDto,
2126
2221
  InfoTypeKey,
@@ -2148,6 +2243,8 @@ export {
2148
2243
  computeGasBudget,
2149
2244
  createCoinReservationRef,
2150
2245
  estimateGasBudget,
2246
+ gasCoinWithdrawal,
2247
+ gasPaymentCover,
2151
2248
  getAllCoins,
2152
2249
  getIntentionContent,
2153
2250
  getMSafeConfig,