@t2000/sdk 0.2.7 → 0.4.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/LICENSE +21 -0
- package/dist/adapters/index.cjs +856 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +3 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +851 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/index-DYQv9Wxo.d.cts +393 -0
- package/dist/index-DYQv9Wxo.d.ts +393 -0
- package/dist/index.cjs +773 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -173
- package/dist/index.d.ts +24 -173
- package/dist/index.js +771 -195
- package/dist/index.js.map +1 -1
- package/package.json +26 -13
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'eventemitter3';
|
|
2
2
|
import { SuiClient } from '@mysten/sui/client';
|
|
3
|
-
import { normalizeSuiAddress, isValidSuiAddress } from '@mysten/sui/utils';
|
|
3
|
+
import { normalizeSuiAddress, isValidSuiAddress, normalizeStructTag } from '@mysten/sui/utils';
|
|
4
4
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
5
5
|
import { decodeSuiPrivateKey } from '@mysten/sui/cryptography';
|
|
6
6
|
import { createHash, randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
|
|
@@ -8,9 +8,9 @@ import { access, mkdir, writeFile, readFile } from 'fs/promises';
|
|
|
8
8
|
import { dirname, resolve } from 'path';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { Transaction } from '@mysten/sui/transactions';
|
|
11
|
-
import { getPool, getCoins, mergeCoinsPTB, depositCoinPTB,
|
|
12
|
-
import { CetusClmmSDK } from '@cetusprotocol/sui-clmm-sdk';
|
|
11
|
+
import { getPool, getLendingState, getHealthFactor as getHealthFactor$1, getCoins, mergeCoinsPTB, depositCoinPTB, withdrawCoinPTB, borrowCoinPTB, repayCoinPTB, getPriceFeeds, filterPriceFeeds, updateOraclePricesPTB } from '@naviprotocol/lending';
|
|
13
12
|
import { bcs } from '@mysten/sui/bcs';
|
|
13
|
+
import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
|
|
14
14
|
|
|
15
15
|
// src/t2000.ts
|
|
16
16
|
|
|
@@ -38,9 +38,9 @@ var SUPPORTED_ASSETS = {
|
|
|
38
38
|
symbol: "SUI"
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
process.env.T2000_PACKAGE_ID ?? "
|
|
42
|
-
process.env.T2000_CONFIG_ID ?? "
|
|
43
|
-
process.env.T2000_TREASURY_ID ?? "
|
|
41
|
+
var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
|
|
42
|
+
var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
|
|
43
|
+
var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
|
|
44
44
|
var DEFAULT_NETWORK = "mainnet";
|
|
45
45
|
var DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
|
|
46
46
|
var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
|
|
@@ -100,7 +100,8 @@ function mapMoveAbortCode(code) {
|
|
|
100
100
|
6: "Not authorized",
|
|
101
101
|
7: "Package version mismatch \u2014 upgrade required",
|
|
102
102
|
8: "Timelock is active \u2014 wait for expiry",
|
|
103
|
-
9: "No pending change to execute"
|
|
103
|
+
9: "No pending change to execute",
|
|
104
|
+
10: "Already at current version"
|
|
104
105
|
};
|
|
105
106
|
return abortMessages[code] ?? `Move abort code: ${code}`;
|
|
106
107
|
}
|
|
@@ -371,6 +372,61 @@ function inferAction(txBlock) {
|
|
|
371
372
|
if (kind === "ProgrammableTransaction") return "transaction";
|
|
372
373
|
return kind ?? "unknown";
|
|
373
374
|
}
|
|
375
|
+
|
|
376
|
+
// src/protocols/protocolFee.ts
|
|
377
|
+
var FEE_RATES = {
|
|
378
|
+
save: SAVE_FEE_BPS,
|
|
379
|
+
swap: SWAP_FEE_BPS,
|
|
380
|
+
borrow: BORROW_FEE_BPS
|
|
381
|
+
};
|
|
382
|
+
var OP_CODES = {
|
|
383
|
+
save: 0,
|
|
384
|
+
swap: 1,
|
|
385
|
+
borrow: 2
|
|
386
|
+
};
|
|
387
|
+
function calculateFee(operation, amount) {
|
|
388
|
+
const bps = FEE_RATES[operation];
|
|
389
|
+
const feeAmount = amount * Number(bps) / Number(BPS_DENOMINATOR);
|
|
390
|
+
const rawAmount = usdcToRaw(feeAmount);
|
|
391
|
+
return {
|
|
392
|
+
amount: feeAmount,
|
|
393
|
+
asset: "USDC",
|
|
394
|
+
rate: Number(bps) / Number(BPS_DENOMINATOR),
|
|
395
|
+
rawAmount
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function addCollectFeeToTx(tx, paymentCoin, operation) {
|
|
399
|
+
const bps = FEE_RATES[operation];
|
|
400
|
+
if (bps <= 0n) return;
|
|
401
|
+
tx.moveCall({
|
|
402
|
+
target: `${T2000_PACKAGE_ID}::treasury::collect_fee`,
|
|
403
|
+
typeArguments: [SUPPORTED_ASSETS.USDC.type],
|
|
404
|
+
arguments: [
|
|
405
|
+
tx.object(T2000_TREASURY_ID),
|
|
406
|
+
tx.object(T2000_CONFIG_ID),
|
|
407
|
+
paymentCoin,
|
|
408
|
+
tx.pure.u8(OP_CODES[operation])
|
|
409
|
+
]
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest) {
|
|
413
|
+
try {
|
|
414
|
+
await fetch(`${API_BASE_URL}/api/fees`, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: { "Content-Type": "application/json" },
|
|
417
|
+
body: JSON.stringify({
|
|
418
|
+
agentAddress,
|
|
419
|
+
operation,
|
|
420
|
+
feeAmount: feeAmount.toString(),
|
|
421
|
+
feeRate: feeRate.toString(),
|
|
422
|
+
txDigest
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/protocols/navi.ts
|
|
374
430
|
var ENV = { env: "prod" };
|
|
375
431
|
var USDC_TYPE = SUPPORTED_ASSETS.USDC.type;
|
|
376
432
|
var RATE_DECIMALS = 27;
|
|
@@ -413,7 +469,7 @@ async function updateOracle(tx, client, address) {
|
|
|
413
469
|
} catch {
|
|
414
470
|
}
|
|
415
471
|
}
|
|
416
|
-
async function buildSaveTx(client, address, amount) {
|
|
472
|
+
async function buildSaveTx(client, address, amount, options = {}) {
|
|
417
473
|
const rawAmount = Number(usdcToRaw(amount));
|
|
418
474
|
const coins = await getCoins(address, { coinType: USDC_TYPE, client });
|
|
419
475
|
if (!coins || coins.length === 0) {
|
|
@@ -422,6 +478,9 @@ async function buildSaveTx(client, address, amount) {
|
|
|
422
478
|
const tx = new Transaction();
|
|
423
479
|
tx.setSender(address);
|
|
424
480
|
const coinObj = mergeCoinsPTB(tx, coins, { balance: rawAmount });
|
|
481
|
+
if (options.collectFee) {
|
|
482
|
+
addCollectFeeToTx(tx, coinObj, "save");
|
|
483
|
+
}
|
|
425
484
|
await depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
|
|
426
485
|
return tx;
|
|
427
486
|
}
|
|
@@ -439,12 +498,15 @@ async function buildWithdrawTx(client, address, amount) {
|
|
|
439
498
|
tx.transferObjects([withdrawnCoin], address);
|
|
440
499
|
return { tx, effectiveAmount };
|
|
441
500
|
}
|
|
442
|
-
async function buildBorrowTx(client, address, amount) {
|
|
501
|
+
async function buildBorrowTx(client, address, amount, options = {}) {
|
|
443
502
|
const rawAmount = Number(usdcToRaw(amount));
|
|
444
503
|
const tx = new Transaction();
|
|
445
504
|
tx.setSender(address);
|
|
446
505
|
await updateOracle(tx, client, address);
|
|
447
506
|
const borrowedCoin = await borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
|
|
507
|
+
if (options.collectFee) {
|
|
508
|
+
addCollectFeeToTx(tx, borrowedCoin, "borrow");
|
|
509
|
+
}
|
|
448
510
|
tx.transferObjects([borrowedCoin], address);
|
|
449
511
|
return tx;
|
|
450
512
|
}
|
|
@@ -460,8 +522,8 @@ async function buildRepayTx(client, address, amount) {
|
|
|
460
522
|
await repayCoinPTB(tx, USDC_TYPE, coinObj, { ...ENV, amount: rawAmount });
|
|
461
523
|
return tx;
|
|
462
524
|
}
|
|
463
|
-
async function getHealthFactor(client,
|
|
464
|
-
const address =
|
|
525
|
+
async function getHealthFactor(client, addressOrKeypair) {
|
|
526
|
+
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
465
527
|
const [healthFactor, state, pool] = await Promise.all([
|
|
466
528
|
getHealthFactor$1(address, clientOpt(client, true)),
|
|
467
529
|
getLendingState(address, clientOpt(client, true)),
|
|
@@ -493,8 +555,8 @@ async function getRates(client) {
|
|
|
493
555
|
return { USDC: { saveApy: 4, borrowApy: 6 } };
|
|
494
556
|
}
|
|
495
557
|
}
|
|
496
|
-
async function getPositions(client,
|
|
497
|
-
const address =
|
|
558
|
+
async function getPositions(client, addressOrKeypair) {
|
|
559
|
+
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
498
560
|
const state = await getLendingState(address, clientOpt(client, true));
|
|
499
561
|
const positions = [];
|
|
500
562
|
for (const pos of state) {
|
|
@@ -522,8 +584,8 @@ async function getPositions(client, keypair) {
|
|
|
522
584
|
}
|
|
523
585
|
return { positions };
|
|
524
586
|
}
|
|
525
|
-
async function maxWithdrawAmount(client,
|
|
526
|
-
const hf = await getHealthFactor(client,
|
|
587
|
+
async function maxWithdrawAmount(client, addressOrKeypair) {
|
|
588
|
+
const hf = await getHealthFactor(client, addressOrKeypair);
|
|
527
589
|
const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
|
|
528
590
|
let maxAmount;
|
|
529
591
|
if (hf.borrowed === 0) {
|
|
@@ -539,8 +601,8 @@ async function maxWithdrawAmount(client, keypair) {
|
|
|
539
601
|
currentHF: hf.healthFactor
|
|
540
602
|
};
|
|
541
603
|
}
|
|
542
|
-
async function maxBorrowAmount(client,
|
|
543
|
-
const hf = await getHealthFactor(client,
|
|
604
|
+
async function maxBorrowAmount(client, addressOrKeypair) {
|
|
605
|
+
const hf = await getHealthFactor(client, addressOrKeypair);
|
|
544
606
|
const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
|
|
545
607
|
const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
|
|
546
608
|
return {
|
|
@@ -549,142 +611,6 @@ async function maxBorrowAmount(client, keypair) {
|
|
|
549
611
|
currentHF: hf.healthFactor
|
|
550
612
|
};
|
|
551
613
|
}
|
|
552
|
-
var DEFAULT_SLIPPAGE_BPS = 300;
|
|
553
|
-
function isA2B(from) {
|
|
554
|
-
return from === "USDC";
|
|
555
|
-
}
|
|
556
|
-
var _cetusSDK = null;
|
|
557
|
-
function getCetusSDK() {
|
|
558
|
-
if (!_cetusSDK) {
|
|
559
|
-
_cetusSDK = CetusClmmSDK.createSDK({ env: "mainnet" });
|
|
560
|
-
}
|
|
561
|
-
return _cetusSDK;
|
|
562
|
-
}
|
|
563
|
-
async function buildSwapTx(params) {
|
|
564
|
-
const { client, address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
|
|
565
|
-
const a2b = isA2B(fromAsset);
|
|
566
|
-
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
567
|
-
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
568
|
-
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
569
|
-
const sdk = getCetusSDK();
|
|
570
|
-
sdk.setSenderAddress(address);
|
|
571
|
-
const pool = await sdk.Pool.getPool(CETUS_USDC_SUI_POOL);
|
|
572
|
-
const preSwapResult = await sdk.Swap.preSwap({
|
|
573
|
-
pool,
|
|
574
|
-
current_sqrt_price: pool.current_sqrt_price,
|
|
575
|
-
coin_type_a: pool.coin_type_a,
|
|
576
|
-
coin_type_b: pool.coin_type_b,
|
|
577
|
-
decimals_a: 6,
|
|
578
|
-
decimals_b: 9,
|
|
579
|
-
a2b,
|
|
580
|
-
by_amount_in: true,
|
|
581
|
-
amount: rawAmount.toString()
|
|
582
|
-
});
|
|
583
|
-
const estimatedOut = Number(preSwapResult.estimated_amount_out);
|
|
584
|
-
const slippageFactor = (1e4 - maxSlippageBps) / 1e4;
|
|
585
|
-
const amountLimit = Math.floor(estimatedOut * slippageFactor);
|
|
586
|
-
const swapPayload = await sdk.Swap.createSwapPayload({
|
|
587
|
-
pool_id: pool.id,
|
|
588
|
-
coin_type_a: pool.coin_type_a,
|
|
589
|
-
coin_type_b: pool.coin_type_b,
|
|
590
|
-
a2b,
|
|
591
|
-
by_amount_in: true,
|
|
592
|
-
amount: preSwapResult.amount.toString(),
|
|
593
|
-
amount_limit: amountLimit.toString()
|
|
594
|
-
});
|
|
595
|
-
return {
|
|
596
|
-
tx: swapPayload,
|
|
597
|
-
estimatedOut,
|
|
598
|
-
toDecimals: toInfo.decimals
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
async function getPoolPrice(client) {
|
|
602
|
-
try {
|
|
603
|
-
const pool = await client.getObject({
|
|
604
|
-
id: CETUS_USDC_SUI_POOL,
|
|
605
|
-
options: { showContent: true }
|
|
606
|
-
});
|
|
607
|
-
if (pool.data?.content?.dataType === "moveObject") {
|
|
608
|
-
const fields = pool.data.content.fields;
|
|
609
|
-
const currentSqrtPrice = BigInt(String(fields.current_sqrt_price ?? "0"));
|
|
610
|
-
if (currentSqrtPrice > 0n) {
|
|
611
|
-
const Q64 = 2n ** 64n;
|
|
612
|
-
const sqrtPriceFloat = Number(currentSqrtPrice) / Number(Q64);
|
|
613
|
-
const rawPrice = sqrtPriceFloat * sqrtPriceFloat;
|
|
614
|
-
const suiPriceUsd = 1e3 / rawPrice;
|
|
615
|
-
if (suiPriceUsd > 0.01 && suiPriceUsd < 1e3) return suiPriceUsd;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
620
|
-
return 3.5;
|
|
621
|
-
}
|
|
622
|
-
async function getSwapQuote(client, fromAsset, toAsset, amount) {
|
|
623
|
-
const a2b = isA2B(fromAsset);
|
|
624
|
-
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
625
|
-
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
626
|
-
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
627
|
-
const poolPrice = await getPoolPrice(client);
|
|
628
|
-
try {
|
|
629
|
-
const sdk = getCetusSDK();
|
|
630
|
-
const pool = await sdk.Pool.getPool(CETUS_USDC_SUI_POOL);
|
|
631
|
-
const preSwapResult = await sdk.Swap.preSwap({
|
|
632
|
-
pool,
|
|
633
|
-
current_sqrt_price: pool.current_sqrt_price,
|
|
634
|
-
coin_type_a: pool.coin_type_a,
|
|
635
|
-
coin_type_b: pool.coin_type_b,
|
|
636
|
-
decimals_a: 6,
|
|
637
|
-
decimals_b: 9,
|
|
638
|
-
a2b,
|
|
639
|
-
by_amount_in: true,
|
|
640
|
-
amount: rawAmount.toString()
|
|
641
|
-
});
|
|
642
|
-
const expectedOutput = Number(preSwapResult.estimated_amount_out) / 10 ** toInfo.decimals;
|
|
643
|
-
return { expectedOutput, priceImpact: 0, poolPrice };
|
|
644
|
-
} catch {
|
|
645
|
-
let expectedOutput;
|
|
646
|
-
if (fromAsset === "USDC") {
|
|
647
|
-
expectedOutput = amount / poolPrice;
|
|
648
|
-
} else {
|
|
649
|
-
expectedOutput = amount * poolPrice;
|
|
650
|
-
}
|
|
651
|
-
return { expectedOutput, priceImpact: 0, poolPrice };
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// src/protocols/protocolFee.ts
|
|
656
|
-
var FEE_RATES = {
|
|
657
|
-
save: SAVE_FEE_BPS,
|
|
658
|
-
swap: SWAP_FEE_BPS,
|
|
659
|
-
borrow: BORROW_FEE_BPS
|
|
660
|
-
};
|
|
661
|
-
function calculateFee(operation, amount) {
|
|
662
|
-
const bps = FEE_RATES[operation];
|
|
663
|
-
const feeAmount = amount * Number(bps) / Number(BPS_DENOMINATOR);
|
|
664
|
-
const rawAmount = usdcToRaw(feeAmount);
|
|
665
|
-
return {
|
|
666
|
-
amount: feeAmount,
|
|
667
|
-
asset: "USDC",
|
|
668
|
-
rate: Number(bps) / Number(BPS_DENOMINATOR),
|
|
669
|
-
rawAmount
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest) {
|
|
673
|
-
try {
|
|
674
|
-
await fetch(`${API_BASE_URL}/api/fees`, {
|
|
675
|
-
method: "POST",
|
|
676
|
-
headers: { "Content-Type": "application/json" },
|
|
677
|
-
body: JSON.stringify({
|
|
678
|
-
agentAddress,
|
|
679
|
-
operation,
|
|
680
|
-
feeAmount: feeAmount.toString(),
|
|
681
|
-
feeRate: feeRate.toString(),
|
|
682
|
-
txDigest
|
|
683
|
-
})
|
|
684
|
-
});
|
|
685
|
-
} catch {
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
614
|
|
|
689
615
|
// src/protocols/yieldTracker.ts
|
|
690
616
|
async function getEarnings(client, keypair) {
|
|
@@ -896,6 +822,298 @@ async function attack(client, signer, sentinelId, prompt, feeMist) {
|
|
|
896
822
|
feePaid: Number(fee) / Number(MIST_PER_SUI)
|
|
897
823
|
};
|
|
898
824
|
}
|
|
825
|
+
|
|
826
|
+
// src/adapters/registry.ts
|
|
827
|
+
var ProtocolRegistry = class {
|
|
828
|
+
lending = /* @__PURE__ */ new Map();
|
|
829
|
+
swap = /* @__PURE__ */ new Map();
|
|
830
|
+
registerLending(adapter) {
|
|
831
|
+
this.lending.set(adapter.id, adapter);
|
|
832
|
+
}
|
|
833
|
+
registerSwap(adapter) {
|
|
834
|
+
this.swap.set(adapter.id, adapter);
|
|
835
|
+
}
|
|
836
|
+
async bestSaveRate(asset) {
|
|
837
|
+
const candidates = [];
|
|
838
|
+
for (const adapter of this.lending.values()) {
|
|
839
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
840
|
+
if (!adapter.capabilities.includes("save")) continue;
|
|
841
|
+
try {
|
|
842
|
+
const rate = await adapter.getRates(asset);
|
|
843
|
+
candidates.push({ adapter, rate });
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (candidates.length === 0) {
|
|
848
|
+
throw new Error(`No lending adapter supports saving ${asset}`);
|
|
849
|
+
}
|
|
850
|
+
candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
|
|
851
|
+
return candidates[0];
|
|
852
|
+
}
|
|
853
|
+
async bestBorrowRate(asset, opts) {
|
|
854
|
+
const candidates = [];
|
|
855
|
+
for (const adapter of this.lending.values()) {
|
|
856
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
857
|
+
if (!adapter.capabilities.includes("borrow")) continue;
|
|
858
|
+
if (opts?.requireSameAssetBorrow && !adapter.supportsSameAssetBorrow) continue;
|
|
859
|
+
try {
|
|
860
|
+
const rate = await adapter.getRates(asset);
|
|
861
|
+
candidates.push({ adapter, rate });
|
|
862
|
+
} catch {
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (candidates.length === 0) {
|
|
866
|
+
throw new Error(`No lending adapter supports borrowing ${asset}`);
|
|
867
|
+
}
|
|
868
|
+
candidates.sort((a, b) => a.rate.borrowApy - b.rate.borrowApy);
|
|
869
|
+
return candidates[0];
|
|
870
|
+
}
|
|
871
|
+
async bestSwapQuote(from, to, amount) {
|
|
872
|
+
const candidates = [];
|
|
873
|
+
for (const adapter of this.swap.values()) {
|
|
874
|
+
const pairs = adapter.getSupportedPairs();
|
|
875
|
+
if (!pairs.some((p) => p.from === from && p.to === to)) continue;
|
|
876
|
+
try {
|
|
877
|
+
const quote = await adapter.getQuote(from, to, amount);
|
|
878
|
+
candidates.push({ adapter, quote });
|
|
879
|
+
} catch {
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (candidates.length === 0) {
|
|
883
|
+
throw new Error(`No swap adapter supports ${from} \u2192 ${to}`);
|
|
884
|
+
}
|
|
885
|
+
candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
|
|
886
|
+
return candidates[0];
|
|
887
|
+
}
|
|
888
|
+
async allRates(asset) {
|
|
889
|
+
const results = [];
|
|
890
|
+
for (const adapter of this.lending.values()) {
|
|
891
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
892
|
+
try {
|
|
893
|
+
const rates = await adapter.getRates(asset);
|
|
894
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, rates });
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return results;
|
|
899
|
+
}
|
|
900
|
+
async allPositions(address) {
|
|
901
|
+
const results = [];
|
|
902
|
+
for (const adapter of this.lending.values()) {
|
|
903
|
+
try {
|
|
904
|
+
const positions = await adapter.getPositions(address);
|
|
905
|
+
if (positions.supplies.length > 0 || positions.borrows.length > 0) {
|
|
906
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, positions });
|
|
907
|
+
}
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return results;
|
|
912
|
+
}
|
|
913
|
+
getLending(id) {
|
|
914
|
+
return this.lending.get(id);
|
|
915
|
+
}
|
|
916
|
+
getSwap(id) {
|
|
917
|
+
return this.swap.get(id);
|
|
918
|
+
}
|
|
919
|
+
listLending() {
|
|
920
|
+
return [...this.lending.values()];
|
|
921
|
+
}
|
|
922
|
+
listSwap() {
|
|
923
|
+
return [...this.swap.values()];
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/adapters/navi.ts
|
|
928
|
+
var NaviAdapter = class {
|
|
929
|
+
id = "navi";
|
|
930
|
+
name = "NAVI Protocol";
|
|
931
|
+
version = "1.0.0";
|
|
932
|
+
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
933
|
+
supportedAssets = ["USDC"];
|
|
934
|
+
supportsSameAssetBorrow = true;
|
|
935
|
+
client;
|
|
936
|
+
async init(client) {
|
|
937
|
+
this.client = client;
|
|
938
|
+
}
|
|
939
|
+
initSync(client) {
|
|
940
|
+
this.client = client;
|
|
941
|
+
}
|
|
942
|
+
async getRates(asset) {
|
|
943
|
+
const rates = await getRates(this.client);
|
|
944
|
+
const key = asset.toUpperCase();
|
|
945
|
+
const r = rates[key];
|
|
946
|
+
if (!r) throw new Error(`NAVI does not support ${asset}`);
|
|
947
|
+
return { asset, saveApy: r.saveApy, borrowApy: r.borrowApy };
|
|
948
|
+
}
|
|
949
|
+
async getPositions(address) {
|
|
950
|
+
const result = await getPositions(this.client, address);
|
|
951
|
+
return {
|
|
952
|
+
supplies: result.positions.filter((p) => p.type === "save").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy })),
|
|
953
|
+
borrows: result.positions.filter((p) => p.type === "borrow").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy }))
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
async getHealth(address) {
|
|
957
|
+
return getHealthFactor(this.client, address);
|
|
958
|
+
}
|
|
959
|
+
async buildSaveTx(address, amount, _asset, options) {
|
|
960
|
+
const tx = await buildSaveTx(this.client, address, amount, options);
|
|
961
|
+
return { tx };
|
|
962
|
+
}
|
|
963
|
+
async buildWithdrawTx(address, amount, _asset) {
|
|
964
|
+
const result = await buildWithdrawTx(this.client, address, amount);
|
|
965
|
+
return { tx: result.tx, effectiveAmount: result.effectiveAmount };
|
|
966
|
+
}
|
|
967
|
+
async buildBorrowTx(address, amount, _asset, options) {
|
|
968
|
+
const tx = await buildBorrowTx(this.client, address, amount, options);
|
|
969
|
+
return { tx };
|
|
970
|
+
}
|
|
971
|
+
async buildRepayTx(address, amount, _asset) {
|
|
972
|
+
const tx = await buildRepayTx(this.client, address, amount);
|
|
973
|
+
return { tx };
|
|
974
|
+
}
|
|
975
|
+
async maxWithdraw(address, _asset) {
|
|
976
|
+
return maxWithdrawAmount(this.client, address);
|
|
977
|
+
}
|
|
978
|
+
async maxBorrow(address, _asset) {
|
|
979
|
+
return maxBorrowAmount(this.client, address);
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var DEFAULT_SLIPPAGE_BPS = 300;
|
|
983
|
+
function createAggregatorClient(client, signer) {
|
|
984
|
+
return new AggregatorClient({
|
|
985
|
+
client,
|
|
986
|
+
signer,
|
|
987
|
+
env: Env.Mainnet
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
async function buildSwapTx(params) {
|
|
991
|
+
const { client, address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
|
|
992
|
+
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
993
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
994
|
+
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
995
|
+
const aggClient = createAggregatorClient(client, address);
|
|
996
|
+
const result = await aggClient.findRouters({
|
|
997
|
+
from: fromInfo.type,
|
|
998
|
+
target: toInfo.type,
|
|
999
|
+
amount: rawAmount,
|
|
1000
|
+
byAmountIn: true
|
|
1001
|
+
});
|
|
1002
|
+
if (!result || result.insufficientLiquidity) {
|
|
1003
|
+
throw new T2000Error(
|
|
1004
|
+
"ASSET_NOT_SUPPORTED",
|
|
1005
|
+
`No swap route found for ${fromAsset} \u2192 ${toAsset}`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
const tx = new Transaction();
|
|
1009
|
+
const slippage = maxSlippageBps / 1e4;
|
|
1010
|
+
await aggClient.fastRouterSwap({
|
|
1011
|
+
router: result,
|
|
1012
|
+
txb: tx,
|
|
1013
|
+
slippage
|
|
1014
|
+
});
|
|
1015
|
+
const estimatedOut = Number(result.amountOut.toString());
|
|
1016
|
+
return {
|
|
1017
|
+
tx,
|
|
1018
|
+
estimatedOut,
|
|
1019
|
+
toDecimals: toInfo.decimals
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
async function getPoolPrice(client) {
|
|
1023
|
+
try {
|
|
1024
|
+
const pool = await client.getObject({
|
|
1025
|
+
id: CETUS_USDC_SUI_POOL,
|
|
1026
|
+
options: { showContent: true }
|
|
1027
|
+
});
|
|
1028
|
+
if (pool.data?.content?.dataType === "moveObject") {
|
|
1029
|
+
const fields = pool.data.content.fields;
|
|
1030
|
+
const currentSqrtPrice = BigInt(String(fields.current_sqrt_price ?? "0"));
|
|
1031
|
+
if (currentSqrtPrice > 0n) {
|
|
1032
|
+
const Q64 = 2n ** 64n;
|
|
1033
|
+
const sqrtPriceFloat = Number(currentSqrtPrice) / Number(Q64);
|
|
1034
|
+
const rawPrice = sqrtPriceFloat * sqrtPriceFloat;
|
|
1035
|
+
const suiPriceUsd = 1e3 / rawPrice;
|
|
1036
|
+
if (suiPriceUsd > 0.01 && suiPriceUsd < 1e3) return suiPriceUsd;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
return 3.5;
|
|
1042
|
+
}
|
|
1043
|
+
async function getSwapQuote(client, fromAsset, toAsset, amount) {
|
|
1044
|
+
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
1045
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1046
|
+
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
1047
|
+
const poolPrice = await getPoolPrice(client);
|
|
1048
|
+
try {
|
|
1049
|
+
const aggClient = createAggregatorClient(client);
|
|
1050
|
+
const result = await aggClient.findRouters({
|
|
1051
|
+
from: fromInfo.type,
|
|
1052
|
+
target: toInfo.type,
|
|
1053
|
+
amount: rawAmount,
|
|
1054
|
+
byAmountIn: true
|
|
1055
|
+
});
|
|
1056
|
+
if (!result || result.insufficientLiquidity) {
|
|
1057
|
+
return fallbackQuote(fromAsset, amount, poolPrice);
|
|
1058
|
+
}
|
|
1059
|
+
const expectedOutput = Number(result.amountOut.toString()) / 10 ** toInfo.decimals;
|
|
1060
|
+
const priceImpact = result.deviationRatio ?? 0;
|
|
1061
|
+
return { expectedOutput, priceImpact, poolPrice };
|
|
1062
|
+
} catch {
|
|
1063
|
+
return fallbackQuote(fromAsset, amount, poolPrice);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function fallbackQuote(fromAsset, amount, poolPrice) {
|
|
1067
|
+
const expectedOutput = fromAsset === "USDC" ? amount / poolPrice : amount * poolPrice;
|
|
1068
|
+
return { expectedOutput, priceImpact: 0, poolPrice };
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/adapters/cetus.ts
|
|
1072
|
+
var CetusAdapter = class {
|
|
1073
|
+
id = "cetus";
|
|
1074
|
+
name = "Cetus";
|
|
1075
|
+
version = "1.0.0";
|
|
1076
|
+
capabilities = ["swap"];
|
|
1077
|
+
client;
|
|
1078
|
+
async init(client) {
|
|
1079
|
+
this.client = client;
|
|
1080
|
+
}
|
|
1081
|
+
initSync(client) {
|
|
1082
|
+
this.client = client;
|
|
1083
|
+
}
|
|
1084
|
+
async getQuote(from, to, amount) {
|
|
1085
|
+
return getSwapQuote(
|
|
1086
|
+
this.client,
|
|
1087
|
+
from.toUpperCase(),
|
|
1088
|
+
to.toUpperCase(),
|
|
1089
|
+
amount
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
async buildSwapTx(address, from, to, amount, maxSlippageBps) {
|
|
1093
|
+
const result = await buildSwapTx({
|
|
1094
|
+
client: this.client,
|
|
1095
|
+
address,
|
|
1096
|
+
fromAsset: from.toUpperCase(),
|
|
1097
|
+
toAsset: to.toUpperCase(),
|
|
1098
|
+
amount,
|
|
1099
|
+
maxSlippageBps
|
|
1100
|
+
});
|
|
1101
|
+
return {
|
|
1102
|
+
tx: result.tx,
|
|
1103
|
+
estimatedOut: result.estimatedOut,
|
|
1104
|
+
toDecimals: result.toDecimals
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
getSupportedPairs() {
|
|
1108
|
+
return [
|
|
1109
|
+
{ from: "USDC", to: "SUI" },
|
|
1110
|
+
{ from: "SUI", to: "USDC" }
|
|
1111
|
+
];
|
|
1112
|
+
}
|
|
1113
|
+
async getPoolPrice() {
|
|
1114
|
+
return getPoolPrice(this.client);
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
899
1117
|
function hasLeadingZeroBits(hash, bits) {
|
|
900
1118
|
const fullBytes = Math.floor(bits / 8);
|
|
901
1119
|
const remainingBits = bits % 8;
|
|
@@ -1128,11 +1346,23 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1128
1346
|
keypair;
|
|
1129
1347
|
client;
|
|
1130
1348
|
_address;
|
|
1131
|
-
|
|
1349
|
+
registry;
|
|
1350
|
+
constructor(keypair, client, registry) {
|
|
1132
1351
|
super();
|
|
1133
1352
|
this.keypair = keypair;
|
|
1134
1353
|
this.client = client;
|
|
1135
1354
|
this._address = getAddress(keypair);
|
|
1355
|
+
this.registry = registry ?? _T2000.createDefaultRegistry(client);
|
|
1356
|
+
}
|
|
1357
|
+
static createDefaultRegistry(client) {
|
|
1358
|
+
const registry = new ProtocolRegistry();
|
|
1359
|
+
const naviAdapter = new NaviAdapter();
|
|
1360
|
+
naviAdapter.initSync(client);
|
|
1361
|
+
registry.registerLending(naviAdapter);
|
|
1362
|
+
const cetusAdapter = new CetusAdapter();
|
|
1363
|
+
cetusAdapter.initSync(client);
|
|
1364
|
+
registry.registerSwap(cetusAdapter);
|
|
1365
|
+
return registry;
|
|
1136
1366
|
}
|
|
1137
1367
|
static async create(options = {}) {
|
|
1138
1368
|
const { keyPath, pin, passphrase, network = DEFAULT_NETWORK, rpcUrl, sponsored, name } = options;
|
|
@@ -1253,6 +1483,11 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1253
1483
|
exportKey() {
|
|
1254
1484
|
return exportPrivateKey(this.keypair);
|
|
1255
1485
|
}
|
|
1486
|
+
async registerAdapter(adapter) {
|
|
1487
|
+
await adapter.init(this.client);
|
|
1488
|
+
if ("buildSaveTx" in adapter) this.registry.registerLending(adapter);
|
|
1489
|
+
if ("buildSwapTx" in adapter) this.registry.registerSwap(adapter);
|
|
1490
|
+
}
|
|
1256
1491
|
// -- Savings --
|
|
1257
1492
|
async save(params) {
|
|
1258
1493
|
const asset = (params.asset ?? "USDC").toUpperCase();
|
|
@@ -1275,15 +1510,15 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1275
1510
|
}
|
|
1276
1511
|
const fee = calculateFee("save", amount);
|
|
1277
1512
|
const saveAmount = amount;
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
this.
|
|
1281
|
-
|
|
1282
|
-
);
|
|
1283
|
-
const rates = await getRates(
|
|
1513
|
+
const adapter = await this.resolveLending(params.protocol, asset, "save");
|
|
1514
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1515
|
+
const { tx } = await adapter.buildSaveTx(this._address, saveAmount, asset, { collectFee: true });
|
|
1516
|
+
return tx;
|
|
1517
|
+
});
|
|
1518
|
+
const rates = await adapter.getRates(asset);
|
|
1284
1519
|
reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
|
|
1285
1520
|
this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
|
|
1286
|
-
let savingsBalance = saveAmount
|
|
1521
|
+
let savingsBalance = saveAmount;
|
|
1287
1522
|
try {
|
|
1288
1523
|
const positions = await this.positions();
|
|
1289
1524
|
savingsBalance = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
|
|
@@ -1293,7 +1528,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1293
1528
|
success: true,
|
|
1294
1529
|
tx: gasResult.digest,
|
|
1295
1530
|
amount: saveAmount,
|
|
1296
|
-
apy: rates.
|
|
1531
|
+
apy: rates.saveApy,
|
|
1297
1532
|
fee: fee.amount,
|
|
1298
1533
|
gasCost: gasResult.gasCostSui,
|
|
1299
1534
|
gasMethod: gasResult.gasMethod,
|
|
@@ -1305,18 +1540,19 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1305
1540
|
if (asset !== "USDC") {
|
|
1306
1541
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for withdraw. Got: ${asset}`);
|
|
1307
1542
|
}
|
|
1543
|
+
const adapter = await this.resolveLending(params.protocol, asset, "withdraw");
|
|
1308
1544
|
let amount;
|
|
1309
1545
|
if (params.amount === "all") {
|
|
1310
|
-
const maxResult = await
|
|
1546
|
+
const maxResult = await adapter.maxWithdraw(this._address, asset);
|
|
1311
1547
|
amount = maxResult.maxAmount;
|
|
1312
1548
|
if (amount <= 0) {
|
|
1313
1549
|
throw new T2000Error("NO_COLLATERAL", "No savings to withdraw");
|
|
1314
1550
|
}
|
|
1315
1551
|
} else {
|
|
1316
1552
|
amount = params.amount;
|
|
1317
|
-
const hf = await this.
|
|
1553
|
+
const hf = await adapter.getHealth(this._address);
|
|
1318
1554
|
if (hf.borrowed > 0) {
|
|
1319
|
-
const maxResult = await
|
|
1555
|
+
const maxResult = await adapter.maxWithdraw(this._address, asset);
|
|
1320
1556
|
if (amount > maxResult.maxAmount) {
|
|
1321
1557
|
throw new T2000Error(
|
|
1322
1558
|
"WITHDRAW_WOULD_LIQUIDATE",
|
|
@@ -1333,7 +1569,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1333
1569
|
const withdrawAmount = amount;
|
|
1334
1570
|
let effectiveAmount = withdrawAmount;
|
|
1335
1571
|
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1336
|
-
const built = await buildWithdrawTx(this.
|
|
1572
|
+
const built = await adapter.buildWithdrawTx(this._address, withdrawAmount, asset);
|
|
1337
1573
|
effectiveAmount = built.effectiveAmount;
|
|
1338
1574
|
return built.tx;
|
|
1339
1575
|
});
|
|
@@ -1347,7 +1583,8 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1347
1583
|
};
|
|
1348
1584
|
}
|
|
1349
1585
|
async maxWithdraw() {
|
|
1350
|
-
|
|
1586
|
+
const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
|
|
1587
|
+
return adapter.maxWithdraw(this._address, "USDC");
|
|
1351
1588
|
}
|
|
1352
1589
|
// -- Borrowing --
|
|
1353
1590
|
async borrow(params) {
|
|
@@ -1355,7 +1592,8 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1355
1592
|
if (asset !== "USDC") {
|
|
1356
1593
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for borrow. Got: ${asset}`);
|
|
1357
1594
|
}
|
|
1358
|
-
const
|
|
1595
|
+
const adapter = await this.resolveLending(params.protocol, asset, "borrow");
|
|
1596
|
+
const maxResult = await adapter.maxBorrow(this._address, asset);
|
|
1359
1597
|
if (params.amount > maxResult.maxAmount) {
|
|
1360
1598
|
throw new T2000Error("HEALTH_FACTOR_TOO_LOW", `Max safe borrow: $${maxResult.maxAmount.toFixed(2)}`, {
|
|
1361
1599
|
maxBorrow: maxResult.maxAmount,
|
|
@@ -1364,12 +1602,11 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1364
1602
|
}
|
|
1365
1603
|
const fee = calculateFee("borrow", params.amount);
|
|
1366
1604
|
const borrowAmount = params.amount;
|
|
1367
|
-
const gasResult = await executeWithGas(
|
|
1368
|
-
this.
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
);
|
|
1372
|
-
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1605
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1606
|
+
const { tx } = await adapter.buildBorrowTx(this._address, borrowAmount, asset, { collectFee: true });
|
|
1607
|
+
return tx;
|
|
1608
|
+
});
|
|
1609
|
+
const hf = await adapter.getHealth(this._address);
|
|
1373
1610
|
reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
|
|
1374
1611
|
this.emitBalanceChange("USDC", borrowAmount, "borrow", gasResult.digest);
|
|
1375
1612
|
return {
|
|
@@ -1387,9 +1624,10 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1387
1624
|
if (asset !== "USDC") {
|
|
1388
1625
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Only USDC is supported for repay. Got: ${asset}`);
|
|
1389
1626
|
}
|
|
1627
|
+
const adapter = await this.resolveLending(params.protocol, asset, "repay");
|
|
1390
1628
|
let amount;
|
|
1391
1629
|
if (params.amount === "all") {
|
|
1392
|
-
const hf2 = await this.
|
|
1630
|
+
const hf2 = await adapter.getHealth(this._address);
|
|
1393
1631
|
amount = hf2.borrowed;
|
|
1394
1632
|
if (amount <= 0) {
|
|
1395
1633
|
throw new T2000Error("NO_COLLATERAL", "No outstanding borrow to repay");
|
|
@@ -1398,12 +1636,11 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1398
1636
|
amount = params.amount;
|
|
1399
1637
|
}
|
|
1400
1638
|
const repayAmount = amount;
|
|
1401
|
-
const gasResult = await executeWithGas(
|
|
1402
|
-
this.
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
);
|
|
1406
|
-
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1639
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1640
|
+
const { tx } = await adapter.buildRepayTx(this._address, repayAmount, asset);
|
|
1641
|
+
return tx;
|
|
1642
|
+
});
|
|
1643
|
+
const hf = await adapter.getHealth(this._address);
|
|
1407
1644
|
this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
|
|
1408
1645
|
return {
|
|
1409
1646
|
success: true,
|
|
@@ -1415,10 +1652,12 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1415
1652
|
};
|
|
1416
1653
|
}
|
|
1417
1654
|
async maxBorrow() {
|
|
1418
|
-
|
|
1655
|
+
const adapter = await this.resolveLending(void 0, "USDC", "borrow");
|
|
1656
|
+
return adapter.maxBorrow(this._address, "USDC");
|
|
1419
1657
|
}
|
|
1420
1658
|
async healthFactor() {
|
|
1421
|
-
const
|
|
1659
|
+
const adapter = await this.resolveLending(void 0, "USDC", "save");
|
|
1660
|
+
const hf = await adapter.getHealth(this._address);
|
|
1422
1661
|
if (hf.healthFactor < 1.2) {
|
|
1423
1662
|
this.emit("healthCritical", { healthFactor: hf.healthFactor, threshold: 1.5, severity: "critical" });
|
|
1424
1663
|
} else if (hf.healthFactor < 2) {
|
|
@@ -1436,19 +1675,21 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1436
1675
|
if (fromAsset === toAsset) {
|
|
1437
1676
|
throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
|
|
1438
1677
|
}
|
|
1678
|
+
let adapter;
|
|
1679
|
+
if (params.protocol) {
|
|
1680
|
+
const found = this.registry.getSwap(params.protocol);
|
|
1681
|
+
if (!found) throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap adapter '${params.protocol}' not found`);
|
|
1682
|
+
adapter = found;
|
|
1683
|
+
} else {
|
|
1684
|
+
const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
|
|
1685
|
+
adapter = best.adapter;
|
|
1686
|
+
}
|
|
1439
1687
|
const fee = calculateFee("swap", params.amount);
|
|
1440
1688
|
const swapAmount = params.amount;
|
|
1441
1689
|
const slippageBps = params.maxSlippage ? params.maxSlippage * 100 : void 0;
|
|
1442
1690
|
let swapMeta = { estimatedOut: 0, toDecimals: 0 };
|
|
1443
1691
|
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1444
|
-
const built = await buildSwapTx(
|
|
1445
|
-
client: this.client,
|
|
1446
|
-
address: this._address,
|
|
1447
|
-
fromAsset,
|
|
1448
|
-
toAsset,
|
|
1449
|
-
amount: swapAmount,
|
|
1450
|
-
maxSlippageBps: slippageBps
|
|
1451
|
-
});
|
|
1692
|
+
const built = await adapter.buildSwapTx(this._address, fromAsset, toAsset, swapAmount, slippageBps);
|
|
1452
1693
|
swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
|
|
1453
1694
|
return built.tx;
|
|
1454
1695
|
});
|
|
@@ -1487,17 +1728,39 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1487
1728
|
async swapQuote(params) {
|
|
1488
1729
|
const fromAsset = params.from.toUpperCase();
|
|
1489
1730
|
const toAsset = params.to.toUpperCase();
|
|
1490
|
-
const
|
|
1731
|
+
const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
|
|
1491
1732
|
const fee = calculateFee("swap", params.amount);
|
|
1492
|
-
return { ...quote, fee: { amount: fee.amount, rate: fee.rate } };
|
|
1733
|
+
return { ...best.quote, fee: { amount: fee.amount, rate: fee.rate } };
|
|
1493
1734
|
}
|
|
1494
1735
|
// -- Info --
|
|
1495
1736
|
async positions() {
|
|
1496
|
-
|
|
1737
|
+
const allPositions = await this.registry.allPositions(this._address);
|
|
1738
|
+
const positions = allPositions.flatMap(
|
|
1739
|
+
(p) => [
|
|
1740
|
+
...p.positions.supplies.map((s) => ({
|
|
1741
|
+
protocol: p.protocolId,
|
|
1742
|
+
asset: s.asset,
|
|
1743
|
+
type: "save",
|
|
1744
|
+
amount: s.amount,
|
|
1745
|
+
apy: s.apy
|
|
1746
|
+
})),
|
|
1747
|
+
...p.positions.borrows.map((b) => ({
|
|
1748
|
+
protocol: p.protocolId,
|
|
1749
|
+
asset: b.asset,
|
|
1750
|
+
type: "borrow",
|
|
1751
|
+
amount: b.amount,
|
|
1752
|
+
apy: b.apy
|
|
1753
|
+
}))
|
|
1754
|
+
]
|
|
1755
|
+
);
|
|
1756
|
+
return { positions };
|
|
1497
1757
|
}
|
|
1498
1758
|
async rates() {
|
|
1499
1759
|
return getRates(this.client);
|
|
1500
1760
|
}
|
|
1761
|
+
async allRates(asset = "USDC") {
|
|
1762
|
+
return this.registry.allRates(asset);
|
|
1763
|
+
}
|
|
1501
1764
|
async earnings() {
|
|
1502
1765
|
const result = await getEarnings(this.client, this.keypair);
|
|
1503
1766
|
if (result.totalYieldEarned > 0) {
|
|
@@ -1524,6 +1787,29 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
1524
1787
|
return attack(this.client, this.keypair, id, prompt, fee);
|
|
1525
1788
|
}
|
|
1526
1789
|
// -- Helpers --
|
|
1790
|
+
async resolveLending(protocol, asset, capability) {
|
|
1791
|
+
if (protocol) {
|
|
1792
|
+
const adapter = this.registry.getLending(protocol);
|
|
1793
|
+
if (!adapter) throw new T2000Error("ASSET_NOT_SUPPORTED", `Lending adapter '${protocol}' not found`);
|
|
1794
|
+
return adapter;
|
|
1795
|
+
}
|
|
1796
|
+
if (capability === "save") {
|
|
1797
|
+
const { adapter } = await this.registry.bestSaveRate(asset);
|
|
1798
|
+
return adapter;
|
|
1799
|
+
}
|
|
1800
|
+
if (capability === "borrow" || capability === "repay") {
|
|
1801
|
+
const adapters2 = this.registry.listLending().filter(
|
|
1802
|
+
(a) => a.supportedAssets.includes(asset) && a.capabilities.includes(capability) && (capability !== "borrow" || a.supportsSameAssetBorrow)
|
|
1803
|
+
);
|
|
1804
|
+
if (adapters2.length === 0) throw new T2000Error("ASSET_NOT_SUPPORTED", `No adapter supports ${capability} ${asset}`);
|
|
1805
|
+
return adapters2[0];
|
|
1806
|
+
}
|
|
1807
|
+
const adapters = this.registry.listLending().filter(
|
|
1808
|
+
(a) => a.supportedAssets.includes(asset) && a.capabilities.includes(capability)
|
|
1809
|
+
);
|
|
1810
|
+
if (adapters.length === 0) throw new T2000Error("ASSET_NOT_SUPPORTED", `No adapter supports ${capability} ${asset}`);
|
|
1811
|
+
return adapters[0];
|
|
1812
|
+
}
|
|
1527
1813
|
emitBalanceChange(asset, amount, cause, tx) {
|
|
1528
1814
|
this.emit("balanceChange", { asset, previous: 0, current: 0, cause, tx });
|
|
1529
1815
|
}
|
|
@@ -1621,7 +1907,297 @@ function parseMoveAbort(errorStr) {
|
|
|
1621
1907
|
}
|
|
1622
1908
|
return { reason: errorStr };
|
|
1623
1909
|
}
|
|
1910
|
+
var USDC_TYPE2 = SUPPORTED_ASSETS.USDC.type;
|
|
1911
|
+
SUPPORTED_ASSETS.USDC.decimals;
|
|
1912
|
+
var WAD = 1e18;
|
|
1913
|
+
var MIN_HEALTH_FACTOR2 = 1.5;
|
|
1914
|
+
function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
|
|
1915
|
+
if (utilBreakpoints.length === 0) return 0;
|
|
1916
|
+
if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
|
|
1917
|
+
if (utilizationPct >= utilBreakpoints[utilBreakpoints.length - 1]) {
|
|
1918
|
+
return aprBreakpoints[aprBreakpoints.length - 1];
|
|
1919
|
+
}
|
|
1920
|
+
for (let i = 1; i < utilBreakpoints.length; i++) {
|
|
1921
|
+
if (utilizationPct <= utilBreakpoints[i]) {
|
|
1922
|
+
const t = (utilizationPct - utilBreakpoints[i - 1]) / (utilBreakpoints[i] - utilBreakpoints[i - 1]);
|
|
1923
|
+
return aprBreakpoints[i - 1] + t * (aprBreakpoints[i] - aprBreakpoints[i - 1]);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return aprBreakpoints[aprBreakpoints.length - 1];
|
|
1927
|
+
}
|
|
1928
|
+
function computeRatesFromReserve(reserve) {
|
|
1929
|
+
const decimals = reserve.mintDecimals;
|
|
1930
|
+
const available = Number(reserve.availableAmount) / 10 ** decimals;
|
|
1931
|
+
const borrowed = Number(reserve.borrowedAmount.value) / WAD / 10 ** decimals;
|
|
1932
|
+
const totalDeposited = available + borrowed;
|
|
1933
|
+
const utilizationPct = totalDeposited > 0 ? borrowed / totalDeposited * 100 : 0;
|
|
1934
|
+
const config = reserve.config.element;
|
|
1935
|
+
if (!config) return { borrowAprPct: 0, depositAprPct: 0, utilizationPct: 0 };
|
|
1936
|
+
const utils = config.interestRateUtils.map(Number);
|
|
1937
|
+
const aprs = config.interestRateAprs.map((a) => Number(a) / 100);
|
|
1938
|
+
const borrowAprPct = interpolateRate(utils, aprs, utilizationPct);
|
|
1939
|
+
const spreadFeeBps = Number(config.spreadFeeBps);
|
|
1940
|
+
const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - spreadFeeBps / 1e4) * 100;
|
|
1941
|
+
return { borrowAprPct, depositAprPct, utilizationPct };
|
|
1942
|
+
}
|
|
1943
|
+
function cTokenRatio(reserve) {
|
|
1944
|
+
if (reserve.ctokenSupply === 0n) return 1;
|
|
1945
|
+
const available = Number(reserve.availableAmount);
|
|
1946
|
+
const borrowed = Number(reserve.borrowedAmount.value) / WAD;
|
|
1947
|
+
const spreadFees = Number(reserve.unclaimedSpreadFees.value) / WAD;
|
|
1948
|
+
const totalSupply = available + borrowed - spreadFees;
|
|
1949
|
+
return totalSupply / Number(reserve.ctokenSupply);
|
|
1950
|
+
}
|
|
1951
|
+
var SuilendAdapter = class {
|
|
1952
|
+
id = "suilend";
|
|
1953
|
+
name = "Suilend";
|
|
1954
|
+
version = "1.0.0";
|
|
1955
|
+
capabilities = ["save", "withdraw"];
|
|
1956
|
+
supportedAssets = ["USDC"];
|
|
1957
|
+
supportsSameAssetBorrow = false;
|
|
1958
|
+
client;
|
|
1959
|
+
suilend;
|
|
1960
|
+
lendingMarketType;
|
|
1961
|
+
initialized = false;
|
|
1962
|
+
async init(client) {
|
|
1963
|
+
let sdk;
|
|
1964
|
+
try {
|
|
1965
|
+
sdk = await import('@suilend/sdk');
|
|
1966
|
+
} catch {
|
|
1967
|
+
throw new T2000Error(
|
|
1968
|
+
"PROTOCOL_UNAVAILABLE",
|
|
1969
|
+
"Suilend SDK not installed. Run: npm install @suilend/sdk@^1"
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
this.client = client;
|
|
1973
|
+
this.lendingMarketType = sdk.LENDING_MARKET_TYPE;
|
|
1974
|
+
try {
|
|
1975
|
+
this.suilend = await sdk.SuilendClient.initialize(
|
|
1976
|
+
sdk.LENDING_MARKET_ID,
|
|
1977
|
+
sdk.LENDING_MARKET_TYPE,
|
|
1978
|
+
client
|
|
1979
|
+
);
|
|
1980
|
+
} catch (err) {
|
|
1981
|
+
throw new T2000Error(
|
|
1982
|
+
"PROTOCOL_UNAVAILABLE",
|
|
1983
|
+
`Failed to initialize Suilend: ${err instanceof Error ? err.message : String(err)}`
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
this.initialized = true;
|
|
1987
|
+
}
|
|
1988
|
+
ensureInit() {
|
|
1989
|
+
if (!this.initialized) {
|
|
1990
|
+
throw new T2000Error(
|
|
1991
|
+
"PROTOCOL_UNAVAILABLE",
|
|
1992
|
+
"SuilendAdapter not initialized. Call init() first."
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
findReserve(asset) {
|
|
1997
|
+
const upper = asset.toUpperCase();
|
|
1998
|
+
let coinType;
|
|
1999
|
+
if (upper === "USDC") coinType = USDC_TYPE2;
|
|
2000
|
+
else if (upper === "SUI") coinType = "0x2::sui::SUI";
|
|
2001
|
+
else if (asset.includes("::")) coinType = asset;
|
|
2002
|
+
else return void 0;
|
|
2003
|
+
try {
|
|
2004
|
+
const normalized = normalizeStructTag(coinType);
|
|
2005
|
+
return this.suilend.lendingMarket.reserves.find(
|
|
2006
|
+
(r) => normalizeStructTag(r.coinType.name) === normalized
|
|
2007
|
+
);
|
|
2008
|
+
} catch {
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
async getObligationCaps(address) {
|
|
2013
|
+
const SuilendClientStatic = (await import('@suilend/sdk')).SuilendClient;
|
|
2014
|
+
return SuilendClientStatic.getObligationOwnerCaps(
|
|
2015
|
+
address,
|
|
2016
|
+
[this.lendingMarketType],
|
|
2017
|
+
this.client
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
resolveSymbol(coinType) {
|
|
2021
|
+
const normalized = normalizeStructTag(coinType);
|
|
2022
|
+
if (normalized === normalizeStructTag(USDC_TYPE2)) return "USDC";
|
|
2023
|
+
if (normalized === normalizeStructTag("0x2::sui::SUI")) return "SUI";
|
|
2024
|
+
const parts = coinType.split("::");
|
|
2025
|
+
return parts[parts.length - 1] || "UNKNOWN";
|
|
2026
|
+
}
|
|
2027
|
+
async getRates(asset) {
|
|
2028
|
+
this.ensureInit();
|
|
2029
|
+
const reserve = this.findReserve(asset);
|
|
2030
|
+
if (!reserve) {
|
|
2031
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
|
|
2032
|
+
}
|
|
2033
|
+
const { borrowAprPct, depositAprPct } = computeRatesFromReserve(reserve);
|
|
2034
|
+
return {
|
|
2035
|
+
asset,
|
|
2036
|
+
saveApy: depositAprPct,
|
|
2037
|
+
borrowApy: borrowAprPct
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
async getPositions(address) {
|
|
2041
|
+
this.ensureInit();
|
|
2042
|
+
const supplies = [];
|
|
2043
|
+
const borrows = [];
|
|
2044
|
+
const caps = await this.getObligationCaps(address);
|
|
2045
|
+
if (caps.length === 0) return { supplies, borrows };
|
|
2046
|
+
const obligation = await this.suilend.getObligation(caps[0].obligationId);
|
|
2047
|
+
for (const deposit of obligation.deposits) {
|
|
2048
|
+
const coinType = normalizeStructTag(deposit.coinType.name);
|
|
2049
|
+
const reserve = this.suilend.lendingMarket.reserves.find(
|
|
2050
|
+
(r) => normalizeStructTag(r.coinType.name) === coinType
|
|
2051
|
+
);
|
|
2052
|
+
if (!reserve) continue;
|
|
2053
|
+
const ctokenAmount = Number(deposit.depositedCtokenAmount.toString());
|
|
2054
|
+
const ratio = cTokenRatio(reserve);
|
|
2055
|
+
const amount = ctokenAmount * ratio / 10 ** reserve.mintDecimals;
|
|
2056
|
+
const { depositAprPct } = computeRatesFromReserve(reserve);
|
|
2057
|
+
supplies.push({ asset: this.resolveSymbol(coinType), amount, apy: depositAprPct });
|
|
2058
|
+
}
|
|
2059
|
+
for (const borrow of obligation.borrows) {
|
|
2060
|
+
const coinType = normalizeStructTag(borrow.coinType.name);
|
|
2061
|
+
const reserve = this.suilend.lendingMarket.reserves.find(
|
|
2062
|
+
(r) => normalizeStructTag(r.coinType.name) === coinType
|
|
2063
|
+
);
|
|
2064
|
+
if (!reserve) continue;
|
|
2065
|
+
const rawBorrowed = Number(borrow.borrowedAmount.value.toString()) / WAD;
|
|
2066
|
+
const amount = rawBorrowed / 10 ** reserve.mintDecimals;
|
|
2067
|
+
const reserveRate = Number(reserve.cumulativeBorrowRate.value.toString()) / WAD;
|
|
2068
|
+
const posRate = Number(borrow.cumulativeBorrowRate.value.toString()) / WAD;
|
|
2069
|
+
const compounded = posRate > 0 ? amount * (reserveRate / posRate) : amount;
|
|
2070
|
+
const { borrowAprPct } = computeRatesFromReserve(reserve);
|
|
2071
|
+
borrows.push({ asset: this.resolveSymbol(coinType), amount: compounded, apy: borrowAprPct });
|
|
2072
|
+
}
|
|
2073
|
+
return { supplies, borrows };
|
|
2074
|
+
}
|
|
2075
|
+
async getHealth(address) {
|
|
2076
|
+
this.ensureInit();
|
|
2077
|
+
const caps = await this.getObligationCaps(address);
|
|
2078
|
+
if (caps.length === 0) {
|
|
2079
|
+
return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
|
|
2080
|
+
}
|
|
2081
|
+
const positions = await this.getPositions(address);
|
|
2082
|
+
const supplied = positions.supplies.reduce((s, p) => s + p.amount, 0);
|
|
2083
|
+
const borrowed = positions.borrows.reduce((s, p) => s + p.amount, 0);
|
|
2084
|
+
const reserve = this.findReserve("USDC");
|
|
2085
|
+
const closeLtv = reserve?.config?.element?.closeLtvPct ?? 75;
|
|
2086
|
+
const openLtv = reserve?.config?.element?.openLtvPct ?? 70;
|
|
2087
|
+
const liqThreshold = closeLtv / 100;
|
|
2088
|
+
const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
|
|
2089
|
+
const maxBorrow = Math.max(0, supplied * (openLtv / 100) - borrowed);
|
|
2090
|
+
return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
|
|
2091
|
+
}
|
|
2092
|
+
async buildSaveTx(address, amount, _asset, options) {
|
|
2093
|
+
this.ensureInit();
|
|
2094
|
+
const rawAmount = usdcToRaw(amount).toString();
|
|
2095
|
+
const tx = new Transaction();
|
|
2096
|
+
tx.setSender(address);
|
|
2097
|
+
const caps = await this.getObligationCaps(address);
|
|
2098
|
+
let capRef;
|
|
2099
|
+
if (caps.length === 0) {
|
|
2100
|
+
const [newCap] = this.suilend.createObligation(tx);
|
|
2101
|
+
capRef = newCap;
|
|
2102
|
+
} else {
|
|
2103
|
+
capRef = caps[0].id;
|
|
2104
|
+
}
|
|
2105
|
+
const allCoins = await this.fetchAllCoins(address, USDC_TYPE2);
|
|
2106
|
+
if (allCoins.length === 0) {
|
|
2107
|
+
throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
|
|
2108
|
+
}
|
|
2109
|
+
const primaryCoinId = allCoins[0].coinObjectId;
|
|
2110
|
+
if (allCoins.length > 1) {
|
|
2111
|
+
tx.mergeCoins(
|
|
2112
|
+
tx.object(primaryCoinId),
|
|
2113
|
+
allCoins.slice(1).map((c) => tx.object(c.coinObjectId))
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
|
|
2117
|
+
if (options?.collectFee) {
|
|
2118
|
+
addCollectFeeToTx(tx, depositCoin, "save");
|
|
2119
|
+
}
|
|
2120
|
+
this.suilend.deposit(depositCoin, USDC_TYPE2, capRef, tx);
|
|
2121
|
+
return { tx };
|
|
2122
|
+
}
|
|
2123
|
+
async buildWithdrawTx(address, amount, _asset) {
|
|
2124
|
+
this.ensureInit();
|
|
2125
|
+
const caps = await this.getObligationCaps(address);
|
|
2126
|
+
if (caps.length === 0) {
|
|
2127
|
+
throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
|
|
2128
|
+
}
|
|
2129
|
+
const positions = await this.getPositions(address);
|
|
2130
|
+
const usdcSupply = positions.supplies.find((s) => s.asset === "USDC");
|
|
2131
|
+
const deposited = usdcSupply?.amount ?? 0;
|
|
2132
|
+
const effectiveAmount = Math.min(amount, deposited);
|
|
2133
|
+
if (effectiveAmount <= 0) {
|
|
2134
|
+
throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw from Suilend");
|
|
2135
|
+
}
|
|
2136
|
+
const rawAmount = usdcToRaw(effectiveAmount).toString();
|
|
2137
|
+
const tx = new Transaction();
|
|
2138
|
+
tx.setSender(address);
|
|
2139
|
+
await this.suilend.withdrawAndSendToUser(
|
|
2140
|
+
address,
|
|
2141
|
+
caps[0].id,
|
|
2142
|
+
caps[0].obligationId,
|
|
2143
|
+
USDC_TYPE2,
|
|
2144
|
+
rawAmount,
|
|
2145
|
+
tx
|
|
2146
|
+
);
|
|
2147
|
+
return { tx, effectiveAmount };
|
|
2148
|
+
}
|
|
2149
|
+
async buildBorrowTx(_address, _amount, _asset, _options) {
|
|
2150
|
+
throw new T2000Error(
|
|
2151
|
+
"ASSET_NOT_SUPPORTED",
|
|
2152
|
+
"SuilendAdapter.buildBorrowTx() not available \u2014 Suilend requires different collateral/borrow assets. Deferred to Phase 10."
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
async buildRepayTx(_address, _amount, _asset) {
|
|
2156
|
+
throw new T2000Error(
|
|
2157
|
+
"ASSET_NOT_SUPPORTED",
|
|
2158
|
+
"SuilendAdapter.buildRepayTx() not available \u2014 deferred to Phase 10."
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
async maxWithdraw(address, _asset) {
|
|
2162
|
+
this.ensureInit();
|
|
2163
|
+
const health = await this.getHealth(address);
|
|
2164
|
+
let maxAmount;
|
|
2165
|
+
if (health.borrowed === 0) {
|
|
2166
|
+
maxAmount = health.supplied;
|
|
2167
|
+
} else {
|
|
2168
|
+
maxAmount = Math.max(
|
|
2169
|
+
0,
|
|
2170
|
+
health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
const remainingSupply = health.supplied - maxAmount;
|
|
2174
|
+
const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
|
|
2175
|
+
return {
|
|
2176
|
+
maxAmount,
|
|
2177
|
+
healthFactorAfter: hfAfter,
|
|
2178
|
+
currentHF: health.healthFactor
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
async maxBorrow(_address, _asset) {
|
|
2182
|
+
throw new T2000Error(
|
|
2183
|
+
"ASSET_NOT_SUPPORTED",
|
|
2184
|
+
"SuilendAdapter.maxBorrow() not available \u2014 deferred to Phase 10."
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
async fetchAllCoins(owner, coinType) {
|
|
2188
|
+
const all = [];
|
|
2189
|
+
let cursor = null;
|
|
2190
|
+
let hasNext = true;
|
|
2191
|
+
while (hasNext) {
|
|
2192
|
+
const page = await this.client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
|
|
2193
|
+
all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
|
|
2194
|
+
cursor = page.nextCursor;
|
|
2195
|
+
hasNext = page.hasNextPage;
|
|
2196
|
+
}
|
|
2197
|
+
return all;
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
1624
2200
|
|
|
1625
|
-
export { BPS_DENOMINATOR, CLOCK_ID, DEFAULT_NETWORK, MIST_PER_SUI, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, T2000, T2000Error, USDC_DECIMALS, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getGasStatus, getPoolPrice, getRates, getSentinelInfo, getSwapQuote, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, submitPrompt, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
2201
|
+
export { BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, DEFAULT_NETWORK, MIST_PER_SUI, NaviAdapter, ProtocolRegistry, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, addCollectFeeToTx, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getGasStatus, getPoolPrice, getRates, getSentinelInfo, getSwapQuote, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, submitPrompt, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
1626
2202
|
//# sourceMappingURL=index.js.map
|
|
1627
2203
|
//# sourceMappingURL=index.js.map
|