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