1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Restore global stats (#1123)

* start with a good refactor

* split up into multiple files
* split up data fetching and manipulation into multiple effects
* adapt for new subgraph

* bookmarks error fixes

* make numbers show up when a subgraph is down

* use testnets, make numbers show up

* style fixes

* making sense of existing numbers

* more stats

* use original total OCEAN

* output datatokenCount

* state data structure change for all sum values

* copy

* output networks at all times

* switch to mainnets numbers
This commit is contained in:
Matthias Kretschmann 2022-02-18 15:53:31 +00:00 committed by GitHub
parent 6c7a474136
commit c1ab042c5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 305 additions and 222 deletions

View File

@ -1,199 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { gql, OperationContext } from 'urql'
import Conversion from '@shared/Price/Conversion'
import PriceUnit from '@shared/Price/PriceUnit'
import Tooltip from '@shared/atoms/Tooltip'
import NetworkName from '@shared/NetworkName'
import { fetchData, getSubgraphUri } from '@utils/subgraph'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import useNetworkMetadata, {
filterNetworksByType
} from '@hooks/useNetworkMetadata'
import { LoggerInstance } from '@oceanprotocol/lib'
import styles from './MarketStats.module.css'
import { FooterStatsValues_globalStatistics_totalLiquidity as LiquidityToken } from 'src/@types/subgraph/FooterStatsValues'
const getGlobalStatsValues = gql`
query FooterStatsValues {
globalStatistics {
poolCount
nftCount
datatokenCount
orderCount
totalLiquidity {
token {
id
name
symbol
}
value
}
}
}
`
interface Value {
[chainId: number]: string
}
function MarketNetworkStats({
totalValueLocked,
poolCount,
totalOceanLiquidity
}: {
totalValueLocked: string
poolCount: string
totalOceanLiquidity: string
}): ReactElement {
return (
<>
<Conversion price={totalValueLocked} hideApproximateSymbol />{' '}
<abbr title="Total Value Locked">TVL</abbr> across{' '}
<strong>{poolCount}</strong> asset pools that contain{' '}
<PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
plus datatokens for each pool.
</>
)
}
function MarketNetworkStatsTooltip({
totalValueLocked,
poolCount,
totalOceanLiquidity,
mainChainIds
}: {
totalValueLocked: Value
poolCount: Value
totalOceanLiquidity: Value
mainChainIds: number[]
}): ReactElement {
return (
<>
<ul className={styles.statsList}>
{totalValueLocked &&
totalOceanLiquidity &&
poolCount &&
mainChainIds?.map((chainId, key) => (
<li className={styles.tooltipStats} key={key}>
<NetworkName networkId={chainId} className={styles.network} />
<br />
<Conversion
price={totalValueLocked[chainId] || '0'}
hideApproximateSymbol
/>{' '}
<abbr title="Total Value Locked">TVL</abbr>
{' | '}
<strong>{poolCount[chainId] || '0'}</strong> pools
{' | '}
<PriceUnit price={totalOceanLiquidity[chainId] || '0'} small />
</li>
))}
</ul>
<p className={styles.note}>
Counted on-chain from our pool factory. Does not filter out assets in{' '}
<a href="https://github.com/oceanprotocol/list-purgatory">
list-purgatory
</a>
</p>
</>
)
}
export default function MarketStats(): ReactElement {
const [totalValueLocked, setTotalValueLocked] = useState<Value>()
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<Value>()
const [poolCount, setPoolCount] = useState<Value>()
const [totalValueLockedSum, setTotalValueLockedSum] = useState<string>()
const [totalOceanLiquiditySum, setTotalOceanLiquiditySum] = useState<string>()
const [poolCountSum, setPoolCountSum] = useState<string>()
const [mainChainIds, setMainChainIds] = useState<number[]>()
const { appConfig } = useSiteMetadata()
const { networksList } = useNetworkMetadata()
async function getMarketStats() {
const mainChainIdsList = await filterNetworksByType(
'mainnet',
appConfig.chainIdsSupported,
networksList
)
setMainChainIds(mainChainIdsList)
let newTotalValueLockedSum = 0
const newTotalOceanLiquiditySum = 0
let newPoolCountSum = 0
for (const chainId of mainChainIdsList) {
const context: OperationContext = {
url: `${getSubgraphUri(
chainId
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
requestPolicy: 'network-only'
}
try {
const response = await fetchData(getGlobalStatsValues, null, context)
if (!response) continue
const {
poolCount,
nftCount,
datatokenCount,
orderCount,
totalLiquidity
} = response?.data?.globalStats[0]
await setTotalValueLocked((prevState) => ({
...prevState,
[chainId]: totalLiquidity.value
}))
// TODO: how to get total OCEAN liquidity? Does this work?
await setTotalOceanLiquidity((prevState) => ({
...prevState,
[chainId]: totalLiquidity.filter(
(token: LiquidityToken) => token.token.symbol === 'OCEAN'
)[0]
}))
await setPoolCount((prevState) => ({
...prevState,
[chainId]: poolCount
}))
newTotalValueLockedSum += parseInt(totalLiquidity.value)
// newTotalOceanLiquiditySum += parseInt(totalOceanLiquidity.value)
newPoolCountSum += parseInt(poolCount)
} catch (error) {
LoggerInstance.error('Error fetchData: ', error.message)
}
}
setTotalValueLockedSum(`${newTotalValueLockedSum}`)
setTotalOceanLiquiditySum(`${newTotalOceanLiquiditySum}`)
setPoolCountSum(`${newPoolCountSum}`)
}
useEffect(() => {
getMarketStats()
}, [])
return (
<div className={styles.stats}>
<>
<MarketNetworkStats
totalValueLocked={totalValueLockedSum || '0'}
totalOceanLiquidity={totalOceanLiquiditySum || '0'}
poolCount={poolCountSum || '0'}
/>{' '}
<Tooltip
className={styles.info}
content={
<MarketNetworkStatsTooltip
totalValueLocked={totalValueLocked}
poolCount={poolCount}
totalOceanLiquidity={totalOceanLiquidity}
mainChainIds={mainChainIds}
/>
}
/>
</>
</div>
)
}

View File

@ -1,28 +1,12 @@
.stats {
margin-bottom: calc(var(--spacer) * 2);
}
/* specificity sledgehammer override without !important */
.stats,
.stats *,
.statsList * {
font-size: var(--font-size-small);
color: var(--color-secondary);
margin-left: 0;
}
.tooltipStats {
margin-bottom: calc(var(--spacer) / 3);
padding-bottom: calc(var(--spacer) / 3);
border-bottom: 1px solid var(--border-color);
}
.network {
font-weight: var(--font-weight-bold);
}
.info {
width: 0.85rem;
.statsList {
composes: statsList from './index.module.css';
padding-bottom: 0;
}
.statsList,
@ -30,13 +14,13 @@
padding: calc(var(--spacer) / 4);
}
.statsList {
padding-bottom: 0;
}
.note {
margin-bottom: 0;
padding-top: 0;
font-size: var(--font-size-mini);
color: var(--color-secondary);
}
.network {
font-weight: var(--font-weight-bold);
}

View File

@ -0,0 +1,51 @@
import React, { ReactElement } from 'react'
import Conversion from '@shared/Price/Conversion'
import PriceUnit from '@shared/Price/PriceUnit'
import NetworkName from '@shared/NetworkName'
import styles from './Tooltip.module.css'
import { StatsValue } from './_types'
export default function MarketStatsTooltip({
totalValueLockedInOcean,
poolCount,
totalOceanLiquidity,
mainChainIds
}: {
totalValueLockedInOcean: StatsValue
poolCount: StatsValue
totalOceanLiquidity: StatsValue
mainChainIds: number[]
}): ReactElement {
return (
<>
<ul className={styles.statsList}>
{mainChainIds?.map((chainId, key) => (
<li className={styles.tooltipStats} key={key}>
<NetworkName networkId={chainId} className={styles.network} />
<br />
<Conversion
price={totalValueLockedInOcean?.[chainId] || '0'}
hideApproximateSymbol
/>{' '}
<abbr title="Total Value Locked">TVL</abbr>
{' | '}
<strong>{poolCount?.[chainId] || '0'}</strong> pools
{' | '}
<PriceUnit
price={totalOceanLiquidity?.[chainId] || '0'}
symbol="OCEAN"
small
/>
</li>
))}
</ul>
<p className={styles.note}>
Counted on-chain from our NFT and pool factories. Does not filter out
assets in{' '}
<a href="https://github.com/oceanprotocol/list-purgatory">
list-purgatory
</a>
</p>
</>
)
}

View File

@ -0,0 +1,28 @@
import React, { ReactElement } from 'react'
import Conversion from '@shared/Price/Conversion'
import PriceUnit from '@shared/Price/PriceUnit'
import { StatsTotal } from './_types'
export default function MarketStatsTotal({
total
}: {
total: StatsTotal
}): ReactElement {
return (
<>
<p>
<strong>{total.orders}</strong> orders across{' '}
<strong>{total.nfts}</strong> assets with{' '}
<strong>{total.datatokens}</strong> different datatokens.
</p>
<Conversion
price={`${total.totalValueLockedInOcean}`}
hideApproximateSymbol
/>{' '}
<abbr title="Total Value Locked">TVL</abbr> across{' '}
<strong>{total.pools}</strong> asset pools that contain{' '}
<PriceUnit price={`${total.totalOceanLiquidity}`} symbol="OCEAN" small />,
plus datatokens for each pool.
</>
)
}

View File

@ -0,0 +1,20 @@
import { gql } from 'urql'
export const queryGlobalStatistics = gql`
query FooterStatsValues {
globalStatistics {
poolCount
nftCount
datatokenCount
orderCount
totalLiquidity {
value
token {
address
name
symbol
}
}
}
}
`

View File

@ -0,0 +1,12 @@
export interface StatsValue {
[chainId: number]: string
}
export interface StatsTotal {
totalValueLockedInOcean: number
totalOceanLiquidity: number
pools: number
nfts: number
datatokens: number
orders: number
}

View File

@ -0,0 +1,21 @@
.stats {
margin-bottom: calc(var(--spacer) * 2);
}
/* specificity sledgehammer override without !important */
.stats,
.stats *,
.statsList * {
font-size: var(--font-size-small);
color: var(--color-secondary);
margin-left: 0;
}
.info {
width: 0.85rem;
}
.stats p {
display: block;
margin-bottom: 0;
}

View File

@ -0,0 +1,166 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import { OperationContext } from 'urql'
import Tooltip from '@shared/atoms/Tooltip'
import { fetchData, getSubgraphUri } from '@utils/subgraph'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import useNetworkMetadata, {
filterNetworksByType
} from '@hooks/useNetworkMetadata'
import { LoggerInstance } from '@oceanprotocol/lib'
import styles from './index.module.css'
import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from 'src/@types/subgraph/FooterStatsValues'
import MarketStatsTooltip from './Tooltip'
import MarketStatsTotal from './Total'
import { queryGlobalStatistics } from './_queries'
import { usePrices } from '@context/Prices'
import { useUserPreferences } from '@context/UserPreferences'
import Decimal from 'decimal.js'
import { StatsTotal, StatsValue } from './_types'
const initialTotal: StatsTotal = {
totalValueLockedInOcean: 0,
totalOceanLiquidity: 0,
pools: 0,
nfts: 0,
datatokens: 0,
orders: 0
}
export default function MarketStats(): ReactElement {
const { appConfig } = useSiteMetadata()
const { networksList } = useNetworkMetadata()
const { currency } = useUserPreferences()
const { prices } = usePrices()
const [mainChainIds, setMainChainIds] = useState<number[]>()
const [data, setData] =
useState<{ [chainId: number]: FooterStatsValuesGlobalStatistics }>()
const [totalValueLockedInOcean, setTotalValueLockedInOcean] =
useState<StatsValue>()
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<StatsValue>()
const [poolCount, setPoolCount] = useState<StatsValue>()
const [total, setTotal] = useState(initialTotal)
//
// Set the main chain ids we want to display stats for
//
useEffect(() => {
if (!networksList) return
const mainChainIdsList = filterNetworksByType(
'mainnet',
appConfig.chainIdsSupported,
networksList
)
setMainChainIds(mainChainIdsList)
}, [appConfig.chainIdsSupported, networksList])
//
// Helper: fetch data from subgraph
//
const getMarketStats = useCallback(async () => {
if (!mainChainIds?.length) return
for (const chainId of mainChainIds) {
const context: OperationContext = {
url: `${getSubgraphUri(
chainId
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
requestPolicy: 'network-only'
}
try {
const response = await fetchData(queryGlobalStatistics, null, context)
if (!response?.data?.globalStatistics) return
setData((prevState) => ({
...prevState,
[chainId]: response.data.globalStatistics[0]
}))
} catch (error) {
LoggerInstance.error('Error fetching global stats: ', error.message)
}
}
}, [mainChainIds])
//
// 1. Fetch Data
//
useEffect(() => {
getMarketStats()
}, [getMarketStats])
//
// 2. Data Manipulation
//
useEffect(() => {
if (!data || !mainChainIds?.length) return
const newTotal: StatsTotal = {
...initialTotal // always start calculating beginning from initial 0 values
}
for (const chainId of mainChainIds) {
const baseTokenValue = data[chainId]?.totalLiquidity[0]?.value
try {
const totalValueLockedInOcean = baseTokenValue
? new Decimal(baseTokenValue).mul(2)
: new Decimal(0)
setTotalValueLockedInOcean((prevState) => ({
...prevState,
[chainId]: `${totalValueLockedInOcean}`
}))
const totalOceanLiquidity = Number(baseTokenValue) || 0
setTotalOceanLiquidity((prevState) => ({
...prevState,
[chainId]: `${totalOceanLiquidity}`
}))
const poolCount = data[chainId]?.poolCount || 0
setPoolCount((prevState) => ({
...prevState,
[chainId]: `${poolCount}`
}))
const nftCount = data[chainId]?.nftCount || 0
const datatokenCount = data[chainId]?.datatokenCount || 0
const orderCount = data[chainId]?.orderCount || 0
newTotal.totalValueLockedInOcean += totalValueLockedInOcean.toNumber()
newTotal.totalOceanLiquidity += totalOceanLiquidity
newTotal.pools += poolCount
newTotal.nfts += nftCount
newTotal.datatokens += datatokenCount
newTotal.orders += orderCount
} catch (error) {
LoggerInstance.error('Error data manipulation: ', error.message)
}
}
setTotal(newTotal)
}, [data, mainChainIds, prices, currency])
return (
<div className={styles.stats}>
<>
<MarketStatsTotal total={total} />{' '}
<Tooltip
className={styles.info}
content={
<MarketStatsTooltip
totalValueLockedInOcean={totalValueLockedInOcean}
poolCount={poolCount}
totalOceanLiquidity={totalOceanLiquidity}
mainChainIds={mainChainIds}
/>
}
/>
</>
</div>
)
}