From d34dc0198431a893782fce41d094624046846a69 Mon Sep 17 00:00:00 2001 From: potvik Date: Tue, 4 Jun 2024 18:30:13 +0300 Subject: [PATCH 1/3] harmony contracts --- src/addresses.js | 10 ++++++++++ src/dataProvider.js | 21 +++++++++++++++++++++ src/routes.js | 2 +- src/tokens.js | 43 ++++++++++--------------------------------- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/addresses.js b/src/addresses.js index a2782ec..6dceafb 100644 --- a/src/addresses.js +++ b/src/addresses.js @@ -26,6 +26,16 @@ export const addresses = { GlpManager: '0x321F653eED006AD1C29D174e17d96351BDe22649' }, + [HARMONY]: { + GMX: '0x3F1bEa7B9e2836C9B559c143f99d717aec6619dd', + BUSD: '0x1Aa1F7815103c0700b98f24138581b88d4cf9769', + WONE: '0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a', + LINK: '0x218532a12a389a4a92fC0C5Fb22901D1c19198aA', + RewardReader: '0x820B9C96ac9afE46b4AfD8912A986f180e40df6b', + GLP: '0x1EE4a17871Aa61eF02B846795b7554aE7EAbb179', + GlpManager: '0xe51CB3361dE553fb7B75B49E5552e9D47B4aeDb0' + }, + [AVALANCHE]: { GMX: '0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a', AVAX: '0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7', diff --git a/src/dataProvider.js b/src/dataProvider.js index 4707f10..b0fd527 100644 --- a/src/dataProvider.js +++ b/src/dataProvider.js @@ -139,6 +139,11 @@ export const tokenDecimals = { "0xfea7a6a0b346362bf88a9e4a88416b77a57d6c2a": 18, // MIM "0x17fc002b466eec40dae837fc4be5c67993ddbd6f": 18, // FRAX "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1": 18, // DAI + + // Harmony + "0x1Aa1F7815103c0700b98f24138581b88d4cf9769": 18, // BUSD + "0x218532a12a389a4a92fC0C5Fb22901D1c19198aA": 18, // LINK + "0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a": 18, // WONE } export const tokenSymbols = { @@ -154,6 +159,11 @@ export const tokenSymbols = { '0x17fc002b466eec40dae837fc4be5c67993ddbd6f': 'FRAX', '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1': 'DAI', + // Harmony + "0x1Aa1F7815103c0700b98f24138581b88d4cf9769": 'BUSD', + "0x218532a12a389a4a92fC0C5Fb22901D1c19198aA": 'LINK', + "0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a": 'WONE', + // Avalanche '0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7': 'AVAX', '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab': 'WETH.e', @@ -194,6 +204,17 @@ const knownSwapSources = { '0xc30141b657f4216252dc59af2e7cdb9d8792e1b0': 'socket.tech', '0xdd94018f54e565dbfc939f7c44a16e163faab331': 'Odos Router' }, + harmony: { + '0x5D76ed731bC8Fb6eC50a0d4Bd39dA00A679E5a66': 'GMX Router', // Router + '0x45440437e3f8dF7B4b99f0CdCA6E14B46765d791': 'GMX OrderBook', // Orderbook + '0x98a00666cfcb2ba5a405415c2bf6547c63bf5491': 'GMX PositionManager A', // PositionManager old + '0x2500Fe3bb03B2B514b0BBF6d1Bd88A84298bBBee': 'GMX PositionManager B', // PositionManager + '0x75e42e6f01baf1d6022bea862a28774a9f8a4a0c': 'GMX PositionManager C', // PositionManager 12 oct 2022 + '0x7119B1F55a949e4D5981c2C9d5dABd897f0feDfA': 'GMX PositionRouter C', // PositionRouter 12 oct 2022 + '0xfa812ed63558f886d2F3Ac89984dfAe87E0D37f6': 'GMX OrderExecutor', // OrderExecutor + '0x820B9C96ac9afE46b4AfD8912A986f180e40df6b': 'GMX FastPriceFeed A', // FastPriceFeed + '0x1EE4a17871Aa61eF02B846795b7554aE7EAbb179': 'GMX PositionExecutor', // Position Executor + }, avalanche: { '0x4296e307f108b2f583ff2f7b7270ee7831574ae5': 'GMX OrderBook', '0x5f719c2f1095f7b9fc68a68e35b51194f4b6abe8': 'GMX Router', diff --git a/src/routes.js b/src/routes.js index 54169fa..04010b2 100644 --- a/src/routes.js +++ b/src/routes.js @@ -71,7 +71,7 @@ export default function routes(app) { return } - const validSymbols = new Set(['BTC', 'ETH', 'BNB', 'UNI', 'LINK', 'AVAX']) + const validSymbols = new Set(['BUSD', 'WONE', 'LINK']) const symbol = req.params.symbol if (!validSymbols.has(symbol)) { next(createHttpError(400, `Invalid symbol ${symbol}`)) diff --git a/src/tokens.js b/src/tokens.js index 3c72590..76cdf6f 100644 --- a/src/tokens.js +++ b/src/tokens.js @@ -1,44 +1,21 @@ export const TOKENS = [ - { - symbol: 'BTC', - address: '0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c', - defaultPrice: 35000, - coingeckoId: 'bitcoin' - }, - { - symbol: 'ETH', - address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', - defaultPrice: 2000, - coingeckoId: 'ethereum' - }, - { - symbol: 'BNB', - address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', - defaultPrice: 300, - coingeckoId: 'binancecoin' - }, - { - symbol: 'USDG', - address: '0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7', - defaultPrice: 1 - }, { symbol: 'BUSD', - address: '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', + address: '0x1Aa1F7815103c0700b98f24138581b88d4cf9769', defaultPrice: 1, - stable: true + coingeckoId: 'busd' }, { - symbol: 'USDT', - address: '0x55d398326f99059fF775485246999027B3197955', - defaultPrice: 1, - stable: true + symbol: 'WONE', + address: '0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a', + defaultPrice: 0.02, + coingeckoId: 'one' }, { - symbol: 'USDC', - address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', - defaultPrice: 1, - stable: true + symbol: 'LINK', + address: '0x218532a12a389a4a92fC0C5Fb22901D1c19198aA', + defaultPrice: 18, + coingeckoId: 'link' } ] From 189358871524a7ed44de1acf3b4f6c631264d2c9 Mon Sep 17 00:00:00 2001 From: potvik Date: Sat, 15 Jun 2024 01:33:52 +0300 Subject: [PATCH 2/3] added harmony support --- .dockerignore | 5 + Dockerfile | 39 ++++ fly.toml | 25 +++ src/App.js | 34 +-- src/addresses.js | 1 + src/dataProvider.js | 27 ++- src/middlewares.js | 4 +- src/views/Harmony.js | 513 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 630 insertions(+), 18 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 fly.toml create mode 100644 src/views/Harmony.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..47719be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +fly.toml +Dockerfile +.dockerignore +node_modules +.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e5331cb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax = docker/dockerfile:1 + +# Adjust NODE_VERSION as desired +ARG NODE_VERSION=18.16.0 +FROM node:${NODE_VERSION}-slim as base + +LABEL fly_launch_runtime="NodeJS" + +# NodeJS app lives here +WORKDIR /app + +# Set production environment +ENV NODE_ENV=production + + +# Throw-away build stage to reduce size of final image +FROM base as build + +# Install packages needed to build node modules +RUN apt-get update -qq && \ + apt-get install -y python-is-python3 pkg-config build-essential + +# Install node modules +COPY --link package.json package-lock.json . +RUN npm install + +# Copy application code +COPY --link . . + + + +# Final stage for app image +FROM base + +# Copy built application +COPY --from=build /app /app + +# Start the server by default, this can be overwritten at runtime +CMD [ "npm", "run", "start:prod" ] diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..ead7739 --- /dev/null +++ b/fly.toml @@ -0,0 +1,25 @@ +# fly.toml app configuration file generated for gmx-stats on 2024-06-14T23:28:35+03:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'gmx-stats' +primary_region = 'hkg' + +[build] + +[env] + PORT = '8080' + +[http_service] + internal_port = 8080 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 diff --git a/src/App.js b/src/App.js index d02f19d..6b78d07 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,7 @@ import { Route, Switch, NavLink, Redirect } from 'react-router-dom'; import { motion, AnimatePresence } from "framer-motion"; import cx from "classnames"; import Arbitrum from './views/Arbitrum'; +import Harmony from './views/Harmony'; import Referrals from './views/Referrals'; import Avalanche from './views/Avalanche'; import Trading from './views/Trading'; @@ -22,16 +23,19 @@ function AppHeaderLinks({ mode, small, clickCloseIcon }) {
clickCloseIcon()}>
- + {/* GMX Logo - + */} } -
+ {/*
Arbitrum
Avalanche +
*/} +
+ Harmony
) @@ -90,15 +94,16 @@ const App = () => { {!isDrawerVisible && } {isDrawerVisible && } - + {/* - - Arbitrum - Avalanche + */} + {/* Arbitrum + Avalanche */} + Harmony
- APP - DOCS + {/* APP + DOCS */}
switchMode()}> {mode == 'dark' ? : }
@@ -121,18 +126,21 @@ const App = () => {
- + - ( + {/* ( + )} /> */} + ( + )} /> - ( + {/* ( )} /> ( )} /> - + */}
} diff --git a/src/addresses.js b/src/addresses.js index 6dceafb..498190a 100644 --- a/src/addresses.js +++ b/src/addresses.js @@ -1,6 +1,7 @@ export const BSC = 56 export const ARBITRUM = 42161 export const AVALANCHE = 43114 +export const HARMONY = 1666600000 export const addresses = { [BSC]: { diff --git a/src/dataProvider.js b/src/dataProvider.js index b0fd527..404067b 100644 --- a/src/dataProvider.js +++ b/src/dataProvider.js @@ -16,6 +16,7 @@ import GlpManager from '../abis/GlpManager.json' import Token from '../abis/v1/Token.json' const providers = { + harmony: new JsonRpcProvider('https://api.harmony.one'), arbitrum: new JsonRpcProvider('https://arb1.arbitrum.io/rpc'), avalanche: new JsonRpcProvider('https://api.avax.network/ext/bc/C/rpc') } @@ -298,6 +299,10 @@ function getImpermanentLoss(change) { } function getChainSubgraph(chainName) { + if(chainName === "harmony" ) { + return "gmx-harmony-stats" + } + return chainName === "arbitrum" ? "gmx-arbitrum-stats" : "gmx-avalanche-stats" } @@ -311,8 +316,14 @@ export function useGraph(querySource, { subgraph = null, subgraphUrl = null, cha subgraphUrl = `https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/${subgraph}/api`; } + subgraphUrl = "https://api.studio.thegraph.com/query/78881/gmx-h-stats/v0.0.6"; + const client = new ApolloClient({ - link: new HttpLink({ uri: subgraphUrl, fetch }), + link: new HttpLink({ + uri: subgraphUrl, + credentials: 'same-origin', + fetch + }), cache: new InMemoryCache() }) const [data, setData] = useState() @@ -447,8 +458,16 @@ export function useTradersData({ from = FIRST_DATE_TS, to = NOW_TS, chainName = } }) : null - if (data) { - const maxProfit = maxBy(data, item => item.profit).profit + if (data && data.length) { + let maxProfit; + + try { + maxProfit = maxBy(data, item => item.profit).profit + } catch (e) { + console.error(e); + maxProfit = 0; + } + const maxLoss = minBy(data, item => item.loss).loss const maxProfitLoss = Math.max(maxProfit, -maxLoss) @@ -1081,7 +1100,7 @@ export function useGlpPerformanceData(glpData, feesData, { from = FIRST_DATE_TS, const feesDataById = feesData.reduce((memo, item) => { memo[item.timestamp] = item return memo - }) + }, {}) let BTC_WEIGHT = 0 let ETH_WEIGHT = 0 diff --git a/src/middlewares.js b/src/middlewares.js index d830e04..ea6a515 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -33,7 +33,9 @@ export function csp(req, res, next) { "https://api.avax.network", "https://gmx-server-mainnet.uw.r.appspot.com", "https://api.coingecko.com", - "https://subgraph.satsuma-prod.com" + "https://subgraph.satsuma-prod.com", + "https://api.studio.thegraph.com", + "https://api.harmony.one", ] } if (!IS_PRODUCTION) { diff --git a/src/views/Harmony.js b/src/views/Harmony.js new file mode 100644 index 0000000..f0f82b9 --- /dev/null +++ b/src/views/Harmony.js @@ -0,0 +1,513 @@ +import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import * as ethers from 'ethers' +import moment from 'moment' +import { RiLoader5Fill } from 'react-icons/ri' +import cx from "classnames"; + +import { + yaxisFormatterNumber, + yaxisFormatterPercent, + yaxisFormatter, + tooltipLabelFormatter, + tooltipLabelFormatterUnits, + tooltipFormatter, + tooltipFormatterNumber, + tooltipFormatterPercent, + formatNumber, + CHART_HEIGHT, + YAXIS_WIDTH, + COLORS, + GREEN, + RED, + convertToPercents +} from '../helpers' + +import { + LineChart, + Line, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + ComposedChart, + Cell +} from 'recharts'; + +import ChartWrapper from '../components/ChartWrapper' +import VolumeChart from '../components/VolumeChart' +import FeesChart from '../components/FeesChart' +import GenericChart from '../components/GenericChart' +import DateRangeSelect from '../components/DateRangeSelect' + +import { + useVolumeData, + useFeesData, + useGlpData, + useGlpPerformanceData, + useTradersData, + useSwapSources, + useFundingRateData, + useUsersData, + useLastSubgraphBlock, + useLastBlock +} from '../dataProvider' +import PoolAmountChart from '../components/PoolAmountChart'; +import TradersProfitLossChart from '../components/TradersProfitLossChart'; +import useChartDomain from '../hooks/useChartDomain'; + +const NOW = Math.floor(Date.now() / 1000) + +function Harmony(props) { + const DEFAULT_GROUP_PERIOD = 86400 + const [groupPeriod] = useState(DEFAULT_GROUP_PERIOD) + const [dataRange, setDataRange] = useState({ fromValue: moment().subtract(3, 'month').toDate(), toValue: null }) + + const { mode } = props + + const from = dataRange.fromValue ? Math.floor(+new Date(dataRange.fromValue) / 1000) : undefined + const to = dataRange.toValue ? Math.floor(+new Date(dataRange.toValue) / 1000) : NOW + + const params = { from, to, groupPeriod, chainName: 'harmony' } + + const [fundingRateData, fundingRateLoading] = useFundingRateData(params) + + const [volumeData, volumeLoading] = useVolumeData(params) + const [totalVolumeData, totalVolumeLoading] = useVolumeData({ chainName: 'harmony' }) + // const [volumeData, volumeLoading] = useVolumeDataFromServer(params) + // const [totalVolume] = useTotalVolumeFromServer() + const [totalVolume, totalVolumeDelta] = useMemo(() => { + if (!totalVolumeData) { + return [] + } + const total = totalVolumeData[totalVolumeData.length - 1]?.cumulative + const delta = total - totalVolumeData[totalVolumeData.length - 2]?.cumulative + return [total, delta] + }, [totalVolumeData]) + + const [feesData, feesLoading] = useFeesData(params) + const [totalFeesData, totalFeesLoading] = useFeesData({ chainName: 'harmony' }) + const [totalFees, totalFeesDelta] = useMemo(() => { + if (!totalFeesData) { + return [] + } + const total = totalFeesData[totalFeesData.length - 1]?.cumulative + const delta = total - totalFeesData[totalFeesData.length - 2]?.cumulative + return [total, delta] + }, [totalFeesData]) + + const [glpData, glpLoading] = useGlpData(params) + const [totalGlpData, totalGlpLoading] = useGlpData({ chainName: 'harmony' }) + const [totalAum, totalAumDelta] = useMemo(() => { + if (!totalGlpData) { + return [] + } + const total = totalGlpData[totalGlpData.length - 1]?.aum + const delta = total - totalGlpData[totalGlpData.length - 2]?.aum + return [total, delta] + }, [totalGlpData]) + + // const [aumPerformanceData, aumPerformanceLoading] = useAumPerformanceData(params) + const [glpPerformanceData, glpPerformanceLoading] = useGlpPerformanceData(glpData, feesData, params) + + const [minCollectedFees, maxCollectedFees] = useChartDomain(glpPerformanceData, ["performanceLpBtcCollectedFees", "performanceLpEthCollectedFees", "performanceLpAvaxCollectedFees", "performanceSyntheticCollectedFees"], [80, 180]) + const [minGlpPrice, maxGlpPrice] = useChartDomain(glpPerformanceData, ["syntheticPrice", "glpPrice", "glpPlusFees", "lpBtcPrice", "lpEthPrice", "lpAvaxPrice"], [0.4, 1.7]) + + const [tradersData, tradersLoading] = useTradersData(params) + const [totalTradersData, totalTradersLoading] = useTradersData({ chainName: 'harmony' }) + const [openInterest, openInterestDelta] = useMemo(() => { + if (!totalTradersData) { + return [] + } + const total = totalTradersData.data[totalTradersData.data.length - 1]?.openInterest + const delta = total - totalTradersData.data[totalTradersData.data.length - 2]?.openInterest + return [total, delta] + }, [totalTradersData]) + + const [usersData, usersLoading] = useUsersData(params) + const [totalUsersData, totalUsersLoading] = useUsersData({ chainName: 'harmony' }) + const [totalUsers, totalUsersDelta] = useMemo(() => { + if (!totalUsersData) { + return [null, null] + } + const total = totalUsersData[totalUsersData.length - 1]?.uniqueCountCumulative + const prevTotal = totalUsersData[totalUsersData.length - 2]?.uniqueCountCumulative + const delta = total && prevTotal ? total - prevTotal : null + return [ + total, + delta + ] + }, [totalUsersData]) + + const [swapSources, swapSourcesLoading] = useSwapSources(params) + const swapSourcesKeys = Object.keys((swapSources || []).reduce((memo, el) => { + Object.keys(el).forEach(key => { + if (key === 'all' || key === 'timestamp') return + memo[key] = true + }) + return memo + }, {})) + + const [lastSubgraphBlock, , lastSubgraphBlockError] = useLastSubgraphBlock(params.chainName) + const [lastBlock] = useLastBlock(params.chainName) + + const isObsolete = lastSubgraphBlock && lastBlock && lastBlock.timestamp - lastSubgraphBlock.timestamp > 3600 + + const onDateRangeChange = (dates) => { + const [start, end] = dates; + setDataRange({ fromValue: start, toValue: end }) + }; + + const dateRangeOptions = [{ + label: "Last Month", + id: 1 + }, { + label: "Last 2 Months", + id: 2, + isDefault: true, + }, { + label: "Last 3 Months", + id: 3, + }, { + label: "All time", + id: 4 + }] + + return ( +
+
+
+

GMX V1 Analytics / Harmony

+

Does not include V2 data

+ {lastSubgraphBlock && lastBlock && +

+ {isObsolete && "Data is obsolete. "} + Updated {moment(lastSubgraphBlock.timestamp * 1000).fromNow()} +  at block {lastSubgraphBlock.number} +

+ } + { + lastSubgraphBlockError && +

+ Subgraph data is temporarily unavailable. +

+ } +
+
+ +
+
+
+
+ {totalVolume ? <> +
Total Volume
+
+ {formatNumber(totalVolume, { currency: true })} + {!!totalVolumeDelta && + +{formatNumber(totalVolumeDelta, { currency: true, compact: true })} + } +
+ : null} + {totalVolumeLoading && } +
+
+ {totalFees ? <> +
Total Fees
+
+ {formatNumber(totalFees, { currency: true })} + {!!totalFeesDelta && + +{formatNumber(totalFeesDelta, { currency: true, compact: true })} + } +
+ : null} + {totalFeesLoading && } +
+
+ {totalAum ? <> +
GLP Pool
+
+ {formatNumber(totalAum, { currency: true })} + {!!totalAumDelta && + 0 ? 'plus' : 'minus'))} title="Change since previous day">{totalAumDelta > 0 ? '+' : ''}{formatNumber(totalAumDelta, { currency: true, compact: true })} + } +
+ : null} + {totalGlpLoading && } +
+
+ {totalUsers && <> +
Total Users
+
+ {formatNumber(totalUsers)} + {!!totalUsersDelta && + +{formatNumber(totalUsersDelta)} + } +
+ } + {totalUsersLoading && } +
+
+ {openInterest ? <> +
Open Interest
+
+ {formatNumber(openInterest, { currency: true })} + {!!openInterestDelta && + 0 ? 'plus' : 'minus'))} title="Change since previous day"> + {openInterestDelta > 0 ? '+' : ''}{formatNumber(openInterestDelta, { currency: true, compact: true })} + + } +
+ : null} + {totalTradersLoading && } +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + + + + +
+

+ % of Index is Glp with fees / Index Price * 100. Index is a basket 16.6% AVAX, 16.6% BTC, 16.6% ETH and 50% USDC rebalanced once a day +
+ % of LP TOKEN-USDC is Glp Price with fees / LP TOKEN-USDC * 100
+

+
+
+
+
+ + + + + + + + + + + + + + + + + +
+

+ Glp with fees is based on GLP share of fees received and excluding esGMX rewards
+ Index Price is a basket 16.6% AVAX, 16.6% BTC, 16.6% ETH and 50% USDC rebalanced once a day +

+
+
+
+
+ + + + + + + + + + + {(tradersData?.data || []).map((item, i) => { + return 0 ? '#22c761' : '#f93333'} /> + })} + + + + +
+

Considers settled (closed) positions

+

Fees are not factored into PnL

+
+
+
+
+ +
+
+ ({ all: item.openInterest, ...item }))} + controls={{ + convertToPercents: convertToPercents + }} + yaxisDataKey="all" + items={[{ key: 'shortOpenInterest', name: 'Short', color: RED }, { key: 'longOpenInterest', name: 'Long', color: GREEN }]} + type="Bar" + /> +
+
+ +
+
+ +
+
+ ({ ...item, all: item.newCount }))} + truncateYThreshold={6000} + yaxisDataKey="newCount" + rightYaxisDataKey="uniqueCountCumulative" + yaxisTickFormatter={yaxisFormatterNumber} + tooltipFormatter={tooltipFormatterNumber} + tooltipLabelFormatter={tooltipLabelFormatterUnits} + items={[ + { key: 'newSwapCount', name: 'Swap' }, + { key: 'newMarginCount', name: 'Margin trading' }, + { key: 'newMintBurnCount', name: 'Mint & Burn' }, + { key: 'cumulativeNewUserCount', name: 'Cumulative', type: 'Line', yAxisId: 'right', strokeWidth: 2, color: COLORS[4] } + ]} + type="Composed" + /> +
+
+ ({ key }))} + /> +
+
+
+ ); +} + +export default Harmony; From 78451b479b7bf1f79706893ad40626e9f6e3d749 Mon Sep 17 00:00:00 2001 From: potvik Date: Fri, 26 Jul 2024 00:25:37 +0300 Subject: [PATCH 3/3] integration with gmx-harmony-prices subgraph --- src/addresses.js | 12 +++++++++--- src/dataProvider.js | 13 ++++++++----- src/graph.js | 26 +++++++++++++++++++++----- src/prices.js | 26 +++++++++++++++++--------- src/routes.js | 6 +++--- src/stats.js | 15 ++++++--------- 6 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/addresses.js b/src/addresses.js index 498190a..3532351 100644 --- a/src/addresses.js +++ b/src/addresses.js @@ -28,10 +28,16 @@ export const addresses = { }, [HARMONY]: { - GMX: '0x3F1bEa7B9e2836C9B559c143f99d717aec6619dd', - BUSD: '0x1Aa1F7815103c0700b98f24138581b88d4cf9769', WONE: '0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a', - LINK: '0x218532a12a389a4a92fC0C5Fb22901D1c19198aA', + ONE: '0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a', + USDT: '0xF2732e8048f1a411C63e2df51d08f4f52E598005', + USDC: '0xBC594CABd205bD993e7FfA6F3e9ceA75c1110da5', + ETH: '0x4cC435d7b9557d54d6EF02d69Bbf72634905Bf11', + BTC: '0x118f50d23810c5E09Ebffb42d7D3328dbF75C2c2', + + GXP: '0x7C54F8d25F224DeE7A0FA7583f621E9AebE24DfE', + GMX: '0x9D609c2c561e31dC0B9664F060590de7953a215A', + RewardReader: '0x820B9C96ac9afE46b4AfD8912A986f180e40df6b', GLP: '0x1EE4a17871Aa61eF02B846795b7554aE7EAbb179', GlpManager: '0xe51CB3361dE553fb7B75B49E5552e9D47B4aeDb0' diff --git a/src/dataProvider.js b/src/dataProvider.js index 404067b..fddf5e8 100644 --- a/src/dataProvider.js +++ b/src/dataProvider.js @@ -300,12 +300,14 @@ function getImpermanentLoss(change) { function getChainSubgraph(chainName) { if(chainName === "harmony" ) { - return "gmx-harmony-stats" + return "gmx-h-stats" } return chainName === "arbitrum" ? "gmx-arbitrum-stats" : "gmx-avalanche-stats" } +const SATSUMA_KEY = process.env.SATSUMA_KEY || ""; // "default" key + export function useGraph(querySource, { subgraph = null, subgraphUrl = null, chainName = "arbitrum" } = {}) { const query = gql(querySource) @@ -313,10 +315,11 @@ export function useGraph(querySource, { subgraph = null, subgraphUrl = null, cha if (!subgraph) { subgraph = getChainSubgraph(chainName) } - subgraphUrl = `https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/${subgraph}/api`; + + subgraphUrl = `https://api.studio.thegraph.com/query/${SATSUMA_KEY}/gmx-h-stats/v0.0.8`; } - subgraphUrl = "https://api.studio.thegraph.com/query/78881/gmx-h-stats/v0.0.6"; + subgraphUrl = `https://api.studio.thegraph.com/query/${SATSUMA_KEY}/gmx-h-stats/v0.0.8`; const client = new ApolloClient({ link: new HttpLink({ @@ -816,9 +819,9 @@ export function useFundingRateData({ from = FIRST_DATE_TS, to = NOW_TS, chainNam const MOVING_AVERAGE_DAYS = 7 const MOVING_AVERAGE_PERIOD = 86400 * MOVING_AVERAGE_DAYS -export function useVolumeData({ from = FIRST_DATE_TS, to = NOW_TS, chainName = "arbitrum" } = {}) { +export function useVolumeData({ from = FIRST_DATE_TS, to = NOW_TS, chainName = "harmony" } = {}) { const PROPS = 'margin liquidation swap mint burn'.split(' ') - const timestampProp = chainName === "arbitrum" ? "id" : "timestamp" + const timestampProp = chainName === "harmony" ? "id" : "timestamp" const query = `{ volumeStats( first: 1000, diff --git a/src/graph.js b/src/graph.js index 242b318..090c00d 100644 --- a/src/graph.js +++ b/src/graph.js @@ -1,7 +1,7 @@ import fetch from 'cross-fetch'; import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client' -import { ARBITRUM, AVALANCHE } from './addresses' +import { ARBITRUM, AVALANCHE, HARMONY } from './addresses' const apolloOptions = { query: { @@ -12,12 +12,18 @@ const apolloOptions = { } } -const SATSUMA_KEY = process.env.SATSUMA_KEY || "3b2ced13c8d9"; // "default" key +const SATSUMA_KEY = process.env.SATSUMA_KEY || ""; // "default" key function getSubgraphUrl(name) { - return `https://subgraph.satsuma-prod.com/${SATSUMA_KEY}/gmx/${name}/api` + return `https://api.studio.thegraph.com/query/${SATSUMA_KEY}/${name}/version/latest` } +const harmonyStatsClient = new ApolloClient({ + link: new HttpLink({ uri: getSubgraphUrl("gmx-h-stats"), fetch }), + cache: new InMemoryCache(), + defaultOptions: apolloOptions +}) + const arbitrumStatsClient = new ApolloClient({ link: new HttpLink({ uri: getSubgraphUrl("gmx-arbitrum-stats"), fetch }), cache: new InMemoryCache(), @@ -31,7 +37,9 @@ const avalancheStatsClient = new ApolloClient({ }) function getStatsClient(chainId) { - if (chainId === ARBITRUM) { + if (chainId === HARMONY) { + return harmonyStatsClient + } else if (chainId === ARBITRUM) { return arbitrumStatsClient } else if (chainId === AVALANCHE) { return avalancheStatsClient @@ -39,6 +47,12 @@ function getStatsClient(chainId) { throw new Error(`Invalid chainId ${chainId}`) } +const harmonyPricesClient = new ApolloClient({ + link: new HttpLink({ uri: getSubgraphUrl("gmx-h-prices"), fetch }), + cache: new InMemoryCache(), + defaultOptions: apolloOptions +}) + const arbitrumPricesClient = new ApolloClient({ link: new HttpLink({ uri: getSubgraphUrl("gmx-arbitrum-prices"), fetch }), cache: new InMemoryCache(), @@ -52,7 +66,9 @@ const avalanchePricesClient = new ApolloClient({ }) function getPricesClient(chainId) { - if (chainId === ARBITRUM) { + if (chainId === HARMONY) { + return harmonyPricesClient + } else if (chainId === ARBITRUM) { return arbitrumPricesClient } else if (chainId === AVALANCHE) { return avalanchePricesClient diff --git a/src/prices.js b/src/prices.js index 544c10a..30daad2 100644 --- a/src/prices.js +++ b/src/prices.js @@ -2,7 +2,7 @@ import { gql } from '@apollo/client' import { getLogger } from './helpers' import TtlCache from './ttl-cache' -import { addresses, ARBITRUM, AVALANCHE } from './addresses' +import { addresses, ARBITRUM, AVALANCHE, HARMONY } from './addresses' import { toReadable, sleep } from './utils' import { getPricesClient } from './graph' import { isEqual } from 'lodash' @@ -33,6 +33,7 @@ const VALID_PERIODS = new Set(Object.keys(PERIOD_TO_SECONDS)) */ const cachedPrices = { [ARBITRUM]: {}, + [HARMONY]: {}, [AVALANCHE]: {} } const candleByPriceId = {} @@ -188,6 +189,9 @@ function getPricesFromTo(from, to, preferableChainId = ARBITRUM, symbol, period) } function getPrices(preferableChainId = ARBITRUM, symbol, period) { + + console.log(addresses[preferableChainId]); + const tokenAddress = addresses[preferableChainId][symbol]?.toLowerCase() if (!tokenAddress) { return [] @@ -229,16 +233,16 @@ async function loadNewPrices(chainId, period) { try { const query = getQuery() const start = Date.now() - logger.info("requesting prices for period %s", period) + // logger.info("requesting prices for period %s", period) const { data } = await graphClient.query({ query: gql(query) }) - logger.info("request done in %sms loaded %s prices", Date.now() - start, data.priceCandles.length) + // logger.info("request done in %sms loaded %s prices", Date.now() - start, data.priceCandles.length) latestUpdateTimestamp = Math.floor(Date.now() / 1000) const prices = data.priceCandles if (prices.length === 0) { logger.info("No prices returned") } else { - logger.info("prices: %s", prices.length) + // logger.info("prices: %s", prices.length) putPricesIntoCache(prices, chainId, true) } } catch (ex) { @@ -331,13 +335,17 @@ async function loadOldPrices(chainId, period) { } } -if (IS_PRODUCTION || process.env.ENABLE_PRICES) { +if (true || IS_PRODUCTION || process.env.ENABLE_PRICES) { for (const period of Object.keys(PERIOD_TO_SECONDS)) { - loadNewPrices(ARBITRUM, period) - loadNewPrices(AVALANCHE, period) + // loadNewPrices(ARBITRUM, period) + // loadNewPrices(AVALANCHE, period) + + // loadOldPrices(ARBITRUM, period) + // loadOldPrices(AVALANCHE, period) + + loadNewPrices(HARMONY, period) - loadOldPrices(ARBITRUM, period) - loadOldPrices(AVALANCHE, period) + loadOldPrices(HARMONY, period) } } diff --git a/src/routes.js b/src/routes.js index 04010b2..803fea8 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,7 +4,7 @@ import { StaticRouter } from 'react-router-dom'; import { renderToString } from 'react-dom/server'; import { createHttpError } from './utils'; -import { ARBITRUM, AVALANCHE } from './addresses' +import { ARBITRUM, AVALANCHE, HARMONY } from './addresses' import { getPricesLimit, getLastUpdatedTimestamp, VALID_PERIODS } from './prices' import { get24HourVolume } from './stats' @@ -71,14 +71,14 @@ export default function routes(app) { return } - const validSymbols = new Set(['BUSD', 'WONE', 'LINK']) + const validSymbols = new Set(['USDT', 'USDC', 'WONE', 'ONE', 'ETH', 'WBTC', 'BTC']) const symbol = req.params.symbol if (!validSymbols.has(symbol)) { next(createHttpError(400, `Invalid symbol ${symbol}`)) return } const preferableChainId = Number(req.query.preferableChainId) - const validSources = new Set([ARBITRUM, AVALANCHE]) + const validSources = new Set([ARBITRUM, AVALANCHE, HARMONY]) if (!validSources.has(preferableChainId)) { next(createHttpError(400, `Invalid preferableChainId ${preferableChainId}. Valid options are ${ARBITRUM}, ${AVALANCHE}`)) return diff --git a/src/stats.js b/src/stats.js index a460c00..a33797d 100644 --- a/src/stats.js +++ b/src/stats.js @@ -4,7 +4,7 @@ import { getLogger } from './helpers' import TtlCache from './ttl-cache' import { getStatsClient } from './graph' -import { ARBITRUM, AVALANCHE } from './addresses' +import { HARMONY } from './addresses' const CACHE_TTL = 300 const ttlCache = new TtlCache(CACHE_TTL, 10) @@ -28,7 +28,7 @@ async function get24HourVolumeForChain(chainId) { const client = getStatsClient(chainId); const query = `{ volumeStats( - orderBy: ${chainId === ARBITRUM ? "id" : "timestamp"}, + orderBy: ${chainId === HARMONY ? "id" : "timestamp"}, orderDirection: desc, first: 24 where: { period: hourly } @@ -41,7 +41,6 @@ async function get24HourVolumeForChain(chainId) { } }`; - const { data } = await client.query({ query: gql(query) }) const volume = data.volumeStats.reduce((acc, item) => { @@ -58,15 +57,13 @@ export async function get24HourVolume(useCache = true) { } logger.info('Requesting 24h volume from subgraphs') - const [arbitrumVolume, avalancheVolume] = await Promise.all([ - get24HourVolumeForChain(ARBITRUM), - get24HourVolumeForChain(AVALANCHE) + const [harmonyVolume] = await Promise.all([ + get24HourVolumeForChain(HARMONY), ]); - const totalVolume = arbitrumVolume.add(avalancheVolume); + const totalVolume = harmonyVolume; const ret = { - [ARBITRUM]: arbitrumVolume.toString(), - [AVALANCHE]: avalancheVolume.toString(), + [HARMONY]: harmonyVolume.toString(), total: totalVolume.toString() } ttlCache.set('24HourVolume', ret)