@t2000/sdk 1.12.0 → 1.13.1

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,8 +1,8 @@
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 { V as TxMetadata, q as SafeguardConfig, T as T2000Error, R as TransactionSigner, a1 as ZkLoginProof, x as SupportedAsset, l as OverlayFeeConfig, y as SwapRouteResult } from './types-DXM0Hixp.cjs';
5
- export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, aB as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, 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, k as OVERLAY_FEE_RATE, aD as Operation, P as ProtocolFeeInfo, S as SAVE_FEE_BPS, m as STABLE_ASSETS, n as SUI_DECIMALS, o as SUI_TYPE, p as SUPPORTED_ASSETS, r as SafeguardError, s as SafeguardErrorDetails, t as SafeguardRule, u as SimulationResult, v as StableAsset, w as SuiRpcTxBlock, z as T2000ErrorCode, H as T2000ErrorData, J as T2000_OVERLAY_FEE_WALLET, Q as TOKEN_MAP, U as TxDirection, W as USDC_DECIMALS, X as USDC_TYPE, Y as USDE_TYPE, Z as USDSUI_TYPE, _ as USDT_TYPE, $ as WAL_TYPE, a0 as WBTC_TYPE, a2 as ZkLoginSigner, a3 as addFeeTransfer, aE as addSwapToTx, aF as assertAllowedAsset, a4 as buildSwapTx, a5 as calculateFee, a6 as classifyAction, a7 as classifyLabel, a8 as classifyTransaction, a9 as extractTransferDetails, aa as extractTxCommands, ab as extractTxSender, ac as fallbackLabel, ad as findSwapRoute, ae as formatAssetAmount, af as formatSui, ag as formatUsd, aG as getCoinMeta, ah as getDecimals, ai as getDecimalsForCoinType, aj as getTier, aH as isAllowedAsset, aI as isInRegistry, ak as isSupported, al as isTier1, am as isTier2, an as mapMoveAbortCode, ao as mapWalletError, ap as mistToSui, aJ as normalizeAsset, aK as normalizeCoinType, aq as parseSuiRpcTx, aL as queryHistory, aM as queryTransaction, ar as rawToStable, as as rawToUsdc, at as refineLendingLabel, au as resolveSymbol, av as resolveTokenType, aN as simulateTransaction, aw as stableToRaw, ax as suiToMist, aO as throwIfSimulationFailed, ay as truncateAddress, az as usdcToRaw, aA as validateAddress } from './types-DXM0Hixp.cjs';
4
+ import { V as TxMetadata, q as SafeguardConfig, T as T2000Error, R as TransactionSigner, a1 as ZkLoginProof, x as SupportedAsset, l as OverlayFeeConfig, y as SwapRouteResult } from './types-D03bON1v.cjs';
5
+ export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, aB as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, 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, k as OVERLAY_FEE_RATE, aD as Operation, P as ProtocolFeeInfo, S as SAVE_FEE_BPS, m as STABLE_ASSETS, n as SUI_DECIMALS, o as SUI_TYPE, p as SUPPORTED_ASSETS, r as SafeguardError, s as SafeguardErrorDetails, t as SafeguardRule, u as SimulationResult, v as StableAsset, w as SuiRpcTxBlock, z as T2000ErrorCode, H as T2000ErrorData, J as T2000_OVERLAY_FEE_WALLET, Q as TOKEN_MAP, U as TxDirection, W as USDC_DECIMALS, X as USDC_TYPE, Y as USDE_TYPE, Z as USDSUI_TYPE, _ as USDT_TYPE, $ as WAL_TYPE, a0 as WBTC_TYPE, a2 as ZkLoginSigner, a3 as addFeeTransfer, aE as addSwapToTx, aF as assertAllowedAsset, a4 as buildSwapTx, a5 as calculateFee, a6 as classifyAction, a7 as classifyLabel, a8 as classifyTransaction, a9 as extractTransferDetails, aa as extractTxCommands, ab as extractTxSender, ac as fallbackLabel, ad as findSwapRoute, ae as formatAssetAmount, af as formatSui, ag as formatUsd, aG as getCoinMeta, ah as getDecimals, ai as getDecimalsForCoinType, aj as getTier, aH as isAllowedAsset, aI as isInRegistry, ak as isSupported, al as isTier1, am as isTier2, an as mapMoveAbortCode, ao as mapWalletError, ap as mistToSui, aJ as normalizeAsset, aK as normalizeCoinType, aq as parseSuiRpcTx, aL as queryHistory, aM as queryTransaction, ar as rawToStable, as as rawToUsdc, at as refineLendingLabel, au as resolveSymbol, av as resolveTokenType, aN as simulateTransaction, aw as stableToRaw, ax as suiToMist, aO as throwIfSimulationFailed, ay as truncateAddress, az as usdcToRaw, aA as validateAddress } from './types-D03bON1v.cjs';
6
6
  import { L as LendingAdapter, a as LendingRates, P as PendingReward$1 } from './descriptors-BnbL3xN8.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-BnbL3xN8.cjs';
8
8
  import { i as T2000Options, P as PayOptions, c as PayResult, j as StakeVSuiResult, U as UnstakeVSuiResult, k as SwapResult, l as SwapQuoteResult, h as SendResult, B as BalanceResponse, T as TransactionRecord, D as DepositInfo, m as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, g as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, d as PendingReward, C as ClaimRewardsResult, n as CompoundRewardsResult, f as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, o as FinancialSummary } from './types-jAD-e7Pq.cjs';
@@ -482,10 +482,23 @@ interface VoloUnstakeInput {
482
482
  * Discriminated union mapping `toolName` → `input`. Used to type
483
483
  * `WriteStep` so consumers get autocomplete + compile-time validation
484
484
  * that the input matches the tool.
485
+ *
486
+ * **[SPEC 13 Phase 1] `inputCoinFromStep`** — consumer steps may
487
+ * reference an earlier step's output coin handle by index. When set,
488
+ * `composeTx`'s orchestration loop threads the producer's
489
+ * `outputCoin` into this step's appender as the `inputCoin` arg,
490
+ * bypassing the wallet pre-fetch path. The producer's terminal
491
+ * `tx.transferObjects([coin], sender)` is suppressed automatically so
492
+ * the same handle isn't double-consumed.
493
+ *
494
+ * Producer-only tools (`withdraw`, `borrow`, `claim_rewards`) don't
495
+ * accept the field — they have no input coin slot. Consumer +
496
+ * dual-mode tools all accept it.
485
497
  */
486
498
  type WriteStep = {
487
499
  toolName: 'save_deposit';
488
500
  input: SaveDepositInput;
501
+ inputCoinFromStep?: number;
489
502
  } | {
490
503
  toolName: 'withdraw';
491
504
  input: WithdrawInput;
@@ -495,21 +508,26 @@ type WriteStep = {
495
508
  } | {
496
509
  toolName: 'repay_debt';
497
510
  input: RepayDebtInput;
511
+ inputCoinFromStep?: number;
498
512
  } | {
499
513
  toolName: 'send_transfer';
500
514
  input: SendTransferInput;
515
+ inputCoinFromStep?: number;
501
516
  } | {
502
517
  toolName: 'swap_execute';
503
518
  input: SwapExecuteInput;
519
+ inputCoinFromStep?: number;
504
520
  } | {
505
521
  toolName: 'claim_rewards';
506
522
  input: ClaimRewardsInput;
507
523
  } | {
508
524
  toolName: 'volo_stake';
509
525
  input: VoloStakeInput;
526
+ inputCoinFromStep?: number;
510
527
  } | {
511
528
  toolName: 'volo_unstake';
512
529
  input: VoloUnstakeInput;
530
+ inputCoinFromStep?: number;
513
531
  };
514
532
  interface ComposeTxOptions {
515
533
  sender: string;
@@ -647,8 +665,8 @@ interface ComposeTxResult {
647
665
  }
648
666
  /**
649
667
  * Per-appender context passed into every registry entry. Carries the
650
- * RPC client, sender, sponsorship flag, and optional per-call overlay
651
- * fee config (Cetus swaps).
668
+ * RPC client, sender, sponsorship flag, optional per-call overlay
669
+ * fee config (Cetus swaps), and SPEC 13 Phase 1 chain-mode fields.
652
670
  */
653
671
  interface AppenderContext {
654
672
  client: SuiJsonRpcClient;
@@ -656,8 +674,46 @@ interface AppenderContext {
656
674
  sponsoredContext: boolean;
657
675
  overlayFee?: OverlayFeeConfig;
658
676
  feeHooks?: ComposeTxFeeHooks;
677
+ /**
678
+ * [SPEC 13 Phase 1] When set, the consumer appender consumes this
679
+ * coin handle directly instead of pre-fetching from the wallet via
680
+ * `selectAndSplitCoin` / `selectSuiCoin`. Provided by the
681
+ * orchestration loop when the step has `inputCoinFromStep` set; the
682
+ * loop looks up `priorOutputs[step.inputCoinFromStep]` and threads
683
+ * it through here.
684
+ *
685
+ * In chain mode, the consumer consumes the handle IN FULL — the
686
+ * `input.amount` field is treated as informational (used for preview
687
+ * math). This matches Cetus's `routerSwap`, NAVI's `deposit`/`repay`,
688
+ * and the Sui `transferObjects` semantics: each takes a coin object
689
+ * and consumes its entire balance.
690
+ */
691
+ chainedCoin?: TransactionObjectArgument;
692
+ /**
693
+ * [SPEC 13 Phase 1] True when this step's output coin will be
694
+ * consumed by a downstream step (some later step has
695
+ * `inputCoinFromStep === currentStepIndex`). Producer appenders MUST
696
+ * skip their terminal `tx.transferObjects([coin], ctx.sender)` call
697
+ * when this is true — otherwise the same `TransactionObjectArgument`
698
+ * gets used twice (once by the consumer, once by the transfer) and
699
+ * the PTB build fails or the on-chain leg reverts.
700
+ */
701
+ isOutputConsumed?: boolean;
702
+ }
703
+ /**
704
+ * [SPEC 13 Phase 1] Appender return shape. Producers populate
705
+ * `outputCoin` so the orchestration loop can thread it into a
706
+ * downstream consumer's `chainedCoin`. Terminal consumers
707
+ * (`save_deposit`, `repay_debt`, `send_transfer`) omit it.
708
+ *
709
+ * `swap_execute`, `volo_stake`, and `volo_unstake` are dual-mode —
710
+ * they accept `chainedCoin` AND populate `outputCoin`.
711
+ */
712
+ interface AppenderResult<TPreview extends StepPreview> {
713
+ preview: TPreview;
714
+ outputCoin?: TransactionObjectArgument;
659
715
  }
660
- type AppenderFn<TInput, TPreview extends StepPreview> = (tx: Transaction, input: TInput, ctx: AppenderContext) => Promise<TPreview>;
716
+ type AppenderFn<TInput, TPreview extends StepPreview> = (tx: Transaction, input: TInput, ctx: AppenderContext) => Promise<AppenderResult<TPreview>>;
661
717
  /**
662
718
  * The typed registry. Each entry is a wallet-mode dispatcher that takes
663
719
  * (tx, input, ctx) and returns a per-step preview. Compile-time check
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
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 { V as TxMetadata, q as SafeguardConfig, T as T2000Error, R as TransactionSigner, a1 as ZkLoginProof, x as SupportedAsset, l as OverlayFeeConfig, y as SwapRouteResult } from './types-BuwFyb-U.js';
5
- export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, aB as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, 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, k as OVERLAY_FEE_RATE, aD as Operation, P as ProtocolFeeInfo, S as SAVE_FEE_BPS, m as STABLE_ASSETS, n as SUI_DECIMALS, o as SUI_TYPE, p as SUPPORTED_ASSETS, r as SafeguardError, s as SafeguardErrorDetails, t as SafeguardRule, u as SimulationResult, v as StableAsset, w as SuiRpcTxBlock, z as T2000ErrorCode, H as T2000ErrorData, J as T2000_OVERLAY_FEE_WALLET, Q as TOKEN_MAP, U as TxDirection, W as USDC_DECIMALS, X as USDC_TYPE, Y as USDE_TYPE, Z as USDSUI_TYPE, _ as USDT_TYPE, $ as WAL_TYPE, a0 as WBTC_TYPE, a2 as ZkLoginSigner, a3 as addFeeTransfer, aE as addSwapToTx, aF as assertAllowedAsset, a4 as buildSwapTx, a5 as calculateFee, a6 as classifyAction, a7 as classifyLabel, a8 as classifyTransaction, a9 as extractTransferDetails, aa as extractTxCommands, ab as extractTxSender, ac as fallbackLabel, ad as findSwapRoute, ae as formatAssetAmount, af as formatSui, ag as formatUsd, aG as getCoinMeta, ah as getDecimals, ai as getDecimalsForCoinType, aj as getTier, aH as isAllowedAsset, aI as isInRegistry, ak as isSupported, al as isTier1, am as isTier2, an as mapMoveAbortCode, ao as mapWalletError, ap as mistToSui, aJ as normalizeAsset, aK as normalizeCoinType, aq as parseSuiRpcTx, aL as queryHistory, aM as queryTransaction, ar as rawToStable, as as rawToUsdc, at as refineLendingLabel, au as resolveSymbol, av as resolveTokenType, aN as simulateTransaction, aw as stableToRaw, ax as suiToMist, aO as throwIfSimulationFailed, ay as truncateAddress, az as usdcToRaw, aA as validateAddress } from './types-BuwFyb-U.js';
4
+ import { V as TxMetadata, q as SafeguardConfig, T as T2000Error, R as TransactionSigner, a1 as ZkLoginProof, x as SupportedAsset, l as OverlayFeeConfig, y as SwapRouteResult } from './types-DCb8Xt_q.js';
5
+ export { A as ALL_NAVI_ASSETS, B as BORROW_FEE_BPS, a as BPS_DENOMINATOR, aB as CETUS_USDC_SUI_POOL, C as CLOCK_ID, b as COIN_REGISTRY, c as ClassifyBalanceChange, d as ClassifyResult, e as CoinMeta, D as DEFAULT_NETWORK, f as DEFAULT_SAFEGUARD_CONFIG, E as ETH_TYPE, g as ExtractedTransfer, F as FeeOperation, 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, k as OVERLAY_FEE_RATE, aD as Operation, P as ProtocolFeeInfo, S as SAVE_FEE_BPS, m as STABLE_ASSETS, n as SUI_DECIMALS, o as SUI_TYPE, p as SUPPORTED_ASSETS, r as SafeguardError, s as SafeguardErrorDetails, t as SafeguardRule, u as SimulationResult, v as StableAsset, w as SuiRpcTxBlock, z as T2000ErrorCode, H as T2000ErrorData, J as T2000_OVERLAY_FEE_WALLET, Q as TOKEN_MAP, U as TxDirection, W as USDC_DECIMALS, X as USDC_TYPE, Y as USDE_TYPE, Z as USDSUI_TYPE, _ as USDT_TYPE, $ as WAL_TYPE, a0 as WBTC_TYPE, a2 as ZkLoginSigner, a3 as addFeeTransfer, aE as addSwapToTx, aF as assertAllowedAsset, a4 as buildSwapTx, a5 as calculateFee, a6 as classifyAction, a7 as classifyLabel, a8 as classifyTransaction, a9 as extractTransferDetails, aa as extractTxCommands, ab as extractTxSender, ac as fallbackLabel, ad as findSwapRoute, ae as formatAssetAmount, af as formatSui, ag as formatUsd, aG as getCoinMeta, ah as getDecimals, ai as getDecimalsForCoinType, aj as getTier, aH as isAllowedAsset, aI as isInRegistry, ak as isSupported, al as isTier1, am as isTier2, an as mapMoveAbortCode, ao as mapWalletError, ap as mistToSui, aJ as normalizeAsset, aK as normalizeCoinType, aq as parseSuiRpcTx, aL as queryHistory, aM as queryTransaction, ar as rawToStable, as as rawToUsdc, at as refineLendingLabel, au as resolveSymbol, av as resolveTokenType, aN as simulateTransaction, aw as stableToRaw, ax as suiToMist, aO as throwIfSimulationFailed, ay as truncateAddress, az as usdcToRaw, aA as validateAddress } from './types-DCb8Xt_q.js';
6
6
  import { L as LendingAdapter, a as LendingRates, P as PendingReward$1 } from './descriptors-BnbL3xN8.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-BnbL3xN8.js';
8
8
  import { i as T2000Options, P as PayOptions, c as PayResult, j as StakeVSuiResult, U as UnstakeVSuiResult, k as SwapResult, l as SwapQuoteResult, h as SendResult, B as BalanceResponse, T as TransactionRecord, D as DepositInfo, m as PaymentRequest, S as SaveResult, W as WithdrawResult, b as MaxWithdrawResult, a as BorrowResult, g as RepayResult, M as MaxBorrowResult, H as HealthFactorResult, d as PendingReward, C as ClaimRewardsResult, n as CompoundRewardsResult, f as PositionsResult, R as RatesResult, E as EarningsResult, F as FundStatusResult, o as FinancialSummary } from './types-jAD-e7Pq.js';
@@ -482,10 +482,23 @@ interface VoloUnstakeInput {
482
482
  * Discriminated union mapping `toolName` → `input`. Used to type
483
483
  * `WriteStep` so consumers get autocomplete + compile-time validation
484
484
  * that the input matches the tool.
485
+ *
486
+ * **[SPEC 13 Phase 1] `inputCoinFromStep`** — consumer steps may
487
+ * reference an earlier step's output coin handle by index. When set,
488
+ * `composeTx`'s orchestration loop threads the producer's
489
+ * `outputCoin` into this step's appender as the `inputCoin` arg,
490
+ * bypassing the wallet pre-fetch path. The producer's terminal
491
+ * `tx.transferObjects([coin], sender)` is suppressed automatically so
492
+ * the same handle isn't double-consumed.
493
+ *
494
+ * Producer-only tools (`withdraw`, `borrow`, `claim_rewards`) don't
495
+ * accept the field — they have no input coin slot. Consumer +
496
+ * dual-mode tools all accept it.
485
497
  */
486
498
  type WriteStep = {
487
499
  toolName: 'save_deposit';
488
500
  input: SaveDepositInput;
501
+ inputCoinFromStep?: number;
489
502
  } | {
490
503
  toolName: 'withdraw';
491
504
  input: WithdrawInput;
@@ -495,21 +508,26 @@ type WriteStep = {
495
508
  } | {
496
509
  toolName: 'repay_debt';
497
510
  input: RepayDebtInput;
511
+ inputCoinFromStep?: number;
498
512
  } | {
499
513
  toolName: 'send_transfer';
500
514
  input: SendTransferInput;
515
+ inputCoinFromStep?: number;
501
516
  } | {
502
517
  toolName: 'swap_execute';
503
518
  input: SwapExecuteInput;
519
+ inputCoinFromStep?: number;
504
520
  } | {
505
521
  toolName: 'claim_rewards';
506
522
  input: ClaimRewardsInput;
507
523
  } | {
508
524
  toolName: 'volo_stake';
509
525
  input: VoloStakeInput;
526
+ inputCoinFromStep?: number;
510
527
  } | {
511
528
  toolName: 'volo_unstake';
512
529
  input: VoloUnstakeInput;
530
+ inputCoinFromStep?: number;
513
531
  };
514
532
  interface ComposeTxOptions {
515
533
  sender: string;
@@ -647,8 +665,8 @@ interface ComposeTxResult {
647
665
  }
648
666
  /**
649
667
  * Per-appender context passed into every registry entry. Carries the
650
- * RPC client, sender, sponsorship flag, and optional per-call overlay
651
- * fee config (Cetus swaps).
668
+ * RPC client, sender, sponsorship flag, optional per-call overlay
669
+ * fee config (Cetus swaps), and SPEC 13 Phase 1 chain-mode fields.
652
670
  */
653
671
  interface AppenderContext {
654
672
  client: SuiJsonRpcClient;
@@ -656,8 +674,46 @@ interface AppenderContext {
656
674
  sponsoredContext: boolean;
657
675
  overlayFee?: OverlayFeeConfig;
658
676
  feeHooks?: ComposeTxFeeHooks;
677
+ /**
678
+ * [SPEC 13 Phase 1] When set, the consumer appender consumes this
679
+ * coin handle directly instead of pre-fetching from the wallet via
680
+ * `selectAndSplitCoin` / `selectSuiCoin`. Provided by the
681
+ * orchestration loop when the step has `inputCoinFromStep` set; the
682
+ * loop looks up `priorOutputs[step.inputCoinFromStep]` and threads
683
+ * it through here.
684
+ *
685
+ * In chain mode, the consumer consumes the handle IN FULL — the
686
+ * `input.amount` field is treated as informational (used for preview
687
+ * math). This matches Cetus's `routerSwap`, NAVI's `deposit`/`repay`,
688
+ * and the Sui `transferObjects` semantics: each takes a coin object
689
+ * and consumes its entire balance.
690
+ */
691
+ chainedCoin?: TransactionObjectArgument;
692
+ /**
693
+ * [SPEC 13 Phase 1] True when this step's output coin will be
694
+ * consumed by a downstream step (some later step has
695
+ * `inputCoinFromStep === currentStepIndex`). Producer appenders MUST
696
+ * skip their terminal `tx.transferObjects([coin], ctx.sender)` call
697
+ * when this is true — otherwise the same `TransactionObjectArgument`
698
+ * gets used twice (once by the consumer, once by the transfer) and
699
+ * the PTB build fails or the on-chain leg reverts.
700
+ */
701
+ isOutputConsumed?: boolean;
702
+ }
703
+ /**
704
+ * [SPEC 13 Phase 1] Appender return shape. Producers populate
705
+ * `outputCoin` so the orchestration loop can thread it into a
706
+ * downstream consumer's `chainedCoin`. Terminal consumers
707
+ * (`save_deposit`, `repay_debt`, `send_transfer`) omit it.
708
+ *
709
+ * `swap_execute`, `volo_stake`, and `volo_unstake` are dual-mode —
710
+ * they accept `chainedCoin` AND populate `outputCoin`.
711
+ */
712
+ interface AppenderResult<TPreview extends StepPreview> {
713
+ preview: TPreview;
714
+ outputCoin?: TransactionObjectArgument;
659
715
  }
660
- type AppenderFn<TInput, TPreview extends StepPreview> = (tx: Transaction, input: TInput, ctx: AppenderContext) => Promise<TPreview>;
716
+ type AppenderFn<TInput, TPreview extends StepPreview> = (tx: Transaction, input: TInput, ctx: AppenderContext) => Promise<AppenderResult<TPreview>>;
661
717
  /**
662
718
  * The typed registry. Each entry is a wallet-mode dispatcher that takes
663
719
  * (tx, input, ctx) and returns a per-step preview. Compile-time check
package/dist/index.js CHANGED
@@ -7480,21 +7480,26 @@ var WRITE_APPENDER_REGISTRY = {
7480
7480
  throw new T2000Error("INVALID_AMOUNT", "Save amount must be greater than zero");
7481
7481
  }
7482
7482
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7483
- const { coin, effectiveAmount } = await selectAndSplitCoin(
7484
- tx,
7485
- ctx.client,
7486
- ctx.sender,
7487
- assetInfo.type,
7488
- rawAmount
7489
- );
7483
+ let coin;
7484
+ let effectiveAmount;
7485
+ if (ctx.chainedCoin) {
7486
+ coin = ctx.chainedCoin;
7487
+ effectiveAmount = rawAmount;
7488
+ } else {
7489
+ const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount);
7490
+ coin = r.coin;
7491
+ effectiveAmount = r.effectiveAmount;
7492
+ }
7490
7493
  if (ctx.feeHooks?.save_deposit) {
7491
7494
  await ctx.feeHooks.save_deposit({ tx, coin, input, sender: ctx.sender });
7492
7495
  }
7493
7496
  await addSaveToTx(tx, ctx.client, ctx.sender, coin, { asset });
7494
7497
  return {
7495
- toolName: "save_deposit",
7496
- effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7497
- asset
7498
+ preview: {
7499
+ toolName: "save_deposit",
7500
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7501
+ asset
7502
+ }
7498
7503
  };
7499
7504
  },
7500
7505
  withdraw: async (tx, input, ctx) => {
@@ -7509,8 +7514,13 @@ var WRITE_APPENDER_REGISTRY = {
7509
7514
  input.amount,
7510
7515
  { asset, skipPythUpdate: ctx.sponsoredContext }
7511
7516
  );
7512
- tx.transferObjects([coin], ctx.sender);
7513
- return { toolName: "withdraw", effectiveAmount, asset };
7517
+ if (!ctx.isOutputConsumed) {
7518
+ tx.transferObjects([coin], ctx.sender);
7519
+ }
7520
+ return {
7521
+ preview: { toolName: "withdraw", effectiveAmount, asset },
7522
+ outputCoin: coin
7523
+ };
7514
7524
  },
7515
7525
  borrow: async (tx, input, ctx) => {
7516
7526
  const asset = resolveSaveableAsset(input.asset);
@@ -7527,8 +7537,13 @@ var WRITE_APPENDER_REGISTRY = {
7527
7537
  if (ctx.feeHooks?.borrow) {
7528
7538
  await ctx.feeHooks.borrow({ tx, coin, input, sender: ctx.sender });
7529
7539
  }
7530
- tx.transferObjects([coin], ctx.sender);
7531
- return { toolName: "borrow", effectiveAmount: input.amount, asset };
7540
+ if (!ctx.isOutputConsumed) {
7541
+ tx.transferObjects([coin], ctx.sender);
7542
+ }
7543
+ return {
7544
+ preview: { toolName: "borrow", effectiveAmount: input.amount, asset },
7545
+ outputCoin: coin
7546
+ };
7532
7547
  },
7533
7548
  repay_debt: async (tx, input, ctx) => {
7534
7549
  const asset = resolveSaveableAsset(input.asset);
@@ -7537,21 +7552,26 @@ var WRITE_APPENDER_REGISTRY = {
7537
7552
  throw new T2000Error("INVALID_AMOUNT", "Repay amount must be greater than zero");
7538
7553
  }
7539
7554
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7540
- const { coin, effectiveAmount } = await selectAndSplitCoin(
7541
- tx,
7542
- ctx.client,
7543
- ctx.sender,
7544
- assetInfo.type,
7545
- rawAmount
7546
- );
7555
+ let coin;
7556
+ let effectiveAmount;
7557
+ if (ctx.chainedCoin) {
7558
+ coin = ctx.chainedCoin;
7559
+ effectiveAmount = rawAmount;
7560
+ } else {
7561
+ const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount);
7562
+ coin = r.coin;
7563
+ effectiveAmount = r.effectiveAmount;
7564
+ }
7547
7565
  await addRepayToTx(tx, ctx.client, ctx.sender, coin, {
7548
7566
  asset,
7549
7567
  skipOracle: ctx.sponsoredContext
7550
7568
  });
7551
7569
  return {
7552
- toolName: "repay_debt",
7553
- effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7554
- asset
7570
+ preview: {
7571
+ toolName: "repay_debt",
7572
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7573
+ asset
7574
+ }
7555
7575
  };
7556
7576
  },
7557
7577
  send_transfer: async (tx, input, ctx) => {
@@ -7567,7 +7587,10 @@ var WRITE_APPENDER_REGISTRY = {
7567
7587
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7568
7588
  let coin;
7569
7589
  let effectiveRaw;
7570
- if (asset === "SUI") {
7590
+ if (ctx.chainedCoin) {
7591
+ coin = ctx.chainedCoin;
7592
+ effectiveRaw = rawAmount;
7593
+ } else if (asset === "SUI") {
7571
7594
  const result = await selectSuiCoin(tx, ctx.client, ctx.sender, rawAmount, ctx.sponsoredContext);
7572
7595
  coin = result.coin;
7573
7596
  effectiveRaw = result.effectiveAmount;
@@ -7578,10 +7601,12 @@ var WRITE_APPENDER_REGISTRY = {
7578
7601
  }
7579
7602
  addSendToTx(tx, coin, recipient);
7580
7603
  return {
7581
- toolName: "send_transfer",
7582
- effectiveAmount: Number(effectiveRaw) / 10 ** assetInfo.decimals,
7583
- recipient,
7584
- asset
7604
+ preview: {
7605
+ toolName: "send_transfer",
7606
+ effectiveAmount: Number(effectiveRaw) / 10 ** assetInfo.decimals,
7607
+ recipient,
7608
+ asset
7609
+ }
7585
7610
  };
7586
7611
  },
7587
7612
  swap_execute: async (tx, input, ctx) => {
@@ -7601,37 +7626,59 @@ var WRITE_APPENDER_REGISTRY = {
7601
7626
  slippage: input.slippage,
7602
7627
  byAmountIn: input.byAmountIn,
7603
7628
  overlayFee: ctx.overlayFee,
7604
- providers
7629
+ providers,
7630
+ inputCoin: ctx.chainedCoin
7605
7631
  });
7606
- tx.transferObjects([result.coin], ctx.sender);
7632
+ if (!ctx.isOutputConsumed) {
7633
+ tx.transferObjects([result.coin], ctx.sender);
7634
+ }
7607
7635
  return {
7608
- toolName: "swap_execute",
7609
- effectiveAmountIn: result.effectiveAmountIn,
7610
- expectedAmountOut: result.expectedAmountOut,
7611
- route: result.route
7636
+ preview: {
7637
+ toolName: "swap_execute",
7638
+ effectiveAmountIn: result.effectiveAmountIn,
7639
+ expectedAmountOut: result.expectedAmountOut,
7640
+ route: result.route
7641
+ },
7642
+ outputCoin: result.coin
7612
7643
  };
7613
7644
  },
7614
7645
  claim_rewards: async (tx, _input, ctx) => {
7615
7646
  const rewards = await addClaimRewardsToTx(tx, ctx.client, ctx.sender);
7616
- return { toolName: "claim_rewards", rewards };
7647
+ return { preview: { toolName: "claim_rewards", rewards } };
7617
7648
  },
7618
7649
  volo_stake: async (tx, input, ctx) => {
7619
7650
  if (input.amountSui <= 0) {
7620
7651
  throw new T2000Error("INVALID_AMOUNT", "Stake amount must be greater than zero");
7621
7652
  }
7622
7653
  const amountMist = BigInt(Math.floor(input.amountSui * 1e9));
7623
- const result = await addStakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7624
- tx.transferObjects([result.coin], ctx.sender);
7625
- return { toolName: "volo_stake", effectiveAmountMist: result.effectiveAmountMist };
7654
+ const result = await addStakeVSuiToTx(tx, ctx.client, ctx.sender, {
7655
+ amountMist,
7656
+ inputCoin: ctx.chainedCoin
7657
+ });
7658
+ if (!ctx.isOutputConsumed) {
7659
+ tx.transferObjects([result.coin], ctx.sender);
7660
+ }
7661
+ return {
7662
+ preview: { toolName: "volo_stake", effectiveAmountMist: result.effectiveAmountMist },
7663
+ outputCoin: result.coin
7664
+ };
7626
7665
  },
7627
7666
  volo_unstake: async (tx, input, ctx) => {
7628
7667
  const amountMist = input.amountVSui === "all" ? "all" : BigInt(Math.floor(input.amountVSui * 1e9));
7629
7668
  if (amountMist !== "all" && amountMist <= 0n) {
7630
7669
  throw new T2000Error("INVALID_AMOUNT", "Unstake amount must be greater than zero");
7631
7670
  }
7632
- const result = await addUnstakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7633
- tx.transferObjects([result.coin], ctx.sender);
7634
- return { toolName: "volo_unstake", effectiveAmountMist: result.effectiveAmountMist };
7671
+ const result = await addUnstakeVSuiToTx(tx, ctx.client, ctx.sender, {
7672
+ amountMist,
7673
+ inputCoin: ctx.chainedCoin
7674
+ });
7675
+ if (!ctx.isOutputConsumed) {
7676
+ tx.transferObjects([result.coin], ctx.sender);
7677
+ }
7678
+ return {
7679
+ preview: { toolName: "volo_unstake", effectiveAmountMist: result.effectiveAmountMist },
7680
+ outputCoin: result.coin
7681
+ };
7635
7682
  }
7636
7683
  };
7637
7684
  function deriveAllowedAddressesFromPtb(tx) {
@@ -7670,15 +7717,38 @@ function base64ToBytes(b64) {
7670
7717
  async function composeTx(opts) {
7671
7718
  const tx = new Transaction();
7672
7719
  tx.setSender(opts.sender);
7673
- const ctx = {
7720
+ const baseCtx = {
7674
7721
  client: opts.client,
7675
7722
  sender: opts.sender,
7676
7723
  sponsoredContext: opts.sponsoredContext ?? false,
7677
7724
  overlayFee: opts.overlayFee,
7678
7725
  feeHooks: opts.feeHooks
7679
7726
  };
7727
+ const consumedSteps = /* @__PURE__ */ new Set();
7728
+ for (let i = 0; i < opts.steps.length; i++) {
7729
+ const step = opts.steps[i];
7730
+ const stepWithChain = step;
7731
+ const idx = stepWithChain.inputCoinFromStep;
7732
+ if (idx === void 0) continue;
7733
+ if (!Number.isInteger(idx) || idx < 0 || idx >= i) {
7734
+ throw new T2000Error(
7735
+ "CHAIN_MODE_INVALID",
7736
+ `Step ${i} (${step.toolName}) has inputCoinFromStep=${idx}, which must be a non-negative integer < ${i} (forward-only references).`
7737
+ );
7738
+ }
7739
+ const producer = opts.steps[idx];
7740
+ if (producer.toolName === "save_deposit" || producer.toolName === "repay_debt" || producer.toolName === "send_transfer" || producer.toolName === "claim_rewards") {
7741
+ throw new T2000Error(
7742
+ "CHAIN_MODE_INVALID",
7743
+ `Step ${i} (${step.toolName}) references step ${idx} (${producer.toolName}) as producer, but '${producer.toolName}' is a terminal consumer that does not produce a chainable coin handle. Allowed producers: withdraw, borrow, swap_execute, volo_stake, volo_unstake.`
7744
+ );
7745
+ }
7746
+ consumedSteps.add(idx);
7747
+ }
7748
+ const priorOutputs = [];
7680
7749
  const previews = [];
7681
- for (const step of opts.steps) {
7750
+ for (let i = 0; i < opts.steps.length; i++) {
7751
+ const step = opts.steps[i];
7682
7752
  const appender = WRITE_APPENDER_REGISTRY[step.toolName];
7683
7753
  if (!appender) {
7684
7754
  throw new T2000Error(
@@ -7686,8 +7756,26 @@ async function composeTx(opts) {
7686
7756
  `No fragment appender registered for tool '${step.toolName}'. Allowed: ${Object.keys(WRITE_APPENDER_REGISTRY).join(", ")}`
7687
7757
  );
7688
7758
  }
7689
- const preview = await appender(tx, step.input, ctx);
7690
- previews.push(preview);
7759
+ const stepWithChain = step;
7760
+ let chainedCoin;
7761
+ if (stepWithChain.inputCoinFromStep !== void 0) {
7762
+ const upstream = priorOutputs[stepWithChain.inputCoinFromStep];
7763
+ if (!upstream) {
7764
+ throw new T2000Error(
7765
+ "CHAIN_MODE_INVALID",
7766
+ `Step ${i} (${step.toolName}) expected a coin handle from step ${stepWithChain.inputCoinFromStep}, but the producer did not return one.`
7767
+ );
7768
+ }
7769
+ chainedCoin = upstream;
7770
+ }
7771
+ const stepCtx = {
7772
+ ...baseCtx,
7773
+ chainedCoin,
7774
+ isOutputConsumed: consumedSteps.has(i)
7775
+ };
7776
+ const result = await appender(tx, step.input, stepCtx);
7777
+ priorOutputs.push(result.outputCoin ?? null);
7778
+ previews.push(result.preview);
7691
7779
  }
7692
7780
  const txKindBytes = await tx.build({ client: opts.client, onlyTransactionKind: true });
7693
7781
  const derivedAllowedAddresses = deriveAllowedAddressesFromPtb(tx);