diff --git a/src/adaptors/aave-v3/index.js b/src/adaptors/aave-v3/index.js index a64c2b02e2..9e62b19b8c 100755 --- a/src/adaptors/aave-v3/index.js +++ b/src/adaptors/aave-v3/index.js @@ -204,7 +204,7 @@ const stkGho = async () => { const stkghoMeritApy = ( await axios.get('https://apps.aavechan.com/api/merit/base-aprs') - ).data.actionsAPR['ethereum-stkgho']; + ).data['ethereum-stkgho']; const stkghoApy = stkghoNativeApy + stkghoMeritApy; diff --git a/src/adaptors/abracadabra-spell/cauldrons.js b/src/adaptors/abracadabra-spell/cauldrons.js index b2cf9efcef..cd4d0a14c3 100644 --- a/src/adaptors/abracadabra-spell/cauldrons.js +++ b/src/adaptors/abracadabra-spell/cauldrons.js @@ -30,31 +30,43 @@ const POOLS = { address: '0x7962acfcfc2ccebc810045391d60040f635404fb', collateralPoolId: '906b233c-8478-4b94-94e5-2d77e6c7c9e5', symbol: "SOL-USDC", - }, // gmSOL + }, // gmSOLUSDC { version: 4, address: '0x2b02bBeAb8eCAb792d3F4DDA7a76f63Aa21934FA', collateralPoolId: '61b4c35c-97f6-4c05-a5ff-aeb4426adf5b', symbol: "ETH-USDC", - }, // gmETH + }, // gmETHUSDC { version: 4, address: '0xD7659D913430945600dfe875434B6d80646d552A', collateralPoolId: '5b8c0691-b9ff-4d82-97e4-19a1247e6dbf', symbol: "WBTC.B-USDC", - }, // gmBTC + }, // gmBTCUSDC { version: 4, address: '0x4F9737E994da9811B8830775Fd73E2F1C8e40741', collateralPoolId: 'f3fa942f-1867-4028-95ff-4eb76816cd07', symbol: "ARB-USDC", - }, // gmARB + }, // gmARBUSDC { version: 4, address: '0x66805F6e719d7e67D46e8b2501C1237980996C6a', collateralPoolId: 'dffb3514-d667-4f2f-8df3-f716ebe09c93', symbol: "LINK-USDC", - }, // gmLINK + }, // gmLINKUSDC + { + version: 4, + address: '0x9fF8b4C842e4a95dAB5089781427c836DAE94831', + collateralPoolId: 'ffb4e407-6507-4615-b776-a0d99cfc1bbb', + symbol: "WBTC.B-WBTC.B", + }, // gmBTC + { + version: 4, + address: '0x625Fe79547828b1B54467E5Ed822a9A8a074bD61', + collateralPoolId: '5e2aac09-71c8-4092-ba4f-00f1ac0d04fd', + symbol: "ETH-ETH", + }, // gmETH { version: 4, address: '0x49De724D7125641F56312EBBcbf48Ef107c8FA57' }, // WBTC { version: 4, address: '0x780db9770dDc236fd659A39430A8a7cC07D0C320' }, // WETHV2 ], diff --git a/src/adaptors/beethoven-x-dex/index.js b/src/adaptors/beets-dex/index.js similarity index 99% rename from src/adaptors/beethoven-x-dex/index.js rename to src/adaptors/beets-dex/index.js index 6dd40310fc..afb28c9019 100644 --- a/src/adaptors/beethoven-x-dex/index.js +++ b/src/adaptors/beets-dex/index.js @@ -46,7 +46,7 @@ const apy = async () => { pool: chain === 'FANTOM' ? p.id : `${p.address}-${chain.toLowerCase()}`, chain: utils.formatChain(chain.toLowerCase()), - project: 'beethoven-x-dex', + project: 'beets-dex', symbol: p.address === '0x43da214fab3315aa6c02e0b8f2bfb7ef2e3c60a5' ? 'USDC-DAI' diff --git a/src/adaptors/blend-pools/index.js b/src/adaptors/blend-pools/index.js index a5c07c395f..a326df91f9 100644 --- a/src/adaptors/blend-pools/index.js +++ b/src/adaptors/blend-pools/index.js @@ -43,7 +43,7 @@ const getApy = async (poolId, backstop) => { if (borrowEmissionsPerAsset > 0) { borrowEmissionsAPR = (borrowEmissionsPerAsset * usdcPerBlnd) / price; } - // Estimate borrow APY compoumded daily + // Estimate borrow APY compounded daily const borrowApy = (1 + reserve.borrowApr / 365) ** 365 - 1; let totalSupply = reserve.totalSupplyFloat() * price; let totalBorrow = reserve.totalLiabilitiesFloat() * price; @@ -51,7 +51,7 @@ const getApy = async (poolId, backstop) => { const url = `https://mainnet.blend.capital/dashboard/?poolId=${poolId}`; pools.push({ - pool: `${reserve.assetId}-stellar`.toLowerCase(), + pool: `${pool.id}-${reserve.assetId}-stellar`.toLowerCase(), chain: formatChain('stellar'), project: 'blend-pools', symbol: reserve.tokenMetadata.symbol, @@ -66,7 +66,7 @@ const getApy = async (poolId, backstop) => { apyBaseBorrow: borrowApy * 100, apyRewardBorrow: borrowEmissionsAPR * 100, ltv: totalBorrow / totalSupply, - poolMeta: `Pool ID: ${pool.id}`, + poolMeta: `${pool.config.name} Pool`, url, }); } @@ -80,18 +80,7 @@ const apy = async () => { for (const poolId of BLEND_POOLS) { let poolApys = await getApy(poolId, backstop); - for (const poolApy of poolApys) { - if (poolApy) { - let index = pools.findIndex((pool) => pool.pool == poolApy.pool); - if (index !== -1) { - if (poolApy.apyReward > pools[index].apyReward) { - pools[index] = poolApy; - } - } else { - pools.push(poolApy); - } - } - } + pools.push(...poolApys) } return pools; }; diff --git a/src/adaptors/cetus-amm/index.js b/src/adaptors/cetus-amm/index.js index b4a5bc0987..8fad7fd911 100644 --- a/src/adaptors/cetus-amm/index.js +++ b/src/adaptors/cetus-amm/index.js @@ -8,28 +8,30 @@ const chains = { const apy = async (chain) => { const data = (await axios.get(chains[chain])).data.data.pools; - return data.map((p) => { - const apyReward = p.rewarder_apr.reduce( - (a, b) => a + Number(b.replace('%', '')), - 0 - ); - return { - chain, - project: 'cetus-amm', - pool: p.swap_account, - symbol: p.symbol, - tvlUsd: Number(p.tvl_in_usd), - apyBase: Number(p.apr_24h.replace('%', '')), - apyBase7d: Number(p.apr_7day.replace('%', '')), - volumeUsd1d: Number(p.vol_in_usd_24h), - volumeUsd7d: Number(p.vol_in_usd_7_day), - apyReward, - rewardTokens: apyReward > 0 ? ['sui', 'cetus'] : [], - poolMeta: `${Number(p.fee) * 100}%`, - underlyingTokens: [p.token_a_address, p.token_b_address], - url: `https://app.cetus.zone/liquidity/deposit?poolAddress=${p.swap_account}`, - }; - }); + return data + .map((p) => { + const apyReward = p.rewarder_apr.reduce( + (a, b) => a + Number(b.replace('%', '')), + 0 + ); + return { + chain, + project: 'cetus-amm', + pool: p.swap_account, + symbol: p.symbol, + tvlUsd: Number(p.tvl_in_usd), + apyBase: Number(p.apr_24h.replace('%', '')), + apyBase7d: Number(p.apr_7day.replace('%', '')), + volumeUsd1d: Number(p.vol_in_usd_24h), + volumeUsd7d: Number(p.vol_in_usd_7_day), + apyReward, + rewardTokens: apyReward > 0 ? ['sui', 'cetus'] : [], + poolMeta: `${Number(p.fee) * 100}%`, + underlyingTokens: [p.token_a_address, p.token_b_address], + url: `https://app.cetus.zone/liquidity/deposit?poolAddress=${p.swap_account}`, + }; + }) + .filter((i) => i.tvlUsd <= 1e8); }; const main = async () => { diff --git a/src/adaptors/deltaprime/index.js b/src/adaptors/deltaprime/index.js index 272391a5aa..3bce20c821 100644 --- a/src/adaptors/deltaprime/index.js +++ b/src/adaptors/deltaprime/index.js @@ -59,16 +59,11 @@ const getBoostRewardTokenAbi = { } // Avalanche -const USDC_POOL_TUP_CONTRACT = '0x2323dAC85C6Ab9bd6a8B5Fb75B0581E31232d12b'; -const USDT_POOL_TUP_CONTRACT = '0xd222e10D7Fe6B7f9608F14A8B5Cf703c74eFBcA1'; -const WAVAX_POOL_TUP_CONTRACT = '0xD26E504fc642B96751fD55D3E68AF295806542f5'; -const BTC_POOL_TUP_CONTRACT = '0x475589b0Ed87591A893Df42EC6076d2499bB63d0'; -const ETH_POOL_TUP_CONTRACT = '0xD7fEB276ba254cD9b34804A986CE9a8C3E359148'; - -const AVAX_POOL_REWARDER_CONTRACT = '0x6373122eD8Eda8ECA439415709318DCB6ddC1af3'; -const USDT_POOL_REWARDER_CONTRACT = '0xBC6Ef309f2eC71698eA310D62FF2E0543472D965'; -const USDC_POOL_REWARDER_CONTRACT = '0x596f6EFD98daF650CF98A1E62A53AB2a44e7E875'; -const BTC_POOL_REWARDER_CONTRACT = '0x3FE9BE379eD15962AFAbE01c002B8c433C6Af4ec'; +const USDC_POOL_TUP_CONTRACT = '0x8027e004d80274FB320e9b8f882C92196d779CE8'; +const USDT_POOL_TUP_CONTRACT = '0x1b6D7A6044fB68163D8E249Bce86F3eFbb12368e'; +const WAVAX_POOL_TUP_CONTRACT = '0xaa39f39802F8C44e48d4cc42E088C09EDF4daad4'; +const BTC_POOL_TUP_CONTRACT = '0x70e80001bDbeC5b9e932cEe2FEcC8F123c98F738'; +const ETH_POOL_TUP_CONTRACT = '0x2A84c101F3d45610595050a622684d5412bdf510'; const WAVAX_TOKEN_ADDRESS = '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7'; const USDC_TOKEN_ADDRESS = '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E'; @@ -80,11 +75,11 @@ const GGAVAX_TOKEN_ADDRESS = '0xA25EaF2906FA1a3a13EdAc9B9657108Af7B703e3'; const SAVAX_TOKEN_ADDRESS = '0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE'; // Arbitrum -const USDC_POOL_TUP_ARBI_CONTRACT = '0x5f3DB5899a7937c9ABF0A5Fc91718E6F813e4195'; -const ETH_POOL_TUP_ARBI_CONTRACT = '0x2E2fE9Bc7904649b65B6373bAF40F9e2E0b883c5'; -const ARB_POOL_TUP_ARBI_CONTRACT = '0x14c82CFc2c651700a66aBDd7dC375c9CeEFDDD72'; -const BTC_POOL_TUP_ARBI_CONTRACT = '0x275Caecf5542bF4a3CF64aa78a3f57dc9939675C'; -const DAI_POOL_TUP_ARBI_CONTRACT = '0x7Dcf909B1E4b280bEe72C6A69b3a7Ed8adfb63f0'; +const USDC_POOL_TUP_ARBI_CONTRACT = '0x8Ac9Dc27a6174a1CC30873B367A60AcdFAb965cc'; +const ETH_POOL_TUP_ARBI_CONTRACT = '0x788A8324943beb1a7A47B76959E6C1e6B87eD360'; +const ARB_POOL_TUP_ARBI_CONTRACT = '0xC629E8889350F1BBBf6eD1955095C2198dDC41c2'; +const BTC_POOL_TUP_ARBI_CONTRACT = '0x0ed7B42B74F039eda928E1AE6F44Eed5EF195Fb5'; +const DAI_POOL_TUP_ARBI_CONTRACT = '0xFA354E4289db87bEB81034A3ABD6D465328378f1'; const USDC_TOKEN_ARBI_ADDRESS = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'; const ETH_TOKEN_ARBI_ADDRESS = '0x82af49447d8a07e3bd95bd0d56f35241523fbab1'; @@ -92,13 +87,6 @@ const ARB_TOKEN_ARBI_ADDRESS = '0x912CE59144191C1204E64559FE8253a0e49E6548'; const BTC_TOKEN_ARBI_ADDRESS = '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f'; const DAI_TOKEN_ARBI_ADDRESS = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1'; -const POOL_ADDRESS_TO_REWARDER_CONTRACT = { - '0x2323dAC85C6Ab9bd6a8B5Fb75B0581E31232d12b': USDC_POOL_REWARDER_CONTRACT, - '0xd222e10D7Fe6B7f9608F14A8B5Cf703c74eFBcA1': USDT_POOL_REWARDER_CONTRACT, - '0xD26E504fc642B96751fD55D3E68AF295806542f5': AVAX_POOL_REWARDER_CONTRACT, - '0x475589b0Ed87591A893Df42EC6076d2499bB63d0': BTC_POOL_REWARDER_CONTRACT -} - const getPoolTVL = async (poolAddress, chain = 'avax') => { return (await sdk.api.abi.call({ abi: getPoolTotalSupplyAbi, @@ -115,33 +103,6 @@ const getTokenPrice = async (tokenAddress, chain='avax') => { return data.coins[Object.keys(data.coins)[0]].price } -const getPoolBoostRate = async (poolAddress, poolTVL, chain = 'avax') => { - if(chain === 'avax') { - let rewarderContractAddress = POOL_ADDRESS_TO_REWARDER_CONTRACT[poolAddress]; - - if(rewarderContractAddress) { - let rewardsRatePerSecond = (await sdk.api.abi.call({ - abi: getPoolBoostRateAbi, - chain: chain, - target: rewarderContractAddress, - params: [], - })).output; - - let rewardTokenAddress = (await sdk.api.abi.call({ - abi: getBoostRewardTokenAbi, - chain: chain, - target: rewarderContractAddress, - params: [], - })).output; - - let rewardTokenPrice = await getTokenPrice(rewardTokenAddress, chain); - - return rewardsRatePerSecond * rewardTokenPrice * 86400 * 365 / poolTVL / 1e16; - } - } - return 0; -} - const getPoolDepositRate = async (poolAddress, chain = 'avax') => { return (await sdk.api.abi.call({ abi: getPoolDepositRateAbi, @@ -262,7 +223,6 @@ const getUsdtPoolTVL = async() => { const getPoolsAPYs = async () => { const usdcPoolTvl = await getUsdcPoolTVL(); - const usdcRewardApy = await getPoolBoostRate(USDC_POOL_TUP_CONTRACT, usdcPoolTvl, 'avax'); const usdcPool = { pool: `dp-${USDC_TOKEN_ADDRESS}-avalanche`, chain: utils.formatChain('avalanche'), @@ -270,13 +230,10 @@ const getPoolsAPYs = async () => { symbol: utils.formatSymbol('USDC'), tvlUsd: usdcPoolTvl, apyBase: await getUsdcPoolDepositRate(), - apyReward: usdcRewardApy, - underlyingTokens: [USDC_TOKEN_ADDRESS], - rewardTokens: [GGAVAX_TOKEN_ADDRESS], + underlyingTokens: [USDC_TOKEN_ADDRESS] }; const usdtPoolTvl = await getUsdtPoolTVL(); - const usdtRewardApy = await getPoolBoostRate(USDT_POOL_TUP_CONTRACT, usdtPoolTvl, 'avax'); const usdtPool = { pool: `dp-${USDT_TOKEN_ADDRESS}-avalanche`, chain: utils.formatChain('avalanche'), @@ -284,13 +241,10 @@ const getPoolsAPYs = async () => { symbol: utils.formatSymbol('USDt'), tvlUsd: usdtPoolTvl, apyBase: await getUsdtPoolDepositRate(), - apyReward: usdtRewardApy, - underlyingTokens: [USDT_TOKEN_ADDRESS], - rewardTokens: [SAVAX_TOKEN_ADDRESS], + underlyingTokens: [USDT_TOKEN_ADDRESS] }; const wavaxPoolTvl = await getWavaxPoolTVL(); - const wavaxRewardApy = await getPoolBoostRate(WAVAX_POOL_TUP_CONTRACT, wavaxPoolTvl, 'avax'); const wavaxPool = { pool: `dp-${WAVAX_TOKEN_ADDRESS}-avalanche`, chain: utils.formatChain('avalanche'), @@ -298,13 +252,10 @@ const getPoolsAPYs = async () => { symbol: utils.formatSymbol('WAVAX'), tvlUsd: wavaxPoolTvl, apyBase: await getWavaxPoolDepositRate(), - apyReward: wavaxRewardApy, underlyingTokens: [WAVAX_TOKEN_ADDRESS], - rewardTokens: [SAVAX_TOKEN_ADDRESS], }; const btcPoolTvl = await getBtcPoolTVL(); - const btcRewardApy = await getPoolBoostRate(BTC_POOL_TUP_CONTRACT, btcPoolTvl, 'avax'); const btcPool = { pool: `dp-${BTC_TOKEN_ADDRESS}-avalanche`, chain: utils.formatChain('avalanche'), @@ -312,9 +263,7 @@ const getPoolsAPYs = async () => { symbol: utils.formatSymbol('BTC.b'), tvlUsd: btcPoolTvl, apyBase: await getBtcPoolDepositRate(), - apyReward: btcRewardApy, - underlyingTokens: [BTC_TOKEN_ADDRESS], - rewardTokens: [GGAVAX_TOKEN_ADDRESS], + underlyingTokens: [BTC_TOKEN_ADDRESS] }; const ethPoolTvl = await getEthPoolTVL(); diff --git a/src/adaptors/evaa-protocol/getPrices.js b/src/adaptors/evaa-protocol/getPrices.js new file mode 100644 index 0000000000..ec5d0666a5 --- /dev/null +++ b/src/adaptors/evaa-protocol/getPrices.js @@ -0,0 +1,376 @@ +const fetch = require('node-fetch'); +const { Cell, Slice, Dictionary, beginCell } = require('@ton/core'); +const { signVerify } = require('@ton/crypto'); + +const ORACLES = [ + { + id: 0, + address: + '0xd3a8c0b9fd44fd25a49289c631e3ac45689281f2f8cf0744400b4c65bed38e5d', + pubkey: Buffer.from( + 'b404f4a2ebb62f2623b370c89189748a0276c071965b1646b996407f10d72eb9', + 'hex' + ), + }, + { + id: 1, + address: + '0x2c21cabdaa89739de16bde7bc44e86401fac334a3c7e55305fe5e7563043e191', + pubkey: Buffer.from( + '9ad115087520d91b6b45d6a8521eb4616ee6914af07fabdc2e9d1826dbb17078', + 'hex' + ), + }, + { + id: 2, + address: + '0x2eb258ce7b5d02466ab8a178ad8b0ba6ffa7b58ef21de3dc3b6dd359a1e16af0', + pubkey: Buffer.from( + 'e503e02e8a9226b34e7c9deb463cbf7f19bce589362eb448a69a8ee7b2fca631', + 'hex' + ), + }, + { + id: 3, + address: + '0xf9a0769954b4430bca95149fb3d876deb7799d8f74852e0ad4ccc5778ce68b52', + pubkey: Buffer.from( + '9cbf8374cf1f2cf17110134871d580198416e101683f4a61f54cf2a3e4e32070', + 'hex' + ), + }, +]; + +const TTL_ORACLE_DATA_SEC = 120; +const MINIMAL_ORACLES = 3; + +function verifyPricesTimestamp(priceData) { + const timestamp = Date.now() / 1000; + const pricesTime = priceData.timestamp; + return timestamp - pricesTime < TTL_ORACLE_DATA_SEC; +} + +function verifyPricesSign(priceData) { + const message = priceData.dataCell.refs[0].hash(); + const signature = priceData.signature; + const publicKey = priceData.pubkey; + + return signVerify(message, signature, publicKey); +} + +function getMedianPrice(pricesData, assetId) { + try { + const usingPrices = pricesData.filter((x) => x.dict.has(assetId)); + const sorted = usingPrices + .map((x) => x.dict.get(assetId)) + .sort((a, b) => Number(a) - Number(b)); + + if (sorted.length === 0) { + return null; + } + + const mid = Math.floor(sorted.length / 2); + if (sorted.length % 2 === 0) { + return (sorted[mid - 1] + sorted[mid]) / 2n; + } else { + return sorted[mid]; + } + } catch { + return null; + } +} + +function packAssetsData(assetsData) { + if (assetsData.length === 0) { + throw new Error('No assets data to pack'); + } + return assetsData.reduceRight( + (acc, { assetId, medianPrice }) => + beginCell() + .storeUint(assetId, 256) + .storeCoins(medianPrice) + .storeMaybeRef(acc) + .endCell(), + null + ); +} + +function packPrices(assetsDataCell, oraclesDataCell) { + return beginCell() + .storeRef(assetsDataCell) + .storeRef(oraclesDataCell) + .endCell(); +} + +function readUnaryLength(slice) { + let res = 0; + while (slice.loadBit()) { + res++; + } + return res; +} + +function doGenerateMerkleProof(prefix, slice, n, keys) { + // Reading label + const originalCell = slice.asCell(); + + if (keys.length == 0) { + // no keys to prove, prune the whole subdict + return convertToPrunedBranch(originalCell); + } + + let lb0 = slice.loadBit() ? 1 : 0; + let prefixLength = 0; + let pp = prefix; + + if (lb0 === 0) { + // Short label detected + + // Read + prefixLength = readUnaryLength(slice); + + // Read prefix + for (let i = 0; i < prefixLength; i++) { + pp += slice.loadBit() ? '1' : '0'; + } + } else { + let lb1 = slice.loadBit() ? 1 : 0; + if (lb1 === 0) { + // Long label detected + prefixLength = slice.loadUint(Math.ceil(Math.log2(n + 1))); + for (let i = 0; i < prefixLength; i++) { + pp += slice.loadBit() ? '1' : '0'; + } + } else { + // Same label detected + let bit = slice.loadBit() ? '1' : '0'; + prefixLength = slice.loadUint(Math.ceil(Math.log2(n + 1))); + for (let i = 0; i < prefixLength; i++) { + pp += bit; + } + } + } + + if (n - prefixLength === 0) { + return originalCell; + } else { + let sl = originalCell.beginParse(); + let left = sl.loadRef(); + let right = sl.loadRef(); + // NOTE: Left and right branches are implicitly contain prefixes '0' and '1' + if (!left.isExotic) { + const leftKeys = keys.filter((key) => { + return pp + '0' === key.slice(0, pp.length + 1); + }); + left = doGenerateMerkleProof( + pp + '0', + left.beginParse(), + n - prefixLength - 1, + leftKeys + ); + } + if (!right.isExotic) { + const rightKeys = keys.filter((key) => { + return pp + '1' === key.slice(0, pp.length + 1); + }); + right = doGenerateMerkleProof( + pp + '1', + right.beginParse(), + n - prefixLength - 1, + rightKeys + ); + } + + return beginCell().storeSlice(sl).storeRef(left).storeRef(right).endCell(); + } +} + +function generateMerkleProofDirect(dict, keys, keyObject) { + keys.forEach((key) => { + if (!dict.has(key)) { + throw new Error( + `Trying to generate merkle proof for a missing key "${key}"` + ); + } + }); + const s = beginCell().storeDictDirect(dict).asSlice(); + return doGenerateMerkleProof( + '', + s, + keyObject.bits, + keys.map((key) => + keyObject.serialize(key).toString(2).padStart(keyObject.bits, '0') + ) + ); +} + +function endExoticCell(b) { + let c = b.endCell(); + return new Cell({ exotic: true, bits: c.bits, refs: c.refs }); +} + +function convertToMerkleProof(c) { + return endExoticCell( + beginCell() + .storeUint(3, 8) + .storeBuffer(c.hash(0)) + .storeUint(c.depth(0), 16) + .storeRef(c) + ); +} + +function createOracleDataProof(oracle, data, signature, assets) { + let prunedDict = generateMerkleProofDirect( + data.prices, + assets, + Dictionary.Keys.BigUint(256) + ); + let prunedData = beginCell() + .storeUint(data.timestamp, 32) + .storeMaybeRef(prunedDict) + .endCell(); + let merkleProof = convertToMerkleProof(prunedData); + let oracleDataProof = beginCell() + .storeUint(oracle.id, 32) + .storeRef(merkleProof) + .storeBuffer(signature) + .asSlice(); + return oracleDataProof; +} + +function packOraclesData(oraclesData, assets) { + if (oraclesData.length == 0) { + throw new Error('no oracles data to pack'); + } + let proofs = oraclesData + .sort((d1, d2) => d1.oracle.id - d2.oracle.id) + .map(({ oracle, data, signature }) => + createOracleDataProof(oracle, data, signature, assets) + ); + return proofs.reduceRight( + (acc, val) => beginCell().storeSlice(val).storeMaybeRef(acc).endCell(), + null + ); +} + +async function getPrices(endpoint = 'api.stardust-mainnet.iotaledger.net') { + try { + const prices = await Promise.all( + ORACLES.map(async (oracle) => { + try { + const outputResponse = await fetch( + `https://${endpoint}/api/indexer/v1/outputs/nft/${oracle.address}`, + { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(5000), + } + ); + const outputData = await outputResponse.json(); + const priceResponse = await fetch( + `https://${endpoint}/api/core/v2/outputs/${outputData.items[0]}`, + { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(5000), + } + ); + const priceData = await priceResponse.json(); + + const data = JSON.parse( + decodeURIComponent( + priceData.output.features[0].data + .replace('0x', '') + .replace(/[0-9a-f]{2}/g, '%$&') + ) + ); + + const pricesCell = Cell.fromBoc( + Buffer.from(data.packedPrices, 'hex') + )[0]; + const signature = Buffer.from(data.signature, 'hex'); + const publicKey = Buffer.from(data.publicKey, 'hex'); + const timestamp = Number(data.timestamp); + + return { + dict: pricesCell + .beginParse() + .loadRef() + .beginParse() + .loadDictDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigVarUint(4) + ), + dataCell: beginCell() + .storeRef(pricesCell) + .storeBuffer(signature) + .endCell(), + oracleId: oracle.id, + signature, + pubkey: publicKey, + timestamp, + }; + } catch (error) { + console.error( + `Error fetching prices from oracle ${oracle.id}:`, + error + ); + return null; + } + }) + ); + + const validPrices = prices.filter( + (price) => + price && verifyPricesTimestamp(price) && verifyPricesSign(price) + ); + + if (validPrices.length < MINIMAL_ORACLES) { + throw new Error('Not enough valid price data'); + } + + const sortedByTimestamp = validPrices + .slice() + .sort((a, b) => b.timestamp - a.timestamp); + const newerPrices = sortedByTimestamp + .slice(0, MINIMAL_ORACLES) + .sort((a, b) => a.oracleId - b.oracleId); + + const allAssetIds = new Set( + newerPrices.flatMap((p) => Array.from(p.dict.keys())) + ); + + const medianData = Array.from(allAssetIds) + .map((assetId) => ({ + assetId, + medianPrice: getMedianPrice(newerPrices, assetId), + })) + .filter((x) => x.medianPrice !== null); + + const packedMedianData = packAssetsData(medianData); + + const oraclesData = newerPrices.map((x) => ({ + oracle: { id: x.oracleId, pubkey: x.pubkey }, + data: { timestamp: x.timestamp, prices: x.dict }, + signature: x.signature, + })); + + const packedOracleData = packOraclesData( + oraclesData, + medianData.map((x) => x.assetId) + ); + + const dict = Dictionary.empty(); + for (const { assetId, medianPrice } of medianData) { + dict.set(assetId, medianPrice); + } + + return { + dict, + dataCell: packPrices(packedMedianData, packedOracleData), + }; + } catch (error) { + console.error('Error processing prices:', error); + return undefined; + } +} + +module.exports = getPrices; diff --git a/src/adaptors/evaa-protocol/index.js b/src/adaptors/evaa-protocol/index.js index 376234e24f..19db20432f 100644 --- a/src/adaptors/evaa-protocol/index.js +++ b/src/adaptors/evaa-protocol/index.js @@ -3,8 +3,10 @@ const utils = require('../utils'); const fetch = require('node-fetch') const { TonClient } = require("@ton/ton"); const { Address, Cell, Slice, Dictionary, beginCell } = require("@ton/core"); +const { signVerify } = require('@ton/crypto'); const crypto = require("crypto"); -const NFT_ID = '0xfb9874544d76ca49c5db9cc3e5121e4c018bc8a2fb2bfe8f2a38c5b9963492f5'; +const getPrices = require('./getPrices'); +const { getDistributions, calculateRewardApy } = require('./rewardApy'); function sha256Hash(input) { const hash = crypto.createHash("sha256"); @@ -31,6 +33,12 @@ const assets = { tsTON: { assetId: sha256Hash("tsTON"), token: 'EQC98_qAmNEptUtPc7W6xdHh_ZHrBUFpw5Ft_IzNU20QAJav' }, }; +function findAssetKeyByBigIntId(searchAssetId) { + return Object.entries(assets).find(([key, value]) => + BigInt(value.assetId) === searchAssetId + )?.[0]; +} + const MASTER_CONSTANTS = { FACTOR_SCALE: BigInt(1e12), @@ -306,37 +314,6 @@ function calculateCurrentRates(assetConfig, assetData) { }; } -async function getPrices(endpoint = "api.stardust-mainnet.iotaledger.net") { - try { - let result = await fetch(`https://${endpoint}/api/indexer/v1/outputs/nft/${NFT_ID}`, { - headers: { accept: 'application/json' }, - }); - let outputId = await result.json(); - - result = await fetch(`https://${endpoint}/api/core/v2/outputs/${outputId.items[0]}`, { - headers: { accept: 'application/json' }, - }); - - let resData = await result.json(); - - const data = JSON.parse( - decodeURIComponent(resData.output.features[0].data.replace('0x', '').replace(/[0-9a-f]{2}/g, '%$&')), - ); - - const pricesCell = Cell.fromBoc(Buffer.from(data['packedPrices'], 'hex'))[0]; - const signature = Buffer.from(data['signature'], 'hex'); - - return { - dict: pricesCell.beginParse().loadDictDirect(Dictionary.Keys.BigUint(256), Dictionary.Values.BigUint(64)), - dataCell: beginCell().storeRef(pricesCell).storeBuffer(signature).endCell(), - }; - } catch (error) { - console.error(error); - return undefined; - } -} - - // ignore pools with TVL below the threshold const MIN_TVL_USD = 100000; @@ -350,6 +327,7 @@ function calculatePresentValue(index, principalValue) { const getApy = async () => { console.log("Requesting prices") let prices = await getPrices(); + let distributions = await getDistributions(); const client = new TonClient({ endpoint: "https://toncenter.com/api/v2/jsonRPC" }); @@ -365,6 +343,9 @@ const getApy = async () => { console.log(e); } }); + + const rewardApys = calculateRewardApy(distributions, 'main', data,prices); + return Object.entries(assets).map(([tokenSymbol, asset]) => { const { assetId, token } = asset; console.log("Process symbol", tokenSymbol, asset, assetId, token) @@ -398,6 +379,27 @@ const getApy = async () => { console.log(tokenSymbol, "supplyApy", supplyApy * 100); console.log(tokenSymbol, "borrowApy", borrowApy * 100); + const apyRewardData = rewardApys.find( + (rewardApy) => + rewardApy.rewardingAssetId == assetId && + rewardApy.rewardType.toLowerCase() === 'supply' + ); + + const apyReward = apyRewardData ? apyRewardData.apy : undefined; + const rewardTokens = apyRewardData + ? [findAssetKeyByBigIntId(apyRewardData.rewardsAssetId)] + : undefined; + + const apyRewardBorrowData = rewardApys.find( + (rewardApy) => + rewardApy.rewardingAssetId == assetId && + rewardApy.rewardType.toLowerCase() === 'borrow' + ); + + const apyRewardBorrow = apyRewardBorrowData + ? apyRewardBorrowData.apy + : undefined; + return { pool: `evaa-${assetId}-ton`.toLowerCase(), chain: 'Ton', @@ -405,6 +407,9 @@ const getApy = async () => { symbol: tokenSymbol, tvlUsd: totalSupplyUsd - totalBorrowUsd, apyBase: supplyApy * 100, + apyReward, + rewardTokens, + // apyRewardBorrow, underlyingTokens: [token], url: `https://app.evaa.finance/token/${tokenSymbol}`, totalSupplyUsd: totalSupplyUsd, diff --git a/src/adaptors/evaa-protocol/rewardApy.js b/src/adaptors/evaa-protocol/rewardApy.js new file mode 100644 index 0000000000..3b1671328c --- /dev/null +++ b/src/adaptors/evaa-protocol/rewardApy.js @@ -0,0 +1,177 @@ +const fetch = require('node-fetch'); + +function isLeapYear(year) { + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; +} + +async function getDistributions(endpoint = 'evaa.space') { + try { + let result = await fetch(`https://${endpoint}/query/distributions/list`, { + headers: { accept: 'application/json' }, + }); + let resData = await result.json(); + return resData; + } catch (error) { + console.error(error); + return undefined; + } +} +const isLeap = isLeapYear(new Date().getFullYear()); +const totalSecsInYear = (isLeap ? 366 : 365) * 24 * 60 * 60; + +function calcApy( + rewardAmount, + totalAmount, + rewardingAssetPrice, + rewardsAssetPrice, + rewardingScaleFactor, + rewardsScaleFactor, + totalSecsInCurrentSeason +) { + const rate = rewardingScaleFactor / Number(totalAmount); + const rewardForUnit = + rate * + (((Number(rewardAmount) / rewardsScaleFactor) * (rewardsAssetPrice ?? 0)) / + (rewardingAssetPrice || 1)) * + rewardingScaleFactor; + + return ( + ((rewardForUnit * totalSecsInYear) / + (totalSecsInCurrentSeason * rewardingScaleFactor)) * + 100 + ); +} + +function calculateRewardApy(distributionsResp, pool, data, prices) { + try { + if ( + !distributionsResp?.distributions || + distributionsResp.distributions.length === 0 + ) { + console.log('Invalid distributions data:', distributionsResp); + return []; + } + + const currentCampaign = distributionsResp?.distributions.find( + (campaign) => campaign.started && !campaign.expired + ); + + if (!currentCampaign) { + return []; + } + + const seasonsApy = currentCampaign.seasons + ?.filter((season) => season.started && !season.expired) + ?.filter((season) => season.pool === pool) + ?.map((season) => { + const rewardingAssetId = BigInt(season?.rewarding_asset_id ?? 0); + const rewardsAssetId = BigInt(season?.rewards_asset_id ?? 0); + + const rewardingAssetData = data.assetsData.get(rewardingAssetId); + const rewardsAssetData = data.assetsData.get(rewardsAssetId); + + if (!rewardingAssetData || !rewardsAssetData) { + return []; + } + + const rewardType = + season?.reward_type?.toLowerCase() === 'borrow' ? 'Borrow' : 'Supply'; + + let rewardAmount = Number(season?.rewards_amount) || 0; + + if (rewardType === 'Borrow' && season?.borrow_budget) { + rewardAmount = season.borrow_budget; + } else if (rewardType === 'Supply' && season?.supply_budget) { + rewardAmount = season.supply_budget; + } + + const totalAmountSupply = + rewardingAssetData.totalSupply?.original ?? + rewardingAssetData.totalSupply; + const totalAmountBorrow = + rewardingAssetData.totalBorrow?.original ?? + rewardingAssetData.totalBorrow; + + const totalAmount = + rewardType === 'Borrow' ? totalAmountBorrow : totalAmountSupply; + + if (!totalAmount || totalAmount === '0') { + return []; + } + + const rewardingAssetConfig = data.assetsConfig.get(rewardingAssetId); + const rewardsAssetConfig = data.assetsConfig.get(rewardsAssetId); + + const rewardingScaleFactor = + 10 ** Number(rewardingAssetConfig?.decimals ?? 0); + const rewardsScaleFactor = + 10 ** Number(rewardsAssetConfig?.decimals ?? 0); + + const rewardingPriceData = prices.dict.get(rewardingAssetId); + const rewardsPriceData = prices.dict.get(rewardsAssetId); + + const rewardingAssetPrice = Number(rewardingPriceData); + const rewardsAssetPrice = Number(rewardsPriceData); + + const seasonStart = new Date(season?.campaign_start ?? 0); + const seasonEnd = new Date(season?.campaign_end ?? 0); + const totalSecsInCurrentSeason = (seasonEnd - seasonStart) / 1000; + + if (totalSecsInCurrentSeason <= 0) { + return []; + } + + const baseApy = calcApy( + rewardAmount, + totalAmount, + rewardingAssetPrice, + rewardsAssetPrice, + rewardingScaleFactor, + rewardsScaleFactor, + totalSecsInCurrentSeason + ); + + const result = [ + { + apy: baseApy, + rewardType, + rewardingAssetId, + rewardsAssetId, + }, + ]; + + if ( + rewardType === 'Borrow' && + season?.supply_budget && + season.supply_budget > 0 + ) { + const supplyApy = calcApy( + season.supply_budget, + totalAmountSupply, + rewardingAssetPrice, + rewardsAssetPrice, + rewardingScaleFactor, + rewardsScaleFactor, + totalSecsInCurrentSeason + ); + result.push({ + apy: supplyApy, + rewardType: 'Supply', + rewardingAssetId, + rewardsAssetId, + }); + } + + return result; + }); + return seasonsApy.flat(); + } catch (error) { + console.error(error); + return []; + } +} + +module.exports = { + getDistributions, + calculateRewardApy, +}; diff --git a/src/adaptors/extra-finance/abis/veloPairsSugar.json b/src/adaptors/extra-finance-leverage-farming/abis/veloPairsSugar.json similarity index 100% rename from src/adaptors/extra-finance/abis/veloPairsSugar.json rename to src/adaptors/extra-finance-leverage-farming/abis/veloPairsSugar.json diff --git a/src/adaptors/extra-finance/abis/veloPairsSugarV2.json b/src/adaptors/extra-finance-leverage-farming/abis/veloPairsSugarV2.json similarity index 100% rename from src/adaptors/extra-finance/abis/veloPairsSugarV2.json rename to src/adaptors/extra-finance-leverage-farming/abis/veloPairsSugarV2.json diff --git a/src/adaptors/extra-finance/compute.js b/src/adaptors/extra-finance-leverage-farming/compute.js similarity index 100% rename from src/adaptors/extra-finance/compute.js rename to src/adaptors/extra-finance-leverage-farming/compute.js diff --git a/src/adaptors/extra-finance/contract.js b/src/adaptors/extra-finance-leverage-farming/contract.js similarity index 84% rename from src/adaptors/extra-finance/contract.js rename to src/adaptors/extra-finance-leverage-farming/contract.js index 34b2518120..621cc76c28 100644 --- a/src/adaptors/extra-finance/contract.js +++ b/src/adaptors/extra-finance-leverage-farming/contract.js @@ -4,8 +4,8 @@ const pairsSugarContractAbi = require("./abis/veloPairsSugarV2.json"); const { concat } = require('lodash'); const veloPairAddress = { - optimism: '0x1381B1E6aaFa01bD28e95AdaB35bdA8191826bC8', - base: '0x82357A700f242476da8C5712C010B2D5e327C588' + optimism: '0xD11Aa38D87C6604A127431D4d3aa8C0e9763f0be', + base: '0x51f290CCCD6a54Af00b38edDd59212dE068B8A4b' } const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000" @@ -27,9 +27,9 @@ exports.getAllVeloPools = async function (chain) { const simpleRpcProvider = new ethers.providers.JsonRpcProvider(rpcUrl); const veloPairContract = new Contract(veloPairAddress[chain], pairsSugarContractAbi, simpleRpcProvider) const poolInfoLists = await Promise.all([ - veloPairContract.all(400, 0), - veloPairContract.all(400, 400), - veloPairContract.all(400, 800), + veloPairContract.all(300, 0), + veloPairContract.all(300, 300), + veloPairContract.all(300, 600), ]) const poolInfoList = concat(...poolInfoLists) return poolInfoList diff --git a/src/adaptors/extra-finance/index.js b/src/adaptors/extra-finance-leverage-farming/index.js similarity index 98% rename from src/adaptors/extra-finance/index.js rename to src/adaptors/extra-finance-leverage-farming/index.js index 185e3c0b1e..a63b52b4c8 100644 --- a/src/adaptors/extra-finance/index.js +++ b/src/adaptors/extra-finance-leverage-farming/index.js @@ -12,7 +12,7 @@ const { getAllVeloPoolInfo, } = require('./compute'); -const project = 'extra-finance'; +const project = 'extra-finance-leverage-farming'; const chains = ['optimism', 'base']; const subgraphUrls = { diff --git a/src/adaptors/fenix-concentrated-liquidity/index.js b/src/adaptors/fenix-concentrated-liquidity/index.js new file mode 100644 index 0000000000..a0fce10477 --- /dev/null +++ b/src/adaptors/fenix-concentrated-liquidity/index.js @@ -0,0 +1,112 @@ +const sdk = require('@defillama/sdk'); +const axios = require('axios'); +const utils = require('../utils'); +const { request, gql } = require('graphql-request'); + +const API_URL = `https://blaze.prod.fenix.aegas.it/liquidity/rewards`; + +const SUBGRAPH_URL = + 'https://api.goldsky.com/api/public/project_clxadvm41bujy01ui2qalezdn/subgraphs/fenix-v3-dex/latest/gn'; + +const FNX_ADDRESS = '0x52f847356b38720B55ee18Cb3e094ca11C85A192'; + +const swapPairsQuery = (skip) => { + return gql` + query MyQuery { + pools(first: 100, skip: ${skip}, where: {totalValueLockedUSD_gt: 10000}) { + totalValueLockedToken0 + totalValueLockedToken1 + totalValueLockedUSD + token1 { + id + symbol + } + token0 { + id + symbol + } + id + } + } + `; +}; + +const getPairs = async () => { + try { + let pools = []; + let index = 0; + let res; + + do { + res = await request(SUBGRAPH_URL, swapPairsQuery(index), {}); + + if (res.pools?.length > 0) { + pools = [...pools, ...res.pools]; + } + index += res.pools?.length || 0; + } while (res.pools?.length > 0); + + return pools; + } catch (error) { + console.error('Error in getPairs:', error); + throw error; + } +}; + +const getApy = async () => { + try { + const pairs = await getPairs(); + + const poolsRes = await axios.get( + `${API_URL}?${pairs.map((pair) => `pools=${pair.id}`).join('&')}` + ); + // console.log('Pools rewards sample:', poolsRes.data); + + const { coins: fnxPrice } = await utils.getData( + `https://coins.llama.fi/prices/current/blast:${FNX_ADDRESS}?searchWidth=4h` + ); + const fnxPriceUsd = fnxPrice[`blast:${FNX_ADDRESS}`]?.price || 0; + + const apyDict = {}; + for (const pool of poolsRes.data) { + const pairData = pairs.find( + (p) => p.id.toLowerCase() === pool.pool.toLowerCase() + ); + + if (pairData) { + const weeklyRewardInFNX = parseFloat(pool.rewardWei) / 1e18; + const annualRewardInFNX = weeklyRewardInFNX * 52; + const annualRewardUSD = annualRewardInFNX * fnxPriceUsd; + const tvl = parseFloat(pairData.totalValueLockedUSD); + apyDict[pool.pool.toLowerCase()] = (annualRewardUSD / tvl) * 100; + } + } + + const pools = pairs.map((pair) => { + let tvl = parseFloat(pair.totalValueLockedUSD); + + const poolData = { + pool: pair.id, + chain: utils.formatChain('blast'), + project: 'fenix-concentrated-liquidity', + symbol: `${pair.token0.symbol}-${pair.token1.symbol}`, + tvlUsd: tvl, + apyReward: parseFloat(apyDict[pair.id.toLowerCase()] || 0), + underlyingTokens: [pair.token0.id, pair.token1.id], + rewardTokens: [FNX_ADDRESS], + }; + + return poolData; + }); + return pools; + } catch (error) { + console.error('Error in getApy:', error); + throw error; + } +}; + +module.exports = { + timetravel: false, + apy: getApy, + url: 'https://www.fenixfinance.io/liquidity', +}; diff --git a/src/adaptors/fenix-standard-pools/index.js b/src/adaptors/fenix-standard-pools/index.js new file mode 100644 index 0000000000..b819857aa1 --- /dev/null +++ b/src/adaptors/fenix-standard-pools/index.js @@ -0,0 +1,101 @@ +const sdk = require('@defillama/sdk'); +const axios = require('axios'); +const utils = require('../utils'); +const { request, gql } = require('graphql-request'); + +const API_URL = `https://blaze.prod.fenix.aegas.it/liquidity/rewards`; + +const SUBGRAPH_URL = + 'https://api.goldsky.com/api/public/project_clxadvm41bujy01ui2qalezdn/subgraphs/fenix-v2-subgraph/latest/gn'; + +const FNX_ADDRESS = '0x52f847356b38720B55ee18Cb3e094ca11C85A192'; + +const swapPairsQuery = (skip) => { + return gql` + query MyQuery { + pairs(first: 100, skip: ${skip}, where: {reserveUSD_gt: 10000}) { + reserve0 + reserve1 + token1 { + id + symbol + } + token0 { + id + symbol + } + reserveUSD + id + } + } + `; +}; + +const getPairs = async () => { + let pairs = []; + let index = 0; + let res; + do { + res = await request(SUBGRAPH_URL, swapPairsQuery(index), {}); + if (res.pairs.length > 0) { + pairs = [...pairs, ...res.pairs]; + } + index += res.pairs.length; + } while (res.pairs.length > 0); + return pairs; +}; + +const getApy = async () => { + const pairs = await getPairs(); + const poolsRes = await axios.get( + `${API_URL}?${pairs.map((pair) => `pools=${pair.id}`).join('&')}` + ); + + // First get FNX price from DeFiLlama + const { coins: fnxPrice } = await utils.getData( + `https://coins.llama.fi/prices/current/blast:${FNX_ADDRESS}?searchWidth=4h` + ); + + const fnxPriceUsd = fnxPrice[`blast:${FNX_ADDRESS}`]?.price || 0; + + // Create apyDict by calculating (rewards/tvl) * 100 * 52 for each pool + const apyDict = {}; + for (const pool of poolsRes.data) { + const pairData = pairs.find( + (p) => p.id.toLowerCase() === pool.pool.toLowerCase() + ); + + if (pairData) { + // Convert reward to annual value (weekly * 52) and from Wei to FNX + const annualReward = (parseFloat(pool.rewardWei) * 52) / 1e18; + // Convert to USD using FNX price + const annualRewardUSD = annualReward * fnxPriceUsd; + // Get TVL + const tvl = parseFloat(pairData.reserveUSD); + // Calculate APY: (annual reward in USD / TVL) * 100 + apyDict[pool.pool.toLowerCase()] = (annualRewardUSD / tvl) * 100; + } + } + + const pools = pairs.map((pair) => { + tvl = parseFloat(pair.reserveUSD); + return { + pool: pair.id, + chain: utils.formatChain('blast'), + project: 'fenix-standard-pools', + symbol: `${pair.token0.symbol}-${pair.token1.symbol}`, + tvlUsd: tvl, + apyReward: parseFloat(apyDict[pair.id.toLowerCase()] || 0), + underlyingTokens: [pair.token0.id, pair.token1.id], + rewardTokens: [FNX_ADDRESS], + }; + }); + + return pools; +}; +getApy(); +module.exports = { + timetravel: false, + apy: getApy, + url: 'https://www.fenixfinance.io/liquidity', +}; diff --git a/src/adaptors/fluid-lending/index.js b/src/adaptors/fluid-lending/index.js index a7b84777f0..1088053368 100644 --- a/src/adaptors/fluid-lending/index.js +++ b/src/adaptors/fluid-lending/index.js @@ -9,6 +9,7 @@ const CONSTANTS = { arbitrum: 42161, base: 8453, }, + SUPPORTED_CHAINS: ['ethereum', 'arbitrum', 'base'], RESOLVERS: { LENDING: { @@ -224,7 +225,6 @@ const calculateVaultPoolData = ( ltv: vaultDetails.ltv[index] / 1e4, })); }; - // Main Function const apy = async () => { const [lendingData, vaultData] = await Promise.all([ @@ -232,7 +232,6 @@ const apy = async () => { Promise.all(CONSTANTS.SUPPORTED_CHAINS.map(getVaultApy)), ]); // Combine and flatten both arrays - console.log([...lendingData.flat(), ...vaultData.flat()]); return [...lendingData.flat(), ...vaultData.flat()]; }; diff --git a/src/adaptors/harmonix-finance/index.js b/src/adaptors/harmonix-finance/index.js new file mode 100644 index 0000000000..9359237324 --- /dev/null +++ b/src/adaptors/harmonix-finance/index.js @@ -0,0 +1,111 @@ +const axios = require('axios'); +const sdk = require('@defillama/sdk'); +const ethers = require('ethers'); +const utils = require('../utils') + +// ABI for totalValueLocked function +const totalValueLockedABI = { + "inputs": [], + "name": "totalValueLocked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}; + +// Mapping of chain names to chain IDs +const chains = { + ethereum: 'ethereum', + arbitrum_one: 'arbitrum', + base: 'base', +}; + +const getTvl = async (contractAddress, chain) => { + try { + const tvl = await sdk.api.abi.call({ + target: contractAddress, + abi: totalValueLockedABI, + chain + }); + return tvl.output; + } catch (error) { + throw new Error(`Failed to fetch TVL from contract ${contractAddress} on chain ${chain} : ${error.message}`); + } +}; + +const assets = { + arbitrum: { + eth: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + wbtc: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f', + rseth: '0x4186BFC76E2E237523CBC30FD220FE055156b41F', + link: '0xf97f4df75117a78c1A5a0DBb814Af92458539FB4', + uni: '0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0' + }, + ethereum: { + eth: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + rseth: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', + }, +}; + +const getApy = async () => { + const response = await axios.get('https://api.harmonix.fi/api/v1/vaults/', { + headers: { + 'accept': 'application/json' + } + }); + + const pools = await Promise.all(response.data.map(async vault => { + return Promise.all(vault.vaults.map(async v => { + const chainId = chains[v.network_chain]; + const tvl = await getTvl(v.contract_address, chainId); + let tvlUsd = tvl; // Default to the fetched TVL + + // Convert TVL to USDC if vault_currency is not USDC + if (v.vault_currency !== 'USDC') { + const tokenKey = `${chainId}:${assets[chainId][v.vault_currency.toLowerCase()]}` + const priceData = await axios.get(`https://coins.llama.fi/prices/current/${tokenKey}`); + const tokenPrice = priceData.data.coins[`${tokenKey}`]?.price; + if (tokenPrice) { + if (v.vault_currency === 'ETH') { + tvlUsd = (tvl / 1e18) * tokenPrice; + } else if (v.vault_currency === 'WBTC') { + tvlUsd = (tvl / 1e8) * tokenPrice; + } else { + tvlUsd = (tvl / 1e6) * tokenPrice; + } + } else { + throw new Error(`Price for ${v.vault_currency} not found.`); + } + } + else { + tvlUsd /= 1e6 + } + + return { + pool: v.contract_address, // unique identifier for the pool + chain: chainId || null, // map chain name to chain ID + project: 'harmonix-finance', // project slug + symbol: utils.formatSymbol(v.vault_currency), // format the symbol + tvlUsd, // total value locked in USD + apyBase: v.apy, // APY from the vault + apyReward: 0, // hardcoded for now + rewardTokens: [], // hardcoded for now + url: `https://app.harmonix.fi/vaults/${v.slug}`, // URL to the vault + underlyingTokens: v.underlying_asset ? [assets[chainId][v.underlying_asset.toLowerCase()]] : [], // underlying asset + }; + })); + })); + + return pools.flat(); // flatten the array of pools +}; + +module.exports = { + timetravel: false, + apy: getApy, + url: 'https://app.harmonix.fi/vaults/', // Link to page with pools +}; diff --git a/src/adaptors/impermax-finance/index.js b/src/adaptors/impermax-finance/index.js index 3fb6dceaf0..ac7d4d4c6f 100644 --- a/src/adaptors/impermax-finance/index.js +++ b/src/adaptors/impermax-finance/index.js @@ -215,6 +215,10 @@ const lendingVaultProfiles = { address: '0x683cc7cbb8b8c5b3c5fae85a4ae70e887217883b'.toLowerCase(), risk: 'Aggressive', }, // ETH (high) + { + address: '0xc68c47085D2B53A0A782c168D1b54a913A668cB5'.toLowerCase(), + risk: 'Conservative', + }, // cbBTC (low) ], }; diff --git a/src/adaptors/ironclad-finance/index.js b/src/adaptors/ironclad-finance/index.js index 824a8754d8..64d63a0102 100644 --- a/src/adaptors/ironclad-finance/index.js +++ b/src/adaptors/ironclad-finance/index.js @@ -16,6 +16,14 @@ const chains = { SimplifiedProtocolDataReader: '0x78d5439da3201F44ce9A642DB95D798e9249952F', rewardTokens: ['0x3b6ea0fa8a487c90007ce120a83920fd52b06f6d'] }, + base: { + LendingPool: '0xB702cE183b4E1Faa574834715E5D4a6378D0eEd3', + ProtocolDataProvider: '0xed984A0E9c12Ee27602314191Fc4487A702bB83f', + url: 'base', + SimplifiedProtocolDataReader: '0x78d5439da3201F44ce9A642DB95D798e9249952F', + rewardTokens: ['0x3b6ea0fa8a487c90007ce120a83920fd52b06f6d'] + } + }; const getApy = async () => { diff --git a/src/adaptors/nucleus/index.js b/src/adaptors/nucleus/index.js new file mode 100644 index 0000000000..0829eacdd1 --- /dev/null +++ b/src/adaptors/nucleus/index.js @@ -0,0 +1,52 @@ +const utils = require('../utils'); +const sdk = require('@defillama/sdk'); + +const vaultData = async () => { + const vaults = await utils.getData( + 'https://backend.nucleusearn.io/v1/protocol/markets' + ); + const tokens = await utils.getData( + 'https://backend.nucleusearn.io/v1/protocol/tokens' + ); + + let pools = []; + await Promise.all( + Object.keys(vaults).map(async (vaultAddress) => { + try { + const vaultApyQuery = await utils.getData( + `https://backend.nucleusearn.io/v1/vaults/apy?token_address=${vaultAddress}&lookback_days=14` + ); + const vaultSymbol = await sdk.api2.erc20.symbol(vaultAddress); + + const ethereumApi = new sdk.ChainApi({ chain: 'ethereum' }); + const vaultBalances = await ethereumApi.sumTokens({ + owner: vaultAddress, + tokens: tokens, + }); + const usdBalance = await ethereumApi.getUSDValue(); + console.log('Vault TVL: ', usdBalance); + + console.log('Vault Symbol: ', vaultSymbol); + const pool = { + pool: `${vaultAddress}-ethereum`, + chain: 'Ethereum', + project: 'nucleus', + symbol: vaultSymbol.output, + tvlUsd: usdBalance, + apy: vaultApyQuery.apy, // 14 days apy + }; + pools.push(pool); + } catch (error) { + console.error(`Error processing vault: ${vaultAddress}`, error); + } + }) + ); + + return pools; +}; + +module.exports = { + timetravel: false, + apy: vaultData, + url: 'https://app.nucleusearn.io/dashboard', +}; diff --git a/src/adaptors/orca/index.js b/src/adaptors/orca/index.js index 75f9532732..c1c99051a7 100644 --- a/src/adaptors/orca/index.js +++ b/src/adaptors/orca/index.js @@ -4,7 +4,7 @@ const utils = require('../utils'); const getApy = async () => { let whirlpools = ( await axios.get('https://api.mainnet.orca.so/v1/whirlpool/list') - ).data.whirlpools; + ).data.whirlpools.filter((i) => i.whitelisted === true); whirlpools = whirlpools.map((p) => { const apyReward = diff --git a/src/adaptors/resolv-rlp/index.js b/src/adaptors/resolv-rlp/index.js index 7226aa9e57..2372a20b09 100644 --- a/src/adaptors/resolv-rlp/index.js +++ b/src/adaptors/resolv-rlp/index.js @@ -5,7 +5,7 @@ const ethers = require('ethers'); const RLP = '0x4956b52aE2fF65D74CA2d61207523288e4528f96'; -const rlpPriceStorage = '0xAa33e5ECAE01779b26cD9dBD3c62E34c29b2D565'; +const rlpPriceStorage = '0xaE2364579D6cB4Bbd6695846C1D595cA9AF3574d'; const topic0priceSet = '0x2f0fe01aa6daff1c7bb411a324bdebe55dc2cd1e0ff2fc504b7569346e7d7d5a'; diff --git a/src/adaptors/scallop-lend/index.js b/src/adaptors/scallop-lend/index.js index 808516335a..e935d3cf52 100644 --- a/src/adaptors/scallop-lend/index.js +++ b/src/adaptors/scallop-lend/index.js @@ -2,24 +2,51 @@ const axios = require('axios'); const utils = require('../utils') const baseUrl = 'https://sdk.api.scallop.io/api'; -const marketEndpoint = `${baseUrl}/market`; -const spoolsEndpoint = `${baseUrl}/spools`; +const marketEndpoint = `${baseUrl}/market/migrate`; +const spoolsEndpoint = `${baseUrl}/spools/migrate`; +const borrowIncentiveEndpoint = `${baseUrl}/borrowIncentivePools`; const main = async () => { - let [market, spools] = await Promise.all([axios.get(marketEndpoint), axios.get(spoolsEndpoint)]); + let [market, spools, borrowIncentive] = await Promise.all([axios.get(marketEndpoint), axios.get(spoolsEndpoint), axios.get(borrowIncentiveEndpoint)]); const supplyRewards = {}; + const rewardTokenPool = {}; spools.data.spools.forEach((spool) => { + if(spool.rewardApr <= 0) { + return; + } supplyRewards[spool.coinType] = { rewardApr: spool.rewardApr, rewardCoinType: spool.rewardCoinType, }; + if(rewardTokenPool[spool.coinType] === undefined) { + rewardTokenPool[spool.coinType] = []; + } + rewardTokenPool[spool.coinType].push(spool.rewardCoinType); + }); + + const borrowRewards = {}; + borrowIncentive.data.forEach((borrow) => { + borrow.rewards.forEach((reward) => { + if (borrowRewards[borrow.coinType] === undefined) { + borrowRewards[borrow.coinType] = []; + } + borrowRewards[borrow.coinType].push({ + rewardApr: reward.rewardApr, + rewardCoinType: reward.coinType, + }); + if(rewardTokenPool[borrow.coinType] === undefined) { + rewardTokenPool[borrow.coinType] = []; + } + rewardTokenPool[borrow.coinType].push(reward.coinType); + }); }); const arr = []; market.data.pools.forEach((pool) => { const supplyUsd = parseFloat(pool.supplyCoin) * parseFloat(pool.coinPrice); const borrowUsd = parseFloat(pool.borrowCoin) * parseFloat(pool.coinPrice); + const collateralFactor = market.data.collaterals.find((collateral) => collateral.coinType === pool.coinType); arr.push({ chain: 'Sui', project: 'scallop-lend', @@ -28,10 +55,12 @@ const main = async () => { tvlUsd: supplyUsd - borrowUsd, apyBase: parseFloat(pool.supplyApy * 100), apyReward: supplyRewards[pool.coinType] ? parseFloat(supplyRewards[pool.coinType].rewardApr * 100) : null, - rewardTokens: supplyRewards[pool.coinType] ? [supplyRewards[pool.coinType].rewardCoinType] : [], + rewardTokens: rewardTokenPool[pool.coinType] ? Array.from(new Set(rewardTokenPool[pool.coinType])) : null, totalSupplyUsd: supplyUsd, totalBorrowUsd: borrowUsd, apyBaseBorrow: parseFloat(pool.borrowApy * 100), + apyRewardBorrow: borrowRewards[pool.coinType] ? parseFloat(borrowRewards[pool.coinType].reduce((prev, curr) => prev + curr.rewardApr, 0) * 100) : null, + ltv: Number(parseFloat(collateralFactor ? collateralFactor.collateralFactor : 0).toFixed(2)), }); }); diff --git a/src/handlers/triggerLSDRates.js b/src/handlers/triggerLSDRates.js index 9e65a5fe16..635c7737e6 100644 --- a/src/handlers/triggerLSDRates.js +++ b/src/handlers/triggerLSDRates.js @@ -417,14 +417,6 @@ const getExpectedRates = async () => { type: 'function', }; - const apxETHAbi = { - inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], - name: 'convertToAssets', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }; - // --- cbETH const cbETHRate = Number((await axios.get(cbETHRateUrl)).data.amount); @@ -577,17 +569,6 @@ const getExpectedRates = async () => { }) ).output / 1e18; - const apxEth = - ( - await sdk.api.abi.call({ - target: lsdTokens.find((lsd) => lsd.name === 'Dinero (pxETH)') - .addressExchangeRate, - chain: 'ethereum', - abi: apxETHAbi, - params: [1000000000000000000n], - }) - ).output / 1e18; - const lsETH = 10000 / ( @@ -660,8 +641,6 @@ const getExpectedRates = async () => { ? uniETH : lsd.name === 'mETH Protocol' ? mETH - : lsd.name === 'Dinero (pxETH)' - ? apxEth : lsd.name === 'Liquid Collective' ? lsETH : lsd.name === 'MEV Protocol' diff --git a/src/utils/exclude.js b/src/utils/exclude.js index f74c1632c4..e25028cd41 100644 --- a/src/utils/exclude.js +++ b/src/utils/exclude.js @@ -151,10 +151,11 @@ const excludeAdaptors = [ 'maia-dao', 'hermes-protocol', 'xtoken', - 'deltaprime', 'arbitrum-exchange-v3', 'bfx-(blast-futures)', 'stack', + 'liquid-bolt', + 'avault', ]; const excludePools = [