Skip to content

Commit

Permalink
Merge pull request #23 from eco-stake/improve-param-checks
Browse files Browse the repository at this point in the history
Improve APR estimation and refactor params update
  • Loading branch information
tombeynon authored Jun 25, 2022
2 parents e1969a5 + 1cb9505 commit 4c54e7f
Showing 1 changed file with 96 additions and 138 deletions.
234 changes: 96 additions & 138 deletions chains/chainMonitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PQueue from 'p-queue';
import got from 'got';
import _ from 'lodash'
import Agent from 'agentkeepalive'
import { bignumber, divide, format } from 'mathjs'
import { bignumber, multiply, divide, format } from 'mathjs'
import { debugLog, timeStamp } from '../utils.js';

function ChainMonitor() {
Expand All @@ -11,6 +11,11 @@ function ChainMonitor() {
https: new Agent.HttpsAgent({ maxSockets: 20 })
}
const queue = new PQueue({ concurrency: 10 });
const axiosOpts = {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}

async function refreshChains(client, chains) {
timeStamp('Running chain update');
Expand Down Expand Up @@ -40,18 +45,22 @@ function ChainMonitor() {
const { denom } = chain
try {
const authzParams = await getAuthzParams(restUrl)
const blockParams = await getBlockParams(restUrl, chain)
const stakingParams = await getStakingParams(restUrl, chain)
let supplyParams, mintParams
const blockParams = await getBlockParams(restUrl, chain), { actualBlocksPerYear } = blockParams
const stakingParams = await getStakingParams(restUrl, chain) || {}, { bondedTokens } = stakingParams
let supplyParams
if (denom) {
supplyParams = await getSupplyParams(restUrl, chain, stakingParams?.bondedTokens)
mintParams = await getMintParams(restUrl, chain, supplyParams?.totalSupply, supplyParams?.bondedRatio, blockParams?.actualBlocksPerYear)
supplyParams = await getSupplyParams(restUrl, chain, bondedTokens)
}
const data = { ...current, ...authzParams, ...blockParams, ...stakingParams, ...supplyParams, ...mintParams }
const mintParams = await getMintParams(restUrl, chain) || {}, { blocksPerYear } = mintParams
const distributionParams = await getDistributionParams(restUrl, chain) || {}, { communityTax } = distributionParams
const provisionParams = await getProvisionParams(restUrl, chain, supplyParams) || {}, { annualProvision } = provisionParams
const aprParams = await calculateApr(chain, annualProvision, bondedTokens, communityTax, blocksPerYear, actualBlocksPerYear)
const data = { ...current, ...authzParams, ...blockParams, ...stakingParams, ...supplyParams, ...mintParams, ...distributionParams, ...provisionParams, ...aprParams }
return _.mapKeys({
...data,
bondedTokens: data.bondedTokens && formatNumber(data.bondedTokens),
totalSupply: data.totalSupply && formatNumber(data.totalSupply)
bondedTokens: formatNumber(data.bondedTokens),
totalSupply: formatNumber(data.totalSupply),
annualProvision: formatNumber(data.annualProvision)
}, (_value, key) => _.snakeCase(key))
} catch (error) {
timeStamp(chain.path, 'Update failed', error.message)
Expand All @@ -60,11 +69,7 @@ function ChainMonitor() {

async function getAuthzParams(restUrl) {
try {
await got.get(restUrl + 'cosmos/authz/v1beta1/grants', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
})
await got.get(restUrl + 'cosmos/authz/v1beta1/grants', axiosOpts)
} catch (error) {
if ([400, 500].includes(error.response?.statusCode)) {
return { authz: true }
Expand All @@ -76,18 +81,10 @@ function ChainMonitor() {

async function getBlockParams(restUrl, chain) {
try {
const currentBlock = await got.get(restUrl + 'blocks/latest', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json()
const currentBlock = await got.get(restUrl + 'blocks/latest', axiosOpts).json()
const currentBlockTime = new Date(currentBlock.block.header.time) / 1000
const currentBlockHeight = currentBlock.block.header.height
const prevBlock = await got.get(restUrl + 'blocks/' + (currentBlockHeight - 100), {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json()
const prevBlock = await got.get(restUrl + 'blocks/' + (currentBlockHeight - 100), axiosOpts).json()
const prevBlockTime = new Date(prevBlock.block.header.time) / 1000
const prevBlockHeight = prevBlock.block.header.height
const actualBlockTime = (currentBlockTime - prevBlockTime) / (currentBlockHeight - prevBlockHeight)
Expand All @@ -101,18 +98,10 @@ function ChainMonitor() {

async function getStakingParams(restUrl, chain) {
try {
const staking = await got.get(restUrl + 'cosmos/staking/v1beta1/params', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json();
const staking = await got.get(restUrl + 'cosmos/staking/v1beta1/params', axiosOpts).json();
const unbondingTime = parseInt(staking.params.unbonding_time.replace('s', ''))
const maxValidators = staking.params.max_validators
const pool = await got.get(restUrl + 'cosmos/staking/v1beta1/pool', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json();
const pool = await got.get(restUrl + 'cosmos/staking/v1beta1/pool', axiosOpts).json();
const bondedTokens = bignumber(pool.pool.bonded_tokens);
return {
unbondingTime,
Expand All @@ -122,127 +111,96 @@ function ChainMonitor() {
} catch (e) { timeStamp(chain.path, 'Staking check failed', e.message) }
}

async function getSupplyParams(restUrl, chain, bondedTokens) {
async function getMintParams(restUrl, chain) {
try {
const { denom } = chain
const supply = await got.get(restUrl + 'cosmos/bank/v1beta1/supply/' + denom, {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json();
const totalSupply = bignumber(supply.amount.amount);
const bondedRatio = bondedTokens && parseFloat(divide(bondedTokens, totalSupply))
return {
totalSupply,
bondedRatio
}
} catch (e) { timeStamp(chain.path, 'Supply check failed', e.message) }
const mint = await got.get(restUrl + 'cosmos/mint/v1beta1/params', axiosOpts).json();
const blocksPerYear = parseInt(mint.params.blocks_per_year)
const blockTime = (365.3 * 24 * 60 * 60) / blocksPerYear
const req = await got.get(restUrl + 'cosmos/mint/v1beta1/inflation', axiosOpts).json()
const baseInflation = parseFloat(req.inflation);
return {
blocksPerYear,
blockTime,
baseInflation,
}
} catch (e) { timeStamp(chain.path, 'Mint check failed', e.message) }
}

async function getDistributionParams(restUrl, chain){
try {
const distribution = await got.get(restUrl + 'cosmos/distribution/v1beta1/params', axiosOpts).json();
const communityTax = parseFloat(distribution.params.community_tax)
return { communityTax }
} catch (e) { timeStamp(chain.path, 'Distribution check failed', e.message) }
}

async function getMintParams(restUrl, chain, totalSupply, bondedRatio, actualBlocksPerYear) {
async function getProvisionParams(restUrl, chain, supplyParams){
const path = chain.path
try {
if (path === 'osmosis') {
return await getOsmosisParams(restUrl, totalSupply, bondedRatio)
} else if (path === 'sifchain') {
const aprRequest = await got.get(
"https://data.sifchain.finance/beta/validator/stakingRewards", {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
if(path === 'crescent') {
const params = await got.get('https://apigw.crescent.network/params', axiosOpts).json();
const provison = params['data'].find(el => el.key === 'liquidstaking.total_reward_ucre_amount_per_year')?.value
return { annualProvision: bignumber(provison) }
} else if(path === 'emoney'){
return { annualProvision: supplyParams.totalSupply * 0.1 }
} else if(path === 'evmos'){
const params = await got.get(restUrl + 'evmos/inflation/v1/params', axiosOpts).json();
const provision = await got.get(restUrl + 'evmos/inflation/v1/epoch_mint_provision', axiosOpts).json();
return { annualProvision: multiply(bignumber(provision.epoch_mint_provision.amount), 365.3, params.params.inflation_distribution.staking_rewards) }
} else if(path === 'osmosis'){
const params = await got.get(restUrl + 'osmosis/mint/v1beta1/params', axiosOpts).json();
const provision = await got.get(restUrl + 'osmosis/mint/v1beta1/epoch_provisions', axiosOpts).json();
return { annualProvision: multiply(bignumber(provision.epoch_provisions), 365.3, params.params.distribution_proportions.staking) }
} else if(path === 'stargaze'){
const params = await got.get(restUrl + 'minting/annual-provisions', axiosOpts).json();
return { annualProvision: multiply(params.result, 0.5) }
} else {
try {
const params = await got.get(restUrl + 'cosmos/mint/v1beta1/annual_provisions', axiosOpts).json();
return { annualProvision: bignumber(params.annual_provisions) }
} catch (e) {
const params = await got.get(restUrl + 'minting/annual-provisions', axiosOpts).json();
return { annualProvision: bignumber(params.result) }
}
).json();
}
} catch (e) { timeStamp(path, 'Provision check failed', e.message) }
}

async function calculateApr(chain, annualProvision, bondedTokens, communityTax, blocksPerYear, actualBlocksPerYear){
const path = chain.path
try {
if (path === 'sifchain') {
const aprRequest = await got.get("https://data.sifchain.finance/beta/validator/stakingRewards", axiosOpts).json();
return {
calculatedApr: aprRequest.rate
}
} else {
const mint = await got.get(restUrl + 'cosmos/mint/v1beta1/params', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json();
const blocksPerYear = parseInt(mint.params.blocks_per_year)
const blockTime = (365 * 24 * 60 * 60) / blocksPerYear
const distribution = await got.get(restUrl + 'cosmos/distribution/v1beta1/params', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json();
const communityTax = parseFloat(distribution.params.community_tax)
const req = await got.get(restUrl + 'cosmos/mint/v1beta1/inflation', {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}).json()
const baseInflation = parseFloat(req.inflation);
let estimatedApr, calculatedApr
if (baseInflation > 0 && bondedRatio) {
estimatedApr = baseInflation > 0 ? ((baseInflation / bondedRatio) - communityTax) : 0
calculatedApr = estimatedApr * (actualBlocksPerYear / blocksPerYear)
}
return {
blocksPerYear,
blockTime,
communityTax,
baseInflation,
estimatedApr,
calculatedApr
const estimatedApr = (annualProvision / bondedTokens) * (1 - communityTax)
if(blocksPerYear){
const calculatedApr = estimatedApr * (actualBlocksPerYear / blocksPerYear)
return { estimatedApr, calculatedApr }
}else{
return { estimatedApr, calculatedApr: estimatedApr }
}
}
} catch (e) { timeStamp(path, 'Mint check failed', e.message) }
} catch (e) { timeStamp(path, 'APR check failed', e.message) }
}

async function getOsmosisParams(restUrl, totalSupply, bondedRatio) {
const mintParams = await got.get(
restUrl + "/osmosis/mint/v1beta1/params", {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}
).json();
const osmosisEpochs = await got.get(
restUrl + "/osmosis/epochs/v1beta1/epochs", {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}
).json();
const epochProvisions = await got.get(
restUrl + "/osmosis/mint/v1beta1/epoch_provisions", {
timeout: { request: 5000 },
retry: { limit: 3 },
agent: agent
}
).json();
const { params } = mintParams;
const { epochs } = osmosisEpochs;
const { epoch_provisions } = epochProvisions;
const mintingEpochProvision = parseFloat(params.distribution_proportions.staking) * epoch_provisions;
const epochDuration = duration(epochs, params.epoch_identifier);
const yearMintingProvision = (mintingEpochProvision * (365 * 24 * 3600)) / epochDuration;
const baseInflation = totalSupply && yearMintingProvision / totalSupply;
const calculatedApr = bondedRatio && baseInflation / bondedRatio;
return {
mintingEpochProvision,
epochDuration,
yearMintingProvision,
baseInflation,
calculatedApr
};
async function getSupplyParams(restUrl, chain, bondedTokens) {
try {
const { denom } = chain
const supply = await got.get(restUrl + 'cosmos/bank/v1beta1/supply/' + denom, axiosOpts).json();
const totalSupply = bignumber(supply.amount.amount);
const bondedRatio = bondedTokens && parseFloat(divide(bondedTokens, totalSupply))
return {
totalSupply,
bondedRatio
}
} catch (e) { timeStamp(chain.path, 'Supply check failed', e.message) }
}

function formatNumber(number) {
return format(number, { notation: 'fixed' })
}

function duration(epochs, epochIdentifier) {
const epoch = epochs.find((epoch) => epoch.identifier === epochIdentifier);
if (!epoch) {
return 0;
}

// Actually, the date type of golang protobuf is returned by the unit of seconds.
return parseInt(epoch.duration.replace("s", ""));
return number && format(number, { notation: 'fixed' })
}

return {
Expand Down

0 comments on commit 4c54e7f

Please sign in to comment.