diff --git a/src/adaptors/fusion-by-ipor/index.js b/src/adaptors/fusion-by-ipor/index.js index 7f5051399e..f36e91d211 100644 --- a/src/adaptors/fusion-by-ipor/index.js +++ b/src/adaptors/fusion-by-ipor/index.js @@ -1,8 +1,77 @@ const axios = require('axios'); +const sdk = require('@defillama/sdk'); const { addMerklRewardApy } = require('../merkl/merkl-additional-reward'); +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; +const ONE_ETHER = 10n ** 18n; +const SECONDS_IN_YEAR = 31556952n; + +const GET_REWARDS_CLAIM_MANAGER_ADDRESS_ABI = + 'function getRewardsClaimManagerAddress() view returns (address)'; +const TOTAL_ASSETS_ABI = 'function totalAssets() view returns (uint256)'; +const GET_VESTING_DATA_ABI = { + type: 'function', + name: 'getVestingData', + inputs: [], + stateMutability: 'view', + outputs: [{ + type: 'tuple', + components: [ + { name: 'vestingTime', type: 'uint32' }, + { name: 'updateBalanceTimestamp', type: 'uint32' }, + { name: 'transferredTokens', type: 'uint128' }, + { name: 'lastUpdateBalance', type: 'uint128' }, + ], + }], +}; + +async function getVestingRewardsApy(vaultAddress, chain) { + try { + const rcm = (await sdk.api.abi.call({ + target: vaultAddress, + chain, + abi: GET_REWARDS_CLAIM_MANAGER_ADDRESS_ABI, + })).output; + + if (!rcm || rcm.toLowerCase() === ZERO_ADDRESS) return 0; + + const [vestingDataRes, totalAssetsRes] = await Promise.all([ + sdk.api.abi.call({ target: rcm, chain, abi: GET_VESTING_DATA_ABI }), + sdk.api.abi.call({ target: vaultAddress, chain, abi: TOTAL_ASSETS_ABI }), + ]); + + const vestingTime = BigInt(vestingDataRes.output.vestingTime); + const lastUpdateBalance = BigInt(vestingDataRes.output.lastUpdateBalance); + const totalAssets = BigInt(totalAssetsRes.output); + + if (vestingTime === 0n || lastUpdateBalance === 0n || totalAssets === 0n) { + return 0; + } + + const apy_18 = + (lastUpdateBalance * ONE_ETHER * SECONDS_IN_YEAR * 100n) / + (totalAssets * vestingTime); + + return Number(apy_18) / 1e18; + } catch (e) { + return 0; + } +} + const IPOR_GITHUB_ADDRESSES_URL = "https://raw.githubusercontent.com/IPOR-Labs/ipor-abi/refs/heads/main/mainnet/addresses.json"; const FUSION_API_URL = 'https://api.ipor.io/fusion/vaults'; + +const VESTING_APY_VAULTS = { + ethereum: ["0xb9e806e8f2d94c015ffefa90cd24ecce18f1663c"], + arbitrum: [], + base: ["0x5900c3b72458f12967dc1bef35b92d271f5cdbc1", "0x17d0f109ee895bad0b68aa104aa72bd0b003ad8e", "0xe883426b4fc84a7f5cc86415cabbef43e73a4cc8"], + unichain: [], + tac: [], + ink: [], + plasma: [], + avax: [], + katana: [], +}; const CHAIN_CONFIG = { ethereum: { chainId: 1 @@ -76,6 +145,7 @@ async function buildPool(vault) { symbol: `${vault.asset}`, tvlUsd, apyBase, + apyReward : 0, underlyingTokens: [vault.assetAddress], poolMeta: `${vault.name}`, url @@ -100,10 +170,26 @@ const apy = async() => { ) ); - return addMerklRewardApy(pools, 'ipor'); + const poolsWithMerkl = await addMerklRewardApy(pools, 'ipor'); + + return Promise.all(poolsWithMerkl.map(async (pool) => { + const allowedVaults = VESTING_APY_VAULTS[pool.chain] || []; + if (!allowedVaults.includes(pool.pool.toLowerCase())) return pool; + + const vestingApy = await getVestingRewardsApy(pool.pool, pool.chain); + if (vestingApy <= 0) return pool; + const rewardTokens = [ + ...new Set([...(pool.rewardTokens || []), ...pool.underlyingTokens]), + ]; + return { + ...pool, + apyReward: (pool.apyReward || 0) + vestingApy, + rewardTokens, + }; + })); }; module.exports = { timetravel: false, apy: apy -}; \ No newline at end of file +};