@t2000/sdk 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var eventemitter3 = require('eventemitter3');
4
- var client = require('@mysten/sui/client');
4
+ var jsonRpc = require('@mysten/sui/jsonRpc');
5
5
  var utils = require('@mysten/sui/utils');
6
6
  var ed25519 = require('@mysten/sui/keypairs/ed25519');
7
7
  var cryptography = require('@mysten/sui/cryptography');
@@ -10,7 +10,6 @@ var promises = require('fs/promises');
10
10
  var path = require('path');
11
11
  var os = require('os');
12
12
  var transactions = require('@mysten/sui/transactions');
13
- var lending = require('@naviprotocol/lending');
14
13
  var bcs = require('@mysten/sui/bcs');
15
14
  var aggregatorSdk = require('@cetusprotocol/aggregator-sdk');
16
15
 
@@ -113,7 +112,7 @@ var cachedClient = null;
113
112
  function getSuiClient(rpcUrl) {
114
113
  const url = rpcUrl ?? DEFAULT_RPC_URL;
115
114
  if (cachedClient) return cachedClient;
116
- cachedClient = new client.SuiClient({ url });
115
+ cachedClient = new jsonRpc.SuiJsonRpcClient({ url, network: "mainnet" });
117
116
  return cachedClient;
118
117
  }
119
118
  function validateAddress(address) {
@@ -429,15 +428,59 @@ async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest)
429
428
  }
430
429
 
431
430
  // src/protocols/navi.ts
432
- var ENV = { env: "prod" };
433
431
  var USDC_TYPE = SUPPORTED_ASSETS.USDC.type;
434
432
  var RATE_DECIMALS = 27;
435
433
  var LTV_DECIMALS = 27;
436
434
  var MIN_HEALTH_FACTOR = 1.5;
437
435
  var WITHDRAW_DUST_BUFFER = 1e-3;
436
+ var CLOCK = "0x06";
437
+ var SUI_SYSTEM_STATE = "0x05";
438
438
  var NAVI_BALANCE_DECIMALS = 9;
439
- function clientOpt(client, fresh = false) {
440
- return { client, ...ENV, ...fresh ? { disableCache: true } : {} };
439
+ var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
440
+ var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
441
+ function toBigInt(v) {
442
+ if (typeof v === "bigint") return v;
443
+ return BigInt(String(v));
444
+ }
445
+ var UserStateInfo = bcs.bcs.struct("UserStateInfo", {
446
+ asset_id: bcs.bcs.u8(),
447
+ borrow_balance: bcs.bcs.u256(),
448
+ supply_balance: bcs.bcs.u256()
449
+ });
450
+ function decodeDevInspect(result, schema) {
451
+ const rv = result.results?.[0]?.returnValues?.[0];
452
+ if (result.error || !rv) return void 0;
453
+ const bytes = Uint8Array.from(rv[0]);
454
+ return schema.parse(bytes);
455
+ }
456
+ var configCache = null;
457
+ var poolsCache = null;
458
+ var CACHE_TTL = 5 * 6e4;
459
+ async function fetchJson(url) {
460
+ const res = await fetch(url);
461
+ if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI API error: ${res.status}`);
462
+ const json = await res.json();
463
+ return json.data ?? json;
464
+ }
465
+ async function getConfig(fresh = false) {
466
+ if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
467
+ const data = await fetchJson(CONFIG_API);
468
+ configCache = { data, ts: Date.now() };
469
+ return data;
470
+ }
471
+ async function getPools(fresh = false) {
472
+ if (poolsCache && !fresh && Date.now() - poolsCache.ts < CACHE_TTL) return poolsCache.data;
473
+ const data = await fetchJson(POOLS_API);
474
+ poolsCache = { data, ts: Date.now() };
475
+ return data;
476
+ }
477
+ async function getUsdcPool() {
478
+ const pools = await getPools();
479
+ const usdc = pools.find(
480
+ (p) => p.token?.symbol === "USDC" || p.coinType?.toLowerCase().includes("usdc")
481
+ );
482
+ if (!usdc) throw new T2000Error("PROTOCOL_UNAVAILABLE", "USDC pool not found on NAVI");
483
+ return usdc;
441
484
  }
442
485
  function rateToApy(rawRate) {
443
486
  if (!rawRate || rawRate === "0") return 0;
@@ -453,59 +496,146 @@ function parseLiqThreshold(val) {
453
496
  if (n > 1) return Number(BigInt(val)) / 10 ** LTV_DECIMALS;
454
497
  return n;
455
498
  }
456
- function findUsdcPosition(state) {
457
- return state.find(
458
- (p) => p.pool.token.symbol === "USDC" || p.pool.coinType.toLowerCase().includes("usdc")
459
- );
499
+ function normalizeHealthFactor(raw) {
500
+ const v = raw / 10 ** RATE_DECIMALS;
501
+ return v > 1e5 ? Infinity : v;
460
502
  }
461
- async function updateOracle(tx, client, address) {
462
- try {
463
- const [feeds, state] = await Promise.all([
464
- lending.getPriceFeeds(ENV),
465
- lending.getLendingState(address, clientOpt(client))
466
- ]);
467
- const relevant = lending.filterPriceFeeds(feeds, { lendingState: state });
468
- if (relevant.length > 0) {
469
- await lending.updateOraclePricesPTB(tx, relevant, { ...ENV, updatePythPriceFeeds: true });
470
- }
471
- } catch {
472
- }
503
+ function compoundBalance(rawBalance, currentIndex) {
504
+ if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
505
+ const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
506
+ const half = scale / 2n;
507
+ const result = (rawBalance * scale + half) / BigInt(currentIndex);
508
+ return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
509
+ }
510
+ async function getUserState(client, address) {
511
+ const config = await getConfig();
512
+ const tx = new transactions.Transaction();
513
+ tx.moveCall({
514
+ target: `${config.uiGetter}::getter_unchecked::get_user_state`,
515
+ arguments: [tx.object(config.storage), tx.pure.address(address)]
516
+ });
517
+ const result = await client.devInspectTransactionBlock({
518
+ transactionBlock: tx,
519
+ sender: address
520
+ });
521
+ const decoded = decodeDevInspect(result, bcs.bcs.vector(UserStateInfo));
522
+ if (!decoded) return [];
523
+ return decoded.map((s) => ({
524
+ assetId: s.asset_id,
525
+ supplyBalance: toBigInt(s.supply_balance),
526
+ borrowBalance: toBigInt(s.borrow_balance)
527
+ })).filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
528
+ }
529
+ async function fetchCoins(client, owner, coinType) {
530
+ const all = [];
531
+ let cursor;
532
+ let hasNext = true;
533
+ while (hasNext) {
534
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
535
+ all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
536
+ cursor = page.nextCursor;
537
+ hasNext = page.hasNextPage;
538
+ }
539
+ return all;
540
+ }
541
+ function mergeCoinsPtb(tx, coins, amount) {
542
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
543
+ const primary = tx.object(coins[0].coinObjectId);
544
+ if (coins.length > 1) {
545
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
546
+ }
547
+ const [split] = tx.splitCoins(primary, [amount]);
548
+ return split;
473
549
  }
474
550
  async function buildSaveTx(client, address, amount, options = {}) {
475
551
  const rawAmount = Number(usdcToRaw(amount));
476
- const coins = await lending.getCoins(address, { coinType: USDC_TYPE, client });
477
- if (!coins || coins.length === 0) {
478
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
479
- }
552
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
553
+ const coins = await fetchCoins(client, address, USDC_TYPE);
554
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
480
555
  const tx = new transactions.Transaction();
481
556
  tx.setSender(address);
482
- const coinObj = lending.mergeCoinsPTB(tx, coins, { balance: rawAmount });
557
+ const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
483
558
  if (options.collectFee) {
484
559
  addCollectFeeToTx(tx, coinObj, "save");
485
560
  }
486
- await lending.depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
561
+ tx.moveCall({
562
+ target: `${config.package}::incentive_v3::entry_deposit`,
563
+ arguments: [
564
+ tx.object(CLOCK),
565
+ tx.object(config.storage),
566
+ tx.object(pool.contract.pool),
567
+ tx.pure.u8(pool.id),
568
+ coinObj,
569
+ tx.pure.u64(rawAmount),
570
+ tx.object(config.incentiveV2),
571
+ tx.object(config.incentiveV3)
572
+ ],
573
+ typeArguments: [pool.suiCoinType]
574
+ });
487
575
  return tx;
488
576
  }
489
577
  async function buildWithdrawTx(client, address, amount) {
490
- const state = await lending.getLendingState(address, clientOpt(client, true));
491
- const usdcPos = findUsdcPosition(state);
492
- const deposited = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
578
+ const [config, pool, pools, states] = await Promise.all([
579
+ getConfig(),
580
+ getUsdcPool(),
581
+ getPools(),
582
+ getUserState(client, address)
583
+ ]);
584
+ const usdcState = states.find((s) => s.assetId === pool.id);
585
+ const deposited = usdcState ? compoundBalance(usdcState.supplyBalance, pool.currentSupplyIndex) : 0;
493
586
  const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
494
587
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw");
495
588
  const rawAmount = Number(usdcToRaw(effectiveAmount));
496
589
  const tx = new transactions.Transaction();
497
590
  tx.setSender(address);
498
- await updateOracle(tx, client, address);
499
- const withdrawnCoin = await lending.withdrawCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
500
- tx.transferObjects([withdrawnCoin], address);
591
+ const [balance] = tx.moveCall({
592
+ target: `${config.package}::incentive_v3::withdraw_v2`,
593
+ arguments: [
594
+ tx.object(CLOCK),
595
+ tx.object(config.oracle.priceOracle),
596
+ tx.object(config.storage),
597
+ tx.object(pool.contract.pool),
598
+ tx.pure.u8(pool.id),
599
+ tx.pure.u64(rawAmount),
600
+ tx.object(config.incentiveV2),
601
+ tx.object(config.incentiveV3),
602
+ tx.object(SUI_SYSTEM_STATE)
603
+ ],
604
+ typeArguments: [pool.suiCoinType]
605
+ });
606
+ const [coin] = tx.moveCall({
607
+ target: "0x2::coin::from_balance",
608
+ arguments: [balance],
609
+ typeArguments: [pool.suiCoinType]
610
+ });
611
+ tx.transferObjects([coin], address);
501
612
  return { tx, effectiveAmount };
502
613
  }
503
614
  async function buildBorrowTx(client, address, amount, options = {}) {
504
615
  const rawAmount = Number(usdcToRaw(amount));
616
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
505
617
  const tx = new transactions.Transaction();
506
618
  tx.setSender(address);
507
- await updateOracle(tx, client, address);
508
- const borrowedCoin = await lending.borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
619
+ const [balance] = tx.moveCall({
620
+ target: `${config.package}::incentive_v3::borrow_v2`,
621
+ arguments: [
622
+ tx.object(CLOCK),
623
+ tx.object(config.oracle.priceOracle),
624
+ tx.object(config.storage),
625
+ tx.object(pool.contract.pool),
626
+ tx.pure.u8(pool.id),
627
+ tx.pure.u64(rawAmount),
628
+ tx.object(config.incentiveV2),
629
+ tx.object(config.incentiveV3),
630
+ tx.object(SUI_SYSTEM_STATE)
631
+ ],
632
+ typeArguments: [pool.suiCoinType]
633
+ });
634
+ const [borrowedCoin] = tx.moveCall({
635
+ target: "0x2::coin::from_balance",
636
+ arguments: [balance],
637
+ typeArguments: [pool.suiCoinType]
638
+ });
509
639
  if (options.collectFee) {
510
640
  addCollectFeeToTx(tx, borrowedCoin, "borrow");
511
641
  }
@@ -514,31 +644,79 @@ async function buildBorrowTx(client, address, amount, options = {}) {
514
644
  }
515
645
  async function buildRepayTx(client, address, amount) {
516
646
  const rawAmount = Number(usdcToRaw(amount));
517
- const coins = await lending.getCoins(address, { coinType: USDC_TYPE, client });
518
- if (!coins || coins.length === 0) {
519
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
520
- }
647
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
648
+ const coins = await fetchCoins(client, address, USDC_TYPE);
649
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
521
650
  const tx = new transactions.Transaction();
522
651
  tx.setSender(address);
523
- const coinObj = lending.mergeCoinsPTB(tx, coins, { balance: rawAmount });
524
- await lending.repayCoinPTB(tx, USDC_TYPE, coinObj, { ...ENV, amount: rawAmount });
652
+ const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
653
+ tx.moveCall({
654
+ target: `${config.package}::incentive_v3::entry_repay`,
655
+ arguments: [
656
+ tx.object(CLOCK),
657
+ tx.object(config.oracle.priceOracle),
658
+ tx.object(config.storage),
659
+ tx.object(pool.contract.pool),
660
+ tx.pure.u8(pool.id),
661
+ coinObj,
662
+ tx.pure.u64(rawAmount),
663
+ tx.object(config.incentiveV2),
664
+ tx.object(config.incentiveV3)
665
+ ],
666
+ typeArguments: [pool.suiCoinType]
667
+ });
525
668
  return tx;
526
669
  }
527
670
  async function getHealthFactor(client, addressOrKeypair) {
528
671
  const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
529
- const [healthFactor, state, pool] = await Promise.all([
530
- lending.getHealthFactor(address, clientOpt(client, true)),
531
- lending.getLendingState(address, clientOpt(client, true)),
532
- lending.getPool(USDC_TYPE, ENV)
672
+ const [config, pool, states] = await Promise.all([
673
+ getConfig(),
674
+ getUsdcPool(),
675
+ getUserState(client, address)
533
676
  ]);
534
- const usdcPos = findUsdcPosition(state);
535
- const supplied = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
536
- const borrowed = usdcPos ? Number(usdcPos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
677
+ const usdcState = states.find((s) => s.assetId === pool.id);
678
+ const supplied = usdcState ? compoundBalance(usdcState.supplyBalance, pool.currentSupplyIndex) : 0;
679
+ const borrowed = usdcState ? compoundBalance(usdcState.borrowBalance, pool.currentBorrowIndex) : 0;
537
680
  const ltv = parseLtv(pool.ltv);
538
681
  const liqThreshold = parseLiqThreshold(pool.liquidationFactor.threshold);
539
682
  const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
683
+ let healthFactor;
684
+ if (borrowed <= 0) {
685
+ healthFactor = Infinity;
686
+ } else {
687
+ try {
688
+ const tx = new transactions.Transaction();
689
+ tx.moveCall({
690
+ target: `${config.uiGetter}::calculator_unchecked::dynamic_health_factor`,
691
+ arguments: [
692
+ tx.object(CLOCK),
693
+ tx.object(config.storage),
694
+ tx.object(config.oracle.priceOracle),
695
+ tx.pure.u8(pool.id),
696
+ tx.pure.address(address),
697
+ tx.pure.u8(pool.id),
698
+ tx.pure.u64(0),
699
+ tx.pure.u64(0),
700
+ tx.pure.bool(false)
701
+ ],
702
+ typeArguments: [pool.suiCoinType]
703
+ });
704
+ const result = await client.devInspectTransactionBlock({
705
+ transactionBlock: tx,
706
+ sender: address
707
+ });
708
+ const decoded = decodeDevInspect(result, bcs.bcs.u256());
709
+ if (decoded !== void 0) {
710
+ healthFactor = normalizeHealthFactor(Number(decoded));
711
+ } else {
712
+ healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
713
+ }
714
+ } catch {
715
+ healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
716
+ }
717
+ }
540
718
  return {
541
- healthFactor: borrowed > 0 ? healthFactor : Infinity,
719
+ healthFactor,
542
720
  supplied,
543
721
  borrowed,
544
722
  maxBorrow: maxBorrowVal,
@@ -547,7 +725,7 @@ async function getHealthFactor(client, addressOrKeypair) {
547
725
  }
548
726
  async function getRates(client) {
549
727
  try {
550
- const pool = await lending.getPool(USDC_TYPE, ENV);
728
+ const pool = await getUsdcPool();
551
729
  let saveApy = rateToApy(pool.currentSupplyRate);
552
730
  let borrowApy = rateToApy(pool.currentBorrowRate);
553
731
  if (saveApy <= 0 || saveApy > 100) saveApy = 4;
@@ -559,19 +737,21 @@ async function getRates(client) {
559
737
  }
560
738
  async function getPositions(client, addressOrKeypair) {
561
739
  const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
562
- const state = await lending.getLendingState(address, clientOpt(client, true));
740
+ const [states, pools] = await Promise.all([getUserState(client, address), getPools()]);
563
741
  const positions = [];
564
- for (const pos of state) {
565
- const symbol = pos.pool.token?.symbol ?? "UNKNOWN";
566
- const supplyBal = Number(pos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS;
567
- const borrowBal = Number(pos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS;
742
+ for (const state of states) {
743
+ const pool = pools.find((p) => p.id === state.assetId);
744
+ if (!pool) continue;
745
+ const symbol = pool.token?.symbol ?? "UNKNOWN";
746
+ const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex);
747
+ const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex);
568
748
  if (supplyBal > 1e-4) {
569
749
  positions.push({
570
750
  protocol: "navi",
571
751
  asset: symbol,
572
752
  type: "save",
573
753
  amount: supplyBal,
574
- apy: rateToApy(pos.pool.currentSupplyRate)
754
+ apy: rateToApy(pool.currentSupplyRate)
575
755
  });
576
756
  }
577
757
  if (borrowBal > 1e-4) {
@@ -580,7 +760,7 @@ async function getPositions(client, addressOrKeypair) {
580
760
  asset: symbol,
581
761
  type: "borrow",
582
762
  amount: borrowBal,
583
- apy: rateToApy(pos.pool.currentBorrowRate)
763
+ apy: rateToApy(pool.currentBorrowRate)
584
764
  });
585
765
  }
586
766
  }
@@ -597,21 +777,13 @@ async function maxWithdrawAmount(client, addressOrKeypair) {
597
777
  }
598
778
  const remainingSupply = hf.supplied - maxAmount;
599
779
  const hfAfter = hf.borrowed > 0 ? remainingSupply / hf.borrowed : Infinity;
600
- return {
601
- maxAmount,
602
- healthFactorAfter: hfAfter,
603
- currentHF: hf.healthFactor
604
- };
780
+ return { maxAmount, healthFactorAfter: hfAfter, currentHF: hf.healthFactor };
605
781
  }
606
782
  async function maxBorrowAmount(client, addressOrKeypair) {
607
783
  const hf = await getHealthFactor(client, addressOrKeypair);
608
784
  const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
609
785
  const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
610
- return {
611
- maxAmount,
612
- healthFactorAfter: MIN_HEALTH_FACTOR,
613
- currentHF: hf.healthFactor
614
- };
786
+ return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR, currentHF: hf.healthFactor };
615
787
  }
616
788
 
617
789
  // src/protocols/yieldTracker.ts
@@ -1117,9 +1289,14 @@ var CetusAdapter = class {
1117
1289
  }
1118
1290
  };
1119
1291
  var USDC_TYPE2 = SUPPORTED_ASSETS.USDC.type;
1120
- SUPPORTED_ASSETS.USDC.decimals;
1121
1292
  var WAD = 1e18;
1122
1293
  var MIN_HEALTH_FACTOR2 = 1.5;
1294
+ var CLOCK2 = "0x6";
1295
+ var LENDING_MARKET_ID = "0x84030d26d85eaa7035084a057f2f11f701b7e2e4eda87551becbc7c97505ece1";
1296
+ var LENDING_MARKET_TYPE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf::suilend::MAIN_POOL";
1297
+ var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
1298
+ var UPGRADE_CAP_ID = "0x3d4ef1859c3ee9fc72858f588b56a09da5466e64f8cc4e90a7b3b909fba8a7ae";
1299
+ var FALLBACK_PUBLISHED_AT = "0xd2a67633ccb8de063163e25bcfca242929caf5cf1a26c2929dab519ee0b8f331";
1123
1300
  function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
1124
1301
  if (utilBreakpoints.length === 0) return 0;
1125
1302
  if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
@@ -1134,85 +1311,121 @@ function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
1134
1311
  }
1135
1312
  return aprBreakpoints[aprBreakpoints.length - 1];
1136
1313
  }
1137
- function computeRatesFromReserve(reserve) {
1138
- const decimals = reserve.mintDecimals;
1139
- const available = Number(reserve.availableAmount) / 10 ** decimals;
1140
- const borrowed = Number(reserve.borrowedAmount.value) / WAD / 10 ** decimals;
1314
+ function computeRates(reserve) {
1315
+ const available = reserve.availableAmount / 10 ** reserve.mintDecimals;
1316
+ const borrowed = reserve.borrowedAmountWad / WAD / 10 ** reserve.mintDecimals;
1141
1317
  const totalDeposited = available + borrowed;
1142
1318
  const utilizationPct = totalDeposited > 0 ? borrowed / totalDeposited * 100 : 0;
1143
- const config = reserve.config.element;
1144
- if (!config) return { borrowAprPct: 0, depositAprPct: 0, utilizationPct: 0 };
1145
- const utils = config.interestRateUtils.map(Number);
1146
- const aprs = config.interestRateAprs.map((a) => Number(a) / 100);
1147
- const borrowAprPct = interpolateRate(utils, aprs, utilizationPct);
1148
- const spreadFeeBps = Number(config.spreadFeeBps);
1149
- const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - spreadFeeBps / 1e4) * 100;
1150
- return { borrowAprPct, depositAprPct, utilizationPct };
1319
+ if (reserve.interestRateUtils.length === 0) return { borrowAprPct: 0, depositAprPct: 0 };
1320
+ const aprs = reserve.interestRateAprs.map((a) => a / 100);
1321
+ const borrowAprPct = interpolateRate(reserve.interestRateUtils, aprs, utilizationPct);
1322
+ const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - reserve.spreadFeeBps / 1e4) * 100;
1323
+ return { borrowAprPct, depositAprPct };
1151
1324
  }
1152
1325
  function cTokenRatio(reserve) {
1153
- if (reserve.ctokenSupply === 0n) return 1;
1154
- const available = Number(reserve.availableAmount);
1155
- const borrowed = Number(reserve.borrowedAmount.value) / WAD;
1156
- const spreadFees = Number(reserve.unclaimedSpreadFees.value) / WAD;
1157
- const totalSupply = available + borrowed - spreadFees;
1158
- return totalSupply / Number(reserve.ctokenSupply);
1326
+ if (reserve.ctokenSupply === 0) return 1;
1327
+ const totalSupply = reserve.availableAmount + reserve.borrowedAmountWad / WAD - reserve.unclaimedSpreadFeesWad / WAD;
1328
+ return totalSupply / reserve.ctokenSupply;
1329
+ }
1330
+ function f(obj) {
1331
+ if (obj && typeof obj === "object" && "fields" in obj) return obj.fields;
1332
+ return obj;
1333
+ }
1334
+ function str(v) {
1335
+ return String(v ?? "0");
1336
+ }
1337
+ function num(v) {
1338
+ return Number(str(v));
1339
+ }
1340
+ function parseReserve(raw, index) {
1341
+ const r = f(raw);
1342
+ const coinTypeField = f(r.coin_type);
1343
+ const config = f(f(r.config)?.element);
1344
+ return {
1345
+ coinType: str(coinTypeField?.name),
1346
+ mintDecimals: num(r.mint_decimals),
1347
+ availableAmount: num(r.available_amount),
1348
+ borrowedAmountWad: num(f(r.borrowed_amount)?.value),
1349
+ ctokenSupply: num(r.ctoken_supply),
1350
+ unclaimedSpreadFeesWad: num(f(r.unclaimed_spread_fees)?.value),
1351
+ cumulativeBorrowRateWad: num(f(r.cumulative_borrow_rate)?.value),
1352
+ openLtvPct: num(config?.open_ltv_pct),
1353
+ closeLtvPct: num(config?.close_ltv_pct),
1354
+ spreadFeeBps: num(config?.spread_fee_bps),
1355
+ interestRateUtils: Array.isArray(config?.interest_rate_utils) ? config.interest_rate_utils.map(num) : [],
1356
+ interestRateAprs: Array.isArray(config?.interest_rate_aprs) ? config.interest_rate_aprs.map(num) : [],
1357
+ arrayIndex: index
1358
+ };
1359
+ }
1360
+ function parseObligation(raw) {
1361
+ const deposits = Array.isArray(raw.deposits) ? raw.deposits.map((d) => {
1362
+ const df = f(d);
1363
+ return {
1364
+ coinType: str(f(df.coin_type)?.name),
1365
+ ctokenAmount: num(df.deposited_ctoken_amount),
1366
+ reserveIdx: num(df.reserve_array_index)
1367
+ };
1368
+ }) : [];
1369
+ const borrows = Array.isArray(raw.borrows) ? raw.borrows.map((b) => {
1370
+ const bf = f(b);
1371
+ return {
1372
+ coinType: str(f(bf.coin_type)?.name),
1373
+ borrowedWad: num(f(bf.borrowed_amount)?.value),
1374
+ cumBorrowRateWad: num(f(bf.cumulative_borrow_rate)?.value),
1375
+ reserveIdx: num(bf.reserve_array_index)
1376
+ };
1377
+ }) : [];
1378
+ return { deposits, borrows };
1159
1379
  }
1160
1380
  var SuilendAdapter = class {
1161
1381
  id = "suilend";
1162
1382
  name = "Suilend";
1163
- version = "1.0.0";
1383
+ version = "2.0.0";
1164
1384
  capabilities = ["save", "withdraw"];
1165
1385
  supportedAssets = ["USDC"];
1166
1386
  supportsSameAssetBorrow = false;
1167
1387
  client;
1168
- suilend;
1169
- lendingMarketType;
1170
- initialized = false;
1171
- initPromise = null;
1388
+ publishedAt = null;
1389
+ reserveCache = null;
1172
1390
  async init(client) {
1173
1391
  this.client = client;
1174
- await this.lazyInit();
1175
1392
  }
1176
1393
  initSync(client) {
1177
1394
  this.client = client;
1178
1395
  }
1179
- async lazyInit() {
1180
- if (this.initialized) return;
1181
- if (this.initPromise) return this.initPromise;
1182
- this.initPromise = (async () => {
1183
- let sdk;
1184
- try {
1185
- sdk = await import('@suilend/sdk');
1186
- } catch {
1187
- throw new T2000Error(
1188
- "PROTOCOL_UNAVAILABLE",
1189
- "Suilend SDK not installed. Run: npm install @suilend/sdk@^1"
1190
- );
1191
- }
1192
- this.lendingMarketType = sdk.LENDING_MARKET_TYPE;
1193
- try {
1194
- this.suilend = await sdk.SuilendClient.initialize(
1195
- sdk.LENDING_MARKET_ID,
1196
- sdk.LENDING_MARKET_TYPE,
1197
- this.client
1198
- );
1199
- } catch (err) {
1200
- this.initPromise = null;
1201
- throw new T2000Error(
1202
- "PROTOCOL_UNAVAILABLE",
1203
- `Failed to initialize Suilend: ${err instanceof Error ? err.message : String(err)}`
1204
- );
1396
+ // -- On-chain reads -------------------------------------------------------
1397
+ async resolvePackage() {
1398
+ if (this.publishedAt) return this.publishedAt;
1399
+ try {
1400
+ const cap = await this.client.getObject({ id: UPGRADE_CAP_ID, options: { showContent: true } });
1401
+ if (cap.data?.content?.dataType === "moveObject") {
1402
+ const fields = cap.data.content.fields;
1403
+ this.publishedAt = str(fields.package);
1404
+ return this.publishedAt;
1205
1405
  }
1206
- this.initialized = true;
1207
- })();
1208
- return this.initPromise;
1406
+ } catch {
1407
+ }
1408
+ this.publishedAt = FALLBACK_PUBLISHED_AT;
1409
+ return this.publishedAt;
1209
1410
  }
1210
- async ensureInit() {
1211
- if (!this.initialized) {
1212
- await this.lazyInit();
1411
+ async loadReserves(fresh = false) {
1412
+ if (this.reserveCache && !fresh) return this.reserveCache;
1413
+ const market = await this.client.getObject({
1414
+ id: LENDING_MARKET_ID,
1415
+ options: { showContent: true }
1416
+ });
1417
+ if (market.data?.content?.dataType !== "moveObject") {
1418
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend lending market");
1419
+ }
1420
+ const fields = market.data.content.fields;
1421
+ const reservesRaw = fields.reserves;
1422
+ if (!Array.isArray(reservesRaw)) {
1423
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to parse Suilend reserves");
1213
1424
  }
1425
+ this.reserveCache = reservesRaw.map((r, i) => parseReserve(r, i));
1426
+ return this.reserveCache;
1214
1427
  }
1215
- findReserve(asset) {
1428
+ findReserve(reserves, asset) {
1216
1429
  const upper = asset.toUpperCase();
1217
1430
  let coinType;
1218
1431
  if (upper === "USDC") coinType = USDC_TYPE2;
@@ -1221,187 +1434,229 @@ var SuilendAdapter = class {
1221
1434
  else return void 0;
1222
1435
  try {
1223
1436
  const normalized = utils.normalizeStructTag(coinType);
1224
- return this.suilend.lendingMarket.reserves.find(
1225
- (r) => utils.normalizeStructTag(r.coinType.name) === normalized
1226
- );
1437
+ return reserves.find((r) => {
1438
+ try {
1439
+ return utils.normalizeStructTag(r.coinType) === normalized;
1440
+ } catch {
1441
+ return false;
1442
+ }
1443
+ });
1227
1444
  } catch {
1228
1445
  return void 0;
1229
1446
  }
1230
1447
  }
1231
- async getObligationCaps(address) {
1232
- const SuilendClientStatic = (await import('@suilend/sdk')).SuilendClient;
1233
- return SuilendClientStatic.getObligationOwnerCaps(
1234
- address,
1235
- [this.lendingMarketType],
1236
- this.client
1237
- );
1448
+ async fetchObligationCaps(address) {
1449
+ const capType = `${SUILEND_PACKAGE}::lending_market::ObligationOwnerCap<${LENDING_MARKET_TYPE}>`;
1450
+ const caps = [];
1451
+ let cursor;
1452
+ let hasNext = true;
1453
+ while (hasNext) {
1454
+ const page = await this.client.getOwnedObjects({
1455
+ owner: address,
1456
+ filter: { StructType: capType },
1457
+ options: { showContent: true },
1458
+ cursor: cursor ?? void 0
1459
+ });
1460
+ for (const item of page.data) {
1461
+ if (item.data?.content?.dataType !== "moveObject") continue;
1462
+ const fields = item.data.content.fields;
1463
+ caps.push({
1464
+ id: item.data.objectId,
1465
+ obligationId: str(fields.obligation_id)
1466
+ });
1467
+ }
1468
+ cursor = page.nextCursor;
1469
+ hasNext = page.hasNextPage;
1470
+ }
1471
+ return caps;
1472
+ }
1473
+ async fetchObligation(obligationId) {
1474
+ const obj = await this.client.getObject({ id: obligationId, options: { showContent: true } });
1475
+ if (obj.data?.content?.dataType !== "moveObject") {
1476
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend obligation");
1477
+ }
1478
+ return parseObligation(obj.data.content.fields);
1238
1479
  }
1239
1480
  resolveSymbol(coinType) {
1240
- const normalized = utils.normalizeStructTag(coinType);
1241
- if (normalized === utils.normalizeStructTag(USDC_TYPE2)) return "USDC";
1242
- if (normalized === utils.normalizeStructTag("0x2::sui::SUI")) return "SUI";
1481
+ try {
1482
+ const normalized = utils.normalizeStructTag(coinType);
1483
+ if (normalized === utils.normalizeStructTag(USDC_TYPE2)) return "USDC";
1484
+ if (normalized === utils.normalizeStructTag("0x2::sui::SUI")) return "SUI";
1485
+ } catch {
1486
+ }
1243
1487
  const parts = coinType.split("::");
1244
1488
  return parts[parts.length - 1] || "UNKNOWN";
1245
1489
  }
1490
+ // -- Adapter interface ----------------------------------------------------
1246
1491
  async getRates(asset) {
1247
- await this.ensureInit();
1248
- const reserve = this.findReserve(asset);
1249
- if (!reserve) {
1250
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1251
- }
1252
- const { borrowAprPct, depositAprPct } = computeRatesFromReserve(reserve);
1253
- return {
1254
- asset,
1255
- saveApy: depositAprPct,
1256
- borrowApy: borrowAprPct
1257
- };
1492
+ const reserves = await this.loadReserves();
1493
+ const reserve = this.findReserve(reserves, asset);
1494
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1495
+ const { borrowAprPct, depositAprPct } = computeRates(reserve);
1496
+ return { asset, saveApy: depositAprPct, borrowApy: borrowAprPct };
1258
1497
  }
1259
1498
  async getPositions(address) {
1260
- await this.ensureInit();
1261
1499
  const supplies = [];
1262
1500
  const borrows = [];
1263
- const caps = await this.getObligationCaps(address);
1501
+ const caps = await this.fetchObligationCaps(address);
1264
1502
  if (caps.length === 0) return { supplies, borrows };
1265
- const obligation = await this.suilend.getObligation(caps[0].obligationId);
1266
- for (const deposit of obligation.deposits) {
1267
- const coinType = utils.normalizeStructTag(deposit.coinType.name);
1268
- const reserve = this.suilend.lendingMarket.reserves.find(
1269
- (r) => utils.normalizeStructTag(r.coinType.name) === coinType
1270
- );
1503
+ const [reserves, obligation] = await Promise.all([
1504
+ this.loadReserves(),
1505
+ this.fetchObligation(caps[0].obligationId)
1506
+ ]);
1507
+ for (const dep of obligation.deposits) {
1508
+ const reserve = reserves[dep.reserveIdx];
1271
1509
  if (!reserve) continue;
1272
- const ctokenAmount = Number(deposit.depositedCtokenAmount.toString());
1273
1510
  const ratio = cTokenRatio(reserve);
1274
- const amount = ctokenAmount * ratio / 10 ** reserve.mintDecimals;
1275
- const { depositAprPct } = computeRatesFromReserve(reserve);
1276
- supplies.push({ asset: this.resolveSymbol(coinType), amount, apy: depositAprPct });
1511
+ const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
1512
+ const { depositAprPct } = computeRates(reserve);
1513
+ supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct });
1277
1514
  }
1278
- for (const borrow of obligation.borrows) {
1279
- const coinType = utils.normalizeStructTag(borrow.coinType.name);
1280
- const reserve = this.suilend.lendingMarket.reserves.find(
1281
- (r) => utils.normalizeStructTag(r.coinType.name) === coinType
1282
- );
1515
+ for (const bor of obligation.borrows) {
1516
+ const reserve = reserves[bor.reserveIdx];
1283
1517
  if (!reserve) continue;
1284
- const rawBorrowed = Number(borrow.borrowedAmount.value.toString()) / WAD;
1285
- const amount = rawBorrowed / 10 ** reserve.mintDecimals;
1286
- const reserveRate = Number(reserve.cumulativeBorrowRate.value.toString()) / WAD;
1287
- const posRate = Number(borrow.cumulativeBorrowRate.value.toString()) / WAD;
1288
- const compounded = posRate > 0 ? amount * (reserveRate / posRate) : amount;
1289
- const { borrowAprPct } = computeRatesFromReserve(reserve);
1290
- borrows.push({ asset: this.resolveSymbol(coinType), amount: compounded, apy: borrowAprPct });
1518
+ const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
1519
+ const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
1520
+ const posRate = bor.cumBorrowRateWad / WAD;
1521
+ const compounded = posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
1522
+ const { borrowAprPct } = computeRates(reserve);
1523
+ borrows.push({ asset: this.resolveSymbol(bor.coinType), amount: compounded, apy: borrowAprPct });
1291
1524
  }
1292
1525
  return { supplies, borrows };
1293
1526
  }
1294
1527
  async getHealth(address) {
1295
- await this.ensureInit();
1296
- const caps = await this.getObligationCaps(address);
1528
+ const caps = await this.fetchObligationCaps(address);
1297
1529
  if (caps.length === 0) {
1298
1530
  return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
1299
1531
  }
1300
1532
  const positions = await this.getPositions(address);
1301
1533
  const supplied = positions.supplies.reduce((s, p) => s + p.amount, 0);
1302
1534
  const borrowed = positions.borrows.reduce((s, p) => s + p.amount, 0);
1303
- const reserve = this.findReserve("USDC");
1304
- const closeLtv = reserve?.config?.element?.closeLtvPct ?? 75;
1305
- const openLtv = reserve?.config?.element?.openLtvPct ?? 70;
1535
+ const reserves = await this.loadReserves();
1536
+ const reserve = this.findReserve(reserves, "USDC");
1537
+ const closeLtv = reserve?.closeLtvPct ?? 75;
1538
+ const openLtv = reserve?.openLtvPct ?? 70;
1306
1539
  const liqThreshold = closeLtv / 100;
1307
1540
  const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
1308
1541
  const maxBorrow = Math.max(0, supplied * (openLtv / 100) - borrowed);
1309
1542
  return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
1310
1543
  }
1311
1544
  async buildSaveTx(address, amount, _asset, options) {
1312
- await this.ensureInit();
1313
- const rawAmount = usdcToRaw(amount).toString();
1545
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1546
+ const usdcReserve = this.findReserve(reserves, "USDC");
1547
+ if (!usdcReserve) throw new T2000Error("ASSET_NOT_SUPPORTED", "USDC reserve not found on Suilend");
1548
+ const caps = await this.fetchObligationCaps(address);
1314
1549
  const tx = new transactions.Transaction();
1315
1550
  tx.setSender(address);
1316
- const caps = await this.getObligationCaps(address);
1317
1551
  let capRef;
1318
1552
  if (caps.length === 0) {
1319
- const [newCap] = this.suilend.createObligation(tx);
1553
+ const [newCap] = tx.moveCall({
1554
+ target: `${pkg}::lending_market::create_obligation`,
1555
+ typeArguments: [LENDING_MARKET_TYPE],
1556
+ arguments: [tx.object(LENDING_MARKET_ID)]
1557
+ });
1320
1558
  capRef = newCap;
1321
1559
  } else {
1322
1560
  capRef = caps[0].id;
1323
1561
  }
1324
1562
  const allCoins = await this.fetchAllCoins(address, USDC_TYPE2);
1325
- if (allCoins.length === 0) {
1326
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
1327
- }
1563
+ if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
1328
1564
  const primaryCoinId = allCoins[0].coinObjectId;
1329
1565
  if (allCoins.length > 1) {
1330
- tx.mergeCoins(
1331
- tx.object(primaryCoinId),
1332
- allCoins.slice(1).map((c) => tx.object(c.coinObjectId))
1333
- );
1566
+ tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1334
1567
  }
1568
+ const rawAmount = usdcToRaw(amount).toString();
1335
1569
  const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
1336
1570
  if (options?.collectFee) {
1337
1571
  addCollectFeeToTx(tx, depositCoin, "save");
1338
1572
  }
1339
- this.suilend.deposit(depositCoin, USDC_TYPE2, capRef, tx);
1573
+ const [ctokens] = tx.moveCall({
1574
+ target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
1575
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1576
+ arguments: [
1577
+ tx.object(LENDING_MARKET_ID),
1578
+ tx.pure.u64(usdcReserve.arrayIndex),
1579
+ tx.object(CLOCK2),
1580
+ depositCoin
1581
+ ]
1582
+ });
1583
+ tx.moveCall({
1584
+ target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
1585
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1586
+ arguments: [
1587
+ tx.object(LENDING_MARKET_ID),
1588
+ tx.pure.u64(usdcReserve.arrayIndex),
1589
+ typeof capRef === "string" ? tx.object(capRef) : capRef,
1590
+ tx.object(CLOCK2),
1591
+ ctokens
1592
+ ]
1593
+ });
1340
1594
  return { tx };
1341
1595
  }
1342
1596
  async buildWithdrawTx(address, amount, _asset) {
1343
- await this.ensureInit();
1344
- const caps = await this.getObligationCaps(address);
1345
- if (caps.length === 0) {
1346
- throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1347
- }
1597
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
1598
+ const usdcReserve = this.findReserve(reserves, "USDC");
1599
+ if (!usdcReserve) throw new T2000Error("ASSET_NOT_SUPPORTED", "USDC reserve not found on Suilend");
1600
+ const caps = await this.fetchObligationCaps(address);
1601
+ if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1348
1602
  const positions = await this.getPositions(address);
1349
- const usdcSupply = positions.supplies.find((s) => s.asset === "USDC");
1350
- const deposited = usdcSupply?.amount ?? 0;
1603
+ const deposited = positions.supplies.find((s) => s.asset === "USDC")?.amount ?? 0;
1351
1604
  const effectiveAmount = Math.min(amount, deposited);
1352
- if (effectiveAmount <= 0) {
1353
- throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw from Suilend");
1354
- }
1355
- const rawAmount = usdcToRaw(effectiveAmount).toString();
1605
+ if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw from Suilend");
1606
+ const ratio = cTokenRatio(usdcReserve);
1607
+ const ctokenAmount = Math.ceil(effectiveAmount * 10 ** usdcReserve.mintDecimals / ratio);
1356
1608
  const tx = new transactions.Transaction();
1357
1609
  tx.setSender(address);
1358
- await this.suilend.withdrawAndSendToUser(
1359
- address,
1360
- caps[0].id,
1361
- caps[0].obligationId,
1362
- USDC_TYPE2,
1363
- rawAmount,
1364
- tx
1365
- );
1610
+ const [ctokens] = tx.moveCall({
1611
+ target: `${pkg}::lending_market::withdraw_ctokens`,
1612
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1613
+ arguments: [
1614
+ tx.object(LENDING_MARKET_ID),
1615
+ tx.pure.u64(usdcReserve.arrayIndex),
1616
+ tx.object(caps[0].id),
1617
+ tx.object(CLOCK2),
1618
+ tx.pure.u64(ctokenAmount)
1619
+ ]
1620
+ });
1621
+ const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${USDC_TYPE2}>`;
1622
+ const [none] = tx.moveCall({
1623
+ target: "0x1::option::none",
1624
+ typeArguments: [exemptionType]
1625
+ });
1626
+ const [coin] = tx.moveCall({
1627
+ target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity`,
1628
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1629
+ arguments: [
1630
+ tx.object(LENDING_MARKET_ID),
1631
+ tx.pure.u64(usdcReserve.arrayIndex),
1632
+ tx.object(CLOCK2),
1633
+ ctokens,
1634
+ none
1635
+ ]
1636
+ });
1637
+ tx.transferObjects([coin], address);
1366
1638
  return { tx, effectiveAmount };
1367
1639
  }
1368
1640
  async buildBorrowTx(_address, _amount, _asset, _options) {
1369
- throw new T2000Error(
1370
- "ASSET_NOT_SUPPORTED",
1371
- "SuilendAdapter.buildBorrowTx() not available \u2014 Suilend requires different collateral/borrow assets. Deferred to Phase 10."
1372
- );
1641
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend borrow requires different collateral/borrow assets. Deferred to Phase 10.");
1373
1642
  }
1374
1643
  async buildRepayTx(_address, _amount, _asset) {
1375
- throw new T2000Error(
1376
- "ASSET_NOT_SUPPORTED",
1377
- "SuilendAdapter.buildRepayTx() not available \u2014 deferred to Phase 10."
1378
- );
1644
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend repay deferred to Phase 10.");
1379
1645
  }
1380
1646
  async maxWithdraw(address, _asset) {
1381
- await this.ensureInit();
1382
1647
  const health = await this.getHealth(address);
1383
1648
  let maxAmount;
1384
1649
  if (health.borrowed === 0) {
1385
1650
  maxAmount = health.supplied;
1386
1651
  } else {
1387
- maxAmount = Math.max(
1388
- 0,
1389
- health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold
1390
- );
1652
+ maxAmount = Math.max(0, health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold);
1391
1653
  }
1392
1654
  const remainingSupply = health.supplied - maxAmount;
1393
1655
  const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
1394
- return {
1395
- maxAmount,
1396
- healthFactorAfter: hfAfter,
1397
- currentHF: health.healthFactor
1398
- };
1656
+ return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
1399
1657
  }
1400
1658
  async maxBorrow(_address, _asset) {
1401
- throw new T2000Error(
1402
- "ASSET_NOT_SUPPORTED",
1403
- "SuilendAdapter.maxBorrow() not available \u2014 deferred to Phase 10."
1404
- );
1659
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend maxBorrow deferred to Phase 10.");
1405
1660
  }
1406
1661
  async fetchAllCoins(owner, coinType) {
1407
1662
  const all = [];
@@ -1716,7 +1971,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1716
1971
  return { agent, address, sponsored };
1717
1972
  }
1718
1973
  // -- Gas --
1719
- /** SuiClient used by this agent — exposed for x402 and other integrations. */
1974
+ /** SuiJsonRpcClient used by this agent — exposed for x402 and other integrations. */
1720
1975
  get suiClient() {
1721
1976
  return this.client;
1722
1977
  }