From 3b477b8cb3430aac65a3435299b2f61089ed06fa Mon Sep 17 00:00:00 2001 From: rav Date: Tue, 12 May 2026 12:53:22 +0200 Subject: [PATCH 1/2] Add vesting APY as reward APY to selected IPOR Fusion vaults --- src/adaptors/fusion-by-ipor/index.js | 88 +++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/adaptors/fusion-by-ipor/index.js b/src/adaptors/fusion-by-ipor/index.js index 7f5051399e..c4ba2ba9a9 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) / totalAssets) + * (SECONDS_IN_YEAR / vestingTime) + * 100n; + + 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,7 +170,23 @@ 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 = { From e833862797f0f8341ac9098941e0cc19b538166f Mon Sep 17 00:00:00 2001 From: rav Date: Tue, 12 May 2026 13:07:47 +0200 Subject: [PATCH 2/2] Add fix to PR review comments --- src/adaptors/fusion-by-ipor/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adaptors/fusion-by-ipor/index.js b/src/adaptors/fusion-by-ipor/index.js index c4ba2ba9a9..f36e91d211 100644 --- a/src/adaptors/fusion-by-ipor/index.js +++ b/src/adaptors/fusion-by-ipor/index.js @@ -48,9 +48,9 @@ async function getVestingRewardsApy(vaultAddress, chain) { return 0; } - const apy_18 = ((lastUpdateBalance * ONE_ETHER) / totalAssets) - * (SECONDS_IN_YEAR / vestingTime) - * 100n; + const apy_18 = + (lastUpdateBalance * ONE_ETHER * SECONDS_IN_YEAR * 100n) / + (totalAssets * vestingTime); return Number(apy_18) / 1e18; } catch (e) { @@ -64,7 +64,7 @@ const FUSION_API_URL = 'https://api.ipor.io/fusion/vaults'; const VESTING_APY_VAULTS = { ethereum: ["0xb9e806e8f2d94c015ffefa90cd24ecce18f1663c"], arbitrum: [], - base: ["0x5900C3b72458F12967DC1bef35b92d271F5cDBc1", "0x17d0f109ee895bad0b68aa104aa72bd0b003ad8e", "0xe883426B4fc84A7f5cc86415CAbBef43E73a4CC8"], + base: ["0x5900c3b72458f12967dc1bef35b92d271f5cdbc1", "0x17d0f109ee895bad0b68aa104aa72bd0b003ad8e", "0xe883426b4fc84a7f5cc86415cabbef43e73a4cc8"], unichain: [], tac: [], ink: [], @@ -192,4 +192,4 @@ const apy = async() => { module.exports = { timetravel: false, apy: apy -}; \ No newline at end of file +};