diff --git a/data/constants.js b/data/constants.js index 71d41c0..c254454 100644 --- a/data/constants.js +++ b/data/constants.js @@ -156,6 +156,7 @@ const TRADING_APY_TYPES = { UNISWAP_GAMMA_API: 'UNISWAP_GAMMA_API', NOTIONAL_API: 'NOTIONAL_API', ZKSWAP: 'ZKSWAP', + SYNCSWAP: 'SYNCSWAP', } const POOL_TYPES = { diff --git a/data/mainnet/pools.js b/data/mainnet/pools.js index e18da3d..a4cc501 100644 --- a/data/mainnet/pools.js +++ b/data/mainnet/pools.js @@ -8,6 +8,10 @@ module.exports = [ chain: CHAIN_IDS.ZKSYNC, id: 'syncswap_ETH_USDCe_aqua', type: POOL_TYPES.INCENTIVE, + tradingApyFunction: { + type: TRADING_APY_TYPES.SYNCSWAP, + params: [addresses.ZKSYNC.V2.syncswap_ETH_USDCe_aqua.Underlying, CHAIN_IDS.ZKSYNC], + }, contractAddress: addresses.ZKSYNC.V2.syncswap_ETH_USDCe_aqua.NewPool, collateralAddress: addresses.ZKSYNC.V2.syncswap_ETH_USDCe_aqua.NewVault, rewardAPY: [], @@ -34,6 +38,10 @@ module.exports = [ chain: CHAIN_IDS.ZKSYNC, id: 'syncswap_ETH_USDCe_classic', type: POOL_TYPES.INCENTIVE, + tradingApyFunction: { + type: TRADING_APY_TYPES.SYNCSWAP, + params: [addresses.ZKSYNC.V2.syncswap_ETH_USDCe_classic.Underlying, CHAIN_IDS.ZKSYNC], + }, contractAddress: addresses.ZKSYNC.V2.syncswap_ETH_USDCe_classic.NewPool, collateralAddress: addresses.ZKSYNC.V2.syncswap_ETH_USDCe_classic.NewVault, rewardAPY: [], @@ -60,6 +68,10 @@ module.exports = [ chain: CHAIN_IDS.ZKSYNC, id: 'syncswap_USDCe_USDT_stable', type: POOL_TYPES.INCENTIVE, + tradingApyFunction: { + type: TRADING_APY_TYPES.SYNCSWAP, + params: [addresses.ZKSYNC.V2.syncswap_USDCe_USDT_stable.Underlying, CHAIN_IDS.ZKSYNC], + }, contractAddress: addresses.ZKSYNC.V2.syncswap_USDCe_USDT_stable.NewPool, collateralAddress: addresses.ZKSYNC.V2.syncswap_USDCe_USDT_stable.NewVault, rewardAPY: [], @@ -86,6 +98,10 @@ module.exports = [ chain: CHAIN_IDS.ZKSYNC, id: 'syncswap_wrsETH_ETH_aqua', type: POOL_TYPES.INCENTIVE, + tradingApyFunction: { + type: TRADING_APY_TYPES.SYNCSWAP, + params: [addresses.ZKSYNC.V2.syncswap_wrsETH_ETH_aqua.Underlying, CHAIN_IDS.ZKSYNC], + }, contractAddress: addresses.ZKSYNC.V2.syncswap_wrsETH_ETH_aqua.NewPool, collateralAddress: addresses.ZKSYNC.V2.syncswap_wrsETH_ETH_aqua.NewVault, rewardAPY: [], diff --git a/src/lib/constants.js b/src/lib/constants.js index 5a01077..598ca8e 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -215,6 +215,14 @@ const STAKEWISE_API_URLS = { const ZKSWAP_URL = 'https://zkswap-dex-api-git-production-buzzteam.vercel.app/api/pools-data/zksyncMainnet' +const SYNCSWAP_API_URL = + 'https://api.syncswap.xyz/api/fetchers/fetchAllPools?network=zkSyncMainnet&type=v2' + +const SYNCSWAP_FEE_API_URL = 'https://api.syncswap.xyz/api/fetchers/fetchFees?network=zkSyncMainnet' + +const SYNCSWAP_SUBGRAPH_URL = + 'https://api.studio.thegraph.com/query/30365/zksync-blocks/version/latest' + const DEXSCREENER_API_URL = 'https://api.dexscreener.com/latest/dex/tokens' const CURVE_API_URLS = { @@ -365,4 +373,7 @@ module.exports = { ZKSYNC_RPC_URL, CURRENCY_RATES, ZKSWAP_URL, + SYNCSWAP_API_URL, + SYNCSWAP_FEE_API_URL, + SYNCSWAP_SUBGRAPH_URL, } diff --git a/src/lib/third-party/syncswap.js b/src/lib/third-party/syncswap.js new file mode 100644 index 0000000..c3f0ff9 --- /dev/null +++ b/src/lib/third-party/syncswap.js @@ -0,0 +1,55 @@ +const { get } = require('lodash') +const axios = require('axios') +const { SYNCSWAP_SUBGRAPH_URL } = require('../constants') + +const executeSyncswapCall = (url, type, query, variables) => { + return axios + .post(url, { + query: query, + variables: variables, + }) + .then(response => { + let data + + if (type) { + data = get(response, `data.data.${type}`) + } else { + data = get(response, `data.data`) + } + if (data) { + return data + } else { + console.error(get(response, 'data.errors', response)) + return null + } + }) + .catch(error => { + console.error(`Syncswap subgraph (${query}) failed:`, error) + return null + }) +} + +const getTradingVolumeDaily = async (pair, date) => { + const tradingVolQuery = ` + query DailyData($pair: Bytes!, $date: Int!) { + pairDayDatas( + where: { pairAddress: $pair, date: $date } + ) { + dailyVolumeUSD + }} +` + + const variables = { pair, date } + + const tradingVolInfo = await executeSyncswapCall( + SYNCSWAP_SUBGRAPH_URL, + null, + tradingVolQuery, + variables, + ) + return parseFloat(tradingVolInfo?.pairDayDatas[0].dailyVolumeUSD) +} + +module.exports = { + getTradingVolumeDaily, +} diff --git a/src/vaults/trading-apys/implementations/syncswap.js b/src/vaults/trading-apys/implementations/syncswap.js new file mode 100644 index 0000000..6626e05 --- /dev/null +++ b/src/vaults/trading-apys/implementations/syncswap.js @@ -0,0 +1,80 @@ +const BigNumber = require('bignumber.js') +const { get } = require('lodash') +const { cachedAxios } = require('../../../lib/db/models/cache') +const { SYNCSWAP_API_URL, SYNCSWAP_FEE_API_URL } = require('../../../lib/constants') +const { getTokenPrice } = require('../../../prices') +const { getTradingVolumeDaily } = require('../../../lib/third-party/syncswap') + +const getTradingApy = async (pair, chain) => { + let apy + + try { + const poolResponse = await cachedAxios.get(SYNCSWAP_API_URL) + const poolData = get(poolResponse, `data.pools`, []).find(pool => pool.p === pair) + const token0Price = new BigNumber(await getTokenPrice(poolData.t0[0], chain)) + const token0Decimal = poolData.t0[3] + const token1Price = new BigNumber(await getTokenPrice(poolData.t1[0], chain)) + const token1Decimal = poolData.t1[3] + + const token0VolumeUSD = new BigNumber(poolData.r0) + .dividedBy(new BigNumber(10).pow(token0Decimal)) + .times(token0Price) + .toFixed(2, 1) + const token1VolumeUSD = new BigNumber(poolData.r1) + .dividedBy(new BigNumber(10).pow(token1Decimal)) + .times(token1Price) + .toFixed(2, 1) + + if (poolData.t == 3) { + //aqua pool + const feeResponse = await cachedAxios.get(SYNCSWAP_FEE_API_URL) + const feeData = get(feeResponse, `data.data`, []).find( + data => data.pair.toLowerCase() === pair.toLowerCase(), + ) + + const token0DailyFee = new BigNumber(feeData.amount0) + .dividedBy(new BigNumber(10).pow(token0Decimal)) + .times(token0Price) + .toFixed(2, 1) + + const token1DailyFee = new BigNumber(feeData.amount1) + .dividedBy(new BigNumber(10).pow(token1Decimal)) + .times(token1Price) + .toFixed(2, 1) + + // fee apr calculation : APR = (24h volume * pool swap fee * LP fee share * 365) / TVL + // dailyFee = 24h volume * pool swap fee + // LP fee Share = 1 - protocolFee + apy = + ((Number(token0DailyFee) + Number(token1DailyFee)) * + (1 - Number(poolData.pf) / 100000) * + 365 * + 100) / + (Number(token0VolumeUSD) + Number(token1VolumeUSD)) + } else { + const today = new Date() + today.setHours(0, 0, 0, 0) + const yesterday = new Date(today) + yesterday.setDate(today.getDate() - 1) + const timestamp = Math.floor(yesterday.getTime() / 1000) + const dailyVolume = await getTradingVolumeDaily(pair.toLowerCase(), timestamp) + const swapFee = new BigNumber(poolData.f0).toFixed() + apy = + (Number(dailyVolume) * + (swapFee / 100000) * + (1 - Number(poolData.pf) / 100000) * + 365 * + 100) / + (Number(token0VolumeUSD) + Number(token1VolumeUSD)) + } + } catch (err) { + console.error('syncswap API error: ', err) + apy = 0 + } + + return apy !== 0 ? apy.toFixed(2, 1) : 0 +} + +module.exports = { + getTradingApy, +}