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