diff --git a/src/abis/IBeefyRewardPool.ts b/src/abis/IBeefyRewardPool.ts index e968bb404..fce1b80d7 100644 --- a/src/abis/IBeefyRewardPool.ts +++ b/src/abis/IBeefyRewardPool.ts @@ -2,136 +2,75 @@ import { Abi } from 'abitype'; export const IBeefyRewardPool = [ { - inputs: [ - { - internalType: 'address', - name: 'caller', - type: 'address', - }, - ], + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], name: 'NotManager', type: 'error', }, { - inputs: [ - { - internalType: 'address', - name: 'reward', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: 'reward', type: 'address' }], name: 'RewardNotFound', type: 'error', }, { - inputs: [ - { - internalType: 'uint256', - name: 'duration', - type: 'uint256', - }, - ], + inputs: [{ internalType: 'uint256', name: 'duration', type: 'uint256' }], name: 'ShortDuration', type: 'error', }, - { - inputs: [], - name: 'StakedTokenIsNotAReward', - type: 'error', - }, + { inputs: [], name: 'StakedTokenIsNotAReward', type: 'error' }, { inputs: [], name: 'TooManyRewards', type: 'error', }, { - inputs: [ - { - internalType: 'address', - name: 'reward', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: 'reward', type: 'address' }], name: 'WithdrawingRewardToken', type: 'error', }, - { - inputs: [], - name: 'WithdrawingStakedToken', - type: 'error', - }, + { inputs: [], name: 'WithdrawingStakedToken', type: 'error' }, { anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'address', - name: 'reward', - type: 'address', - }, - ], + inputs: [{ indexed: false, internalType: 'address', name: 'reward', type: 'address' }], name: 'AddReward', type: 'event', }, { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, { indexed: true, internalType: 'address', name: 'spender', type: 'address', }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, ], name: 'Approval', type: 'event', }, { anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'uint8', - name: 'version', - type: 'uint8', - }, - ], + inputs: [{ indexed: false, internalType: 'uint8', name: 'version', type: 'uint8' }], name: 'Initialized', type: 'event', }, { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'reward', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'reward', type: 'address' }, { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256', }, - { - indexed: false, - internalType: 'uint256', - name: 'duration', - type: 'uint256', - }, + { indexed: false, internalType: 'uint256', name: 'duration', type: 'uint256' }, ], name: 'NotifyReward', type: 'event', @@ -145,44 +84,21 @@ export const IBeefyRewardPool = [ name: 'previousOwner', type: 'address', }, - { - indexed: true, - internalType: 'address', - name: 'newOwner', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, ], name: 'OwnershipTransferred', type: 'event', }, { anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'address', - name: 'reward', - type: 'address', - }, - { - indexed: false, - internalType: 'address', - name: 'recipient', - type: 'address', - }, - ], + inputs: [{ indexed: false, internalType: 'address', name: 'reward', type: 'address' }], name: 'RemoveReward', type: 'event', }, { anonymous: false, inputs: [ - { - indexed: false, - internalType: 'address', - name: 'token', - type: 'address', - }, + { indexed: false, internalType: 'address', name: 'token', type: 'address' }, { indexed: false, internalType: 'address', @@ -196,24 +112,14 @@ export const IBeefyRewardPool = [ { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'user', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, { indexed: true, internalType: 'address', name: 'reward', type: 'address', }, - { - indexed: false, - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, + { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256' }, ], name: 'RewardPaid', type: 'event', @@ -221,12 +127,7 @@ export const IBeefyRewardPool = [ { anonymous: false, inputs: [ - { - indexed: false, - internalType: 'address', - name: 'manager', - type: 'address', - }, + { indexed: false, internalType: 'address', name: 'manager', type: 'address' }, { indexed: false, internalType: 'bool', @@ -240,12 +141,7 @@ export const IBeefyRewardPool = [ { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'user', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, { indexed: false, internalType: 'uint256', @@ -259,24 +155,14 @@ export const IBeefyRewardPool = [ { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, { indexed: true, internalType: 'address', name: 'to', type: 'address', }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, ], name: 'Transfer', type: 'event', @@ -284,12 +170,7 @@ export const IBeefyRewardPool = [ { anonymous: false, inputs: [ - { - indexed: true, - internalType: 'address', - name: 'user', - type: 'address', - }, + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, { indexed: false, internalType: 'uint256', @@ -302,11 +183,7 @@ export const IBeefyRewardPool = [ }, { inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, + { internalType: 'address', name: 'owner', type: 'address' }, { internalType: 'address', name: 'spender', @@ -314,23 +191,13 @@ export const IBeefyRewardPool = [ }, ], name: 'allowance', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, + { internalType: 'address', name: 'spender', type: 'address' }, { internalType: 'uint256', name: 'amount', @@ -338,55 +205,27 @@ export const IBeefyRewardPool = [ }, ], name: 'approve', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'decimals', - outputs: [ - { - internalType: 'uint8', - name: '', - type: 'uint8', - }, - ], + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, + { internalType: 'address', name: 'spender', type: 'address' }, { internalType: 'uint256', name: 'subtractedValue', @@ -394,24 +233,12 @@ export const IBeefyRewardPool = [ }, ], name: 'decreaseAllowance', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { - inputs: [ - { - internalType: 'address', - name: '_user', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: '_user', type: 'address' }], name: 'earned', outputs: [ { @@ -419,22 +246,14 @@ export const IBeefyRewardPool = [ name: 'rewardTokens', type: 'address[]', }, - { - internalType: 'uint256[]', - name: 'earnedAmounts', - type: 'uint256[]', - }, + { internalType: 'uint256[]', name: 'earnedAmounts', type: 'uint256[]' }, ], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: '_user', - type: 'address', - }, + { internalType: 'address', name: '_user', type: 'address' }, { internalType: 'address', name: '_reward', @@ -442,23 +261,11 @@ export const IBeefyRewardPool = [ }, ], name: 'earned', - outputs: [ - { - internalType: 'uint256', - name: 'earnedAmount', - type: 'uint256', - }, - ], + outputs: [{ internalType: 'uint256', name: 'earnedAmount', type: 'uint256' }], stateMutability: 'view', type: 'function', }, - { - inputs: [], - name: 'exit', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, + { inputs: [], name: 'exit', outputs: [], stateMutability: 'nonpayable', type: 'function' }, { inputs: [], name: 'getReward', @@ -468,11 +275,7 @@ export const IBeefyRewardPool = [ }, { inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, + { internalType: 'address', name: 'spender', type: 'address' }, { internalType: 'uint256', name: 'addedValue', @@ -480,23 +283,19 @@ export const IBeefyRewardPool = [ }, ], name: 'increaseAllowance', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { inputs: [ + { internalType: 'address', name: '_stakedToken', type: 'address' }, { - internalType: 'address', - name: '_stakedToken', - type: 'address', + internalType: 'string', + name: '_name', + type: 'string', }, + { internalType: 'string', name: '_symbol', type: 'string' }, ], name: 'initialize', outputs: [], @@ -506,33 +305,19 @@ export const IBeefyRewardPool = [ { inputs: [], name: 'name', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], + outputs: [{ internalType: 'string', name: '', type: 'string' }], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: '_reward', - type: 'address', - }, + { internalType: 'address', name: '_reward', type: 'address' }, { internalType: 'uint256', name: '_amount', type: 'uint256', }, - { - internalType: 'uint256', - name: '_duration', - type: 'uint256', - }, + { internalType: 'uint256', name: '_duration', type: 'uint256' }, ], name: 'notifyRewardAmount', outputs: [], @@ -542,29 +327,12 @@ export const IBeefyRewardPool = [ { inputs: [], name: 'owner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], + outputs: [{ internalType: 'address', name: '', type: 'address' }], stateMutability: 'view', type: 'function', }, { - inputs: [ - { - internalType: 'address', - name: '_reward', - type: 'address', - }, - { - internalType: 'address', - name: '_recipient', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: '_reward', type: 'address' }], name: 'removeReward', outputs: [], stateMutability: 'nonpayable', @@ -579,11 +347,7 @@ export const IBeefyRewardPool = [ }, { inputs: [ - { - internalType: 'address', - name: '_token', - type: 'address', - }, + { internalType: 'address', name: '_token', type: 'address' }, { internalType: 'address', name: '_recipient', @@ -596,70 +360,43 @@ export const IBeefyRewardPool = [ type: 'function', }, { - inputs: [ - { - internalType: 'uint256', - name: '_rewardId', - type: 'uint256', - }, - ], + inputs: [{ internalType: 'uint256', name: '_rewardId', type: 'uint256' }], name: 'rewardInfo', outputs: [ - { - internalType: 'address', - name: 'reward', - type: 'address', - }, + { internalType: 'address', name: 'reward', type: 'address' }, { internalType: 'uint256', name: 'periodFinish', type: 'uint256', }, - { - internalType: 'uint256', - name: 'duration', - type: 'uint256', - }, + { internalType: 'uint256', name: 'duration', type: 'uint256' }, { internalType: 'uint256', name: 'lastUpdateTime', type: 'uint256', }, - { - internalType: 'uint256', - name: 'rate', - type: 'uint256', - }, + { internalType: 'uint256', name: 'rate', type: 'uint256' }, ], stateMutability: 'view', type: 'function', }, { - inputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], name: 'rewards', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'rewardsLength', + outputs: [{ internalType: 'uint256', name: 'length', type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: '_manager', - type: 'address', - }, + { internalType: 'address', name: '_manager', type: 'address' }, { internalType: 'bool', name: '_whitelisted', @@ -672,13 +409,7 @@ export const IBeefyRewardPool = [ type: 'function', }, { - inputs: [ - { - internalType: 'uint256', - name: '_amount', - type: 'uint256', - }, - ], + inputs: [{ internalType: 'uint256', name: '_amount', type: 'uint256' }], name: 'stake', outputs: [], stateMutability: 'nonpayable', @@ -686,31 +417,19 @@ export const IBeefyRewardPool = [ }, { inputs: [ - { - internalType: 'address', - name: '_user', - type: 'address', - }, + { internalType: 'address', name: '_user', type: 'address' }, { internalType: 'uint256', name: '_amount', type: 'uint256', }, - { - internalType: 'uint256', - name: '_deadline', - type: 'uint256', - }, + { internalType: 'uint256', name: '_deadline', type: 'uint256' }, { internalType: 'uint8', name: '_v', type: 'uint8', }, - { - internalType: 'bytes32', - name: '_r', - type: 'bytes32', - }, + { internalType: 'bytes32', name: '_r', type: 'bytes32' }, { internalType: 'bytes32', name: '_s', @@ -725,49 +444,27 @@ export const IBeefyRewardPool = [ { inputs: [], name: 'stakedToken', - outputs: [ - { - internalType: 'contract IERC20Upgradeable', - name: '', - type: 'address', - }, - ], + outputs: [{ internalType: 'contract IERC20Upgradeable', name: '', type: 'address' }], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'symbol', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], + outputs: [{ internalType: 'string', name: '', type: 'string' }], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'totalSupply', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: '_to', - type: 'address', - }, + { internalType: 'address', name: '_to', type: 'address' }, { internalType: 'uint256', name: '_value', @@ -775,85 +472,41 @@ export const IBeefyRewardPool = [ }, ], name: 'transfer', - outputs: [ - { - internalType: 'bool', - name: 'success', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: 'success', type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { inputs: [ - { - internalType: 'address', - name: '_from', - type: 'address', - }, + { internalType: 'address', name: '_from', type: 'address' }, { internalType: 'address', name: '_to', type: 'address', }, - { - internalType: 'uint256', - name: '_value', - type: 'uint256', - }, + { internalType: 'uint256', name: '_value', type: 'uint256' }, ], name: 'transferFrom', - outputs: [ - { - internalType: 'bool', - name: 'success', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: 'success', type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { - inputs: [ - { - internalType: 'address', - name: 'newOwner', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], name: 'transferOwnership', outputs: [], stateMutability: 'nonpayable', type: 'function', }, { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], + inputs: [{ internalType: 'address', name: '', type: 'address' }], name: 'whitelisted', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], stateMutability: 'view', type: 'function', }, { - inputs: [ - { - internalType: 'uint256', - name: '_amount', - type: 'uint256', - }, - ], + inputs: [{ internalType: 'uint256', name: '_amount', type: 'uint256' }], name: 'withdraw', outputs: [], stateMutability: 'nonpayable', diff --git a/src/api/boosts/fetchBoostData.ts b/src/api/boosts/fetchBoostData.ts index c885e383a..2ca408d11 100644 --- a/src/api/boosts/fetchBoostData.ts +++ b/src/api/boosts/fetchBoostData.ts @@ -1,12 +1,20 @@ import { ChainId } from '../../../packages/address-book/src/address-book'; -import BigNumber from 'bignumber.js'; -import { ApiChain } from '../../utils/chain'; +import { ApiChain, toAppChain } from '../../utils/chain'; import { fetchContract } from '../rpc/client'; import BeefyBoostAbi from '../../abis/BeefyBoost'; -import { Boost } from './types'; +import { IBeefyRewardPool } from '../../abis/IBeefyRewardPool'; +import { Boost, BoostConfig } from './types'; +import { bigintRange } from '../../utils/array'; +import { bigintToNumber } from '../../utils/big-int'; -export const getBoosts = async chain => { - const boostsEndpoint = `https://raw.githubusercontent.com/beefyfinance/beefy-v2/prod/src/config/boost/${chain}.json`; +type BoostConfigRaw = Omit & { + version?: number; +}; + +export const getBoosts = async (chain: ApiChain): Promise => { + const boostsEndpoint = `https://raw.githubusercontent.com/beefyfinance/beefy-v2/prod/src/config/boost/${toAppChain( + chain + )}.json`; const response = await fetch(boostsEndpoint); if (response.status !== 200) { throw new Error( @@ -19,25 +27,42 @@ export const getBoosts = async chain => { throw new Error(`Invalid boosts data for ${chain}`); } - return (boosts as Boost[]).filter(b => !b.version || b.version === 1); + return (boosts as BoostConfigRaw[]).map(b => ({ + ...b, + version: b.version || 1, + chain, + })); }; -export const getBoostPeriodFinish = async (chain: ApiChain, boosts: any[]) => { +export const getBoostPeriodFinish = async ( + chain: ApiChain, + boosts: BoostConfig[] +): Promise => { const chainId = ChainId[chain]; + const periodFinishCalls = boosts.map(async (boost): Promise => { + if (boost.version >= 2) { + const poolContract = fetchContract(boost.earnContractAddress, IBeefyRewardPool, chainId); + const numRewards = await poolContract.read.rewardsLength(); + if (numRewards === 0n) { + return []; + } - const boostAddresses = boosts.map(v => v.earnContractAddress); - const periodFinishCalls = boostAddresses.map(boostAddress => { - const boostContract = fetchContract(boostAddress, BeefyBoostAbi, chainId); - return boostContract.read.periodFinish(); - }); - - const res = await Promise.all(periodFinishCalls); + return await Promise.all( + bigintRange(numRewards).map(async (rewardId): Promise => { + const rewardInfo = await poolContract.read.rewardInfo([rewardId]); + return bigintToNumber(rewardInfo[1]); + }) + ); + } - const periodFinishes = res.map(v => new BigNumber(v.toString()).toNumber()); - - for (let i = 0; i < periodFinishes.length; i++) { - boosts[i].periodFinish = periodFinishes[i]; - } + const boostContract = fetchContract(boost.earnContractAddress, BeefyBoostAbi, chainId); + return [bigintToNumber(await boostContract.read.periodFinish())]; + }); - return boosts; + const periodFinishes = await Promise.all(periodFinishCalls); + return boosts.map((boost, i) => ({ + ...boost, + periodFinish: periodFinishes[i].length ? Math.max(...periodFinishes[i]) : 0, + periodFinishes: periodFinishes[i], + })); }; diff --git a/src/api/boosts/getBoosts.ts b/src/api/boosts/getBoosts.ts index 3a9e2cd32..bc994dc55 100644 --- a/src/api/boosts/getBoosts.ts +++ b/src/api/boosts/getBoosts.ts @@ -1,10 +1,10 @@ import { MULTICHAIN_ENDPOINTS } from '../../constants'; import { getKey, setKey } from '../../utils/cache'; import { getBoostPeriodFinish, getBoosts } from './fetchBoostData'; -import { Boost } from './types'; +import { Boost, BoostConfig } from './types'; import { serviceEventBus } from '../../utils/ServiceEventBus'; import { isResultFulfilled, isResultRejected, withTimeout } from '../../utils/promise'; -import { ApiChain, toAppChain } from '../../utils/chain'; +import { ApiChain } from '../../utils/chain'; const REDIS_KEY = 'BOOSTS_BY_CHAIN'; @@ -67,10 +67,8 @@ function buildFromChains() { } async function updateChainBoosts(chain: ApiChain) { - let chainBoosts: Boost[] = await getBoosts(toAppChain(chain)); - chainBoosts.forEach(boost => (boost.chain = chain)); - chainBoosts = await getBoostPeriodFinish(chain, chainBoosts); - boostsByChain[chain] = chainBoosts; + const chainBoosts: BoostConfig[] = await getBoosts(chain); + boostsByChain[chain] = await getBoostPeriodFinish(chain, chainBoosts); } async function loadFromRedis() { diff --git a/src/api/boosts/types.ts b/src/api/boosts/types.ts index bd6bf93b9..939b72eb7 100644 --- a/src/api/boosts/types.ts +++ b/src/api/boosts/types.ts @@ -1,4 +1,6 @@ -export type Boost = { +import { ApiChain } from '../../utils/chain'; + +export type BoostConfig = { id: string; poolId: string; name: string; @@ -14,7 +16,11 @@ export type Boost = { status: 'active' | 'prestake' | 'closed'; isMooStaked: boolean; partners: string[]; - chain: string; + version: number; + chain: ApiChain; +}; + +export type Boost = BoostConfig & { periodFinish: number; - version?: number; + periodFinishes: number[]; }; diff --git a/src/api/stats/common/getCowVaultApys.ts b/src/api/stats/common/getCowVaultApys.ts index b5431c919..bf3606c35 100644 --- a/src/api/stats/common/getCowVaultApys.ts +++ b/src/api/stats/common/getCowVaultApys.ts @@ -126,7 +126,7 @@ function getCowVaultApyBreakdown( return { vaultId: clm.vault.oracleId, vault: - (clmBreakdown?.clmApr || 0) + + (clmBreakdown?.clmApr || 0) + // TODO clmApr already has fee removed (rewardPoolBreakdown?.merklApr || 0) + (rewardPoolBreakdown?.rewardPoolApr || 0), compoundingsPerYear: DAILY_HPY, diff --git a/src/api/stats/getApys.js b/src/api/stats/getApys.js index 625d6b816..c303b70b7 100644 --- a/src/api/stats/getApys.js +++ b/src/api/stats/getApys.js @@ -26,7 +26,7 @@ const { getModeApys } = require('./mode'); const { getMantaApys } = require('./manta'); const { getRealApys } = require('./real'); const { getKey, setKey } = require('../../utils/cache'); -const { fetchBoostAprs } = require('./getBoostAprs'); +const { fetchBoostAprs, BOOST_APR_EXPIRED } = require('./getBoostAprs'); const INIT_DELAY = process.env.INIT_DELAY || 30 * 1000; const BOOST_APR_INIT_DELAY = 30 * 1000; @@ -130,7 +130,7 @@ const updateBoostAprs = async () => { }; //-1 will be returned when boost has ended and it will be removed from the api response Object.keys(boostAprs) - .filter(boostId => boostAprs[boostId] === -1) + .filter(boostId => boostAprs[boostId] === BOOST_APR_EXPIRED) .forEach(boostId => { delete boostAprs[boostId]; }); diff --git a/src/api/stats/getBoostAprs.ts b/src/api/stats/getBoostAprs.ts index 37470476d..2e489b0a1 100644 --- a/src/api/stats/getBoostAprs.ts +++ b/src/api/stats/getBoostAprs.ts @@ -7,10 +7,66 @@ import { ApiChain, toChainId } from '../../utils/chain'; import BeefyBoostAbi from '../../abis/BeefyBoost'; import { fetchContract } from '../rpc/client'; import { isFiniteNumber } from '../../utils/number'; +import { partition } from 'lodash'; +import { getBeefyRewardPoolV2Aprs } from './common/getBeefyRewardPoolV2Apr'; +import { getAddress } from 'viem'; +import { isDefined } from '../../utils/array'; const { getVaultByID } = require('../stats/getMultichainVaults'); -const updateBoostAprsForChain = async (chain: ApiChain, boosts: Boost[]) => { +export const BOOST_APR_EXPIRED = -1; + +const updateBoostV2AprsForChain = async (chain: ApiChain, boosts: Boost[]) => { + try { + const chainId = toChainId(chain); + + //TODO: check boost update data frequency (/boosts already has periodFinish property) to see if periodFinish is still valid and rpc call can be avoided + + const results = await getBeefyRewardPoolV2Aprs( + chainId, + boosts + .map(boost => { + const vault = getVaultByID(boost.poolId); + if (!vault) { + console.warn( + `updateBoostV2AprsForChain`, + chain, + `vault ${boost.poolId} not found for boost ${boost.id}` + ); + return undefined; + } + + return { + oracleId: boost.id, + address: getAddress(boost.earnContractAddress), + stakedToken: { + oracleId: vault.oracleId, + address: vault.earnContractAddress, + decimals: 18, + }, + }; + }) + .filter(isDefined) + ); + + return results.reduce((aprs: Record, result) => { + if ( + result && + result.totalApr !== undefined && + result.rewardsApr && + result.rewardsApr.length > 0 + ) { + aprs[result.oracleId] = result.totalApr; + } + return aprs; + }, Object.fromEntries(boosts.map(boost => [boost.id, BOOST_APR_EXPIRED]))); + } catch (err) { + console.error('updateBoostV2AprsForChain', chain, err.message); + return {}; + } +}; + +const updateBoostV1AprsForChain = async (chain: ApiChain, boosts: Boost[]) => { const chainId = toChainId(chain); //TODO: check boost update data frequency (/boosts already has periodFinish property) to see if periodFinish is still valid and rpc call can be avoided @@ -50,14 +106,26 @@ const updateBoostAprsForChain = async (chain: ApiChain, boosts: Boost[]) => { return boostAprs; } catch (err) { - console.log(err.message); + console.error('updateBoostV1AprsForChain', chain, err.message); return {}; } }; +const updateBoostAprsForChain = async ( + chain: ApiChain, + boosts: Boost[] +): Promise> => { + const [boostsV2, boostsV1] = partition(boosts, boost => boost.version >= 2); + const [aprsV2, aprsV1] = await Promise.all([ + updateBoostV2AprsForChain(chain, boostsV2), + updateBoostV1AprsForChain(chain, boostsV1), + ]); + + return { ...aprsV1, ...aprsV2 }; +}; + /** - * @param callReturnContext - * @returns -1 if boost has expired, null if error ocurred, apr number value if successful + * @returns -1 if boost has expired, null if error occurred, apr number value if successful */ const mapResponseToBoostApr = async ( boost: Boost, @@ -69,7 +137,7 @@ const mapResponseToBoostApr = async ( const rewardRate = new BigNumber(rate.toString()); const periodFinish = new BigNumber(finish.toString()); - if (periodFinish.times(1000).lte(new BigNumber(Date.now()))) return -1; + if (periodFinish.times(1000).lte(new BigNumber(Date.now()))) return BOOST_APR_EXPIRED; try { const vault: Vault = getVaultByID(boost.poolId); diff --git a/src/utils/array.ts b/src/utils/array.ts index 2f696c123..5e83c966a 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -27,3 +27,21 @@ export function isDefined(value: T): value is Exclude { export function toArray(value: T | T[]): T[] { return Array.isArray(value) ? value : [value]; } + +export function numberRange(start: number, end?: number | undefined): number[] { + if (end === undefined) { + end = start; + start = 0; + } + + return Array.from({ length: end - start }, (_, i) => i + start); +} + +export function bigintRange(start: bigint, end?: bigint | undefined): bigint[] { + if (end === undefined) { + end = start; + start = BigInt(0); + } + + return Array.from({ length: parseInt((end - start).toString(10)) }, (_, i) => start + BigInt(i)); +} diff --git a/src/utils/big-int.ts b/src/utils/big-int.ts new file mode 100644 index 000000000..8c621067a --- /dev/null +++ b/src/utils/big-int.ts @@ -0,0 +1,7 @@ +export function bigintToNumber(value: bigint): number { + if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) { + return Number(value); + } + + throw new Error(`BigInt ${value} is out of range for a Number`); +}