@t2000/sdk 0.9.2 → 0.9.5

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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
+ import { Transaction } from '@mysten/sui/transactions';
2
3
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
3
4
  import { normalizeSuiAddress, isValidSuiAddress, normalizeStructTag } from '@mysten/sui/utils';
4
5
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
@@ -7,7 +8,6 @@ import { createHash, randomBytes, createCipheriv, createDecipheriv, scryptSync }
7
8
  import { access, mkdir, writeFile, readFile } from 'fs/promises';
8
9
  import { dirname, resolve } from 'path';
9
10
  import { homedir } from 'os';
10
- import { Transaction } from '@mysten/sui/transactions';
11
11
  import { bcs } from '@mysten/sui/bcs';
12
12
  import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
13
13
 
@@ -125,8 +125,11 @@ function mapMoveAbortCode(code) {
125
125
  10: "Already at current version",
126
126
  // NAVI Protocol abort codes
127
127
  1502: "Oracle price is stale \u2014 try again in a moment",
128
+ 1503: "Oracle validation failed during withdrawal \u2014 try again in a moment",
128
129
  1600: "Health factor too low \u2014 withdrawal would risk liquidation",
129
- 1605: "Asset borrowing is disabled or at capacity on this protocol"
130
+ 1605: "Asset borrowing is disabled or at capacity on this protocol",
131
+ // Cetus DEX abort codes
132
+ 46001: "Swap failed \u2014 the DEX pool rejected the trade (liquidity or routing issue). Try again."
130
133
  };
131
134
  return abortMessages[code] ?? `Move abort code: ${code}`;
132
135
  }
@@ -137,7 +140,12 @@ function parseMoveAbortMessage(msg) {
137
140
  const abortMatch = msg.match(/abort code:\s*(\d+)/i) ?? msg.match(/MoveAbort[^,]*,\s*(\d+)/);
138
141
  if (abortMatch) {
139
142
  const code = parseInt(abortMatch[1], 10);
140
- return mapMoveAbortCode(code);
143
+ const mapped = mapMoveAbortCode(code);
144
+ if (mapped.startsWith("Move abort code:")) {
145
+ const moduleMatch = msg.match(/in '([^']+)'/);
146
+ if (moduleMatch) return `${mapped} (in ${moduleMatch[1]})`;
147
+ }
148
+ return mapped;
141
149
  }
142
150
  return msg;
143
151
  }
@@ -633,7 +641,7 @@ function compoundBalance(rawBalance, currentIndex) {
633
641
  const result = (rawBalance * BigInt(currentIndex) + half) / scale;
634
642
  return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
635
643
  }
636
- async function getUserState(client, address) {
644
+ async function getUserState(client, address, includeZero = false) {
637
645
  const config = await getConfig();
638
646
  const tx = new Transaction();
639
647
  tx.moveCall({
@@ -646,11 +654,13 @@ async function getUserState(client, address) {
646
654
  });
647
655
  const decoded = decodeDevInspect(result, bcs.vector(UserStateInfo));
648
656
  if (!decoded) return [];
649
- return decoded.map((s) => ({
657
+ const mapped = decoded.map((s) => ({
650
658
  assetId: s.asset_id,
651
659
  supplyBalance: toBigInt(s.supply_balance),
652
660
  borrowBalance: toBigInt(s.borrow_balance)
653
- })).filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
661
+ }));
662
+ if (includeZero) return mapped;
663
+ return mapped.filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
654
664
  }
655
665
  async function fetchCoins(client, owner, coinType) {
656
666
  const all = [];
@@ -707,20 +717,20 @@ async function buildSaveTx(client, address, amount, options = {}) {
707
717
  async function buildWithdrawTx(client, address, amount, options = {}) {
708
718
  const asset = options.asset ?? "USDC";
709
719
  const assetInfo = SUPPORTED_ASSETS[asset];
710
- const [config, pool, pools, states] = await Promise.all([
720
+ const [config, pool, pools, allStates] = await Promise.all([
711
721
  getConfig(),
712
722
  getPool(asset),
713
723
  getPools(),
714
- getUserState(client, address)
724
+ getUserState(client, address, true)
715
725
  ]);
716
- const assetState = states.find((s) => s.assetId === pool.id);
726
+ const assetState = allStates.find((s) => s.assetId === pool.id);
717
727
  const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex) : 0;
718
728
  const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
719
729
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
720
730
  const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
721
731
  const tx = new Transaction();
722
732
  tx.setSender(address);
723
- addOracleUpdatesForPositions(tx, config, pools, states, pool);
733
+ addOracleUpdatesForPositions(tx, config, pools, allStates, pool);
724
734
  const [balance] = tx.moveCall({
725
735
  target: `${config.package}::incentive_v3::withdraw_v2`,
726
736
  arguments: [
@@ -744,6 +754,94 @@ async function buildWithdrawTx(client, address, amount, options = {}) {
744
754
  tx.transferObjects([coin], address);
745
755
  return { tx, effectiveAmount };
746
756
  }
757
+ async function addWithdrawToTx(tx, client, address, amount, options = {}) {
758
+ const asset = options.asset ?? "USDC";
759
+ const assetInfo = SUPPORTED_ASSETS[asset];
760
+ const [config, pool, pools, allStates] = await Promise.all([
761
+ getConfig(),
762
+ getPool(asset),
763
+ getPools(),
764
+ getUserState(client, address, true)
765
+ ]);
766
+ const assetState = allStates.find((s) => s.assetId === pool.id);
767
+ const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex) : 0;
768
+ const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
769
+ if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
770
+ const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
771
+ addOracleUpdatesForPositions(tx, config, pools, allStates, pool);
772
+ const [balance] = tx.moveCall({
773
+ target: `${config.package}::incentive_v3::withdraw_v2`,
774
+ arguments: [
775
+ tx.object(CLOCK),
776
+ tx.object(config.oracle.priceOracle),
777
+ tx.object(config.storage),
778
+ tx.object(pool.contract.pool),
779
+ tx.pure.u8(pool.id),
780
+ tx.pure.u64(rawAmount),
781
+ tx.object(config.incentiveV2),
782
+ tx.object(config.incentiveV3),
783
+ tx.object(SUI_SYSTEM_STATE)
784
+ ],
785
+ typeArguments: [pool.suiCoinType]
786
+ });
787
+ const [coin] = tx.moveCall({
788
+ target: "0x2::coin::from_balance",
789
+ arguments: [balance],
790
+ typeArguments: [pool.suiCoinType]
791
+ });
792
+ return { coin, effectiveAmount };
793
+ }
794
+ async function addSaveToTx(tx, _client, _address, coin, options = {}) {
795
+ const asset = options.asset ?? "USDC";
796
+ const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
797
+ if (options.collectFee) {
798
+ addCollectFeeToTx(tx, coin, "save");
799
+ }
800
+ const [coinValue] = tx.moveCall({
801
+ target: "0x2::coin::value",
802
+ typeArguments: [pool.suiCoinType],
803
+ arguments: [coin]
804
+ });
805
+ tx.moveCall({
806
+ target: `${config.package}::incentive_v3::entry_deposit`,
807
+ arguments: [
808
+ tx.object(CLOCK),
809
+ tx.object(config.storage),
810
+ tx.object(pool.contract.pool),
811
+ tx.pure.u8(pool.id),
812
+ coin,
813
+ coinValue,
814
+ tx.object(config.incentiveV2),
815
+ tx.object(config.incentiveV3)
816
+ ],
817
+ typeArguments: [pool.suiCoinType]
818
+ });
819
+ }
820
+ async function addRepayToTx(tx, client, _address, coin, options = {}) {
821
+ const asset = options.asset ?? "USDC";
822
+ const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
823
+ addOracleUpdate(tx, config, pool);
824
+ const [coinValue] = tx.moveCall({
825
+ target: "0x2::coin::value",
826
+ typeArguments: [pool.suiCoinType],
827
+ arguments: [coin]
828
+ });
829
+ tx.moveCall({
830
+ target: `${config.package}::incentive_v3::entry_repay`,
831
+ arguments: [
832
+ tx.object(CLOCK),
833
+ tx.object(config.oracle.priceOracle),
834
+ tx.object(config.storage),
835
+ tx.object(pool.contract.pool),
836
+ tx.pure.u8(pool.id),
837
+ coin,
838
+ coinValue,
839
+ tx.object(config.incentiveV2),
840
+ tx.object(config.incentiveV3)
841
+ ],
842
+ typeArguments: [pool.suiCoinType]
843
+ });
844
+ }
747
845
  async function buildBorrowTx(client, address, amount, options = {}) {
748
846
  if (!amount || amount <= 0 || !Number.isFinite(amount)) {
749
847
  throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
@@ -751,15 +849,15 @@ async function buildBorrowTx(client, address, amount, options = {}) {
751
849
  const asset = options.asset ?? "USDC";
752
850
  const assetInfo = SUPPORTED_ASSETS[asset];
753
851
  const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
754
- const [config, pool, pools, states] = await Promise.all([
852
+ const [config, pool, pools, allStates] = await Promise.all([
755
853
  getConfig(),
756
854
  getPool(asset),
757
855
  getPools(),
758
- getUserState(client, address)
856
+ getUserState(client, address, true)
759
857
  ]);
760
858
  const tx = new Transaction();
761
859
  tx.setSender(address);
762
- addOracleUpdatesForPositions(tx, config, pools, states, pool);
860
+ addOracleUpdatesForPositions(tx, config, pools, allStates, pool);
763
861
  const [balance] = tx.moveCall({
764
862
  target: `${config.package}::incentive_v3::borrow_v2`,
765
863
  arguments: [
@@ -1391,6 +1489,18 @@ var NaviAdapter = class {
1391
1489
  async maxBorrow(address, _asset) {
1392
1490
  return maxBorrowAmount(this.client, address);
1393
1491
  }
1492
+ async addWithdrawToTx(tx, address, amount, asset) {
1493
+ const stableAsset = normalizeAsset(asset);
1494
+ return addWithdrawToTx(tx, this.client, address, amount, { asset: stableAsset });
1495
+ }
1496
+ async addSaveToTx(tx, address, coin, asset, options) {
1497
+ const stableAsset = normalizeAsset(asset);
1498
+ return addSaveToTx(tx, this.client, address, coin, { ...options, asset: stableAsset });
1499
+ }
1500
+ async addRepayToTx(tx, address, coin, asset) {
1501
+ const stableAsset = normalizeAsset(asset);
1502
+ return addRepayToTx(tx, this.client, address, coin, { asset: stableAsset });
1503
+ }
1394
1504
  };
1395
1505
  var DEFAULT_SLIPPAGE_BPS = 300;
1396
1506
  function createAggregatorClient(client, signer) {
@@ -1435,6 +1545,41 @@ async function buildSwapTx(params) {
1435
1545
  toDecimals: toInfo.decimals
1436
1546
  };
1437
1547
  }
1548
+ async function addSwapToTx(params) {
1549
+ const { tx, client, address, inputCoin, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
1550
+ const fromInfo = SUPPORTED_ASSETS[fromAsset];
1551
+ const toInfo = SUPPORTED_ASSETS[toAsset];
1552
+ if (!fromInfo || !toInfo) {
1553
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
1554
+ }
1555
+ const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
1556
+ const aggClient = createAggregatorClient(client, address);
1557
+ const result = await aggClient.findRouters({
1558
+ from: fromInfo.type,
1559
+ target: toInfo.type,
1560
+ amount: rawAmount,
1561
+ byAmountIn: true
1562
+ });
1563
+ if (!result || result.insufficientLiquidity) {
1564
+ throw new T2000Error(
1565
+ "ASSET_NOT_SUPPORTED",
1566
+ `No swap route found for ${fromAsset} \u2192 ${toAsset}`
1567
+ );
1568
+ }
1569
+ const slippage = maxSlippageBps / 1e4;
1570
+ const outputCoin = await aggClient.routerSwap({
1571
+ router: result,
1572
+ txb: tx,
1573
+ inputCoin,
1574
+ slippage
1575
+ });
1576
+ const estimatedOut = Number(result.amountOut.toString());
1577
+ return {
1578
+ outputCoin,
1579
+ estimatedOut,
1580
+ toDecimals: toInfo.decimals
1581
+ };
1582
+ }
1438
1583
  async function getPoolPrice(client) {
1439
1584
  try {
1440
1585
  const pool = await client.getObject({
@@ -1545,6 +1690,18 @@ var CetusAdapter = class {
1545
1690
  async getPoolPrice() {
1546
1691
  return getPoolPrice(this.client);
1547
1692
  }
1693
+ async addSwapToTx(tx, address, inputCoin, from, to, amount, maxSlippageBps) {
1694
+ return addSwapToTx({
1695
+ tx,
1696
+ client: this.client,
1697
+ address,
1698
+ inputCoin,
1699
+ fromAsset: from,
1700
+ toAsset: to,
1701
+ amount,
1702
+ maxSlippageBps
1703
+ });
1704
+ }
1548
1705
  };
1549
1706
  SUPPORTED_ASSETS.USDC.type;
1550
1707
  var WAD = 1e18;
@@ -1941,6 +2098,95 @@ var SuilendAdapter = class {
1941
2098
  tx.transferObjects([coin], address);
1942
2099
  return { tx, effectiveAmount };
1943
2100
  }
2101
+ async addWithdrawToTx(tx, address, amount, asset) {
2102
+ const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
2103
+ const assetInfo = SUPPORTED_ASSETS[assetKey];
2104
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
2105
+ const reserve = this.findReserve(reserves, assetKey);
2106
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
2107
+ const caps = await this.fetchObligationCaps(address);
2108
+ if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
2109
+ const positions = await this.getPositions(address);
2110
+ const deposited = positions.supplies.find((s) => s.asset === assetKey)?.amount ?? 0;
2111
+ const effectiveAmount = Math.min(amount, deposited);
2112
+ if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
2113
+ const ratio = cTokenRatio(reserve);
2114
+ const ctokenAmount = Math.ceil(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
2115
+ const [ctokens] = tx.moveCall({
2116
+ target: `${pkg}::lending_market::withdraw_ctokens`,
2117
+ typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
2118
+ arguments: [
2119
+ tx.object(LENDING_MARKET_ID),
2120
+ tx.pure.u64(reserve.arrayIndex),
2121
+ tx.object(caps[0].id),
2122
+ tx.object(CLOCK2),
2123
+ tx.pure.u64(ctokenAmount)
2124
+ ]
2125
+ });
2126
+ const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${assetInfo.type}>`;
2127
+ const [none] = tx.moveCall({
2128
+ target: "0x1::option::none",
2129
+ typeArguments: [exemptionType]
2130
+ });
2131
+ const [coin] = tx.moveCall({
2132
+ target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity`,
2133
+ typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
2134
+ arguments: [
2135
+ tx.object(LENDING_MARKET_ID),
2136
+ tx.pure.u64(reserve.arrayIndex),
2137
+ tx.object(CLOCK2),
2138
+ ctokens,
2139
+ none
2140
+ ]
2141
+ });
2142
+ return { coin, effectiveAmount };
2143
+ }
2144
+ async addSaveToTx(tx, address, coin, asset, options) {
2145
+ const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
2146
+ const assetInfo = SUPPORTED_ASSETS[assetKey];
2147
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
2148
+ const reserve = this.findReserve(reserves, assetKey);
2149
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
2150
+ const caps = await this.fetchObligationCaps(address);
2151
+ let capRef;
2152
+ if (caps.length === 0) {
2153
+ const [newCap] = tx.moveCall({
2154
+ target: `${pkg}::lending_market::create_obligation`,
2155
+ typeArguments: [LENDING_MARKET_TYPE],
2156
+ arguments: [tx.object(LENDING_MARKET_ID)]
2157
+ });
2158
+ capRef = newCap;
2159
+ } else {
2160
+ capRef = caps[0].id;
2161
+ }
2162
+ if (options?.collectFee) {
2163
+ addCollectFeeToTx(tx, coin, "save");
2164
+ }
2165
+ const [ctokens] = tx.moveCall({
2166
+ target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
2167
+ typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
2168
+ arguments: [
2169
+ tx.object(LENDING_MARKET_ID),
2170
+ tx.pure.u64(reserve.arrayIndex),
2171
+ tx.object(CLOCK2),
2172
+ coin
2173
+ ]
2174
+ });
2175
+ tx.moveCall({
2176
+ target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
2177
+ typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
2178
+ arguments: [
2179
+ tx.object(LENDING_MARKET_ID),
2180
+ tx.pure.u64(reserve.arrayIndex),
2181
+ typeof capRef === "string" ? tx.object(capRef) : capRef,
2182
+ tx.object(CLOCK2),
2183
+ ctokens
2184
+ ]
2185
+ });
2186
+ if (typeof capRef !== "string") {
2187
+ tx.transferObjects([capRef], address);
2188
+ }
2189
+ }
1944
2190
  async buildBorrowTx(address, amount, asset, options) {
1945
2191
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1946
2192
  const assetInfo = SUPPORTED_ASSETS[assetKey];
@@ -2000,6 +2246,26 @@ var SuilendAdapter = class {
2000
2246
  });
2001
2247
  return { tx };
2002
2248
  }
2249
+ async addRepayToTx(tx, address, coin, asset) {
2250
+ const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
2251
+ const assetInfo = SUPPORTED_ASSETS[assetKey];
2252
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
2253
+ const reserve = this.findReserve(reserves, assetKey);
2254
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
2255
+ const caps = await this.fetchObligationCaps(address);
2256
+ if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
2257
+ tx.moveCall({
2258
+ target: `${pkg}::lending_market::repay`,
2259
+ typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
2260
+ arguments: [
2261
+ tx.object(LENDING_MARKET_ID),
2262
+ tx.pure.u64(reserve.arrayIndex),
2263
+ tx.object(caps[0].id),
2264
+ tx.object(CLOCK2),
2265
+ coin
2266
+ ]
2267
+ });
2268
+ }
2003
2269
  async maxWithdraw(address, _asset) {
2004
2270
  const health = await this.getHealth(address);
2005
2271
  let maxAmount;
@@ -2416,31 +2682,73 @@ var T2000 = class _T2000 extends EventEmitter {
2416
2682
  const asset = "USDC";
2417
2683
  const bal = await queryBalance(this.client, this._address);
2418
2684
  const usdcBalance = bal.stables.USDC ?? 0;
2685
+ const needsAutoConvert = params.amount === "all" ? Object.entries(bal.stables).some(([k, v]) => k !== "USDC" && v > 0.01) : typeof params.amount === "number" && params.amount > usdcBalance;
2419
2686
  let amount;
2420
2687
  if (params.amount === "all") {
2421
- await this._convertWalletStablesToUsdc(bal);
2422
- const refreshedBal = await queryBalance(this.client, this._address);
2423
- amount = (refreshedBal.stables.USDC ?? 0) - 1;
2688
+ amount = (bal.available ?? 0) - 1;
2424
2689
  if (amount <= 0) {
2425
2690
  throw new T2000Error("INSUFFICIENT_BALANCE", "Balance too low to save after $1 gas reserve", {
2426
2691
  reason: "gas_reserve_required",
2427
- available: refreshedBal.stables.USDC ?? 0
2692
+ available: bal.available ?? 0
2428
2693
  });
2429
2694
  }
2430
2695
  } else {
2431
2696
  amount = params.amount;
2432
- if (amount > usdcBalance) {
2433
- const totalStables = bal.available;
2434
- if (amount > totalStables) {
2435
- throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance. Available: $${totalStables.toFixed(2)}, requested: $${amount.toFixed(2)}`);
2436
- }
2437
- await this._convertWalletStablesToUsdc(bal, amount - usdcBalance);
2697
+ if (amount > (bal.available ?? 0)) {
2698
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance. Available: $${(bal.available ?? 0).toFixed(2)}, requested: $${amount.toFixed(2)}`);
2438
2699
  }
2439
2700
  }
2440
2701
  const fee = calculateFee("save", amount);
2441
2702
  const saveAmount = amount;
2442
2703
  const adapter = await this.resolveLending(params.protocol, asset, "save");
2704
+ const swapAdapter = this.registry.listSwap()[0];
2705
+ const canPTB = adapter.addSaveToTx && (!needsAutoConvert || swapAdapter?.addSwapToTx);
2443
2706
  const gasResult = await executeWithGas(this.client, this.keypair, async () => {
2707
+ if (canPTB && needsAutoConvert) {
2708
+ const tx2 = new Transaction();
2709
+ tx2.setSender(this._address);
2710
+ const usdcCoins = [];
2711
+ for (const [stableAsset, stableAmount] of Object.entries(bal.stables)) {
2712
+ if (stableAsset === "USDC" || stableAmount <= 0.01) continue;
2713
+ const assetInfo = SUPPORTED_ASSETS[stableAsset];
2714
+ if (!assetInfo) continue;
2715
+ const coins = await this._fetchCoins(assetInfo.type);
2716
+ if (coins.length === 0) continue;
2717
+ const merged = this._mergeCoinsInTx(tx2, coins);
2718
+ const { outputCoin } = await swapAdapter.addSwapToTx(
2719
+ tx2,
2720
+ this._address,
2721
+ merged,
2722
+ stableAsset,
2723
+ "USDC",
2724
+ stableAmount
2725
+ );
2726
+ usdcCoins.push(outputCoin);
2727
+ }
2728
+ const existingUsdc = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
2729
+ if (existingUsdc.length > 0) {
2730
+ usdcCoins.push(this._mergeCoinsInTx(tx2, existingUsdc));
2731
+ }
2732
+ if (usdcCoins.length > 1) {
2733
+ tx2.mergeCoins(usdcCoins[0], usdcCoins.slice(1));
2734
+ }
2735
+ await adapter.addSaveToTx(tx2, this._address, usdcCoins[0], asset, { collectFee: true });
2736
+ return tx2;
2737
+ }
2738
+ if (canPTB && !needsAutoConvert) {
2739
+ const tx2 = new Transaction();
2740
+ tx2.setSender(this._address);
2741
+ const existingUsdc = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
2742
+ if (existingUsdc.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
2743
+ const merged = this._mergeCoinsInTx(tx2, existingUsdc);
2744
+ const rawAmount = BigInt(Math.floor(saveAmount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
2745
+ const [depositCoin] = tx2.splitCoins(merged, [rawAmount]);
2746
+ await adapter.addSaveToTx(tx2, this._address, depositCoin, asset, { collectFee: true });
2747
+ return tx2;
2748
+ }
2749
+ if (needsAutoConvert) {
2750
+ await this._convertWalletStablesToUsdc(bal, params.amount === "all" ? void 0 : amount - usdcBalance);
2751
+ }
2444
2752
  const { tx } = await adapter.buildSaveTx(this._address, saveAmount, asset, { collectFee: true });
2445
2753
  return tx;
2446
2754
  });
@@ -2509,35 +2817,41 @@ var T2000 = class _T2000 extends EventEmitter {
2509
2817
  }
2510
2818
  }
2511
2819
  const withdrawAmount = amount;
2512
- let effectiveAmount = withdrawAmount;
2820
+ let finalAmount = withdrawAmount;
2821
+ const swapAdapter = target.asset !== "USDC" ? this.registry.listSwap()[0] : void 0;
2822
+ const canPTB = adapter.addWithdrawToTx && (!swapAdapter || swapAdapter.addSwapToTx);
2513
2823
  const gasResult = await executeWithGas(this.client, this.keypair, async () => {
2824
+ if (canPTB) {
2825
+ const tx = new Transaction();
2826
+ tx.setSender(this._address);
2827
+ const { coin, effectiveAmount } = await adapter.addWithdrawToTx(tx, this._address, withdrawAmount, target.asset);
2828
+ finalAmount = effectiveAmount;
2829
+ if (target.asset !== "USDC" && swapAdapter?.addSwapToTx) {
2830
+ const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
2831
+ tx,
2832
+ this._address,
2833
+ coin,
2834
+ target.asset,
2835
+ "USDC",
2836
+ effectiveAmount
2837
+ );
2838
+ finalAmount = estimatedOut / 10 ** toDecimals;
2839
+ tx.transferObjects([outputCoin], this._address);
2840
+ } else {
2841
+ tx.transferObjects([coin], this._address);
2842
+ }
2843
+ return tx;
2844
+ }
2514
2845
  const built = await adapter.buildWithdrawTx(this._address, withdrawAmount, target.asset);
2515
- effectiveAmount = built.effectiveAmount;
2846
+ finalAmount = built.effectiveAmount;
2516
2847
  return built.tx;
2517
2848
  });
2518
- let totalGasCost = gasResult.gasCostSui;
2519
- let finalAmount = effectiveAmount;
2520
- let lastDigest = gasResult.digest;
2521
- if (target.asset !== "USDC") {
2522
- try {
2523
- const swapResult = await this._swapToUsdc(target.asset, effectiveAmount);
2524
- finalAmount = swapResult.usdcReceived;
2525
- lastDigest = swapResult.digest;
2526
- totalGasCost += swapResult.gasCost;
2527
- } catch (err) {
2528
- throw new T2000Error(
2529
- "SWAP_FAILED",
2530
- `Withdrew $${effectiveAmount.toFixed(2)} ${target.asset} but swap to USDC failed. Your ${target.asset} is safe in your wallet.`,
2531
- { withdrawDigest: gasResult.digest, originalError: err instanceof Error ? err.message : String(err) }
2532
- );
2533
- }
2534
- }
2535
- this.emitBalanceChange("USDC", finalAmount, "withdraw", lastDigest);
2849
+ this.emitBalanceChange("USDC", finalAmount, "withdraw", gasResult.digest);
2536
2850
  return {
2537
2851
  success: true,
2538
- tx: lastDigest,
2852
+ tx: gasResult.digest,
2539
2853
  amount: finalAmount,
2540
- gasCost: totalGasCost,
2854
+ gasCost: gasResult.gasCostSui,
2541
2855
  gasMethod: gasResult.gasMethod
2542
2856
  };
2543
2857
  }
@@ -2554,49 +2868,94 @@ var T2000 = class _T2000 extends EventEmitter {
2554
2868
  if (withdrawable.length === 0) {
2555
2869
  throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
2556
2870
  }
2557
- let totalUsdcReceived = 0;
2558
- let lastDigest = "";
2559
- let totalGasCost = 0;
2560
- let lastGasMethod = "self-funded";
2871
+ const entries = [];
2561
2872
  for (const entry of withdrawable) {
2562
2873
  const adapter = this.registry.getLending(entry.protocolId);
2563
2874
  if (!adapter) continue;
2564
2875
  const maxResult = await adapter.maxWithdraw(this._address, entry.asset);
2565
- if (maxResult.maxAmount <= 1e-3) continue;
2566
- let effectiveAmount = maxResult.maxAmount;
2567
- const gasResult = await executeWithGas(this.client, this.keypair, async () => {
2568
- const built = await adapter.buildWithdrawTx(this._address, maxResult.maxAmount, entry.asset);
2569
- effectiveAmount = built.effectiveAmount;
2570
- return built.tx;
2571
- });
2572
- lastDigest = gasResult.digest;
2573
- totalGasCost += gasResult.gasCostSui;
2574
- lastGasMethod = gasResult.gasMethod;
2575
- this.emitBalanceChange(entry.asset, effectiveAmount, "withdraw", gasResult.digest);
2576
- if (entry.asset !== "USDC") {
2577
- try {
2578
- const swapResult = await this._swapToUsdc(entry.asset, effectiveAmount);
2579
- totalUsdcReceived += swapResult.usdcReceived;
2580
- lastDigest = swapResult.digest;
2581
- totalGasCost += swapResult.gasCost;
2582
- } catch {
2583
- totalUsdcReceived += effectiveAmount;
2584
- }
2585
- } else {
2586
- totalUsdcReceived += effectiveAmount;
2876
+ if (maxResult.maxAmount > 1e-3) {
2877
+ entries.push({ ...entry, maxAmount: maxResult.maxAmount, adapter });
2587
2878
  }
2588
2879
  }
2880
+ if (entries.length === 0) {
2881
+ throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
2882
+ }
2883
+ const swapAdapter = this.registry.listSwap()[0];
2884
+ const canPTB = entries.every((e) => e.adapter.addWithdrawToTx) && (!swapAdapter || swapAdapter.addSwapToTx);
2885
+ let totalUsdcReceived = 0;
2886
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
2887
+ if (canPTB) {
2888
+ const tx = new Transaction();
2889
+ tx.setSender(this._address);
2890
+ const usdcCoins = [];
2891
+ for (const entry of entries) {
2892
+ const { coin, effectiveAmount } = await entry.adapter.addWithdrawToTx(
2893
+ tx,
2894
+ this._address,
2895
+ entry.maxAmount,
2896
+ entry.asset
2897
+ );
2898
+ if (entry.asset !== "USDC" && swapAdapter?.addSwapToTx) {
2899
+ const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
2900
+ tx,
2901
+ this._address,
2902
+ coin,
2903
+ entry.asset,
2904
+ "USDC",
2905
+ effectiveAmount
2906
+ );
2907
+ totalUsdcReceived += estimatedOut / 10 ** toDecimals;
2908
+ usdcCoins.push(outputCoin);
2909
+ } else {
2910
+ totalUsdcReceived += effectiveAmount;
2911
+ usdcCoins.push(coin);
2912
+ }
2913
+ }
2914
+ if (usdcCoins.length > 1) {
2915
+ tx.mergeCoins(usdcCoins[0], usdcCoins.slice(1));
2916
+ }
2917
+ tx.transferObjects([usdcCoins[0]], this._address);
2918
+ return tx;
2919
+ }
2920
+ let lastTx;
2921
+ for (const entry of entries) {
2922
+ const built = await entry.adapter.buildWithdrawTx(this._address, entry.maxAmount, entry.asset);
2923
+ totalUsdcReceived += built.effectiveAmount;
2924
+ lastTx = built.tx;
2925
+ }
2926
+ return lastTx;
2927
+ });
2589
2928
  if (totalUsdcReceived <= 0) {
2590
2929
  throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
2591
2930
  }
2592
2931
  return {
2593
2932
  success: true,
2594
- tx: lastDigest,
2933
+ tx: gasResult.digest,
2595
2934
  amount: totalUsdcReceived,
2596
- gasCost: totalGasCost,
2597
- gasMethod: lastGasMethod
2935
+ gasCost: gasResult.gasCostSui,
2936
+ gasMethod: gasResult.gasMethod
2598
2937
  };
2599
2938
  }
2939
+ async _fetchCoins(coinType) {
2940
+ const all = [];
2941
+ let cursor;
2942
+ let hasNext = true;
2943
+ while (hasNext) {
2944
+ const page = await this.client.getCoins({ owner: this._address, coinType, cursor: cursor ?? void 0 });
2945
+ all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
2946
+ cursor = page.nextCursor;
2947
+ hasNext = page.hasNextPage;
2948
+ }
2949
+ return all;
2950
+ }
2951
+ _mergeCoinsInTx(tx, coins) {
2952
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
2953
+ const primary = tx.object(coins[0].coinObjectId);
2954
+ if (coins.length > 1) {
2955
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
2956
+ }
2957
+ return primary;
2958
+ }
2600
2959
  async _swapToUsdc(asset, amount) {
2601
2960
  const swapAdapter = this.registry.listSwap()[0];
2602
2961
  if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
@@ -2701,72 +3060,122 @@ var T2000 = class _T2000 extends EventEmitter {
2701
3060
  const adapter = this.registry.getLending(target.protocolId);
2702
3061
  if (!adapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${target.protocolId} not found`);
2703
3062
  const repayAmount = Math.min(params.amount, target.amount);
2704
- let totalGasCost = 0;
2705
- let lastDigest = "";
2706
- if (target.asset !== "USDC") {
2707
- const buffer = repayAmount * 1.005;
2708
- const swapResult = await this._swapFromUsdc(target.asset, buffer);
2709
- totalGasCost += swapResult.gasCost;
2710
- lastDigest = swapResult.digest;
2711
- }
3063
+ const swapAdapter = target.asset !== "USDC" ? this.registry.listSwap()[0] : void 0;
3064
+ const canPTB = adapter.addRepayToTx && (!swapAdapter || swapAdapter.addSwapToTx);
2712
3065
  const gasResult = await executeWithGas(this.client, this.keypair, async () => {
3066
+ if (canPTB && target.asset !== "USDC" && swapAdapter?.addSwapToTx) {
3067
+ const tx2 = new Transaction();
3068
+ tx2.setSender(this._address);
3069
+ const buffer = repayAmount * 1.005;
3070
+ const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
3071
+ if (usdcCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins for swap");
3072
+ const merged = this._mergeCoinsInTx(tx2, usdcCoins);
3073
+ const rawSwap = BigInt(Math.floor(buffer * 10 ** SUPPORTED_ASSETS.USDC.decimals));
3074
+ const [splitCoin] = tx2.splitCoins(merged, [rawSwap]);
3075
+ const { outputCoin } = await swapAdapter.addSwapToTx(
3076
+ tx2,
3077
+ this._address,
3078
+ splitCoin,
3079
+ "USDC",
3080
+ target.asset,
3081
+ buffer
3082
+ );
3083
+ await adapter.addRepayToTx(tx2, this._address, outputCoin, target.asset);
3084
+ return tx2;
3085
+ }
3086
+ if (canPTB && target.asset === "USDC") {
3087
+ const tx2 = new Transaction();
3088
+ tx2.setSender(this._address);
3089
+ const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
3090
+ if (usdcCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins");
3091
+ const merged = this._mergeCoinsInTx(tx2, usdcCoins);
3092
+ const raw = BigInt(Math.floor(repayAmount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
3093
+ const [repayCoin] = tx2.splitCoins(merged, [raw]);
3094
+ await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
3095
+ return tx2;
3096
+ }
3097
+ if (target.asset !== "USDC") {
3098
+ await this._swapFromUsdc(target.asset, repayAmount * 1.005);
3099
+ }
2713
3100
  const { tx } = await adapter.buildRepayTx(this._address, repayAmount, target.asset);
2714
3101
  return tx;
2715
3102
  });
2716
- totalGasCost += gasResult.gasCostSui;
2717
- lastDigest = gasResult.digest;
2718
3103
  const hf = await adapter.getHealth(this._address);
2719
- this.emitBalanceChange("USDC", repayAmount, "repay", lastDigest);
3104
+ this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
2720
3105
  return {
2721
3106
  success: true,
2722
- tx: lastDigest,
3107
+ tx: gasResult.digest,
2723
3108
  amount: repayAmount,
2724
3109
  remainingDebt: hf.borrowed,
2725
- gasCost: totalGasCost,
3110
+ gasCost: gasResult.gasCostSui,
2726
3111
  gasMethod: gasResult.gasMethod
2727
3112
  };
2728
3113
  }
2729
3114
  async _repayAllBorrows(borrows) {
2730
- let totalRepaid = 0;
2731
- let totalGasCost = 0;
2732
- let lastDigest = "";
2733
- let lastGasMethod = "self-funded";
2734
3115
  borrows.sort((a, b) => b.apy - a.apy);
3116
+ const entries = [];
2735
3117
  for (const borrow of borrows) {
2736
3118
  const adapter = this.registry.getLending(borrow.protocolId);
2737
- if (!adapter) continue;
2738
- if (borrow.asset !== "USDC") {
2739
- try {
2740
- const buffer = borrow.amount * 1.005;
2741
- const swapResult = await this._swapFromUsdc(borrow.asset, buffer);
2742
- totalGasCost += swapResult.gasCost;
2743
- } catch (err) {
2744
- throw new T2000Error(
2745
- "SWAP_FAILED",
2746
- `Could not convert USDC to ${borrow.asset} for repayment. Try again later.`,
2747
- { originalError: err instanceof Error ? err.message : String(err) }
2748
- );
3119
+ if (adapter) entries.push({ borrow, adapter });
3120
+ }
3121
+ const swapAdapter = this.registry.listSwap()[0];
3122
+ const canPTB = entries.every((e) => e.adapter.addRepayToTx) && (entries.every((e) => e.borrow.asset === "USDC") || swapAdapter?.addSwapToTx);
3123
+ let totalRepaid = 0;
3124
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
3125
+ if (canPTB) {
3126
+ const tx = new Transaction();
3127
+ tx.setSender(this._address);
3128
+ const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
3129
+ let usdcMerged;
3130
+ if (usdcCoins.length > 0) {
3131
+ usdcMerged = this._mergeCoinsInTx(tx, usdcCoins);
3132
+ }
3133
+ for (const { borrow, adapter } of entries) {
3134
+ if (borrow.asset !== "USDC" && swapAdapter?.addSwapToTx) {
3135
+ const buffer = borrow.amount * 1.005;
3136
+ const rawSwap = BigInt(Math.floor(buffer * 10 ** SUPPORTED_ASSETS.USDC.decimals));
3137
+ if (!usdcMerged) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC for swap");
3138
+ const [splitCoin] = tx.splitCoins(usdcMerged, [rawSwap]);
3139
+ const { outputCoin } = await swapAdapter.addSwapToTx(
3140
+ tx,
3141
+ this._address,
3142
+ splitCoin,
3143
+ "USDC",
3144
+ borrow.asset,
3145
+ buffer
3146
+ );
3147
+ await adapter.addRepayToTx(tx, this._address, outputCoin, borrow.asset);
3148
+ } else {
3149
+ const raw = BigInt(Math.floor(borrow.amount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
3150
+ if (!usdcMerged) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC for repayment");
3151
+ const [repayCoin] = tx.splitCoins(usdcMerged, [raw]);
3152
+ await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
3153
+ }
3154
+ totalRepaid += borrow.amount;
2749
3155
  }
3156
+ return tx;
2750
3157
  }
2751
- const gasResult = await executeWithGas(this.client, this.keypair, async () => {
3158
+ let lastTx;
3159
+ for (const { borrow, adapter } of entries) {
3160
+ if (borrow.asset !== "USDC") {
3161
+ await this._swapFromUsdc(borrow.asset, borrow.amount * 1.005);
3162
+ }
2752
3163
  const { tx } = await adapter.buildRepayTx(this._address, borrow.amount, borrow.asset);
2753
- return tx;
2754
- });
2755
- totalRepaid += borrow.amount;
2756
- totalGasCost += gasResult.gasCostSui;
2757
- lastDigest = gasResult.digest;
2758
- lastGasMethod = gasResult.gasMethod;
2759
- }
2760
- const firstAdapter = this.registry.getLending(borrows[0].protocolId);
3164
+ lastTx = tx;
3165
+ totalRepaid += borrow.amount;
3166
+ }
3167
+ return lastTx;
3168
+ });
3169
+ const firstAdapter = entries[0]?.adapter;
2761
3170
  const hf = firstAdapter ? await firstAdapter.getHealth(this._address) : { borrowed: 0 };
2762
- this.emitBalanceChange("USDC", totalRepaid, "repay", lastDigest);
3171
+ this.emitBalanceChange("USDC", totalRepaid, "repay", gasResult.digest);
2763
3172
  return {
2764
3173
  success: true,
2765
- tx: lastDigest,
3174
+ tx: gasResult.digest,
2766
3175
  amount: totalRepaid,
2767
3176
  remainingDebt: hf.borrowed,
2768
- gasCost: totalGasCost,
2769
- gasMethod: lastGasMethod
3177
+ gasCost: gasResult.gasCostSui,
3178
+ gasMethod: gasResult.gasMethod
2770
3179
  };
2771
3180
  }
2772
3181
  async maxBorrow() {
@@ -3056,35 +3465,76 @@ var T2000 = class _T2000 extends EventEmitter {
3056
3465
  totalGasCost: 0
3057
3466
  };
3058
3467
  }
3059
- const txDigests = [];
3060
- let totalGasCost = 0;
3061
3468
  if (!withdrawAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${current.protocolId} not found`);
3062
- const withdrawResult = await executeWithGas(this.client, this.keypair, async () => {
3063
- const built = await withdrawAdapter.buildWithdrawTx(this._address, current.amount, current.asset);
3064
- amountToDeposit = isSameAsset ? built.effectiveAmount : built.effectiveAmount;
3065
- return built.tx;
3066
- });
3067
- txDigests.push(withdrawResult.digest);
3068
- totalGasCost += withdrawResult.gasCostSui;
3069
- if (!isSameAsset) {
3070
- const swapAdapter = this.registry.listSwap()[0];
3071
- if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
3072
- const swapResult = await executeWithGas(this.client, this.keypair, async () => {
3073
- const built = await swapAdapter.buildSwapTx(this._address, current.asset, bestRate.asset, amountToDeposit);
3074
- amountToDeposit = built.estimatedOut / 10 ** built.toDecimals;
3469
+ const depositAdapter = this.registry.getLending(bestRate.protocolId);
3470
+ if (!depositAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${bestRate.protocolId} not found`);
3471
+ const canComposePTB = withdrawAdapter.addWithdrawToTx && depositAdapter.addSaveToTx && (isSameAsset || this.registry.listSwap()[0]?.addSwapToTx);
3472
+ let txDigests;
3473
+ let totalGasCost;
3474
+ if (canComposePTB) {
3475
+ const result = await executeWithGas(this.client, this.keypair, async () => {
3476
+ const tx = new Transaction();
3477
+ tx.setSender(this._address);
3478
+ const { coin: withdrawnCoin, effectiveAmount } = await withdrawAdapter.addWithdrawToTx(
3479
+ tx,
3480
+ this._address,
3481
+ current.amount,
3482
+ current.asset
3483
+ );
3484
+ amountToDeposit = effectiveAmount;
3485
+ let depositCoin = withdrawnCoin;
3486
+ if (!isSameAsset) {
3487
+ const swapAdapter = this.registry.listSwap()[0];
3488
+ const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
3489
+ tx,
3490
+ this._address,
3491
+ withdrawnCoin,
3492
+ current.asset,
3493
+ bestRate.asset,
3494
+ amountToDeposit
3495
+ );
3496
+ depositCoin = outputCoin;
3497
+ amountToDeposit = estimatedOut / 10 ** toDecimals;
3498
+ }
3499
+ await depositAdapter.addSaveToTx(
3500
+ tx,
3501
+ this._address,
3502
+ depositCoin,
3503
+ bestRate.asset,
3504
+ { collectFee: bestRate.asset === "USDC" }
3505
+ );
3506
+ return tx;
3507
+ });
3508
+ txDigests = [result.digest];
3509
+ totalGasCost = result.gasCostSui;
3510
+ } else {
3511
+ txDigests = [];
3512
+ totalGasCost = 0;
3513
+ const withdrawResult = await executeWithGas(this.client, this.keypair, async () => {
3514
+ const built = await withdrawAdapter.buildWithdrawTx(this._address, current.amount, current.asset);
3515
+ amountToDeposit = built.effectiveAmount;
3075
3516
  return built.tx;
3076
3517
  });
3077
- txDigests.push(swapResult.digest);
3078
- totalGasCost += swapResult.gasCostSui;
3518
+ txDigests.push(withdrawResult.digest);
3519
+ totalGasCost += withdrawResult.gasCostSui;
3520
+ if (!isSameAsset) {
3521
+ const swapAdapter = this.registry.listSwap()[0];
3522
+ if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
3523
+ const swapResult = await executeWithGas(this.client, this.keypair, async () => {
3524
+ const built = await swapAdapter.buildSwapTx(this._address, current.asset, bestRate.asset, amountToDeposit);
3525
+ amountToDeposit = built.estimatedOut / 10 ** built.toDecimals;
3526
+ return built.tx;
3527
+ });
3528
+ txDigests.push(swapResult.digest);
3529
+ totalGasCost += swapResult.gasCostSui;
3530
+ }
3531
+ const depositResult = await executeWithGas(this.client, this.keypair, async () => {
3532
+ const { tx } = await depositAdapter.buildSaveTx(this._address, amountToDeposit, bestRate.asset, { collectFee: bestRate.asset === "USDC" });
3533
+ return tx;
3534
+ });
3535
+ txDigests.push(depositResult.digest);
3536
+ totalGasCost += depositResult.gasCostSui;
3079
3537
  }
3080
- const depositAdapter = this.registry.getLending(bestRate.protocolId);
3081
- if (!depositAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${bestRate.protocolId} not found`);
3082
- const depositResult = await executeWithGas(this.client, this.keypair, async () => {
3083
- const { tx } = await depositAdapter.buildSaveTx(this._address, amountToDeposit, bestRate.asset, { collectFee: bestRate.asset === "USDC" });
3084
- return tx;
3085
- });
3086
- txDigests.push(depositResult.digest);
3087
- totalGasCost += depositResult.gasCostSui;
3088
3538
  return {
3089
3539
  executed: true,
3090
3540
  steps,