@t2000/sdk 1.5.0 → 1.7.0

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
@@ -145,6 +145,32 @@ var init_errors = __esm({
145
145
  });
146
146
 
147
147
  // src/token-registry.ts
148
+ var token_registry_exports = {};
149
+ __export(token_registry_exports, {
150
+ COIN_REGISTRY: () => exports.COIN_REGISTRY,
151
+ ETH_TYPE: () => exports.ETH_TYPE,
152
+ IKA_TYPE: () => exports.IKA_TYPE,
153
+ LOFI_TYPE: () => exports.LOFI_TYPE,
154
+ MANIFEST_TYPE: () => exports.MANIFEST_TYPE,
155
+ NAVX_TYPE: () => exports.NAVX_TYPE,
156
+ SUI_TYPE: () => exports.SUI_TYPE,
157
+ TOKEN_MAP: () => exports.TOKEN_MAP,
158
+ USDC_TYPE: () => exports.USDC_TYPE,
159
+ USDE_TYPE: () => exports.USDE_TYPE,
160
+ USDSUI_TYPE: () => exports.USDSUI_TYPE,
161
+ USDT_TYPE: () => exports.USDT_TYPE,
162
+ WAL_TYPE: () => exports.WAL_TYPE,
163
+ WBTC_TYPE: () => exports.WBTC_TYPE,
164
+ getCoinMeta: () => getCoinMeta,
165
+ getDecimalsForCoinType: () => getDecimalsForCoinType,
166
+ getTier: () => getTier,
167
+ isInRegistry: () => isInRegistry,
168
+ isSupported: () => isSupported,
169
+ isTier1: () => isTier1,
170
+ isTier2: () => isTier2,
171
+ resolveSymbol: () => resolveSymbol,
172
+ resolveTokenType: () => resolveTokenType
173
+ });
148
174
  function getCoinMeta(coinType) {
149
175
  return BY_TYPE.get(coinType);
150
176
  }
@@ -628,6 +654,8 @@ __export(volo_exports, {
628
654
  VOLO_PKG: () => exports.VOLO_PKG,
629
655
  VOLO_POOL: () => exports.VOLO_POOL,
630
656
  VSUI_TYPE: () => exports.VSUI_TYPE,
657
+ addStakeVSuiToTx: () => addStakeVSuiToTx,
658
+ addUnstakeVSuiToTx: () => addUnstakeVSuiToTx,
631
659
  buildStakeVSuiTx: () => buildStakeVSuiTx,
632
660
  buildUnstakeVSuiTx: () => buildUnstakeVSuiTx,
633
661
  getVoloStats: () => getVoloStats
@@ -667,7 +695,7 @@ async function buildStakeVSuiTx(_client, address, amountMist) {
667
695
  return tx;
668
696
  }
669
697
  async function buildUnstakeVSuiTx(client, address, amountMist) {
670
- const coins = await fetchVSuiCoins(client, address);
698
+ const coins = await fetchCoinsByType(client, address, exports.VSUI_TYPE);
671
699
  if (coins.length === 0) {
672
700
  throw new Error("No vSUI found in wallet.");
673
701
  }
@@ -695,14 +723,81 @@ async function buildUnstakeVSuiTx(client, address, amountMist) {
695
723
  tx.transferObjects([suiCoin], address);
696
724
  return tx;
697
725
  }
698
- async function fetchVSuiCoins(client, address) {
726
+ async function addStakeVSuiToTx(tx, client, address, input) {
727
+ if (input.amountMist < MIN_STAKE_MIST) {
728
+ throw new Error(`Minimum stake is 1 SUI (${MIN_STAKE_MIST} MIST). Got: ${input.amountMist}`);
729
+ }
730
+ let suiCoin;
731
+ if (input.inputCoin) {
732
+ suiCoin = input.inputCoin;
733
+ } else {
734
+ const coins = await fetchCoinsByType(client, address, exports.SUI_TYPE);
735
+ if (coins.length === 0) {
736
+ throw new Error("No SUI coins found in wallet");
737
+ }
738
+ const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
739
+ if (totalBalance < input.amountMist) {
740
+ throw new Error(`Insufficient SUI: need ${input.amountMist} MIST, have ${totalBalance}`);
741
+ }
742
+ const primary = tx.object(coins[0].coinObjectId);
743
+ if (coins.length > 1) {
744
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
745
+ }
746
+ [suiCoin] = tx.splitCoins(primary, [input.amountMist]);
747
+ }
748
+ const [vSuiCoin] = tx.moveCall({
749
+ target: `${exports.VOLO_PKG}::stake_pool::stake`,
750
+ arguments: [
751
+ tx.object(exports.VOLO_POOL),
752
+ tx.object(exports.VOLO_METADATA),
753
+ tx.object(SUI_SYSTEM_STATE),
754
+ suiCoin
755
+ ]
756
+ });
757
+ return { coin: vSuiCoin, effectiveAmountMist: input.amountMist };
758
+ }
759
+ async function addUnstakeVSuiToTx(tx, client, address, input) {
760
+ let vSuiCoin;
761
+ if (input.inputCoin) {
762
+ if (input.amountMist === "all") {
763
+ vSuiCoin = input.inputCoin;
764
+ } else {
765
+ [vSuiCoin] = tx.splitCoins(input.inputCoin, [input.amountMist]);
766
+ }
767
+ } else {
768
+ const coins = await fetchCoinsByType(client, address, exports.VSUI_TYPE);
769
+ if (coins.length === 0) {
770
+ throw new Error("No vSUI found in wallet.");
771
+ }
772
+ const primary = tx.object(coins[0].coinObjectId);
773
+ if (coins.length > 1) {
774
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
775
+ }
776
+ if (input.amountMist === "all") {
777
+ vSuiCoin = primary;
778
+ } else {
779
+ [vSuiCoin] = tx.splitCoins(primary, [input.amountMist]);
780
+ }
781
+ }
782
+ const [suiCoin] = tx.moveCall({
783
+ target: `${exports.VOLO_PKG}::stake_pool::unstake`,
784
+ arguments: [
785
+ tx.object(exports.VOLO_POOL),
786
+ tx.object(exports.VOLO_METADATA),
787
+ tx.object(SUI_SYSTEM_STATE),
788
+ vSuiCoin
789
+ ]
790
+ });
791
+ return { coin: suiCoin, effectiveAmountMist: input.amountMist };
792
+ }
793
+ async function fetchCoinsByType(client, owner, coinType) {
699
794
  const all = [];
700
795
  let cursor;
701
796
  let hasNext = true;
702
797
  while (hasNext) {
703
798
  const page = await client.getCoins({
704
- owner: address,
705
- coinType: exports.VSUI_TYPE,
799
+ owner,
800
+ coinType,
706
801
  cursor: cursor ?? void 0
707
802
  });
708
803
  all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
@@ -714,6 +809,7 @@ async function fetchVSuiCoins(client, address) {
714
809
  exports.VOLO_PKG = void 0; exports.VOLO_POOL = void 0; exports.VOLO_METADATA = void 0; exports.VSUI_TYPE = void 0; var SUI_SYSTEM_STATE, MIN_STAKE_MIST, VOLO_STATS_URL;
715
810
  var init_volo = __esm({
716
811
  "src/protocols/volo.ts"() {
812
+ init_token_registry();
717
813
  exports.VOLO_PKG = "0x68d22cf8bdbcd11ecba1e094922873e4080d4d11133e2443fddda0bfd11dae20";
718
814
  exports.VOLO_POOL = "0x2d914e23d82fedef1b5f56a32d5c64bdcc3087ccfea2b4d6ea51a71f587840e5";
719
815
  exports.VOLO_METADATA = "0x680cd26af32b2bde8d3361e804c53ec1d1cfe24c7f039eb7f549e8dfde389a60";
@@ -729,6 +825,7 @@ var cetus_swap_exports = {};
729
825
  __export(cetus_swap_exports, {
730
826
  OVERLAY_FEE_RATE: () => exports.OVERLAY_FEE_RATE,
731
827
  TOKEN_MAP: () => exports.TOKEN_MAP,
828
+ addSwapToTx: () => addSwapToTx,
732
829
  buildSwapTx: () => buildSwapTx,
733
830
  findSwapRoute: () => findSwapRoute,
734
831
  resolveTokenType: () => resolveTokenType,
@@ -754,7 +851,8 @@ async function findSwapRoute(params) {
754
851
  from: params.from,
755
852
  target: params.to,
756
853
  amount: params.amount.toString(),
757
- byAmountIn: params.byAmountIn
854
+ byAmountIn: params.byAmountIn,
855
+ ...params.providers ? { providers: params.providers } : {}
758
856
  };
759
857
  const routerData = await client.findRouters(findParams);
760
858
  if (!routerData) return null;
@@ -796,6 +894,85 @@ async function buildSwapTx(params) {
796
894
  });
797
895
  return outputCoin;
798
896
  }
897
+ async function addSwapToTx(tx, client, address, input) {
898
+ const { T2000Error: T2000Error2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
899
+ const fromType = resolveTokenType(input.from);
900
+ const toType = resolveTokenType(input.to);
901
+ if (!fromType) throw new T2000Error2("ASSET_NOT_SUPPORTED", `Unknown token: ${input.from}. Provide the symbol (USDC, SUI, ...) or full coin type.`);
902
+ if (!toType) throw new T2000Error2("ASSET_NOT_SUPPORTED", `Unknown token: ${input.to}. Provide the symbol (USDC, SUI, ...) or full coin type.`);
903
+ if (fromType === toType) throw new T2000Error2("SWAP_FAILED", "Cannot swap a token to itself");
904
+ if (!Number.isFinite(input.amount) || input.amount <= 0) {
905
+ throw new T2000Error2("INVALID_AMOUNT", "Amount must be greater than zero");
906
+ }
907
+ const fromDecimals = getDecimalsForCoinType(fromType);
908
+ const toDecimals = getDecimalsForCoinType(toType);
909
+ const requestedRaw = BigInt(Math.floor(input.amount * 10 ** fromDecimals));
910
+ const slippage = Math.max(1e-3, Math.min(input.slippage ?? 0.01, 0.05));
911
+ const byAmountIn = input.byAmountIn ?? true;
912
+ let inputCoin;
913
+ let effectiveRaw;
914
+ if (input.inputCoin) {
915
+ inputCoin = input.inputCoin;
916
+ effectiveRaw = requestedRaw;
917
+ } else {
918
+ const { ids, totalBalance } = await fetchAllCoinsForSwap(client, address, fromType);
919
+ if (ids.length === 0) {
920
+ throw new T2000Error2("INSUFFICIENT_BALANCE", `No ${input.from} coins found in wallet`);
921
+ }
922
+ const swapAll = requestedRaw >= totalBalance;
923
+ effectiveRaw = swapAll ? totalBalance : requestedRaw;
924
+ const primary = tx.object(ids[0]);
925
+ if (ids.length > 1) {
926
+ tx.mergeCoins(primary, ids.slice(1).map((id) => tx.object(id)));
927
+ }
928
+ inputCoin = swapAll ? primary : tx.splitCoins(primary, [effectiveRaw])[0];
929
+ }
930
+ const route = await findSwapRoute({
931
+ walletAddress: address,
932
+ from: fromType,
933
+ to: toType,
934
+ amount: effectiveRaw,
935
+ byAmountIn,
936
+ overlayFee: input.overlayFee,
937
+ providers: input.providers
938
+ });
939
+ if (!route) {
940
+ throw new T2000Error2("SWAP_NO_ROUTE", `No swap route found for ${input.from} \u2192 ${input.to}`);
941
+ }
942
+ if (route.insufficientLiquidity) {
943
+ throw new T2000Error2("SWAP_NO_ROUTE", `Insufficient liquidity for ${input.from} \u2192 ${input.to}`);
944
+ }
945
+ const outputCoin = await buildSwapTx({
946
+ walletAddress: address,
947
+ route,
948
+ tx,
949
+ inputCoin,
950
+ slippage,
951
+ overlayFee: input.overlayFee
952
+ });
953
+ return {
954
+ coin: outputCoin,
955
+ effectiveAmountIn: Number(effectiveRaw) / 10 ** fromDecimals,
956
+ expectedAmountOut: Number(route.amountOut) / 10 ** toDecimals,
957
+ route
958
+ };
959
+ }
960
+ async function fetchAllCoinsForSwap(client, owner, coinType) {
961
+ const ids = [];
962
+ let totalBalance = 0n;
963
+ let cursor;
964
+ let hasNext = true;
965
+ while (hasNext) {
966
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
967
+ for (const c of page.data) {
968
+ ids.push(c.coinObjectId);
969
+ totalBalance += BigInt(c.balance);
970
+ }
971
+ cursor = page.nextCursor;
972
+ hasNext = page.hasNextPage;
973
+ }
974
+ return { ids, totalBalance };
975
+ }
799
976
  async function simulateSwap(params) {
800
977
  const client = getClient(params.walletAddress, params.overlayFee);
801
978
  try {
@@ -808,6 +985,7 @@ async function simulateSwap(params) {
808
985
  exports.OVERLAY_FEE_RATE = void 0; var clientCache;
809
986
  var init_cetus_swap = __esm({
810
987
  "src/protocols/cetus-swap.ts"() {
988
+ init_token_registry();
811
989
  init_token_registry();
812
990
  exports.OVERLAY_FEE_RATE = 1e-3;
813
991
  clientCache = /* @__PURE__ */ new Map();
@@ -1173,6 +1351,10 @@ async function buildSendTx({
1173
1351
  }
1174
1352
  return tx;
1175
1353
  }
1354
+ function addSendToTx(tx, coin, recipient) {
1355
+ const validRecipient = validateAddress(recipient);
1356
+ tx.transferObjects([coin], validRecipient);
1357
+ }
1176
1358
 
1177
1359
  // src/wallet/balance.ts
1178
1360
  var SUI_PRICE_FALLBACK = 1;
@@ -5315,7 +5497,10 @@ async function addSaveToTx(tx, _client, _address, coin, options = {}) {
5315
5497
  async function addRepayToTx(tx, client, address, coin, options = {}) {
5316
5498
  const asset = options.asset ?? "USDC";
5317
5499
  const assetInfo = resolveAssetInfo(asset);
5318
- await refreshOracle(tx, client, address, { skipPythUpdate: options.skipPythUpdate });
5500
+ await refreshOracle(tx, client, address, {
5501
+ skipPythUpdate: options.skipPythUpdate,
5502
+ skipOracle: options.skipOracle
5503
+ });
5319
5504
  try {
5320
5505
  await xe(tx, assetInfo.type, coin, { env: "prod" });
5321
5506
  } catch (err) {
@@ -5459,6 +5644,12 @@ async function addClaimRewardsToTx(tx, client, address) {
5459
5644
  return [];
5460
5645
  }
5461
5646
  }
5647
+ async function buildClaimRewardsTx(client, address) {
5648
+ const tx = new transactions.Transaction();
5649
+ tx.setSender(address);
5650
+ const rewards = await addClaimRewardsToTx(tx, client, address);
5651
+ return { tx, rewards };
5652
+ }
5462
5653
  function aggregateClaimableRewards(claimable) {
5463
5654
  const aggregated = /* @__PURE__ */ new Map();
5464
5655
  for (const c of claimable) {
@@ -7190,6 +7381,305 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7190
7381
  // src/index.ts
7191
7382
  init_errors();
7192
7383
 
7384
+ // src/wallet/coinSelection.ts
7385
+ init_errors();
7386
+ async function fetchAllCoins(client, owner, coinType) {
7387
+ const ids = [];
7388
+ let totalBalance = 0n;
7389
+ let cursor;
7390
+ let hasNext = true;
7391
+ while (hasNext) {
7392
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
7393
+ for (const c of page.data) {
7394
+ ids.push(c.coinObjectId);
7395
+ totalBalance += BigInt(c.balance);
7396
+ }
7397
+ cursor = page.nextCursor;
7398
+ hasNext = page.hasNextPage;
7399
+ }
7400
+ return { ids, totalBalance };
7401
+ }
7402
+ async function selectAndSplitCoin(tx, client, owner, coinType, amount, options = {}) {
7403
+ const { ids, totalBalance } = await fetchAllCoins(client, owner, coinType);
7404
+ if (ids.length === 0) {
7405
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No coins found for ${coinType}`);
7406
+ }
7407
+ const allowSwapAll = options.allowSwapAll ?? true;
7408
+ if (amount !== "all" && amount > totalBalance && !allowSwapAll) {
7409
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance for ${coinType}`, {
7410
+ available: totalBalance.toString(),
7411
+ required: amount.toString()
7412
+ });
7413
+ }
7414
+ const requested = amount === "all" ? totalBalance : amount;
7415
+ const swapAll = amount === "all" || requested >= totalBalance;
7416
+ const effectiveAmount = swapAll ? totalBalance : requested;
7417
+ const primary = tx.object(ids[0]);
7418
+ if (ids.length > 1) {
7419
+ tx.mergeCoins(primary, ids.slice(1).map((id) => tx.object(id)));
7420
+ }
7421
+ const coin = swapAll ? primary : tx.splitCoins(primary, [effectiveAmount])[0];
7422
+ return { coin, effectiveAmount, swapAll };
7423
+ }
7424
+ async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext) {
7425
+ if (sponsoredContext) {
7426
+ const { SUI_TYPE: SUI_TYPE2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
7427
+ return selectAndSplitCoin(tx, client, owner, SUI_TYPE2, amountMist);
7428
+ }
7429
+ const [coin] = tx.splitCoins(tx.gas, [amountMist]);
7430
+ return { coin, effectiveAmount: amountMist, swapAll: false };
7431
+ }
7432
+ init_cetus_swap();
7433
+ init_volo();
7434
+ init_token_registry();
7435
+ init_errors();
7436
+ var SPONSORED_PYTH_DEPENDENT_PROVIDERS = [
7437
+ "HAEDALPMM",
7438
+ "METASTABLE",
7439
+ "OBRIC",
7440
+ "STEAMM_OMM",
7441
+ "STEAMM_OMM_V2",
7442
+ "SEVENK",
7443
+ "HAEDALHMMV2"
7444
+ ];
7445
+ async function getSponsoredSwapProviders() {
7446
+ const { getProvidersExcluding } = await import('@cetusprotocol/aggregator-sdk');
7447
+ return getProvidersExcluding([...SPONSORED_PYTH_DEPENDENT_PROVIDERS]);
7448
+ }
7449
+ function resolveSaveableAsset(asset) {
7450
+ if (!asset) return "USDC";
7451
+ if (asset !== "USDC" && asset !== "USDsui") {
7452
+ throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Saveable asset must be USDC or USDsui, got ${asset}`);
7453
+ }
7454
+ return asset;
7455
+ }
7456
+ var WRITE_APPENDER_REGISTRY = {
7457
+ save_deposit: async (tx, input, ctx) => {
7458
+ const asset = resolveSaveableAsset(input.asset);
7459
+ const assetInfo = SUPPORTED_ASSETS[asset];
7460
+ if (input.amount <= 0) {
7461
+ throw new exports.T2000Error("INVALID_AMOUNT", "Save amount must be greater than zero");
7462
+ }
7463
+ const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7464
+ const { coin, effectiveAmount } = await selectAndSplitCoin(
7465
+ tx,
7466
+ ctx.client,
7467
+ ctx.sender,
7468
+ assetInfo.type,
7469
+ rawAmount
7470
+ );
7471
+ if (ctx.feeHooks?.save_deposit) {
7472
+ await ctx.feeHooks.save_deposit({ tx, coin, input, sender: ctx.sender });
7473
+ }
7474
+ await addSaveToTx(tx, ctx.client, ctx.sender, coin, { asset });
7475
+ return {
7476
+ toolName: "save_deposit",
7477
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7478
+ asset
7479
+ };
7480
+ },
7481
+ withdraw: async (tx, input, ctx) => {
7482
+ const asset = resolveSaveableAsset(input.asset);
7483
+ if (input.amount <= 0) {
7484
+ throw new exports.T2000Error("INVALID_AMOUNT", "Withdraw amount must be greater than zero");
7485
+ }
7486
+ const { coin, effectiveAmount } = await addWithdrawToTx(
7487
+ tx,
7488
+ ctx.client,
7489
+ ctx.sender,
7490
+ input.amount,
7491
+ { asset, skipPythUpdate: ctx.sponsoredContext }
7492
+ );
7493
+ tx.transferObjects([coin], ctx.sender);
7494
+ return { toolName: "withdraw", effectiveAmount, asset };
7495
+ },
7496
+ borrow: async (tx, input, ctx) => {
7497
+ const asset = resolveSaveableAsset(input.asset);
7498
+ if (input.amount <= 0) {
7499
+ throw new exports.T2000Error("INVALID_AMOUNT", "Borrow amount must be greater than zero");
7500
+ }
7501
+ const coin = await addBorrowToTx(
7502
+ tx,
7503
+ ctx.client,
7504
+ ctx.sender,
7505
+ input.amount,
7506
+ { asset, skipPythUpdate: ctx.sponsoredContext }
7507
+ );
7508
+ if (ctx.feeHooks?.borrow) {
7509
+ await ctx.feeHooks.borrow({ tx, coin, input, sender: ctx.sender });
7510
+ }
7511
+ tx.transferObjects([coin], ctx.sender);
7512
+ return { toolName: "borrow", effectiveAmount: input.amount, asset };
7513
+ },
7514
+ repay_debt: async (tx, input, ctx) => {
7515
+ const asset = resolveSaveableAsset(input.asset);
7516
+ const assetInfo = SUPPORTED_ASSETS[asset];
7517
+ if (input.amount <= 0) {
7518
+ throw new exports.T2000Error("INVALID_AMOUNT", "Repay amount must be greater than zero");
7519
+ }
7520
+ const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7521
+ const { coin, effectiveAmount } = await selectAndSplitCoin(
7522
+ tx,
7523
+ ctx.client,
7524
+ ctx.sender,
7525
+ assetInfo.type,
7526
+ rawAmount
7527
+ );
7528
+ await addRepayToTx(tx, ctx.client, ctx.sender, coin, {
7529
+ asset,
7530
+ skipOracle: ctx.sponsoredContext
7531
+ });
7532
+ return {
7533
+ toolName: "repay_debt",
7534
+ effectiveAmount: Number(effectiveAmount) / 10 ** assetInfo.decimals,
7535
+ asset
7536
+ };
7537
+ },
7538
+ send_transfer: async (tx, input, ctx) => {
7539
+ const recipient = validateAddress(input.to);
7540
+ const asset = input.asset ?? "USDC";
7541
+ const assetInfo = SUPPORTED_ASSETS[asset];
7542
+ if (!assetInfo) {
7543
+ throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
7544
+ }
7545
+ if (input.amount <= 0) {
7546
+ throw new exports.T2000Error("INVALID_AMOUNT", "Send amount must be greater than zero");
7547
+ }
7548
+ const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
7549
+ let coin;
7550
+ let effectiveRaw;
7551
+ if (asset === "SUI") {
7552
+ const result = await selectSuiCoin(tx, ctx.client, ctx.sender, rawAmount, ctx.sponsoredContext);
7553
+ coin = result.coin;
7554
+ effectiveRaw = result.effectiveAmount;
7555
+ } else {
7556
+ const result = await selectAndSplitCoin(tx, ctx.client, ctx.sender, assetInfo.type, rawAmount);
7557
+ coin = result.coin;
7558
+ effectiveRaw = result.effectiveAmount;
7559
+ }
7560
+ addSendToTx(tx, coin, recipient);
7561
+ return {
7562
+ toolName: "send_transfer",
7563
+ effectiveAmount: Number(effectiveRaw) / 10 ** assetInfo.decimals,
7564
+ recipient,
7565
+ asset
7566
+ };
7567
+ },
7568
+ swap_execute: async (tx, input, ctx) => {
7569
+ const fromType = resolveTokenType(input.from);
7570
+ const toType = resolveTokenType(input.to);
7571
+ if (!fromType || !toType) {
7572
+ throw new exports.T2000Error(
7573
+ "ASSET_NOT_SUPPORTED",
7574
+ `Unknown token in swap: from=${input.from}, to=${input.to}`
7575
+ );
7576
+ }
7577
+ const providers = input.providers ?? (ctx.sponsoredContext ? await getSponsoredSwapProviders() : void 0);
7578
+ const result = await addSwapToTx(tx, ctx.client, ctx.sender, {
7579
+ from: input.from,
7580
+ to: input.to,
7581
+ amount: input.amount,
7582
+ slippage: input.slippage,
7583
+ byAmountIn: input.byAmountIn,
7584
+ overlayFee: ctx.overlayFee,
7585
+ providers
7586
+ });
7587
+ tx.transferObjects([result.coin], ctx.sender);
7588
+ return {
7589
+ toolName: "swap_execute",
7590
+ effectiveAmountIn: result.effectiveAmountIn,
7591
+ expectedAmountOut: result.expectedAmountOut,
7592
+ route: result.route
7593
+ };
7594
+ },
7595
+ claim_rewards: async (tx, _input, ctx) => {
7596
+ const rewards = await addClaimRewardsToTx(tx, ctx.client, ctx.sender);
7597
+ return { toolName: "claim_rewards", rewards };
7598
+ },
7599
+ volo_stake: async (tx, input, ctx) => {
7600
+ if (input.amountSui <= 0) {
7601
+ throw new exports.T2000Error("INVALID_AMOUNT", "Stake amount must be greater than zero");
7602
+ }
7603
+ const amountMist = BigInt(Math.floor(input.amountSui * 1e9));
7604
+ const result = await addStakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7605
+ tx.transferObjects([result.coin], ctx.sender);
7606
+ return { toolName: "volo_stake", effectiveAmountMist: result.effectiveAmountMist };
7607
+ },
7608
+ volo_unstake: async (tx, input, ctx) => {
7609
+ const amountMist = input.amountVSui === "all" ? "all" : BigInt(Math.floor(input.amountVSui * 1e9));
7610
+ if (amountMist !== "all" && amountMist <= 0n) {
7611
+ throw new exports.T2000Error("INVALID_AMOUNT", "Unstake amount must be greater than zero");
7612
+ }
7613
+ const result = await addUnstakeVSuiToTx(tx, ctx.client, ctx.sender, { amountMist });
7614
+ tx.transferObjects([result.coin], ctx.sender);
7615
+ return { toolName: "volo_unstake", effectiveAmountMist: result.effectiveAmountMist };
7616
+ }
7617
+ };
7618
+ function deriveAllowedAddressesFromPtb(tx) {
7619
+ const addresses = /* @__PURE__ */ new Set();
7620
+ const data = tx.getData();
7621
+ for (const cmd of data.commands) {
7622
+ const transferCmd = cmd.TransferObjects;
7623
+ if (!transferCmd) continue;
7624
+ const addressArg = transferCmd.address;
7625
+ if (!addressArg) continue;
7626
+ const addressInputIndex = addressArg.Input;
7627
+ if (addressInputIndex === void 0) continue;
7628
+ const input = data.inputs[addressInputIndex];
7629
+ if (!input) continue;
7630
+ const pureBytes = input.Pure?.bytes;
7631
+ if (!pureBytes) continue;
7632
+ try {
7633
+ const bytes = base64ToBytes(pureBytes);
7634
+ if (bytes.length !== 32) continue;
7635
+ const hex = "0x" + Array.from(bytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
7636
+ addresses.add(hex);
7637
+ } catch {
7638
+ }
7639
+ }
7640
+ return Array.from(addresses);
7641
+ }
7642
+ function base64ToBytes(b64) {
7643
+ if (typeof Buffer !== "undefined") {
7644
+ return Uint8Array.from(Buffer.from(b64, "base64"));
7645
+ }
7646
+ const binary = atob(b64);
7647
+ const bytes = new Uint8Array(binary.length);
7648
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
7649
+ return bytes;
7650
+ }
7651
+ async function composeTx(opts) {
7652
+ const tx = new transactions.Transaction();
7653
+ tx.setSender(opts.sender);
7654
+ const ctx = {
7655
+ client: opts.client,
7656
+ sender: opts.sender,
7657
+ sponsoredContext: opts.sponsoredContext ?? false,
7658
+ overlayFee: opts.overlayFee,
7659
+ feeHooks: opts.feeHooks
7660
+ };
7661
+ const previews = [];
7662
+ for (const step of opts.steps) {
7663
+ const appender = WRITE_APPENDER_REGISTRY[step.toolName];
7664
+ if (!appender) {
7665
+ throw new exports.T2000Error(
7666
+ "UNKNOWN",
7667
+ `No fragment appender registered for tool '${step.toolName}'. Allowed: ${Object.keys(WRITE_APPENDER_REGISTRY).join(", ")}`
7668
+ );
7669
+ }
7670
+ const preview = await appender(tx, step.input, ctx);
7671
+ previews.push(preview);
7672
+ }
7673
+ const txKindBytes = await tx.build({ client: opts.client, onlyTransactionKind: true });
7674
+ const derivedAllowedAddresses = deriveAllowedAddressesFromPtb(tx);
7675
+ return {
7676
+ tx,
7677
+ txKindBytes,
7678
+ derivedAllowedAddresses,
7679
+ perStepPreviews: previews
7680
+ };
7681
+ }
7682
+
7193
7683
  // src/protocols/protocolFee.ts
7194
7684
  var FEE_RATES = {
7195
7685
  save: SAVE_FEE_BPS,
@@ -7431,10 +7921,17 @@ exports.SafeguardError = SafeguardError;
7431
7921
  exports.T2000 = T2000;
7432
7922
  exports.T2000_OVERLAY_FEE_WALLET = T2000_OVERLAY_FEE_WALLET;
7433
7923
  exports.USDC_DECIMALS = USDC_DECIMALS;
7924
+ exports.WRITE_APPENDER_REGISTRY = WRITE_APPENDER_REGISTRY;
7434
7925
  exports.ZkLoginSigner = ZkLoginSigner;
7435
7926
  exports.addFeeTransfer = addFeeTransfer;
7927
+ exports.addSendToTx = addSendToTx;
7928
+ exports.addStakeVSuiToTx = addStakeVSuiToTx;
7929
+ exports.addSwapToTx = addSwapToTx;
7930
+ exports.addUnstakeVSuiToTx = addUnstakeVSuiToTx;
7436
7931
  exports.allDescriptors = allDescriptors;
7437
7932
  exports.assertAllowedAsset = assertAllowedAsset;
7933
+ exports.buildClaimRewardsTx = buildClaimRewardsTx;
7934
+ exports.buildSendTx = buildSendTx;
7438
7935
  exports.buildStakeVSuiTx = buildStakeVSuiTx;
7439
7936
  exports.buildSwapTx = buildSwapTx;
7440
7937
  exports.buildUnstakeVSuiTx = buildUnstakeVSuiTx;
@@ -7442,11 +7939,14 @@ exports.calculateFee = calculateFee;
7442
7939
  exports.classifyAction = classifyAction;
7443
7940
  exports.classifyLabel = classifyLabel;
7444
7941
  exports.classifyTransaction = classifyTransaction;
7942
+ exports.composeTx = composeTx;
7943
+ exports.deriveAllowedAddressesFromPtb = deriveAllowedAddressesFromPtb;
7445
7944
  exports.exportPrivateKey = exportPrivateKey;
7446
7945
  exports.extractTransferDetails = extractTransferDetails;
7447
7946
  exports.extractTxCommands = extractTxCommands;
7448
7947
  exports.extractTxSender = extractTxSender;
7449
7948
  exports.fallbackLabel = fallbackLabel;
7949
+ exports.fetchAllCoins = fetchAllCoins;
7450
7950
  exports.findSwapRoute = findSwapRoute;
7451
7951
  exports.formatAssetAmount = formatAssetAmount;
7452
7952
  exports.formatSui = formatSui;
@@ -7483,6 +7983,8 @@ exports.refineLendingLabel = refineLendingLabel;
7483
7983
  exports.resolveSymbol = resolveSymbol;
7484
7984
  exports.resolveTokenType = resolveTokenType;
7485
7985
  exports.saveKey = saveKey;
7986
+ exports.selectAndSplitCoin = selectAndSplitCoin;
7987
+ exports.selectSuiCoin = selectSuiCoin;
7486
7988
  exports.simulateTransaction = simulateTransaction;
7487
7989
  exports.stableToRaw = stableToRaw;
7488
7990
  exports.suiToMist = suiToMist;