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