@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/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, getLendingState, withdrawCoinPTB, borrowCoinPTB, getHealthFactor as getHealthFactor$1, repayCoinPTB, getPriceFeeds, filterPriceFeeds, updateOraclePricesPTB } from '@naviprotocol/lending';
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 ?? "0x51c44bb2ad3ba608cf9adbc6e37ee67268ef9313a4ff70957d4c6e7955dc7eef";
42
- process.env.T2000_CONFIG_ID ?? "0xd30408960ac38eced670acc102df9e178b5b46b3a8c0e96a53ec2fd3f39b5936";
43
- process.env.T2000_TREASURY_ID ?? "0x2398c2759cfce40f1b0f2b3e524eeba9e8f6428fcb1d1e39235dd042d48defc8";
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, keypair) {
464
- const address = keypair.getPublicKey().toSuiAddress();
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, keypair) {
497
- const address = keypair.getPublicKey().toSuiAddress();
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, keypair) {
526
- const hf = await getHealthFactor(client, keypair);
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, keypair) {
543
- const hf = await getHealthFactor(client, keypair);
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
- constructor(keypair, client) {
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 gasResult = await executeWithGas(
1279
- this.client,
1280
- this.keypair,
1281
- () => buildSaveTx(this.client, this._address, saveAmount)
1282
- );
1283
- const rates = await getRates(this.client);
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 - fee.amount;
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.USDC.saveApy,
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 this.maxWithdraw();
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.healthFactor();
1553
+ const hf = await adapter.getHealth(this._address);
1318
1554
  if (hf.borrowed > 0) {
1319
- const maxResult = await this.maxWithdraw();
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.client, this._address, withdrawAmount);
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
- return maxWithdrawAmount(this.client, this.keypair);
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 maxResult = await this.maxBorrow();
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.client,
1369
- this.keypair,
1370
- () => buildBorrowTx(this.client, this._address, borrowAmount)
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.healthFactor();
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.client,
1403
- this.keypair,
1404
- () => buildRepayTx(this.client, this._address, repayAmount)
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
- return maxBorrowAmount(this.client, this.keypair);
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 hf = await getHealthFactor(this.client, this.keypair);
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 quote = await getSwapQuote(this.client, fromAsset, toAsset, params.amount);
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
- return getPositions(this.client, this.keypair);
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