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