@t2000/sdk 0.4.2 → 0.5.0

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