@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.cjs CHANGED
@@ -7482,21 +7482,26 @@ var WRITE_APPENDER_REGISTRY = {
7482
7482
  throw new exports.T2000Error("INVALID_AMOUNT", "Save amount must be greater than zero");
7483
7483
  }
7484
7484
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7485
- const { coin, effectiveAmount } = await selectAndSplitCoin(
7486
- tx,
7487
- ctx.client,
7488
- ctx.sender,
7489
- assetInfo.type,
7490
- rawAmount
7491
- );
7485
+ let coin;
7486
+ let effectiveAmount;
7487
+ if (ctx.chainedCoin) {
7488
+ coin = ctx.chainedCoin;
7489
+ effectiveAmount = rawAmount;
7490
+ } else {
7491
+ const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount);
7492
+ coin = r.coin;
7493
+ effectiveAmount = r.effectiveAmount;
7494
+ }
7492
7495
  if (ctx.feeHooks?.save_deposit) {
7493
7496
  await ctx.feeHooks.save_deposit({ tx, coin, input, sender: ctx.sender });
7494
7497
  }
7495
7498
  await addSaveToTx(tx, ctx.client, ctx.sender, coin, { asset });
7496
7499
  return {
7497
- toolName: "save_deposit",
7498
- effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7499
- asset
7500
+ preview: {
7501
+ toolName: "save_deposit",
7502
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7503
+ asset
7504
+ }
7500
7505
  };
7501
7506
  },
7502
7507
  withdraw: async (tx, input, ctx) => {
@@ -7511,8 +7516,13 @@ var WRITE_APPENDER_REGISTRY = {
7511
7516
  input.amount,
7512
7517
  { asset, skipPythUpdate: ctx.sponsoredContext }
7513
7518
  );
7514
- tx.transferObjects([coin], ctx.sender);
7515
- return { toolName: "withdraw", effectiveAmount, asset };
7519
+ if (!ctx.isOutputConsumed) {
7520
+ tx.transferObjects([coin], ctx.sender);
7521
+ }
7522
+ return {
7523
+ preview: { toolName: "withdraw", effectiveAmount, asset },
7524
+ outputCoin: coin
7525
+ };
7516
7526
  },
7517
7527
  borrow: async (tx, input, ctx) => {
7518
7528
  const asset = resolveSaveableAsset(input.asset);
@@ -7529,8 +7539,13 @@ var WRITE_APPENDER_REGISTRY = {
7529
7539
  if (ctx.feeHooks?.borrow) {
7530
7540
  await ctx.feeHooks.borrow({ tx, coin, input, sender: ctx.sender });
7531
7541
  }
7532
- tx.transferObjects([coin], ctx.sender);
7533
- return { toolName: "borrow", effectiveAmount: input.amount, asset };
7542
+ if (!ctx.isOutputConsumed) {
7543
+ tx.transferObjects([coin], ctx.sender);
7544
+ }
7545
+ return {
7546
+ preview: { toolName: "borrow", effectiveAmount: input.amount, asset },
7547
+ outputCoin: coin
7548
+ };
7534
7549
  },
7535
7550
  repay_debt: async (tx, input, ctx) => {
7536
7551
  const asset = resolveSaveableAsset(input.asset);
@@ -7539,21 +7554,26 @@ var WRITE_APPENDER_REGISTRY = {
7539
7554
  throw new exports.T2000Error("INVALID_AMOUNT", "Repay amount must be greater than zero");
7540
7555
  }
7541
7556
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7542
- const { coin, effectiveAmount } = await selectAndSplitCoin(
7543
- tx,
7544
- ctx.client,
7545
- ctx.sender,
7546
- assetInfo.type,
7547
- rawAmount
7548
- );
7557
+ let coin;
7558
+ let effectiveAmount;
7559
+ if (ctx.chainedCoin) {
7560
+ coin = ctx.chainedCoin;
7561
+ effectiveAmount = rawAmount;
7562
+ } else {
7563
+ const r = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount);
7564
+ coin = r.coin;
7565
+ effectiveAmount = r.effectiveAmount;
7566
+ }
7549
7567
  await addRepayToTx(tx, ctx.client, ctx.sender, coin, {
7550
7568
  asset,
7551
7569
  skipOracle: ctx.sponsoredContext
7552
7570
  });
7553
7571
  return {
7554
- toolName: "repay_debt",
7555
- effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7556
- asset
7572
+ preview: {
7573
+ toolName: "repay_debt",
7574
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7575
+ asset
7576
+ }
7557
7577
  };
7558
7578
  },
7559
7579
  send_transfer: async (tx, input, ctx) => {
@@ -7569,7 +7589,10 @@ var WRITE_APPENDER_REGISTRY = {
7569
7589
  const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7570
7590
  let coin;
7571
7591
  let effectiveRaw;
7572
- if (asset === "SUI") {
7592
+ if (ctx.chainedCoin) {
7593
+ coin = ctx.chainedCoin;
7594
+ effectiveRaw = rawAmount;
7595
+ } else if (asset === "SUI") {
7573
7596
  const result = await selectSuiCoin(tx, ctx.client, ctx.sender, rawAmount, ctx.sponsoredContext);
7574
7597
  coin = result.coin;
7575
7598
  effectiveRaw = result.effectiveAmount;
@@ -7580,10 +7603,12 @@ var WRITE_APPENDER_REGISTRY = {
7580
7603
  }
7581
7604
  addSendToTx(tx, coin, recipient);
7582
7605
  return {
7583
- toolName: "send_transfer",
7584
- effectiveAmount: Number(effectiveRaw) / 10 ** assetInfo.decimals,
7585
- recipient,
7586
- asset
7606
+ preview: {
7607
+ toolName: "send_transfer",
7608
+ effectiveAmount: Number(effectiveRaw) / 10 ** assetInfo.decimals,
7609
+ recipient,
7610
+ asset
7611
+ }
7587
7612
  };
7588
7613
  },
7589
7614
  swap_execute: async (tx, input, ctx) => {
@@ -7603,37 +7628,59 @@ var WRITE_APPENDER_REGISTRY = {
7603
7628
  slippage: input.slippage,
7604
7629
  byAmountIn: input.byAmountIn,
7605
7630
  overlayFee: ctx.overlayFee,
7606
- providers
7631
+ providers,
7632
+ inputCoin: ctx.chainedCoin
7607
7633
  });
7608
- tx.transferObjects([result.coin], ctx.sender);
7634
+ if (!ctx.isOutputConsumed) {
7635
+ tx.transferObjects([result.coin], ctx.sender);
7636
+ }
7609
7637
  return {
7610
- toolName: "swap_execute",
7611
- effectiveAmountIn: result.effectiveAmountIn,
7612
- expectedAmountOut: result.expectedAmountOut,
7613
- route: result.route
7638
+ preview: {
7639
+ toolName: "swap_execute",
7640
+ effectiveAmountIn: result.effectiveAmountIn,
7641
+ expectedAmountOut: result.expectedAmountOut,
7642
+ route: result.route
7643
+ },
7644
+ outputCoin: result.coin
7614
7645
  };
7615
7646
  },
7616
7647
  claim_rewards: async (tx, _input, ctx) => {
7617
7648
  const rewards = await addClaimRewardsToTx(tx, ctx.client, ctx.sender);
7618
- return { toolName: "claim_rewards", rewards };
7649
+ return { preview: { toolName: "claim_rewards", rewards } };
7619
7650
  },
7620
7651
  volo_stake: async (tx, input, ctx) => {
7621
7652
  if (input.amountSui <= 0) {
7622
7653
  throw new exports.T2000Error("INVALID_AMOUNT", "Stake amount must be greater than zero");
7623
7654
  }
7624
7655
  const amountMist = BigInt(Math.floor(input.amountSui * 1e9));
7625
- const result = await addStakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7626
- tx.transferObjects([result.coin], ctx.sender);
7627
- return { toolName: "volo_stake", effectiveAmountMist: result.effectiveAmountMist };
7656
+ const result = await addStakeVSuiToTx(tx, ctx.client, ctx.sender, {
7657
+ amountMist,
7658
+ inputCoin: ctx.chainedCoin
7659
+ });
7660
+ if (!ctx.isOutputConsumed) {
7661
+ tx.transferObjects([result.coin], ctx.sender);
7662
+ }
7663
+ return {
7664
+ preview: { toolName: "volo_stake", effectiveAmountMist: result.effectiveAmountMist },
7665
+ outputCoin: result.coin
7666
+ };
7628
7667
  },
7629
7668
  volo_unstake: async (tx, input, ctx) => {
7630
7669
  const amountMist = input.amountVSui === "all" ? "all" : BigInt(Math.floor(input.amountVSui * 1e9));
7631
7670
  if (amountMist !== "all" && amountMist <= 0n) {
7632
7671
  throw new exports.T2000Error("INVALID_AMOUNT", "Unstake amount must be greater than zero");
7633
7672
  }
7634
- const result = await addUnstakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7635
- tx.transferObjects([result.coin], ctx.sender);
7636
- return { toolName: "volo_unstake", effectiveAmountMist: result.effectiveAmountMist };
7673
+ const result = await addUnstakeVSuiToTx(tx, ctx.client, ctx.sender, {
7674
+ amountMist,
7675
+ inputCoin: ctx.chainedCoin
7676
+ });
7677
+ if (!ctx.isOutputConsumed) {
7678
+ tx.transferObjects([result.coin], ctx.sender);
7679
+ }
7680
+ return {
7681
+ preview: { toolName: "volo_unstake", effectiveAmountMist: result.effectiveAmountMist },
7682
+ outputCoin: result.coin
7683
+ };
7637
7684
  }
7638
7685
  };
7639
7686
  function deriveAllowedAddressesFromPtb(tx) {
@@ -7672,15 +7719,38 @@ function base64ToBytes(b64) {
7672
7719
  async function composeTx(opts) {
7673
7720
  const tx = new transactions.Transaction();
7674
7721
  tx.setSender(opts.sender);
7675
- const ctx = {
7722
+ const baseCtx = {
7676
7723
  client: opts.client,
7677
7724
  sender: opts.sender,
7678
7725
  sponsoredContext: opts.sponsoredContext ?? false,
7679
7726
  overlayFee: opts.overlayFee,
7680
7727
  feeHooks: opts.feeHooks
7681
7728
  };
7729
+ const consumedSteps = /* @__PURE__ */ new Set();
7730
+ for (let i = 0; i < opts.steps.length; i++) {
7731
+ const step = opts.steps[i];
7732
+ const stepWithChain = step;
7733
+ const idx = stepWithChain.inputCoinFromStep;
7734
+ if (idx === void 0) continue;
7735
+ if (!Number.isInteger(idx) || idx < 0 || idx >= i) {
7736
+ throw new exports.T2000Error(
7737
+ "CHAIN_MODE_INVALID",
7738
+ `Step ${i} (${step.toolName}) has inputCoinFromStep=${idx}, which must be a non-negative integer < ${i} (forward-only references).`
7739
+ );
7740
+ }
7741
+ const producer = opts.steps[idx];
7742
+ if (producer.toolName === "save_deposit" || producer.toolName === "repay_debt" || producer.toolName === "send_transfer" || producer.toolName === "claim_rewards") {
7743
+ throw new exports.T2000Error(
7744
+ "CHAIN_MODE_INVALID",
7745
+ `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.`
7746
+ );
7747
+ }
7748
+ consumedSteps.add(idx);
7749
+ }
7750
+ const priorOutputs = [];
7682
7751
  const previews = [];
7683
- for (const step of opts.steps) {
7752
+ for (let i = 0; i < opts.steps.length; i++) {
7753
+ const step = opts.steps[i];
7684
7754
  const appender = WRITE_APPENDER_REGISTRY[step.toolName];
7685
7755
  if (!appender) {
7686
7756
  throw new exports.T2000Error(
@@ -7688,8 +7758,26 @@ async function composeTx(opts) {
7688
7758
  `No fragment appender registered for tool '${step.toolName}'. Allowed: ${Object.keys(WRITE_APPENDER_REGISTRY).join(", ")}`
7689
7759
  );
7690
7760
  }
7691
- const preview = await appender(tx, step.input, ctx);
7692
- previews.push(preview);
7761
+ const stepWithChain = step;
7762
+ let chainedCoin;
7763
+ if (stepWithChain.inputCoinFromStep !== void 0) {
7764
+ const upstream = priorOutputs[stepWithChain.inputCoinFromStep];
7765
+ if (!upstream) {
7766
+ throw new exports.T2000Error(
7767
+ "CHAIN_MODE_INVALID",
7768
+ `Step ${i} (${step.toolName}) expected a coin handle from step ${stepWithChain.inputCoinFromStep}, but the producer did not return one.`
7769
+ );
7770
+ }
7771
+ chainedCoin = upstream;
7772
+ }
7773
+ const stepCtx = {
7774
+ ...baseCtx,
7775
+ chainedCoin,
7776
+ isOutputConsumed: consumedSteps.has(i)
7777
+ };
7778
+ const result = await appender(tx, step.input, stepCtx);
7779
+ priorOutputs.push(result.outputCoin ?? null);
7780
+ previews.push(result.preview);
7693
7781
  }
7694
7782
  const txKindBytes = await tx.build({ client: opts.client, onlyTransactionKind: true });
7695
7783
  const derivedAllowedAddresses = deriveAllowedAddressesFromPtb(tx);