@t2000/sdk 0.17.13 → 0.17.15

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.
@@ -867,6 +867,173 @@ async function maxBorrowAmount(client, addressOrKeypair) {
867
867
  const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
868
868
  return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR, currentHF: hf.healthFactor };
869
869
  }
870
+ var CERT_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
871
+ var DEEP_TYPE = "0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP";
872
+ var REWARD_FUNDS = {
873
+ [CERT_TYPE]: "0x7093cf7549d5e5b35bfde2177223d1050f71655c7f676a5e610ee70eb4d93b5c",
874
+ [DEEP_TYPE]: "0xc889d78b634f954979e80e622a2ae0fece824c0f6d9590044378a2563035f32f"
875
+ };
876
+ var REWARD_SYMBOLS = {
877
+ [CERT_TYPE]: "vSUI",
878
+ [DEEP_TYPE]: "DEEP"
879
+ };
880
+ var incentiveRulesCache = null;
881
+ async function getIncentiveRules(client) {
882
+ if (incentiveRulesCache && Date.now() - incentiveRulesCache.ts < CACHE_TTL) {
883
+ return incentiveRulesCache.data;
884
+ }
885
+ const [pools, obj] = await Promise.all([
886
+ getPools(),
887
+ client.getObject({
888
+ id: "0x62982dad27fb10bb314b3384d5de8d2ac2d72ab2dbeae5d801dbdb9efa816c80",
889
+ options: { showContent: true }
890
+ })
891
+ ]);
892
+ const rewardCoinMap = /* @__PURE__ */ new Map();
893
+ for (const pool of pools) {
894
+ const ct = (pool.suiCoinType || pool.coinType || "").toLowerCase();
895
+ const suffix = ct.split("::").slice(1).join("::");
896
+ const coins = pool.supplyIncentiveApyInfo?.rewardCoin;
897
+ if (Array.isArray(coins) && coins.length > 0) {
898
+ rewardCoinMap.set(suffix, coins[0]);
899
+ }
900
+ }
901
+ const result = /* @__PURE__ */ new Map();
902
+ if (obj.data?.content?.dataType !== "moveObject") {
903
+ incentiveRulesCache = { data: result, ts: Date.now() };
904
+ return result;
905
+ }
906
+ const fields = obj.data.content.fields;
907
+ const poolsObj = fields.pools;
908
+ const entries = poolsObj?.fields?.contents;
909
+ if (!Array.isArray(entries)) {
910
+ incentiveRulesCache = { data: result, ts: Date.now() };
911
+ return result;
912
+ }
913
+ for (const entry of entries) {
914
+ const ef = entry?.fields;
915
+ if (!ef) continue;
916
+ const key = String(ef.key ?? "");
917
+ const value = ef.value;
918
+ const rules = value?.fields?.rules;
919
+ const ruleEntries = rules?.fields?.contents;
920
+ if (!Array.isArray(ruleEntries)) continue;
921
+ const ruleIds = ruleEntries.map((re) => {
922
+ const rf = re?.fields;
923
+ return String(rf?.key ?? "");
924
+ }).filter(Boolean);
925
+ const suffix = key.split("::").slice(1).join("::").toLowerCase();
926
+ const full = key.toLowerCase();
927
+ const rewardCoin = rewardCoinMap.get(suffix) ?? rewardCoinMap.get(full) ?? null;
928
+ result.set(key, { ruleIds, rewardCoinType: rewardCoin });
929
+ }
930
+ incentiveRulesCache = { data: result, ts: Date.now() };
931
+ return result;
932
+ }
933
+ function stripPrefix(coinType) {
934
+ return coinType.replace(/^0x0*/, "");
935
+ }
936
+ async function getPendingRewards(client, address) {
937
+ const [pools, states, rules] = await Promise.all([
938
+ getPools(),
939
+ getUserState(client, address),
940
+ getIncentiveRules(client)
941
+ ]);
942
+ const rewards = [];
943
+ const deposited = states.filter((s) => s.supplyBalance > 0n);
944
+ if (deposited.length === 0) return rewards;
945
+ const rewardTotals = /* @__PURE__ */ new Map();
946
+ for (const state of deposited) {
947
+ const pool = pools.find((p) => p.id === state.assetId);
948
+ if (!pool) continue;
949
+ const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
950
+ if (boostedApr <= 0) continue;
951
+ const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
952
+ if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
953
+ const rewardType = rewardCoins[0];
954
+ const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex, pool);
955
+ const price = pool.token?.price ?? 0;
956
+ const depositUsd = supplyBal * price;
957
+ const annualRewardUsd = depositUsd * (boostedApr / 100);
958
+ const estimatedUsd = annualRewardUsd / 365;
959
+ rewardTotals.set(rewardType, (rewardTotals.get(rewardType) ?? 0) + estimatedUsd);
960
+ }
961
+ for (const [coinType, dailyUsd] of rewardTotals) {
962
+ if (dailyUsd < 1e-3) continue;
963
+ rewards.push({
964
+ protocol: "navi",
965
+ coinType,
966
+ symbol: REWARD_SYMBOLS[coinType] ?? coinType.split("::").pop() ?? "UNKNOWN",
967
+ amount: 0,
968
+ estimatedValueUsd: dailyUsd
969
+ });
970
+ }
971
+ return rewards;
972
+ }
973
+ async function addClaimRewardsToTx(tx, client, address) {
974
+ const [config, pools, states, rules] = await Promise.all([
975
+ getConfig(),
976
+ getPools(),
977
+ getUserState(client, address),
978
+ getIncentiveRules(client)
979
+ ]);
980
+ const deposited = states.filter((s) => s.supplyBalance > 0n);
981
+ if (deposited.length === 0) return [];
982
+ const claimGroups = /* @__PURE__ */ new Map();
983
+ for (const state of deposited) {
984
+ const pool = pools.find((p) => p.id === state.assetId);
985
+ if (!pool) continue;
986
+ const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
987
+ if (boostedApr <= 0) continue;
988
+ const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
989
+ if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
990
+ const rewardType = rewardCoins[0];
991
+ const fundId = REWARD_FUNDS[rewardType];
992
+ if (!fundId) continue;
993
+ const coinType = pool.suiCoinType || pool.coinType || "";
994
+ const strippedType = stripPrefix(coinType);
995
+ const ruleData = Array.from(rules.entries()).find(
996
+ ([key]) => stripPrefix(key) === strippedType || key.split("::").slice(1).join("::").toLowerCase() === coinType.split("::").slice(1).join("::").toLowerCase()
997
+ );
998
+ if (!ruleData || ruleData[1].ruleIds.length === 0) continue;
999
+ const group = claimGroups.get(rewardType) ?? { assets: [], ruleIds: [] };
1000
+ for (const ruleId of ruleData[1].ruleIds) {
1001
+ group.assets.push(strippedType);
1002
+ group.ruleIds.push(ruleId);
1003
+ }
1004
+ claimGroups.set(rewardType, group);
1005
+ }
1006
+ const claimed = [];
1007
+ for (const [rewardType, { assets, ruleIds }] of claimGroups) {
1008
+ const fundId = REWARD_FUNDS[rewardType];
1009
+ const [balance] = tx.moveCall({
1010
+ target: `${config.package}::incentive_v3::claim_reward`,
1011
+ typeArguments: [rewardType],
1012
+ arguments: [
1013
+ tx.object(CLOCK),
1014
+ tx.object(config.incentiveV3),
1015
+ tx.object(config.storage),
1016
+ tx.object(fundId),
1017
+ tx.pure(bcs.bcs.vector(bcs.bcs.string()).serialize(assets)),
1018
+ tx.pure(bcs.bcs.vector(bcs.bcs.Address).serialize(ruleIds))
1019
+ ]
1020
+ });
1021
+ const [coin] = tx.moveCall({
1022
+ target: "0x2::coin::from_balance",
1023
+ typeArguments: [rewardType],
1024
+ arguments: [balance]
1025
+ });
1026
+ tx.transferObjects([coin], address);
1027
+ claimed.push({
1028
+ protocol: "navi",
1029
+ coinType: rewardType,
1030
+ symbol: REWARD_SYMBOLS[rewardType] ?? "UNKNOWN",
1031
+ amount: 0,
1032
+ estimatedValueUsd: 0
1033
+ });
1034
+ }
1035
+ return claimed;
1036
+ }
870
1037
 
871
1038
  // src/adapters/navi.ts
872
1039
  var descriptor = {
@@ -954,6 +1121,12 @@ var NaviAdapter = class {
954
1121
  const normalized = normalizeAsset(asset);
955
1122
  return addRepayToTx(tx, this.client, address, coin, { asset: normalized });
956
1123
  }
1124
+ async getPendingRewards(address) {
1125
+ return getPendingRewards(this.client, address);
1126
+ }
1127
+ async addClaimRewardsToTx(tx, address) {
1128
+ return addClaimRewardsToTx(tx, this.client, address);
1129
+ }
957
1130
  };
958
1131
  var DEFAULT_SLIPPAGE_BPS = 300;
959
1132
  function createAggregatorClient(client, signer) {
@@ -1236,6 +1409,26 @@ function computeRates(reserve) {
1236
1409
  const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - reserve.spreadFeeBps / 1e4) * 100;
1237
1410
  return { borrowAprPct, depositAprPct };
1238
1411
  }
1412
+ var MS_PER_YEAR = 365.25 * 24 * 3600 * 1e3;
1413
+ function computeDepositRewardApr(reserve, allReserves) {
1414
+ if (reserve.depositTotalShares <= 0 || reserve.price <= 0) return 0;
1415
+ const totalDepositValue = reserve.depositTotalShares / 10 ** reserve.mintDecimals * reserve.price;
1416
+ if (totalDepositValue <= 0) return 0;
1417
+ const priceMap = /* @__PURE__ */ new Map();
1418
+ for (const r of allReserves) {
1419
+ if (r.price > 0) priceMap.set(r.coinType, { price: r.price, decimals: r.mintDecimals });
1420
+ }
1421
+ let rewardApr = 0;
1422
+ for (const rw of reserve.depositPoolRewards) {
1423
+ const info = priceMap.get(rw.coinType);
1424
+ if (!info || info.price <= 0) continue;
1425
+ const durationMs = rw.endTimeMs - rw.startTimeMs;
1426
+ if (durationMs <= 0) continue;
1427
+ const annualTokens = rw.totalRewards / 10 ** info.decimals * (MS_PER_YEAR / durationMs);
1428
+ rewardApr += annualTokens * info.price / totalDepositValue * 100;
1429
+ }
1430
+ return rewardApr;
1431
+ }
1239
1432
  function cTokenRatio(reserve) {
1240
1433
  if (reserve.ctokenSupply === 0) return 1;
1241
1434
  const totalSupply = reserve.availableAmount + reserve.borrowedAmountWad / WAD - reserve.unclaimedSpreadFeesWad / WAD;
@@ -1255,6 +1448,20 @@ function parseReserve(raw, index) {
1255
1448
  const r = f(raw);
1256
1449
  const coinTypeField = f(r.coin_type);
1257
1450
  const config = f(f(r.config)?.element);
1451
+ const dMgr = f(r.deposits_pool_reward_manager);
1452
+ const rawRewards = Array.isArray(dMgr?.pool_rewards) ? dMgr.pool_rewards : [];
1453
+ const now = Date.now();
1454
+ const depositPoolRewards = rawRewards.map((rw, idx) => {
1455
+ if (rw === null) return null;
1456
+ const rwf = f(rw);
1457
+ return {
1458
+ coinType: str(f(rwf.coin_type)?.name),
1459
+ totalRewards: num(rwf.total_rewards),
1460
+ startTimeMs: num(rwf.start_time_ms),
1461
+ endTimeMs: num(rwf.end_time_ms),
1462
+ rewardIndex: idx
1463
+ };
1464
+ }).filter((rw) => rw !== null && rw.endTimeMs > now && rw.totalRewards > 0);
1258
1465
  return {
1259
1466
  coinType: str(coinTypeField?.name),
1260
1467
  mintDecimals: num(r.mint_decimals),
@@ -1268,7 +1475,10 @@ function parseReserve(raw, index) {
1268
1475
  spreadFeeBps: num(config?.spread_fee_bps),
1269
1476
  interestRateUtils: Array.isArray(config?.interest_rate_utils) ? config.interest_rate_utils.map(num) : [],
1270
1477
  interestRateAprs: Array.isArray(config?.interest_rate_aprs) ? config.interest_rate_aprs.map(num) : [],
1271
- arrayIndex: index
1478
+ arrayIndex: index,
1479
+ price: num(f(r.price)?.value) / WAD,
1480
+ depositTotalShares: num(dMgr?.total_shares),
1481
+ depositPoolRewards
1272
1482
  };
1273
1483
  }
1274
1484
  function parseObligation(raw) {
@@ -1413,7 +1623,8 @@ var SuilendAdapter = class {
1413
1623
  const reserve = this.findReserve(reserves, asset);
1414
1624
  if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1415
1625
  const { borrowAprPct, depositAprPct } = computeRates(reserve);
1416
- return { asset, saveApy: depositAprPct, borrowApy: borrowAprPct };
1626
+ const rewardApr = computeDepositRewardApr(reserve, reserves);
1627
+ return { asset, saveApy: depositAprPct + rewardApr, borrowApy: borrowAprPct };
1417
1628
  }
1418
1629
  async getPositions(address) {
1419
1630
  const supplies = [];
@@ -1430,7 +1641,8 @@ var SuilendAdapter = class {
1430
1641
  const ratio = cTokenRatio(reserve);
1431
1642
  const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
1432
1643
  const { depositAprPct } = computeRates(reserve);
1433
- supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct });
1644
+ const rewardApr = computeDepositRewardApr(reserve, reserves);
1645
+ supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct + rewardApr });
1434
1646
  }
1435
1647
  for (const bor of obligation.borrows) {
1436
1648
  const reserve = reserves[bor.reserveIdx];
@@ -1800,6 +2012,109 @@ var SuilendAdapter = class {
1800
2012
  }
1801
2013
  return all;
1802
2014
  }
2015
+ // -- Claim Rewards --------------------------------------------------------
2016
+ isClaimableReward(coinType) {
2017
+ const ct = coinType.toLowerCase();
2018
+ return ct.includes("spring_sui") || ct.includes("deep::deep") || ct.includes("cert::cert");
2019
+ }
2020
+ async getPendingRewards(address) {
2021
+ const caps = await this.fetchObligationCaps(address);
2022
+ if (caps.length === 0) return [];
2023
+ const [reserves, obligation] = await Promise.all([
2024
+ this.loadReserves(true),
2025
+ this.fetchObligation(caps[0].obligationId)
2026
+ ]);
2027
+ const rewards = [];
2028
+ const rewardEstimates = /* @__PURE__ */ new Map();
2029
+ for (const dep of obligation.deposits) {
2030
+ const reserve = reserves[dep.reserveIdx];
2031
+ if (!reserve) continue;
2032
+ const ratio = cTokenRatio(reserve);
2033
+ const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
2034
+ const price = reserve.price;
2035
+ const depositUsd = amount * price;
2036
+ for (const rw of reserve.depositPoolRewards) {
2037
+ if (!this.isClaimableReward(rw.coinType)) continue;
2038
+ const rewardReserve = reserves.find((r) => {
2039
+ try {
2040
+ return utils.normalizeStructTag(r.coinType) === utils.normalizeStructTag(rw.coinType);
2041
+ } catch {
2042
+ return false;
2043
+ }
2044
+ });
2045
+ const rewardPrice = rewardReserve?.price ?? 0;
2046
+ const rewardDecimals = rewardReserve?.mintDecimals ?? 9;
2047
+ const durationMs = rw.endTimeMs - rw.startTimeMs;
2048
+ if (durationMs <= 0) continue;
2049
+ const annualTokens = rw.totalRewards / 10 ** rewardDecimals * (MS_PER_YEAR / durationMs);
2050
+ const totalDepositValue = reserve.depositTotalShares / 10 ** reserve.mintDecimals * price;
2051
+ if (totalDepositValue <= 0) continue;
2052
+ const userShare = depositUsd / totalDepositValue;
2053
+ const dailyRewardUsd = annualTokens * rewardPrice / 365 * userShare;
2054
+ rewardEstimates.set(rw.coinType, (rewardEstimates.get(rw.coinType) ?? 0) + dailyRewardUsd);
2055
+ }
2056
+ }
2057
+ for (const [coinType, dailyUsd] of rewardEstimates) {
2058
+ if (dailyUsd < 1e-3) continue;
2059
+ const symbol = coinType.includes("spring_sui") ? "SPRING_SUI" : coinType.includes("deep::") ? "DEEP" : coinType.split("::").pop() ?? "UNKNOWN";
2060
+ rewards.push({
2061
+ protocol: "suilend",
2062
+ coinType,
2063
+ symbol,
2064
+ amount: 0,
2065
+ estimatedValueUsd: dailyUsd
2066
+ });
2067
+ }
2068
+ return rewards;
2069
+ }
2070
+ async addClaimRewardsToTx(tx, address) {
2071
+ const caps = await this.fetchObligationCaps(address);
2072
+ if (caps.length === 0) return [];
2073
+ const [pkg, reserves, obligation] = await Promise.all([
2074
+ this.resolvePackage(),
2075
+ this.loadReserves(true),
2076
+ this.fetchObligation(caps[0].obligationId)
2077
+ ]);
2078
+ const claimsByToken = /* @__PURE__ */ new Map();
2079
+ const claimed = [];
2080
+ for (const dep of obligation.deposits) {
2081
+ const reserve = reserves[dep.reserveIdx];
2082
+ if (!reserve) continue;
2083
+ for (const rw of reserve.depositPoolRewards) {
2084
+ if (!this.isClaimableReward(rw.coinType)) continue;
2085
+ const [coin] = tx.moveCall({
2086
+ target: `${pkg}::lending_market::claim_rewards`,
2087
+ typeArguments: [LENDING_MARKET_TYPE, rw.coinType],
2088
+ arguments: [
2089
+ tx.object(LENDING_MARKET_ID),
2090
+ tx.object(caps[0].id),
2091
+ tx.object(CLOCK2),
2092
+ tx.pure.u64(reserve.arrayIndex),
2093
+ tx.pure.u64(rw.rewardIndex),
2094
+ tx.pure.bool(true)
2095
+ ]
2096
+ });
2097
+ const existing = claimsByToken.get(rw.coinType) ?? [];
2098
+ existing.push(coin);
2099
+ claimsByToken.set(rw.coinType, existing);
2100
+ }
2101
+ }
2102
+ for (const [coinType, coins] of claimsByToken) {
2103
+ if (coins.length > 1) {
2104
+ tx.mergeCoins(coins[0], coins.slice(1));
2105
+ }
2106
+ tx.transferObjects([coins[0]], address);
2107
+ const symbol = coinType.includes("spring_sui") ? "SPRING_SUI" : coinType.includes("deep::") ? "DEEP" : coinType.split("::").pop() ?? "UNKNOWN";
2108
+ claimed.push({
2109
+ protocol: "suilend",
2110
+ coinType,
2111
+ symbol,
2112
+ amount: 0,
2113
+ estimatedValueUsd: 0
2114
+ });
2115
+ }
2116
+ return claimed;
2117
+ }
1803
2118
  };
1804
2119
  var descriptor4 = {
1805
2120
  id: "sentinel",