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