@t2000/sdk 4.2.0 → 4.2.2

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
@@ -1,12 +1,12 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
2
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
3
3
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
4
- import { Q as TxMetadata, o as SafeguardConfig, T as T2000Error, H as TransactionSigner, v as SupportedAsset, _ as ZkLoginProof, ax as SendableAsset } from './types-CIclvR39.cjs';
5
- export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, ay as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, az as DEFAULT_GRPC_URL, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, aA as GASLESS_MIN_STABLE_AMOUNT, aB as GASLESS_STABLE_TYPES, G as GAS_RESERVE_MIN, I as IKA_TYPE, K as KNOWN_TARGETS, h as KeypairSigner, L as LABEL_PATTERNS, i as LOFI_TYPE, M as MANIFEST_TYPE, j as MIST_PER_SUI, N as NAVX_TYPE, aC as OPERATION_ASSETS, O as OUTBOUND_OPS, aD as Operation, P as ProtocolFeeInfo, aE as SAVEABLE_ASSETS, S as SAVE_FEE_BPS, aF as SENDABLE_ASSETS, k as STABLE_ASSETS, l as SUI_DECIMALS, m as SUI_TYPE, n as SUPPORTED_ASSETS, p as SafeguardError, q as SafeguardErrorDetails, r as SafeguardRule, aG as SaveableAsset, s as SimulationResult, t as StableAsset, u as SuiRpcTxBlock, w as T2000ErrorCode, x as T2000ErrorData, y as T2000_OVERLAY_FEE_WALLET, z as TOKEN_MAP, J as TxDirection, U as USDC_DECIMALS, R as USDC_TYPE, V as USDE_TYPE, W as USDSUI_TYPE, X as USDT_TYPE, Y as WAL_TYPE, Z as WBTC_TYPE, $ as ZkLoginSigner, a0 as addFeeTransfer, aH as assertAllowedAsset, a1 as calculateFee, a2 as classifyAction, a3 as classifyLabel, a4 as classifyTransaction, aI as createSuiClient, a5 as extractAllUserLegs, a6 as extractTransferDetails, a7 as extractTxCommands, a8 as extractTxSender, a9 as fallbackLabel, aa as formatAssetAmount, ab as formatSui, ac as formatUsd, aJ as getCoinMeta, ad as getDecimals, ae as getDecimalsForCoinType, aK as getSuiClient, aL as getSuiGrpcClient, af as getTier, aM as isAllowedAsset, aN as isInRegistry, ag as isSupported, ah as isTier1, ai as isTier2, aj as mapMoveAbortCode, ak as mapWalletError, al as mistToSui, aO as normalizeAsset, aP as normalizeCoinType, am as parseSuiRpcTx, aQ as queryHistory, aR as queryTransaction, an as rawToStable, ao as rawToUsdc, ap as refineLendingLabel, aq as resolveSymbol, ar as resolveTokenType, aS as simulateTransaction, as as stableToRaw, at as suiToMist, aT as throwIfSimulationFailed, au as truncateAddress, av as usdcToRaw, aw as validateAddress } from './types-CIclvR39.cjs';
4
+ import { Q as TxMetadata, o as SafeguardConfig, T as T2000Error, H as TransactionSigner, v as SupportedAsset, _ as ZkLoginProof, ax as SendableAsset } from './types-BjvEpkwT.cjs';
5
+ export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, ay as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, az as DEFAULT_GRPC_URL, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, aA as GASLESS_MIN_STABLE_AMOUNT, aB as GASLESS_STABLE_TYPES, G as GAS_RESERVE_MIN, I as IKA_TYPE, K as KNOWN_TARGETS, h as KeypairSigner, L as LABEL_PATTERNS, i as LOFI_TYPE, M as MANIFEST_TYPE, j as MIST_PER_SUI, N as NAVX_TYPE, aC as OPERATION_ASSETS, O as OUTBOUND_OPS, aD as Operation, P as ProtocolFeeInfo, aE as SAVEABLE_ASSETS, S as SAVE_FEE_BPS, aF as SENDABLE_ASSETS, k as STABLE_ASSETS, l as SUI_DECIMALS, m as SUI_TYPE, n as SUPPORTED_ASSETS, p as SafeguardError, q as SafeguardErrorDetails, r as SafeguardRule, aG as SaveableAsset, s as SimulationResult, t as StableAsset, u as SuiRpcTxBlock, w as T2000ErrorCode, x as T2000ErrorData, y as T2000_OVERLAY_FEE_WALLET, z as TOKEN_MAP, J as TxDirection, U as USDC_DECIMALS, R as USDC_TYPE, V as USDE_TYPE, W as USDSUI_TYPE, X as USDT_TYPE, Y as WAL_TYPE, Z as WBTC_TYPE, $ as ZkLoginSigner, a0 as addFeeTransfer, aH as assertAllowedAsset, a1 as calculateFee, a2 as classifyAction, a3 as classifyLabel, a4 as classifyTransaction, aI as createSuiClient, a5 as extractAllUserLegs, a6 as extractTransferDetails, a7 as extractTxCommands, a8 as extractTxSender, a9 as fallbackLabel, aa as formatAssetAmount, ab as formatSui, ac as formatUsd, aJ as getCoinMeta, ad as getDecimals, ae as getDecimalsForCoinType, aK as getSuiClient, aL as getSuiGrpcClient, af as getTier, aM as isAllowedAsset, aN as isInRegistry, ag as isSupported, ah as isTier1, ai as isTier2, aj as mapMoveAbortCode, ak as mapWalletError, al as mistToSui, aO as normalizeAsset, aP as normalizeCoinType, am as parseSuiRpcTx, aQ as queryHistory, aR as queryTransaction, an as rawToStable, ao as rawToUsdc, ap as refineLendingLabel, aq as resolveSymbol, ar as resolveTokenType, aS as simulateTransaction, as as stableToRaw, at as suiToMist, aT as throwIfSimulationFailed, au as truncateAddress, av as usdcToRaw, aw as validateAddress } from './types-BjvEpkwT.cjs';
6
6
  import { L as LendingAdapter, a as LendingRates, P as PendingReward$1 } from './descriptors-DqIb4AnV.cjs';
7
7
  export { A as AdapterCapability, b as AdapterPositions, c as AdapterTxResult, H as HealthInfo, d as ProtocolDescriptor, e as allDescriptors, n as naviDescriptor } from './descriptors-DqIb4AnV.cjs';
8
- import { n as T2000Options, P as PayOptions, d as PayResult, o as SwapResult, p as SwapQuoteResult, i as SendResult, B as BalanceResponse, k as TransactionRecord, D as DepositInfo, q as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, h as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, e as PendingReward, C as ClaimRewardsResult, r as CompoundRewardsResult, g as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, c as OverlayFeeConfig, j as SwapRouteResult, s as FinancialSummary } from './types-HJGbXJ37.cjs';
9
- export { A as AssetRates, G as GasReserve, t as HFAlertLevel, O as OVERLAY_FEE_RATE, f as PositionEntry, u as SerializedCetusRoute, v as SerializedCetusRoutePath, w as SerializedRouterDataV3, T as TransactionLeg, x as addSwapToTx, l as buildSwapTx, y as deserializeCetusRoute, m as findSwapRoute, z as isCetusRouteFresh, I as serializeCetusRoute, J as verifyCetusRouteCoinMatch } from './types-HJGbXJ37.cjs';
8
+ import { n as T2000Options, P as PayOptions, d as PayResult, o as SwapResult, p as SwapQuoteResult, i as SendResult, B as BalanceResponse, k as TransactionRecord, D as DepositInfo, q as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, h as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, e as PendingReward, C as ClaimRewardsResult, r as CompoundRewardsResult, g as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, c as OverlayFeeConfig, s as SponsoredCoinMergeCache, j as SwapRouteResult, t as FinancialSummary } from './types-Ch0zVUpC.cjs';
9
+ export { A as AssetRates, u as CoinPage, G as GasReserve, v as HFAlertLevel, O as OVERLAY_FEE_RATE, f as PositionEntry, w as SelectAndSplitResult, x as SerializedCetusRoute, y as SerializedCetusRoutePath, z as SerializedRouterDataV3, T as TransactionLeg, I as addSwapToTx, l as buildSwapTx, J as deserializeCetusRoute, K as fetchAllCoins, m as findSwapRoute, L as isCetusRouteFresh, N as selectAndSplitCoin, Q as selectSuiCoin, U as serializeCetusRoute, V as verifyCetusRouteCoinMatch } from './types-Ch0zVUpC.cjs';
10
10
  import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
11
11
  import { SuinsClient } from '@mysten/suins';
12
12
  export { NaviAdapter, ProtocolRegistry } from './adapters/index.cjs';
@@ -497,114 +497,6 @@ declare function buildSendTx({ client, address, to, amount, asset, }: {
497
497
  */
498
498
  declare function addSendToTx(tx: Transaction, coin: TransactionObjectArgument, recipient: string): void;
499
499
 
500
- /**
501
- * Wallet-side coin selection helpers — single source of truth for the
502
- * "produce a `Coin<T>` argument holding `amount` raw units of `coinType`,
503
- * owned by `address`" pattern. Used by every wallet-mode appender that
504
- * needs a coin input (save, send, swap, repay, stake, etc.).
505
- *
506
- * **2026-05-22 — address-balance migration.** Sui mainnet's address-balance
507
- * feature ships funds account-style instead of as discrete `Coin<T>` objects.
508
- * After a payment via `0x2::balance::send_funds`, the leftover lands in the
509
- * sender's address balance with a synthetic "coin reservation" representing
510
- * the deposit. `client.getCoins()` correctly filters those reservations out
511
- * (they aren't real owned objects), so the old fetch+merge+split pattern
512
- * threw `INSUFFICIENT_BALANCE` for users whose stables had drifted into
513
- * address balance — even when `getBalance().totalBalance` showed plenty.
514
- *
515
- * The fix is structural: hand the work to `coinWithBalance({ type, balance })`
516
- * from `@mysten/sui/transactions`. Its build-time resolver inspects coins +
517
- * address balance together (`getBalance` + `listCoins`), then emits the
518
- * right shape — direct `redeem_funds` from address balance when AB ≥
519
- * required, or merge-and-split across coins + AB withdrawal when not. Multi
520
- * intents per coin type get batched into a single merge in one PTB, so the
521
- * old per-PTB merge cache is no longer needed.
522
- *
523
- * Pre-flight uses `client.getBalance().totalBalance` (sums coins + AB)
524
- * instead of summing the paginated `getCoins` page. That's the OTHER half
525
- * of the migration — the legacy path could see `0` from `getCoins` and
526
- * mistakenly throw before `coinWithBalance` ever ran.
527
- */
528
-
529
- interface CoinPage {
530
- ids: string[];
531
- totalBalance: bigint;
532
- }
533
- /**
534
- * Sum every coin of `coinType` owned by `owner`, INCLUDING address balance.
535
- * Returns the IDs of any discrete coin objects that exist (callers
536
- * occasionally need this for non-`coinWithBalance` paths, e.g. SUIns name
537
- * registration which expects raw object IDs).
538
- *
539
- * Pre-2026-05-22 this function paginated `client.getCoins` and summed
540
- * the page balances. That misses address-balance funds (the SDK filters
541
- * them out of `getCoins` for back-compat). The new implementation calls
542
- * `getBalance` for the canonical total and `getCoins` for the optional
543
- * ID list — both round-trips, but they happen in parallel.
544
- */
545
- declare function fetchAllCoins(client: SuiJsonRpcClient, owner: string, coinType: string): Promise<CoinPage>;
546
- interface SelectAndSplitResult {
547
- /** TransactionObjectArgument for a coin holding `effectiveAmount` raw units. */
548
- coin: TransactionObjectArgument;
549
- /** Actual raw amount the returned coin holds. May be < requested if `swapAll` is true. */
550
- effectiveAmount: bigint;
551
- /** True iff the request consumed the entire wallet balance (no split needed). */
552
- swapAll: boolean;
553
- }
554
- /**
555
- * Wallet-mode coin selection prelude. Pre-flights against
556
- * `getBalance().totalBalance` (coins + address balance combined), then
557
- * returns a `coinWithBalance({ type, balance })` argument that the
558
- * `@mysten/sui` resolver fulfills at build time.
559
- *
560
- * Throws `T2000Error` (`INSUFFICIENT_BALANCE`) when:
561
- * - `amount` is bigger than the total balance AND the caller did NOT
562
- * opt into `swapAll: true` clipping.
563
- * - `amount === 'all'` AND total balance is zero.
564
- *
565
- * @param tx — PTB to register the `coinWithBalance` intent against.
566
- * @param client — Sui RPC client for the pre-flight `getBalance` lookup.
567
- * @param owner — wallet address whose coins to source from.
568
- * @param coinType — fully-qualified Sui coin type (e.g. `"0x...::usdc::USDC"`).
569
- * @param amount — raw amount to source (in MIST / smallest unit). Pass
570
- * `'all'` to consume the entire balance.
571
- * @param options.allowSwapAll — if true (default), `amount` >= totalBalance
572
- * auto-clips to total. If false, throws when the request would over-consume.
573
- * @param options.sponsoredContext — when true, source ONLY from discrete coin
574
- * objects (never the address balance). See the long note below — this exists
575
- * because Enoki's gas station can't yet deserialize a `TransactionData` that
576
- * contains the address-balance `FundsWithdrawal` reservation that
577
- * `coinWithBalance` emits. Self-funded callers leave this false: the fullnode
578
- * handles `FundsWithdrawal` fine, so the address-balance path is preferred
579
- * (it can reach funds that aren't held as coin objects).
580
- *
581
- * @returns
582
- * - `coin` — `TransactionObjectArgument` ready for downstream consumption.
583
- * - `effectiveAmount` — the raw amount the returned coin holds (handles
584
- * swapAll clipping).
585
- * - `swapAll` — true iff the entire balance was consumed.
586
- */
587
- declare function selectAndSplitCoin(tx: Transaction, client: SuiJsonRpcClient, owner: string, coinType: string, amount: bigint | 'all', options?: {
588
- allowSwapAll?: boolean;
589
- sponsoredContext?: boolean;
590
- }): Promise<SelectAndSplitResult>;
591
- /**
592
- * SUI-specific coin selection. Branches on sponsorship context:
593
- *
594
- * - **Self-funded (`sponsoredContext: false`)** — splits from `tx.gas`
595
- * directly (the user's gas coin IS their SUI). More efficient — no
596
- * `getBalance` RTT.
597
- *
598
- * - **Sponsored (`sponsoredContext: true`)** — sources from the user's
599
- * discrete SUI coin objects (`selectCoinObjectsOnly`). This both (a) avoids
600
- * `tx.gas`, which belongs to the Enoki sponsor — NOT the user — under
601
- * sponsored flows (the original S.260 reason for `useGasCoin: false`), AND
602
- * (b) avoids `coinWithBalance`'s address-balance `FundsWithdrawal`, which
603
- * Enoki's gas station can't deserialize (issue #93). If the user's SUI is
604
- * address-balance-only, it raises `ADDRESS_BALANCE_UNSPONSORABLE`.
605
- */
606
- declare function selectSuiCoin(tx: Transaction, client: SuiJsonRpcClient, owner: string, amountMist: bigint, sponsoredContext: boolean): Promise<SelectAndSplitResult>;
607
-
608
500
  /**
609
501
  * SPEC 7 v0.4 § Layer 0 — Canonical Write Architecture.
610
502
  *
@@ -981,6 +873,16 @@ interface AppenderContext {
981
873
  * the PTB build fails or the on-chain leg reverts.
982
874
  */
983
875
  isOutputConsumed?: boolean;
876
+ /**
877
+ * Per-PTB merge cache for sponsored coin-object sourcing, shared across
878
+ * every appender in a single `composeTx` run. Lets multiple legs that
879
+ * source the same coin type (two SUI swaps, swap USDC + save USDC, etc.)
880
+ * reuse a single merged primary coin instead of each re-fetching +
881
+ * re-merging the same coin objects (the second merge of which references
882
+ * already-consumed coins → Enoki dry-run `ArgumentWithoutValue`). Applies
883
+ * to ALL coin types, not just SUI. See `SponsoredCoinMergeCache` JSDoc.
884
+ */
885
+ coinMergeCache?: SponsoredCoinMergeCache;
984
886
  }
985
887
  /**
986
888
  * [SPEC 13 Phase 1] Appender return shape. Producers populate
@@ -1498,4 +1400,4 @@ declare function fullHandle(label: string): string;
1498
1400
  */
1499
1401
  declare function displayHandle(label: string): string;
1500
1402
 
1501
- export { AUDRIC_PARENT_NAME, AUDRIC_PARENT_NFT_ID, type AppenderContext, BalanceResponse, type BorrowInput, BorrowResult, type BuildAddLeafParams, type BuildHarvestRewardsTxOptions, type BuildRevokeLeafParams, type ClaimRewardsInput, ClaimRewardsResult, type CoinPage, type ComposeTxFeeHookContext, type ComposeTxFeeHooks, type ComposeTxOptions, type ComposeTxResult, CompoundRewardsResult, ContactManager, DepositInfo, EarningsResult, FinancialSummary, type FinancialSummaryOptions, FundStatusResult, HF_CRITICAL_THRESHOLD, HF_WARN_THRESHOLD, type HarvestPlan, type HarvestSkippedLeg, type HarvestSwapLeg, HealthFactorResult, InvalidAddressError, type LabelValidationResult, LendingAdapter, LendingRates, MaxBorrowResult, MaxWithdrawResult, type NormalizedAddress, OverlayFeeConfig, PayOptions, PayResult, PaymentRequest, PendingReward, PositionsResult, RatesResult, type RepayDebtInput, RepayResult, SPONSORED_PYTH_DEPENDENT_PROVIDERS, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SafeguardConfig, SafeguardEnforcer, type SaveDepositInput, SaveResult, type SelectAndSplitResult, SendResult, type SendTransferInput, SendableAsset, type StepPreview, SuinsNotRegisteredError, SuinsRpcError, SupportedAsset, type SwapExecuteInput, SwapQuoteResult, SwapResult, SwapRouteResult, T2000, T2000Error, T2000Options, TransactionRecord, TransactionSigner, TxMetadata, WRITE_APPENDER_REGISTRY, type WithdrawInput, WithdrawResult, type WriteStep, type WriteToolName, ZkLoginProof, addClaimRewardsToTx, addSendToTx, aggregateClaimableRewards, buildAddLeafTx, buildClaimRewardsTx, buildHarvestRewardsTx, buildRevokeLeafTx, buildSendTx, composeTx, deriveAllowedAddressesFromPtb, displayHandle, exportPrivateKey, fetchAllCoins, fullHandle, generateKeypair, getAddress, getFinancialSummary, getPendingRewards, getPendingRewardsByAddress, getRates, getSponsoredSwapProviders, getSwapQuote, keypairFromPrivateKey, loadKey, looksLikeSuiNs, normalizeAddressInput, resolveAddressToSuinsViaRpc, resolveSuinsViaRpc, saveBech32, saveKey, selectAndSplitCoin, selectSuiCoin, validateLabel, walletExists };
1403
+ export { AUDRIC_PARENT_NAME, AUDRIC_PARENT_NFT_ID, type AppenderContext, BalanceResponse, type BorrowInput, BorrowResult, type BuildAddLeafParams, type BuildHarvestRewardsTxOptions, type BuildRevokeLeafParams, type ClaimRewardsInput, ClaimRewardsResult, type ComposeTxFeeHookContext, type ComposeTxFeeHooks, type ComposeTxOptions, type ComposeTxResult, CompoundRewardsResult, ContactManager, DepositInfo, EarningsResult, FinancialSummary, type FinancialSummaryOptions, FundStatusResult, HF_CRITICAL_THRESHOLD, HF_WARN_THRESHOLD, type HarvestPlan, type HarvestSkippedLeg, type HarvestSwapLeg, HealthFactorResult, InvalidAddressError, type LabelValidationResult, LendingAdapter, LendingRates, MaxBorrowResult, MaxWithdrawResult, type NormalizedAddress, OverlayFeeConfig, PayOptions, PayResult, PaymentRequest, PendingReward, PositionsResult, RatesResult, type RepayDebtInput, RepayResult, SPONSORED_PYTH_DEPENDENT_PROVIDERS, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SafeguardConfig, SafeguardEnforcer, type SaveDepositInput, SaveResult, SendResult, type SendTransferInput, SendableAsset, type StepPreview, SuinsNotRegisteredError, SuinsRpcError, SupportedAsset, type SwapExecuteInput, SwapQuoteResult, SwapResult, SwapRouteResult, T2000, T2000Error, T2000Options, TransactionRecord, TransactionSigner, TxMetadata, WRITE_APPENDER_REGISTRY, type WithdrawInput, WithdrawResult, type WriteStep, type WriteToolName, ZkLoginProof, addClaimRewardsToTx, addSendToTx, aggregateClaimableRewards, buildAddLeafTx, buildClaimRewardsTx, buildHarvestRewardsTx, buildRevokeLeafTx, buildSendTx, composeTx, deriveAllowedAddressesFromPtb, displayHandle, exportPrivateKey, fullHandle, generateKeypair, getAddress, getFinancialSummary, getPendingRewards, getPendingRewardsByAddress, getRates, getSponsoredSwapProviders, getSwapQuote, keypairFromPrivateKey, loadKey, looksLikeSuiNs, normalizeAddressInput, resolveAddressToSuinsViaRpc, resolveSuinsViaRpc, saveBech32, saveKey, validateLabel, walletExists };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
2
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
3
3
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
4
- import { Q as TxMetadata, o as SafeguardConfig, T as T2000Error, H as TransactionSigner, v as SupportedAsset, _ as ZkLoginProof, ax as SendableAsset } from './types-B63ogqZZ.js';
5
- export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, ay as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, az as DEFAULT_GRPC_URL, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, aA as GASLESS_MIN_STABLE_AMOUNT, aB as GASLESS_STABLE_TYPES, G as GAS_RESERVE_MIN, I as IKA_TYPE, K as KNOWN_TARGETS, h as KeypairSigner, L as LABEL_PATTERNS, i as LOFI_TYPE, M as MANIFEST_TYPE, j as MIST_PER_SUI, N as NAVX_TYPE, aC as OPERATION_ASSETS, O as OUTBOUND_OPS, aD as Operation, P as ProtocolFeeInfo, aE as SAVEABLE_ASSETS, S as SAVE_FEE_BPS, aF as SENDABLE_ASSETS, k as STABLE_ASSETS, l as SUI_DECIMALS, m as SUI_TYPE, n as SUPPORTED_ASSETS, p as SafeguardError, q as SafeguardErrorDetails, r as SafeguardRule, aG as SaveableAsset, s as SimulationResult, t as StableAsset, u as SuiRpcTxBlock, w as T2000ErrorCode, x as T2000ErrorData, y as T2000_OVERLAY_FEE_WALLET, z as TOKEN_MAP, J as TxDirection, U as USDC_DECIMALS, R as USDC_TYPE, V as USDE_TYPE, W as USDSUI_TYPE, X as USDT_TYPE, Y as WAL_TYPE, Z as WBTC_TYPE, $ as ZkLoginSigner, a0 as addFeeTransfer, aH as assertAllowedAsset, a1 as calculateFee, a2 as classifyAction, a3 as classifyLabel, a4 as classifyTransaction, aI as createSuiClient, a5 as extractAllUserLegs, a6 as extractTransferDetails, a7 as extractTxCommands, a8 as extractTxSender, a9 as fallbackLabel, aa as formatAssetAmount, ab as formatSui, ac as formatUsd, aJ as getCoinMeta, ad as getDecimals, ae as getDecimalsForCoinType, aK as getSuiClient, aL as getSuiGrpcClient, af as getTier, aM as isAllowedAsset, aN as isInRegistry, ag as isSupported, ah as isTier1, ai as isTier2, aj as mapMoveAbortCode, ak as mapWalletError, al as mistToSui, aO as normalizeAsset, aP as normalizeCoinType, am as parseSuiRpcTx, aQ as queryHistory, aR as queryTransaction, an as rawToStable, ao as rawToUsdc, ap as refineLendingLabel, aq as resolveSymbol, ar as resolveTokenType, aS as simulateTransaction, as as stableToRaw, at as suiToMist, aT as throwIfSimulationFailed, au as truncateAddress, av as usdcToRaw, aw as validateAddress } from './types-B63ogqZZ.js';
4
+ import { Q as TxMetadata, o as SafeguardConfig, T as T2000Error, H as TransactionSigner, v as SupportedAsset, _ as ZkLoginProof, ax as SendableAsset } from './types-CElCPOFA.js';
5
+ export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, ay as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, az as DEFAULT_GRPC_URL, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, aA as GASLESS_MIN_STABLE_AMOUNT, aB as GASLESS_STABLE_TYPES, G as GAS_RESERVE_MIN, I as IKA_TYPE, K as KNOWN_TARGETS, h as KeypairSigner, L as LABEL_PATTERNS, i as LOFI_TYPE, M as MANIFEST_TYPE, j as MIST_PER_SUI, N as NAVX_TYPE, aC as OPERATION_ASSETS, O as OUTBOUND_OPS, aD as Operation, P as ProtocolFeeInfo, aE as SAVEABLE_ASSETS, S as SAVE_FEE_BPS, aF as SENDABLE_ASSETS, k as STABLE_ASSETS, l as SUI_DECIMALS, m as SUI_TYPE, n as SUPPORTED_ASSETS, p as SafeguardError, q as SafeguardErrorDetails, r as SafeguardRule, aG as SaveableAsset, s as SimulationResult, t as StableAsset, u as SuiRpcTxBlock, w as T2000ErrorCode, x as T2000ErrorData, y as T2000_OVERLAY_FEE_WALLET, z as TOKEN_MAP, J as TxDirection, U as USDC_DECIMALS, R as USDC_TYPE, V as USDE_TYPE, W as USDSUI_TYPE, X as USDT_TYPE, Y as WAL_TYPE, Z as WBTC_TYPE, $ as ZkLoginSigner, a0 as addFeeTransfer, aH as assertAllowedAsset, a1 as calculateFee, a2 as classifyAction, a3 as classifyLabel, a4 as classifyTransaction, aI as createSuiClient, a5 as extractAllUserLegs, a6 as extractTransferDetails, a7 as extractTxCommands, a8 as extractTxSender, a9 as fallbackLabel, aa as formatAssetAmount, ab as formatSui, ac as formatUsd, aJ as getCoinMeta, ad as getDecimals, ae as getDecimalsForCoinType, aK as getSuiClient, aL as getSuiGrpcClient, af as getTier, aM as isAllowedAsset, aN as isInRegistry, ag as isSupported, ah as isTier1, ai as isTier2, aj as mapMoveAbortCode, ak as mapWalletError, al as mistToSui, aO as normalizeAsset, aP as normalizeCoinType, am as parseSuiRpcTx, aQ as queryHistory, aR as queryTransaction, an as rawToStable, ao as rawToUsdc, ap as refineLendingLabel, aq as resolveSymbol, ar as resolveTokenType, aS as simulateTransaction, as as stableToRaw, at as suiToMist, aT as throwIfSimulationFailed, au as truncateAddress, av as usdcToRaw, aw as validateAddress } from './types-CElCPOFA.js';
6
6
  import { L as LendingAdapter, a as LendingRates, P as PendingReward$1 } from './descriptors-DqIb4AnV.js';
7
7
  export { A as AdapterCapability, b as AdapterPositions, c as AdapterTxResult, H as HealthInfo, d as ProtocolDescriptor, e as allDescriptors, n as naviDescriptor } from './descriptors-DqIb4AnV.js';
8
- import { n as T2000Options, P as PayOptions, d as PayResult, o as SwapResult, p as SwapQuoteResult, i as SendResult, B as BalanceResponse, k as TransactionRecord, D as DepositInfo, q as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, h as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, e as PendingReward, C as ClaimRewardsResult, r as CompoundRewardsResult, g as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, c as OverlayFeeConfig, j as SwapRouteResult, s as FinancialSummary } from './types-HJGbXJ37.js';
9
- export { A as AssetRates, G as GasReserve, t as HFAlertLevel, O as OVERLAY_FEE_RATE, f as PositionEntry, u as SerializedCetusRoute, v as SerializedCetusRoutePath, w as SerializedRouterDataV3, T as TransactionLeg, x as addSwapToTx, l as buildSwapTx, y as deserializeCetusRoute, m as findSwapRoute, z as isCetusRouteFresh, I as serializeCetusRoute, J as verifyCetusRouteCoinMatch } from './types-HJGbXJ37.js';
8
+ import { n as T2000Options, P as PayOptions, d as PayResult, o as SwapResult, p as SwapQuoteResult, i as SendResult, B as BalanceResponse, k as TransactionRecord, D as DepositInfo, q as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, h as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, e as PendingReward, C as ClaimRewardsResult, r as CompoundRewardsResult, g as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, c as OverlayFeeConfig, s as SponsoredCoinMergeCache, j as SwapRouteResult, t as FinancialSummary } from './types-Ch0zVUpC.js';
9
+ export { A as AssetRates, u as CoinPage, G as GasReserve, v as HFAlertLevel, O as OVERLAY_FEE_RATE, f as PositionEntry, w as SelectAndSplitResult, x as SerializedCetusRoute, y as SerializedCetusRoutePath, z as SerializedRouterDataV3, T as TransactionLeg, I as addSwapToTx, l as buildSwapTx, J as deserializeCetusRoute, K as fetchAllCoins, m as findSwapRoute, L as isCetusRouteFresh, N as selectAndSplitCoin, Q as selectSuiCoin, U as serializeCetusRoute, V as verifyCetusRouteCoinMatch } from './types-Ch0zVUpC.js';
10
10
  import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
11
11
  import { SuinsClient } from '@mysten/suins';
12
12
  export { NaviAdapter, ProtocolRegistry } from './adapters/index.js';
@@ -497,114 +497,6 @@ declare function buildSendTx({ client, address, to, amount, asset, }: {
497
497
  */
498
498
  declare function addSendToTx(tx: Transaction, coin: TransactionObjectArgument, recipient: string): void;
499
499
 
500
- /**
501
- * Wallet-side coin selection helpers — single source of truth for the
502
- * "produce a `Coin<T>` argument holding `amount` raw units of `coinType`,
503
- * owned by `address`" pattern. Used by every wallet-mode appender that
504
- * needs a coin input (save, send, swap, repay, stake, etc.).
505
- *
506
- * **2026-05-22 — address-balance migration.** Sui mainnet's address-balance
507
- * feature ships funds account-style instead of as discrete `Coin<T>` objects.
508
- * After a payment via `0x2::balance::send_funds`, the leftover lands in the
509
- * sender's address balance with a synthetic "coin reservation" representing
510
- * the deposit. `client.getCoins()` correctly filters those reservations out
511
- * (they aren't real owned objects), so the old fetch+merge+split pattern
512
- * threw `INSUFFICIENT_BALANCE` for users whose stables had drifted into
513
- * address balance — even when `getBalance().totalBalance` showed plenty.
514
- *
515
- * The fix is structural: hand the work to `coinWithBalance({ type, balance })`
516
- * from `@mysten/sui/transactions`. Its build-time resolver inspects coins +
517
- * address balance together (`getBalance` + `listCoins`), then emits the
518
- * right shape — direct `redeem_funds` from address balance when AB ≥
519
- * required, or merge-and-split across coins + AB withdrawal when not. Multi
520
- * intents per coin type get batched into a single merge in one PTB, so the
521
- * old per-PTB merge cache is no longer needed.
522
- *
523
- * Pre-flight uses `client.getBalance().totalBalance` (sums coins + AB)
524
- * instead of summing the paginated `getCoins` page. That's the OTHER half
525
- * of the migration — the legacy path could see `0` from `getCoins` and
526
- * mistakenly throw before `coinWithBalance` ever ran.
527
- */
528
-
529
- interface CoinPage {
530
- ids: string[];
531
- totalBalance: bigint;
532
- }
533
- /**
534
- * Sum every coin of `coinType` owned by `owner`, INCLUDING address balance.
535
- * Returns the IDs of any discrete coin objects that exist (callers
536
- * occasionally need this for non-`coinWithBalance` paths, e.g. SUIns name
537
- * registration which expects raw object IDs).
538
- *
539
- * Pre-2026-05-22 this function paginated `client.getCoins` and summed
540
- * the page balances. That misses address-balance funds (the SDK filters
541
- * them out of `getCoins` for back-compat). The new implementation calls
542
- * `getBalance` for the canonical total and `getCoins` for the optional
543
- * ID list — both round-trips, but they happen in parallel.
544
- */
545
- declare function fetchAllCoins(client: SuiJsonRpcClient, owner: string, coinType: string): Promise<CoinPage>;
546
- interface SelectAndSplitResult {
547
- /** TransactionObjectArgument for a coin holding `effectiveAmount` raw units. */
548
- coin: TransactionObjectArgument;
549
- /** Actual raw amount the returned coin holds. May be < requested if `swapAll` is true. */
550
- effectiveAmount: bigint;
551
- /** True iff the request consumed the entire wallet balance (no split needed). */
552
- swapAll: boolean;
553
- }
554
- /**
555
- * Wallet-mode coin selection prelude. Pre-flights against
556
- * `getBalance().totalBalance` (coins + address balance combined), then
557
- * returns a `coinWithBalance({ type, balance })` argument that the
558
- * `@mysten/sui` resolver fulfills at build time.
559
- *
560
- * Throws `T2000Error` (`INSUFFICIENT_BALANCE`) when:
561
- * - `amount` is bigger than the total balance AND the caller did NOT
562
- * opt into `swapAll: true` clipping.
563
- * - `amount === 'all'` AND total balance is zero.
564
- *
565
- * @param tx — PTB to register the `coinWithBalance` intent against.
566
- * @param client — Sui RPC client for the pre-flight `getBalance` lookup.
567
- * @param owner — wallet address whose coins to source from.
568
- * @param coinType — fully-qualified Sui coin type (e.g. `"0x...::usdc::USDC"`).
569
- * @param amount — raw amount to source (in MIST / smallest unit). Pass
570
- * `'all'` to consume the entire balance.
571
- * @param options.allowSwapAll — if true (default), `amount` >= totalBalance
572
- * auto-clips to total. If false, throws when the request would over-consume.
573
- * @param options.sponsoredContext — when true, source ONLY from discrete coin
574
- * objects (never the address balance). See the long note below — this exists
575
- * because Enoki's gas station can't yet deserialize a `TransactionData` that
576
- * contains the address-balance `FundsWithdrawal` reservation that
577
- * `coinWithBalance` emits. Self-funded callers leave this false: the fullnode
578
- * handles `FundsWithdrawal` fine, so the address-balance path is preferred
579
- * (it can reach funds that aren't held as coin objects).
580
- *
581
- * @returns
582
- * - `coin` — `TransactionObjectArgument` ready for downstream consumption.
583
- * - `effectiveAmount` — the raw amount the returned coin holds (handles
584
- * swapAll clipping).
585
- * - `swapAll` — true iff the entire balance was consumed.
586
- */
587
- declare function selectAndSplitCoin(tx: Transaction, client: SuiJsonRpcClient, owner: string, coinType: string, amount: bigint | 'all', options?: {
588
- allowSwapAll?: boolean;
589
- sponsoredContext?: boolean;
590
- }): Promise<SelectAndSplitResult>;
591
- /**
592
- * SUI-specific coin selection. Branches on sponsorship context:
593
- *
594
- * - **Self-funded (`sponsoredContext: false`)** — splits from `tx.gas`
595
- * directly (the user's gas coin IS their SUI). More efficient — no
596
- * `getBalance` RTT.
597
- *
598
- * - **Sponsored (`sponsoredContext: true`)** — sources from the user's
599
- * discrete SUI coin objects (`selectCoinObjectsOnly`). This both (a) avoids
600
- * `tx.gas`, which belongs to the Enoki sponsor — NOT the user — under
601
- * sponsored flows (the original S.260 reason for `useGasCoin: false`), AND
602
- * (b) avoids `coinWithBalance`'s address-balance `FundsWithdrawal`, which
603
- * Enoki's gas station can't deserialize (issue #93). If the user's SUI is
604
- * address-balance-only, it raises `ADDRESS_BALANCE_UNSPONSORABLE`.
605
- */
606
- declare function selectSuiCoin(tx: Transaction, client: SuiJsonRpcClient, owner: string, amountMist: bigint, sponsoredContext: boolean): Promise<SelectAndSplitResult>;
607
-
608
500
  /**
609
501
  * SPEC 7 v0.4 § Layer 0 — Canonical Write Architecture.
610
502
  *
@@ -981,6 +873,16 @@ interface AppenderContext {
981
873
  * the PTB build fails or the on-chain leg reverts.
982
874
  */
983
875
  isOutputConsumed?: boolean;
876
+ /**
877
+ * Per-PTB merge cache for sponsored coin-object sourcing, shared across
878
+ * every appender in a single `composeTx` run. Lets multiple legs that
879
+ * source the same coin type (two SUI swaps, swap USDC + save USDC, etc.)
880
+ * reuse a single merged primary coin instead of each re-fetching +
881
+ * re-merging the same coin objects (the second merge of which references
882
+ * already-consumed coins → Enoki dry-run `ArgumentWithoutValue`). Applies
883
+ * to ALL coin types, not just SUI. See `SponsoredCoinMergeCache` JSDoc.
884
+ */
885
+ coinMergeCache?: SponsoredCoinMergeCache;
984
886
  }
985
887
  /**
986
888
  * [SPEC 13 Phase 1] Appender return shape. Producers populate
@@ -1498,4 +1400,4 @@ declare function fullHandle(label: string): string;
1498
1400
  */
1499
1401
  declare function displayHandle(label: string): string;
1500
1402
 
1501
- export { AUDRIC_PARENT_NAME, AUDRIC_PARENT_NFT_ID, type AppenderContext, BalanceResponse, type BorrowInput, BorrowResult, type BuildAddLeafParams, type BuildHarvestRewardsTxOptions, type BuildRevokeLeafParams, type ClaimRewardsInput, ClaimRewardsResult, type CoinPage, type ComposeTxFeeHookContext, type ComposeTxFeeHooks, type ComposeTxOptions, type ComposeTxResult, CompoundRewardsResult, ContactManager, DepositInfo, EarningsResult, FinancialSummary, type FinancialSummaryOptions, FundStatusResult, HF_CRITICAL_THRESHOLD, HF_WARN_THRESHOLD, type HarvestPlan, type HarvestSkippedLeg, type HarvestSwapLeg, HealthFactorResult, InvalidAddressError, type LabelValidationResult, LendingAdapter, LendingRates, MaxBorrowResult, MaxWithdrawResult, type NormalizedAddress, OverlayFeeConfig, PayOptions, PayResult, PaymentRequest, PendingReward, PositionsResult, RatesResult, type RepayDebtInput, RepayResult, SPONSORED_PYTH_DEPENDENT_PROVIDERS, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SafeguardConfig, SafeguardEnforcer, type SaveDepositInput, SaveResult, type SelectAndSplitResult, SendResult, type SendTransferInput, SendableAsset, type StepPreview, SuinsNotRegisteredError, SuinsRpcError, SupportedAsset, type SwapExecuteInput, SwapQuoteResult, SwapResult, SwapRouteResult, T2000, T2000Error, T2000Options, TransactionRecord, TransactionSigner, TxMetadata, WRITE_APPENDER_REGISTRY, type WithdrawInput, WithdrawResult, type WriteStep, type WriteToolName, ZkLoginProof, addClaimRewardsToTx, addSendToTx, aggregateClaimableRewards, buildAddLeafTx, buildClaimRewardsTx, buildHarvestRewardsTx, buildRevokeLeafTx, buildSendTx, composeTx, deriveAllowedAddressesFromPtb, displayHandle, exportPrivateKey, fetchAllCoins, fullHandle, generateKeypair, getAddress, getFinancialSummary, getPendingRewards, getPendingRewardsByAddress, getRates, getSponsoredSwapProviders, getSwapQuote, keypairFromPrivateKey, loadKey, looksLikeSuiNs, normalizeAddressInput, resolveAddressToSuinsViaRpc, resolveSuinsViaRpc, saveBech32, saveKey, selectAndSplitCoin, selectSuiCoin, validateLabel, walletExists };
1403
+ export { AUDRIC_PARENT_NAME, AUDRIC_PARENT_NFT_ID, type AppenderContext, BalanceResponse, type BorrowInput, BorrowResult, type BuildAddLeafParams, type BuildHarvestRewardsTxOptions, type BuildRevokeLeafParams, type ClaimRewardsInput, ClaimRewardsResult, type ComposeTxFeeHookContext, type ComposeTxFeeHooks, type ComposeTxOptions, type ComposeTxResult, CompoundRewardsResult, ContactManager, DepositInfo, EarningsResult, FinancialSummary, type FinancialSummaryOptions, FundStatusResult, HF_CRITICAL_THRESHOLD, HF_WARN_THRESHOLD, type HarvestPlan, type HarvestSkippedLeg, type HarvestSwapLeg, HealthFactorResult, InvalidAddressError, type LabelValidationResult, LendingAdapter, LendingRates, MaxBorrowResult, MaxWithdrawResult, type NormalizedAddress, OverlayFeeConfig, PayOptions, PayResult, PaymentRequest, PendingReward, PositionsResult, RatesResult, type RepayDebtInput, RepayResult, SPONSORED_PYTH_DEPENDENT_PROVIDERS, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SafeguardConfig, SafeguardEnforcer, type SaveDepositInput, SaveResult, SendResult, type SendTransferInput, SendableAsset, type StepPreview, SuinsNotRegisteredError, SuinsRpcError, SupportedAsset, type SwapExecuteInput, SwapQuoteResult, SwapResult, SwapRouteResult, T2000, T2000Error, T2000Options, TransactionRecord, TransactionSigner, TxMetadata, WRITE_APPENDER_REGISTRY, type WithdrawInput, WithdrawResult, type WriteStep, type WriteToolName, ZkLoginProof, addClaimRewardsToTx, addSendToTx, aggregateClaimableRewards, buildAddLeafTx, buildClaimRewardsTx, buildHarvestRewardsTx, buildRevokeLeafTx, buildSendTx, composeTx, deriveAllowedAddressesFromPtb, displayHandle, exportPrivateKey, fullHandle, generateKeypair, getAddress, getFinancialSummary, getPendingRewards, getPendingRewardsByAddress, getRates, getSponsoredSwapProviders, getSwapQuote, keypairFromPrivateKey, loadKey, looksLikeSuiNs, normalizeAddressInput, resolveAddressToSuinsViaRpc, resolveSuinsViaRpc, saveBech32, saveKey, validateLabel, walletExists };
package/dist/index.js CHANGED
@@ -672,7 +672,15 @@ async function fetchAllCoins(client, owner, coinType) {
672
672
  }
673
673
  async function selectAndSplitCoin(tx, client, owner, coinType, amount, options = {}) {
674
674
  if (options.sponsoredContext) {
675
- return selectCoinObjectsOnly(tx, client, owner, coinType, amount, options.allowSwapAll ?? true);
675
+ return selectCoinObjectsOnly(
676
+ tx,
677
+ client,
678
+ owner,
679
+ coinType,
680
+ amount,
681
+ options.allowSwapAll ?? true,
682
+ options.mergeCache
683
+ );
676
684
  }
677
685
  const balanceResp = await client.getBalance({ owner, coinType });
678
686
  const totalBalance = BigInt(balanceResp.totalBalance);
@@ -692,7 +700,23 @@ async function selectAndSplitCoin(tx, client, owner, coinType, amount, options =
692
700
  const coin = coinWithBalance({ type: coinType, balance: effectiveAmount })(tx);
693
701
  return { coin, effectiveAmount, swapAll };
694
702
  }
695
- async function selectCoinObjectsOnly(tx, client, owner, coinType, amount, allowSwapAll) {
703
+ async function selectCoinObjectsOnly(tx, client, owner, coinType, amount, allowSwapAll, mergeCache) {
704
+ const cached = mergeCache?.get(coinType);
705
+ if (cached) {
706
+ const requested2 = amount === "all" ? cached.remaining : amount;
707
+ if (cached.remaining === 0n || requested2 > cached.remaining) {
708
+ throw new T2000Error(
709
+ "ADDRESS_BALANCE_UNSPONSORABLE",
710
+ `Not enough ${coinType} in coin objects to cover all legs of this sponsored bundle. The remaining funds are in your address balance, which sponsored transactions can't access yet.`,
711
+ { remaining: cached.remaining.toString(), requested: requested2.toString(), coinType }
712
+ );
713
+ }
714
+ const swapAll2 = amount === "all" || requested2 >= cached.remaining;
715
+ const effectiveAmount2 = swapAll2 ? cached.remaining : requested2;
716
+ const coin2 = swapAll2 ? cached.primary : tx.splitCoins(cached.primary, [effectiveAmount2])[0];
717
+ cached.remaining -= effectiveAmount2;
718
+ return { coin: coin2, effectiveAmount: effectiveAmount2, swapAll: swapAll2 };
719
+ }
696
720
  const objects = [];
697
721
  let coinObjectTotal = 0n;
698
722
  let cursor;
@@ -731,12 +755,16 @@ async function selectCoinObjectsOnly(tx, client, owner, coinType, amount, allowS
731
755
  );
732
756
  }
733
757
  const coin = swapAll ? primary : tx.splitCoins(primary, [effectiveAmount])[0];
758
+ mergeCache?.set(coinType, {
759
+ primary,
760
+ remaining: coinObjectTotal - effectiveAmount
761
+ });
734
762
  return { coin, effectiveAmount, swapAll };
735
763
  }
736
- async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext) {
764
+ async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext, mergeCache) {
737
765
  if (sponsoredContext) {
738
766
  const { SUI_TYPE: SUI_TYPE2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
739
- return selectCoinObjectsOnly(tx, client, owner, SUI_TYPE2, amountMist, false);
767
+ return selectCoinObjectsOnly(tx, client, owner, SUI_TYPE2, amountMist, false, mergeCache);
740
768
  }
741
769
  const [coin] = tx.splitCoins(tx.gas, [amountMist]);
742
770
  return { coin, effectiveAmount: amountMist, swapAll: false };
@@ -958,14 +986,16 @@ async function addSwapToTx(tx, client, address, input) {
958
986
  client,
959
987
  address,
960
988
  requestedRaw,
961
- input.sponsoredContext ?? false
989
+ input.sponsoredContext ?? false,
990
+ input.coinMergeCache
962
991
  );
963
992
  inputCoin = result.coin;
964
993
  effectiveRaw = result.effectiveAmount;
965
994
  } else {
966
995
  const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
967
996
  const result = await selectAndSplitCoin2(tx, client, address, fromType, requestedRaw, {
968
- sponsoredContext: input.sponsoredContext ?? false
997
+ sponsoredContext: input.sponsoredContext ?? false,
998
+ mergeCache: input.coinMergeCache
969
999
  });
970
1000
  inputCoin = result.coin;
971
1001
  effectiveRaw = result.effectiveAmount;
@@ -7946,7 +7976,8 @@ var WRITE_APPENDER_REGISTRY = {
7946
7976
  effectiveAmount = rawAmount;
7947
7977
  } else {
7948
7978
  const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount, {
7949
- sponsoredContext: ctx.sponsoredContext
7979
+ sponsoredContext: ctx.sponsoredContext,
7980
+ mergeCache: ctx.coinMergeCache
7950
7981
  });
7951
7982
  coin = r.coin;
7952
7983
  effectiveAmount = r.effectiveAmount;
@@ -8020,7 +8051,8 @@ var WRITE_APPENDER_REGISTRY = {
8020
8051
  effectiveAmount = rawAmount;
8021
8052
  } else {
8022
8053
  const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount, {
8023
- sponsoredContext: ctx.sponsoredContext
8054
+ sponsoredContext: ctx.sponsoredContext,
8055
+ mergeCache: ctx.coinMergeCache
8024
8056
  });
8025
8057
  coin = r.coin;
8026
8058
  effectiveAmount = r.effectiveAmount;
@@ -8064,7 +8096,14 @@ var WRITE_APPENDER_REGISTRY = {
8064
8096
  };
8065
8097
  }
8066
8098
  if (asset === "SUI") {
8067
- const result = await selectSuiCoin(tx, ctx.client, ctx.sender, rawAmount, ctx.sponsoredContext);
8099
+ const result = await selectSuiCoin(
8100
+ tx,
8101
+ ctx.client,
8102
+ ctx.sender,
8103
+ rawAmount,
8104
+ ctx.sponsoredContext,
8105
+ ctx.coinMergeCache
8106
+ );
8068
8107
  addSendToTx(tx, result.coin, recipient);
8069
8108
  return {
8070
8109
  preview: {
@@ -8126,7 +8165,8 @@ var WRITE_APPENDER_REGISTRY = {
8126
8165
  providers,
8127
8166
  inputCoin: ctx.chainedCoin,
8128
8167
  precomputedRoute: input.precomputedRoute,
8129
- sponsoredContext: ctx.sponsoredContext
8168
+ sponsoredContext: ctx.sponsoredContext,
8169
+ coinMergeCache: ctx.coinMergeCache
8130
8170
  });
8131
8171
  if (!ctx.isOutputConsumed) {
8132
8172
  tx.transferObjects([result.coin], ctx.sender);
@@ -8229,7 +8269,11 @@ async function composeTx(opts) {
8229
8269
  sender: opts.sender,
8230
8270
  sponsoredContext: opts.sponsoredContext ?? false,
8231
8271
  overlayFee: opts.overlayFee,
8232
- feeHooks: opts.feeHooks
8272
+ feeHooks: opts.feeHooks,
8273
+ // One cache per compose run — shared across all legs (any coin type)
8274
+ // so multi-leg sponsored bundles that source the same coin (two SUI
8275
+ // swaps, swap USDC + save USDC, ...) merge that coin's objects once.
8276
+ coinMergeCache: /* @__PURE__ */ new Map()
8233
8277
  };
8234
8278
  const consumedSteps = /* @__PURE__ */ new Set();
8235
8279
  for (let i = 0; i < opts.steps.length; i++) {