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