@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/README.md +10 -3
- package/dist/adapters/index.cjs +268 -10
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +268 -10
- package/dist/adapters/index.js.map +1 -1
- package/dist/{index-C7W686z2.d.cts → index-CrLRqDFL.d.cts} +34 -0
- package/dist/{index-C7W686z2.d.ts → index-CrLRqDFL.d.ts} +34 -0
- package/dist/index.cjs +591 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +591 -141
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
}))
|
|
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,
|
|
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 =
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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:
|
|
2694
|
+
available: bal.available ?? 0
|
|
2430
2695
|
});
|
|
2431
2696
|
}
|
|
2432
2697
|
} else {
|
|
2433
2698
|
amount = params.amount;
|
|
2434
|
-
if (amount >
|
|
2435
|
-
|
|
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
|
|
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
|
-
|
|
2848
|
+
finalAmount = built.effectiveAmount;
|
|
2518
2849
|
return built.tx;
|
|
2519
2850
|
});
|
|
2520
|
-
|
|
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:
|
|
2854
|
+
tx: gasResult.digest,
|
|
2541
2855
|
amount: finalAmount,
|
|
2542
|
-
gasCost:
|
|
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
|
-
|
|
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
|
|
2568
|
-
|
|
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:
|
|
2935
|
+
tx: gasResult.digest,
|
|
2597
2936
|
amount: totalUsdcReceived,
|
|
2598
|
-
gasCost:
|
|
2599
|
-
gasMethod:
|
|
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
|
-
|
|
2707
|
-
|
|
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",
|
|
3106
|
+
this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
|
|
2722
3107
|
return {
|
|
2723
3108
|
success: true,
|
|
2724
|
-
tx:
|
|
3109
|
+
tx: gasResult.digest,
|
|
2725
3110
|
amount: repayAmount,
|
|
2726
3111
|
remainingDebt: hf.borrowed,
|
|
2727
|
-
gasCost:
|
|
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 (
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
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",
|
|
3173
|
+
this.emitBalanceChange("USDC", totalRepaid, "repay", gasResult.digest);
|
|
2765
3174
|
return {
|
|
2766
3175
|
success: true,
|
|
2767
|
-
tx:
|
|
3176
|
+
tx: gasResult.digest,
|
|
2768
3177
|
amount: totalRepaid,
|
|
2769
3178
|
remainingDebt: hf.borrowed,
|
|
2770
|
-
gasCost:
|
|
2771
|
-
gasMethod:
|
|
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
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
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(
|
|
3080
|
-
totalGasCost +=
|
|
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,
|