From 19f5b85735948f7bcec42438a21f5dcc58f1de4b Mon Sep 17 00:00:00 2001 From: 0xNx Date: Wed, 27 Aug 2025 16:36:17 +0800 Subject: [PATCH] Fix SparkDEX V3 and V3.1 adapters for proper DefiLlama integration --- src/adaptors/sparkdex-v3.1/index.js | 19 ++- src/adaptors/sparkdex-v3/index.js | 215 +++++++++++----------------- 2 files changed, 101 insertions(+), 133 deletions(-) diff --git a/src/adaptors/sparkdex-v3.1/index.js b/src/adaptors/sparkdex-v3.1/index.js index f3d761182e..0542823204 100644 --- a/src/adaptors/sparkdex-v3.1/index.js +++ b/src/adaptors/sparkdex-v3.1/index.js @@ -3,6 +3,11 @@ const axios = require('axios'); const rFLR = '0x26d460c3Cf931Fb2014FA436a49e3Af08619810e'; // Reward FLR const calculateApy = (_apr) => { + // Handle extremely high APR values that could cause calculation issues + if (_apr > 10000) { // If APR > 10000%, cap it to prevent extreme values + return 10000; + } + const APR = _apr / 100; const n = 365; @@ -10,7 +15,8 @@ const calculateApy = (_apr) => { const APY = (1 + APR / n) ** n - 1; const APYPercentage = APY * 100; - return APYPercentage; + // Cap APY to reasonable maximum (10000%) + return Math.min(APYPercentage, 10000); }; const apy = async () => { @@ -20,15 +26,20 @@ const apy = async () => { ) ).data[0].data; - const chain = 'Flare'; + const chain = 'flare'; const i = pools.map((lp) => { + // Skip pools without APR data if (!lp.apr) return; + + // Skip pools with zero or negative TVL + if (!lp.tvlUSD || lp.tvlUSD <= 0) return; const tvlUsd = lp.tvlUSD; - const feeUsd = lp.feesUSDDay; + const feeUsd = lp.feesUSDDay || 0; - const baseApy = (feeUsd / tvlUsd) * 365 * 100; + // Calculate base APY, handle division by zero + const baseApy = tvlUsd > 0 && feeUsd > 0 ? (feeUsd / tvlUsd) * 365 * 100 : 0; let pool = { pool: `${lp.id}-${chain}`.toLowerCase(), diff --git a/src/adaptors/sparkdex-v3/index.js b/src/adaptors/sparkdex-v3/index.js index c136dcaa18..18e145ed80 100644 --- a/src/adaptors/sparkdex-v3/index.js +++ b/src/adaptors/sparkdex-v3/index.js @@ -1,144 +1,101 @@ -const utils = require('../utils'); - -/** - * @typedef {Object} LPPage - * @property {string} address - * @property {Object} token0 - * @property {string} token0.address - * @property {string} token0.symbol - * @property {Object} token1 - * @property {string} token1.address - * @property {string} token1.symbol - * @property {Array} statistics - * @property {number} statistics.tvlUSD - * @property {number} statistics.feeUSD - * @property {string} statistics.period - * @property {Array} emissions - * @property {number} emissions.dailyEmission - * @property {string} emissions.startDate - * @property {string} emissions.endDate - */ - -/** - * @typedef {Object} Pool - * @property {string} pool - * @property {string} chain - * @property {string} project - * @property {string} symbol - * @property {number} tvlUsd - * @property {number} apyBase - * @property {number} apyReward - * @property {Array} rewardTokens - * @property {Array} underlyingTokens - * @property {string} poolMeta - * @property {string} url - * @property {number} apyBaseBorrow - * @property {number} apyRewardBorrow - * @property {number} totalSupplyUsd - * @property {number} totalBorrowUsd - * @property {number} ltv - */ - -/** - * - * @returns {Promise} - */ -function getFlrPrice() { - return utils.getData('https://api.flaremetrics.io/v2/defi/flare/price'); -} - -/** - * - * @param {number} limit - * @param {number} offset - * @returns {Promise} - */ -function getLPPage(limit, offset) { - return utils.getData( - `https://api.flaremetrics.io/v2/defi/flare/liquidity-pools?product=sparkdex-pool&tvlUSD=10000&limit=${limit}&offset=${offset}` - ); -} - -/** - * @param {number} now - * @param {number} flrPrice - * @returns {(lp: LPPage) => Pool[]} - */ -function makePoolFlatmap(now, flrPrice) { - /** - * @param {LPPage} lp - */ - return function poolFlatMap(lp) { - const chain = 'Flare'; - const address = lp.address; - const token0symbol = lp.token0.symbol; - const token1symbol = lp.token1.symbol; - const token0address = lp.token0.address; - const token1address = lp.token1.address; - const stats = lp.statistics.find((s) => s.period == '1d'); - - if (!stats) return []; - - const tvlUsd = stats.tvlUSD; - const feeUsd = stats.feeUSD; - - const apyBase = (feeUsd / tvlUsd) * 365 * 100; - /** - * @type {Pool} - */ - const pool = { - pool: `${address}-${chain}`.toLowerCase(), - chain, +const axios = require('axios'); + +const rFLR = '0x26d460c3Cf931Fb2014FA436a49e3Af08619810e'; // Reward FLR + +const calculateApy = (_apr) => { + // Handle extremely high APR values that could cause calculation issues + if (_apr > 10000) { // If APR > 10000%, cap it to prevent extreme values + return 10000; + } + + const APR = _apr / 100; + const n = 365; + + // APY calculation + const APY = (1 + APR / n) ** n - 1; + const APYPercentage = APY * 100; + + // Cap APY to reasonable maximum (10000%) + return Math.min(APYPercentage, 10000); +}; + +const apy = async () => { + const pools = ( + await axios.get( + 'https://api.sparkdex.ai/dex/v3/pairs?chainId=14&dex=SparkDEX&version=v3' + ) + ).data[0].data; + + const chain = 'flare'; + + const i = pools.map((lp) => { + // Skip pools without APR data + if (!lp.apr) return; + + // Skip pools with zero or negative TVL + if (!lp.tvlUSD || lp.tvlUSD <= 0) return; + + const tvlUsd = lp.tvlUSD; + const feeUsd = lp.feesUSDDay || 0; + + // Calculate base APY, handle division by zero + const baseApy = tvlUsd > 0 && feeUsd > 0 ? (feeUsd / tvlUsd) * 365 * 100 : 0; + + let pool = { + pool: `${lp.id}-${chain}`.toLowerCase(), + symbol: `${lp.token0.symbol}-${lp.token1.symbol}`, project: 'sparkdex-v3', - symbol: `${token0symbol}-${token1symbol}`, + chain, tvlUsd, - apyBase, - underlyingTokens: [token0address, token1address], + apyBase: baseApy, + underlyingTokens: [lp.token0.address, lp.token1.address], }; - const emissions = lp.emissions || []; - const emission = emissions.find((e) => { - const startDate = Date.parse(e.startDate); - const endDate = Date.parse(e.endDate); - return now >= startDate && now < endDate; - }); - - if (!emission) { - if (apyBase == 0) return []; - - return pool; + // Extract rFLR reward APRs + const rFlrRewardAprs = lp.aprs.filter( + (apr) => apr.provider === 'rFLR Rewards' + ); + + // Extract other reward APRs + const rewardAprs = lp.aprs.filter( + (apr) => !apr.isPoolApr && apr.provider !== 'rFLR Rewards' + ); + + // Calculate total reward APY from all sources + const totalRewardApy = calculateApy( + rewardAprs.reduce((sum, apr) => sum + apr.apr, 0) + ); + const rFlrRewardApy = calculateApy( + rFlrRewardAprs.reduce((sum, apr) => sum + apr.apr, 0) + ); + + // Add remaining rewards + if (totalRewardApy > 0) { + // Rewards can be swapped instantly to wFLR + pool.apyReward = totalRewardApy; + pool.rewardTokens = [rFLR]; // rFLR } - pool.apyReward = ((emission.dailyEmission * flrPrice) / tvlUsd) * 365 * 100; - pool.rewardTokens = ['0x26d460c3Cf931Fb2014FA436a49e3Af08619810e']; // rFLR + // Add rFLR apr + if (rFlrRewardApy > 0) { + // rFLR can be swapped with 50% penalty instantly to wFLR or linear 12 months + // so taking care to lower bund %50 + pool.apyReward = (pool.apyReward || 0) + rFlrRewardApy / 2; + pool.rewardTokens = [rFLR]; // rFLR + } return pool; - }; -} - -async function poolsFunction() { - const now = Date.now(); - const flrPrice = await getFlrPrice(); - /** - * @type {LPPage[]} - */ - const liquidityPools = []; - const limit = 250; + }); - for (let i = 0; i < 10; i += 1) { - const offset = i * limit; - const lpPage = await getLPPage(limit, offset); + const result = i.filter(Boolean); - liquidityPools.push(...lpPage); + console.log(result); - if (lpPage.length < limit) break; - } - - return liquidityPools.flatMap(makePoolFlatmap(now, flrPrice)); -} + return result; +}; module.exports = { timetravel: false, - apy: poolsFunction, - url: 'https://sparkdex.ai/apps/liquidity', + apy, + url: 'https://sparkdex.ai/pool', };