-
Notifications
You must be signed in to change notification settings - Fork 840
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
a0e2a40
commit 81b833f
Showing
4 changed files
with
398 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
Oops, something went wrong.