From bc5eeba3c45159183254db91c9afe8cd69fe420e Mon Sep 17 00:00:00 2001 From: Eden Date: Thu, 12 Jun 2025 19:37:49 +0700 Subject: [PATCH 1/5] add fluid-dex pools --- src/adaptors/fluid-dex/abi.json | 201 ++++++++++++++++++++++++++++++++ src/adaptors/fluid-dex/index.js | 192 ++++++++++++++++++++++++++++++ 2 files changed, 393 insertions(+) create mode 100644 src/adaptors/fluid-dex/abi.json create mode 100644 src/adaptors/fluid-dex/index.js diff --git a/src/adaptors/fluid-dex/abi.json b/src/adaptors/fluid-dex/abi.json new file mode 100644 index 0000000000..e0e3ba637b --- /dev/null +++ b/src/adaptors/fluid-dex/abi.json @@ -0,0 +1,201 @@ +[ + { + "inputs": [], + "name": "getAllPoolsReserves", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "centerPrice", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves[]", + "name": "poolsReserves_", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/adaptors/fluid-dex/index.js b/src/adaptors/fluid-dex/index.js new file mode 100644 index 0000000000..212f4b2a24 --- /dev/null +++ b/src/adaptors/fluid-dex/index.js @@ -0,0 +1,192 @@ +const superagent = require('superagent'); +const sdk = require('@defillama/sdk'); +const utils = require('../utils'); +const ethers = require('ethers'); +const abis = require('./abi.json'); + +const PROJECT = 'fluid-dex'; + +const DexReservesResolvers = { + ethereum: '0xC93876C0EEd99645DD53937b25433e311881A27C', + // arbitrum: '0x666A400b8cDA0Dc9b59D61706B0F982dDdAF2d98', + // polygon: '0x18DeDd1cF3Af3537D4e726D2Aa81004D65DA8581', + // base: '0x41E6055a282F8b7Abdb8D22Bcd85c2A0eE22e38A', +} + +const EventSwap = 'event Swap(bool swap0to1, uint256 amountIn, uint256 amountOut, address to)'; + +function formatAddress(address) { + return address.toLowerCase(); +} + +function getTokenInfo(chain, address, symbol, decimals) { + if (address === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') { + return { + address: '0x0000000000000000000000000000000000000000', + symbol: chain === 'polygon' ? 'POL' : 'ETH', + decimals: 18, + } + } else { + return { + address: formatAddress(address), + symbol: symbol, + decimals: Number(decimals), + } + } +} + +function getPoolLink(chain, poolIndex) { + let chainId = 1 + if (chain === 'arbitrum') chainId = 42161; + if (chain === 'polygon') chainId = 137; + if (chain === '8453') chainId = 42161; + + return `https://fluid.io/stats/${chainId}/dex#${poolIndex}`; +} + +const main = async (unixTimestamp) => { + const yieldPools = [] + + const timestamp = unixTimestamp ? unixTimestamp : Math.floor(new Date().getTime() / 1000); + const currentBlocks = await sdk.blocks.getBlocks(timestamp, Object.keys(DexReservesResolvers)) + const last1DaysBlocks = await sdk.blocks.getBlocks(timestamp - 24 * 60 * 60, Object.keys(DexReservesResolvers)) + const last7DaysBlocks = await sdk.blocks.getBlocks(timestamp - 7 * 24 * 60 * 60, Object.keys(DexReservesResolvers)) + + for (const [chain, dexResolver] of Object.entries(DexReservesResolvers)) { + const currentBlock = currentBlocks.chainBlocks[chain]; + const last1DaysBlock = last1DaysBlocks.chainBlocks[chain]; + const last7DaysBlock = last7DaysBlocks.chainBlocks[chain]; + + const rawPoolReserves = (await sdk.api2.abi.call({ + chain: chain, + target: DexReservesResolvers[chain], + abi: abis.find(item => item.name === 'getAllPoolsReserves'), + })) + + const allTokens = {}; + const allDexPools = {}; + for (const pool of rawPoolReserves) { + const [symbols, decimals] = await Promise.all([ + sdk.api2.abi.multiCall({ + chain: chain, + abi: 'string:symbol', + calls: [pool.token0, pool.token1], + permitFailure: true, + }), + sdk.api2.abi.multiCall({ + chain: chain, + abi: 'uint8:decimals', + calls: [pool.token0, pool.token1], + permitFailure: true, + }), + ]); + + const poolAddress = formatAddress(pool.pool); + const token0 = getTokenInfo(chain, pool.token0, symbols[0], decimals[0]); + const token1 = getTokenInfo(chain, pool.token1, symbols[1], decimals[1]); + const feeRate = Number(pool.fee) / 1e6; + + allTokens[token0.address] = token0; + allTokens[token1.address] = token1; + allDexPools[poolAddress] = { + pool: poolAddress, + token0, + token1, + feeRate, + reserve0: Number(pool.collateralReserves.token0RealReserves) + Number(pool.debtReserves.token0RealReserves), + reserve1: Number(pool.collateralReserves.token1RealReserves) + Number(pool.debtReserves.token1RealReserves), + + // will fill below + tvlUsd: 0, + volumeUsd1d: 0, + volumeUsd7d: 0, + } + } + + // get token price from llama coins api + const coinLists = Object.keys(allTokens).map(token => `${chain}:${token}`); + const coinPrices = (await superagent.get(`https://coins.llama.fi/prices/current/${coinLists.toString()}`)).body.coins; + for (const [coinId, coinPrice] of Object.entries(coinPrices)) { + allTokens[formatAddress(coinId.split(':')[1])].price = Number(coinPrice.price); + } + + for (const [address, dexPool] of Object.entries(allDexPools)) { + const token0Price = allTokens[dexPool.token0.address].price ? allTokens[dexPool.token0.address].price : 0; + const token1Price = allTokens[dexPool.token1.address].price ? allTokens[dexPool.token1.address].price : 0; + const token0Reserve = dexPool.reserve0 * token0Price / 10**dexPool.token0.decimals; + const token1Reserve = dexPool.reserve1 * token1Price / 10**dexPool.token1.decimals; + + // update pool tvl USD + allDexPools[address].tvlUsd = token0Reserve + token1Reserve; + } + + const iface = new ethers.utils.Interface([EventSwap]) + const swapLogs = (await sdk.getEventLogs({ + chain: chain, + eventAbi: EventSwap, + targets: Object.keys(allDexPools), + flatten: true, // !!! + fromBlock: last7DaysBlock, + toBlock: currentBlock, + entireLog: true, + })).map(log => { + const event = iface.parseLog({ + topics: log.topics, + data: log.data, + }); + + return { + address: formatAddress(log.address), + blockNumber: Number(log.blockNumber), + args: { + swap0to1: event.args.swap0to1, + amountIn: Number(event.args.amountIn), + amountOut: Number(event.args.amountOut), + } + } + }); + + for (const log of swapLogs) { + let volumeUsd = 0; + const dexPool = allDexPools[log.address]; + if (log.args.swap0to1) { + const tokenPrice = allTokens[dexPool.token0.address].price ? allTokens[dexPool.token0.address].price : 0; + volumeUsd = Number(log.args.amountIn) * tokenPrice / 10**dexPool.token0.decimals; + } else { + const tokenPrice = allTokens[dexPool.token1.address].price ? allTokens[dexPool.token1.address].price : 0; + volumeUsd = Number(log.args.amountIn) * tokenPrice / 10**dexPool.token1.decimals; + } + + if (log.blockNumber >= last1DaysBlock) { + allDexPools[log.address].volumeUsd1d += volumeUsd; + } + allDexPools[log.address].volumeUsd7d += volumeUsd; + } + + for (const p of Object.values(allDexPools)) { + const feeUsd = p.volumeUsd1d * p.feeRate; + const feeUsd7d = p.volumeUsd7d * p.feeRate; + + yieldPools.push({ + chain: utils.formatChain(chain), + project: PROJECT, + pool: p.pool, + symbol: utils.formatSymbol(`${p.token0.symbol}-${p.token1.symbol}`), + underlyingTokens: [p.token0.address, p.token1.address], + tvlUsd: p.tvlUsd, + apyBase: feeUsd * 100 * 365 / p.tvlUsd, + apyBase7d: feeUsd7d * 100 * 365 / 7 / p.tvlUsd, + volumeUsd1d: p.volumeUsd1d, + volumeUsd7d: p.volumeUsd7d, + url: getPoolLink(chain, Object.keys(allDexPools).indexOf(p.pool) + 1), + }) + } + } + + return yieldPools; +}; + +module.exports = { + timetravel: true, + apy: main, +}; From 3ffaa657dc06ab8c6d1dad7b6ccb1d4215abee2a Mon Sep 17 00:00:00 2001 From: Eden Date: Thu, 12 Jun 2025 20:19:11 +0700 Subject: [PATCH 2/5] add pools volumes for curve dex --- src/adaptors/curve-dex/config.js | 1 + src/adaptors/curve-dex/index.js | 39 ++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/adaptors/curve-dex/config.js b/src/adaptors/curve-dex/config.js index 5c9fdb81e7..26d2ea6ab8 100644 --- a/src/adaptors/curve-dex/config.js +++ b/src/adaptors/curve-dex/config.js @@ -1,4 +1,5 @@ exports.CRV_API_BASE_URL = 'https://api.curve.finance/api'; +exports.CRV_API_BASE_URL_V1 = 'https://api.curve.finance/v1'; exports.BLOCKCHAINIDS = [ 'ethereum', 'polygon', diff --git a/src/adaptors/curve-dex/index.js b/src/adaptors/curve-dex/index.js index 8d482fbefc..e6f6ea6dc0 100644 --- a/src/adaptors/curve-dex/index.js +++ b/src/adaptors/curve-dex/index.js @@ -5,6 +5,7 @@ const utils = require('../utils'); const { CRV_API_BASE_URL, + CRV_API_BASE_URL_V1, BLOCKCHAINIDS, BLOCKCHAINID_TO_REGISTRIES, OVERRIDE_DATA, @@ -50,6 +51,25 @@ const getPools = async (blockchainId) => { return poolsByAddress; }; +const getPoolsVolumes = async (blockchainId) => { + const poolsByAddress = {}; + for (const registry of BLOCKCHAINID_TO_REGISTRIES[blockchainId]) { + const uri = `/getVolumes/${blockchainId}`; + let response; + try { + response = await utils.getData(CRV_API_BASE_URL_V1 + uri); + } catch (error) { + continue; + } + if (response?.success && response?.data?.pools?.length) { + for (const pool of response.data.pools) { + poolsByAddress[String(pool.address).toLowerCase()] = pool; + } + } + } + return poolsByAddress; +}; + const getSubGraphData = async (blockchainId) => { const uri = `/getSubgraphData/${blockchainId}`; let response; @@ -208,6 +228,9 @@ const main = async () => { const blockchainToPoolPromise = Object.fromEntries( BLOCKCHAINIDS.map((blockchainId) => [blockchainId, getPools(blockchainId)]) ); + const blockchainToPoolsVolumesPromise = Object.fromEntries( + BLOCKCHAINIDS.map((blockchainId) => [blockchainId, getPoolsVolumes(blockchainId)]) + ); // we need the ethereum data first for the crv prive and await extra query to CG const ethereumPools = await blockchainToPoolPromise.ethereum; @@ -233,6 +256,7 @@ const main = async () => { const [ addressToPool, addressToPoolSubgraph, + addressToPoolVolumes, addressToGauge, gaugeAddressToExtraRewards, ] = poolData; @@ -355,7 +379,7 @@ const main = async () => { const url = overrideData?.url || `https://curve.finance/#/${blockchainId}/pools`; - defillamaPooldata.push({ + const yieldPool = { pool: address + '-' + blockchainId, chain: utils.formatChain(blockchainId), project: 'curve-dex', @@ -377,7 +401,17 @@ const main = async () => { .filter((i) => i !== '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32'), underlyingTokens, url, - }); + } + + if (addressToPoolVolumes) { + const normalAddress = String(address).toLowerCase(); + if (addressToPoolVolumes[normalAddress]) { + yieldPool.volumeUsd1d = addressToPoolVolumes[normalAddress].volumeUSD + yieldPool.volumeUsd7d = yieldPool.volumeUsd1d * 7 * Number(addressToPoolVolumes[normalAddress].latestWeeklyApyPcent) / Number(addressToPoolVolumes[normalAddress].latestDailyApyPcent); + } + } + + defillamaPooldata.push(yieldPool); } }; @@ -390,6 +424,7 @@ const main = async () => { Promise.all([ poolPromise, blockchainToPoolSubgraphPromise[blockchainId], + blockchainToPoolsVolumesPromise[blockchainId], gaugePromise, extraRewardPromise, ]).then((poolData) => feedLlama(poolData, blockchainId)) From 779e980d58ace203479737e4dd2c09511ffdec78 Mon Sep 17 00:00:00 2001 From: Eden Date: Fri, 13 Jun 2025 02:14:47 +0700 Subject: [PATCH 3/5] add maverick version 1, 2 --- src/adaptors/fluid-dex/index.js | 2 +- src/adaptors/maverick-v1/index.js | 119 ++++++++++++++++++ src/adaptors/maverick-v2/index.js | 199 ++++++++++++++++++++++++++++++ src/adaptors/utils.js | 4 + 4 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/adaptors/maverick-v1/index.js create mode 100644 src/adaptors/maverick-v2/index.js diff --git a/src/adaptors/fluid-dex/index.js b/src/adaptors/fluid-dex/index.js index 212f4b2a24..f3db171cbf 100644 --- a/src/adaptors/fluid-dex/index.js +++ b/src/adaptors/fluid-dex/index.js @@ -39,7 +39,7 @@ function getPoolLink(chain, poolIndex) { let chainId = 1 if (chain === 'arbitrum') chainId = 42161; if (chain === 'polygon') chainId = 137; - if (chain === '8453') chainId = 42161; + if (chain === 'base') chainId = 8453; return `https://fluid.io/stats/${chainId}/dex#${poolIndex}`; } diff --git a/src/adaptors/maverick-v1/index.js b/src/adaptors/maverick-v1/index.js new file mode 100644 index 0000000000..ee89cc44f1 --- /dev/null +++ b/src/adaptors/maverick-v1/index.js @@ -0,0 +1,119 @@ +const sdk = require('@defillama/sdk'); +const utils = require('../utils'); +const { request, gql } = require('graphql-request'); + +const PROJECT = 'maverick-v1'; + +const SubgraphConfigs = { + ethereum: sdk.graph.modifyEndpoint('H4KMc3uRaRqKrM8dq8GKCt9gwmMQsRRiQRThZCM16KtB'), +} + +const query = function(blockNumber) { + return gql` + { + pools(first: 1000, orderBy: balanceUSD, orderDirection: desc, block: {number: ${blockNumber}}) { + id + tokenA { + id + symbol + } + tokenB { + id + symbol + } + volumeUSD + balanceUSD + fee + } + } + `; +} + +async function fetchPools(subgraph, blockNumber) { + const poolsMap = {} + + const poolsList = (await request(subgraph, query(blockNumber))).pools; + for (const pool of poolsList) { + const poolAddress = String(pool.id).toLowerCase(); + const token0Address = String(pool.tokenA.id).toLowerCase(); + const token1Address = String(pool.tokenB.id).toLowerCase(); + poolsMap[poolAddress] = { + address: poolAddress, + token0: { + address: token0Address, + symbol: pool.tokenA.symbol, + }, + token1: { + address: token1Address, + symbol: pool.tokenB.symbol, + }, + volumeUSD: Number(pool.volumeUSD), + balanceUSD: Number(pool.balanceUSD), + feeRate: Number(pool.fee), + } + } + + return poolsMap +} + +function getPoolLink(chain, poolAddress) { + let chainId = 1 + if (chain === 'arbitrum') chainId = 42161; + if (chain === 'polygon') chainId = 137; + if (chain === 'base') chainId = 42161; + + return `https://app-v1.mav.xyz/pool/${poolAddress}?chain=${chainId}`; +} + +const main = async (unixTimestamp) => { + const yieldPools = [] + + const timestamp = unixTimestamp ? unixTimestamp : Math.floor(new Date().getTime() / 1000); + const currentBlocks = await sdk.blocks.getBlocks(timestamp, Object.keys(SubgraphConfigs)); + const last1DaysBlocks = await sdk.blocks.getBlocks(timestamp - 24 * 60 * 60, Object.keys(SubgraphConfigs)); + const last7DaysBlocks = await sdk.blocks.getBlocks(timestamp - 7 * 24 * 60 * 60, Object.keys(SubgraphConfigs)); + + for (const [chain, subgraph] of Object.entries(SubgraphConfigs)) { + const currentPoolsData = await fetchPools(subgraph, currentBlocks.chainBlocks[chain]); + const last1DaysPoolsData = await fetchPools(subgraph, last1DaysBlocks.chainBlocks[chain]); + const last7DaysPoolsData = await fetchPools(subgraph, last7DaysBlocks.chainBlocks[chain]); + + for (const [address, pool] of Object.entries(currentPoolsData)) { + let volumeUsd1d = 0; + let volumeUsd7d = 0; + let feeUsd1d = 0; + let feeUsd7d = 0; + + if (last1DaysPoolsData[address]) { + volumeUsd1d = currentPoolsData[address].volumeUSD - last1DaysPoolsData[address].volumeUSD; + feeUsd1d = volumeUsd1d * currentPoolsData[address].feeRate; + } + if (last7DaysPoolsData[address]) { + volumeUsd7d = currentPoolsData[address].volumeUSD - last7DaysPoolsData[address].volumeUSD; + feeUsd7d = volumeUsd7d * currentPoolsData[address].feeRate; + } + + yieldPools.push({ + chain: utils.formatChain(chain), + project: PROJECT, + pool: address, + symbol: utils.formatSymbol(`${pool.token0.symbol}-${pool.token1.symbol}`), + underlyingTokens: [pool.token0.address, pool.token1.address], + tvlUsd: pool.balanceUSD, + apyBase: pool.balanceUSD > 0 ? feeUsd1d * 365 * 100 / pool.balanceUSD : 0, + apyBase7d: pool.balanceUSD > 0 ? feeUsd7d * 365 * 100 / 7 / pool.balanceUSD : 0, + volumeUsd1d: volumeUsd1d, + volumeUsd7d: volumeUsd7d, + url: getPoolLink(chain, address), + }) + } + } + + return yieldPools + .filter(pool => pool.tvlUsd > 0); +}; + +module.exports = { + timetravel: true, + apy: main, +}; diff --git a/src/adaptors/maverick-v2/index.js b/src/adaptors/maverick-v2/index.js new file mode 100644 index 0000000000..7a5f648083 --- /dev/null +++ b/src/adaptors/maverick-v2/index.js @@ -0,0 +1,199 @@ +const superagent = require('superagent'); +const ethers = require('ethers'); +const sdk = require('@defillama/sdk'); +const utils = require('../utils'); + +const PROJECT = 'maverick-v2'; + +const FactoryConfigs = { + ethereum: { + factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', + fromBlock: 20027237, + } +} + +const EventPoolCreated = 'event PoolCreated(address poolAddress, uint8 protocolFeeRatio, uint256 feeAIn, uint256 feeBIn, uint256 tickSpacing, uint256 lookback, int32 activeTick, address tokenA, address tokenB, uint8 kinds, address accessor)'; +const EventPoolSwap = 'event PoolSwap(address sender, address recipient, tuple(uint256 amount, bool tokenAIn, bool exactOutput, int32 tickLimit), uint256 amountIn, uint256 amountOut)'; + +function getPoolLink(chain, poolAddress) { + let chainId = 1 + if (chain === 'arbitrum') chainId = 42161; + if (chain === 'polygon') chainId = 137; + if (chain === 'base') chainId = 42161; + + return `https://app.mav.xyz/pool/${poolAddress}?chain=${chainId}`; +} + +async function fetchPools(chain, currentBlock) { + const events = await sdk.getEventLogs({ + chain: chain, + eventAbi: EventPoolCreated, + target: FactoryConfigs[chain].factory, + fromBlock: FactoryConfigs[chain].fromBlock, + toBlock: currentBlock, + }); + + const pools = {}; + for (const event of events) { + const poolAddress = utils.formatAddress(event.args.poolAddress); + pools[poolAddress] = { + pool: poolAddress, + token0Address: utils.formatAddress(event.args.tokenA), + token1Address: utils.formatAddress(event.args.tokenB), + fee0In: Number(event.args.feeAIn) / 1e18, + fee1In: Number(event.args.feeBIn) / 1e18, + } + } + + return pools; +} + +const main = async (unixTimestamp) => { + const yieldPools = [] + + const timestamp = unixTimestamp ? unixTimestamp : Math.floor(new Date().getTime() / 1000); + const currentBlocks = await sdk.blocks.getBlocks(timestamp, Object.keys(FactoryConfigs)); + const last1DaysBlocks = await sdk.blocks.getBlocks(timestamp - 24 * 60 * 60, Object.keys(FactoryConfigs)); + const last7DaysBlocks = await sdk.blocks.getBlocks(timestamp - 7 * 24 * 60 * 60, Object.keys(FactoryConfigs)); + + for (const [chain, factoryConfig] of Object.entries(FactoryConfigs)) { + const currentBlock = currentBlocks.chainBlocks[chain]; + const last1DaysBlock = last1DaysBlocks.chainBlocks[chain]; + const last7DaysBlock = last7DaysBlocks.chainBlocks[chain]; + + const allTokens = {}; + const allDexPools = {}; + const rawPools = await fetchPools(chain, currentBlock); + + const tokenCalls = []; + const balanceCalls = []; + for (const rawPool of Object.values(rawPools)) { + tokenCalls.push(rawPool.token0Address); + tokenCalls.push(rawPool.token1Address); + balanceCalls.push({ target: rawPool.token0Address, params: [rawPool.pool] }); + balanceCalls.push({ target: rawPool.token1Address, params: [rawPool.pool] }); + } + const [symbols, decimals, balances] = await Promise.all([ + sdk.api2.abi.multiCall({ chain: chain, abi: 'string:symbol', calls: tokenCalls }), + sdk.api2.abi.multiCall({ chain: chain, abi: 'uint8:decimals', calls: tokenCalls }), + sdk.api2.abi.multiCall({ chain: chain, abi: 'function balanceOf(address) view returns (uint256)', calls: balanceCalls }), + ]); + + for (let i = 0; i < Object.values(rawPools).length; i++) { + const rawPool = Object.values(rawPools)[i]; + + const token0 = { address: rawPool.token0Address, symbol: symbols[i * 2], decimals: Number(decimals[i * 2]) } + const token1 = { address: rawPool.token1Address, symbol: symbols[i * 2 + 1], decimals: Number(decimals[i * 2 + 1]) } + + allDexPools[rawPool.pool] = { + pool: rawPool.pool, + fee0In: rawPool.fee0In, + fee1In: rawPool.fee1In, + token0: token0, + token1: token1, + reserve0: Number(balances[i * 2]), + reserve1: Number(balances[i * 2 + 1]), + + // will fill below + tvlUsd: 0, + volumeUsd1d: 0, + volumeUsd7d: 0, + feeUsd1d: 0, + feeUsd7d: 0, + } + + allTokens[rawPool.token0Address] = token0; + allTokens[rawPool.token1Address] = token1; + } + + // get token price from llama coins api + const coinLists = Object.keys(allTokens).map(token => `${chain}:${token}`); + const coinPrices = (await superagent.get(`https://coins.llama.fi/prices/current/${coinLists.toString()}`)).body.coins; + for (const [coinId, coinPrice] of Object.entries(coinPrices)) { + allTokens[utils.formatAddress(coinId.split(':')[1])].price = Number(coinPrice.price); + } + + // cal tvl + for (const [address, dexPool] of Object.entries(allDexPools)) { + const token0Price = allTokens[dexPool.token0.address].price ? allTokens[dexPool.token0.address].price : 0; + const token1Price = allTokens[dexPool.token1.address].price ? allTokens[dexPool.token1.address].price : 0; + const token0Reserve = dexPool.reserve0 * token0Price / 10**dexPool.token0.decimals; + const token1Reserve = dexPool.reserve1 * token1Price / 10**dexPool.token1.decimals; + + // update pool tvl USD + allDexPools[address].tvlUsd = token0Reserve + token1Reserve; + } + + const iface = new ethers.utils.Interface([EventPoolSwap]) + const swapLogs = (await sdk.getEventLogs({ + chain: chain, + eventAbi: EventPoolSwap, + targets: Object.values(allDexPools).filter(pool => pool.tvlUsd > 10000).map(pool => pool.pool), + flatten: true, // !!! + fromBlock: last1DaysBlock, + toBlock: currentBlock, + entireLog: true, + })).map(log => { + const event = iface.parseLog({ + topics: log.topics, + data: log.data, + }); + + return { + address: utils.formatAddress(log.address), + blockNumber: Number(log.blockNumber), + args: { + swap0to1: event.args[2].tokenAIn, + amountIn: Number(event.args.amountIn), + amountOut: Number(event.args.amountOut), + } + } + }); + + for (const log of swapLogs) { + let volumeUsd = 0; + let feeUsd = 0; + const dexPool = allDexPools[log.address]; + if (log.args.swap0to1) { + const tokenPrice = allTokens[dexPool.token0.address].price ? allTokens[dexPool.token0.address].price : 0; + volumeUsd = Number(log.args.amountIn) * tokenPrice / 10**dexPool.token0.decimals; + feeUsd = volumeUsd * dexPool.fee0In; + } else { + const tokenPrice = allTokens[dexPool.token1.address].price ? allTokens[dexPool.token1.address].price : 0; + volumeUsd = Number(log.args.amountIn) * tokenPrice / 10**dexPool.token1.decimals; + feeUsd = volumeUsd * dexPool.fee1In; + } + + if (log.blockNumber >= last1DaysBlock) { + allDexPools[log.address].volumeUsd1d += volumeUsd; + allDexPools[log.address].feeUsd1d += feeUsd; + } + allDexPools[log.address].volumeUsd7d += volumeUsd; + allDexPools[log.address].feeUsd7d += feeUsd; + } + + for (const p of Object.values(allDexPools)) { + yieldPools.push({ + chain: utils.formatChain(chain), + project: PROJECT, + pool: p.pool, + symbol: utils.formatSymbol(`${p.token0.symbol}-${p.token1.symbol}`), + underlyingTokens: [p.token0.address, p.token1.address], + tvlUsd: p.tvlUsd, + apyBase: p.feeUsd1d * 100 * 365 / p.tvlUsd, + apyBase7d: p.feeUsd7d * 100 * 365 / 7 / p.tvlUsd, + volumeUsd1d: p.volumeUsd1d, + volumeUsd7d: p.volumeUsd7d, + url: getPoolLink(chain, p.pool), + }) + } + } + + return yieldPools + .filter(pool => pool.tvlUsd > 0); +}; + +module.exports = { + timetravel: true, + apy: main, +}; diff --git a/src/adaptors/utils.js b/src/adaptors/utils.js index 37e6beba6c..d2ad7eeea4 100755 --- a/src/adaptors/utils.js +++ b/src/adaptors/utils.js @@ -5,6 +5,10 @@ const { chunk } = require('lodash'); const sdk = require('@defillama/sdk'); const { default: BigNumber } = require('bignumber.js'); +exports.formatAddress = (address) => { + return String(address).toLowerCase(); +} + exports.formatChain = (chain) => { if (chain && chain.toLowerCase() === 'xdai') return 'Gnosis'; if (chain && chain.toLowerCase() === 'kcc') return 'KCC'; From 2a3b5b98f67f6e7949848bd42dc3b032d7246934 Mon Sep 17 00:00:00 2001 From: Eden Date: Fri, 13 Jun 2025 14:08:34 +0700 Subject: [PATCH 4/5] fix: count 7d volumes --- src/adaptors/maverick-v2/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/maverick-v2/index.js b/src/adaptors/maverick-v2/index.js index 7a5f648083..1c670386c9 100644 --- a/src/adaptors/maverick-v2/index.js +++ b/src/adaptors/maverick-v2/index.js @@ -130,7 +130,7 @@ const main = async (unixTimestamp) => { eventAbi: EventPoolSwap, targets: Object.values(allDexPools).filter(pool => pool.tvlUsd > 10000).map(pool => pool.pool), flatten: true, // !!! - fromBlock: last1DaysBlock, + fromBlock: last7DaysBlock, toBlock: currentBlock, entireLog: true, })).map(log => { From 8e3f796da79d8ae69bb48b5ab46b4e24938bdd02 Mon Sep 17 00:00:00 2001 From: Eden Date: Fri, 13 Jun 2025 15:21:38 +0700 Subject: [PATCH 5/5] support multichain fluid-dex maverick-v2 --- src/adaptors/fluid-dex/index.js | 8 ++++---- src/adaptors/maverick-v2/index.js | 29 ++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/adaptors/fluid-dex/index.js b/src/adaptors/fluid-dex/index.js index f3db171cbf..01af6cdcb0 100644 --- a/src/adaptors/fluid-dex/index.js +++ b/src/adaptors/fluid-dex/index.js @@ -8,9 +8,9 @@ const PROJECT = 'fluid-dex'; const DexReservesResolvers = { ethereum: '0xC93876C0EEd99645DD53937b25433e311881A27C', - // arbitrum: '0x666A400b8cDA0Dc9b59D61706B0F982dDdAF2d98', - // polygon: '0x18DeDd1cF3Af3537D4e726D2Aa81004D65DA8581', - // base: '0x41E6055a282F8b7Abdb8D22Bcd85c2A0eE22e38A', + arbitrum: '0x666A400b8cDA0Dc9b59D61706B0F982dDdAF2d98', + polygon: '0x18DeDd1cF3Af3537D4e726D2Aa81004D65DA8581', + base: '0x41E6055a282F8b7Abdb8D22Bcd85c2A0eE22e38A', } const EventSwap = 'event Swap(bool swap0to1, uint256 amountIn, uint256 amountOut, address to)'; @@ -163,7 +163,7 @@ const main = async (unixTimestamp) => { allDexPools[log.address].volumeUsd7d += volumeUsd; } - for (const p of Object.values(allDexPools)) { + for (const p of Object.values(allDexPools).filter(pool => pool.tvlUsd > 0)) { const feeUsd = p.volumeUsd1d * p.feeRate; const feeUsd7d = p.volumeUsd7d * p.feeRate; diff --git a/src/adaptors/maverick-v2/index.js b/src/adaptors/maverick-v2/index.js index 1c670386c9..dd119a0d15 100644 --- a/src/adaptors/maverick-v2/index.js +++ b/src/adaptors/maverick-v2/index.js @@ -9,7 +9,27 @@ const FactoryConfigs = { ethereum: { factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', fromBlock: 20027237, - } + }, + // arbitrum: { + // factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', + // fromBlock: 219205178, + // }, + // base: { + // factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', + // fromBlock: 15321282, + // }, + // bsc: { + // factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', + // fromBlock: 39421941, + // }, + // scroll: { + // factory: '0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e', + // fromBlock: 7332349, + // }, + // era: { + // factory: '0x7A6902af768a06bdfAb4F076552036bf68D1dc56', + // fromBlock: 35938168, + // }, } const EventPoolCreated = 'event PoolCreated(address poolAddress, uint8 protocolFeeRatio, uint256 feeAIn, uint256 feeBIn, uint256 tickSpacing, uint256 lookback, int32 activeTick, address tokenA, address tokenB, uint8 kinds, address accessor)'; @@ -128,7 +148,7 @@ const main = async (unixTimestamp) => { const swapLogs = (await sdk.getEventLogs({ chain: chain, eventAbi: EventPoolSwap, - targets: Object.values(allDexPools).filter(pool => pool.tvlUsd > 10000).map(pool => pool.pool), + targets: Object.values(allDexPools).filter(pool => pool.tvlUsd > 0).map(pool => pool.pool), flatten: true, // !!! fromBlock: last7DaysBlock, toBlock: currentBlock, @@ -172,7 +192,7 @@ const main = async (unixTimestamp) => { allDexPools[log.address].feeUsd7d += feeUsd; } - for (const p of Object.values(allDexPools)) { + for (const p of Object.values(allDexPools).filter(pool => pool.tvlUsd > 0)) { yieldPools.push({ chain: utils.formatChain(chain), project: PROJECT, @@ -189,8 +209,7 @@ const main = async (unixTimestamp) => { } } - return yieldPools - .filter(pool => pool.tvlUsd > 0); + return yieldPools; }; module.exports = {