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