@t2000/sdk 0.18.9 → 0.18.11

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,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var transactions = require('@mysten/sui/transactions');
4
- var bcs = require('@mysten/sui/bcs');
4
+ var lending = require('@naviprotocol/lending');
5
5
  var aggregatorSdk = require('@cetusprotocol/aggregator-sdk');
6
6
  var utils = require('@mysten/sui/utils');
7
+ var client = require('@suilend/sdk/client');
8
+ var initialize = require('@suilend/sdk/lib/initialize');
9
+ var types = require('@suilend/sdk/lib/types');
10
+ require('@mysten/sui/bcs');
7
11
 
8
12
  // src/constants.ts
9
13
  var SAVE_FEE_BPS = 10n;
@@ -282,79 +286,34 @@ function addCollectFeeToTx(tx, paymentCoin, operation) {
282
286
  ]
283
287
  });
284
288
  }
285
- var RATE_DECIMALS = 27;
286
- var LTV_DECIMALS = 27;
289
+
290
+ // src/protocols/navi.ts
287
291
  var MIN_HEALTH_FACTOR = 1.5;
288
- function withdrawDustBuffer(decimals) {
289
- return 1e3 / 10 ** decimals;
290
- }
291
- var CLOCK = "0x06";
292
- var SUI_SYSTEM_STATE = "0x05";
293
- var NAVI_BALANCE_DECIMALS = 9;
294
- var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
295
- var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
296
- var PACKAGE_API = "https://open-api.naviprotocol.io/api/package";
297
- var packageCache = null;
298
- function toBigInt(v) {
299
- if (typeof v === "bigint") return v;
300
- return BigInt(String(v));
301
- }
302
- var UserStateInfo = bcs.bcs.struct("UserStateInfo", {
303
- asset_id: bcs.bcs.u8(),
304
- borrow_balance: bcs.bcs.u256(),
305
- supply_balance: bcs.bcs.u256()
306
- });
307
- function decodeDevInspect(result, schema) {
308
- const rv = result.results?.[0]?.returnValues?.[0];
309
- if (result.error || !rv) return void 0;
310
- const bytes = Uint8Array.from(rv[0]);
311
- return schema.parse(bytes);
312
- }
313
- var configCache = null;
314
- var poolsCache = null;
315
- var CACHE_TTL = 5 * 6e4;
316
- async function fetchJson(url) {
317
- const res = await fetch(url);
318
- if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI API error: ${res.status}`);
319
- const json = await res.json();
320
- return json.data ?? json;
321
- }
322
- async function getLatestPackageId() {
323
- if (packageCache && Date.now() - packageCache.ts < CACHE_TTL) return packageCache.id;
324
- const res = await fetch(PACKAGE_API);
325
- if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI package API error: ${res.status}`);
326
- const json = await res.json();
327
- if (!json.packageId) throw new T2000Error("PROTOCOL_UNAVAILABLE", "NAVI package API returned no packageId");
328
- packageCache = { id: json.packageId, ts: Date.now() };
329
- return json.packageId;
330
- }
331
- async function getConfig(fresh = false) {
332
- if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
333
- const [data, latestPkg] = await Promise.all([
334
- fetchJson(CONFIG_API),
335
- getLatestPackageId()
336
- ]);
337
- data.package = latestPkg;
338
- configCache = { data, ts: Date.now() };
339
- return data;
340
- }
341
- async function getPools(fresh = false) {
342
- if (poolsCache && !fresh && Date.now() - poolsCache.ts < CACHE_TTL) return poolsCache.data;
343
- const data = await fetchJson(POOLS_API);
344
- poolsCache = { data, ts: Date.now() };
345
- return data;
346
- }
347
- function matchesCoinType(poolType, targetType) {
348
- const poolSuffix = poolType.split("::").slice(1).join("::").toLowerCase();
349
- const targetSuffix = targetType.split("::").slice(1).join("::").toLowerCase();
350
- return poolSuffix === targetSuffix;
292
+ var NAVI_SUPPORTED_ASSETS = [...STABLE_ASSETS, "SUI", "ETH", "GOLD"];
293
+ function sdkOptions(client) {
294
+ return { env: "prod", client };
351
295
  }
352
- function resolvePoolSymbol(pool) {
353
- const coinType = pool.suiCoinType || pool.coinType || "";
296
+ var NAVI_SYMBOL_MAP = {
297
+ nUSDC: "USDC",
298
+ suiUSDT: "USDT",
299
+ suiUSDe: "USDe",
300
+ XAUM: "GOLD",
301
+ WBTC: "BTC",
302
+ suiETH: "ETH",
303
+ WETH: "ETH",
304
+ SUI: "SUI",
305
+ USDC: "USDC",
306
+ USDT: "USDT",
307
+ USDe: "USDe",
308
+ USDsui: "USDsui"
309
+ };
310
+ function resolveNaviSymbol(sdkSymbol, coinType) {
354
311
  for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
355
- if (matchesCoinType(coinType, info.type)) return key;
312
+ const poolSuffix = coinType.split("::").slice(1).join("::").toLowerCase();
313
+ const targetSuffix = info.type.split("::").slice(1).join("::").toLowerCase();
314
+ if (poolSuffix === targetSuffix) return key;
356
315
  }
357
- return pool.token?.symbol ?? "UNKNOWN";
316
+ return NAVI_SYMBOL_MAP[sdkSymbol] ?? sdkSymbol;
358
317
  }
359
318
  function resolveAssetInfo(asset) {
360
319
  if (asset in SUPPORTED_ASSETS) {
@@ -363,109 +322,6 @@ function resolveAssetInfo(asset) {
363
322
  }
364
323
  throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
365
324
  }
366
- async function getPool(asset = "USDC") {
367
- const pools = await getPools();
368
- const { type: targetType, displayName } = resolveAssetInfo(asset);
369
- const pool = pools.find(
370
- (p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType)
371
- );
372
- if (!pool) {
373
- throw new T2000Error(
374
- "ASSET_NOT_SUPPORTED",
375
- `${displayName} pool not found on NAVI`
376
- );
377
- }
378
- return pool;
379
- }
380
- function addOracleUpdate(tx, config, pool) {
381
- const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
382
- if (!feed) {
383
- throw new T2000Error("PROTOCOL_UNAVAILABLE", `Oracle feed not found for asset ${pool.token?.symbol ?? pool.id}`);
384
- }
385
- tx.moveCall({
386
- target: `${config.oracle.packageId}::oracle_pro::update_single_price_v2`,
387
- arguments: [
388
- tx.object(CLOCK),
389
- tx.object(config.oracle.oracleConfig),
390
- tx.object(config.oracle.priceOracle),
391
- tx.object(config.oracle.supraOracleHolder),
392
- tx.object(feed.pythPriceInfoObject),
393
- tx.object(config.oracle.switchboardAggregator),
394
- tx.pure.address(feed.feedId)
395
- ]
396
- });
397
- }
398
- function refreshOracles(tx, config, pools, opts) {
399
- const assetsToRefresh = NAVI_SUPPORTED_ASSETS;
400
- const targetTypes = assetsToRefresh.map((a) => SUPPORTED_ASSETS[a].type);
401
- const matchedPools = pools.filter((p) => {
402
- const ct = p.suiCoinType || p.coinType || "";
403
- return targetTypes.some((t) => matchesCoinType(ct, t));
404
- });
405
- for (const pool of matchedPools) {
406
- addOracleUpdate(tx, config, pool);
407
- }
408
- }
409
- function rateToApy(rawRate) {
410
- if (!rawRate || rawRate === "0") return 0;
411
- return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
412
- }
413
- function poolSaveApy(pool) {
414
- const incentive = parseFloat(pool.supplyIncentiveApyInfo?.apy ?? "0");
415
- if (incentive > 0) return incentive;
416
- return rateToApy(pool.currentSupplyRate);
417
- }
418
- function poolBorrowApy(pool) {
419
- const incentive = parseFloat(pool.borrowIncentiveApyInfo?.apy ?? "0");
420
- if (incentive > 0) return incentive;
421
- return rateToApy(pool.currentBorrowRate);
422
- }
423
- function parseLtv(rawLtv) {
424
- if (!rawLtv || rawLtv === "0") return 0.75;
425
- return Number(BigInt(rawLtv)) / 10 ** LTV_DECIMALS;
426
- }
427
- function parseLiqThreshold(val) {
428
- if (typeof val === "number") return val;
429
- const n = Number(val);
430
- if (n > 1) return Number(BigInt(val)) / 10 ** LTV_DECIMALS;
431
- return n;
432
- }
433
- function normalizeHealthFactor(raw) {
434
- const v = raw / 10 ** RATE_DECIMALS;
435
- return v > 1e5 ? Infinity : v;
436
- }
437
- function naviStorageDecimals(poolId, tokenDecimals) {
438
- if (poolId <= 10) return NAVI_BALANCE_DECIMALS;
439
- return tokenDecimals;
440
- }
441
- function compoundBalance(rawBalance, currentIndex, pool) {
442
- if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
443
- const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
444
- const half = scale / 2n;
445
- const result = (rawBalance * BigInt(currentIndex) + half) / scale;
446
- const decimals = pool ? naviStorageDecimals(pool.id, pool.token.decimals) : NAVI_BALANCE_DECIMALS;
447
- return Number(result) / 10 ** decimals;
448
- }
449
- async function getUserState(client, address) {
450
- const config = await getConfig();
451
- const tx = new transactions.Transaction();
452
- tx.moveCall({
453
- target: `${config.uiGetter}::getter_unchecked::get_user_state`,
454
- arguments: [tx.object(config.storage), tx.pure.address(address)]
455
- });
456
- const result = await client.devInspectTransactionBlock({
457
- transactionBlock: tx,
458
- sender: address
459
- });
460
- const decoded = decodeDevInspect(result, bcs.bcs.vector(UserStateInfo));
461
- if (!decoded) return [];
462
- const mapped = decoded.map((s) => ({
463
- assetId: s.asset_id,
464
- supplyBalance: toBigInt(s.supply_balance),
465
- borrowBalance: toBigInt(s.borrow_balance)
466
- }));
467
- return mapped.filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
468
- }
469
325
  async function fetchCoins(client, owner, coinType) {
470
326
  const all = [];
471
327
  let cursor;
@@ -486,14 +342,98 @@ function mergeCoins(tx, coins) {
486
342
  }
487
343
  return primary;
488
344
  }
345
+ async function getPositions(client, addressOrKeypair) {
346
+ const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
347
+ try {
348
+ const naviPositions = await lending.getLendingPositions(address, {
349
+ ...sdkOptions(client),
350
+ markets: ["main"]
351
+ });
352
+ const positions = [];
353
+ for (const pos of naviPositions) {
354
+ const data = pos["navi-lending-supply"] ?? pos["navi-lending-emode-supply"] ?? pos["navi-lending-borrow"] ?? pos["navi-lending-emode-borrow"];
355
+ if (!data) continue;
356
+ const isBorrow = pos.type.includes("borrow");
357
+ const symbol = resolveNaviSymbol(data.token.symbol, data.token.coinType);
358
+ const amount = parseFloat(data.amount);
359
+ const amountUsd = parseFloat(data.valueUSD);
360
+ const pool = data.pool;
361
+ const apy = isBorrow ? parseFloat(pool.borrowIncentiveApyInfo?.apy ?? "0") : parseFloat(pool.supplyIncentiveApyInfo?.apy ?? "0");
362
+ if (amount > 1e-4 || amountUsd > 1e-3) {
363
+ positions.push({
364
+ protocol: "navi",
365
+ asset: symbol,
366
+ type: isBorrow ? "borrow" : "save",
367
+ amount,
368
+ amountUsd,
369
+ apy
370
+ });
371
+ }
372
+ }
373
+ return { positions };
374
+ } catch (err) {
375
+ const msg = err instanceof Error ? err.message : String(err);
376
+ if (msg.includes("not found") || msg.includes("404")) return { positions: [] };
377
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI getPositions failed: ${msg}`);
378
+ }
379
+ }
380
+ async function getRates(client) {
381
+ try {
382
+ const pools = await lending.getPools(sdkOptions(client));
383
+ const result = {};
384
+ for (const asset of NAVI_SUPPORTED_ASSETS) {
385
+ const targetType = SUPPORTED_ASSETS[asset].type;
386
+ const pool = pools.find((p) => {
387
+ const poolSuffix = (p.suiCoinType || p.coinType || "").split("::").slice(1).join("::").toLowerCase();
388
+ const targetSuffix = targetType.split("::").slice(1).join("::").toLowerCase();
389
+ return poolSuffix === targetSuffix;
390
+ });
391
+ if (!pool) continue;
392
+ const saveApy = parseFloat(pool.supplyIncentiveApyInfo?.apy ?? "0");
393
+ const borrowApy = parseFloat(pool.borrowIncentiveApyInfo?.apy ?? "0");
394
+ if (saveApy >= 0 && saveApy < 200) {
395
+ result[asset] = { saveApy, borrowApy: borrowApy >= 0 && borrowApy < 200 ? borrowApy : 0 };
396
+ }
397
+ }
398
+ if (!result.USDC) result.USDC = { saveApy: 4, borrowApy: 6 };
399
+ return result;
400
+ } catch {
401
+ return { USDC: { saveApy: 4, borrowApy: 6 } };
402
+ }
403
+ }
404
+ async function getHealthFactor(client, addressOrKeypair) {
405
+ const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
406
+ const posResult = await getPositions(client, address);
407
+ let supplied = 0;
408
+ let borrowed = 0;
409
+ for (const pos of posResult.positions) {
410
+ const usd = pos.amountUsd ?? pos.amount;
411
+ if (pos.type === "save") supplied += usd;
412
+ else if (pos.type === "borrow") borrowed += usd;
413
+ }
414
+ let healthFactor;
415
+ try {
416
+ const hf = await lending.getHealthFactor(address, sdkOptions(client));
417
+ healthFactor = hf > 1e5 ? Infinity : hf;
418
+ } catch {
419
+ healthFactor = borrowed > 0 ? supplied * 0.75 / borrowed : Infinity;
420
+ }
421
+ const ltv = 0.75;
422
+ const maxBorrow = Math.max(0, supplied * ltv - borrowed);
423
+ return {
424
+ healthFactor,
425
+ supplied,
426
+ borrowed,
427
+ maxBorrow,
428
+ liquidationThreshold: ltv
429
+ };
430
+ }
489
431
  async function buildSaveTx(client, address, amount, options = {}) {
490
432
  if (!amount || amount <= 0 || !Number.isFinite(amount)) {
491
433
  throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
492
434
  }
493
435
  const asset = options.asset ?? "USDC";
494
436
  const assetInfo = resolveAssetInfo(asset);
495
- const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
496
- const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
497
437
  const coins = await fetchCoins(client, address, assetInfo.type);
498
438
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
499
439
  const tx = new transactions.Transaction();
@@ -502,159 +442,103 @@ async function buildSaveTx(client, address, amount, options = {}) {
502
442
  if (options.collectFee) {
503
443
  addCollectFeeToTx(tx, coinObj, "save");
504
444
  }
505
- tx.moveCall({
506
- target: `${config.package}::incentive_v3::entry_deposit`,
507
- arguments: [
508
- tx.object(CLOCK),
509
- tx.object(config.storage),
510
- tx.object(pool.contract.pool),
511
- tx.pure.u8(pool.id),
512
- coinObj,
513
- tx.pure.u64(rawAmount),
514
- tx.object(config.incentiveV2),
515
- tx.object(config.incentiveV3)
516
- ],
517
- typeArguments: [pool.suiCoinType]
518
- });
445
+ const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
446
+ try {
447
+ await lending.depositCoinPTB(tx, assetInfo.type, coinObj, {
448
+ ...sdkOptions(client),
449
+ amount: rawAmount
450
+ });
451
+ } catch (err) {
452
+ const msg = err instanceof Error ? err.message : String(err);
453
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI deposit failed: ${msg}`);
454
+ }
519
455
  return tx;
520
456
  }
521
457
  async function buildWithdrawTx(client, address, amount, options = {}) {
522
458
  const asset = options.asset ?? "USDC";
523
459
  const assetInfo = resolveAssetInfo(asset);
524
- const [config, pool, pools, states] = await Promise.all([
525
- getConfig(),
526
- getPool(asset),
527
- getPools(),
528
- getUserState(client, address)
529
- ]);
530
- const assetState = states.find((s) => s.assetId === pool.id);
531
- const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex, pool) : 0;
532
- const effectiveAmount = Math.min(amount, Math.max(0, deposited - withdrawDustBuffer(assetInfo.decimals)));
460
+ const posResult = await getPositions(client, address);
461
+ const supply = posResult.positions.find(
462
+ (p) => p.type === "save" && p.asset === asset
463
+ );
464
+ const deposited = supply?.amount ?? 0;
465
+ const dustBuffer = 1e3 / 10 ** assetInfo.decimals;
466
+ const effectiveAmount = Math.min(amount, Math.max(0, deposited - dustBuffer));
533
467
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
534
468
  const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
535
469
  if (rawAmount <= 0) {
536
- throw new T2000Error("INVALID_AMOUNT", `Withdrawal amount rounds to zero \u2014 balance is dust`);
470
+ throw new T2000Error("INVALID_AMOUNT", "Withdrawal amount rounds to zero \u2014 balance is dust");
537
471
  }
538
472
  const tx = new transactions.Transaction();
539
473
  tx.setSender(address);
540
- refreshOracles(tx, config, pools);
541
- const [balance] = tx.moveCall({
542
- target: `${config.package}::incentive_v3::withdraw_v2`,
543
- arguments: [
544
- tx.object(CLOCK),
545
- tx.object(config.oracle.priceOracle),
546
- tx.object(config.storage),
547
- tx.object(pool.contract.pool),
548
- tx.pure.u8(pool.id),
549
- tx.pure.u64(rawAmount),
550
- tx.object(config.incentiveV2),
551
- tx.object(config.incentiveV3),
552
- tx.object(SUI_SYSTEM_STATE)
553
- ],
554
- typeArguments: [pool.suiCoinType]
555
- });
556
- const [coin] = tx.moveCall({
557
- target: "0x2::coin::from_balance",
558
- arguments: [balance],
559
- typeArguments: [pool.suiCoinType]
560
- });
561
- tx.transferObjects([coin], address);
474
+ try {
475
+ const coinResult = await lending.withdrawCoinPTB(tx, assetInfo.type, rawAmount, sdkOptions(client));
476
+ const [coin] = tx.moveCall({
477
+ target: "0x2::coin::from_balance",
478
+ arguments: [coinResult],
479
+ typeArguments: [assetInfo.type]
480
+ });
481
+ tx.transferObjects([coin], address);
482
+ } catch (err) {
483
+ const msg = err instanceof Error ? err.message : String(err);
484
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI withdraw failed: ${msg}`);
485
+ }
562
486
  return { tx, effectiveAmount };
563
487
  }
564
488
  async function addWithdrawToTx(tx, client, address, amount, options = {}) {
565
489
  const asset = options.asset ?? "USDC";
566
490
  const assetInfo = resolveAssetInfo(asset);
567
- const [config, pool, pools, states] = await Promise.all([
568
- getConfig(),
569
- getPool(asset),
570
- getPools(),
571
- getUserState(client, address)
572
- ]);
573
- const assetState = states.find((s) => s.assetId === pool.id);
574
- const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex, pool) : 0;
575
- const effectiveAmount = Math.min(amount, Math.max(0, deposited - withdrawDustBuffer(assetInfo.decimals)));
491
+ const posResult = await getPositions(client, address);
492
+ const supply = posResult.positions.find(
493
+ (p) => p.type === "save" && p.asset === asset
494
+ );
495
+ const deposited = supply?.amount ?? 0;
496
+ const dustBuffer = 1e3 / 10 ** assetInfo.decimals;
497
+ const effectiveAmount = Math.min(amount, Math.max(0, deposited - dustBuffer));
576
498
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
577
499
  const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
578
500
  if (rawAmount <= 0) {
579
- const [coin2] = tx.moveCall({
501
+ const [coin] = tx.moveCall({
580
502
  target: "0x2::coin::zero",
581
- typeArguments: [pool.suiCoinType]
503
+ typeArguments: [assetInfo.type]
582
504
  });
583
- return { coin: coin2, effectiveAmount: 0 };
505
+ return { coin, effectiveAmount: 0 };
506
+ }
507
+ try {
508
+ const coinResult = await lending.withdrawCoinPTB(tx, assetInfo.type, rawAmount, sdkOptions(client));
509
+ const [coin] = tx.moveCall({
510
+ target: "0x2::coin::from_balance",
511
+ arguments: [coinResult],
512
+ typeArguments: [assetInfo.type]
513
+ });
514
+ return { coin, effectiveAmount };
515
+ } catch (err) {
516
+ const msg = err instanceof Error ? err.message : String(err);
517
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI withdraw failed: ${msg}`);
584
518
  }
585
- refreshOracles(tx, config, pools);
586
- const [balance] = tx.moveCall({
587
- target: `${config.package}::incentive_v3::withdraw_v2`,
588
- arguments: [
589
- tx.object(CLOCK),
590
- tx.object(config.oracle.priceOracle),
591
- tx.object(config.storage),
592
- tx.object(pool.contract.pool),
593
- tx.pure.u8(pool.id),
594
- tx.pure.u64(rawAmount),
595
- tx.object(config.incentiveV2),
596
- tx.object(config.incentiveV3),
597
- tx.object(SUI_SYSTEM_STATE)
598
- ],
599
- typeArguments: [pool.suiCoinType]
600
- });
601
- const [coin] = tx.moveCall({
602
- target: "0x2::coin::from_balance",
603
- arguments: [balance],
604
- typeArguments: [pool.suiCoinType]
605
- });
606
- return { coin, effectiveAmount };
607
519
  }
608
520
  async function addSaveToTx(tx, _client, _address, coin, options = {}) {
609
521
  const asset = options.asset ?? "USDC";
610
- const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
522
+ const assetInfo = resolveAssetInfo(asset);
611
523
  if (options.collectFee) {
612
524
  addCollectFeeToTx(tx, coin, "save");
613
525
  }
614
- const [coinValue] = tx.moveCall({
615
- target: "0x2::coin::value",
616
- typeArguments: [pool.suiCoinType],
617
- arguments: [coin]
618
- });
619
- tx.moveCall({
620
- target: `${config.package}::incentive_v3::entry_deposit`,
621
- arguments: [
622
- tx.object(CLOCK),
623
- tx.object(config.storage),
624
- tx.object(pool.contract.pool),
625
- tx.pure.u8(pool.id),
626
- coin,
627
- coinValue,
628
- tx.object(config.incentiveV2),
629
- tx.object(config.incentiveV3)
630
- ],
631
- typeArguments: [pool.suiCoinType]
632
- });
526
+ try {
527
+ await lending.depositCoinPTB(tx, assetInfo.type, coin, { env: "prod" });
528
+ } catch (err) {
529
+ const msg = err instanceof Error ? err.message : String(err);
530
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI deposit failed: ${msg}`);
531
+ }
633
532
  }
634
533
  async function addRepayToTx(tx, _client, _address, coin, options = {}) {
635
534
  const asset = options.asset ?? "USDC";
636
- const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
637
- addOracleUpdate(tx, config, pool);
638
- const [coinValue] = tx.moveCall({
639
- target: "0x2::coin::value",
640
- typeArguments: [pool.suiCoinType],
641
- arguments: [coin]
642
- });
643
- tx.moveCall({
644
- target: `${config.package}::incentive_v3::entry_repay`,
645
- arguments: [
646
- tx.object(CLOCK),
647
- tx.object(config.oracle.priceOracle),
648
- tx.object(config.storage),
649
- tx.object(pool.contract.pool),
650
- tx.pure.u8(pool.id),
651
- coin,
652
- coinValue,
653
- tx.object(config.incentiveV2),
654
- tx.object(config.incentiveV3)
655
- ],
656
- typeArguments: [pool.suiCoinType]
657
- });
535
+ const assetInfo = resolveAssetInfo(asset);
536
+ try {
537
+ await lending.repayCoinPTB(tx, assetInfo.type, coin, { env: "prod" });
538
+ } catch (err) {
539
+ const msg = err instanceof Error ? err.message : String(err);
540
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI repay failed: ${msg}`);
541
+ }
658
542
  }
659
543
  async function buildBorrowTx(client, address, amount, options = {}) {
660
544
  if (!amount || amount <= 0 || !Number.isFinite(amount)) {
@@ -663,38 +547,23 @@ async function buildBorrowTx(client, address, amount, options = {}) {
663
547
  const asset = options.asset ?? "USDC";
664
548
  const assetInfo = resolveAssetInfo(asset);
665
549
  const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
666
- const [config, pool, pools] = await Promise.all([
667
- getConfig(),
668
- getPool(asset),
669
- getPools()
670
- ]);
671
550
  const tx = new transactions.Transaction();
672
551
  tx.setSender(address);
673
- refreshOracles(tx, config, pools);
674
- const [balance] = tx.moveCall({
675
- target: `${config.package}::incentive_v3::borrow_v2`,
676
- arguments: [
677
- tx.object(CLOCK),
678
- tx.object(config.oracle.priceOracle),
679
- tx.object(config.storage),
680
- tx.object(pool.contract.pool),
681
- tx.pure.u8(pool.id),
682
- tx.pure.u64(rawAmount),
683
- tx.object(config.incentiveV2),
684
- tx.object(config.incentiveV3),
685
- tx.object(SUI_SYSTEM_STATE)
686
- ],
687
- typeArguments: [pool.suiCoinType]
688
- });
689
- const [borrowedCoin] = tx.moveCall({
690
- target: "0x2::coin::from_balance",
691
- arguments: [balance],
692
- typeArguments: [pool.suiCoinType]
693
- });
694
- if (options.collectFee) {
695
- addCollectFeeToTx(tx, borrowedCoin, "borrow");
552
+ try {
553
+ const coinResult = await lending.borrowCoinPTB(tx, assetInfo.type, rawAmount, sdkOptions(client));
554
+ const [borrowedCoin] = tx.moveCall({
555
+ target: "0x2::coin::from_balance",
556
+ arguments: [coinResult],
557
+ typeArguments: [assetInfo.type]
558
+ });
559
+ if (options.collectFee) {
560
+ addCollectFeeToTx(tx, borrowedCoin, "borrow");
561
+ }
562
+ tx.transferObjects([borrowedCoin], address);
563
+ } catch (err) {
564
+ const msg = err instanceof Error ? err.message : String(err);
565
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI borrow failed: ${msg}`);
696
566
  }
697
- tx.transferObjects([borrowedCoin], address);
698
567
  return tx;
699
568
  }
700
569
  async function buildRepayTx(client, address, amount, options = {}) {
@@ -703,163 +572,23 @@ async function buildRepayTx(client, address, amount, options = {}) {
703
572
  }
704
573
  const asset = options.asset ?? "USDC";
705
574
  const assetInfo = resolveAssetInfo(asset);
706
- const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
707
- const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
708
575
  const coins = await fetchCoins(client, address, assetInfo.type);
709
576
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
710
577
  const tx = new transactions.Transaction();
711
578
  tx.setSender(address);
712
- addOracleUpdate(tx, config, pool);
713
579
  const coinObj = mergeCoins(tx, coins);
714
- tx.moveCall({
715
- target: `${config.package}::incentive_v3::entry_repay`,
716
- arguments: [
717
- tx.object(CLOCK),
718
- tx.object(config.oracle.priceOracle),
719
- tx.object(config.storage),
720
- tx.object(pool.contract.pool),
721
- tx.pure.u8(pool.id),
722
- coinObj,
723
- tx.pure.u64(rawAmount),
724
- tx.object(config.incentiveV2),
725
- tx.object(config.incentiveV3)
726
- ],
727
- typeArguments: [pool.suiCoinType]
728
- });
729
- return tx;
730
- }
731
- async function getHealthFactor(client, addressOrKeypair) {
732
- const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
733
- const [config, pools, states] = await Promise.all([
734
- getConfig(),
735
- getPools(),
736
- getUserState(client, address)
737
- ]);
738
- let supplied = 0;
739
- let borrowed = 0;
740
- let weightedLtv = 0;
741
- let weightedLiqThreshold = 0;
742
- for (const state of states) {
743
- const pool = pools.find((p) => p.id === state.assetId);
744
- if (!pool) continue;
745
- const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex, pool);
746
- const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex, pool);
747
- const price = pool.token?.price ?? 1;
748
- supplied += supplyBal * price;
749
- borrowed += borrowBal * price;
750
- if (supplyBal > 0) {
751
- weightedLtv += supplyBal * price * parseLtv(pool.ltv);
752
- weightedLiqThreshold += supplyBal * price * parseLiqThreshold(pool.liquidationFactor.threshold);
753
- }
754
- }
755
- const ltv = supplied > 0 ? weightedLtv / supplied : 0.75;
756
- const liqThreshold = supplied > 0 ? weightedLiqThreshold / supplied : 0.75;
757
- const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
758
- const usdcPool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", SUPPORTED_ASSETS.USDC.type));
759
- let healthFactor;
760
- if (borrowed <= 0) {
761
- healthFactor = Infinity;
762
- } else if (usdcPool) {
763
- try {
764
- const tx = new transactions.Transaction();
765
- tx.moveCall({
766
- target: `${config.uiGetter}::calculator_unchecked::dynamic_health_factor`,
767
- arguments: [
768
- tx.object(CLOCK),
769
- tx.object(config.storage),
770
- tx.object(config.oracle.priceOracle),
771
- tx.pure.u8(usdcPool.id),
772
- tx.pure.address(address),
773
- tx.pure.u8(usdcPool.id),
774
- tx.pure.u64(0),
775
- tx.pure.u64(0),
776
- tx.pure.bool(false)
777
- ],
778
- typeArguments: [usdcPool.suiCoinType]
779
- });
780
- const result = await client.devInspectTransactionBlock({
781
- transactionBlock: tx,
782
- sender: address
783
- });
784
- const decoded = decodeDevInspect(result, bcs.bcs.u256());
785
- if (decoded !== void 0) {
786
- healthFactor = normalizeHealthFactor(Number(decoded));
787
- } else {
788
- healthFactor = supplied * liqThreshold / borrowed;
789
- }
790
- } catch {
791
- healthFactor = supplied * liqThreshold / borrowed;
792
- }
793
- } else {
794
- healthFactor = supplied * liqThreshold / borrowed;
795
- }
796
- return {
797
- healthFactor,
798
- supplied,
799
- borrowed,
800
- maxBorrow: maxBorrowVal,
801
- liquidationThreshold: liqThreshold
802
- };
803
- }
804
- var NAVI_SUPPORTED_ASSETS = [...STABLE_ASSETS, "SUI", "ETH", "GOLD"];
805
- async function getRates(client) {
580
+ const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
581
+ const [repayCoin] = tx.splitCoins(coinObj, [rawAmount]);
806
582
  try {
807
- const pools = await getPools();
808
- const result = {};
809
- for (const asset of NAVI_SUPPORTED_ASSETS) {
810
- const targetType = SUPPORTED_ASSETS[asset].type;
811
- const pool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType));
812
- if (!pool) continue;
813
- let saveApy = poolSaveApy(pool);
814
- let borrowApy = poolBorrowApy(pool);
815
- if (saveApy <= 0 || saveApy > 200) saveApy = 0;
816
- if (borrowApy <= 0 || borrowApy > 200) borrowApy = 0;
817
- result[asset] = { saveApy, borrowApy };
818
- }
819
- if (!result.USDC) result.USDC = { saveApy: 4, borrowApy: 6 };
820
- return result;
821
- } catch {
822
- return { USDC: { saveApy: 4, borrowApy: 6 } };
823
- }
824
- }
825
- async function getPositions(client, addressOrKeypair) {
826
- const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
827
- const [states, cachedPools] = await Promise.all([getUserState(client, address), getPools()]);
828
- let pools = cachedPools;
829
- const unmatchedIds = states.filter((s) => !pools.find((p) => p.id === s.assetId)).map((s) => s.assetId);
830
- if (unmatchedIds.length > 0) {
831
- pools = await getPools(true);
832
- }
833
- const positions = [];
834
- for (const state of states) {
835
- const pool = pools.find((p) => p.id === state.assetId);
836
- if (!pool) {
837
- console.warn(`[NAVI] No pool found for assetId=${state.assetId} (supply=${state.supplyBalance}, borrow=${state.borrowBalance}) \u2014 funds may be invisible`);
838
- continue;
839
- }
840
- const symbol = resolvePoolSymbol(pool);
841
- const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex, pool);
842
- const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex, pool);
843
- if (supplyBal > 1e-4) {
844
- positions.push({
845
- protocol: "navi",
846
- asset: symbol,
847
- type: "save",
848
- amount: supplyBal,
849
- apy: poolSaveApy(pool)
850
- });
851
- }
852
- if (borrowBal > 1e-4) {
853
- positions.push({
854
- protocol: "navi",
855
- asset: symbol,
856
- type: "borrow",
857
- amount: borrowBal,
858
- apy: poolBorrowApy(pool)
859
- });
860
- }
583
+ await lending.repayCoinPTB(tx, assetInfo.type, repayCoin, {
584
+ ...sdkOptions(client),
585
+ amount: rawAmount
586
+ });
587
+ } catch (err) {
588
+ const msg = err instanceof Error ? err.message : String(err);
589
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI repay failed: ${msg}`);
861
590
  }
862
- return { positions };
591
+ return tx;
863
592
  }
864
593
  async function maxWithdrawAmount(client, addressOrKeypair) {
865
594
  const hf = await getHealthFactor(client, addressOrKeypair);
@@ -880,164 +609,61 @@ async function maxBorrowAmount(client, addressOrKeypair) {
880
609
  const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
881
610
  return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR, currentHF: hf.healthFactor };
882
611
  }
883
- var CERT_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
884
- var DEEP_TYPE = "0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP";
885
- var REWARD_FUNDS = {
886
- [CERT_TYPE]: "0x7093cf7549d5e5b35bfde2177223d1050f71655c7f676a5e610ee70eb4d93b5c",
887
- [DEEP_TYPE]: "0xc889d78b634f954979e80e622a2ae0fece824c0f6d9590044378a2563035f32f"
888
- };
889
- var REWARD_SYMBOLS = {
890
- [CERT_TYPE]: "vSUI",
891
- [DEEP_TYPE]: "DEEP"
892
- };
893
- var incentiveRulesCache = null;
894
- async function getIncentiveRules(client) {
895
- if (incentiveRulesCache && Date.now() - incentiveRulesCache.ts < CACHE_TTL) {
896
- return incentiveRulesCache.data;
897
- }
898
- const [pools, obj] = await Promise.all([
899
- getPools(),
900
- client.getObject({
901
- id: "0x62982dad27fb10bb314b3384d5de8d2ac2d72ab2dbeae5d801dbdb9efa816c80",
902
- options: { showContent: true }
903
- })
904
- ]);
905
- const rewardCoinMap = /* @__PURE__ */ new Map();
906
- for (const pool of pools) {
907
- const ct = (pool.suiCoinType || pool.coinType || "").toLowerCase();
908
- const suffix = ct.split("::").slice(1).join("::");
909
- const coins = pool.supplyIncentiveApyInfo?.rewardCoin;
910
- if (Array.isArray(coins) && coins.length > 0) {
911
- rewardCoinMap.set(suffix, coins[0]);
912
- }
913
- }
914
- const result = /* @__PURE__ */ new Map();
915
- if (obj.data?.content?.dataType !== "moveObject") {
916
- incentiveRulesCache = { data: result, ts: Date.now() };
917
- return result;
918
- }
919
- const fields = obj.data.content.fields;
920
- const poolsObj = fields.pools;
921
- const entries = poolsObj?.fields?.contents;
922
- if (!Array.isArray(entries)) {
923
- incentiveRulesCache = { data: result, ts: Date.now() };
924
- return result;
925
- }
926
- for (const entry of entries) {
927
- const ef = entry?.fields;
928
- if (!ef) continue;
929
- const key = String(ef.key ?? "");
930
- const value = ef.value;
931
- const rules = value?.fields?.rules;
932
- const ruleEntries = rules?.fields?.contents;
933
- if (!Array.isArray(ruleEntries)) continue;
934
- const ruleIds = ruleEntries.map((re) => {
935
- const rf = re?.fields;
936
- return String(rf?.key ?? "");
937
- }).filter(Boolean);
938
- const suffix = key.split("::").slice(1).join("::").toLowerCase();
939
- const full = key.toLowerCase();
940
- const rewardCoin = rewardCoinMap.get(suffix) ?? rewardCoinMap.get(full) ?? null;
941
- result.set(key, { ruleIds, rewardCoinType: rewardCoin });
942
- }
943
- incentiveRulesCache = { data: result, ts: Date.now() };
944
- return result;
945
- }
946
- function stripPrefix(coinType) {
947
- return coinType.replace(/^0x0*/, "");
948
- }
949
612
  async function getPendingRewards(client, address) {
950
- const [pools, states] = await Promise.all([
951
- getPools(),
952
- getUserState(client, address)
953
- ]);
954
- const rewards = [];
955
- const deposited = states.filter((s) => s.supplyBalance > 0n);
956
- if (deposited.length === 0) return rewards;
957
- for (const state of deposited) {
958
- const pool = pools.find((p) => p.id === state.assetId);
959
- if (!pool) continue;
960
- const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
961
- if (boostedApr <= 0) continue;
962
- const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
963
- if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
964
- const rewardType = rewardCoins[0];
965
- const assetSymbol = resolvePoolSymbol(pool);
966
- rewards.push({
967
- protocol: "navi",
968
- asset: assetSymbol,
969
- coinType: rewardType,
970
- symbol: REWARD_SYMBOLS[rewardType] ?? rewardType.split("::").pop() ?? "UNKNOWN",
971
- amount: 0,
972
- estimatedValueUsd: 0
613
+ try {
614
+ const rewards = await lending.getUserAvailableLendingRewards(address, {
615
+ ...sdkOptions(client),
616
+ markets: ["main"]
973
617
  });
618
+ if (!rewards || rewards.length === 0) return [];
619
+ const summary = lending.summaryLendingRewards(rewards);
620
+ const result = [];
621
+ for (const s of summary) {
622
+ for (const rw of s.rewards) {
623
+ const available = Number(rw.available);
624
+ if (available <= 0) continue;
625
+ const symbol = rw.coinType.split("::").pop() ?? "UNKNOWN";
626
+ result.push({
627
+ protocol: "navi",
628
+ asset: String(s.assetId),
629
+ coinType: rw.coinType,
630
+ symbol,
631
+ amount: available,
632
+ estimatedValueUsd: 0
633
+ });
634
+ }
635
+ }
636
+ return result;
637
+ } catch {
638
+ return [];
974
639
  }
975
- return rewards;
976
640
  }
977
641
  async function addClaimRewardsToTx(tx, client, address) {
978
- const [config, pools, states, rules] = await Promise.all([
979
- getConfig(),
980
- getPools(),
981
- getUserState(client, address),
982
- getIncentiveRules(client)
983
- ]);
984
- const deposited = states.filter((s) => s.supplyBalance > 0n);
985
- if (deposited.length === 0) return [];
986
- const claimGroups = /* @__PURE__ */ new Map();
987
- for (const state of deposited) {
988
- const pool = pools.find((p) => p.id === state.assetId);
989
- if (!pool) continue;
990
- const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
991
- if (boostedApr <= 0) continue;
992
- const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
993
- if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
994
- const rewardType = rewardCoins[0];
995
- const fundId = REWARD_FUNDS[rewardType];
996
- if (!fundId) continue;
997
- const coinType = pool.suiCoinType || pool.coinType || "";
998
- const strippedType = stripPrefix(coinType);
999
- const ruleData = Array.from(rules.entries()).find(
1000
- ([key]) => stripPrefix(key) === strippedType || key.split("::").slice(1).join("::").toLowerCase() === coinType.split("::").slice(1).join("::").toLowerCase()
1001
- );
1002
- if (!ruleData || ruleData[1].ruleIds.length === 0) continue;
1003
- const group = claimGroups.get(rewardType) ?? { assets: [], ruleIds: [] };
1004
- for (const ruleId of ruleData[1].ruleIds) {
1005
- group.assets.push(strippedType);
1006
- group.ruleIds.push(ruleId);
1007
- }
1008
- claimGroups.set(rewardType, group);
1009
- }
1010
- const claimed = [];
1011
- for (const [rewardType, { assets, ruleIds }] of claimGroups) {
1012
- const fundId = REWARD_FUNDS[rewardType];
1013
- const [balance] = tx.moveCall({
1014
- target: `${config.package}::incentive_v3::claim_reward`,
1015
- typeArguments: [rewardType],
1016
- arguments: [
1017
- tx.object(CLOCK),
1018
- tx.object(config.incentiveV3),
1019
- tx.object(config.storage),
1020
- tx.object(fundId),
1021
- tx.pure(bcs.bcs.vector(bcs.bcs.string()).serialize(assets)),
1022
- tx.pure(bcs.bcs.vector(bcs.bcs.Address).serialize(ruleIds))
1023
- ]
642
+ try {
643
+ const rewards = await lending.getUserAvailableLendingRewards(address, {
644
+ ...sdkOptions(client),
645
+ markets: ["main"]
1024
646
  });
1025
- const [coin] = tx.moveCall({
1026
- target: "0x2::coin::from_balance",
1027
- typeArguments: [rewardType],
1028
- arguments: [balance]
647
+ if (!rewards || rewards.length === 0) return [];
648
+ const claimable = rewards.filter(
649
+ (r) => Number(r.userClaimableReward) > 0
650
+ );
651
+ if (claimable.length === 0) return [];
652
+ const claimed = await lending.claimLendingRewardsPTB(tx, claimable, {
653
+ env: "prod",
654
+ customCoinReceive: { type: "transfer", transfer: address }
1029
655
  });
1030
- tx.transferObjects([coin], address);
1031
- claimed.push({
656
+ return claimed.map((c) => ({
1032
657
  protocol: "navi",
1033
- asset: assets.join(", "),
1034
- coinType: rewardType,
1035
- symbol: REWARD_SYMBOLS[rewardType] ?? "UNKNOWN",
658
+ asset: "",
659
+ coinType: "",
660
+ symbol: "REWARD",
1036
661
  amount: 0,
1037
662
  estimatedValueUsd: 0
1038
- });
663
+ }));
664
+ } catch {
665
+ return [];
1039
666
  }
1040
- return claimed;
1041
667
  }
1042
668
 
1043
669
  // src/adapters/navi.ts
@@ -1081,8 +707,8 @@ var NaviAdapter = class {
1081
707
  async getPositions(address) {
1082
708
  const result = await getPositions(this.client, address);
1083
709
  return {
1084
- supplies: result.positions.filter((p) => p.type === "save").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy })),
1085
- borrows: result.positions.filter((p) => p.type === "borrow").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy }))
710
+ supplies: result.positions.filter((p) => p.type === "save").map((p) => ({ asset: p.asset, amount: p.amount, amountUsd: p.amountUsd, apy: p.apy })),
711
+ borrows: result.positions.filter((p) => p.type === "borrow").map((p) => ({ asset: p.asset, amount: p.amount, amountUsd: p.amountUsd, apy: p.apy }))
1086
712
  };
1087
713
  }
1088
714
  async getHealth(address) {
@@ -1363,15 +989,8 @@ var CetusAdapter = class {
1363
989
  });
1364
990
  }
1365
991
  };
1366
- var WAD = 1e18;
1367
- var MIN_HEALTH_FACTOR2 = 1.5;
1368
- var CLOCK2 = "0x6";
1369
- var SUI_SYSTEM_STATE2 = "0x5";
1370
- var LENDING_MARKET_ID = "0x84030d26d85eaa7035084a057f2f11f701b7e2e4eda87551becbc7c97505ece1";
1371
- var LENDING_MARKET_TYPE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf::suilend::MAIN_POOL";
1372
992
  var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
1373
- var UPGRADE_CAP_ID = "0x3d4ef1859c3ee9fc72858f588b56a09da5466e64f8cc4e90a7b3b909fba8a7ae";
1374
- var FALLBACK_PUBLISHED_AT = "0x3d4353f3bd3565329655e6b77bc2abfd31e558b86662ebd078ae453d416bc10f";
993
+ var MIN_HEALTH_FACTOR2 = 1.5;
1375
994
  var descriptor3 = {
1376
995
  id: "suilend",
1377
996
  name: "Suilend",
@@ -1389,224 +1008,31 @@ var descriptor3 = {
1389
1008
  "lending_market::repay": "repay"
1390
1009
  }
1391
1010
  };
1392
- function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
1393
- if (utilBreakpoints.length === 0) return 0;
1394
- if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
1395
- if (utilizationPct >= utilBreakpoints[utilBreakpoints.length - 1]) {
1396
- return aprBreakpoints[aprBreakpoints.length - 1];
1397
- }
1398
- for (let i = 1; i < utilBreakpoints.length; i++) {
1399
- if (utilizationPct <= utilBreakpoints[i]) {
1400
- const t = (utilizationPct - utilBreakpoints[i - 1]) / (utilBreakpoints[i] - utilBreakpoints[i - 1]);
1401
- return aprBreakpoints[i - 1] + t * (aprBreakpoints[i] - aprBreakpoints[i - 1]);
1402
- }
1403
- }
1404
- return aprBreakpoints[aprBreakpoints.length - 1];
1405
- }
1406
- function computeRates(reserve) {
1407
- const available = reserve.availableAmount / 10 ** reserve.mintDecimals;
1408
- const borrowed = reserve.borrowedAmountWad / WAD / 10 ** reserve.mintDecimals;
1409
- const totalDeposited = available + borrowed;
1410
- const utilizationPct = totalDeposited > 0 ? borrowed / totalDeposited * 100 : 0;
1411
- if (reserve.interestRateUtils.length === 0) return { borrowAprPct: 0, depositAprPct: 0 };
1412
- const aprs = reserve.interestRateAprs.map((a) => a / 100);
1413
- const borrowAprPct = interpolateRate(reserve.interestRateUtils, aprs, utilizationPct);
1414
- const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - reserve.spreadFeeBps / 1e4) * 100;
1415
- return { borrowAprPct, depositAprPct };
1416
- }
1417
- var MS_PER_YEAR = 365.25 * 24 * 3600 * 1e3;
1418
- function computeDepositRewardApr(reserve, allReserves) {
1419
- if (reserve.depositTotalShares <= 0 || reserve.price <= 0) return 0;
1420
- const totalDepositValue = reserve.depositTotalShares / 10 ** reserve.mintDecimals * reserve.price;
1421
- if (totalDepositValue <= 0) return 0;
1422
- const priceMap = /* @__PURE__ */ new Map();
1423
- for (const r of allReserves) {
1424
- if (r.price > 0) priceMap.set(r.coinType, { price: r.price, decimals: r.mintDecimals });
1425
- }
1426
- let rewardApr = 0;
1427
- for (const rw of reserve.depositPoolRewards) {
1428
- const info = priceMap.get(rw.coinType);
1429
- if (!info || info.price <= 0) continue;
1430
- const durationMs = rw.endTimeMs - rw.startTimeMs;
1431
- if (durationMs <= 0) continue;
1432
- const annualTokens = rw.totalRewards / 10 ** info.decimals * (MS_PER_YEAR / durationMs);
1433
- rewardApr += annualTokens * info.price / totalDepositValue * 100;
1434
- }
1435
- return rewardApr;
1436
- }
1437
- function cTokenRatio(reserve) {
1438
- if (reserve.ctokenSupply === 0) return 1;
1439
- const totalSupply = reserve.availableAmount + reserve.borrowedAmountWad / WAD - reserve.unclaimedSpreadFeesWad / WAD;
1440
- return totalSupply / reserve.ctokenSupply;
1441
- }
1442
- function f(obj) {
1443
- if (obj && typeof obj === "object" && "fields" in obj) return obj.fields;
1444
- return obj;
1445
- }
1446
- function str(v) {
1447
- return String(v ?? "0");
1448
- }
1449
- function num(v) {
1450
- return Number(str(v));
1451
- }
1452
- function parseReserve(raw, index) {
1453
- const r = f(raw);
1454
- const coinTypeField = f(r.coin_type);
1455
- const config = f(f(r.config)?.element);
1456
- const dMgr = f(r.deposits_pool_reward_manager);
1457
- const rawRewards = Array.isArray(dMgr?.pool_rewards) ? dMgr.pool_rewards : [];
1458
- const now = Date.now();
1459
- const depositPoolRewards = rawRewards.map((rw, idx) => {
1460
- if (rw === null) return null;
1461
- const rwf = f(rw);
1462
- return {
1463
- coinType: str(f(rwf.coin_type)?.name),
1464
- totalRewards: num(rwf.total_rewards),
1465
- startTimeMs: num(rwf.start_time_ms),
1466
- endTimeMs: num(rwf.end_time_ms),
1467
- rewardIndex: idx
1468
- };
1469
- }).filter((rw) => rw !== null && rw.endTimeMs > now && rw.totalRewards > 0);
1470
- return {
1471
- coinType: str(coinTypeField?.name),
1472
- mintDecimals: num(r.mint_decimals),
1473
- availableAmount: num(r.available_amount),
1474
- borrowedAmountWad: num(f(r.borrowed_amount)?.value),
1475
- ctokenSupply: num(r.ctoken_supply),
1476
- unclaimedSpreadFeesWad: num(f(r.unclaimed_spread_fees)?.value),
1477
- cumulativeBorrowRateWad: num(f(r.cumulative_borrow_rate)?.value),
1478
- openLtvPct: num(config?.open_ltv_pct),
1479
- closeLtvPct: num(config?.close_ltv_pct),
1480
- spreadFeeBps: num(config?.spread_fee_bps),
1481
- interestRateUtils: Array.isArray(config?.interest_rate_utils) ? config.interest_rate_utils.map(num) : [],
1482
- interestRateAprs: Array.isArray(config?.interest_rate_aprs) ? config.interest_rate_aprs.map(num) : [],
1483
- arrayIndex: index,
1484
- price: num(f(r.price)?.value) / WAD,
1485
- depositTotalShares: num(dMgr?.total_shares),
1486
- depositPoolRewards
1487
- };
1488
- }
1489
- function parseObligation(raw) {
1490
- const deposits = Array.isArray(raw.deposits) ? raw.deposits.map((d) => {
1491
- const df = f(d);
1492
- return {
1493
- coinType: str(f(df.coin_type)?.name),
1494
- ctokenAmount: num(df.deposited_ctoken_amount),
1495
- reserveIdx: num(df.reserve_array_index)
1496
- };
1497
- }) : [];
1498
- const borrows = Array.isArray(raw.borrows) ? raw.borrows.map((b) => {
1499
- const bf = f(b);
1500
- return {
1501
- coinType: str(f(bf.coin_type)?.name),
1502
- borrowedWad: num(f(bf.borrowed_amount)?.value),
1503
- cumBorrowRateWad: num(f(bf.cumulative_borrow_rate)?.value),
1504
- reserveIdx: num(bf.reserve_array_index)
1505
- };
1506
- }) : [];
1507
- return { deposits, borrows };
1508
- }
1509
1011
  var SuilendAdapter = class {
1510
1012
  id = "suilend";
1511
1013
  name = "Suilend";
1512
- version = "2.0.0";
1014
+ version = "3.0.0";
1513
1015
  capabilities = ["save", "withdraw", "borrow", "repay"];
1514
1016
  supportedAssets = [...STABLE_ASSETS, "SUI", "ETH", "BTC", "GOLD"];
1515
1017
  supportsSameAssetBorrow = false;
1516
1018
  client;
1517
- publishedAt = null;
1518
- reserveCache = null;
1019
+ sdkClient = null;
1519
1020
  async init(client) {
1520
1021
  this.client = client;
1521
1022
  }
1522
1023
  initSync(client) {
1523
1024
  this.client = client;
1524
1025
  }
1525
- // -- On-chain reads -------------------------------------------------------
1526
- async resolvePackage() {
1527
- if (this.publishedAt) return this.publishedAt;
1528
- try {
1529
- const cap = await this.client.getObject({ id: UPGRADE_CAP_ID, options: { showContent: true } });
1530
- if (cap.data?.content?.dataType === "moveObject") {
1531
- const fields = cap.data.content.fields;
1532
- this.publishedAt = str(fields.package);
1533
- return this.publishedAt;
1534
- }
1535
- } catch {
1026
+ async getSdkClient() {
1027
+ if (!this.sdkClient) {
1028
+ this.sdkClient = await client.SuilendClient.initialize(
1029
+ client.LENDING_MARKET_ID,
1030
+ client.LENDING_MARKET_TYPE,
1031
+ this.client,
1032
+ false
1033
+ );
1536
1034
  }
1537
- this.publishedAt = FALLBACK_PUBLISHED_AT;
1538
- return this.publishedAt;
1539
- }
1540
- async loadReserves(fresh = false) {
1541
- if (this.reserveCache && !fresh) return this.reserveCache;
1542
- const market = await this.client.getObject({
1543
- id: LENDING_MARKET_ID,
1544
- options: { showContent: true }
1545
- });
1546
- if (market.data?.content?.dataType !== "moveObject") {
1547
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend lending market");
1548
- }
1549
- const fields = market.data.content.fields;
1550
- const reservesRaw = fields.reserves;
1551
- if (!Array.isArray(reservesRaw)) {
1552
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to parse Suilend reserves");
1553
- }
1554
- this.reserveCache = reservesRaw.map((r, i) => parseReserve(r, i));
1555
- return this.reserveCache;
1556
- }
1557
- findReserve(reserves, asset) {
1558
- let coinType;
1559
- if (asset in SUPPORTED_ASSETS) {
1560
- coinType = SUPPORTED_ASSETS[asset].type;
1561
- } else if (asset.includes("::")) {
1562
- coinType = asset;
1563
- } else {
1564
- return void 0;
1565
- }
1566
- try {
1567
- const normalized = utils.normalizeStructTag(coinType);
1568
- return reserves.find((r) => {
1569
- try {
1570
- return utils.normalizeStructTag(r.coinType) === normalized;
1571
- } catch {
1572
- return false;
1573
- }
1574
- });
1575
- } catch {
1576
- return void 0;
1577
- }
1578
- }
1579
- async fetchObligationCaps(address) {
1580
- const capType = `${SUILEND_PACKAGE}::lending_market::ObligationOwnerCap<${LENDING_MARKET_TYPE}>`;
1581
- const caps = [];
1582
- let cursor;
1583
- let hasNext = true;
1584
- while (hasNext) {
1585
- const page = await this.client.getOwnedObjects({
1586
- owner: address,
1587
- filter: { StructType: capType },
1588
- options: { showContent: true },
1589
- cursor: cursor ?? void 0
1590
- });
1591
- for (const item of page.data) {
1592
- if (item.data?.content?.dataType !== "moveObject") continue;
1593
- const fields = item.data.content.fields;
1594
- caps.push({
1595
- id: item.data.objectId,
1596
- obligationId: str(fields.obligation_id)
1597
- });
1598
- }
1599
- cursor = page.nextCursor;
1600
- hasNext = page.hasNextPage;
1601
- }
1602
- return caps;
1603
- }
1604
- async fetchObligation(obligationId) {
1605
- const obj = await this.client.getObject({ id: obligationId, options: { showContent: true } });
1606
- if (obj.data?.content?.dataType !== "moveObject") {
1607
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend obligation");
1608
- }
1609
- return parseObligation(obj.data.content.fields);
1035
+ return this.sdkClient;
1610
1036
  }
1611
1037
  resolveSymbol(coinType) {
1612
1038
  try {
@@ -1622,371 +1048,217 @@ var SuilendAdapter = class {
1622
1048
  const parts = coinType.split("::");
1623
1049
  return parts[parts.length - 1] || "UNKNOWN";
1624
1050
  }
1625
- // -- Adapter interface ----------------------------------------------------
1626
1051
  async getRates(asset) {
1627
- const reserves = await this.loadReserves();
1628
- const reserve = this.findReserve(reserves, asset);
1629
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1630
- const { borrowAprPct, depositAprPct } = computeRates(reserve);
1631
- const rewardApr = computeDepositRewardApr(reserve, reserves);
1632
- return { asset, saveApy: depositAprPct + rewardApr, borrowApy: borrowAprPct };
1052
+ try {
1053
+ const sdk = await this.getSdkClient();
1054
+ const { reserveMap } = await initialize.initializeSuilend(this.client, sdk);
1055
+ const assetInfo = SUPPORTED_ASSETS[asset];
1056
+ if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1057
+ const normalized = utils.normalizeStructTag(assetInfo.type);
1058
+ const reserve = Object.values(reserveMap).find((r) => {
1059
+ try {
1060
+ return utils.normalizeStructTag(r.coinType) === normalized;
1061
+ } catch {
1062
+ return false;
1063
+ }
1064
+ });
1065
+ if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1066
+ return {
1067
+ asset,
1068
+ saveApy: reserve.depositAprPercent.toNumber(),
1069
+ borrowApy: reserve.borrowAprPercent.toNumber()
1070
+ };
1071
+ } catch (err) {
1072
+ if (err instanceof T2000Error) throw err;
1073
+ const msg = err instanceof Error ? err.message : String(err);
1074
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Suilend getRates failed: ${msg}`);
1075
+ }
1633
1076
  }
1634
1077
  async getPositions(address) {
1635
1078
  const supplies = [];
1636
1079
  const borrows = [];
1637
- const caps = await this.fetchObligationCaps(address);
1638
- if (caps.length === 0) return { supplies, borrows };
1639
- const [reserves, obligation] = await Promise.all([
1640
- this.loadReserves(),
1641
- this.fetchObligation(caps[0].obligationId)
1642
- ]);
1643
- for (const dep of obligation.deposits) {
1644
- const reserve = reserves[dep.reserveIdx];
1645
- if (!reserve) continue;
1646
- const ratio = cTokenRatio(reserve);
1647
- const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
1648
- const { depositAprPct } = computeRates(reserve);
1649
- const rewardApr = computeDepositRewardApr(reserve, reserves);
1650
- supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct + rewardApr });
1651
- }
1652
- for (const bor of obligation.borrows) {
1653
- const reserve = reserves[bor.reserveIdx];
1654
- if (!reserve) continue;
1655
- const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
1656
- const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
1657
- const posRate = bor.cumBorrowRateWad / WAD;
1658
- const compounded = posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
1659
- const { borrowAprPct } = computeRates(reserve);
1660
- borrows.push({ asset: this.resolveSymbol(bor.coinType), amount: compounded, apy: borrowAprPct });
1080
+ try {
1081
+ const sdk = await this.getSdkClient();
1082
+ const { reserveMap, refreshedRawReserves } = await initialize.initializeSuilend(this.client, sdk);
1083
+ const { obligations, obligationOwnerCaps } = await initialize.initializeObligations(
1084
+ this.client,
1085
+ sdk,
1086
+ refreshedRawReserves,
1087
+ reserveMap,
1088
+ address
1089
+ );
1090
+ if (obligationOwnerCaps.length === 0 || obligations.length === 0) {
1091
+ return { supplies, borrows };
1092
+ }
1093
+ const obligation = obligations[0];
1094
+ for (const dep of obligation.deposits) {
1095
+ const symbol = this.resolveSymbol(dep.coinType);
1096
+ const amount = dep.depositedAmount.toNumber();
1097
+ const amountUsd = dep.depositedAmountUsd.toNumber();
1098
+ const apy = dep.reserve.depositAprPercent.toNumber();
1099
+ if (amount > 1e-4) {
1100
+ supplies.push({ asset: symbol, amount, amountUsd, apy });
1101
+ }
1102
+ }
1103
+ for (const bor of obligation.borrows) {
1104
+ const symbol = this.resolveSymbol(bor.coinType);
1105
+ const amount = bor.borrowedAmount.toNumber();
1106
+ const amountUsd = bor.borrowedAmountUsd.toNumber();
1107
+ const apy = bor.reserve.borrowAprPercent.toNumber();
1108
+ if (amount > 1e-4) {
1109
+ borrows.push({ asset: symbol, amount, amountUsd, apy });
1110
+ }
1111
+ }
1112
+ } catch (err) {
1113
+ if (err instanceof T2000Error) throw err;
1114
+ const msg = err instanceof Error ? err.message : String(err);
1115
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Suilend getPositions failed: ${msg}`);
1661
1116
  }
1662
1117
  return { supplies, borrows };
1663
1118
  }
1664
1119
  async getHealth(address) {
1665
- const caps = await this.fetchObligationCaps(address);
1666
- if (caps.length === 0) {
1120
+ try {
1121
+ const sdk = await this.getSdkClient();
1122
+ const { reserveMap, refreshedRawReserves } = await initialize.initializeSuilend(this.client, sdk);
1123
+ const { obligations, obligationOwnerCaps } = await initialize.initializeObligations(
1124
+ this.client,
1125
+ sdk,
1126
+ refreshedRawReserves,
1127
+ reserveMap,
1128
+ address
1129
+ );
1130
+ if (obligationOwnerCaps.length === 0 || obligations.length === 0) {
1131
+ return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
1132
+ }
1133
+ const ob = obligations[0];
1134
+ const supplied = ob.depositedAmountUsd.toNumber();
1135
+ const borrowed = ob.borrowedAmountUsd.toNumber();
1136
+ const borrowLimit = ob.borrowLimitUsd.toNumber();
1137
+ const unhealthy = ob.unhealthyBorrowValueUsd.toNumber();
1138
+ const liqThreshold = supplied > 0 ? unhealthy / supplied : 0.75;
1139
+ const healthFactor = borrowed > 0 ? unhealthy / borrowed : Infinity;
1140
+ const maxBorrow = Math.max(0, borrowLimit - borrowed);
1141
+ return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
1142
+ } catch {
1667
1143
  return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
1668
1144
  }
1669
- const [reserves, obligation] = await Promise.all([
1670
- this.loadReserves(),
1671
- this.fetchObligation(caps[0].obligationId)
1672
- ]);
1673
- let supplied = 0;
1674
- let borrowed = 0;
1675
- let weightedCloseLtv = 0;
1676
- let weightedOpenLtv = 0;
1677
- for (const dep of obligation.deposits) {
1678
- const reserve = reserves[dep.reserveIdx];
1679
- if (!reserve) continue;
1680
- const ratio = cTokenRatio(reserve);
1681
- const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
1682
- supplied += amount;
1683
- weightedCloseLtv += amount * (reserve.closeLtvPct / 100);
1684
- weightedOpenLtv += amount * (reserve.openLtvPct / 100);
1685
- }
1686
- for (const bor of obligation.borrows) {
1687
- const reserve = reserves[bor.reserveIdx];
1688
- if (!reserve) continue;
1689
- const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
1690
- const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
1691
- const posRate = bor.cumBorrowRateWad / WAD;
1692
- borrowed += posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
1693
- }
1694
- const liqThreshold = supplied > 0 ? weightedCloseLtv / supplied : 0.75;
1695
- const openLtv = supplied > 0 ? weightedOpenLtv / supplied : 0.7;
1696
- const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
1697
- const maxBorrow = Math.max(0, supplied * openLtv - borrowed);
1698
- return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
1699
1145
  }
1700
1146
  async buildSaveTx(address, amount, asset, options) {
1701
1147
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1702
1148
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1703
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1704
- const reserve = this.findReserve(reserves, assetKey);
1705
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
1706
- const caps = await this.fetchObligationCaps(address);
1149
+ const sdk = await this.getSdkClient();
1150
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1707
1151
  const tx = new transactions.Transaction();
1708
1152
  tx.setSender(address);
1709
- let capRef;
1710
1153
  if (caps.length === 0) {
1711
- const [newCap] = tx.moveCall({
1712
- target: `${pkg}::lending_market::create_obligation`,
1713
- typeArguments: [LENDING_MARKET_TYPE],
1714
- arguments: [tx.object(LENDING_MARKET_ID)]
1715
- });
1716
- capRef = newCap;
1717
- } else {
1718
- capRef = caps[0].id;
1154
+ const newCap = sdk.createObligation(tx);
1155
+ tx.transferObjects([newCap], address);
1719
1156
  }
1720
- const allCoins = await this.fetchAllCoins(address, assetInfo.type);
1721
- if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
1722
- const primaryCoinId = allCoins[0].coinObjectId;
1723
- if (allCoins.length > 1) {
1724
- tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1725
- }
1726
- const rawAmount = stableToRaw(amount, assetInfo.decimals).toString();
1727
- const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
1157
+ const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1728
1158
  if (options?.collectFee) {
1159
+ const allCoins = await this.fetchAllCoins(address, assetInfo.type);
1160
+ if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
1161
+ const primaryCoinId = allCoins[0].coinObjectId;
1162
+ if (allCoins.length > 1) {
1163
+ tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1164
+ }
1165
+ const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawValue]);
1729
1166
  addCollectFeeToTx(tx, depositCoin, "save");
1730
1167
  }
1731
- const [ctokens] = tx.moveCall({
1732
- target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
1733
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1734
- arguments: [
1735
- tx.object(LENDING_MARKET_ID),
1736
- tx.pure.u64(reserve.arrayIndex),
1737
- tx.object(CLOCK2),
1738
- depositCoin
1739
- ]
1740
- });
1741
- tx.moveCall({
1742
- target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
1743
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1744
- arguments: [
1745
- tx.object(LENDING_MARKET_ID),
1746
- tx.pure.u64(reserve.arrayIndex),
1747
- typeof capRef === "string" ? tx.object(capRef) : capRef,
1748
- tx.object(CLOCK2),
1749
- ctokens
1750
- ]
1751
- });
1752
- if (typeof capRef !== "string") {
1753
- tx.transferObjects([capRef], address);
1168
+ if (caps.length > 0) {
1169
+ await sdk.depositIntoObligation(address, assetInfo.type, rawValue, tx, caps[0].id);
1170
+ } else {
1171
+ await sdk.depositIntoObligation(address, assetInfo.type, rawValue, tx, tx.object(caps[0]?.id ?? ""));
1754
1172
  }
1755
1173
  return { tx };
1756
1174
  }
1757
1175
  async buildWithdrawTx(address, amount, asset) {
1758
1176
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1759
1177
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1760
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
1761
- const reserve = this.findReserve(reserves, assetKey);
1762
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
1763
- const caps = await this.fetchObligationCaps(address);
1178
+ const sdk = await this.getSdkClient();
1179
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1764
1180
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1765
- const obligation = await this.fetchObligation(caps[0].obligationId);
1766
- const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
1767
- const ratio = cTokenRatio(reserve);
1768
- const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
1181
+ const positions = await this.getPositions(address);
1182
+ const dep = positions.supplies.find((s) => s.asset === assetKey);
1183
+ const deposited = dep?.amount ?? 0;
1769
1184
  const effectiveAmount = Math.min(amount, deposited);
1770
1185
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
1771
- const U64_MAX = "18446744073709551615";
1772
- const isFullWithdraw = dep && effectiveAmount >= deposited * 0.999;
1773
- const withdrawArg = isFullWithdraw ? U64_MAX : String(Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio));
1186
+ const rawValue = stableToRaw(effectiveAmount, assetInfo.decimals).toString();
1774
1187
  const tx = new transactions.Transaction();
1775
1188
  tx.setSender(address);
1776
- const [ctokens] = tx.moveCall({
1777
- target: `${pkg}::lending_market::withdraw_ctokens`,
1778
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1779
- arguments: [
1780
- tx.object(LENDING_MARKET_ID),
1781
- tx.pure.u64(reserve.arrayIndex),
1782
- tx.object(caps[0].id),
1783
- tx.object(CLOCK2),
1784
- tx.pure("u64", BigInt(withdrawArg))
1785
- ]
1786
- });
1787
- const coin = this.redeemCtokens(tx, pkg, reserve, assetInfo.type, assetKey, ctokens);
1788
- tx.transferObjects([coin], address);
1189
+ await sdk.withdrawAndSendToUser(address, caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1789
1190
  return { tx, effectiveAmount };
1790
1191
  }
1791
1192
  async addWithdrawToTx(tx, address, amount, asset) {
1792
1193
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1793
1194
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1794
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
1795
- const reserve = this.findReserve(reserves, assetKey);
1796
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
1797
- const caps = await this.fetchObligationCaps(address);
1195
+ const sdk = await this.getSdkClient();
1196
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1798
1197
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1799
- const obligation = await this.fetchObligation(caps[0].obligationId);
1800
- const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
1801
- const ratio = cTokenRatio(reserve);
1802
- const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
1198
+ const positions = await this.getPositions(address);
1199
+ const dep = positions.supplies.find((s) => s.asset === assetKey);
1200
+ const deposited = dep?.amount ?? 0;
1803
1201
  const effectiveAmount = Math.min(amount, deposited);
1804
1202
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
1805
- const ctokenAmount = dep && effectiveAmount >= deposited * 0.999 ? dep.ctokenAmount : Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
1806
- const [ctokens] = tx.moveCall({
1807
- target: `${pkg}::lending_market::withdraw_ctokens`,
1808
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1809
- arguments: [
1810
- tx.object(LENDING_MARKET_ID),
1811
- tx.pure.u64(reserve.arrayIndex),
1812
- tx.object(caps[0].id),
1813
- tx.object(CLOCK2),
1814
- tx.pure.u64(ctokenAmount)
1815
- ]
1816
- });
1817
- const coin = this.redeemCtokens(tx, pkg, reserve, assetInfo.type, assetKey, ctokens);
1203
+ const rawValue = stableToRaw(effectiveAmount, assetInfo.decimals).toString();
1204
+ const coin = await sdk.withdraw(caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1818
1205
  return { coin, effectiveAmount };
1819
1206
  }
1820
- /**
1821
- * 3-step cToken redemption matching the official Suilend SDK flow:
1822
- * 1. redeem_ctokens_and_withdraw_liquidity_request — creates a LiquidityRequest
1823
- * 2. unstake_sui_from_staker — (SUI only) unstakes from validators to replenish available_liquidity
1824
- * 3. fulfill_liquidity_request — splits underlying tokens from the reserve
1825
- */
1826
- redeemCtokens(tx, pkg, reserve, coinType, assetKey, ctokens) {
1827
- const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${coinType}>`;
1828
- const [none] = tx.moveCall({
1829
- target: "0x1::option::none",
1830
- typeArguments: [exemptionType]
1831
- });
1832
- const [liquidityRequest] = tx.moveCall({
1833
- target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity_request`,
1834
- typeArguments: [LENDING_MARKET_TYPE, coinType],
1835
- arguments: [
1836
- tx.object(LENDING_MARKET_ID),
1837
- tx.pure.u64(reserve.arrayIndex),
1838
- tx.object(CLOCK2),
1839
- ctokens,
1840
- none
1841
- ]
1842
- });
1843
- if (assetKey === "SUI") {
1844
- tx.moveCall({
1845
- target: `${pkg}::lending_market::unstake_sui_from_staker`,
1846
- typeArguments: [LENDING_MARKET_TYPE],
1847
- arguments: [
1848
- tx.object(LENDING_MARKET_ID),
1849
- tx.pure.u64(reserve.arrayIndex),
1850
- liquidityRequest,
1851
- tx.object(SUI_SYSTEM_STATE2)
1852
- ]
1853
- });
1854
- }
1855
- const [coin] = tx.moveCall({
1856
- target: `${pkg}::lending_market::fulfill_liquidity_request`,
1857
- typeArguments: [LENDING_MARKET_TYPE, coinType],
1858
- arguments: [
1859
- tx.object(LENDING_MARKET_ID),
1860
- tx.pure.u64(reserve.arrayIndex),
1861
- liquidityRequest
1862
- ]
1863
- });
1864
- return coin;
1865
- }
1866
1207
  async addSaveToTx(tx, address, coin, asset, options) {
1867
1208
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1868
1209
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1869
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1870
- const reserve = this.findReserve(reserves, assetKey);
1871
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
1872
- const caps = await this.fetchObligationCaps(address);
1210
+ const sdk = await this.getSdkClient();
1211
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1873
1212
  let capRef;
1874
1213
  if (caps.length === 0) {
1875
- const [newCap] = tx.moveCall({
1876
- target: `${pkg}::lending_market::create_obligation`,
1877
- typeArguments: [LENDING_MARKET_TYPE],
1878
- arguments: [tx.object(LENDING_MARKET_ID)]
1879
- });
1214
+ const newCap = sdk.createObligation(tx);
1880
1215
  capRef = newCap;
1216
+ tx.transferObjects([newCap], address);
1881
1217
  } else {
1882
1218
  capRef = caps[0].id;
1883
1219
  }
1884
1220
  if (options?.collectFee) {
1885
1221
  addCollectFeeToTx(tx, coin, "save");
1886
1222
  }
1887
- const [ctokens] = tx.moveCall({
1888
- target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
1889
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1890
- arguments: [
1891
- tx.object(LENDING_MARKET_ID),
1892
- tx.pure.u64(reserve.arrayIndex),
1893
- tx.object(CLOCK2),
1894
- coin
1895
- ]
1896
- });
1897
- tx.moveCall({
1898
- target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
1899
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1900
- arguments: [
1901
- tx.object(LENDING_MARKET_ID),
1902
- tx.pure.u64(reserve.arrayIndex),
1903
- typeof capRef === "string" ? tx.object(capRef) : capRef,
1904
- tx.object(CLOCK2),
1905
- ctokens
1906
- ]
1907
- });
1908
- if (typeof capRef !== "string") {
1909
- tx.transferObjects([capRef], address);
1910
- }
1223
+ sdk.deposit(coin, assetInfo.type, capRef, tx);
1911
1224
  }
1912
1225
  async buildBorrowTx(address, amount, asset, options) {
1913
1226
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1914
1227
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1915
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1916
- const reserve = this.findReserve(reserves, assetKey);
1917
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
1918
- const caps = await this.fetchObligationCaps(address);
1228
+ const sdk = await this.getSdkClient();
1229
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1919
1230
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found. Deposit collateral first with: t2000 save <amount>");
1920
- const rawAmount = stableToRaw(amount, assetInfo.decimals);
1231
+ const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1921
1232
  const tx = new transactions.Transaction();
1922
1233
  tx.setSender(address);
1923
- const [coin] = tx.moveCall({
1924
- target: `${pkg}::lending_market::borrow`,
1925
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1926
- arguments: [
1927
- tx.object(LENDING_MARKET_ID),
1928
- tx.pure.u64(reserve.arrayIndex),
1929
- tx.object(caps[0].id),
1930
- tx.object(CLOCK2),
1931
- tx.pure.u64(rawAmount)
1932
- ]
1933
- });
1934
1234
  if (options?.collectFee) {
1235
+ const coin = await sdk.borrow(caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1935
1236
  addCollectFeeToTx(tx, coin, "borrow");
1237
+ tx.transferObjects([coin], address);
1238
+ } else {
1239
+ await sdk.borrowAndSendToUser(address, caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1936
1240
  }
1937
- tx.transferObjects([coin], address);
1938
1241
  return { tx };
1939
1242
  }
1940
1243
  async buildRepayTx(address, amount, asset) {
1941
1244
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1942
1245
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1943
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1944
- const reserve = this.findReserve(reserves, assetKey);
1945
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
1946
- const caps = await this.fetchObligationCaps(address);
1246
+ const sdk = await this.getSdkClient();
1247
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1947
1248
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
1948
- const allCoins = await this.fetchAllCoins(address, assetInfo.type);
1949
- if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
1950
- const rawAmount = stableToRaw(amount, assetInfo.decimals);
1249
+ const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1951
1250
  const tx = new transactions.Transaction();
1952
1251
  tx.setSender(address);
1953
- const primaryCoinId = allCoins[0].coinObjectId;
1954
- if (allCoins.length > 1) {
1955
- tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1956
- }
1957
- const [repayCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount.toString()]);
1958
- tx.moveCall({
1959
- target: `${pkg}::lending_market::repay`,
1960
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1961
- arguments: [
1962
- tx.object(LENDING_MARKET_ID),
1963
- tx.pure.u64(reserve.arrayIndex),
1964
- tx.object(caps[0].id),
1965
- tx.object(CLOCK2),
1966
- repayCoin
1967
- ]
1968
- });
1252
+ await sdk.repayIntoObligation(address, caps[0].obligationId, assetInfo.type, rawValue, tx);
1969
1253
  return { tx };
1970
1254
  }
1971
1255
  async addRepayToTx(tx, address, coin, asset) {
1972
1256
  const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1973
1257
  const assetInfo = SUPPORTED_ASSETS[assetKey];
1974
- const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
1975
- const reserve = this.findReserve(reserves, assetKey);
1976
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
1977
- const caps = await this.fetchObligationCaps(address);
1258
+ const sdk = await this.getSdkClient();
1259
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1978
1260
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
1979
- tx.moveCall({
1980
- target: `${pkg}::lending_market::repay`,
1981
- typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
1982
- arguments: [
1983
- tx.object(LENDING_MARKET_ID),
1984
- tx.pure.u64(reserve.arrayIndex),
1985
- tx.object(caps[0].id),
1986
- tx.object(CLOCK2),
1987
- coin
1988
- ]
1989
- });
1261
+ sdk.repay(caps[0].obligationId, assetInfo.type, coin, tx);
1990
1262
  }
1991
1263
  async maxWithdraw(address, _asset) {
1992
1264
  const health = await this.getHealth(address);
@@ -2002,8 +1274,7 @@ var SuilendAdapter = class {
2002
1274
  }
2003
1275
  async maxBorrow(address, _asset) {
2004
1276
  const health = await this.getHealth(address);
2005
- const maxAmount = health.maxBorrow;
2006
- return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
1277
+ return { maxAmount: health.maxBorrow, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
2007
1278
  }
2008
1279
  async fetchAllCoins(owner, coinType) {
2009
1280
  const all = [];
@@ -2017,88 +1288,79 @@ var SuilendAdapter = class {
2017
1288
  }
2018
1289
  return all;
2019
1290
  }
2020
- // -- Claim Rewards --------------------------------------------------------
2021
- isClaimableReward(coinType) {
2022
- const ct = coinType.toLowerCase();
2023
- return ct.includes("spring_sui") || ct.includes("deep::deep") || ct.includes("cert::cert");
2024
- }
2025
1291
  async getPendingRewards(address) {
2026
- const caps = await this.fetchObligationCaps(address);
2027
- if (caps.length === 0) return [];
2028
- const [reserves, obligation] = await Promise.all([
2029
- this.loadReserves(true),
2030
- this.fetchObligation(caps[0].obligationId)
2031
- ]);
2032
- const rewards = [];
2033
- for (const dep of obligation.deposits) {
2034
- const reserve = reserves[dep.reserveIdx];
2035
- if (!reserve) continue;
2036
- for (const rw of reserve.depositPoolRewards) {
2037
- if (!this.isClaimableReward(rw.coinType)) continue;
2038
- const durationMs = rw.endTimeMs - rw.startTimeMs;
2039
- if (durationMs <= 0) continue;
2040
- const assetSymbol = this.resolveSymbol(dep.coinType);
2041
- if (!(assetSymbol in SUPPORTED_ASSETS)) continue;
2042
- rewards.push({
2043
- protocol: "suilend",
2044
- asset: assetSymbol,
2045
- coinType: rw.coinType,
2046
- symbol: rw.coinType.includes("spring_sui") ? "sSUI" : rw.coinType.includes("deep::") ? "DEEP" : rw.coinType.split("::").pop() ?? "UNKNOWN",
2047
- amount: 0,
2048
- estimatedValueUsd: 0
2049
- });
1292
+ try {
1293
+ const sdk = await this.getSdkClient();
1294
+ const { reserveMap, refreshedRawReserves } = await initialize.initializeSuilend(this.client, sdk);
1295
+ const { obligations, obligationOwnerCaps } = await initialize.initializeObligations(
1296
+ this.client,
1297
+ sdk,
1298
+ refreshedRawReserves,
1299
+ reserveMap,
1300
+ address
1301
+ );
1302
+ if (obligationOwnerCaps.length === 0 || obligations.length === 0) return [];
1303
+ const ob = obligations[0];
1304
+ const rewards = [];
1305
+ for (const dep of ob.deposits) {
1306
+ for (const rw of dep.reserve.depositsPoolRewardManager.poolRewards) {
1307
+ if (rw.endTimeMs <= Date.now()) continue;
1308
+ const symbol = rw.symbol || rw.coinType.split("::").pop() || "UNKNOWN";
1309
+ rewards.push({
1310
+ protocol: "suilend",
1311
+ asset: this.resolveSymbol(dep.coinType),
1312
+ coinType: rw.coinType,
1313
+ symbol,
1314
+ amount: 0,
1315
+ estimatedValueUsd: 0
1316
+ });
1317
+ }
2050
1318
  }
1319
+ return rewards;
1320
+ } catch {
1321
+ return [];
2051
1322
  }
2052
- return rewards;
2053
1323
  }
2054
1324
  async addClaimRewardsToTx(tx, address) {
2055
- const caps = await this.fetchObligationCaps(address);
2056
- if (caps.length === 0) return [];
2057
- const [pkg, reserves, obligation] = await Promise.all([
2058
- this.resolvePackage(),
2059
- this.loadReserves(true),
2060
- this.fetchObligation(caps[0].obligationId)
2061
- ]);
2062
- const claimsByToken = /* @__PURE__ */ new Map();
2063
- const claimed = [];
2064
- for (const dep of obligation.deposits) {
2065
- const reserve = reserves[dep.reserveIdx];
2066
- if (!reserve) continue;
2067
- for (const rw of reserve.depositPoolRewards) {
2068
- if (!this.isClaimableReward(rw.coinType)) continue;
2069
- const [coin] = tx.moveCall({
2070
- target: `${pkg}::lending_market::claim_rewards`,
2071
- typeArguments: [LENDING_MARKET_TYPE, rw.coinType],
2072
- arguments: [
2073
- tx.object(LENDING_MARKET_ID),
2074
- tx.object(caps[0].id),
2075
- tx.object(CLOCK2),
2076
- tx.pure.u64(reserve.arrayIndex),
2077
- tx.pure.u64(rw.rewardIndex),
2078
- tx.pure.bool(true)
2079
- ]
2080
- });
2081
- const existing = claimsByToken.get(rw.coinType) ?? [];
2082
- existing.push(coin);
2083
- claimsByToken.set(rw.coinType, existing);
2084
- }
2085
- }
2086
- for (const [coinType, coins] of claimsByToken) {
2087
- if (coins.length > 1) {
2088
- tx.mergeCoins(coins[0], coins.slice(1));
1325
+ try {
1326
+ const sdk = await this.getSdkClient();
1327
+ const caps = await client.SuilendClient.getObligationOwnerCaps(address, [client.LENDING_MARKET_TYPE], this.client);
1328
+ if (caps.length === 0) return [];
1329
+ const { reserveMap, refreshedRawReserves } = await initialize.initializeSuilend(this.client, sdk);
1330
+ const { obligations } = await initialize.initializeObligations(
1331
+ this.client,
1332
+ sdk,
1333
+ refreshedRawReserves,
1334
+ reserveMap,
1335
+ address
1336
+ );
1337
+ if (obligations.length === 0) return [];
1338
+ const ob = obligations[0];
1339
+ const claimRewards = [];
1340
+ for (const dep of ob.deposits) {
1341
+ for (const rw of dep.reserve.depositsPoolRewardManager.poolRewards) {
1342
+ if (rw.endTimeMs <= Date.now()) continue;
1343
+ claimRewards.push({
1344
+ reserveArrayIndex: dep.reserveArrayIndex,
1345
+ rewardIndex: BigInt(rw.rewardIndex),
1346
+ rewardCoinType: rw.coinType,
1347
+ side: types.Side.DEPOSIT
1348
+ });
1349
+ }
2089
1350
  }
2090
- tx.transferObjects([coins[0]], address);
2091
- const symbol = coinType.includes("spring_sui") ? "SPRING_SUI" : coinType.includes("deep::") ? "DEEP" : coinType.split("::").pop() ?? "UNKNOWN";
2092
- claimed.push({
1351
+ if (claimRewards.length === 0) return [];
1352
+ sdk.claimRewardsAndSendToUser(address, caps[0].id, claimRewards, tx);
1353
+ return claimRewards.map((r) => ({
2093
1354
  protocol: "suilend",
2094
1355
  asset: "",
2095
- coinType,
2096
- symbol,
1356
+ coinType: r.rewardCoinType,
1357
+ symbol: r.rewardCoinType.split("::").pop() ?? "UNKNOWN",
2097
1358
  amount: 0,
2098
1359
  estimatedValueUsd: 0
2099
- });
1360
+ }));
1361
+ } catch {
1362
+ return [];
2100
1363
  }
2101
- return claimed;
2102
1364
  }
2103
1365
  };
2104
1366
  var descriptor4 = {