@t2000/sdk 0.7.2 → 0.8.0
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 +19 -6
- package/dist/adapters/index.cjs +338 -128
- 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 +338 -128
- package/dist/adapters/index.js.map +1 -1
- package/dist/{index-rT0oHn8M.d.cts → index-DNjooNFy.d.cts} +54 -14
- package/dist/{index-rT0oHn8M.d.ts → index-DNjooNFy.d.ts} +54 -14
- package/dist/index.cjs +641 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -3
- package/dist/index.d.ts +45 -3
- package/dist/index.js +637 -162
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,14 +29,35 @@ var SUPPORTED_ASSETS = {
|
|
|
29
29
|
USDC: {
|
|
30
30
|
type: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
|
|
31
31
|
decimals: 6,
|
|
32
|
-
symbol: "USDC"
|
|
32
|
+
symbol: "USDC",
|
|
33
|
+
displayName: "USDC"
|
|
34
|
+
},
|
|
35
|
+
USDT: {
|
|
36
|
+
type: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
|
|
37
|
+
decimals: 6,
|
|
38
|
+
symbol: "USDT",
|
|
39
|
+
displayName: "suiUSDT"
|
|
40
|
+
},
|
|
41
|
+
USDe: {
|
|
42
|
+
type: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
|
|
43
|
+
decimals: 6,
|
|
44
|
+
symbol: "USDe",
|
|
45
|
+
displayName: "suiUSDe"
|
|
46
|
+
},
|
|
47
|
+
USDsui: {
|
|
48
|
+
type: "0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI",
|
|
49
|
+
decimals: 6,
|
|
50
|
+
symbol: "USDsui",
|
|
51
|
+
displayName: "USDsui"
|
|
33
52
|
},
|
|
34
53
|
SUI: {
|
|
35
54
|
type: "0x2::sui::SUI",
|
|
36
55
|
decimals: 9,
|
|
37
|
-
symbol: "SUI"
|
|
56
|
+
symbol: "SUI",
|
|
57
|
+
displayName: "SUI"
|
|
38
58
|
}
|
|
39
59
|
};
|
|
60
|
+
var STABLE_ASSETS = ["USDC", "USDT", "USDe", "USDsui"];
|
|
40
61
|
var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
|
|
41
62
|
var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
|
|
42
63
|
var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
|
|
@@ -236,6 +257,15 @@ function usdcToRaw(amount) {
|
|
|
236
257
|
function rawToUsdc(raw) {
|
|
237
258
|
return Number(raw) / 10 ** USDC_DECIMALS;
|
|
238
259
|
}
|
|
260
|
+
function stableToRaw(amount, decimals) {
|
|
261
|
+
return BigInt(Math.round(amount * 10 ** decimals));
|
|
262
|
+
}
|
|
263
|
+
function rawToStable(raw, decimals) {
|
|
264
|
+
return Number(raw) / 10 ** decimals;
|
|
265
|
+
}
|
|
266
|
+
function getDecimals(asset) {
|
|
267
|
+
return SUPPORTED_ASSETS[asset].decimals;
|
|
268
|
+
}
|
|
239
269
|
function displayToRaw(amount, decimals) {
|
|
240
270
|
return BigInt(Math.round(amount * 10 ** decimals));
|
|
241
271
|
}
|
|
@@ -246,6 +276,12 @@ function formatSui(amount) {
|
|
|
246
276
|
if (amount < 1e-3) return `${amount.toFixed(6)} SUI`;
|
|
247
277
|
return `${amount.toFixed(3)} SUI`;
|
|
248
278
|
}
|
|
279
|
+
var ASSET_LOOKUP = new Map(
|
|
280
|
+
Object.keys(SUPPORTED_ASSETS).map((k) => [k.toUpperCase(), k])
|
|
281
|
+
);
|
|
282
|
+
function normalizeAsset(input) {
|
|
283
|
+
return ASSET_LOOKUP.get(input.toUpperCase()) ?? input;
|
|
284
|
+
}
|
|
249
285
|
|
|
250
286
|
// src/wallet/send.ts
|
|
251
287
|
async function buildSendTx({
|
|
@@ -318,26 +354,35 @@ async function fetchSuiPrice(client) {
|
|
|
318
354
|
return _cachedSuiPrice;
|
|
319
355
|
}
|
|
320
356
|
async function queryBalance(client, address) {
|
|
321
|
-
const
|
|
322
|
-
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.
|
|
357
|
+
const stableBalancePromises = STABLE_ASSETS.map(
|
|
358
|
+
(asset) => client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS[asset].type }).then((b) => ({ asset, amount: Number(b.totalBalance) / 10 ** SUPPORTED_ASSETS[asset].decimals }))
|
|
359
|
+
);
|
|
360
|
+
const [suiBalance, suiPriceUsd, ...stableResults] = await Promise.all([
|
|
323
361
|
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
|
|
324
|
-
fetchSuiPrice(client)
|
|
362
|
+
fetchSuiPrice(client),
|
|
363
|
+
...stableBalancePromises
|
|
325
364
|
]);
|
|
326
|
-
const
|
|
365
|
+
const stables = {};
|
|
366
|
+
let totalStables = 0;
|
|
367
|
+
for (const { asset, amount } of stableResults) {
|
|
368
|
+
stables[asset] = amount;
|
|
369
|
+
totalStables += amount;
|
|
370
|
+
}
|
|
327
371
|
const suiAmount = Number(suiBalance.totalBalance) / Number(MIST_PER_SUI);
|
|
328
372
|
const savings = 0;
|
|
329
373
|
const usdEquiv = suiAmount * suiPriceUsd;
|
|
330
|
-
const total =
|
|
374
|
+
const total = totalStables + savings + usdEquiv;
|
|
331
375
|
return {
|
|
332
|
-
available:
|
|
376
|
+
available: totalStables,
|
|
333
377
|
savings,
|
|
334
378
|
gasReserve: {
|
|
335
379
|
sui: suiAmount,
|
|
336
380
|
usdEquiv
|
|
337
381
|
},
|
|
338
382
|
total,
|
|
383
|
+
stables,
|
|
339
384
|
assets: {
|
|
340
|
-
USDC:
|
|
385
|
+
USDC: stables.USDC ?? 0,
|
|
341
386
|
SUI: suiAmount
|
|
342
387
|
}
|
|
343
388
|
};
|
|
@@ -425,7 +470,7 @@ async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest)
|
|
|
425
470
|
} catch {
|
|
426
471
|
}
|
|
427
472
|
}
|
|
428
|
-
|
|
473
|
+
SUPPORTED_ASSETS.USDC.type;
|
|
429
474
|
var RATE_DECIMALS = 27;
|
|
430
475
|
var LTV_DECIMALS = 27;
|
|
431
476
|
var MIN_HEALTH_FACTOR = 1.5;
|
|
@@ -486,13 +531,24 @@ async function getPools(fresh = false) {
|
|
|
486
531
|
poolsCache = { data, ts: Date.now() };
|
|
487
532
|
return data;
|
|
488
533
|
}
|
|
489
|
-
|
|
534
|
+
function matchesCoinType(poolType, targetType) {
|
|
535
|
+
const poolSuffix = poolType.split("::").slice(1).join("::").toLowerCase();
|
|
536
|
+
const targetSuffix = targetType.split("::").slice(1).join("::").toLowerCase();
|
|
537
|
+
return poolSuffix === targetSuffix;
|
|
538
|
+
}
|
|
539
|
+
async function getPool(asset = "USDC") {
|
|
490
540
|
const pools = await getPools();
|
|
491
|
-
const
|
|
492
|
-
|
|
541
|
+
const targetType = SUPPORTED_ASSETS[asset].type;
|
|
542
|
+
const pool = pools.find(
|
|
543
|
+
(p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType)
|
|
493
544
|
);
|
|
494
|
-
if (!
|
|
495
|
-
|
|
545
|
+
if (!pool) {
|
|
546
|
+
throw new T2000Error(
|
|
547
|
+
"ASSET_NOT_SUPPORTED",
|
|
548
|
+
`${SUPPORTED_ASSETS[asset].displayName} pool not found on NAVI. Try: ${STABLE_ASSETS.filter((a) => a !== asset).join(", ")}`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
return pool;
|
|
496
552
|
}
|
|
497
553
|
function addOracleUpdate(tx, config, pool) {
|
|
498
554
|
const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
|
|
@@ -580,10 +636,12 @@ async function buildSaveTx(client, address, amount, options = {}) {
|
|
|
580
636
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
581
637
|
throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
|
|
582
638
|
}
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
|
|
639
|
+
const asset = options.asset ?? "USDC";
|
|
640
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
641
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
642
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
643
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
644
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
587
645
|
const tx = new Transaction();
|
|
588
646
|
tx.setSender(address);
|
|
589
647
|
const coinObj = mergeCoins(tx, coins);
|
|
@@ -606,18 +664,20 @@ async function buildSaveTx(client, address, amount, options = {}) {
|
|
|
606
664
|
});
|
|
607
665
|
return tx;
|
|
608
666
|
}
|
|
609
|
-
async function buildWithdrawTx(client, address, amount) {
|
|
667
|
+
async function buildWithdrawTx(client, address, amount, options = {}) {
|
|
668
|
+
const asset = options.asset ?? "USDC";
|
|
669
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
610
670
|
const [config, pool, pools, states] = await Promise.all([
|
|
611
671
|
getConfig(),
|
|
612
|
-
|
|
672
|
+
getPool(asset),
|
|
613
673
|
getPools(),
|
|
614
674
|
getUserState(client, address)
|
|
615
675
|
]);
|
|
616
|
-
const
|
|
617
|
-
const deposited =
|
|
676
|
+
const assetState = states.find((s) => s.assetId === pool.id);
|
|
677
|
+
const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex) : 0;
|
|
618
678
|
const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
|
|
619
|
-
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL",
|
|
620
|
-
const rawAmount = Number(
|
|
679
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
|
|
680
|
+
const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
|
|
621
681
|
const tx = new Transaction();
|
|
622
682
|
tx.setSender(address);
|
|
623
683
|
addOracleUpdate(tx, config, pool);
|
|
@@ -648,8 +708,10 @@ async function buildBorrowTx(client, address, amount, options = {}) {
|
|
|
648
708
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
649
709
|
throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
|
|
650
710
|
}
|
|
651
|
-
const
|
|
652
|
-
const
|
|
711
|
+
const asset = options.asset ?? "USDC";
|
|
712
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
713
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
714
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
653
715
|
const tx = new Transaction();
|
|
654
716
|
tx.setSender(address);
|
|
655
717
|
addOracleUpdate(tx, config, pool);
|
|
@@ -679,14 +741,16 @@ async function buildBorrowTx(client, address, amount, options = {}) {
|
|
|
679
741
|
tx.transferObjects([borrowedCoin], address);
|
|
680
742
|
return tx;
|
|
681
743
|
}
|
|
682
|
-
async function buildRepayTx(client, address, amount) {
|
|
744
|
+
async function buildRepayTx(client, address, amount, options = {}) {
|
|
683
745
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
684
746
|
throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
|
|
685
747
|
}
|
|
686
|
-
const
|
|
687
|
-
const
|
|
688
|
-
const
|
|
689
|
-
|
|
748
|
+
const asset = options.asset ?? "USDC";
|
|
749
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
750
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
751
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
752
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
753
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
690
754
|
const tx = new Transaction();
|
|
691
755
|
tx.setSender(address);
|
|
692
756
|
addOracleUpdate(tx, config, pool);
|
|
@@ -710,21 +774,36 @@ async function buildRepayTx(client, address, amount) {
|
|
|
710
774
|
}
|
|
711
775
|
async function getHealthFactor(client, addressOrKeypair) {
|
|
712
776
|
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
713
|
-
const [config,
|
|
777
|
+
const [config, pools, states] = await Promise.all([
|
|
714
778
|
getConfig(),
|
|
715
|
-
|
|
779
|
+
getPools(),
|
|
716
780
|
getUserState(client, address)
|
|
717
781
|
]);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const
|
|
782
|
+
let supplied = 0;
|
|
783
|
+
let borrowed = 0;
|
|
784
|
+
let weightedLtv = 0;
|
|
785
|
+
let weightedLiqThreshold = 0;
|
|
786
|
+
for (const state of states) {
|
|
787
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
788
|
+
if (!pool) continue;
|
|
789
|
+
const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex);
|
|
790
|
+
const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex);
|
|
791
|
+
const price = pool.token?.price ?? 1;
|
|
792
|
+
supplied += supplyBal * price;
|
|
793
|
+
borrowed += borrowBal * price;
|
|
794
|
+
if (supplyBal > 0) {
|
|
795
|
+
weightedLtv += supplyBal * price * parseLtv(pool.ltv);
|
|
796
|
+
weightedLiqThreshold += supplyBal * price * parseLiqThreshold(pool.liquidationFactor.threshold);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
const ltv = supplied > 0 ? weightedLtv / supplied : 0.75;
|
|
800
|
+
const liqThreshold = supplied > 0 ? weightedLiqThreshold / supplied : 0.75;
|
|
723
801
|
const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
|
|
802
|
+
const usdcPool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", SUPPORTED_ASSETS.USDC.type));
|
|
724
803
|
let healthFactor;
|
|
725
804
|
if (borrowed <= 0) {
|
|
726
805
|
healthFactor = Infinity;
|
|
727
|
-
} else {
|
|
806
|
+
} else if (usdcPool) {
|
|
728
807
|
try {
|
|
729
808
|
const tx = new Transaction();
|
|
730
809
|
tx.moveCall({
|
|
@@ -733,14 +812,14 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
733
812
|
tx.object(CLOCK),
|
|
734
813
|
tx.object(config.storage),
|
|
735
814
|
tx.object(config.oracle.priceOracle),
|
|
736
|
-
tx.pure.u8(
|
|
815
|
+
tx.pure.u8(usdcPool.id),
|
|
737
816
|
tx.pure.address(address),
|
|
738
|
-
tx.pure.u8(
|
|
817
|
+
tx.pure.u8(usdcPool.id),
|
|
739
818
|
tx.pure.u64(0),
|
|
740
819
|
tx.pure.u64(0),
|
|
741
820
|
tx.pure.bool(false)
|
|
742
821
|
],
|
|
743
|
-
typeArguments: [
|
|
822
|
+
typeArguments: [usdcPool.suiCoinType]
|
|
744
823
|
});
|
|
745
824
|
const result = await client.devInspectTransactionBlock({
|
|
746
825
|
transactionBlock: tx,
|
|
@@ -750,11 +829,13 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
750
829
|
if (decoded !== void 0) {
|
|
751
830
|
healthFactor = normalizeHealthFactor(Number(decoded));
|
|
752
831
|
} else {
|
|
753
|
-
healthFactor =
|
|
832
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
754
833
|
}
|
|
755
834
|
} catch {
|
|
756
|
-
healthFactor =
|
|
835
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
757
836
|
}
|
|
837
|
+
} else {
|
|
838
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
758
839
|
}
|
|
759
840
|
return {
|
|
760
841
|
healthFactor,
|
|
@@ -766,12 +847,20 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
766
847
|
}
|
|
767
848
|
async function getRates(client) {
|
|
768
849
|
try {
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
850
|
+
const pools = await getPools();
|
|
851
|
+
const result = {};
|
|
852
|
+
for (const asset of STABLE_ASSETS) {
|
|
853
|
+
const targetType = SUPPORTED_ASSETS[asset].type;
|
|
854
|
+
const pool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType));
|
|
855
|
+
if (!pool) continue;
|
|
856
|
+
let saveApy = rateToApy(pool.currentSupplyRate);
|
|
857
|
+
let borrowApy = rateToApy(pool.currentBorrowRate);
|
|
858
|
+
if (saveApy <= 0 || saveApy > 100) saveApy = 0;
|
|
859
|
+
if (borrowApy <= 0 || borrowApy > 100) borrowApy = 0;
|
|
860
|
+
result[asset] = { saveApy, borrowApy };
|
|
861
|
+
}
|
|
862
|
+
if (!result.USDC) result.USDC = { saveApy: 4, borrowApy: 6 };
|
|
863
|
+
return result;
|
|
775
864
|
} catch {
|
|
776
865
|
return { USDC: { saveApy: 4, borrowApy: 6 } };
|
|
777
866
|
}
|
|
@@ -1109,6 +1198,41 @@ var ProtocolRegistry = class {
|
|
|
1109
1198
|
candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
|
|
1110
1199
|
return candidates[0];
|
|
1111
1200
|
}
|
|
1201
|
+
async bestSaveRateAcrossAssets() {
|
|
1202
|
+
const candidates = [];
|
|
1203
|
+
for (const asset of STABLE_ASSETS) {
|
|
1204
|
+
for (const adapter of this.lending.values()) {
|
|
1205
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
1206
|
+
if (!adapter.capabilities.includes("save")) continue;
|
|
1207
|
+
try {
|
|
1208
|
+
const rate = await adapter.getRates(asset);
|
|
1209
|
+
candidates.push({ adapter, rate, asset });
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (candidates.length === 0) {
|
|
1215
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", "No lending adapter found for any stablecoin");
|
|
1216
|
+
}
|
|
1217
|
+
candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
|
|
1218
|
+
return candidates[0];
|
|
1219
|
+
}
|
|
1220
|
+
async allRatesAcrossAssets() {
|
|
1221
|
+
const results = [];
|
|
1222
|
+
for (const asset of STABLE_ASSETS) {
|
|
1223
|
+
for (const adapter of this.lending.values()) {
|
|
1224
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
1225
|
+
try {
|
|
1226
|
+
const rates = await adapter.getRates(asset);
|
|
1227
|
+
if (rates.saveApy > 0 || rates.borrowApy > 0) {
|
|
1228
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, asset, rates });
|
|
1229
|
+
}
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return results;
|
|
1235
|
+
}
|
|
1112
1236
|
async allRates(asset) {
|
|
1113
1237
|
const results = [];
|
|
1114
1238
|
for (const adapter of this.lending.values()) {
|
|
@@ -1146,6 +1270,24 @@ var ProtocolRegistry = class {
|
|
|
1146
1270
|
listSwap() {
|
|
1147
1271
|
return [...this.swap.values()];
|
|
1148
1272
|
}
|
|
1273
|
+
isSupportedAsset(asset, capability) {
|
|
1274
|
+
for (const adapter of this.lending.values()) {
|
|
1275
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
1276
|
+
if (capability && !adapter.capabilities.includes(capability)) continue;
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
getSupportedAssets(capability) {
|
|
1282
|
+
const assets = /* @__PURE__ */ new Set();
|
|
1283
|
+
for (const adapter of this.lending.values()) {
|
|
1284
|
+
if (capability && !adapter.capabilities.includes(capability)) continue;
|
|
1285
|
+
for (const a of adapter.supportedAssets) {
|
|
1286
|
+
assets.add(a);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return [...assets];
|
|
1290
|
+
}
|
|
1149
1291
|
};
|
|
1150
1292
|
|
|
1151
1293
|
// src/adapters/navi.ts
|
|
@@ -1170,7 +1312,7 @@ var NaviAdapter = class {
|
|
|
1170
1312
|
name = "NAVI Protocol";
|
|
1171
1313
|
version = "1.0.0";
|
|
1172
1314
|
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
1173
|
-
supportedAssets = [
|
|
1315
|
+
supportedAssets = [...STABLE_ASSETS];
|
|
1174
1316
|
supportsSameAssetBorrow = true;
|
|
1175
1317
|
client;
|
|
1176
1318
|
async init(client) {
|
|
@@ -1196,20 +1338,24 @@ var NaviAdapter = class {
|
|
|
1196
1338
|
async getHealth(address) {
|
|
1197
1339
|
return getHealthFactor(this.client, address);
|
|
1198
1340
|
}
|
|
1199
|
-
async buildSaveTx(address, amount,
|
|
1200
|
-
const
|
|
1341
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
1342
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
1343
|
+
const tx = await buildSaveTx(this.client, address, amount, { ...options, asset: stableAsset });
|
|
1201
1344
|
return { tx };
|
|
1202
1345
|
}
|
|
1203
|
-
async buildWithdrawTx(address, amount,
|
|
1204
|
-
const
|
|
1346
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
1347
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
1348
|
+
const result = await buildWithdrawTx(this.client, address, amount, { asset: stableAsset });
|
|
1205
1349
|
return { tx: result.tx, effectiveAmount: result.effectiveAmount };
|
|
1206
1350
|
}
|
|
1207
|
-
async buildBorrowTx(address, amount,
|
|
1208
|
-
const
|
|
1351
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
1352
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
1353
|
+
const tx = await buildBorrowTx(this.client, address, amount, { ...options, asset: stableAsset });
|
|
1209
1354
|
return { tx };
|
|
1210
1355
|
}
|
|
1211
|
-
async buildRepayTx(address, amount,
|
|
1212
|
-
const
|
|
1356
|
+
async buildRepayTx(address, amount, asset) {
|
|
1357
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
1358
|
+
const tx = await buildRepayTx(this.client, address, amount, { asset: stableAsset });
|
|
1213
1359
|
return { tx };
|
|
1214
1360
|
}
|
|
1215
1361
|
async maxWithdraw(address, _asset) {
|
|
@@ -1357,16 +1503,22 @@ var CetusAdapter = class {
|
|
|
1357
1503
|
};
|
|
1358
1504
|
}
|
|
1359
1505
|
getSupportedPairs() {
|
|
1360
|
-
|
|
1506
|
+
const pairs = [
|
|
1361
1507
|
{ from: "USDC", to: "SUI" },
|
|
1362
1508
|
{ from: "SUI", to: "USDC" }
|
|
1363
1509
|
];
|
|
1510
|
+
for (const a of STABLE_ASSETS) {
|
|
1511
|
+
for (const b of STABLE_ASSETS) {
|
|
1512
|
+
if (a !== b) pairs.push({ from: a, to: b });
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return pairs;
|
|
1364
1516
|
}
|
|
1365
1517
|
async getPoolPrice() {
|
|
1366
1518
|
return getPoolPrice(this.client);
|
|
1367
1519
|
}
|
|
1368
1520
|
};
|
|
1369
|
-
|
|
1521
|
+
SUPPORTED_ASSETS.USDC.type;
|
|
1370
1522
|
var WAD = 1e18;
|
|
1371
1523
|
var MIN_HEALTH_FACTOR2 = 1.5;
|
|
1372
1524
|
var CLOCK2 = "0x6";
|
|
@@ -1473,8 +1625,8 @@ var SuilendAdapter = class {
|
|
|
1473
1625
|
id = "suilend";
|
|
1474
1626
|
name = "Suilend";
|
|
1475
1627
|
version = "2.0.0";
|
|
1476
|
-
capabilities = ["save", "withdraw"];
|
|
1477
|
-
supportedAssets = [
|
|
1628
|
+
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
1629
|
+
supportedAssets = [...STABLE_ASSETS];
|
|
1478
1630
|
supportsSameAssetBorrow = false;
|
|
1479
1631
|
client;
|
|
1480
1632
|
publishedAt = null;
|
|
@@ -1518,12 +1670,14 @@ var SuilendAdapter = class {
|
|
|
1518
1670
|
return this.reserveCache;
|
|
1519
1671
|
}
|
|
1520
1672
|
findReserve(reserves, asset) {
|
|
1521
|
-
const upper = asset.toUpperCase();
|
|
1522
1673
|
let coinType;
|
|
1523
|
-
if (
|
|
1524
|
-
|
|
1525
|
-
else if (asset.includes("::"))
|
|
1526
|
-
|
|
1674
|
+
if (asset in SUPPORTED_ASSETS) {
|
|
1675
|
+
coinType = SUPPORTED_ASSETS[asset].type;
|
|
1676
|
+
} else if (asset.includes("::")) {
|
|
1677
|
+
coinType = asset;
|
|
1678
|
+
} else {
|
|
1679
|
+
return void 0;
|
|
1680
|
+
}
|
|
1527
1681
|
try {
|
|
1528
1682
|
const normalized = normalizeStructTag(coinType);
|
|
1529
1683
|
return reserves.find((r) => {
|
|
@@ -1572,8 +1726,12 @@ var SuilendAdapter = class {
|
|
|
1572
1726
|
resolveSymbol(coinType) {
|
|
1573
1727
|
try {
|
|
1574
1728
|
const normalized = normalizeStructTag(coinType);
|
|
1575
|
-
|
|
1576
|
-
|
|
1729
|
+
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
1730
|
+
try {
|
|
1731
|
+
if (normalizeStructTag(info.type) === normalized) return key;
|
|
1732
|
+
} catch {
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1577
1735
|
} catch {
|
|
1578
1736
|
}
|
|
1579
1737
|
const parts = coinType.split("::");
|
|
@@ -1621,22 +1779,43 @@ var SuilendAdapter = class {
|
|
|
1621
1779
|
if (caps.length === 0) {
|
|
1622
1780
|
return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
|
|
1623
1781
|
}
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1782
|
+
const [reserves, obligation] = await Promise.all([
|
|
1783
|
+
this.loadReserves(),
|
|
1784
|
+
this.fetchObligation(caps[0].obligationId)
|
|
1785
|
+
]);
|
|
1786
|
+
let supplied = 0;
|
|
1787
|
+
let borrowed = 0;
|
|
1788
|
+
let weightedCloseLtv = 0;
|
|
1789
|
+
let weightedOpenLtv = 0;
|
|
1790
|
+
for (const dep of obligation.deposits) {
|
|
1791
|
+
const reserve = reserves[dep.reserveIdx];
|
|
1792
|
+
if (!reserve) continue;
|
|
1793
|
+
const ratio = cTokenRatio(reserve);
|
|
1794
|
+
const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
|
|
1795
|
+
supplied += amount;
|
|
1796
|
+
weightedCloseLtv += amount * (reserve.closeLtvPct / 100);
|
|
1797
|
+
weightedOpenLtv += amount * (reserve.openLtvPct / 100);
|
|
1798
|
+
}
|
|
1799
|
+
for (const bor of obligation.borrows) {
|
|
1800
|
+
const reserve = reserves[bor.reserveIdx];
|
|
1801
|
+
if (!reserve) continue;
|
|
1802
|
+
const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
|
|
1803
|
+
const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
|
|
1804
|
+
const posRate = bor.cumBorrowRateWad / WAD;
|
|
1805
|
+
borrowed += posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
|
|
1806
|
+
}
|
|
1807
|
+
const liqThreshold = supplied > 0 ? weightedCloseLtv / supplied : 0.75;
|
|
1808
|
+
const openLtv = supplied > 0 ? weightedOpenLtv / supplied : 0.7;
|
|
1632
1809
|
const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
|
|
1633
|
-
const maxBorrow = Math.max(0, supplied *
|
|
1810
|
+
const maxBorrow = Math.max(0, supplied * openLtv - borrowed);
|
|
1634
1811
|
return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
|
|
1635
1812
|
}
|
|
1636
|
-
async buildSaveTx(address, amount,
|
|
1813
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
1814
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1815
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1637
1816
|
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1638
|
-
const
|
|
1639
|
-
if (!
|
|
1817
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1818
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1640
1819
|
const caps = await this.fetchObligationCaps(address);
|
|
1641
1820
|
const tx = new Transaction();
|
|
1642
1821
|
tx.setSender(address);
|
|
@@ -1651,33 +1830,33 @@ var SuilendAdapter = class {
|
|
|
1651
1830
|
} else {
|
|
1652
1831
|
capRef = caps[0].id;
|
|
1653
1832
|
}
|
|
1654
|
-
const allCoins = await this.fetchAllCoins(address,
|
|
1655
|
-
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE",
|
|
1833
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1834
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
1656
1835
|
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1657
1836
|
if (allCoins.length > 1) {
|
|
1658
1837
|
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1659
1838
|
}
|
|
1660
|
-
const rawAmount =
|
|
1839
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals).toString();
|
|
1661
1840
|
const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
|
|
1662
1841
|
if (options?.collectFee) {
|
|
1663
1842
|
addCollectFeeToTx(tx, depositCoin, "save");
|
|
1664
1843
|
}
|
|
1665
1844
|
const [ctokens] = tx.moveCall({
|
|
1666
1845
|
target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
|
|
1667
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1846
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1668
1847
|
arguments: [
|
|
1669
1848
|
tx.object(LENDING_MARKET_ID),
|
|
1670
|
-
tx.pure.u64(
|
|
1849
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1671
1850
|
tx.object(CLOCK2),
|
|
1672
1851
|
depositCoin
|
|
1673
1852
|
]
|
|
1674
1853
|
});
|
|
1675
1854
|
tx.moveCall({
|
|
1676
1855
|
target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
|
|
1677
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1856
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1678
1857
|
arguments: [
|
|
1679
1858
|
tx.object(LENDING_MARKET_ID),
|
|
1680
|
-
tx.pure.u64(
|
|
1859
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1681
1860
|
typeof capRef === "string" ? tx.object(capRef) : capRef,
|
|
1682
1861
|
tx.object(CLOCK2),
|
|
1683
1862
|
ctokens
|
|
@@ -1688,42 +1867,44 @@ var SuilendAdapter = class {
|
|
|
1688
1867
|
}
|
|
1689
1868
|
return { tx };
|
|
1690
1869
|
}
|
|
1691
|
-
async buildWithdrawTx(address, amount,
|
|
1870
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
1871
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1872
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1692
1873
|
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
|
|
1693
|
-
const
|
|
1694
|
-
if (!
|
|
1874
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1875
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1695
1876
|
const caps = await this.fetchObligationCaps(address);
|
|
1696
1877
|
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
|
|
1697
1878
|
const positions = await this.getPositions(address);
|
|
1698
|
-
const deposited = positions.supplies.find((s) => s.asset ===
|
|
1879
|
+
const deposited = positions.supplies.find((s) => s.asset === assetKey)?.amount ?? 0;
|
|
1699
1880
|
const effectiveAmount = Math.min(amount, deposited);
|
|
1700
|
-
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL",
|
|
1701
|
-
const ratio = cTokenRatio(
|
|
1702
|
-
const ctokenAmount = Math.ceil(effectiveAmount * 10 **
|
|
1881
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
|
|
1882
|
+
const ratio = cTokenRatio(reserve);
|
|
1883
|
+
const ctokenAmount = Math.ceil(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
|
|
1703
1884
|
const tx = new Transaction();
|
|
1704
1885
|
tx.setSender(address);
|
|
1705
1886
|
const [ctokens] = tx.moveCall({
|
|
1706
1887
|
target: `${pkg}::lending_market::withdraw_ctokens`,
|
|
1707
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1888
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1708
1889
|
arguments: [
|
|
1709
1890
|
tx.object(LENDING_MARKET_ID),
|
|
1710
|
-
tx.pure.u64(
|
|
1891
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1711
1892
|
tx.object(caps[0].id),
|
|
1712
1893
|
tx.object(CLOCK2),
|
|
1713
1894
|
tx.pure.u64(ctokenAmount)
|
|
1714
1895
|
]
|
|
1715
1896
|
});
|
|
1716
|
-
const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${
|
|
1897
|
+
const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${assetInfo.type}>`;
|
|
1717
1898
|
const [none] = tx.moveCall({
|
|
1718
1899
|
target: "0x1::option::none",
|
|
1719
1900
|
typeArguments: [exemptionType]
|
|
1720
1901
|
});
|
|
1721
1902
|
const [coin] = tx.moveCall({
|
|
1722
1903
|
target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity`,
|
|
1723
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1904
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1724
1905
|
arguments: [
|
|
1725
1906
|
tx.object(LENDING_MARKET_ID),
|
|
1726
|
-
tx.pure.u64(
|
|
1907
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1727
1908
|
tx.object(CLOCK2),
|
|
1728
1909
|
ctokens,
|
|
1729
1910
|
none
|
|
@@ -1732,11 +1913,64 @@ var SuilendAdapter = class {
|
|
|
1732
1913
|
tx.transferObjects([coin], address);
|
|
1733
1914
|
return { tx, effectiveAmount };
|
|
1734
1915
|
}
|
|
1735
|
-
async buildBorrowTx(
|
|
1736
|
-
|
|
1916
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
1917
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1918
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1919
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1920
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1921
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1922
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1923
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found. Deposit collateral first with: t2000 save <amount>");
|
|
1924
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1925
|
+
const tx = new Transaction();
|
|
1926
|
+
tx.setSender(address);
|
|
1927
|
+
const [coin] = tx.moveCall({
|
|
1928
|
+
target: `${pkg}::lending_market::borrow`,
|
|
1929
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1930
|
+
arguments: [
|
|
1931
|
+
tx.object(LENDING_MARKET_ID),
|
|
1932
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1933
|
+
tx.object(caps[0].id),
|
|
1934
|
+
tx.object(CLOCK2),
|
|
1935
|
+
tx.pure.u64(rawAmount)
|
|
1936
|
+
]
|
|
1937
|
+
});
|
|
1938
|
+
if (options?.collectFee) {
|
|
1939
|
+
addCollectFeeToTx(tx, coin, "borrow");
|
|
1940
|
+
}
|
|
1941
|
+
tx.transferObjects([coin], address);
|
|
1942
|
+
return { tx };
|
|
1737
1943
|
}
|
|
1738
|
-
async buildRepayTx(
|
|
1739
|
-
|
|
1944
|
+
async buildRepayTx(address, amount, asset) {
|
|
1945
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1946
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1947
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1948
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1949
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1950
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1951
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
|
|
1952
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1953
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
1954
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1955
|
+
const tx = new Transaction();
|
|
1956
|
+
tx.setSender(address);
|
|
1957
|
+
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1958
|
+
if (allCoins.length > 1) {
|
|
1959
|
+
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1960
|
+
}
|
|
1961
|
+
const [repayCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount.toString()]);
|
|
1962
|
+
tx.moveCall({
|
|
1963
|
+
target: `${pkg}::lending_market::repay`,
|
|
1964
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1965
|
+
arguments: [
|
|
1966
|
+
tx.object(LENDING_MARKET_ID),
|
|
1967
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1968
|
+
tx.object(caps[0].id),
|
|
1969
|
+
tx.object(CLOCK2),
|
|
1970
|
+
repayCoin
|
|
1971
|
+
]
|
|
1972
|
+
});
|
|
1973
|
+
return { tx };
|
|
1740
1974
|
}
|
|
1741
1975
|
async maxWithdraw(address, _asset) {
|
|
1742
1976
|
const health = await this.getHealth(address);
|
|
@@ -1750,8 +1984,10 @@ var SuilendAdapter = class {
|
|
|
1750
1984
|
const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
|
|
1751
1985
|
return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
|
|
1752
1986
|
}
|
|
1753
|
-
async maxBorrow(
|
|
1754
|
-
|
|
1987
|
+
async maxBorrow(address, _asset) {
|
|
1988
|
+
const health = await this.getHealth(address);
|
|
1989
|
+
const maxAmount = health.maxBorrow;
|
|
1990
|
+
return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
|
|
1755
1991
|
}
|
|
1756
1992
|
async fetchAllCoins(owner, coinType) {
|
|
1757
1993
|
const all = [];
|
|
@@ -2145,48 +2381,55 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2145
2381
|
}
|
|
2146
2382
|
// -- Savings --
|
|
2147
2383
|
async save(params) {
|
|
2148
|
-
const asset = (params.asset ?? "USDC")
|
|
2149
|
-
if (asset
|
|
2150
|
-
|
|
2384
|
+
const asset = normalizeAsset(params.asset ?? "USDC");
|
|
2385
|
+
if (!this.registry.isSupportedAsset(asset, "save")) {
|
|
2386
|
+
const supported = this.registry.getSupportedAssets("save").join(", ");
|
|
2387
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `${asset} is not supported for save. Supported: ${supported}`);
|
|
2151
2388
|
}
|
|
2152
2389
|
let amount;
|
|
2153
2390
|
if (params.amount === "all") {
|
|
2154
2391
|
const bal = await queryBalance(this.client, this._address);
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2392
|
+
const assetBalance = bal.stables[asset] ?? 0;
|
|
2393
|
+
const reserve = asset === "USDC" ? 1 : 0;
|
|
2394
|
+
amount = assetBalance - reserve;
|
|
2157
2395
|
if (amount <= 0) {
|
|
2158
|
-
throw new T2000Error("INSUFFICIENT_BALANCE",
|
|
2159
|
-
reason: "gas_reserve_required",
|
|
2160
|
-
available:
|
|
2396
|
+
throw new T2000Error("INSUFFICIENT_BALANCE", `Balance too low to save${asset === "USDC" ? " after $1 gas reserve" : ""}`, {
|
|
2397
|
+
reason: asset === "USDC" ? "gas_reserve_required" : "zero_balance",
|
|
2398
|
+
available: assetBalance
|
|
2161
2399
|
});
|
|
2162
2400
|
}
|
|
2163
2401
|
} else {
|
|
2164
2402
|
amount = params.amount;
|
|
2165
2403
|
const bal = await queryBalance(this.client, this._address);
|
|
2166
|
-
|
|
2167
|
-
|
|
2404
|
+
const assetBalance = bal.stables[asset] ?? 0;
|
|
2405
|
+
if (amount > assetBalance) {
|
|
2406
|
+
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset}. Available: $${assetBalance.toFixed(2)}, requested: $${amount.toFixed(2)}`);
|
|
2168
2407
|
}
|
|
2169
2408
|
}
|
|
2170
|
-
const
|
|
2409
|
+
const shouldCollectFee = asset === "USDC";
|
|
2410
|
+
const fee = shouldCollectFee ? calculateFee("save", amount) : { amount: 0, rate: 0};
|
|
2171
2411
|
const saveAmount = amount;
|
|
2172
2412
|
const adapter = await this.resolveLending(params.protocol, asset, "save");
|
|
2173
2413
|
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2174
|
-
const { tx } = await adapter.buildSaveTx(this._address, saveAmount, asset, { collectFee:
|
|
2414
|
+
const { tx } = await adapter.buildSaveTx(this._address, saveAmount, asset, { collectFee: shouldCollectFee });
|
|
2175
2415
|
return tx;
|
|
2176
2416
|
});
|
|
2177
2417
|
const rates = await adapter.getRates(asset);
|
|
2178
|
-
|
|
2179
|
-
|
|
2418
|
+
if (shouldCollectFee) {
|
|
2419
|
+
reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
|
|
2420
|
+
}
|
|
2421
|
+
this.emitBalanceChange(asset, saveAmount, "save", gasResult.digest);
|
|
2180
2422
|
let savingsBalance = saveAmount;
|
|
2181
2423
|
try {
|
|
2182
2424
|
const positions = await this.positions();
|
|
2183
|
-
savingsBalance = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
|
|
2425
|
+
savingsBalance = positions.positions.filter((p) => p.type === "save" && p.asset === asset).reduce((sum, p) => sum + p.amount, 0);
|
|
2184
2426
|
} catch {
|
|
2185
2427
|
}
|
|
2186
2428
|
return {
|
|
2187
2429
|
success: true,
|
|
2188
2430
|
tx: gasResult.digest,
|
|
2189
2431
|
amount: saveAmount,
|
|
2432
|
+
asset,
|
|
2190
2433
|
apy: rates.saveApy,
|
|
2191
2434
|
fee: fee.amount,
|
|
2192
2435
|
gasCost: gasResult.gasCostSui,
|
|
@@ -2195,10 +2438,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2195
2438
|
};
|
|
2196
2439
|
}
|
|
2197
2440
|
async withdraw(params) {
|
|
2198
|
-
const asset = (params.asset ?? "USDC")
|
|
2199
|
-
if (asset !== "USDC") {
|
|
2200
|
-
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for withdraw. Got: ${asset}`);
|
|
2201
|
-
}
|
|
2441
|
+
const asset = normalizeAsset(params.asset ?? "USDC");
|
|
2202
2442
|
if (params.amount === "all" && !params.protocol) {
|
|
2203
2443
|
return this.withdrawAllProtocols(asset);
|
|
2204
2444
|
}
|
|
@@ -2235,7 +2475,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2235
2475
|
effectiveAmount = built.effectiveAmount;
|
|
2236
2476
|
return built.tx;
|
|
2237
2477
|
});
|
|
2238
|
-
this.emitBalanceChange(
|
|
2478
|
+
this.emitBalanceChange(asset, effectiveAmount, "withdraw", gasResult.digest);
|
|
2239
2479
|
return {
|
|
2240
2480
|
success: true,
|
|
2241
2481
|
tx: gasResult.digest,
|
|
@@ -2244,26 +2484,31 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2244
2484
|
gasMethod: gasResult.gasMethod
|
|
2245
2485
|
};
|
|
2246
2486
|
}
|
|
2247
|
-
async withdrawAllProtocols(
|
|
2487
|
+
async withdrawAllProtocols(_asset) {
|
|
2248
2488
|
const allPositions = await this.registry.allPositions(this._address);
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2489
|
+
const withdrawable = [];
|
|
2490
|
+
for (const pos of allPositions) {
|
|
2491
|
+
for (const supply of pos.positions.supplies) {
|
|
2492
|
+
if (supply.amount > 1e-3) {
|
|
2493
|
+
withdrawable.push({ protocolId: pos.protocolId, asset: supply.asset, amount: supply.amount });
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (withdrawable.length === 0) {
|
|
2253
2498
|
throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
|
|
2254
2499
|
}
|
|
2255
2500
|
let totalWithdrawn = 0;
|
|
2256
2501
|
let lastDigest = "";
|
|
2257
2502
|
let totalGasCost = 0;
|
|
2258
2503
|
let lastGasMethod = "self-funded";
|
|
2259
|
-
for (const
|
|
2260
|
-
const adapter = this.registry.getLending(
|
|
2504
|
+
for (const entry of withdrawable) {
|
|
2505
|
+
const adapter = this.registry.getLending(entry.protocolId);
|
|
2261
2506
|
if (!adapter) continue;
|
|
2262
|
-
const maxResult = await adapter.maxWithdraw(this._address, asset);
|
|
2507
|
+
const maxResult = await adapter.maxWithdraw(this._address, entry.asset);
|
|
2263
2508
|
if (maxResult.maxAmount <= 1e-3) continue;
|
|
2264
2509
|
let effectiveAmount = maxResult.maxAmount;
|
|
2265
2510
|
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2266
|
-
const built = await adapter.buildWithdrawTx(this._address, maxResult.maxAmount, asset);
|
|
2511
|
+
const built = await adapter.buildWithdrawTx(this._address, maxResult.maxAmount, entry.asset);
|
|
2267
2512
|
effectiveAmount = built.effectiveAmount;
|
|
2268
2513
|
return built.tx;
|
|
2269
2514
|
});
|
|
@@ -2271,7 +2516,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2271
2516
|
lastDigest = gasResult.digest;
|
|
2272
2517
|
totalGasCost += gasResult.gasCostSui;
|
|
2273
2518
|
lastGasMethod = gasResult.gasMethod;
|
|
2274
|
-
this.emitBalanceChange(
|
|
2519
|
+
this.emitBalanceChange(entry.asset, effectiveAmount, "withdraw", gasResult.digest);
|
|
2275
2520
|
}
|
|
2276
2521
|
if (totalWithdrawn <= 0) {
|
|
2277
2522
|
throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
|
|
@@ -2290,27 +2535,30 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2290
2535
|
}
|
|
2291
2536
|
// -- Borrowing --
|
|
2292
2537
|
async borrow(params) {
|
|
2293
|
-
const asset = (params.asset ?? "USDC")
|
|
2294
|
-
if (asset !== "USDC") {
|
|
2295
|
-
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for borrow. Got: ${asset}`);
|
|
2296
|
-
}
|
|
2538
|
+
const asset = normalizeAsset(params.asset ?? "USDC");
|
|
2297
2539
|
const adapter = await this.resolveLending(params.protocol, asset, "borrow");
|
|
2298
2540
|
const maxResult = await adapter.maxBorrow(this._address, asset);
|
|
2541
|
+
if (maxResult.maxAmount <= 0) {
|
|
2542
|
+
throw new T2000Error("NO_COLLATERAL", "No collateral deposited. Save first with `t2000 save <amount>`.");
|
|
2543
|
+
}
|
|
2299
2544
|
if (params.amount > maxResult.maxAmount) {
|
|
2300
2545
|
throw new T2000Error("HEALTH_FACTOR_TOO_LOW", `Max safe borrow: $${maxResult.maxAmount.toFixed(2)}`, {
|
|
2301
2546
|
maxBorrow: maxResult.maxAmount,
|
|
2302
2547
|
currentHF: maxResult.currentHF
|
|
2303
2548
|
});
|
|
2304
2549
|
}
|
|
2305
|
-
const
|
|
2550
|
+
const shouldCollectFee = asset === "USDC";
|
|
2551
|
+
const fee = shouldCollectFee ? calculateFee("borrow", params.amount) : { amount: 0, rate: 0};
|
|
2306
2552
|
const borrowAmount = params.amount;
|
|
2307
2553
|
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2308
|
-
const { tx } = await adapter.buildBorrowTx(this._address, borrowAmount, asset, { collectFee:
|
|
2554
|
+
const { tx } = await adapter.buildBorrowTx(this._address, borrowAmount, asset, { collectFee: shouldCollectFee });
|
|
2309
2555
|
return tx;
|
|
2310
2556
|
});
|
|
2311
2557
|
const hf = await adapter.getHealth(this._address);
|
|
2312
|
-
|
|
2313
|
-
|
|
2558
|
+
if (shouldCollectFee) {
|
|
2559
|
+
reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
|
|
2560
|
+
}
|
|
2561
|
+
this.emitBalanceChange(asset, borrowAmount, "borrow", gasResult.digest);
|
|
2314
2562
|
return {
|
|
2315
2563
|
success: true,
|
|
2316
2564
|
tx: gasResult.digest,
|
|
@@ -2322,10 +2570,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2322
2570
|
};
|
|
2323
2571
|
}
|
|
2324
2572
|
async repay(params) {
|
|
2325
|
-
const asset = (params.asset ?? "USDC")
|
|
2326
|
-
if (asset !== "USDC") {
|
|
2327
|
-
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for repay. Got: ${asset}`);
|
|
2328
|
-
}
|
|
2573
|
+
const asset = normalizeAsset(params.asset ?? "USDC");
|
|
2329
2574
|
const adapter = await this.resolveLending(params.protocol, asset, "repay");
|
|
2330
2575
|
let amount;
|
|
2331
2576
|
if (params.amount === "all") {
|
|
@@ -2343,7 +2588,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2343
2588
|
return tx;
|
|
2344
2589
|
});
|
|
2345
2590
|
const hf = await adapter.getHealth(this._address);
|
|
2346
|
-
this.emitBalanceChange(
|
|
2591
|
+
this.emitBalanceChange(asset, repayAmount, "repay", gasResult.digest);
|
|
2347
2592
|
return {
|
|
2348
2593
|
success: true,
|
|
2349
2594
|
tx: gasResult.digest,
|
|
@@ -2369,8 +2614,8 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2369
2614
|
}
|
|
2370
2615
|
// -- Swap --
|
|
2371
2616
|
async swap(params) {
|
|
2372
|
-
const fromAsset = params.from
|
|
2373
|
-
const toAsset = params.to
|
|
2617
|
+
const fromAsset = normalizeAsset(params.from);
|
|
2618
|
+
const toAsset = normalizeAsset(params.to);
|
|
2374
2619
|
if (!(fromAsset in SUPPORTED_ASSETS) || !(toAsset in SUPPORTED_ASSETS)) {
|
|
2375
2620
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
|
|
2376
2621
|
}
|
|
@@ -2429,8 +2674,8 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2429
2674
|
};
|
|
2430
2675
|
}
|
|
2431
2676
|
async swapQuote(params) {
|
|
2432
|
-
const fromAsset = params.from
|
|
2433
|
-
const toAsset = params.to
|
|
2677
|
+
const fromAsset = normalizeAsset(params.from);
|
|
2678
|
+
const toAsset = normalizeAsset(params.to);
|
|
2434
2679
|
const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
|
|
2435
2680
|
const fee = calculateFee("swap", params.amount);
|
|
2436
2681
|
return { ...best.quote, fee: { amount: fee.amount, rate: fee.rate } };
|
|
@@ -2459,14 +2704,225 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2459
2704
|
return { positions };
|
|
2460
2705
|
}
|
|
2461
2706
|
async rates() {
|
|
2462
|
-
const allRatesResult = await this.registry.
|
|
2463
|
-
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2707
|
+
const allRatesResult = await this.registry.allRatesAcrossAssets();
|
|
2708
|
+
const result = {};
|
|
2709
|
+
for (const entry of allRatesResult) {
|
|
2710
|
+
if (!result[entry.asset] || entry.rates.saveApy > result[entry.asset].saveApy) {
|
|
2711
|
+
result[entry.asset] = { saveApy: entry.rates.saveApy, borrowApy: entry.rates.borrowApy };
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
if (!result.USDC) result.USDC = { saveApy: 0, borrowApy: 0 };
|
|
2715
|
+
return result;
|
|
2466
2716
|
}
|
|
2467
2717
|
async allRates(asset = "USDC") {
|
|
2468
2718
|
return this.registry.allRates(asset);
|
|
2469
2719
|
}
|
|
2720
|
+
async allRatesAcrossAssets() {
|
|
2721
|
+
return this.registry.allRatesAcrossAssets();
|
|
2722
|
+
}
|
|
2723
|
+
async rebalance(opts = {}) {
|
|
2724
|
+
const dryRun = opts.dryRun ?? false;
|
|
2725
|
+
const minYieldDiff = opts.minYieldDiff ?? 0.5;
|
|
2726
|
+
const maxBreakEven = opts.maxBreakEven ?? 30;
|
|
2727
|
+
const [allPositions, allRates] = await Promise.all([
|
|
2728
|
+
this.registry.allPositions(this._address),
|
|
2729
|
+
this.registry.allRatesAcrossAssets()
|
|
2730
|
+
]);
|
|
2731
|
+
const savePositions = allPositions.flatMap(
|
|
2732
|
+
(p) => p.positions.supplies.filter((s) => s.amount > 0.01).map((s) => ({
|
|
2733
|
+
protocolId: p.protocolId,
|
|
2734
|
+
protocol: p.protocol,
|
|
2735
|
+
asset: s.asset,
|
|
2736
|
+
amount: s.amount,
|
|
2737
|
+
apy: s.apy
|
|
2738
|
+
}))
|
|
2739
|
+
);
|
|
2740
|
+
if (savePositions.length === 0) {
|
|
2741
|
+
throw new T2000Error("NO_COLLATERAL", "No savings positions to rebalance. Use `t2000 save <amount>` first.");
|
|
2742
|
+
}
|
|
2743
|
+
const borrowPositions = allPositions.flatMap(
|
|
2744
|
+
(p) => p.positions.borrows.filter((b) => b.amount > 0.01)
|
|
2745
|
+
);
|
|
2746
|
+
if (borrowPositions.length > 0) {
|
|
2747
|
+
const healthResults = await Promise.all(
|
|
2748
|
+
allPositions.filter((p) => p.positions.borrows.some((b) => b.amount > 0.01)).map(async (p) => {
|
|
2749
|
+
const adapter = this.registry.getLending(p.protocolId);
|
|
2750
|
+
if (!adapter) return null;
|
|
2751
|
+
return adapter.getHealth(this._address);
|
|
2752
|
+
})
|
|
2753
|
+
);
|
|
2754
|
+
for (const hf of healthResults) {
|
|
2755
|
+
if (hf && hf.healthFactor < 1.5) {
|
|
2756
|
+
throw new T2000Error(
|
|
2757
|
+
"HEALTH_FACTOR_TOO_LOW",
|
|
2758
|
+
`Cannot rebalance \u2014 health factor is ${hf.healthFactor.toFixed(2)} (minimum 1.5). Repay some debt first.`,
|
|
2759
|
+
{ healthFactor: hf.healthFactor }
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const bestRate = allRates.reduce(
|
|
2765
|
+
(best, r) => r.rates.saveApy > best.rates.saveApy ? r : best
|
|
2766
|
+
);
|
|
2767
|
+
const current = savePositions.reduce(
|
|
2768
|
+
(worst, p) => p.apy < worst.apy ? p : worst
|
|
2769
|
+
);
|
|
2770
|
+
const apyDiff = bestRate.rates.saveApy - current.apy;
|
|
2771
|
+
const isSameProtocol = current.protocolId === bestRate.protocolId;
|
|
2772
|
+
const isSameAsset = current.asset === bestRate.asset;
|
|
2773
|
+
if (apyDiff < minYieldDiff) {
|
|
2774
|
+
return {
|
|
2775
|
+
executed: false,
|
|
2776
|
+
steps: [],
|
|
2777
|
+
fromProtocol: current.protocol,
|
|
2778
|
+
fromAsset: current.asset,
|
|
2779
|
+
toProtocol: bestRate.protocol,
|
|
2780
|
+
toAsset: bestRate.asset,
|
|
2781
|
+
amount: current.amount,
|
|
2782
|
+
currentApy: current.apy,
|
|
2783
|
+
newApy: bestRate.rates.saveApy,
|
|
2784
|
+
annualGain: current.amount * apyDiff / 100,
|
|
2785
|
+
estimatedSwapCost: 0,
|
|
2786
|
+
breakEvenDays: Infinity,
|
|
2787
|
+
txDigests: [],
|
|
2788
|
+
totalGasCost: 0
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
if (isSameProtocol && isSameAsset) {
|
|
2792
|
+
return {
|
|
2793
|
+
executed: false,
|
|
2794
|
+
steps: [],
|
|
2795
|
+
fromProtocol: current.protocol,
|
|
2796
|
+
fromAsset: current.asset,
|
|
2797
|
+
toProtocol: bestRate.protocol,
|
|
2798
|
+
toAsset: bestRate.asset,
|
|
2799
|
+
amount: current.amount,
|
|
2800
|
+
currentApy: current.apy,
|
|
2801
|
+
newApy: bestRate.rates.saveApy,
|
|
2802
|
+
annualGain: 0,
|
|
2803
|
+
estimatedSwapCost: 0,
|
|
2804
|
+
breakEvenDays: Infinity,
|
|
2805
|
+
txDigests: [],
|
|
2806
|
+
totalGasCost: 0
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
const steps = [];
|
|
2810
|
+
let estimatedSwapCost = 0;
|
|
2811
|
+
steps.push({
|
|
2812
|
+
action: "withdraw",
|
|
2813
|
+
protocol: current.protocolId,
|
|
2814
|
+
fromAsset: current.asset,
|
|
2815
|
+
amount: current.amount
|
|
2816
|
+
});
|
|
2817
|
+
let amountToDeposit = current.amount;
|
|
2818
|
+
if (!isSameAsset) {
|
|
2819
|
+
try {
|
|
2820
|
+
const quote = await this.registry.bestSwapQuote(current.asset, bestRate.asset, current.amount);
|
|
2821
|
+
amountToDeposit = quote.quote.expectedOutput;
|
|
2822
|
+
estimatedSwapCost = Math.abs(current.amount - amountToDeposit);
|
|
2823
|
+
} catch {
|
|
2824
|
+
estimatedSwapCost = current.amount * 3e-3;
|
|
2825
|
+
amountToDeposit = current.amount - estimatedSwapCost;
|
|
2826
|
+
}
|
|
2827
|
+
steps.push({
|
|
2828
|
+
action: "swap",
|
|
2829
|
+
fromAsset: current.asset,
|
|
2830
|
+
toAsset: bestRate.asset,
|
|
2831
|
+
amount: current.amount,
|
|
2832
|
+
estimatedOutput: amountToDeposit
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
steps.push({
|
|
2836
|
+
action: "deposit",
|
|
2837
|
+
protocol: bestRate.protocolId,
|
|
2838
|
+
toAsset: bestRate.asset,
|
|
2839
|
+
amount: amountToDeposit
|
|
2840
|
+
});
|
|
2841
|
+
const annualGain = amountToDeposit * apyDiff / 100;
|
|
2842
|
+
const breakEvenDays = estimatedSwapCost > 0 ? Math.ceil(estimatedSwapCost / annualGain * 365) : 0;
|
|
2843
|
+
if (breakEvenDays > maxBreakEven && estimatedSwapCost > 0) {
|
|
2844
|
+
return {
|
|
2845
|
+
executed: false,
|
|
2846
|
+
steps,
|
|
2847
|
+
fromProtocol: current.protocol,
|
|
2848
|
+
fromAsset: current.asset,
|
|
2849
|
+
toProtocol: bestRate.protocol,
|
|
2850
|
+
toAsset: bestRate.asset,
|
|
2851
|
+
amount: current.amount,
|
|
2852
|
+
currentApy: current.apy,
|
|
2853
|
+
newApy: bestRate.rates.saveApy,
|
|
2854
|
+
annualGain,
|
|
2855
|
+
estimatedSwapCost,
|
|
2856
|
+
breakEvenDays,
|
|
2857
|
+
txDigests: [],
|
|
2858
|
+
totalGasCost: 0
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
if (dryRun) {
|
|
2862
|
+
return {
|
|
2863
|
+
executed: false,
|
|
2864
|
+
steps,
|
|
2865
|
+
fromProtocol: current.protocol,
|
|
2866
|
+
fromAsset: current.asset,
|
|
2867
|
+
toProtocol: bestRate.protocol,
|
|
2868
|
+
toAsset: bestRate.asset,
|
|
2869
|
+
amount: current.amount,
|
|
2870
|
+
currentApy: current.apy,
|
|
2871
|
+
newApy: bestRate.rates.saveApy,
|
|
2872
|
+
annualGain,
|
|
2873
|
+
estimatedSwapCost,
|
|
2874
|
+
breakEvenDays,
|
|
2875
|
+
txDigests: [],
|
|
2876
|
+
totalGasCost: 0
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
const txDigests = [];
|
|
2880
|
+
let totalGasCost = 0;
|
|
2881
|
+
const withdrawAdapter = this.registry.getLending(current.protocolId);
|
|
2882
|
+
if (!withdrawAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${current.protocolId} not found`);
|
|
2883
|
+
const withdrawResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2884
|
+
const built = await withdrawAdapter.buildWithdrawTx(this._address, current.amount, current.asset);
|
|
2885
|
+
amountToDeposit = isSameAsset ? built.effectiveAmount : built.effectiveAmount;
|
|
2886
|
+
return built.tx;
|
|
2887
|
+
});
|
|
2888
|
+
txDigests.push(withdrawResult.digest);
|
|
2889
|
+
totalGasCost += withdrawResult.gasCostSui;
|
|
2890
|
+
if (!isSameAsset) {
|
|
2891
|
+
const swapAdapter = this.registry.listSwap()[0];
|
|
2892
|
+
if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
|
|
2893
|
+
const swapResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2894
|
+
const built = await swapAdapter.buildSwapTx(this._address, current.asset, bestRate.asset, amountToDeposit);
|
|
2895
|
+
amountToDeposit = built.estimatedOut / 10 ** built.toDecimals;
|
|
2896
|
+
return built.tx;
|
|
2897
|
+
});
|
|
2898
|
+
txDigests.push(swapResult.digest);
|
|
2899
|
+
totalGasCost += swapResult.gasCostSui;
|
|
2900
|
+
}
|
|
2901
|
+
const depositAdapter = this.registry.getLending(bestRate.protocolId);
|
|
2902
|
+
if (!depositAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${bestRate.protocolId} not found`);
|
|
2903
|
+
const depositResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
2904
|
+
const { tx } = await depositAdapter.buildSaveTx(this._address, amountToDeposit, bestRate.asset, { collectFee: bestRate.asset === "USDC" });
|
|
2905
|
+
return tx;
|
|
2906
|
+
});
|
|
2907
|
+
txDigests.push(depositResult.digest);
|
|
2908
|
+
totalGasCost += depositResult.gasCostSui;
|
|
2909
|
+
return {
|
|
2910
|
+
executed: true,
|
|
2911
|
+
steps,
|
|
2912
|
+
fromProtocol: current.protocol,
|
|
2913
|
+
fromAsset: current.asset,
|
|
2914
|
+
toProtocol: bestRate.protocol,
|
|
2915
|
+
toAsset: bestRate.asset,
|
|
2916
|
+
amount: current.amount,
|
|
2917
|
+
currentApy: current.apy,
|
|
2918
|
+
newApy: bestRate.rates.saveApy,
|
|
2919
|
+
annualGain,
|
|
2920
|
+
estimatedSwapCost,
|
|
2921
|
+
breakEvenDays,
|
|
2922
|
+
txDigests,
|
|
2923
|
+
totalGasCost
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2470
2926
|
async earnings() {
|
|
2471
2927
|
const result = await getEarnings(this.client, this.keypair);
|
|
2472
2928
|
if (result.totalYieldEarned > 0) {
|
|
@@ -2507,13 +2963,32 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2507
2963
|
const adapters2 = this.registry.listLending().filter(
|
|
2508
2964
|
(a) => a.supportedAssets.includes(asset) && a.capabilities.includes(capability) && (capability !== "borrow" || a.supportsSameAssetBorrow)
|
|
2509
2965
|
);
|
|
2510
|
-
if (adapters2.length === 0)
|
|
2966
|
+
if (adapters2.length === 0) {
|
|
2967
|
+
const alternatives = this.registry.listLending().filter(
|
|
2968
|
+
(a) => a.capabilities.includes(capability) && (capability !== "borrow" || a.supportsSameAssetBorrow)
|
|
2969
|
+
);
|
|
2970
|
+
if (alternatives.length > 0) {
|
|
2971
|
+
const altList = alternatives.map((a) => a.name).join(", ");
|
|
2972
|
+
const altAssets = [...new Set(alternatives.flatMap((a) => [...a.supportedAssets]))].join(", ");
|
|
2973
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No protocol supports ${capability} for ${asset}. Available for ${capability}: ${altList} (assets: ${altAssets})`);
|
|
2974
|
+
}
|
|
2975
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No adapter supports ${capability} ${asset}`);
|
|
2976
|
+
}
|
|
2511
2977
|
return adapters2[0];
|
|
2512
2978
|
}
|
|
2513
2979
|
const adapters = this.registry.listLending().filter(
|
|
2514
2980
|
(a) => a.supportedAssets.includes(asset) && a.capabilities.includes(capability)
|
|
2515
2981
|
);
|
|
2516
|
-
if (adapters.length === 0)
|
|
2982
|
+
if (adapters.length === 0) {
|
|
2983
|
+
const alternatives = this.registry.listLending().filter(
|
|
2984
|
+
(a) => a.capabilities.includes(capability)
|
|
2985
|
+
);
|
|
2986
|
+
if (alternatives.length > 0) {
|
|
2987
|
+
const altList = alternatives.map((a) => `${a.name} (${[...a.supportedAssets].join(", ")})`).join("; ");
|
|
2988
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No protocol supports ${capability} for ${asset}. Try: ${altList}`);
|
|
2989
|
+
}
|
|
2990
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No adapter supports ${capability} ${asset}`);
|
|
2991
|
+
}
|
|
2517
2992
|
return adapters[0];
|
|
2518
2993
|
}
|
|
2519
2994
|
emitBalanceChange(asset, amount, cause, tx) {
|
|
@@ -2622,6 +3097,6 @@ var allDescriptors = [
|
|
|
2622
3097
|
descriptor
|
|
2623
3098
|
];
|
|
2624
3099
|
|
|
2625
|
-
export { BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, DEFAULT_NETWORK, MIST_PER_SUI, NaviAdapter, ProtocolRegistry, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, addCollectFeeToTx, allDescriptors, calculateFee, descriptor3 as cetusDescriptor, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getGasStatus, getPoolPrice, getRates, getSentinelInfo, getSwapQuote, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, descriptor2 as naviDescriptor, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, descriptor as sentinelDescriptor, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, submitPrompt, suiToMist, descriptor4 as suilendDescriptor, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
3100
|
+
export { BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, DEFAULT_NETWORK, MIST_PER_SUI, NaviAdapter, ProtocolRegistry, SENTINEL, STABLE_ASSETS, SUI_DECIMALS, SUPPORTED_ASSETS, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, addCollectFeeToTx, allDescriptors, calculateFee, descriptor3 as cetusDescriptor, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, getSentinelInfo, getSwapQuote, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, descriptor2 as naviDescriptor, normalizeAsset, rawToStable, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, descriptor as sentinelDescriptor, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, submitPrompt, suiToMist, descriptor4 as suilendDescriptor, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
2626
3101
|
//# sourceMappingURL=index.js.map
|
|
2627
3102
|
//# sourceMappingURL=index.js.map
|