Skip to content

Commit 81b833f

Browse files
authored
Adding hatom lending (#1117)
* Base structura passing test * Adding querys and some calculations * Calculate base and rewards apy * Get total boosted colateral to calculate the boostedAPY * refactor: ➖ Delete boosted rewards * refactor: 🗑️ Clean unnecesary code and packages
1 parent a0e2a40 commit 81b833f

File tree

4 files changed

+398
-0
lines changed

4 files changed

+398
-0
lines changed

src/adaptors/hatom-lending/index.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const BigNumber = require('bignumber.js');
2+
const utils = require('../utils');
3+
const { calcRewardsAPY } = require('./utils/math.js');
4+
const { getMoneyMarkets, getTokenPrices, getExchangeRates, getRewardsBatches, getBoostedRewards, getBoostedColateralMap } = require('./utils/data.js');
5+
6+
const MARKETS = [
7+
{ symbol: "USDC", address: "erd1qqqqqqqqqqqqqpgqkrgsvct7hfx7ru30mfzk3uy6pxzxn6jj78ss84aldu" },
8+
{ symbol: 'WBTC', address: "erd1qqqqqqqqqqqqqpgqg47t8v5nwzvdxgf6g5jkxleuplu8y4f678ssfcg5gy" },
9+
{ symbol: "WETH", address: "erd1qqqqqqqqqqqqqpgq8h8upp38fe9p4ny9ecvsett0usu2ep7978ssypgmrs" },
10+
{ symbol: "USDT", address: "erd1qqqqqqqqqqqqqpgqvxn0cl35r74tlw2a8d794v795jrzfxyf78sstg8pjr" }
11+
]
12+
13+
const apy = async () => {
14+
const [mm, prices, rewards] = await Promise.all([
15+
getMoneyMarkets(),
16+
getTokenPrices(),
17+
getRewardsBatches(),
18+
]);
19+
const exchangeRates = getExchangeRates(mm)
20+
21+
return MARKETS.map(({ symbol }) => {
22+
const currentMM = mm[symbol]
23+
const currentPrice = prices[symbol]
24+
const currentExchangeRate = exchangeRates[symbol]
25+
const currentRewards = rewards[symbol]
26+
27+
const rewardsAPY = calcRewardsAPY({
28+
speed: currentRewards.speed,
29+
hTokenExchangeRate: currentExchangeRate,
30+
totalCollateral: currentMM.totalColateral.toString(),
31+
marketPrice: currentPrice,
32+
rewardsToken: currentRewards.rewardsToken,
33+
rewardsTokenPrice: prices[currentRewards.rewardsToken.symbol],
34+
marketDecimals: currentMM.decimals
35+
})
36+
37+
const tvlUsd = new BigNumber(currentMM.cash).multipliedBy(currentPrice).dividedBy(`1e${currentMM.decimals}`).toNumber()
38+
const apyBase = mm[symbol].supplyAPY
39+
const apyReward = new BigNumber(rewardsAPY).toNumber()
40+
return {
41+
pool: symbol,
42+
chain: 'MultiversX',
43+
project: 'hatom-lending',
44+
symbol: symbol,
45+
tvlUsd: tvlUsd,
46+
apyBase: apyBase,
47+
apyReward: apyReward,
48+
rewardTokens: [currentRewards.rewardsToken.symbol],
49+
}
50+
})
51+
}
52+
53+
module.exports = {
54+
timetravel: false,
55+
apy: apy,
56+
url: 'https://app.hatom.com/lend',
57+
};
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const BigNumber = require('bignumber.js');
2+
const { default: axios } = require('axios');
3+
const { request } = require('graphql-request');
4+
5+
const { queryPrices, queryMoneyMarkets, queryRewards } = require('./queries')
6+
const { calcLiquidStakingExchangeRate, calcSimulateExchangeRate } = require('./math');
7+
8+
const API_URL = 'https://mainnet-api.hatom.com/graphql';
9+
10+
async function getMoneyMarkets() {
11+
const response = await request(API_URL, queryMoneyMarkets, {});
12+
return response.queryMoneyMarket.reduce((prev, market) => {
13+
const symbol = market.underlying.symbol;
14+
const value = {
15+
address: market.address,
16+
decimals: market.underlying.decimals,
17+
cash: market.stateHistory[0].cash,
18+
borrows: market.stateHistory[0].borrows,
19+
reserves: market.stateHistory[0].reserves,
20+
rate: market.stateHistory[0].supplyRatePerSecond,
21+
timestamp: market.stateHistory[0].timestamp,
22+
totalSupply: market.stateHistory[0].totalSupply,
23+
borrowRatePerSecond: market.stateHistory[0].borrowRatePerSecond,
24+
supplyAPY: market.stateHistory[0].supplyAPY,
25+
supplyRatePerSecond: market.stateHistory[0].supplyRatePerSecond,
26+
totalColateral: market.totalCollateral,
27+
}
28+
return {
29+
...prev,
30+
[symbol]: value,
31+
};
32+
}, {})
33+
}
34+
35+
async function getTokenPrices() {
36+
const { queryToken, queryLiquidStaking } =
37+
await request(API_URL, queryPrices, {});
38+
39+
const liquidStakingExchangeRate = calcLiquidStakingExchangeRate(
40+
queryLiquidStaking?.[0]?.state?.cashReserve,
41+
queryLiquidStaking?.[0]?.state?.totalShares,
42+
);
43+
44+
const queryTokenPopulated = queryToken
45+
.filter(
46+
({ dailyPriceHistory, symbol }) =>
47+
dailyPriceHistory.length > 0 || symbol === 'EGLD',
48+
)
49+
.map((tokenItem) => {
50+
const filteredToken = tokenItem.dailyPriceHistory;
51+
52+
const priceEgld = filteredToken?.[0]?.quote?.priceInEgld || '0';
53+
54+
let dailyPriceInEgld = '0';
55+
56+
if (tokenItem.symbol == 'EGLD') {
57+
dailyPriceInEgld = '1';
58+
} else if (tokenItem.symbol == 'SEGLD') {
59+
dailyPriceInEgld = new BigNumber(1).multipliedBy(liquidStakingExchangeRate).dividedBy(BigNumber.WAD).toString();
60+
} else {
61+
dailyPriceInEgld = priceEgld;
62+
}
63+
64+
const dailyPriceInUSD = filteredToken?.[0]?.price?.price || '0';
65+
66+
return {
67+
...tokenItem,
68+
dailyPriceInEgld,
69+
dailyPriceInUSD,
70+
};
71+
});
72+
73+
const itemEgldInUSD = queryTokenPopulated.find(
74+
({ symbol }) => symbol === 'EGLD',
75+
);
76+
const itemEgldInUSDC = queryTokenPopulated.find(
77+
({ symbol }) => symbol === 'USDC',
78+
);
79+
80+
const agregatorEGLDInUSD = new BigNumber(
81+
itemEgldInUSD?.dailyPriceInUSD || '0',
82+
)
83+
.dividedBy(`1e${18}`)
84+
.toString();
85+
86+
const priceHistoryEGLDInUSDC =
87+
new BigNumber(1)
88+
.dividedBy(itemEgldInUSDC?.dailyPriceInEgld || 0)
89+
.toString() || '0';
90+
91+
const usdcPriceInEgld = new BigNumber(agregatorEGLDInUSD).isZero()
92+
? priceHistoryEGLDInUSDC
93+
: agregatorEGLDInUSD;
94+
95+
const egldInUsdc = usdcPriceInEgld !== '0' ? usdcPriceInEgld : '0';
96+
97+
return queryTokenPopulated.reduce(
98+
(prev, { dailyPriceInEgld, dailyPriceInUSD, symbol }) => {
99+
const priceUSD =
100+
!new BigNumber(egldInUsdc).isEqualTo('0') ||
101+
!new BigNumber(dailyPriceInEgld).isEqualTo('0')
102+
? new BigNumber(egldInUsdc)
103+
.multipliedBy(dailyPriceInEgld)
104+
.toString()
105+
: '0';
106+
107+
const value = !new BigNumber(dailyPriceInUSD).isZero()
108+
? new BigNumber(dailyPriceInUSD).dividedBy(`1e${18}`).toString()
109+
: priceUSD;
110+
111+
return {
112+
...prev,
113+
[symbol]: value,
114+
};
115+
},
116+
{},
117+
)
118+
}
119+
120+
function getExchangeRates(moneyMarkets) {
121+
const symbols = Object.keys(moneyMarkets);
122+
return symbols.reduce(
123+
(prev, symbol) => {
124+
const value = calcSimulateExchangeRate({
125+
cash: moneyMarkets[symbol].cash || '0',
126+
borrows:
127+
moneyMarkets[symbol].borrows || '0',
128+
reserves:
129+
moneyMarkets[symbol].reserves || '0',
130+
totalSupply:
131+
moneyMarkets[symbol].totalSupply || '0',
132+
rate:
133+
moneyMarkets[symbol].supplyRatePerSecond || '0',
134+
timestamp:
135+
moneyMarkets[symbol].timestamp || new Date().toISOString(),
136+
})
137+
return {
138+
...prev,
139+
[symbol]: value
140+
}
141+
},
142+
{},
143+
);
144+
}
145+
146+
async function getRewardsBatches() {
147+
const response = await request(API_URL, queryRewards, {});
148+
return response.queryRewardsBatchState.reduce((prev, batch) => {
149+
const symbol = batch.moneyMarket.underlying.symbol;
150+
const value = {
151+
id: batch.id,
152+
speed: batch.speed,
153+
type: batch.type,
154+
endTime: batch.endTime,
155+
fullyDistributed: batch.fullyDistributed,
156+
totalAmount: batch.totalAmount,
157+
rewardsToken: batch.rewardsToken,
158+
}
159+
return {
160+
...prev,
161+
[symbol]: value,
162+
};
163+
}, {})
164+
}
165+
166+
module.exports = {
167+
getMoneyMarkets,
168+
getTokenPrices,
169+
getExchangeRates,
170+
getRewardsBatches,
171+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const BigNumber = require('bignumber.js');
2+
3+
const calcRewardsAPY = ({
4+
speed,
5+
hTokenExchangeRate,
6+
totalCollateral,
7+
marketPrice,
8+
rewardsToken,
9+
rewardsTokenPrice,
10+
marketDecimals,
11+
}) => {
12+
const SECONDS_PER_DAY = new BigNumber(86400);
13+
const DAYS_PER_YEAR = new BigNumber(365);
14+
const secondsInAYear = new BigNumber(SECONDS_PER_DAY).multipliedBy(
15+
DAYS_PER_YEAR,
16+
);
17+
18+
const sp = new BigNumber(speed).dividedBy(`1e${18 + rewardsToken?.decimals}`);
19+
20+
const calc1 = sp
21+
.multipliedBy(rewardsTokenPrice)
22+
.multipliedBy(secondsInAYear);
23+
24+
const calc2 = new BigNumber(totalCollateral)
25+
.multipliedBy(hTokenExchangeRate)
26+
.dividedBy(`1e18`)
27+
.dividedBy(`1e${marketDecimals}`)
28+
.multipliedBy(marketPrice);
29+
30+
if (calc2.isEqualTo(0)) {
31+
return '0';
32+
}
33+
34+
const result = calc1.dividedBy(calc2).multipliedBy(100);
35+
36+
return result.isNaN() ? '0' : result.toString();
37+
};
38+
39+
const calcSimulateExchangeRate = ({
40+
cash,
41+
borrows,
42+
reserves,
43+
totalSupply,
44+
rate,
45+
timestamp,
46+
}) => {
47+
return new BigNumber(
48+
calcExchangeRate({ cash, borrows, reserves, totalSupply }),
49+
)
50+
.multipliedBy(calcRateSimulate(rate, timestamp))
51+
.toString();
52+
};
53+
54+
const calcRateSimulate = (rate, timestamp) => {
55+
const currentDate = new Date();
56+
const currentDateInSeconds = currentDate.getTime() / 1000;
57+
const timestampInSeconds = new Date(timestamp).getTime() / 1000;
58+
59+
return new BigNumber(rate)
60+
.multipliedBy(currentDateInSeconds - timestampInSeconds)
61+
.dividedBy(`1e18`)
62+
.plus(1)
63+
.toString();
64+
};
65+
66+
const calcExchangeRate = ({ cash, borrows, reserves, totalSupply }) => {
67+
const value = new BigNumber(cash)
68+
.plus(borrows)
69+
.minus(reserves)
70+
.times(1e18)
71+
.div(totalSupply)
72+
.toFixed(0);
73+
74+
return new BigNumber(value).isNaN() ? '0' : value;
75+
};
76+
77+
const calcLiquidStakingExchangeRate = (cashReserve, totalShares) => {
78+
if (totalShares === '0') {
79+
return INITIAL_EXCHANGE_RATE;
80+
}
81+
82+
return new BigNumber(cashReserve)
83+
.multipliedBy(`1e18`)
84+
.dividedBy(totalShares)
85+
.toFixed(0, BigNumber.ROUND_DOWN);
86+
};
87+
88+
module.exports = {
89+
calcRewardsAPY,
90+
calcLiquidStakingExchangeRate,
91+
calcSimulateExchangeRate,
92+
};

0 commit comments

Comments
 (0)