@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.
@@ -1,5 +1,5 @@
1
- import { getPool, getLendingState, getHealthFactor as getHealthFactor$1, getCoins, mergeCoinsPTB, depositCoinPTB, withdrawCoinPTB, borrowCoinPTB, repayCoinPTB, getPriceFeeds, filterPriceFeeds, updateOraclePricesPTB } from '@naviprotocol/lending';
2
1
  import { Transaction } from '@mysten/sui/transactions';
2
+ import { bcs } from '@mysten/sui/bcs';
3
3
  import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
4
4
  import { normalizeStructTag } from '@mysten/sui/utils';
5
5
 
@@ -181,15 +181,59 @@ function addCollectFeeToTx(tx, paymentCoin, operation) {
181
181
  }
182
182
 
183
183
  // src/protocols/navi.ts
184
- var ENV = { env: "prod" };
185
184
  var USDC_TYPE = SUPPORTED_ASSETS.USDC.type;
186
185
  var RATE_DECIMALS = 27;
187
186
  var LTV_DECIMALS = 27;
188
187
  var MIN_HEALTH_FACTOR = 1.5;
189
188
  var WITHDRAW_DUST_BUFFER = 1e-3;
189
+ var CLOCK = "0x06";
190
+ var SUI_SYSTEM_STATE = "0x05";
190
191
  var NAVI_BALANCE_DECIMALS = 9;
191
- function clientOpt(client, fresh = false) {
192
- return { client, ...ENV, ...fresh ? { disableCache: true } : {} };
192
+ var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
193
+ var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
194
+ function toBigInt(v) {
195
+ if (typeof v === "bigint") return v;
196
+ return BigInt(String(v));
197
+ }
198
+ var UserStateInfo = bcs.struct("UserStateInfo", {
199
+ asset_id: bcs.u8(),
200
+ borrow_balance: bcs.u256(),
201
+ supply_balance: bcs.u256()
202
+ });
203
+ function decodeDevInspect(result, schema) {
204
+ const rv = result.results?.[0]?.returnValues?.[0];
205
+ if (result.error || !rv) return void 0;
206
+ const bytes = Uint8Array.from(rv[0]);
207
+ return schema.parse(bytes);
208
+ }
209
+ var configCache = null;
210
+ var poolsCache = null;
211
+ var CACHE_TTL = 5 * 6e4;
212
+ async function fetchJson(url) {
213
+ const res = await fetch(url);
214
+ if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI API error: ${res.status}`);
215
+ const json = await res.json();
216
+ return json.data ?? json;
217
+ }
218
+ async function getConfig(fresh = false) {
219
+ if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
220
+ const data = await fetchJson(CONFIG_API);
221
+ configCache = { data, ts: Date.now() };
222
+ return data;
223
+ }
224
+ async function getPools(fresh = false) {
225
+ if (poolsCache && !fresh && Date.now() - poolsCache.ts < CACHE_TTL) return poolsCache.data;
226
+ const data = await fetchJson(POOLS_API);
227
+ poolsCache = { data, ts: Date.now() };
228
+ return data;
229
+ }
230
+ async function getUsdcPool() {
231
+ const pools = await getPools();
232
+ const usdc = pools.find(
233
+ (p) => p.token?.symbol === "USDC" || p.coinType?.toLowerCase().includes("usdc")
234
+ );
235
+ if (!usdc) throw new T2000Error("PROTOCOL_UNAVAILABLE", "USDC pool not found on NAVI");
236
+ return usdc;
193
237
  }
194
238
  function rateToApy(rawRate) {
195
239
  if (!rawRate || rawRate === "0") return 0;
@@ -205,59 +249,146 @@ function parseLiqThreshold(val) {
205
249
  if (n > 1) return Number(BigInt(val)) / 10 ** LTV_DECIMALS;
206
250
  return n;
207
251
  }
208
- function findUsdcPosition(state) {
209
- return state.find(
210
- (p) => p.pool.token.symbol === "USDC" || p.pool.coinType.toLowerCase().includes("usdc")
211
- );
252
+ function normalizeHealthFactor(raw) {
253
+ const v = raw / 10 ** RATE_DECIMALS;
254
+ return v > 1e5 ? Infinity : v;
212
255
  }
213
- async function updateOracle(tx, client, address) {
214
- try {
215
- const [feeds, state] = await Promise.all([
216
- getPriceFeeds(ENV),
217
- getLendingState(address, clientOpt(client))
218
- ]);
219
- const relevant = filterPriceFeeds(feeds, { lendingState: state });
220
- if (relevant.length > 0) {
221
- await updateOraclePricesPTB(tx, relevant, { ...ENV, updatePythPriceFeeds: true });
222
- }
223
- } catch {
256
+ function compoundBalance(rawBalance, currentIndex) {
257
+ if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
258
+ const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
259
+ const half = scale / 2n;
260
+ const result = (rawBalance * scale + half) / BigInt(currentIndex);
261
+ return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
262
+ }
263
+ async function getUserState(client, address) {
264
+ const config = await getConfig();
265
+ const tx = new Transaction();
266
+ tx.moveCall({
267
+ target: `${config.uiGetter}::getter_unchecked::get_user_state`,
268
+ arguments: [tx.object(config.storage), tx.pure.address(address)]
269
+ });
270
+ const result = await client.devInspectTransactionBlock({
271
+ transactionBlock: tx,
272
+ sender: address
273
+ });
274
+ const decoded = decodeDevInspect(result, bcs.vector(UserStateInfo));
275
+ if (!decoded) return [];
276
+ return decoded.map((s) => ({
277
+ assetId: s.asset_id,
278
+ supplyBalance: toBigInt(s.supply_balance),
279
+ borrowBalance: toBigInt(s.borrow_balance)
280
+ })).filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
281
+ }
282
+ async function fetchCoins(client, owner, coinType) {
283
+ const all = [];
284
+ let cursor;
285
+ let hasNext = true;
286
+ while (hasNext) {
287
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
288
+ all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
289
+ cursor = page.nextCursor;
290
+ hasNext = page.hasNextPage;
291
+ }
292
+ return all;
293
+ }
294
+ function mergeCoinsPtb(tx, coins, amount) {
295
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
296
+ const primary = tx.object(coins[0].coinObjectId);
297
+ if (coins.length > 1) {
298
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
224
299
  }
300
+ const [split] = tx.splitCoins(primary, [amount]);
301
+ return split;
225
302
  }
226
303
  async function buildSaveTx(client, address, amount, options = {}) {
227
304
  const rawAmount = Number(usdcToRaw(amount));
228
- const coins = await getCoins(address, { coinType: USDC_TYPE, client });
229
- if (!coins || coins.length === 0) {
230
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
231
- }
305
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
306
+ const coins = await fetchCoins(client, address, USDC_TYPE);
307
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
232
308
  const tx = new Transaction();
233
309
  tx.setSender(address);
234
- const coinObj = mergeCoinsPTB(tx, coins, { balance: rawAmount });
310
+ const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
235
311
  if (options.collectFee) {
236
312
  addCollectFeeToTx(tx, coinObj, "save");
237
313
  }
238
- await depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
314
+ tx.moveCall({
315
+ target: `${config.package}::incentive_v3::entry_deposit`,
316
+ arguments: [
317
+ tx.object(CLOCK),
318
+ tx.object(config.storage),
319
+ tx.object(pool.contract.pool),
320
+ tx.pure.u8(pool.id),
321
+ coinObj,
322
+ tx.pure.u64(rawAmount),
323
+ tx.object(config.incentiveV2),
324
+ tx.object(config.incentiveV3)
325
+ ],
326
+ typeArguments: [pool.suiCoinType]
327
+ });
239
328
  return tx;
240
329
  }
241
330
  async function buildWithdrawTx(client, address, amount) {
242
- const state = await getLendingState(address, clientOpt(client, true));
243
- const usdcPos = findUsdcPosition(state);
244
- const deposited = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
331
+ const [config, pool, pools, states] = await Promise.all([
332
+ getConfig(),
333
+ getUsdcPool(),
334
+ getPools(),
335
+ getUserState(client, address)
336
+ ]);
337
+ const usdcState = states.find((s) => s.assetId === pool.id);
338
+ const deposited = usdcState ? compoundBalance(usdcState.supplyBalance, pool.currentSupplyIndex) : 0;
245
339
  const effectiveAmount = Math.min(amount, Math.max(0, deposited - WITHDRAW_DUST_BUFFER));
246
340
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw");
247
341
  const rawAmount = Number(usdcToRaw(effectiveAmount));
248
342
  const tx = new Transaction();
249
343
  tx.setSender(address);
250
- await updateOracle(tx, client, address);
251
- const withdrawnCoin = await withdrawCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
252
- tx.transferObjects([withdrawnCoin], address);
344
+ const [balance] = tx.moveCall({
345
+ target: `${config.package}::incentive_v3::withdraw_v2`,
346
+ arguments: [
347
+ tx.object(CLOCK),
348
+ tx.object(config.oracle.priceOracle),
349
+ tx.object(config.storage),
350
+ tx.object(pool.contract.pool),
351
+ tx.pure.u8(pool.id),
352
+ tx.pure.u64(rawAmount),
353
+ tx.object(config.incentiveV2),
354
+ tx.object(config.incentiveV3),
355
+ tx.object(SUI_SYSTEM_STATE)
356
+ ],
357
+ typeArguments: [pool.suiCoinType]
358
+ });
359
+ const [coin] = tx.moveCall({
360
+ target: "0x2::coin::from_balance",
361
+ arguments: [balance],
362
+ typeArguments: [pool.suiCoinType]
363
+ });
364
+ tx.transferObjects([coin], address);
253
365
  return { tx, effectiveAmount };
254
366
  }
255
367
  async function buildBorrowTx(client, address, amount, options = {}) {
256
368
  const rawAmount = Number(usdcToRaw(amount));
369
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
257
370
  const tx = new Transaction();
258
371
  tx.setSender(address);
259
- await updateOracle(tx, client, address);
260
- const borrowedCoin = await borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
372
+ const [balance] = tx.moveCall({
373
+ target: `${config.package}::incentive_v3::borrow_v2`,
374
+ arguments: [
375
+ tx.object(CLOCK),
376
+ tx.object(config.oracle.priceOracle),
377
+ tx.object(config.storage),
378
+ tx.object(pool.contract.pool),
379
+ tx.pure.u8(pool.id),
380
+ tx.pure.u64(rawAmount),
381
+ tx.object(config.incentiveV2),
382
+ tx.object(config.incentiveV3),
383
+ tx.object(SUI_SYSTEM_STATE)
384
+ ],
385
+ typeArguments: [pool.suiCoinType]
386
+ });
387
+ const [borrowedCoin] = tx.moveCall({
388
+ target: "0x2::coin::from_balance",
389
+ arguments: [balance],
390
+ typeArguments: [pool.suiCoinType]
391
+ });
261
392
  if (options.collectFee) {
262
393
  addCollectFeeToTx(tx, borrowedCoin, "borrow");
263
394
  }
@@ -266,31 +397,79 @@ async function buildBorrowTx(client, address, amount, options = {}) {
266
397
  }
267
398
  async function buildRepayTx(client, address, amount) {
268
399
  const rawAmount = Number(usdcToRaw(amount));
269
- const coins = await getCoins(address, { coinType: USDC_TYPE, client });
270
- if (!coins || coins.length === 0) {
271
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
272
- }
400
+ const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
401
+ const coins = await fetchCoins(client, address, USDC_TYPE);
402
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
273
403
  const tx = new Transaction();
274
404
  tx.setSender(address);
275
- const coinObj = mergeCoinsPTB(tx, coins, { balance: rawAmount });
276
- await repayCoinPTB(tx, USDC_TYPE, coinObj, { ...ENV, amount: rawAmount });
405
+ const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
406
+ tx.moveCall({
407
+ target: `${config.package}::incentive_v3::entry_repay`,
408
+ arguments: [
409
+ tx.object(CLOCK),
410
+ tx.object(config.oracle.priceOracle),
411
+ tx.object(config.storage),
412
+ tx.object(pool.contract.pool),
413
+ tx.pure.u8(pool.id),
414
+ coinObj,
415
+ tx.pure.u64(rawAmount),
416
+ tx.object(config.incentiveV2),
417
+ tx.object(config.incentiveV3)
418
+ ],
419
+ typeArguments: [pool.suiCoinType]
420
+ });
277
421
  return tx;
278
422
  }
279
423
  async function getHealthFactor(client, addressOrKeypair) {
280
424
  const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
281
- const [healthFactor, state, pool] = await Promise.all([
282
- getHealthFactor$1(address, clientOpt(client, true)),
283
- getLendingState(address, clientOpt(client, true)),
284
- getPool(USDC_TYPE, ENV)
425
+ const [config, pool, states] = await Promise.all([
426
+ getConfig(),
427
+ getUsdcPool(),
428
+ getUserState(client, address)
285
429
  ]);
286
- const usdcPos = findUsdcPosition(state);
287
- const supplied = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
288
- const borrowed = usdcPos ? Number(usdcPos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
430
+ const usdcState = states.find((s) => s.assetId === pool.id);
431
+ const supplied = usdcState ? compoundBalance(usdcState.supplyBalance, pool.currentSupplyIndex) : 0;
432
+ const borrowed = usdcState ? compoundBalance(usdcState.borrowBalance, pool.currentBorrowIndex) : 0;
289
433
  const ltv = parseLtv(pool.ltv);
290
434
  const liqThreshold = parseLiqThreshold(pool.liquidationFactor.threshold);
291
435
  const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
436
+ let healthFactor;
437
+ if (borrowed <= 0) {
438
+ healthFactor = Infinity;
439
+ } else {
440
+ try {
441
+ const tx = new Transaction();
442
+ tx.moveCall({
443
+ target: `${config.uiGetter}::calculator_unchecked::dynamic_health_factor`,
444
+ arguments: [
445
+ tx.object(CLOCK),
446
+ tx.object(config.storage),
447
+ tx.object(config.oracle.priceOracle),
448
+ tx.pure.u8(pool.id),
449
+ tx.pure.address(address),
450
+ tx.pure.u8(pool.id),
451
+ tx.pure.u64(0),
452
+ tx.pure.u64(0),
453
+ tx.pure.bool(false)
454
+ ],
455
+ typeArguments: [pool.suiCoinType]
456
+ });
457
+ const result = await client.devInspectTransactionBlock({
458
+ transactionBlock: tx,
459
+ sender: address
460
+ });
461
+ const decoded = decodeDevInspect(result, bcs.u256());
462
+ if (decoded !== void 0) {
463
+ healthFactor = normalizeHealthFactor(Number(decoded));
464
+ } else {
465
+ healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
466
+ }
467
+ } catch {
468
+ healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
469
+ }
470
+ }
292
471
  return {
293
- healthFactor: borrowed > 0 ? healthFactor : Infinity,
472
+ healthFactor,
294
473
  supplied,
295
474
  borrowed,
296
475
  maxBorrow: maxBorrowVal,
@@ -299,7 +478,7 @@ async function getHealthFactor(client, addressOrKeypair) {
299
478
  }
300
479
  async function getRates(client) {
301
480
  try {
302
- const pool = await getPool(USDC_TYPE, ENV);
481
+ const pool = await getUsdcPool();
303
482
  let saveApy = rateToApy(pool.currentSupplyRate);
304
483
  let borrowApy = rateToApy(pool.currentBorrowRate);
305
484
  if (saveApy <= 0 || saveApy > 100) saveApy = 4;
@@ -311,19 +490,21 @@ async function getRates(client) {
311
490
  }
312
491
  async function getPositions(client, addressOrKeypair) {
313
492
  const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
314
- const state = await getLendingState(address, clientOpt(client, true));
493
+ const [states, pools] = await Promise.all([getUserState(client, address), getPools()]);
315
494
  const positions = [];
316
- for (const pos of state) {
317
- const symbol = pos.pool.token?.symbol ?? "UNKNOWN";
318
- const supplyBal = Number(pos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS;
319
- const borrowBal = Number(pos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS;
495
+ for (const state of states) {
496
+ const pool = pools.find((p) => p.id === state.assetId);
497
+ if (!pool) continue;
498
+ const symbol = pool.token?.symbol ?? "UNKNOWN";
499
+ const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex);
500
+ const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex);
320
501
  if (supplyBal > 1e-4) {
321
502
  positions.push({
322
503
  protocol: "navi",
323
504
  asset: symbol,
324
505
  type: "save",
325
506
  amount: supplyBal,
326
- apy: rateToApy(pos.pool.currentSupplyRate)
507
+ apy: rateToApy(pool.currentSupplyRate)
327
508
  });
328
509
  }
329
510
  if (borrowBal > 1e-4) {
@@ -332,7 +513,7 @@ async function getPositions(client, addressOrKeypair) {
332
513
  asset: symbol,
333
514
  type: "borrow",
334
515
  amount: borrowBal,
335
- apy: rateToApy(pos.pool.currentBorrowRate)
516
+ apy: rateToApy(pool.currentBorrowRate)
336
517
  });
337
518
  }
338
519
  }
@@ -349,21 +530,13 @@ async function maxWithdrawAmount(client, addressOrKeypair) {
349
530
  }
350
531
  const remainingSupply = hf.supplied - maxAmount;
351
532
  const hfAfter = hf.borrowed > 0 ? remainingSupply / hf.borrowed : Infinity;
352
- return {
353
- maxAmount,
354
- healthFactorAfter: hfAfter,
355
- currentHF: hf.healthFactor
356
- };
533
+ return { maxAmount, healthFactorAfter: hfAfter, currentHF: hf.healthFactor };
357
534
  }
358
535
  async function maxBorrowAmount(client, addressOrKeypair) {
359
536
  const hf = await getHealthFactor(client, addressOrKeypair);
360
537
  const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
361
538
  const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
362
- return {
363
- maxAmount,
364
- healthFactorAfter: MIN_HEALTH_FACTOR,
365
- currentHF: hf.healthFactor
366
- };
539
+ return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR, currentHF: hf.healthFactor };
367
540
  }
368
541
 
369
542
  // src/adapters/navi.ts
@@ -559,6 +732,12 @@ var CetusAdapter = class {
559
732
  var USDC_TYPE2 = SUPPORTED_ASSETS.USDC.type;
560
733
  var WAD = 1e18;
561
734
  var MIN_HEALTH_FACTOR2 = 1.5;
735
+ var CLOCK2 = "0x6";
736
+ var LENDING_MARKET_ID = "0x84030d26d85eaa7035084a057f2f11f701b7e2e4eda87551becbc7c97505ece1";
737
+ var LENDING_MARKET_TYPE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf::suilend::MAIN_POOL";
738
+ var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
739
+ var UPGRADE_CAP_ID = "0x3d4ef1859c3ee9fc72858f588b56a09da5466e64f8cc4e90a7b3b909fba8a7ae";
740
+ var FALLBACK_PUBLISHED_AT = "0xd2a67633ccb8de063163e25bcfca242929caf5cf1a26c2929dab519ee0b8f331";
562
741
  function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
563
742
  if (utilBreakpoints.length === 0) return 0;
564
743
  if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
@@ -573,85 +752,121 @@ function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
573
752
  }
574
753
  return aprBreakpoints[aprBreakpoints.length - 1];
575
754
  }
576
- function computeRatesFromReserve(reserve) {
577
- const decimals = reserve.mintDecimals;
578
- const available = Number(reserve.availableAmount) / 10 ** decimals;
579
- const borrowed = Number(reserve.borrowedAmount.value) / WAD / 10 ** decimals;
755
+ function computeRates(reserve) {
756
+ const available = reserve.availableAmount / 10 ** reserve.mintDecimals;
757
+ const borrowed = reserve.borrowedAmountWad / WAD / 10 ** reserve.mintDecimals;
580
758
  const totalDeposited = available + borrowed;
581
759
  const utilizationPct = totalDeposited > 0 ? borrowed / totalDeposited * 100 : 0;
582
- const config = reserve.config.element;
583
- if (!config) return { borrowAprPct: 0, depositAprPct: 0, utilizationPct: 0 };
584
- const utils = config.interestRateUtils.map(Number);
585
- const aprs = config.interestRateAprs.map((a) => Number(a) / 100);
586
- const borrowAprPct = interpolateRate(utils, aprs, utilizationPct);
587
- const spreadFeeBps = Number(config.spreadFeeBps);
588
- const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - spreadFeeBps / 1e4) * 100;
589
- return { borrowAprPct, depositAprPct, utilizationPct };
760
+ if (reserve.interestRateUtils.length === 0) return { borrowAprPct: 0, depositAprPct: 0 };
761
+ const aprs = reserve.interestRateAprs.map((a) => a / 100);
762
+ const borrowAprPct = interpolateRate(reserve.interestRateUtils, aprs, utilizationPct);
763
+ const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - reserve.spreadFeeBps / 1e4) * 100;
764
+ return { borrowAprPct, depositAprPct };
590
765
  }
591
766
  function cTokenRatio(reserve) {
592
- if (reserve.ctokenSupply === 0n) return 1;
593
- const available = Number(reserve.availableAmount);
594
- const borrowed = Number(reserve.borrowedAmount.value) / WAD;
595
- const spreadFees = Number(reserve.unclaimedSpreadFees.value) / WAD;
596
- const totalSupply = available + borrowed - spreadFees;
597
- return totalSupply / Number(reserve.ctokenSupply);
767
+ if (reserve.ctokenSupply === 0) return 1;
768
+ const totalSupply = reserve.availableAmount + reserve.borrowedAmountWad / WAD - reserve.unclaimedSpreadFeesWad / WAD;
769
+ return totalSupply / reserve.ctokenSupply;
770
+ }
771
+ function f(obj) {
772
+ if (obj && typeof obj === "object" && "fields" in obj) return obj.fields;
773
+ return obj;
774
+ }
775
+ function str(v) {
776
+ return String(v ?? "0");
777
+ }
778
+ function num(v) {
779
+ return Number(str(v));
780
+ }
781
+ function parseReserve(raw, index) {
782
+ const r = f(raw);
783
+ const coinTypeField = f(r.coin_type);
784
+ const config = f(f(r.config)?.element);
785
+ return {
786
+ coinType: str(coinTypeField?.name),
787
+ mintDecimals: num(r.mint_decimals),
788
+ availableAmount: num(r.available_amount),
789
+ borrowedAmountWad: num(f(r.borrowed_amount)?.value),
790
+ ctokenSupply: num(r.ctoken_supply),
791
+ unclaimedSpreadFeesWad: num(f(r.unclaimed_spread_fees)?.value),
792
+ cumulativeBorrowRateWad: num(f(r.cumulative_borrow_rate)?.value),
793
+ openLtvPct: num(config?.open_ltv_pct),
794
+ closeLtvPct: num(config?.close_ltv_pct),
795
+ spreadFeeBps: num(config?.spread_fee_bps),
796
+ interestRateUtils: Array.isArray(config?.interest_rate_utils) ? config.interest_rate_utils.map(num) : [],
797
+ interestRateAprs: Array.isArray(config?.interest_rate_aprs) ? config.interest_rate_aprs.map(num) : [],
798
+ arrayIndex: index
799
+ };
800
+ }
801
+ function parseObligation(raw) {
802
+ const deposits = Array.isArray(raw.deposits) ? raw.deposits.map((d) => {
803
+ const df = f(d);
804
+ return {
805
+ coinType: str(f(df.coin_type)?.name),
806
+ ctokenAmount: num(df.deposited_ctoken_amount),
807
+ reserveIdx: num(df.reserve_array_index)
808
+ };
809
+ }) : [];
810
+ const borrows = Array.isArray(raw.borrows) ? raw.borrows.map((b) => {
811
+ const bf = f(b);
812
+ return {
813
+ coinType: str(f(bf.coin_type)?.name),
814
+ borrowedWad: num(f(bf.borrowed_amount)?.value),
815
+ cumBorrowRateWad: num(f(bf.cumulative_borrow_rate)?.value),
816
+ reserveIdx: num(bf.reserve_array_index)
817
+ };
818
+ }) : [];
819
+ return { deposits, borrows };
598
820
  }
599
821
  var SuilendAdapter = class {
600
822
  id = "suilend";
601
823
  name = "Suilend";
602
- version = "1.0.0";
824
+ version = "2.0.0";
603
825
  capabilities = ["save", "withdraw"];
604
826
  supportedAssets = ["USDC"];
605
827
  supportsSameAssetBorrow = false;
606
828
  client;
607
- suilend;
608
- lendingMarketType;
609
- initialized = false;
610
- initPromise = null;
829
+ publishedAt = null;
830
+ reserveCache = null;
611
831
  async init(client) {
612
832
  this.client = client;
613
- await this.lazyInit();
614
833
  }
615
834
  initSync(client) {
616
835
  this.client = client;
617
836
  }
618
- async lazyInit() {
619
- if (this.initialized) return;
620
- if (this.initPromise) return this.initPromise;
621
- this.initPromise = (async () => {
622
- let sdk;
623
- try {
624
- sdk = await import('@suilend/sdk');
625
- } catch {
626
- throw new T2000Error(
627
- "PROTOCOL_UNAVAILABLE",
628
- "Suilend SDK not installed. Run: npm install @suilend/sdk@^1"
629
- );
630
- }
631
- this.lendingMarketType = sdk.LENDING_MARKET_TYPE;
632
- try {
633
- this.suilend = await sdk.SuilendClient.initialize(
634
- sdk.LENDING_MARKET_ID,
635
- sdk.LENDING_MARKET_TYPE,
636
- this.client
637
- );
638
- } catch (err) {
639
- this.initPromise = null;
640
- throw new T2000Error(
641
- "PROTOCOL_UNAVAILABLE",
642
- `Failed to initialize Suilend: ${err instanceof Error ? err.message : String(err)}`
643
- );
837
+ // -- On-chain reads -------------------------------------------------------
838
+ async resolvePackage() {
839
+ if (this.publishedAt) return this.publishedAt;
840
+ try {
841
+ const cap = await this.client.getObject({ id: UPGRADE_CAP_ID, options: { showContent: true } });
842
+ if (cap.data?.content?.dataType === "moveObject") {
843
+ const fields = cap.data.content.fields;
844
+ this.publishedAt = str(fields.package);
845
+ return this.publishedAt;
644
846
  }
645
- this.initialized = true;
646
- })();
647
- return this.initPromise;
847
+ } catch {
848
+ }
849
+ this.publishedAt = FALLBACK_PUBLISHED_AT;
850
+ return this.publishedAt;
648
851
  }
649
- async ensureInit() {
650
- if (!this.initialized) {
651
- await this.lazyInit();
852
+ async loadReserves(fresh = false) {
853
+ if (this.reserveCache && !fresh) return this.reserveCache;
854
+ const market = await this.client.getObject({
855
+ id: LENDING_MARKET_ID,
856
+ options: { showContent: true }
857
+ });
858
+ if (market.data?.content?.dataType !== "moveObject") {
859
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend lending market");
652
860
  }
861
+ const fields = market.data.content.fields;
862
+ const reservesRaw = fields.reserves;
863
+ if (!Array.isArray(reservesRaw)) {
864
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to parse Suilend reserves");
865
+ }
866
+ this.reserveCache = reservesRaw.map((r, i) => parseReserve(r, i));
867
+ return this.reserveCache;
653
868
  }
654
- findReserve(asset) {
869
+ findReserve(reserves, asset) {
655
870
  const upper = asset.toUpperCase();
656
871
  let coinType;
657
872
  if (upper === "USDC") coinType = USDC_TYPE2;
@@ -660,187 +875,229 @@ var SuilendAdapter = class {
660
875
  else return void 0;
661
876
  try {
662
877
  const normalized = normalizeStructTag(coinType);
663
- return this.suilend.lendingMarket.reserves.find(
664
- (r) => normalizeStructTag(r.coinType.name) === normalized
665
- );
878
+ return reserves.find((r) => {
879
+ try {
880
+ return normalizeStructTag(r.coinType) === normalized;
881
+ } catch {
882
+ return false;
883
+ }
884
+ });
666
885
  } catch {
667
886
  return void 0;
668
887
  }
669
888
  }
670
- async getObligationCaps(address) {
671
- const SuilendClientStatic = (await import('@suilend/sdk')).SuilendClient;
672
- return SuilendClientStatic.getObligationOwnerCaps(
673
- address,
674
- [this.lendingMarketType],
675
- this.client
676
- );
889
+ async fetchObligationCaps(address) {
890
+ const capType = `${SUILEND_PACKAGE}::lending_market::ObligationOwnerCap<${LENDING_MARKET_TYPE}>`;
891
+ const caps = [];
892
+ let cursor;
893
+ let hasNext = true;
894
+ while (hasNext) {
895
+ const page = await this.client.getOwnedObjects({
896
+ owner: address,
897
+ filter: { StructType: capType },
898
+ options: { showContent: true },
899
+ cursor: cursor ?? void 0
900
+ });
901
+ for (const item of page.data) {
902
+ if (item.data?.content?.dataType !== "moveObject") continue;
903
+ const fields = item.data.content.fields;
904
+ caps.push({
905
+ id: item.data.objectId,
906
+ obligationId: str(fields.obligation_id)
907
+ });
908
+ }
909
+ cursor = page.nextCursor;
910
+ hasNext = page.hasNextPage;
911
+ }
912
+ return caps;
913
+ }
914
+ async fetchObligation(obligationId) {
915
+ const obj = await this.client.getObject({ id: obligationId, options: { showContent: true } });
916
+ if (obj.data?.content?.dataType !== "moveObject") {
917
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend obligation");
918
+ }
919
+ return parseObligation(obj.data.content.fields);
677
920
  }
678
921
  resolveSymbol(coinType) {
679
- const normalized = normalizeStructTag(coinType);
680
- if (normalized === normalizeStructTag(USDC_TYPE2)) return "USDC";
681
- if (normalized === normalizeStructTag("0x2::sui::SUI")) return "SUI";
922
+ try {
923
+ const normalized = normalizeStructTag(coinType);
924
+ if (normalized === normalizeStructTag(USDC_TYPE2)) return "USDC";
925
+ if (normalized === normalizeStructTag("0x2::sui::SUI")) return "SUI";
926
+ } catch {
927
+ }
682
928
  const parts = coinType.split("::");
683
929
  return parts[parts.length - 1] || "UNKNOWN";
684
930
  }
931
+ // -- Adapter interface ----------------------------------------------------
685
932
  async getRates(asset) {
686
- await this.ensureInit();
687
- const reserve = this.findReserve(asset);
688
- if (!reserve) {
689
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
690
- }
691
- const { borrowAprPct, depositAprPct } = computeRatesFromReserve(reserve);
692
- return {
693
- asset,
694
- saveApy: depositAprPct,
695
- borrowApy: borrowAprPct
696
- };
933
+ const reserves = await this.loadReserves();
934
+ const reserve = this.findReserve(reserves, asset);
935
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
936
+ const { borrowAprPct, depositAprPct } = computeRates(reserve);
937
+ return { asset, saveApy: depositAprPct, borrowApy: borrowAprPct };
697
938
  }
698
939
  async getPositions(address) {
699
- await this.ensureInit();
700
940
  const supplies = [];
701
941
  const borrows = [];
702
- const caps = await this.getObligationCaps(address);
942
+ const caps = await this.fetchObligationCaps(address);
703
943
  if (caps.length === 0) return { supplies, borrows };
704
- const obligation = await this.suilend.getObligation(caps[0].obligationId);
705
- for (const deposit of obligation.deposits) {
706
- const coinType = normalizeStructTag(deposit.coinType.name);
707
- const reserve = this.suilend.lendingMarket.reserves.find(
708
- (r) => normalizeStructTag(r.coinType.name) === coinType
709
- );
944
+ const [reserves, obligation] = await Promise.all([
945
+ this.loadReserves(),
946
+ this.fetchObligation(caps[0].obligationId)
947
+ ]);
948
+ for (const dep of obligation.deposits) {
949
+ const reserve = reserves[dep.reserveIdx];
710
950
  if (!reserve) continue;
711
- const ctokenAmount = Number(deposit.depositedCtokenAmount.toString());
712
951
  const ratio = cTokenRatio(reserve);
713
- const amount = ctokenAmount * ratio / 10 ** reserve.mintDecimals;
714
- const { depositAprPct } = computeRatesFromReserve(reserve);
715
- supplies.push({ asset: this.resolveSymbol(coinType), amount, apy: depositAprPct });
952
+ const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
953
+ const { depositAprPct } = computeRates(reserve);
954
+ supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct });
716
955
  }
717
- for (const borrow of obligation.borrows) {
718
- const coinType = normalizeStructTag(borrow.coinType.name);
719
- const reserve = this.suilend.lendingMarket.reserves.find(
720
- (r) => normalizeStructTag(r.coinType.name) === coinType
721
- );
956
+ for (const bor of obligation.borrows) {
957
+ const reserve = reserves[bor.reserveIdx];
722
958
  if (!reserve) continue;
723
- const rawBorrowed = Number(borrow.borrowedAmount.value.toString()) / WAD;
724
- const amount = rawBorrowed / 10 ** reserve.mintDecimals;
725
- const reserveRate = Number(reserve.cumulativeBorrowRate.value.toString()) / WAD;
726
- const posRate = Number(borrow.cumulativeBorrowRate.value.toString()) / WAD;
727
- const compounded = posRate > 0 ? amount * (reserveRate / posRate) : amount;
728
- const { borrowAprPct } = computeRatesFromReserve(reserve);
729
- borrows.push({ asset: this.resolveSymbol(coinType), amount: compounded, apy: borrowAprPct });
959
+ const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
960
+ const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
961
+ const posRate = bor.cumBorrowRateWad / WAD;
962
+ const compounded = posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
963
+ const { borrowAprPct } = computeRates(reserve);
964
+ borrows.push({ asset: this.resolveSymbol(bor.coinType), amount: compounded, apy: borrowAprPct });
730
965
  }
731
966
  return { supplies, borrows };
732
967
  }
733
968
  async getHealth(address) {
734
- await this.ensureInit();
735
- const caps = await this.getObligationCaps(address);
969
+ const caps = await this.fetchObligationCaps(address);
736
970
  if (caps.length === 0) {
737
971
  return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
738
972
  }
739
973
  const positions = await this.getPositions(address);
740
974
  const supplied = positions.supplies.reduce((s, p) => s + p.amount, 0);
741
975
  const borrowed = positions.borrows.reduce((s, p) => s + p.amount, 0);
742
- const reserve = this.findReserve("USDC");
743
- const closeLtv = reserve?.config?.element?.closeLtvPct ?? 75;
744
- const openLtv = reserve?.config?.element?.openLtvPct ?? 70;
976
+ const reserves = await this.loadReserves();
977
+ const reserve = this.findReserve(reserves, "USDC");
978
+ const closeLtv = reserve?.closeLtvPct ?? 75;
979
+ const openLtv = reserve?.openLtvPct ?? 70;
745
980
  const liqThreshold = closeLtv / 100;
746
981
  const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
747
982
  const maxBorrow = Math.max(0, supplied * (openLtv / 100) - borrowed);
748
983
  return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
749
984
  }
750
985
  async buildSaveTx(address, amount, _asset, options) {
751
- await this.ensureInit();
752
- const rawAmount = usdcToRaw(amount).toString();
986
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
987
+ const usdcReserve = this.findReserve(reserves, "USDC");
988
+ if (!usdcReserve) throw new T2000Error("ASSET_NOT_SUPPORTED", "USDC reserve not found on Suilend");
989
+ const caps = await this.fetchObligationCaps(address);
753
990
  const tx = new Transaction();
754
991
  tx.setSender(address);
755
- const caps = await this.getObligationCaps(address);
756
992
  let capRef;
757
993
  if (caps.length === 0) {
758
- const [newCap] = this.suilend.createObligation(tx);
994
+ const [newCap] = tx.moveCall({
995
+ target: `${pkg}::lending_market::create_obligation`,
996
+ typeArguments: [LENDING_MARKET_TYPE],
997
+ arguments: [tx.object(LENDING_MARKET_ID)]
998
+ });
759
999
  capRef = newCap;
760
1000
  } else {
761
1001
  capRef = caps[0].id;
762
1002
  }
763
1003
  const allCoins = await this.fetchAllCoins(address, USDC_TYPE2);
764
- if (allCoins.length === 0) {
765
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
766
- }
1004
+ if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
767
1005
  const primaryCoinId = allCoins[0].coinObjectId;
768
1006
  if (allCoins.length > 1) {
769
- tx.mergeCoins(
770
- tx.object(primaryCoinId),
771
- allCoins.slice(1).map((c) => tx.object(c.coinObjectId))
772
- );
1007
+ tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
773
1008
  }
1009
+ const rawAmount = usdcToRaw(amount).toString();
774
1010
  const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
775
1011
  if (options?.collectFee) {
776
1012
  addCollectFeeToTx(tx, depositCoin, "save");
777
1013
  }
778
- this.suilend.deposit(depositCoin, USDC_TYPE2, capRef, tx);
1014
+ const [ctokens] = tx.moveCall({
1015
+ target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
1016
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1017
+ arguments: [
1018
+ tx.object(LENDING_MARKET_ID),
1019
+ tx.pure.u64(usdcReserve.arrayIndex),
1020
+ tx.object(CLOCK2),
1021
+ depositCoin
1022
+ ]
1023
+ });
1024
+ tx.moveCall({
1025
+ target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
1026
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1027
+ arguments: [
1028
+ tx.object(LENDING_MARKET_ID),
1029
+ tx.pure.u64(usdcReserve.arrayIndex),
1030
+ typeof capRef === "string" ? tx.object(capRef) : capRef,
1031
+ tx.object(CLOCK2),
1032
+ ctokens
1033
+ ]
1034
+ });
779
1035
  return { tx };
780
1036
  }
781
1037
  async buildWithdrawTx(address, amount, _asset) {
782
- await this.ensureInit();
783
- const caps = await this.getObligationCaps(address);
784
- if (caps.length === 0) {
785
- throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
786
- }
1038
+ const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
1039
+ const usdcReserve = this.findReserve(reserves, "USDC");
1040
+ if (!usdcReserve) throw new T2000Error("ASSET_NOT_SUPPORTED", "USDC reserve not found on Suilend");
1041
+ const caps = await this.fetchObligationCaps(address);
1042
+ if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
787
1043
  const positions = await this.getPositions(address);
788
- const usdcSupply = positions.supplies.find((s) => s.asset === "USDC");
789
- const deposited = usdcSupply?.amount ?? 0;
1044
+ const deposited = positions.supplies.find((s) => s.asset === "USDC")?.amount ?? 0;
790
1045
  const effectiveAmount = Math.min(amount, deposited);
791
- if (effectiveAmount <= 0) {
792
- throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw from Suilend");
793
- }
794
- const rawAmount = usdcToRaw(effectiveAmount).toString();
1046
+ if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", "Nothing to withdraw from Suilend");
1047
+ const ratio = cTokenRatio(usdcReserve);
1048
+ const ctokenAmount = Math.ceil(effectiveAmount * 10 ** usdcReserve.mintDecimals / ratio);
795
1049
  const tx = new Transaction();
796
1050
  tx.setSender(address);
797
- await this.suilend.withdrawAndSendToUser(
798
- address,
799
- caps[0].id,
800
- caps[0].obligationId,
801
- USDC_TYPE2,
802
- rawAmount,
803
- tx
804
- );
1051
+ const [ctokens] = tx.moveCall({
1052
+ target: `${pkg}::lending_market::withdraw_ctokens`,
1053
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1054
+ arguments: [
1055
+ tx.object(LENDING_MARKET_ID),
1056
+ tx.pure.u64(usdcReserve.arrayIndex),
1057
+ tx.object(caps[0].id),
1058
+ tx.object(CLOCK2),
1059
+ tx.pure.u64(ctokenAmount)
1060
+ ]
1061
+ });
1062
+ const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${USDC_TYPE2}>`;
1063
+ const [none] = tx.moveCall({
1064
+ target: "0x1::option::none",
1065
+ typeArguments: [exemptionType]
1066
+ });
1067
+ const [coin] = tx.moveCall({
1068
+ target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity`,
1069
+ typeArguments: [LENDING_MARKET_TYPE, USDC_TYPE2],
1070
+ arguments: [
1071
+ tx.object(LENDING_MARKET_ID),
1072
+ tx.pure.u64(usdcReserve.arrayIndex),
1073
+ tx.object(CLOCK2),
1074
+ ctokens,
1075
+ none
1076
+ ]
1077
+ });
1078
+ tx.transferObjects([coin], address);
805
1079
  return { tx, effectiveAmount };
806
1080
  }
807
1081
  async buildBorrowTx(_address, _amount, _asset, _options) {
808
- throw new T2000Error(
809
- "ASSET_NOT_SUPPORTED",
810
- "SuilendAdapter.buildBorrowTx() not available \u2014 Suilend requires different collateral/borrow assets. Deferred to Phase 10."
811
- );
1082
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend borrow requires different collateral/borrow assets. Deferred to Phase 10.");
812
1083
  }
813
1084
  async buildRepayTx(_address, _amount, _asset) {
814
- throw new T2000Error(
815
- "ASSET_NOT_SUPPORTED",
816
- "SuilendAdapter.buildRepayTx() not available \u2014 deferred to Phase 10."
817
- );
1085
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend repay deferred to Phase 10.");
818
1086
  }
819
1087
  async maxWithdraw(address, _asset) {
820
- await this.ensureInit();
821
1088
  const health = await this.getHealth(address);
822
1089
  let maxAmount;
823
1090
  if (health.borrowed === 0) {
824
1091
  maxAmount = health.supplied;
825
1092
  } else {
826
- maxAmount = Math.max(
827
- 0,
828
- health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold
829
- );
1093
+ maxAmount = Math.max(0, health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold);
830
1094
  }
831
1095
  const remainingSupply = health.supplied - maxAmount;
832
1096
  const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
833
- return {
834
- maxAmount,
835
- healthFactorAfter: hfAfter,
836
- currentHF: health.healthFactor
837
- };
1097
+ return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
838
1098
  }
839
1099
  async maxBorrow(_address, _asset) {
840
- throw new T2000Error(
841
- "ASSET_NOT_SUPPORTED",
842
- "SuilendAdapter.maxBorrow() not available \u2014 deferred to Phase 10."
843
- );
1100
+ throw new T2000Error("ASSET_NOT_SUPPORTED", "Suilend maxBorrow deferred to Phase 10.");
844
1101
  }
845
1102
  async fetchAllCoins(owner, coinType) {
846
1103
  const all = [];