mirror of
https://github.com/oceanprotocol/market.git
synced 2024-06-30 22:01:44 +02:00
Merge pull request #1020 from oceanprotocol/v4-pool-graph
Pool chart updates, combine all pool data subgraph queries
This commit is contained in:
commit
2fb7ed3516
6432
package-lock.json
generated
6432
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
|
@ -27,15 +27,15 @@
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"@urql/introspection": "^0.3.1",
|
"@urql/introspection": "^0.3.1",
|
||||||
"@walletconnect/web3-provider": "^1.7.1",
|
"@walletconnect/web3-provider": "^1.7.1",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.25.0",
|
||||||
"bignumber.js": "^9.0.2",
|
"bignumber.js": "^9.0.2",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^3.7.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"d3": "^7.3.0",
|
"d3": "^7.3.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"decimal.js": "^10.3.1",
|
"decimal.js": "^10.3.1",
|
||||||
"dom-confetti": "^0.2.2",
|
"dom-confetti": "^0.2.2",
|
||||||
"dotenv": "^14.1.0",
|
"dotenv": "^15.0.0",
|
||||||
"ethereum-address": "0.0.4",
|
"ethereum-address": "0.0.4",
|
||||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||||
"filesize": "^8.0.6",
|
"filesize": "^8.0.6",
|
||||||
|
@ -46,11 +46,11 @@
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.omit": "^4.5.0",
|
"lodash.omit": "^4.5.0",
|
||||||
"next": "^12.0.8",
|
"next": "^12.0.9",
|
||||||
"query-string": "^7.1.0",
|
"query-string": "^7.1.0",
|
||||||
"querystring": "^0.2.1",
|
"querystring": "^0.2.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-chartjs-2": "^2.11.2",
|
"react-chartjs-2": "^4.0.1",
|
||||||
"react-clipboard.js": "^2.0.16",
|
"react-clipboard.js": "^2.0.16",
|
||||||
"react-data-table-component": "^6.11.7",
|
"react-data-table-component": "^6.11.7",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
@ -65,22 +65,22 @@
|
||||||
"remark-html": "^13.0.1",
|
"remark-html": "^13.0.1",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"slugify": "^1.6.5",
|
"slugify": "^1.6.5",
|
||||||
"swr": "^1.1.2",
|
"swr": "^1.2.0",
|
||||||
"urql": "^2.0.6",
|
"urql": "^2.1.1",
|
||||||
"use-dark-mode": "^2.3.1",
|
"use-dark-mode": "^2.3.1",
|
||||||
"web3": "^1.6.1",
|
"web3": "^1.6.1",
|
||||||
"web3modal": "^1.9.5",
|
"web3modal": "^1.9.5",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@svgr/webpack": "^6.2.0",
|
"@svgr/webpack": "^6.2.1",
|
||||||
"@types/chart.js": "^2.9.35",
|
"@types/chart.js": "^2.9.35",
|
||||||
"@types/d3": "^7.1.0",
|
"@types/d3": "^7.1.0",
|
||||||
"@types/js-cookie": "^3.0.1",
|
"@types/js-cookie": "^3.0.1",
|
||||||
"@types/loadable__component": "^5.13.1",
|
"@types/loadable__component": "^5.13.1",
|
||||||
"@types/lodash.debounce": "^4.0.3",
|
"@types/lodash.debounce": "^4.0.3",
|
||||||
"@types/lodash.omit": "^4.5.6",
|
"@types/lodash.omit": "^4.5.6",
|
||||||
"@types/node": "^17.0.8",
|
"@types/node": "^17.0.13",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-modal": "^3.13.1",
|
"@types/react-modal": "^3.13.1",
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
} from '../@types/subgraph/PoolShares'
|
} from '../@types/subgraph/PoolShares'
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||||
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
||||||
import { PoolLiquidity } from 'src/@types/subgraph/PoolLiquidity'
|
import { PoolData } from 'src/@types/subgraph/PoolData'
|
||||||
|
|
||||||
export interface UserLiquidity {
|
export interface UserLiquidity {
|
||||||
price: string
|
price: string
|
||||||
|
@ -285,9 +285,14 @@ const TopSalesQuery = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const poolLiquidityQuery = gql`
|
const poolDataQuery = gql`
|
||||||
query PoolLiquidity($pool: ID!, $owner: String!) {
|
query PoolData(
|
||||||
pool(id: $pool) {
|
$pool: ID!
|
||||||
|
$poolAsString: String!
|
||||||
|
$owner: String!
|
||||||
|
$user: String
|
||||||
|
) {
|
||||||
|
poolData: pool(id: $pool) {
|
||||||
id
|
id
|
||||||
totalShares
|
totalShares
|
||||||
poolFee
|
poolFee
|
||||||
|
@ -310,16 +315,18 @@ const poolLiquidityQuery = gql`
|
||||||
shares
|
shares
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
poolDataUser: pool(id: $pool) {
|
||||||
`
|
|
||||||
|
|
||||||
const userPoolShareQuery = gql`
|
|
||||||
query PoolShare($pool: ID!, $user: String!) {
|
|
||||||
pool(id: $pool) {
|
|
||||||
shares(where: { user: $user }) {
|
shares(where: { user: $user }) {
|
||||||
shares
|
shares
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
poolSnapshots(first: 1000, where: { pool: $poolAsString }, orderBy: date) {
|
||||||
|
date
|
||||||
|
spotPrice
|
||||||
|
baseTokenLiquidity
|
||||||
|
datatokenLiquidity
|
||||||
|
swapVolume
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -822,33 +829,22 @@ export async function getTopAssetsPublishers(
|
||||||
export async function getPoolData(
|
export async function getPoolData(
|
||||||
chainId: number,
|
chainId: number,
|
||||||
pool: string,
|
pool: string,
|
||||||
owner: string
|
owner: string,
|
||||||
|
user: string
|
||||||
) {
|
) {
|
||||||
const queryVariables = {
|
const queryVariables = {
|
||||||
|
// Using `pool` & `poolAsString` is a workaround to make the mega query work.
|
||||||
|
// See https://github.com/oceanprotocol/ocean-subgraph/issues/301
|
||||||
pool: pool.toLowerCase(),
|
pool: pool.toLowerCase(),
|
||||||
owner: owner.toLowerCase()
|
poolAsString: pool.toLowerCase(),
|
||||||
|
owner: owner.toLowerCase(),
|
||||||
|
user: user.toLowerCase()
|
||||||
}
|
}
|
||||||
const response: OperationResult<PoolLiquidity> = await fetchData(
|
|
||||||
poolLiquidityQuery,
|
|
||||||
queryVariables,
|
|
||||||
getQueryContext(chainId)
|
|
||||||
)
|
|
||||||
return response?.data?.pool
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUserPoolShareBalance(
|
const response: OperationResult<PoolData> = await fetchData(
|
||||||
chainId: number,
|
poolDataQuery,
|
||||||
pool: string,
|
|
||||||
accountId: string
|
|
||||||
): Promise<string> {
|
|
||||||
const queryVariables = {
|
|
||||||
pool: pool.toLowerCase(),
|
|
||||||
user: accountId.toLowerCase()
|
|
||||||
}
|
|
||||||
const response: OperationResult<PoolLiquidity> = await fetchData(
|
|
||||||
userPoolShareQuery,
|
|
||||||
queryVariables,
|
queryVariables,
|
||||||
getQueryContext(chainId)
|
getQueryContext(chainId)
|
||||||
)
|
)
|
||||||
return response?.data?.pool?.shares[0]?.shares || '0'
|
return response?.data
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,12 @@ const txHistoryQueryByPool = gql`
|
||||||
symbol
|
symbol
|
||||||
address
|
address
|
||||||
}
|
}
|
||||||
|
baseTokenValue
|
||||||
datatoken {
|
datatoken {
|
||||||
symbol
|
symbol
|
||||||
address
|
address
|
||||||
}
|
}
|
||||||
|
datatokenValue
|
||||||
type
|
type
|
||||||
tx
|
tx
|
||||||
timestamp
|
timestamp
|
||||||
|
|
|
@ -1,241 +0,0 @@
|
||||||
/* eslint-disable camelcase */
|
|
||||||
import React, {
|
|
||||||
ChangeEvent,
|
|
||||||
ReactElement,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { Line, defaults } from 'react-chartjs-2'
|
|
||||||
import {
|
|
||||||
ChartData,
|
|
||||||
ChartDataSets,
|
|
||||||
ChartOptions,
|
|
||||||
ChartTooltipItem,
|
|
||||||
ChartTooltipOptions
|
|
||||||
} from 'chart.js'
|
|
||||||
import Loader from '@shared/atoms/Loader'
|
|
||||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
|
||||||
import useDarkMode from 'use-dark-mode'
|
|
||||||
import { darkModeConfig } from '../../../../../app.config'
|
|
||||||
import Button from '@shared/atoms/Button'
|
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
|
||||||
import { useAsset } from '@context/Asset'
|
|
||||||
import { gql, OperationResult } from 'urql'
|
|
||||||
import { PoolHistory } from '../../../../@types/subgraph/PoolHistory'
|
|
||||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
|
||||||
import styles from './Graph.module.css'
|
|
||||||
|
|
||||||
declare type GraphType = 'liquidity' | 'price'
|
|
||||||
|
|
||||||
// Chart.js global defaults
|
|
||||||
defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystemFont,
|
|
||||||
'Segoe UI', Helvetica, Arial, sans-serif`
|
|
||||||
defaults.global.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
|
||||||
|
|
||||||
const REFETCH_INTERVAL = 10000
|
|
||||||
|
|
||||||
const lineStyle: Partial<ChartDataSets> = {
|
|
||||||
fill: false,
|
|
||||||
lineTension: 0.1,
|
|
||||||
borderWidth: 2,
|
|
||||||
pointBorderWidth: 0,
|
|
||||||
pointRadius: 0,
|
|
||||||
pointHoverRadius: 4,
|
|
||||||
pointHoverBorderWidth: 0,
|
|
||||||
pointHitRadius: 2,
|
|
||||||
pointHoverBackgroundColor: '#ff4092'
|
|
||||||
}
|
|
||||||
|
|
||||||
const tooltipOptions: Partial<ChartTooltipOptions> = {
|
|
||||||
intersect: false,
|
|
||||||
titleFontStyle: 'normal',
|
|
||||||
titleFontSize: 10,
|
|
||||||
bodyFontSize: 12,
|
|
||||||
bodyFontStyle: 'bold',
|
|
||||||
displayColors: false,
|
|
||||||
xPadding: 10,
|
|
||||||
yPadding: 10,
|
|
||||||
cornerRadius: 3,
|
|
||||||
borderWidth: 1,
|
|
||||||
caretSize: 7
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
|
||||||
return {
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
...tooltipOptions,
|
|
||||||
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
|
||||||
titleFontColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
|
||||||
bodyFontColor: isDarkMode ? `#fff` : `#141414`,
|
|
||||||
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
|
||||||
callbacks: {
|
|
||||||
label: (tooltipItem: ChartTooltipItem) =>
|
|
||||||
`${formatPrice(`${tooltipItem.yLabel}`, locale)} OCEAN`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
intersect: false,
|
|
||||||
animationDuration: 0
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
display: false
|
|
||||||
// gridLines: {
|
|
||||||
// drawBorder: false,
|
|
||||||
// color: isDarkMode ? '#303030' : '#e2e2e2',
|
|
||||||
// zeroLineColor: isDarkMode ? '#303030' : '#e2e2e2'
|
|
||||||
// },
|
|
||||||
// ticks: { display: false }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
xAxes: [{ display: false, gridLines: { display: true } }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphTypes = ['Liquidity', 'Price']
|
|
||||||
|
|
||||||
const poolHistoryQuery = gql`
|
|
||||||
query PoolHistory($id: String!) {
|
|
||||||
poolSnapshots(first: 1000, where: { pool: $id }, orderBy: date) {
|
|
||||||
date
|
|
||||||
spotPrice
|
|
||||||
baseTokenLiquidity
|
|
||||||
datatokenLiquidity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default function Graph(): ReactElement {
|
|
||||||
const { locale } = useUserPreferences()
|
|
||||||
const { price, ddo } = useAsset()
|
|
||||||
const darkMode = useDarkMode(false, darkModeConfig)
|
|
||||||
const [options, setOptions] = useState<ChartOptions>()
|
|
||||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
|
||||||
const [error, setError] = useState<Error>()
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
|
||||||
const [dataHistory, setDataHistory] = useState<PoolHistory>()
|
|
||||||
const [graphData, setGraphData] = useState<ChartData>()
|
|
||||||
const [graphFetchInterval, setGraphFetchInterval] = useState<NodeJS.Timeout>()
|
|
||||||
|
|
||||||
const getPoolHistory = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const queryResult: OperationResult<PoolHistory> = await fetchData(
|
|
||||||
poolHistoryQuery,
|
|
||||||
{ id: price.address.toLowerCase() },
|
|
||||||
getQueryContext(ddo.chainId)
|
|
||||||
)
|
|
||||||
setDataHistory(queryResult?.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetchData: ', error.message)
|
|
||||||
setError(error)
|
|
||||||
}
|
|
||||||
}, [ddo?.chainId, price?.address])
|
|
||||||
|
|
||||||
const refetchGraph = useCallback(async () => {
|
|
||||||
if (graphFetchInterval) return
|
|
||||||
|
|
||||||
const newInterval = setInterval(() => getPoolHistory(), REFETCH_INTERVAL)
|
|
||||||
setGraphFetchInterval(newInterval)
|
|
||||||
}, [getPoolHistory, graphFetchInterval])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
LoggerInstance.log('Fired GraphOptions!')
|
|
||||||
const options = getOptions(locale, darkMode.value)
|
|
||||||
setOptions(options)
|
|
||||||
}, [locale, darkMode.value])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function init() {
|
|
||||||
if (!dataHistory) {
|
|
||||||
await getPoolHistory()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
LoggerInstance.log('Fired GraphData!')
|
|
||||||
|
|
||||||
const latestTimestamps = [
|
|
||||||
...dataHistory.poolSnapshots.map((item) => {
|
|
||||||
const date = new Date(item.date * 1000)
|
|
||||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
|
||||||
})
|
|
||||||
]
|
|
||||||
|
|
||||||
const latestLiquidityHistory = [
|
|
||||||
...dataHistory.poolSnapshots.map((item) => item.baseTokenLiquidity)
|
|
||||||
]
|
|
||||||
|
|
||||||
const latestPriceHistory = [
|
|
||||||
...dataHistory.poolSnapshots.map((item) => item.datatokenLiquidity)
|
|
||||||
]
|
|
||||||
|
|
||||||
setGraphData({
|
|
||||||
labels: latestTimestamps.slice(0),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
...lineStyle,
|
|
||||||
label: 'Liquidity (OCEAN)',
|
|
||||||
data:
|
|
||||||
graphType === 'liquidity'
|
|
||||||
? latestLiquidityHistory.slice(0)
|
|
||||||
: latestPriceHistory.slice(0),
|
|
||||||
borderColor: `#8b98a9`,
|
|
||||||
pointBackgroundColor: `#8b98a9`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
setIsLoading(false)
|
|
||||||
refetchGraph()
|
|
||||||
}
|
|
||||||
init()
|
|
||||||
|
|
||||||
return () => clearInterval(graphFetchInterval)
|
|
||||||
}, [dataHistory, graphType, graphFetchInterval, getPoolHistory, refetchGraph])
|
|
||||||
|
|
||||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
|
||||||
e.preventDefault()
|
|
||||||
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.graphWrap}>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loader />
|
|
||||||
) : error ? (
|
|
||||||
<small>{error.message}</small>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<nav className={styles.type}>
|
|
||||||
{graphTypes.map((type: GraphType) => (
|
|
||||||
<Button
|
|
||||||
key={type}
|
|
||||||
style="text"
|
|
||||||
size="small"
|
|
||||||
onClick={handleGraphTypeSwitch}
|
|
||||||
className={`${styles.button} ${
|
|
||||||
graphType === type.toLowerCase() ? styles.active : null
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{type}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
<Line height={70} data={graphData} options={options} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,24 +1,3 @@
|
||||||
.graphWrap {
|
|
||||||
min-height: 97px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 40rem) {
|
|
||||||
.graphWrap {
|
|
||||||
margin-left: -2rem;
|
|
||||||
margin-right: -2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.graphWrap canvas {
|
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type {
|
.type {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -10px;
|
bottom: -10px;
|
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import Button from '@shared/atoms/Button'
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
Dispatch,
|
||||||
|
ReactElement,
|
||||||
|
SetStateAction
|
||||||
|
} from 'react'
|
||||||
|
import styles from './Nav.module.css'
|
||||||
|
import { graphTypes, GraphType } from './_constants'
|
||||||
|
|
||||||
|
export default function Nav({
|
||||||
|
graphType,
|
||||||
|
setGraphType
|
||||||
|
}: {
|
||||||
|
graphType: GraphType
|
||||||
|
setGraphType: Dispatch<SetStateAction<GraphType>>
|
||||||
|
}): ReactElement {
|
||||||
|
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault()
|
||||||
|
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={styles.type}>
|
||||||
|
{graphTypes.map((type: GraphType) => (
|
||||||
|
<Button
|
||||||
|
key={type}
|
||||||
|
style="text"
|
||||||
|
size="small"
|
||||||
|
onClick={handleGraphTypeSwitch}
|
||||||
|
className={`${styles.button} ${
|
||||||
|
graphType === type.toLowerCase() ? styles.active : null
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
67
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
67
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
BarElement,
|
||||||
|
LineElement,
|
||||||
|
LineController,
|
||||||
|
BarController,
|
||||||
|
ChartDataset,
|
||||||
|
TooltipOptions,
|
||||||
|
defaults
|
||||||
|
} from 'chart.js'
|
||||||
|
|
||||||
|
export declare type GraphType = 'liquidity' | 'price' | 'volume'
|
||||||
|
|
||||||
|
export const graphTypes = ['Liquidity', 'Price', 'Volume']
|
||||||
|
|
||||||
|
// Chart.js global defaults
|
||||||
|
ChartJS.register(
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
PointElement,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
Tooltip,
|
||||||
|
LineController,
|
||||||
|
BarController
|
||||||
|
)
|
||||||
|
|
||||||
|
defaults.font.family = `'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`
|
||||||
|
defaults.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||||
|
|
||||||
|
export const lineStyle: Partial<ChartDataset> = {
|
||||||
|
fill: false,
|
||||||
|
borderWidth: 2,
|
||||||
|
pointBorderWidth: 1,
|
||||||
|
pointRadius: 2,
|
||||||
|
pointHoverRadius: 4,
|
||||||
|
pointHoverBorderWidth: 0,
|
||||||
|
pointHitRadius: 2,
|
||||||
|
pointHoverBackgroundColor: '#ff4092'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tooltipOptions: Partial<TooltipOptions> = {
|
||||||
|
intersect: false,
|
||||||
|
displayColors: false,
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 3,
|
||||||
|
borderWidth: 1,
|
||||||
|
caretSize: 7,
|
||||||
|
bodyFont: {
|
||||||
|
size: 13,
|
||||||
|
weight: 'bold',
|
||||||
|
lineHeight: 1,
|
||||||
|
style: 'normal',
|
||||||
|
family: defaults.font.family
|
||||||
|
},
|
||||||
|
titleFont: {
|
||||||
|
size: 10,
|
||||||
|
weight: 'normal',
|
||||||
|
lineHeight: 1,
|
||||||
|
style: 'normal',
|
||||||
|
family: defaults.font.family
|
||||||
|
}
|
||||||
|
}
|
34
src/components/Asset/AssetActions/Pool/Graph/_utils.ts
Normal file
34
src/components/Asset/AssetActions/Pool/Graph/_utils.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||||
|
import { ChartOptions, TooltipItem } from 'chart.js'
|
||||||
|
import { tooltipOptions } from './_constants'
|
||||||
|
|
||||||
|
export function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||||
|
return {
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
...tooltipOptions,
|
||||||
|
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
||||||
|
titleColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
||||||
|
bodyColor: isDarkMode ? `#fff` : `#141414`,
|
||||||
|
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem: TooltipItem<any>) =>
|
||||||
|
`${formatPrice(`${tooltipItem.formattedValue}`, locale)} OCEAN`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hover: { intersect: false },
|
||||||
|
scales: {
|
||||||
|
y: { display: false, beginAtZero: true },
|
||||||
|
x: { display: false, offset: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
.graphWrap {
|
||||||
|
min-height: 120px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
.graphWrap {
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-right: -2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.graphWrap canvas {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
104
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
104
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import { ChartData, ChartOptions } from 'chart.js'
|
||||||
|
import { Bar, Line } from 'react-chartjs-2'
|
||||||
|
import Loader from '@shared/atoms/Loader'
|
||||||
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import useDarkMode from 'use-dark-mode'
|
||||||
|
import { darkModeConfig } from '../../../../../../app.config'
|
||||||
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
|
import { lineStyle, GraphType } from './_constants'
|
||||||
|
import Nav from './Nav'
|
||||||
|
import { getOptions } from './_utils'
|
||||||
|
import { PoolData_poolSnapshots as PoolDataPoolSnapshots } from 'src/@types/subgraph/PoolData'
|
||||||
|
|
||||||
|
export default function Graph({
|
||||||
|
poolSnapshots
|
||||||
|
}: {
|
||||||
|
poolSnapshots: PoolDataPoolSnapshots[]
|
||||||
|
}): ReactElement {
|
||||||
|
const { locale } = useUserPreferences()
|
||||||
|
const darkMode = useDarkMode(false, darkModeConfig)
|
||||||
|
|
||||||
|
const [options, setOptions] = useState<ChartOptions<any>>()
|
||||||
|
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||||
|
const [graphData, setGraphData] = useState<ChartData<any>>()
|
||||||
|
|
||||||
|
//
|
||||||
|
// 0 Get Graph options
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
LoggerInstance.log('[pool graph] Fired getOptions().')
|
||||||
|
const options = getOptions(locale, darkMode.value)
|
||||||
|
setOptions(options)
|
||||||
|
}, [locale, darkMode.value, graphType])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 1 Data manipulation
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!poolSnapshots) return
|
||||||
|
|
||||||
|
const timestamps = poolSnapshots.map((item) => {
|
||||||
|
const date = new Date(item.date * 1000)
|
||||||
|
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||||
|
})
|
||||||
|
|
||||||
|
let baseTokenLiquidityCumulative = '0'
|
||||||
|
const liquidityHistory = poolSnapshots.map((item) => {
|
||||||
|
baseTokenLiquidityCumulative = new Decimal(baseTokenLiquidityCumulative)
|
||||||
|
.add(item.baseTokenLiquidity)
|
||||||
|
.toString()
|
||||||
|
return baseTokenLiquidityCumulative
|
||||||
|
})
|
||||||
|
|
||||||
|
const priceHistory = poolSnapshots.map((item) => item.spotPrice)
|
||||||
|
|
||||||
|
let volumeCumulative = '0'
|
||||||
|
const volumeHistory = poolSnapshots.map((item) => {
|
||||||
|
volumeCumulative = new Decimal(volumeCumulative)
|
||||||
|
.add(item.swapVolume)
|
||||||
|
.toString()
|
||||||
|
return volumeCumulative
|
||||||
|
})
|
||||||
|
|
||||||
|
let data
|
||||||
|
switch (graphType) {
|
||||||
|
case 'price':
|
||||||
|
data = priceHistory.slice(0)
|
||||||
|
break
|
||||||
|
case 'volume':
|
||||||
|
data = volumeHistory.slice(0)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
data = liquidityHistory.slice(0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGraphData = {
|
||||||
|
labels: timestamps.slice(0),
|
||||||
|
datasets: [{ ...lineStyle, data, borderColor: `#8b98a9` }]
|
||||||
|
}
|
||||||
|
setGraphData(newGraphData)
|
||||||
|
LoggerInstance.log('[pool graph] New graph data created:', newGraphData)
|
||||||
|
}, [poolSnapshots, graphType])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.graphWrap}>
|
||||||
|
{!graphData ? (
|
||||||
|
<Loader />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Nav graphType={graphType} setGraphType={setGraphType} />
|
||||||
|
|
||||||
|
{graphType === 'volume' ? (
|
||||||
|
<Bar width={416} height={120} data={graphData} options={options} />
|
||||||
|
) : (
|
||||||
|
<Line width={416} height={120} data={graphData} options={options} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -13,13 +13,16 @@ import TokenList from './TokenList'
|
||||||
import AssetActionHistoryTable from '../AssetActionHistoryTable'
|
import AssetActionHistoryTable from '../AssetActionHistoryTable'
|
||||||
import Graph from './Graph'
|
import Graph from './Graph'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { PoolLiquidity_pool as PoolLiquidityData } from '../../../../@types/subgraph/PoolLiquidity'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import PoolTransactions from '@shared/PoolTransactions'
|
import PoolTransactions from '@shared/PoolTransactions'
|
||||||
import { isValidNumber } from '@utils/numbers'
|
import { isValidNumber } from '@utils/numbers'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
import content from '../../../../../content/price.json'
|
import content from '../../../../../content/price.json'
|
||||||
import { getPoolData, getUserPoolShareBalance } from '@utils/subgraph'
|
import { getPoolData } from '@utils/subgraph'
|
||||||
|
import {
|
||||||
|
PoolData_poolSnapshots as PoolDataPoolSnapshots,
|
||||||
|
PoolData_poolData as PoolDataPoolData
|
||||||
|
} from 'src/@types/subgraph/PoolData'
|
||||||
|
|
||||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||||
|
|
||||||
|
@ -60,7 +63,7 @@ export default function Pool(): ReactElement {
|
||||||
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
||||||
useAsset()
|
useAsset()
|
||||||
|
|
||||||
const [poolData, setPoolData] = useState<PoolLiquidityData>()
|
const [poolData, setPoolData] = useState<PoolDataPoolData>()
|
||||||
const [poolInfo, setPoolInfo] = useState<PoolInfo>(
|
const [poolInfo, setPoolInfo] = useState<PoolInfo>(
|
||||||
initialPoolInfo as PoolInfo
|
initialPoolInfo as PoolInfo
|
||||||
)
|
)
|
||||||
|
@ -70,6 +73,7 @@ export default function Pool(): ReactElement {
|
||||||
const [poolInfoUser, setPoolInfoUser] = useState<PoolInfoUser>(
|
const [poolInfoUser, setPoolInfoUser] = useState<PoolInfoUser>(
|
||||||
initialPoolInfoUser as PoolInfoUser
|
initialPoolInfoUser as PoolInfoUser
|
||||||
)
|
)
|
||||||
|
const [poolSnapshots, setPoolSnapshots] = useState<PoolDataPoolSnapshots[]>()
|
||||||
|
|
||||||
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
||||||
const [showAdd, setShowAdd] = useState(false)
|
const [showAdd, setShowAdd] = useState(false)
|
||||||
|
@ -77,34 +81,27 @@ export default function Pool(): ReactElement {
|
||||||
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
||||||
const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
||||||
|
|
||||||
const fetchPoolData = useCallback(async () => {
|
const fetchAllData = useCallback(async () => {
|
||||||
if (!ddo?.chainId || !price?.address || !owner) return
|
if (!ddo?.chainId || !price?.address || !owner) return
|
||||||
|
|
||||||
const poolData = await getPoolData(ddo.chainId, price.address, owner)
|
const response = await getPoolData(
|
||||||
setPoolData(poolData)
|
|
||||||
LoggerInstance.log('[pool] Fetched pool data:', poolData)
|
|
||||||
}, [ddo?.chainId, price?.address, owner])
|
|
||||||
|
|
||||||
const fetchUserShares = useCallback(async () => {
|
|
||||||
if (!ddo?.chainId || !price?.address || !accountId) return
|
|
||||||
|
|
||||||
const userShares = await getUserPoolShareBalance(
|
|
||||||
ddo.chainId,
|
ddo.chainId,
|
||||||
price.address,
|
price.address,
|
||||||
accountId
|
owner,
|
||||||
|
accountId || ''
|
||||||
)
|
)
|
||||||
|
if (!response) return
|
||||||
|
|
||||||
|
setPoolData(response.poolData)
|
||||||
setPoolInfoUser((prevState) => ({
|
setPoolInfoUser((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
poolShares: userShares
|
poolShares: response.poolDataUser?.shares[0]?.shares
|
||||||
}))
|
}))
|
||||||
LoggerInstance.log(`[pool] Fetched user shares: ${userShares}`)
|
setPoolSnapshots(response.poolSnapshots)
|
||||||
}, [ddo?.chainId, price?.address, accountId])
|
LoggerInstance.log('[pool] Fetched pool data:', response.poolData)
|
||||||
|
LoggerInstance.log('[pool] Fetched user data:', response.poolDataUser)
|
||||||
// Helper: fetch everything
|
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
|
||||||
const fetchAllData = useCallback(() => {
|
}, [ddo?.chainId, price?.address, owner, accountId])
|
||||||
fetchPoolData()
|
|
||||||
fetchUserShares()
|
|
||||||
}, [fetchPoolData, fetchUserShares])
|
|
||||||
|
|
||||||
// Helper: start interval fetching
|
// Helper: start interval fetching
|
||||||
const initFetchInterval = useCallback(() => {
|
const initFetchInterval = useCallback(() => {
|
||||||
|
@ -443,7 +440,7 @@ export default function Pool(): ReactElement {
|
||||||
{poolInfo?.weightBaseToken}/{poolInfo?.weightDt}
|
{poolInfo?.weightBaseToken}/{poolInfo?.weightDt}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Graph />
|
<Graph poolSnapshots={poolSnapshots} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
baseTokenValue={`${price?.ocean}`}
|
baseTokenValue={`${price?.ocean}`}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user