@t2000/sdk 0.7.2 → 0.8.1
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/adapters/index.js
CHANGED
|
@@ -3,6 +3,52 @@ import { bcs } from '@mysten/sui/bcs';
|
|
|
3
3
|
import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
|
|
4
4
|
import { normalizeStructTag } from '@mysten/sui/utils';
|
|
5
5
|
|
|
6
|
+
// src/constants.ts
|
|
7
|
+
var SAVE_FEE_BPS = 10n;
|
|
8
|
+
var SWAP_FEE_BPS = 0n;
|
|
9
|
+
var BORROW_FEE_BPS = 5n;
|
|
10
|
+
var SUPPORTED_ASSETS = {
|
|
11
|
+
USDC: {
|
|
12
|
+
type: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
|
|
13
|
+
decimals: 6,
|
|
14
|
+
symbol: "USDC",
|
|
15
|
+
displayName: "USDC"
|
|
16
|
+
},
|
|
17
|
+
USDT: {
|
|
18
|
+
type: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
|
|
19
|
+
decimals: 6,
|
|
20
|
+
symbol: "USDT",
|
|
21
|
+
displayName: "suiUSDT"
|
|
22
|
+
},
|
|
23
|
+
USDe: {
|
|
24
|
+
type: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
|
|
25
|
+
decimals: 6,
|
|
26
|
+
symbol: "USDe",
|
|
27
|
+
displayName: "suiUSDe"
|
|
28
|
+
},
|
|
29
|
+
USDsui: {
|
|
30
|
+
type: "0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI",
|
|
31
|
+
decimals: 6,
|
|
32
|
+
symbol: "USDsui",
|
|
33
|
+
displayName: "USDsui"
|
|
34
|
+
},
|
|
35
|
+
SUI: {
|
|
36
|
+
type: "0x2::sui::SUI",
|
|
37
|
+
decimals: 9,
|
|
38
|
+
symbol: "SUI",
|
|
39
|
+
displayName: "SUI"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var STABLE_ASSETS = ["USDC", "USDT", "USDe", "USDsui"];
|
|
43
|
+
var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
|
|
44
|
+
var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
|
|
45
|
+
var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
|
|
46
|
+
process.env.T2000_API_URL ?? "https://api.t2000.ai";
|
|
47
|
+
var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
|
|
48
|
+
var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
|
|
49
|
+
var SENTINEL = {
|
|
50
|
+
PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7"};
|
|
51
|
+
|
|
6
52
|
// src/errors.ts
|
|
7
53
|
var T2000Error = class extends Error {
|
|
8
54
|
code;
|
|
@@ -87,6 +133,41 @@ var ProtocolRegistry = class {
|
|
|
87
133
|
candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
|
|
88
134
|
return candidates[0];
|
|
89
135
|
}
|
|
136
|
+
async bestSaveRateAcrossAssets() {
|
|
137
|
+
const candidates = [];
|
|
138
|
+
for (const asset of STABLE_ASSETS) {
|
|
139
|
+
for (const adapter of this.lending.values()) {
|
|
140
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
141
|
+
if (!adapter.capabilities.includes("save")) continue;
|
|
142
|
+
try {
|
|
143
|
+
const rate = await adapter.getRates(asset);
|
|
144
|
+
candidates.push({ adapter, rate, asset });
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (candidates.length === 0) {
|
|
150
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", "No lending adapter found for any stablecoin");
|
|
151
|
+
}
|
|
152
|
+
candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
|
|
153
|
+
return candidates[0];
|
|
154
|
+
}
|
|
155
|
+
async allRatesAcrossAssets() {
|
|
156
|
+
const results = [];
|
|
157
|
+
for (const asset of STABLE_ASSETS) {
|
|
158
|
+
for (const adapter of this.lending.values()) {
|
|
159
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
160
|
+
try {
|
|
161
|
+
const rates = await adapter.getRates(asset);
|
|
162
|
+
if (rates.saveApy > 0 || rates.borrowApy > 0) {
|
|
163
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, asset, rates });
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
90
171
|
async allRates(asset) {
|
|
91
172
|
const results = [];
|
|
92
173
|
for (const adapter of this.lending.values()) {
|
|
@@ -124,38 +205,33 @@ var ProtocolRegistry = class {
|
|
|
124
205
|
listSwap() {
|
|
125
206
|
return [...this.swap.values()];
|
|
126
207
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
208
|
+
isSupportedAsset(asset, capability) {
|
|
209
|
+
for (const adapter of this.lending.values()) {
|
|
210
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
211
|
+
if (capability && !adapter.capabilities.includes(capability)) continue;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
getSupportedAssets(capability) {
|
|
217
|
+
const assets = /* @__PURE__ */ new Set();
|
|
218
|
+
for (const adapter of this.lending.values()) {
|
|
219
|
+
if (capability && !adapter.capabilities.includes(capability)) continue;
|
|
220
|
+
for (const a of adapter.supportedAssets) {
|
|
221
|
+
assets.add(a);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return [...assets];
|
|
144
225
|
}
|
|
145
226
|
};
|
|
146
|
-
var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
|
|
147
|
-
var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
|
|
148
|
-
var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
|
|
149
|
-
process.env.T2000_API_URL ?? "https://api.t2000.ai";
|
|
150
|
-
var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
|
|
151
|
-
var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
|
|
152
|
-
var SENTINEL = {
|
|
153
|
-
PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7"};
|
|
154
227
|
|
|
155
228
|
// src/utils/format.ts
|
|
156
|
-
function
|
|
157
|
-
return BigInt(Math.round(amount * 10 **
|
|
229
|
+
function stableToRaw(amount, decimals) {
|
|
230
|
+
return BigInt(Math.round(amount * 10 ** decimals));
|
|
158
231
|
}
|
|
232
|
+
new Map(
|
|
233
|
+
Object.keys(SUPPORTED_ASSETS).map((k) => [k.toUpperCase(), k])
|
|
234
|
+
);
|
|
159
235
|
|
|
160
236
|
// src/protocols/protocolFee.ts
|
|
161
237
|
var FEE_RATES = {
|
|
@@ -182,9 +258,6 @@ function addCollectFeeToTx(tx, paymentCoin, operation) {
|
|
|
182
258
|
]
|
|
183
259
|
});
|
|
184
260
|
}
|
|
185
|
-
|
|
186
|
-
// src/protocols/navi.ts
|
|
187
|
-
var USDC_TYPE = SUPPORTED_ASSETS.USDC.type;
|
|
188
261
|
var RATE_DECIMALS = 27;
|
|
189
262
|
var LTV_DECIMALS = 27;
|
|
190
263
|
var MIN_HEALTH_FACTOR = 1.5;
|
|
@@ -245,13 +318,24 @@ async function getPools(fresh = false) {
|
|
|
245
318
|
poolsCache = { data, ts: Date.now() };
|
|
246
319
|
return data;
|
|
247
320
|
}
|
|
248
|
-
|
|
321
|
+
function matchesCoinType(poolType, targetType) {
|
|
322
|
+
const poolSuffix = poolType.split("::").slice(1).join("::").toLowerCase();
|
|
323
|
+
const targetSuffix = targetType.split("::").slice(1).join("::").toLowerCase();
|
|
324
|
+
return poolSuffix === targetSuffix;
|
|
325
|
+
}
|
|
326
|
+
async function getPool(asset = "USDC") {
|
|
249
327
|
const pools = await getPools();
|
|
250
|
-
const
|
|
251
|
-
|
|
328
|
+
const targetType = SUPPORTED_ASSETS[asset].type;
|
|
329
|
+
const pool = pools.find(
|
|
330
|
+
(p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType)
|
|
252
331
|
);
|
|
253
|
-
if (!
|
|
254
|
-
|
|
332
|
+
if (!pool) {
|
|
333
|
+
throw new T2000Error(
|
|
334
|
+
"ASSET_NOT_SUPPORTED",
|
|
335
|
+
`${SUPPORTED_ASSETS[asset].displayName} pool not found on NAVI. Try: ${STABLE_ASSETS.filter((a) => a !== asset).join(", ")}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return pool;
|
|
255
339
|
}
|
|
256
340
|
function addOracleUpdate(tx, config, pool) {
|
|
257
341
|
const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
|
|
@@ -339,10 +423,12 @@ async function buildSaveTx(client, address, amount, options = {}) {
|
|
|
339
423
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
340
424
|
throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
|
|
341
425
|
}
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
426
|
+
const asset = options.asset ?? "USDC";
|
|
427
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
428
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
429
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
430
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
431
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
346
432
|
const tx = new Transaction();
|
|
347
433
|
tx.setSender(address);
|
|
348
434
|
const coinObj = mergeCoins(tx, coins);
|
|
@@ -365,18 +451,20 @@ async function buildSaveTx(client, address, amount, options = {}) {
|
|
|
365
451
|
});
|
|
366
452
|
return tx;
|
|
367
453
|
}
|
|
368
|
-
async function buildWithdrawTx(client, address, amount) {
|
|
454
|
+
async function buildWithdrawTx(client, address, amount, options = {}) {
|
|
455
|
+
const asset = options.asset ?? "USDC";
|
|
456
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
369
457
|
const [config, pool, pools, states] = await Promise.all([
|
|
370
458
|
getConfig(),
|
|
371
|
-
|
|
459
|
+
getPool(asset),
|
|
372
460
|
getPools(),
|
|
373
461
|
getUserState(client, address)
|
|
374
462
|
]);
|
|
375
|
-
const
|
|
376
|
-
const deposited =
|
|
463
|
+
const assetState = states.find((s) => s.assetId === pool.id);
|
|
464
|
+
const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex) : 0;
|
|
377
465
|
const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
|
|
378
|
-
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL",
|
|
379
|
-
const rawAmount = Number(
|
|
466
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
|
|
467
|
+
const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
|
|
380
468
|
const tx = new Transaction();
|
|
381
469
|
tx.setSender(address);
|
|
382
470
|
addOracleUpdate(tx, config, pool);
|
|
@@ -407,8 +495,10 @@ async function buildBorrowTx(client, address, amount, options = {}) {
|
|
|
407
495
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
408
496
|
throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
|
|
409
497
|
}
|
|
410
|
-
const
|
|
411
|
-
const
|
|
498
|
+
const asset = options.asset ?? "USDC";
|
|
499
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
500
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
501
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
412
502
|
const tx = new Transaction();
|
|
413
503
|
tx.setSender(address);
|
|
414
504
|
addOracleUpdate(tx, config, pool);
|
|
@@ -438,14 +528,16 @@ async function buildBorrowTx(client, address, amount, options = {}) {
|
|
|
438
528
|
tx.transferObjects([borrowedCoin], address);
|
|
439
529
|
return tx;
|
|
440
530
|
}
|
|
441
|
-
async function buildRepayTx(client, address, amount) {
|
|
531
|
+
async function buildRepayTx(client, address, amount, options = {}) {
|
|
442
532
|
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
443
533
|
throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
|
|
444
534
|
}
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
|
|
535
|
+
const asset = options.asset ?? "USDC";
|
|
536
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
537
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
538
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
539
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
540
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
449
541
|
const tx = new Transaction();
|
|
450
542
|
tx.setSender(address);
|
|
451
543
|
addOracleUpdate(tx, config, pool);
|
|
@@ -469,21 +561,36 @@ async function buildRepayTx(client, address, amount) {
|
|
|
469
561
|
}
|
|
470
562
|
async function getHealthFactor(client, addressOrKeypair) {
|
|
471
563
|
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
472
|
-
const [config,
|
|
564
|
+
const [config, pools, states] = await Promise.all([
|
|
473
565
|
getConfig(),
|
|
474
|
-
|
|
566
|
+
getPools(),
|
|
475
567
|
getUserState(client, address)
|
|
476
568
|
]);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
569
|
+
let supplied = 0;
|
|
570
|
+
let borrowed = 0;
|
|
571
|
+
let weightedLtv = 0;
|
|
572
|
+
let weightedLiqThreshold = 0;
|
|
573
|
+
for (const state of states) {
|
|
574
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
575
|
+
if (!pool) continue;
|
|
576
|
+
const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex);
|
|
577
|
+
const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex);
|
|
578
|
+
const price = pool.token?.price ?? 1;
|
|
579
|
+
supplied += supplyBal * price;
|
|
580
|
+
borrowed += borrowBal * price;
|
|
581
|
+
if (supplyBal > 0) {
|
|
582
|
+
weightedLtv += supplyBal * price * parseLtv(pool.ltv);
|
|
583
|
+
weightedLiqThreshold += supplyBal * price * parseLiqThreshold(pool.liquidationFactor.threshold);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const ltv = supplied > 0 ? weightedLtv / supplied : 0.75;
|
|
587
|
+
const liqThreshold = supplied > 0 ? weightedLiqThreshold / supplied : 0.75;
|
|
482
588
|
const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
|
|
589
|
+
const usdcPool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", SUPPORTED_ASSETS.USDC.type));
|
|
483
590
|
let healthFactor;
|
|
484
591
|
if (borrowed <= 0) {
|
|
485
592
|
healthFactor = Infinity;
|
|
486
|
-
} else {
|
|
593
|
+
} else if (usdcPool) {
|
|
487
594
|
try {
|
|
488
595
|
const tx = new Transaction();
|
|
489
596
|
tx.moveCall({
|
|
@@ -492,14 +599,14 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
492
599
|
tx.object(CLOCK),
|
|
493
600
|
tx.object(config.storage),
|
|
494
601
|
tx.object(config.oracle.priceOracle),
|
|
495
|
-
tx.pure.u8(
|
|
602
|
+
tx.pure.u8(usdcPool.id),
|
|
496
603
|
tx.pure.address(address),
|
|
497
|
-
tx.pure.u8(
|
|
604
|
+
tx.pure.u8(usdcPool.id),
|
|
498
605
|
tx.pure.u64(0),
|
|
499
606
|
tx.pure.u64(0),
|
|
500
607
|
tx.pure.bool(false)
|
|
501
608
|
],
|
|
502
|
-
typeArguments: [
|
|
609
|
+
typeArguments: [usdcPool.suiCoinType]
|
|
503
610
|
});
|
|
504
611
|
const result = await client.devInspectTransactionBlock({
|
|
505
612
|
transactionBlock: tx,
|
|
@@ -509,11 +616,13 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
509
616
|
if (decoded !== void 0) {
|
|
510
617
|
healthFactor = normalizeHealthFactor(Number(decoded));
|
|
511
618
|
} else {
|
|
512
|
-
healthFactor =
|
|
619
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
513
620
|
}
|
|
514
621
|
} catch {
|
|
515
|
-
healthFactor =
|
|
622
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
516
623
|
}
|
|
624
|
+
} else {
|
|
625
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
517
626
|
}
|
|
518
627
|
return {
|
|
519
628
|
healthFactor,
|
|
@@ -525,12 +634,20 @@ async function getHealthFactor(client, addressOrKeypair) {
|
|
|
525
634
|
}
|
|
526
635
|
async function getRates(client) {
|
|
527
636
|
try {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
637
|
+
const pools = await getPools();
|
|
638
|
+
const result = {};
|
|
639
|
+
for (const asset of STABLE_ASSETS) {
|
|
640
|
+
const targetType = SUPPORTED_ASSETS[asset].type;
|
|
641
|
+
const pool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType));
|
|
642
|
+
if (!pool) continue;
|
|
643
|
+
let saveApy = rateToApy(pool.currentSupplyRate);
|
|
644
|
+
let borrowApy = rateToApy(pool.currentBorrowRate);
|
|
645
|
+
if (saveApy <= 0 || saveApy > 100) saveApy = 0;
|
|
646
|
+
if (borrowApy <= 0 || borrowApy > 100) borrowApy = 0;
|
|
647
|
+
result[asset] = { saveApy, borrowApy };
|
|
648
|
+
}
|
|
649
|
+
if (!result.USDC) result.USDC = { saveApy: 4, borrowApy: 6 };
|
|
650
|
+
return result;
|
|
534
651
|
} catch {
|
|
535
652
|
return { USDC: { saveApy: 4, borrowApy: 6 } };
|
|
536
653
|
}
|
|
@@ -608,7 +725,7 @@ var NaviAdapter = class {
|
|
|
608
725
|
name = "NAVI Protocol";
|
|
609
726
|
version = "1.0.0";
|
|
610
727
|
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
611
|
-
supportedAssets = [
|
|
728
|
+
supportedAssets = [...STABLE_ASSETS];
|
|
612
729
|
supportsSameAssetBorrow = true;
|
|
613
730
|
client;
|
|
614
731
|
async init(client) {
|
|
@@ -634,20 +751,24 @@ var NaviAdapter = class {
|
|
|
634
751
|
async getHealth(address) {
|
|
635
752
|
return getHealthFactor(this.client, address);
|
|
636
753
|
}
|
|
637
|
-
async buildSaveTx(address, amount,
|
|
638
|
-
const
|
|
754
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
755
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
756
|
+
const tx = await buildSaveTx(this.client, address, amount, { ...options, asset: stableAsset });
|
|
639
757
|
return { tx };
|
|
640
758
|
}
|
|
641
|
-
async buildWithdrawTx(address, amount,
|
|
642
|
-
const
|
|
759
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
760
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
761
|
+
const result = await buildWithdrawTx(this.client, address, amount, { asset: stableAsset });
|
|
643
762
|
return { tx: result.tx, effectiveAmount: result.effectiveAmount };
|
|
644
763
|
}
|
|
645
|
-
async buildBorrowTx(address, amount,
|
|
646
|
-
const
|
|
764
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
765
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
766
|
+
const tx = await buildBorrowTx(this.client, address, amount, { ...options, asset: stableAsset });
|
|
647
767
|
return { tx };
|
|
648
768
|
}
|
|
649
|
-
async buildRepayTx(address, amount,
|
|
650
|
-
const
|
|
769
|
+
async buildRepayTx(address, amount, asset) {
|
|
770
|
+
const stableAsset = asset?.toUpperCase() === "USDC" ? "USDC" : asset;
|
|
771
|
+
const tx = await buildRepayTx(this.client, address, amount, { asset: stableAsset });
|
|
651
772
|
return { tx };
|
|
652
773
|
}
|
|
653
774
|
async maxWithdraw(address, _asset) {
|
|
@@ -795,16 +916,21 @@ var CetusAdapter = class {
|
|
|
795
916
|
};
|
|
796
917
|
}
|
|
797
918
|
getSupportedPairs() {
|
|
798
|
-
|
|
919
|
+
const pairs = [
|
|
799
920
|
{ from: "USDC", to: "SUI" },
|
|
800
921
|
{ from: "SUI", to: "USDC" }
|
|
801
922
|
];
|
|
923
|
+
for (const a of STABLE_ASSETS) {
|
|
924
|
+
for (const b of STABLE_ASSETS) {
|
|
925
|
+
if (a !== b) pairs.push({ from: a, to: b });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return pairs;
|
|
802
929
|
}
|
|
803
930
|
async getPoolPrice() {
|
|
804
931
|
return getPoolPrice(this.client);
|
|
805
932
|
}
|
|
806
933
|
};
|
|
807
|
-
var USDC_TYPE2 = SUPPORTED_ASSETS.USDC.type;
|
|
808
934
|
var WAD = 1e18;
|
|
809
935
|
var MIN_HEALTH_FACTOR2 = 1.5;
|
|
810
936
|
var CLOCK2 = "0x6";
|
|
@@ -911,8 +1037,8 @@ var SuilendAdapter = class {
|
|
|
911
1037
|
id = "suilend";
|
|
912
1038
|
name = "Suilend";
|
|
913
1039
|
version = "2.0.0";
|
|
914
|
-
capabilities = ["save", "withdraw"];
|
|
915
|
-
supportedAssets = [
|
|
1040
|
+
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
1041
|
+
supportedAssets = [...STABLE_ASSETS];
|
|
916
1042
|
supportsSameAssetBorrow = false;
|
|
917
1043
|
client;
|
|
918
1044
|
publishedAt = null;
|
|
@@ -956,12 +1082,14 @@ var SuilendAdapter = class {
|
|
|
956
1082
|
return this.reserveCache;
|
|
957
1083
|
}
|
|
958
1084
|
findReserve(reserves, asset) {
|
|
959
|
-
const upper = asset.toUpperCase();
|
|
960
1085
|
let coinType;
|
|
961
|
-
if (
|
|
962
|
-
|
|
963
|
-
else if (asset.includes("::"))
|
|
964
|
-
|
|
1086
|
+
if (asset in SUPPORTED_ASSETS) {
|
|
1087
|
+
coinType = SUPPORTED_ASSETS[asset].type;
|
|
1088
|
+
} else if (asset.includes("::")) {
|
|
1089
|
+
coinType = asset;
|
|
1090
|
+
} else {
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
965
1093
|
try {
|
|
966
1094
|
const normalized = normalizeStructTag(coinType);
|
|
967
1095
|
return reserves.find((r) => {
|
|
@@ -1010,8 +1138,12 @@ var SuilendAdapter = class {
|
|
|
1010
1138
|
resolveSymbol(coinType) {
|
|
1011
1139
|
try {
|
|
1012
1140
|
const normalized = normalizeStructTag(coinType);
|
|
1013
|
-
|
|
1014
|
-
|
|
1141
|
+
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
1142
|
+
try {
|
|
1143
|
+
if (normalizeStructTag(info.type) === normalized) return key;
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1015
1147
|
} catch {
|
|
1016
1148
|
}
|
|
1017
1149
|
const parts = coinType.split("::");
|
|
@@ -1059,22 +1191,43 @@ var SuilendAdapter = class {
|
|
|
1059
1191
|
if (caps.length === 0) {
|
|
1060
1192
|
return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
|
|
1061
1193
|
}
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1194
|
+
const [reserves, obligation] = await Promise.all([
|
|
1195
|
+
this.loadReserves(),
|
|
1196
|
+
this.fetchObligation(caps[0].obligationId)
|
|
1197
|
+
]);
|
|
1198
|
+
let supplied = 0;
|
|
1199
|
+
let borrowed = 0;
|
|
1200
|
+
let weightedCloseLtv = 0;
|
|
1201
|
+
let weightedOpenLtv = 0;
|
|
1202
|
+
for (const dep of obligation.deposits) {
|
|
1203
|
+
const reserve = reserves[dep.reserveIdx];
|
|
1204
|
+
if (!reserve) continue;
|
|
1205
|
+
const ratio = cTokenRatio(reserve);
|
|
1206
|
+
const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
|
|
1207
|
+
supplied += amount;
|
|
1208
|
+
weightedCloseLtv += amount * (reserve.closeLtvPct / 100);
|
|
1209
|
+
weightedOpenLtv += amount * (reserve.openLtvPct / 100);
|
|
1210
|
+
}
|
|
1211
|
+
for (const bor of obligation.borrows) {
|
|
1212
|
+
const reserve = reserves[bor.reserveIdx];
|
|
1213
|
+
if (!reserve) continue;
|
|
1214
|
+
const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
|
|
1215
|
+
const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
|
|
1216
|
+
const posRate = bor.cumBorrowRateWad / WAD;
|
|
1217
|
+
borrowed += posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
|
|
1218
|
+
}
|
|
1219
|
+
const liqThreshold = supplied > 0 ? weightedCloseLtv / supplied : 0.75;
|
|
1220
|
+
const openLtv = supplied > 0 ? weightedOpenLtv / supplied : 0.7;
|
|
1070
1221
|
const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
|
|
1071
|
-
const maxBorrow = Math.max(0, supplied *
|
|
1222
|
+
const maxBorrow = Math.max(0, supplied * openLtv - borrowed);
|
|
1072
1223
|
return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
|
|
1073
1224
|
}
|
|
1074
|
-
async buildSaveTx(address, amount,
|
|
1225
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
1226
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1227
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1075
1228
|
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1076
|
-
const
|
|
1077
|
-
if (!
|
|
1229
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1230
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1078
1231
|
const caps = await this.fetchObligationCaps(address);
|
|
1079
1232
|
const tx = new Transaction();
|
|
1080
1233
|
tx.setSender(address);
|
|
@@ -1089,33 +1242,33 @@ var SuilendAdapter = class {
|
|
|
1089
1242
|
} else {
|
|
1090
1243
|
capRef = caps[0].id;
|
|
1091
1244
|
}
|
|
1092
|
-
const allCoins = await this.fetchAllCoins(address,
|
|
1093
|
-
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE",
|
|
1245
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1246
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
1094
1247
|
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1095
1248
|
if (allCoins.length > 1) {
|
|
1096
1249
|
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1097
1250
|
}
|
|
1098
|
-
const rawAmount =
|
|
1251
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals).toString();
|
|
1099
1252
|
const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
|
|
1100
1253
|
if (options?.collectFee) {
|
|
1101
1254
|
addCollectFeeToTx(tx, depositCoin, "save");
|
|
1102
1255
|
}
|
|
1103
1256
|
const [ctokens] = tx.moveCall({
|
|
1104
1257
|
target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
|
|
1105
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1258
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1106
1259
|
arguments: [
|
|
1107
1260
|
tx.object(LENDING_MARKET_ID),
|
|
1108
|
-
tx.pure.u64(
|
|
1261
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1109
1262
|
tx.object(CLOCK2),
|
|
1110
1263
|
depositCoin
|
|
1111
1264
|
]
|
|
1112
1265
|
});
|
|
1113
1266
|
tx.moveCall({
|
|
1114
1267
|
target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
|
|
1115
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1268
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1116
1269
|
arguments: [
|
|
1117
1270
|
tx.object(LENDING_MARKET_ID),
|
|
1118
|
-
tx.pure.u64(
|
|
1271
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1119
1272
|
typeof capRef === "string" ? tx.object(capRef) : capRef,
|
|
1120
1273
|
tx.object(CLOCK2),
|
|
1121
1274
|
ctokens
|
|
@@ -1126,42 +1279,44 @@ var SuilendAdapter = class {
|
|
|
1126
1279
|
}
|
|
1127
1280
|
return { tx };
|
|
1128
1281
|
}
|
|
1129
|
-
async buildWithdrawTx(address, amount,
|
|
1282
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
1283
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1284
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1130
1285
|
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
|
|
1131
|
-
const
|
|
1132
|
-
if (!
|
|
1286
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1287
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1133
1288
|
const caps = await this.fetchObligationCaps(address);
|
|
1134
1289
|
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
|
|
1135
1290
|
const positions = await this.getPositions(address);
|
|
1136
|
-
const deposited = positions.supplies.find((s) => s.asset ===
|
|
1291
|
+
const deposited = positions.supplies.find((s) => s.asset === assetKey)?.amount ?? 0;
|
|
1137
1292
|
const effectiveAmount = Math.min(amount, deposited);
|
|
1138
|
-
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL",
|
|
1139
|
-
const ratio = cTokenRatio(
|
|
1140
|
-
const ctokenAmount = Math.ceil(effectiveAmount * 10 **
|
|
1293
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
|
|
1294
|
+
const ratio = cTokenRatio(reserve);
|
|
1295
|
+
const ctokenAmount = Math.ceil(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
|
|
1141
1296
|
const tx = new Transaction();
|
|
1142
1297
|
tx.setSender(address);
|
|
1143
1298
|
const [ctokens] = tx.moveCall({
|
|
1144
1299
|
target: `${pkg}::lending_market::withdraw_ctokens`,
|
|
1145
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1300
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1146
1301
|
arguments: [
|
|
1147
1302
|
tx.object(LENDING_MARKET_ID),
|
|
1148
|
-
tx.pure.u64(
|
|
1303
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1149
1304
|
tx.object(caps[0].id),
|
|
1150
1305
|
tx.object(CLOCK2),
|
|
1151
1306
|
tx.pure.u64(ctokenAmount)
|
|
1152
1307
|
]
|
|
1153
1308
|
});
|
|
1154
|
-
const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${
|
|
1309
|
+
const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${assetInfo.type}>`;
|
|
1155
1310
|
const [none] = tx.moveCall({
|
|
1156
1311
|
target: "0x1::option::none",
|
|
1157
1312
|
typeArguments: [exemptionType]
|
|
1158
1313
|
});
|
|
1159
1314
|
const [coin] = tx.moveCall({
|
|
1160
1315
|
target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity`,
|
|
1161
|
-
typeArguments: [LENDING_MARKET_TYPE,
|
|
1316
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1162
1317
|
arguments: [
|
|
1163
1318
|
tx.object(LENDING_MARKET_ID),
|
|
1164
|
-
tx.pure.u64(
|
|
1319
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1165
1320
|
tx.object(CLOCK2),
|
|
1166
1321
|
ctokens,
|
|
1167
1322
|
none
|
|
@@ -1170,11 +1325,64 @@ var SuilendAdapter = class {
|
|
|
1170
1325
|
tx.transferObjects([coin], address);
|
|
1171
1326
|
return { tx, effectiveAmount };
|
|
1172
1327
|
}
|
|
1173
|
-
async buildBorrowTx(
|
|
1174
|
-
|
|
1328
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
1329
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1330
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1331
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1332
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1333
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1334
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1335
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found. Deposit collateral first with: t2000 save <amount>");
|
|
1336
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1337
|
+
const tx = new Transaction();
|
|
1338
|
+
tx.setSender(address);
|
|
1339
|
+
const [coin] = tx.moveCall({
|
|
1340
|
+
target: `${pkg}::lending_market::borrow`,
|
|
1341
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1342
|
+
arguments: [
|
|
1343
|
+
tx.object(LENDING_MARKET_ID),
|
|
1344
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1345
|
+
tx.object(caps[0].id),
|
|
1346
|
+
tx.object(CLOCK2),
|
|
1347
|
+
tx.pure.u64(rawAmount)
|
|
1348
|
+
]
|
|
1349
|
+
});
|
|
1350
|
+
if (options?.collectFee) {
|
|
1351
|
+
addCollectFeeToTx(tx, coin, "borrow");
|
|
1352
|
+
}
|
|
1353
|
+
tx.transferObjects([coin], address);
|
|
1354
|
+
return { tx };
|
|
1175
1355
|
}
|
|
1176
|
-
async buildRepayTx(
|
|
1177
|
-
|
|
1356
|
+
async buildRepayTx(address, amount, asset) {
|
|
1357
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1358
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1359
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1360
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1361
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1362
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1363
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
|
|
1364
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1365
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
1366
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1367
|
+
const tx = new Transaction();
|
|
1368
|
+
tx.setSender(address);
|
|
1369
|
+
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1370
|
+
if (allCoins.length > 1) {
|
|
1371
|
+
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1372
|
+
}
|
|
1373
|
+
const [repayCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount.toString()]);
|
|
1374
|
+
tx.moveCall({
|
|
1375
|
+
target: `${pkg}::lending_market::repay`,
|
|
1376
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1377
|
+
arguments: [
|
|
1378
|
+
tx.object(LENDING_MARKET_ID),
|
|
1379
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1380
|
+
tx.object(caps[0].id),
|
|
1381
|
+
tx.object(CLOCK2),
|
|
1382
|
+
repayCoin
|
|
1383
|
+
]
|
|
1384
|
+
});
|
|
1385
|
+
return { tx };
|
|
1178
1386
|
}
|
|
1179
1387
|
async maxWithdraw(address, _asset) {
|
|
1180
1388
|
const health = await this.getHealth(address);
|
|
@@ -1188,8 +1396,10 @@ var SuilendAdapter = class {
|
|
|
1188
1396
|
const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
|
|
1189
1397
|
return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
|
|
1190
1398
|
}
|
|
1191
|
-
async maxBorrow(
|
|
1192
|
-
|
|
1399
|
+
async maxBorrow(address, _asset) {
|
|
1400
|
+
const health = await this.getHealth(address);
|
|
1401
|
+
const maxAmount = health.maxBorrow;
|
|
1402
|
+
return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
|
|
1193
1403
|
}
|
|
1194
1404
|
async fetchAllCoins(owner, coinType) {
|
|
1195
1405
|
const all = [];
|