Skip to content

Commit

Permalink
Adding hatom lending (#1117)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
amilcarrey authored Dec 20, 2023
1 parent a0e2a40 commit 81b833f
Show file tree
Hide file tree
Showing 4 changed files with 398 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/adaptors/hatom-lending/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const BigNumber = require('bignumber.js');
const utils = require('../utils');
const { calcRewardsAPY } = require('./utils/math.js');
const { getMoneyMarkets, getTokenPrices, getExchangeRates, getRewardsBatches, getBoostedRewards, getBoostedColateralMap } = require('./utils/data.js');

const MARKETS = [
{ symbol: "USDC", address: "erd1qqqqqqqqqqqqqpgqkrgsvct7hfx7ru30mfzk3uy6pxzxn6jj78ss84aldu" },
{ symbol: 'WBTC', address: "erd1qqqqqqqqqqqqqpgqg47t8v5nwzvdxgf6g5jkxleuplu8y4f678ssfcg5gy" },
{ symbol: "WETH", address: "erd1qqqqqqqqqqqqqpgq8h8upp38fe9p4ny9ecvsett0usu2ep7978ssypgmrs" },
{ symbol: "USDT", address: "erd1qqqqqqqqqqqqqpgqvxn0cl35r74tlw2a8d794v795jrzfxyf78sstg8pjr" }
]

const apy = async () => {
const [mm, prices, rewards] = await Promise.all([
getMoneyMarkets(),
getTokenPrices(),
getRewardsBatches(),
]);
const exchangeRates = getExchangeRates(mm)

return MARKETS.map(({ symbol }) => {
const currentMM = mm[symbol]
const currentPrice = prices[symbol]
const currentExchangeRate = exchangeRates[symbol]
const currentRewards = rewards[symbol]

const rewardsAPY = calcRewardsAPY({
speed: currentRewards.speed,
hTokenExchangeRate: currentExchangeRate,
totalCollateral: currentMM.totalColateral.toString(),
marketPrice: currentPrice,
rewardsToken: currentRewards.rewardsToken,
rewardsTokenPrice: prices[currentRewards.rewardsToken.symbol],
marketDecimals: currentMM.decimals
})

const tvlUsd = new BigNumber(currentMM.cash).multipliedBy(currentPrice).dividedBy(`1e${currentMM.decimals}`).toNumber()
const apyBase = mm[symbol].supplyAPY
const apyReward = new BigNumber(rewardsAPY).toNumber()
return {
pool: symbol,
chain: 'MultiversX',
project: 'hatom-lending',
symbol: symbol,
tvlUsd: tvlUsd,
apyBase: apyBase,
apyReward: apyReward,
rewardTokens: [currentRewards.rewardsToken.symbol],
}
})
}

module.exports = {
timetravel: false,
apy: apy,
url: 'https://app.hatom.com/lend',
};
171 changes: 171 additions & 0 deletions src/adaptors/hatom-lending/utils/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
const BigNumber = require('bignumber.js');
const { default: axios } = require('axios');
const { request } = require('graphql-request');

const { queryPrices, queryMoneyMarkets, queryRewards } = require('./queries')
const { calcLiquidStakingExchangeRate, calcSimulateExchangeRate } = require('./math');

const API_URL = 'https://mainnet-api.hatom.com/graphql';

async function getMoneyMarkets() {
const response = await request(API_URL, queryMoneyMarkets, {});
return response.queryMoneyMarket.reduce((prev, market) => {
const symbol = market.underlying.symbol;
const value = {
address: market.address,
decimals: market.underlying.decimals,
cash: market.stateHistory[0].cash,
borrows: market.stateHistory[0].borrows,
reserves: market.stateHistory[0].reserves,
rate: market.stateHistory[0].supplyRatePerSecond,
timestamp: market.stateHistory[0].timestamp,
totalSupply: market.stateHistory[0].totalSupply,
borrowRatePerSecond: market.stateHistory[0].borrowRatePerSecond,
supplyAPY: market.stateHistory[0].supplyAPY,
supplyRatePerSecond: market.stateHistory[0].supplyRatePerSecond,
totalColateral: market.totalCollateral,
}
return {
...prev,
[symbol]: value,
};
}, {})
}

async function getTokenPrices() {
const { queryToken, queryLiquidStaking } =
await request(API_URL, queryPrices, {});

const liquidStakingExchangeRate = calcLiquidStakingExchangeRate(
queryLiquidStaking?.[0]?.state?.cashReserve,
queryLiquidStaking?.[0]?.state?.totalShares,
);

const queryTokenPopulated = queryToken
.filter(
({ dailyPriceHistory, symbol }) =>
dailyPriceHistory.length > 0 || symbol === 'EGLD',
)
.map((tokenItem) => {
const filteredToken = tokenItem.dailyPriceHistory;

const priceEgld = filteredToken?.[0]?.quote?.priceInEgld || '0';

let dailyPriceInEgld = '0';

if (tokenItem.symbol == 'EGLD') {
dailyPriceInEgld = '1';
} else if (tokenItem.symbol == 'SEGLD') {
dailyPriceInEgld = new BigNumber(1).multipliedBy(liquidStakingExchangeRate).dividedBy(BigNumber.WAD).toString();
} else {
dailyPriceInEgld = priceEgld;
}

const dailyPriceInUSD = filteredToken?.[0]?.price?.price || '0';

return {
...tokenItem,
dailyPriceInEgld,
dailyPriceInUSD,
};
});

const itemEgldInUSD = queryTokenPopulated.find(
({ symbol }) => symbol === 'EGLD',
);
const itemEgldInUSDC = queryTokenPopulated.find(
({ symbol }) => symbol === 'USDC',
);

const agregatorEGLDInUSD = new BigNumber(
itemEgldInUSD?.dailyPriceInUSD || '0',
)
.dividedBy(`1e${18}`)
.toString();

const priceHistoryEGLDInUSDC =
new BigNumber(1)
.dividedBy(itemEgldInUSDC?.dailyPriceInEgld || 0)
.toString() || '0';

const usdcPriceInEgld = new BigNumber(agregatorEGLDInUSD).isZero()
? priceHistoryEGLDInUSDC
: agregatorEGLDInUSD;

const egldInUsdc = usdcPriceInEgld !== '0' ? usdcPriceInEgld : '0';

return queryTokenPopulated.reduce(
(prev, { dailyPriceInEgld, dailyPriceInUSD, symbol }) => {
const priceUSD =
!new BigNumber(egldInUsdc).isEqualTo('0') ||
!new BigNumber(dailyPriceInEgld).isEqualTo('0')
? new BigNumber(egldInUsdc)
.multipliedBy(dailyPriceInEgld)
.toString()
: '0';

const value = !new BigNumber(dailyPriceInUSD).isZero()
? new BigNumber(dailyPriceInUSD).dividedBy(`1e${18}`).toString()
: priceUSD;

return {
...prev,
[symbol]: value,
};
},
{},
)
}

function getExchangeRates(moneyMarkets) {
const symbols = Object.keys(moneyMarkets);
return symbols.reduce(
(prev, symbol) => {
const value = calcSimulateExchangeRate({
cash: moneyMarkets[symbol].cash || '0',
borrows:
moneyMarkets[symbol].borrows || '0',
reserves:
moneyMarkets[symbol].reserves || '0',
totalSupply:
moneyMarkets[symbol].totalSupply || '0',
rate:
moneyMarkets[symbol].supplyRatePerSecond || '0',
timestamp:
moneyMarkets[symbol].timestamp || new Date().toISOString(),
})
return {
...prev,
[symbol]: value
}
},
{},
);
}

async function getRewardsBatches() {
const response = await request(API_URL, queryRewards, {});
return response.queryRewardsBatchState.reduce((prev, batch) => {
const symbol = batch.moneyMarket.underlying.symbol;
const value = {
id: batch.id,
speed: batch.speed,
type: batch.type,
endTime: batch.endTime,
fullyDistributed: batch.fullyDistributed,
totalAmount: batch.totalAmount,
rewardsToken: batch.rewardsToken,
}
return {
...prev,
[symbol]: value,
};
}, {})
}

module.exports = {
getMoneyMarkets,
getTokenPrices,
getExchangeRates,
getRewardsBatches,
}
92 changes: 92 additions & 0 deletions src/adaptors/hatom-lending/utils/math.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const BigNumber = require('bignumber.js');

const calcRewardsAPY = ({
speed,
hTokenExchangeRate,
totalCollateral,
marketPrice,
rewardsToken,
rewardsTokenPrice,
marketDecimals,
}) => {
const SECONDS_PER_DAY = new BigNumber(86400);
const DAYS_PER_YEAR = new BigNumber(365);
const secondsInAYear = new BigNumber(SECONDS_PER_DAY).multipliedBy(
DAYS_PER_YEAR,
);

const sp = new BigNumber(speed).dividedBy(`1e${18 + rewardsToken?.decimals}`);

const calc1 = sp
.multipliedBy(rewardsTokenPrice)
.multipliedBy(secondsInAYear);

const calc2 = new BigNumber(totalCollateral)
.multipliedBy(hTokenExchangeRate)
.dividedBy(`1e18`)
.dividedBy(`1e${marketDecimals}`)
.multipliedBy(marketPrice);

if (calc2.isEqualTo(0)) {
return '0';
}

const result = calc1.dividedBy(calc2).multipliedBy(100);

return result.isNaN() ? '0' : result.toString();
};

const calcSimulateExchangeRate = ({
cash,
borrows,
reserves,
totalSupply,
rate,
timestamp,
}) => {
return new BigNumber(
calcExchangeRate({ cash, borrows, reserves, totalSupply }),
)
.multipliedBy(calcRateSimulate(rate, timestamp))
.toString();
};

const calcRateSimulate = (rate, timestamp) => {
const currentDate = new Date();
const currentDateInSeconds = currentDate.getTime() / 1000;
const timestampInSeconds = new Date(timestamp).getTime() / 1000;

return new BigNumber(rate)
.multipliedBy(currentDateInSeconds - timestampInSeconds)
.dividedBy(`1e18`)
.plus(1)
.toString();
};

const calcExchangeRate = ({ cash, borrows, reserves, totalSupply }) => {
const value = new BigNumber(cash)
.plus(borrows)
.minus(reserves)
.times(1e18)
.div(totalSupply)
.toFixed(0);

return new BigNumber(value).isNaN() ? '0' : value;
};

const calcLiquidStakingExchangeRate = (cashReserve, totalShares) => {
if (totalShares === '0') {
return INITIAL_EXCHANGE_RATE;
}

return new BigNumber(cashReserve)
.multipliedBy(`1e18`)
.dividedBy(totalShares)
.toFixed(0, BigNumber.ROUND_DOWN);
};

module.exports = {
calcRewardsAPY,
calcLiquidStakingExchangeRate,
calcSimulateExchangeRate,
};
Loading

0 comments on commit 81b833f

Please sign in to comment.