From 38a220deffa96a616d2ad7be188f963754c2b444 Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 13:45:09 +0200 Subject: [PATCH 1/7] add stats page --- src/components/Stats/PoolList/PoolList.tsx | 9 +- .../Stats/PoolListItem/PoolListItem.tsx | 20 +- src/containers/WrappedStats/WrappedStats.tsx | 68 +++--- src/pages/StatsPage/index.tsx | 6 +- src/store/consts/types.ts | 15 ++ src/store/reducers/stats.ts | 2 - src/store/sagas/index.ts | 5 +- src/store/sagas/stats.ts | 204 ++++++++++++++++++ src/utils/utils.ts | 9 + 9 files changed, 289 insertions(+), 49 deletions(-) create mode 100644 src/store/sagas/stats.ts diff --git a/src/components/Stats/PoolList/PoolList.tsx b/src/components/Stats/PoolList/PoolList.tsx index 51601fe6..bb3dd564 100644 --- a/src/components/Stats/PoolList/PoolList.tsx +++ b/src/components/Stats/PoolList/PoolList.tsx @@ -5,6 +5,7 @@ import { useStyles } from './style' import { Grid } from '@mui/material' import { PaginationList } from '@components/PaginationList/PaginationList' import { SortTypePoolList } from '@store/consts/static' +import { Network } from '@invariant-labs/a0-sdk' interface PoolListInterface { data: Array<{ @@ -15,6 +16,8 @@ interface PoolListInterface { volume: number TVL: number fee: number + addressFrom: string + addressTo: string // apy: number // apyData: { // fees: number @@ -22,9 +25,10 @@ interface PoolListInterface { // accumulatedFarmsSingleTick: number // } }> + network: Network } -const PoolList: React.FC = ({ data }) => { +const PoolList: React.FC = ({ data, network }) => { const { classes } = useStyles() const [page, setPage] = React.useState(1) const [sortType, setSortType] = React.useState(SortTypePoolList.VOLUME_DESC) @@ -92,6 +96,9 @@ const PoolList: React.FC = ({ data }) => { // apy={element.apy} // apyData={element.apyData} key={index} + addressFrom={element.addressFrom} + addressTo={element.addressTo} + network={network} /> ))} {pages > 1 ? ( diff --git a/src/components/Stats/PoolListItem/PoolListItem.tsx b/src/components/Stats/PoolListItem/PoolListItem.tsx index a7e4d614..7ba91f26 100644 --- a/src/components/Stats/PoolListItem/PoolListItem.tsx +++ b/src/components/Stats/PoolListItem/PoolListItem.tsx @@ -2,12 +2,14 @@ import React from 'react' import { theme } from '@static/theme' import { useStyles } from './style' import { Box, Grid, Typography, useMediaQuery } from '@mui/material' -import { formatNumbers, showPrefix } from '@utils/utils' +import { addressToTicker, formatNumbers, parseFeeToPathFee, showPrefix } from '@utils/utils' import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown' import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp' import { useNavigate } from 'react-router-dom' import icons from '@static/icons' import { SortTypePoolList } from '@store/consts/static' +import { Network } from '@invariant-labs/a0-sdk' +import { PERCENTAGE_SCALE } from '@invariant-labs/a0-sdk/target/consts' interface IProps { TVL?: number @@ -22,6 +24,9 @@ interface IProps { sortType?: SortTypePoolList onSort?: (type: SortTypePoolList) => void hideBottomLine?: boolean + addressFrom?: string + addressTo?: string + network?: Network // apy?: number // apyData?: { // fees: number @@ -42,7 +47,10 @@ const PoolListItem: React.FC = ({ tokenIndex, sortType, onSort, - hideBottomLine = false + hideBottomLine = false, + addressFrom, + addressTo, + network // apy = 0, // apyData = { // fees: 0, @@ -56,11 +64,15 @@ const PoolListItem: React.FC = ({ const isXs = useMediaQuery(theme.breakpoints.down('xs')) const handleOpenPosition = () => { - navigate(`/newPosition/${symbolFrom}/${symbolTo}/0_01`) + navigate( + `/newPosition/${addressToTicker(network ?? Network.Testnet, addressFrom ?? '')}/${addressToTicker(network ?? Network.Testnet, addressTo ?? '')}/${parseFeeToPathFee(BigInt(Math.round(fee * 10 ** Number(PERCENTAGE_SCALE - 2n))))}` + ) } const handleOpenSwap = () => { - navigate(`/exchange/${symbolFrom}/${symbolTo}`) + navigate( + `/exchange/${addressToTicker(network ?? Network.Testnet, addressFrom ?? '')}/${addressToTicker(network ?? Network.Testnet, addressTo ?? '')}` + ) } return ( diff --git a/src/containers/WrappedStats/WrappedStats.tsx b/src/containers/WrappedStats/WrappedStats.tsx index 31a77c48..00cfd4b7 100644 --- a/src/containers/WrappedStats/WrappedStats.tsx +++ b/src/containers/WrappedStats/WrappedStats.tsx @@ -5,16 +5,16 @@ import useStyles from './styles' import { Grid, Typography } from '@mui/material' import { Network } from '@invariant-labs/a0-sdk' import { EmptyPlaceholder } from '@components/EmptyPlaceholder/EmptyPlaceholder' -// import { -// fees24, -// isLoading, -// liquidityPlot, -// poolsStatsWithTokensDetails, -// tokensStatsWithTokensDetails, -// tvl24, -// volume24, -// volumePlot -// } from '@store/selectors/stats' +import { + fees24, + isLoading, + liquidityPlot, + poolsStatsWithTokensDetails, + tokensStatsWithTokensDetails, + tvl24, + volume24, + volumePlot +} from '@store/selectors/stats' import { networkType } from '@store/selectors/connection' import { actions } from '@store/reducers/stats' import Volume from '@components/Stats/Volume/Volume' @@ -22,30 +22,20 @@ import Liquidity from '@components/Stats/Liquidity/Liquidity' import VolumeBar from '@components/Stats/volumeBar/VolumeBar' import TokensList from '@components/Stats/TokensList/TokensList' import PoolList from '@components/Stats/PoolList/PoolList' -import { - isLoadingStats, - liquidityPlotData, - fees24h, - poolsList, - tokensList, - tvl24h, - volume24h, - volumePlotData -} from './mockStats' export const WrappedStats: React.FC = () => { const { classes } = useStyles() const dispatch = useDispatch() - // const poolsList = useSelector(poolsStatsWithTokensDetails) - // const tokensList = useSelector(tokensStatsWithTokensDetails) - // const volume24h = useSelector(volume24) - // const tvl24h = useSelector(tvl24) - // const fees24h = useSelector(fees24) - // const volumePlotData = useSelector(volumePlot) - // const liquidityPlotData = useSelector(liquidityPlot) - // const isLoadingStats = useSelector(isLoading) + const poolsList = useSelector(poolsStatsWithTokensDetails) + const tokensList = useSelector(tokensStatsWithTokensDetails) + const volume24h = useSelector(volume24) + const tvl24h = useSelector(tvl24) + const fees24h = useSelector(fees24) + const volumePlotData = useSelector(volumePlot) + const liquidityPlotData = useSelector(liquidityPlot) + const isLoadingStats = useSelector(isLoading) const currentNetwork = useSelector(networkType) useEffect(() => { @@ -95,11 +85,12 @@ export const WrappedStats: React.FC = () => { ({ - icon: tokenData.tokenDetails.logoURI, - name: tokenData.tokenDetails.name, - symbol: tokenData.tokenDetails.symbol, + icon: tokenData.tokenDetails?.logoURI, + name: tokenData.tokenDetails?.name, + symbol: tokenData.tokenDetails?.symbol, price: tokenData.price, - priceChange: tokenData.priceChange, + // priceChange: tokenData.priceChange, + priceChange: 0, volume: tokenData.volume24, TVL: tokenData.tvl }))} @@ -108,13 +99,15 @@ export const WrappedStats: React.FC = () => { Top pools ({ - symbolFrom: poolData.tokenXDetails.symbol, - symbolTo: poolData.tokenYDetails.symbol, - iconFrom: poolData.tokenXDetails.logoURI, - iconTo: poolData.tokenYDetails.logoURI, + symbolFrom: poolData.tokenXDetails?.symbol, + symbolTo: poolData.tokenYDetails?.symbol, + iconFrom: poolData.tokenXDetails?.logoURI, + iconTo: poolData.tokenYDetails?.logoURI, volume: poolData.volume24, TVL: poolData.tvl, - fee: poolData.fee + fee: poolData.fee, + addressFrom: poolData.tokenX, + addressTo: poolData.tokenY // apy: poolData.apy, // apyData: { // fees: poolData.apy, @@ -130,6 +123,7 @@ export const WrappedStats: React.FC = () => { // accumulatedFarmsAvg: accumulatedAverageAPY?.[poolData.poolAddress.toString()] ?? 0 // } }))} + network={currentNetwork} /> )} diff --git a/src/pages/StatsPage/index.tsx b/src/pages/StatsPage/index.tsx index a4f6ecca..95870a58 100644 --- a/src/pages/StatsPage/index.tsx +++ b/src/pages/StatsPage/index.tsx @@ -1,15 +1,13 @@ import { Grid } from '@mui/material' import { useStyles } from './styles' -import comingSoon from '../../static/png/coming-soon.png' -// import WrappedStats from '@containers/WrappedStats/WrappedStats' +import WrappedStats from '@containers/WrappedStats/WrappedStats' export const StatsPage: React.FC = () => { const { classes } = useStyles() return ( - Coming soon - {/* */} + ) } diff --git a/src/store/consts/types.ts b/src/store/consts/types.ts index 6772254a..ba5394a4 100644 --- a/src/store/consts/types.ts +++ b/src/store/consts/types.ts @@ -78,3 +78,18 @@ export enum Chain { AlephZero = 'Aleph Zero', Eclipse = 'Eclipse' } + +export interface SnapshotValueData { + tokenBNFromBeginning: string + usdValue24: number +} + +export interface PoolSnapshot { + timestamp: number + volumeX: SnapshotValueData + volumeY: SnapshotValueData + liquidityX: SnapshotValueData + liquidityY: SnapshotValueData + feeX: SnapshotValueData + feeY: SnapshotValueData +} diff --git a/src/store/reducers/stats.ts b/src/store/reducers/stats.ts index 1b792752..472237a7 100644 --- a/src/store/reducers/stats.ts +++ b/src/store/reducers/stats.ts @@ -14,13 +14,11 @@ export interface Value24H { export interface TokenStatsData { address: string price: number - priceChange: number volume24: number tvl: number } export interface PoolStatsData { - poolAddress: string tokenX: string tokenY: string fee: number diff --git a/src/store/sagas/index.ts b/src/store/sagas/index.ts index 6abd3e53..0ca38379 100644 --- a/src/store/sagas/index.ts +++ b/src/store/sagas/index.ts @@ -4,8 +4,11 @@ import { poolsSaga } from './pools' import { positionsSaga } from './positions' import { swapSaga } from './swap' import { walletSaga } from './wallet' +import { statsHandler } from './stats' function* rootSaga(): Generator { - yield all([connectionSaga, walletSaga, poolsSaga, positionsSaga, swapSaga].map(spawn)) + yield all( + [connectionSaga, walletSaga, poolsSaga, positionsSaga, swapSaga, statsHandler].map(spawn) + ) } export default rootSaga diff --git a/src/store/sagas/stats.ts b/src/store/sagas/stats.ts new file mode 100644 index 00000000..f9093f2f --- /dev/null +++ b/src/store/sagas/stats.ts @@ -0,0 +1,204 @@ +import { PoolKey } from '@invariant-labs/a0-sdk' +import { actions, PoolStatsData, TimeData, TokenStatsData } from '@store/reducers/stats' +import { actions as poolsActions } from '@store/reducers/pools' +import { networkType } from '@store/selectors/connection' +import { tokens } from '@store/selectors/pools' +import { address } from '@store/selectors/wallet' +import { getNetworkStats, getTokenDataByAddresses, printBigint } from '@utils/utils' +import { call, put, select, takeEvery } from 'typed-redux-saga' +import { getPSP22 } from './connection' + +export function* getStats(): Generator { + try { + const currentNetwork = yield* select(networkType) + const walletAddress = yield* select(address) + const psp22 = yield* getPSP22() + + const data = yield* call(getNetworkStats, currentNetwork.toLowerCase()) + + const volume24 = { + value: 0, + change: 0 + } + const tvl24 = { + value: 0, + change: 0 + } + const fees24 = { + value: 0, + change: 0 + } + + const tokensDataObject: Record = {} + let poolsData: PoolStatsData[] = [] + + const volumeForTimestamps: Record = {} + const liquidityForTimestamps: Record = {} + const feesForTimestamps: Record = {} + + const lastTimestamp = Math.max( + ...Object.values(data) + .filter(snaps => snaps.length > 0) + .map(snaps => +snaps[snaps.length - 1].timestamp) + ) + + Object.entries(data).forEach(([poolKey, snapshots]) => { + const parsedPoolKey: PoolKey = JSON.parse(poolKey) + + if (!tokensDataObject[parsedPoolKey.tokenX]) { + tokensDataObject[parsedPoolKey.tokenX] = { + address: parsedPoolKey.tokenX, + price: 0, + volume24: 0, + tvl: 0 + } + } + + if (!tokensDataObject[parsedPoolKey.tokenY]) { + tokensDataObject[parsedPoolKey.tokenY] = { + address: parsedPoolKey.tokenY, + price: 0, + volume24: 0, + tvl: 0 + } + } + + if (!snapshots.length) { + poolsData.push({ + volume24: 0, + tvl: 0, + tokenX: parsedPoolKey.tokenX, + tokenY: parsedPoolKey.tokenY, + fee: +printBigint(parsedPoolKey.feeTier.fee, 10n) + // apy: poolsApy[address] ?? 0, + }) + return + } + + const tokenX = parsedPoolKey.tokenX + const tokenY = parsedPoolKey.tokenY + + const lastSnapshot = snapshots[snapshots.length - 1] + + tokensDataObject[tokenX].volume24 += + lastSnapshot.timestamp === lastTimestamp ? lastSnapshot.volumeX.usdValue24 : 0 + tokensDataObject[tokenY].volume24 += + lastSnapshot.timestamp === lastTimestamp ? lastSnapshot.volumeY.usdValue24 : 0 + tokensDataObject[tokenX].tvl += lastSnapshot.liquidityX.usdValue24 + tokensDataObject[tokenY].tvl += lastSnapshot.liquidityY.usdValue24 + + poolsData.push({ + volume24: + lastSnapshot.timestamp === lastTimestamp + ? lastSnapshot.volumeX.usdValue24 + lastSnapshot.volumeY.usdValue24 + : 0, + tvl: + lastSnapshot.timestamp === lastTimestamp + ? lastSnapshot.liquidityX.usdValue24 + lastSnapshot.liquidityY.usdValue24 + : 0, + tokenX: parsedPoolKey.tokenX, + tokenY: parsedPoolKey.tokenY, + fee: +printBigint(parsedPoolKey.feeTier.fee, 10n) + // apy: poolsApy[address] ?? 0, + }) + + snapshots.slice(-30).forEach(snapshot => { + const timestamp = snapshot.timestamp.toString() + + if (!volumeForTimestamps[timestamp]) { + volumeForTimestamps[timestamp] = 0 + } + + if (!liquidityForTimestamps[timestamp]) { + liquidityForTimestamps[timestamp] = 0 + } + + if (!feesForTimestamps[timestamp]) { + feesForTimestamps[timestamp] = 0 + } + + volumeForTimestamps[timestamp] += snapshot.volumeX.usdValue24 + snapshot.volumeY.usdValue24 + liquidityForTimestamps[timestamp] += + snapshot.liquidityX.usdValue24 + snapshot.liquidityY.usdValue24 + feesForTimestamps[timestamp] += snapshot.feeX.usdValue24 + snapshot.feeY.usdValue24 + }) + }) + + const volumePlot: TimeData[] = Object.entries(volumeForTimestamps) + .map(([timestamp, value]) => ({ + timestamp: +timestamp, + value + })) + .sort((a, b) => a.timestamp - b.timestamp) + const liquidityPlot: TimeData[] = Object.entries(liquidityForTimestamps) + .map(([timestamp, value]) => ({ + timestamp: +timestamp, + value + })) + .sort((a, b) => a.timestamp - b.timestamp) + const feePlot: TimeData[] = Object.entries(feesForTimestamps) + .map(([timestamp, value]) => ({ + timestamp: +timestamp, + value + })) + .sort((a, b) => a.timestamp - b.timestamp) + + const tiersToOmit = [0.001, 0.003] + + poolsData = poolsData.filter(pool => !tiersToOmit.includes(pool.fee)) + + volume24.value = volumePlot.length ? volumePlot[volumePlot.length - 1].value : 0 + tvl24.value = liquidityPlot.length ? liquidityPlot[liquidityPlot.length - 1].value : 0 + fees24.value = feePlot.length ? feePlot[feePlot.length - 1].value : 0 + + const prevVolume24 = volumePlot.length > 1 ? volumePlot[volumePlot.length - 2].value : 0 + const prevTvl24 = liquidityPlot.length > 1 ? liquidityPlot[liquidityPlot.length - 2].value : 0 + const prevFees24 = feePlot.length > 1 ? feePlot[feePlot.length - 2].value : 0 + + volume24.change = prevVolume24 ? ((volume24.value - prevVolume24) / prevVolume24) * 100 : 0 + tvl24.change = prevTvl24 ? ((tvl24.value - prevTvl24) / prevTvl24) * 100 : 0 + fees24.change = prevFees24 ? ((fees24.value - prevFees24) / prevFees24) * 100 : 0 + + yield* put( + actions.setCurrentStats({ + volume24, + tvl24, + fees24, + tokensData: Object.values(tokensDataObject), + poolsData, + volumePlot, + liquidityPlot + }) + ) + + const allTokens = yield* select(tokens) + + const unknownTokens = new Set() + + Object.keys(data).forEach(poolKey => { + const parsedPoolKey: PoolKey = JSON.parse(poolKey) + + if (!allTokens[parsedPoolKey.tokenX]) { + unknownTokens.add(parsedPoolKey.tokenX) + } + + if (!allTokens[parsedPoolKey.tokenY]) { + unknownTokens.add(parsedPoolKey.tokenY) + } + }) + + const unknownTokensData = yield* call( + getTokenDataByAddresses, + [...unknownTokens], + psp22, + walletAddress + ) + yield* put(poolsActions.addTokens(unknownTokensData)) + } catch (error) { + console.log(error) + } +} + +export function* statsHandler(): Generator { + yield* takeEvery(actions.getCurrentStats, getStats) +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ca126cab..bf622ca6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -62,6 +62,7 @@ import { BestTier, CoinGeckoAPIData, FormatNumberThreshold, + PoolSnapshot, PrefixConfig, Token, TokenPriceData @@ -1186,3 +1187,11 @@ export const findClosestIndexByValue = (arr: number[], value: number): number => } return high } + +export const getNetworkStats = async (name: string): Promise> => { + const { data } = await axios.get>( + `https://stats.invariant.app/a0/full/${name}` + ) + + return data +} From 6c05c4419d9a7f41c0211ae907e9d7f3d61b6d65 Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 13:47:25 +0200 Subject: [PATCH 2/7] fix build --- src/components/Stats/PoolList/Stats.PoolList.stories.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Stats/PoolList/Stats.PoolList.stories.tsx b/src/components/Stats/PoolList/Stats.PoolList.stories.tsx index 98ce3e51..cb4104d2 100644 --- a/src/components/Stats/PoolList/Stats.PoolList.stories.tsx +++ b/src/components/Stats/PoolList/Stats.PoolList.stories.tsx @@ -3,6 +3,7 @@ import { MemoryRouter } from 'react-router-dom' import PoolList from './PoolList' import { store } from '@store/index' import { Provider } from 'react-redux' +import { Network } from '@invariant-labs/a0-sdk' const meta = { title: 'Stats/PoolList', @@ -75,12 +76,15 @@ const poolsList = Array(40) coingeckoId: 'usd-coin' }, volume24: randomVolume24, - tvl: randomTvl24 + tvl: randomTvl24, + addressFrom: '5Dvb5E8zKU4E9c7YxfNL5VC8YQj4VAFUTCGYY9ayFLnnY3UA', + addressTo: '5Dvb5E8zKU4E9c7YxfNL5VC8YQj4VAFUTCGYY9ayFLnnY3UA' } }) export const Primary: Story = { args: { - data: poolsList + data: poolsList, + network: Network.Local } } From fe6dd1179a8a81c288bdb2c7d4bb7fd40ee3a428 Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 14:23:53 +0200 Subject: [PATCH 3/7] bump sdk version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3abd12fb..7f6fadc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@invariant-labs/a0-sdk": "^0.2.19", + "@invariant-labs/a0-sdk": "^0.2.20", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.15", "@nightlylabs/wallet-selector-polkadot": "^0.2.5", @@ -2540,9 +2540,9 @@ "license": "BSD-3-Clause" }, "node_modules/@invariant-labs/a0-sdk": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@invariant-labs/a0-sdk/-/a0-sdk-0.2.19.tgz", - "integrity": "sha512-XCes1Y0LREz4GD4sg9D5bZAFn8guxZT63rGcXFAN6O/CA7uiV0IxxtZFotc1gl2S+25oLprdrsun9bh4+qYc0Q==", + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@invariant-labs/a0-sdk/-/a0-sdk-0.2.20.tgz", + "integrity": "sha512-LhvkqW7PXX5X7UiAXGxpN0DrEFgXGN0XElDm8lT/x8p4DWzdqvkdl9e0u0rAq7D0kbjmHx5EpjnGER9rvjHYww==", "dependencies": { "@invariant-labs/a0-sdk-wasm": "^0.1.23", "@polkadot/api": "^10.12.4", diff --git a/package.json b/package.json index 1488b275..a70470ac 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@invariant-labs/a0-sdk": "^0.2.19", + "@invariant-labs/a0-sdk": "^0.2.20", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.15", "@nightlylabs/wallet-selector-polkadot": "^0.2.5", From 890f556cd5dfd596f57703975c7be5cab998571a Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 14:25:43 +0200 Subject: [PATCH 4/7] fix mainnet stats --- src/containers/WrappedStats/WrappedStats.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/containers/WrappedStats/WrappedStats.tsx b/src/containers/WrappedStats/WrappedStats.tsx index 00cfd4b7..1b80029c 100644 --- a/src/containers/WrappedStats/WrappedStats.tsx +++ b/src/containers/WrappedStats/WrappedStats.tsx @@ -3,7 +3,6 @@ import { useDispatch, useSelector } from 'react-redux' import loader from '@static/gif/loader.gif' import useStyles from './styles' import { Grid, Typography } from '@mui/material' -import { Network } from '@invariant-labs/a0-sdk' import { EmptyPlaceholder } from '@components/EmptyPlaceholder/EmptyPlaceholder' import { fees24, @@ -44,11 +43,7 @@ export const WrappedStats: React.FC = () => { return ( - {currentNetwork !== Network.Testnet ? ( - - - - ) : isLoadingStats ? ( + {isLoadingStats ? ( Loading ) : liquidityPlotData.length === 0 ? ( From 1f00165c6b64556246abd38bd6377a098db0c00d Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 15:09:47 +0200 Subject: [PATCH 5/7] add prices to common tokens --- src/store/sagas/stats.ts | 21 ++++++++++++++++++--- src/utils/utils.ts | 8 ++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/store/sagas/stats.ts b/src/store/sagas/stats.ts index f9093f2f..12460ccf 100644 --- a/src/store/sagas/stats.ts +++ b/src/store/sagas/stats.ts @@ -4,7 +4,13 @@ import { actions as poolsActions } from '@store/reducers/pools' import { networkType } from '@store/selectors/connection' import { tokens } from '@store/selectors/pools' import { address } from '@store/selectors/wallet' -import { getNetworkStats, getTokenDataByAddresses, printBigint } from '@utils/utils' +import { + getCoingeckoPricesData, + getNetworkStats, + getTokenDataByAddresses, + printBigint, + tickerToAddress +} from '@utils/utils' import { call, put, select, takeEvery } from 'typed-redux-saga' import { getPSP22 } from './connection' @@ -124,6 +130,17 @@ export function* getStats(): Generator { }) }) + const tokensPricesData = yield* call(getCoingeckoPricesData) + const allTokens = yield* select(tokens) + + tokensPricesData.forEach(token => { + Object.entries(allTokens).forEach(([address, tokenData]) => { + if (tokenData.coingeckoId === token.id) { + tokensDataObject[address].price = token.current_price + } + }) + }) + const volumePlot: TimeData[] = Object.entries(volumeForTimestamps) .map(([timestamp, value]) => ({ timestamp: +timestamp, @@ -171,8 +188,6 @@ export function* getStats(): Generator { }) ) - const allTokens = yield* select(tokens) - const unknownTokens = new Set() Object.keys(data).forEach(poolKey => { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index bf622ca6..90ae3ae0 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1195,3 +1195,11 @@ export const getNetworkStats = async (name: string): Promise => { + const { data } = await axios.get( + `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${DEFAULT_TOKENS}` + ) + + return data +} From 1dec1a9dc9b2e96f7497196c06cbf5428c537651 Mon Sep 17 00:00:00 2001 From: zielvna Date: Thu, 12 Sep 2024 15:10:00 +0200 Subject: [PATCH 6/7] fix build --- src/store/sagas/stats.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/store/sagas/stats.ts b/src/store/sagas/stats.ts index 12460ccf..f5e85044 100644 --- a/src/store/sagas/stats.ts +++ b/src/store/sagas/stats.ts @@ -8,8 +8,7 @@ import { getCoingeckoPricesData, getNetworkStats, getTokenDataByAddresses, - printBigint, - tickerToAddress + printBigint } from '@utils/utils' import { call, put, select, takeEvery } from 'typed-redux-saga' import { getPSP22 } from './connection' From 004eb9bf25216653ff45287300861455a888956b Mon Sep 17 00:00:00 2001 From: zielvna Date: Fri, 13 Sep 2024 14:25:02 +0200 Subject: [PATCH 7/7] refactor stats --- src/store/consts/types.ts | 41 +++++++++ src/store/sagas/stats.ts | 188 ++------------------------------------ src/utils/utils.ts | 7 ++ 3 files changed, 57 insertions(+), 179 deletions(-) diff --git a/src/store/consts/types.ts b/src/store/consts/types.ts index ba5394a4..60f01ed9 100644 --- a/src/store/consts/types.ts +++ b/src/store/consts/types.ts @@ -93,3 +93,44 @@ export interface PoolSnapshot { feeX: SnapshotValueData feeY: SnapshotValueData } + +export interface FullSnap { + volume24: { + value: number + change: number + } + tvl24: { + value: number + change: number + } + fees24: { + value: number + change: number + } + tokensData: TokenStatsDataWithString[] + poolsData: PoolStatsDataWithString[] + volumePlot: TimeData[] + liquidityPlot: TimeData[] +} + +export interface TokenStatsDataWithString { + address: string + price: number + volume24: number + tvl: number +} + +export interface TimeData { + timestamp: number + value: number +} + +export interface PoolStatsDataWithString { + poolAddress: string + tokenX: string + tokenY: string + fee: number + volume24: number + tvl: number + apy: number +} diff --git a/src/store/sagas/stats.ts b/src/store/sagas/stats.ts index f5e85044..54b8fb03 100644 --- a/src/store/sagas/stats.ts +++ b/src/store/sagas/stats.ts @@ -1,15 +1,9 @@ -import { PoolKey } from '@invariant-labs/a0-sdk' -import { actions, PoolStatsData, TimeData, TokenStatsData } from '@store/reducers/stats' +import { actions } from '@store/reducers/stats' import { actions as poolsActions } from '@store/reducers/pools' import { networkType } from '@store/selectors/connection' import { tokens } from '@store/selectors/pools' import { address } from '@store/selectors/wallet' -import { - getCoingeckoPricesData, - getNetworkStats, - getTokenDataByAddresses, - printBigint -} from '@utils/utils' +import { getFullSnap, getTokenDataByAddresses } from '@utils/utils' import { call, put, select, takeEvery } from 'typed-redux-saga' import { getPSP22 } from './connection' @@ -19,185 +13,21 @@ export function* getStats(): Generator { const walletAddress = yield* select(address) const psp22 = yield* getPSP22() - const data = yield* call(getNetworkStats, currentNetwork.toLowerCase()) + const fullSnap = yield* call(getFullSnap, currentNetwork.toLowerCase()) - const volume24 = { - value: 0, - change: 0 - } - const tvl24 = { - value: 0, - change: 0 - } - const fees24 = { - value: 0, - change: 0 - } + yield* put(actions.setCurrentStats(fullSnap)) - const tokensDataObject: Record = {} - let poolsData: PoolStatsData[] = [] - - const volumeForTimestamps: Record = {} - const liquidityForTimestamps: Record = {} - const feesForTimestamps: Record = {} - - const lastTimestamp = Math.max( - ...Object.values(data) - .filter(snaps => snaps.length > 0) - .map(snaps => +snaps[snaps.length - 1].timestamp) - ) - - Object.entries(data).forEach(([poolKey, snapshots]) => { - const parsedPoolKey: PoolKey = JSON.parse(poolKey) - - if (!tokensDataObject[parsedPoolKey.tokenX]) { - tokensDataObject[parsedPoolKey.tokenX] = { - address: parsedPoolKey.tokenX, - price: 0, - volume24: 0, - tvl: 0 - } - } - - if (!tokensDataObject[parsedPoolKey.tokenY]) { - tokensDataObject[parsedPoolKey.tokenY] = { - address: parsedPoolKey.tokenY, - price: 0, - volume24: 0, - tvl: 0 - } - } - - if (!snapshots.length) { - poolsData.push({ - volume24: 0, - tvl: 0, - tokenX: parsedPoolKey.tokenX, - tokenY: parsedPoolKey.tokenY, - fee: +printBigint(parsedPoolKey.feeTier.fee, 10n) - // apy: poolsApy[address] ?? 0, - }) - return - } - - const tokenX = parsedPoolKey.tokenX - const tokenY = parsedPoolKey.tokenY - - const lastSnapshot = snapshots[snapshots.length - 1] - - tokensDataObject[tokenX].volume24 += - lastSnapshot.timestamp === lastTimestamp ? lastSnapshot.volumeX.usdValue24 : 0 - tokensDataObject[tokenY].volume24 += - lastSnapshot.timestamp === lastTimestamp ? lastSnapshot.volumeY.usdValue24 : 0 - tokensDataObject[tokenX].tvl += lastSnapshot.liquidityX.usdValue24 - tokensDataObject[tokenY].tvl += lastSnapshot.liquidityY.usdValue24 - - poolsData.push({ - volume24: - lastSnapshot.timestamp === lastTimestamp - ? lastSnapshot.volumeX.usdValue24 + lastSnapshot.volumeY.usdValue24 - : 0, - tvl: - lastSnapshot.timestamp === lastTimestamp - ? lastSnapshot.liquidityX.usdValue24 + lastSnapshot.liquidityY.usdValue24 - : 0, - tokenX: parsedPoolKey.tokenX, - tokenY: parsedPoolKey.tokenY, - fee: +printBigint(parsedPoolKey.feeTier.fee, 10n) - // apy: poolsApy[address] ?? 0, - }) - - snapshots.slice(-30).forEach(snapshot => { - const timestamp = snapshot.timestamp.toString() - - if (!volumeForTimestamps[timestamp]) { - volumeForTimestamps[timestamp] = 0 - } - - if (!liquidityForTimestamps[timestamp]) { - liquidityForTimestamps[timestamp] = 0 - } - - if (!feesForTimestamps[timestamp]) { - feesForTimestamps[timestamp] = 0 - } - - volumeForTimestamps[timestamp] += snapshot.volumeX.usdValue24 + snapshot.volumeY.usdValue24 - liquidityForTimestamps[timestamp] += - snapshot.liquidityX.usdValue24 + snapshot.liquidityY.usdValue24 - feesForTimestamps[timestamp] += snapshot.feeX.usdValue24 + snapshot.feeY.usdValue24 - }) - }) - - const tokensPricesData = yield* call(getCoingeckoPricesData) const allTokens = yield* select(tokens) - tokensPricesData.forEach(token => { - Object.entries(allTokens).forEach(([address, tokenData]) => { - if (tokenData.coingeckoId === token.id) { - tokensDataObject[address].price = token.current_price - } - }) - }) - - const volumePlot: TimeData[] = Object.entries(volumeForTimestamps) - .map(([timestamp, value]) => ({ - timestamp: +timestamp, - value - })) - .sort((a, b) => a.timestamp - b.timestamp) - const liquidityPlot: TimeData[] = Object.entries(liquidityForTimestamps) - .map(([timestamp, value]) => ({ - timestamp: +timestamp, - value - })) - .sort((a, b) => a.timestamp - b.timestamp) - const feePlot: TimeData[] = Object.entries(feesForTimestamps) - .map(([timestamp, value]) => ({ - timestamp: +timestamp, - value - })) - .sort((a, b) => a.timestamp - b.timestamp) - - const tiersToOmit = [0.001, 0.003] - - poolsData = poolsData.filter(pool => !tiersToOmit.includes(pool.fee)) - - volume24.value = volumePlot.length ? volumePlot[volumePlot.length - 1].value : 0 - tvl24.value = liquidityPlot.length ? liquidityPlot[liquidityPlot.length - 1].value : 0 - fees24.value = feePlot.length ? feePlot[feePlot.length - 1].value : 0 - - const prevVolume24 = volumePlot.length > 1 ? volumePlot[volumePlot.length - 2].value : 0 - const prevTvl24 = liquidityPlot.length > 1 ? liquidityPlot[liquidityPlot.length - 2].value : 0 - const prevFees24 = feePlot.length > 1 ? feePlot[feePlot.length - 2].value : 0 - - volume24.change = prevVolume24 ? ((volume24.value - prevVolume24) / prevVolume24) * 100 : 0 - tvl24.change = prevTvl24 ? ((tvl24.value - prevTvl24) / prevTvl24) * 100 : 0 - fees24.change = prevFees24 ? ((fees24.value - prevFees24) / prevFees24) * 100 : 0 - - yield* put( - actions.setCurrentStats({ - volume24, - tvl24, - fees24, - tokensData: Object.values(tokensDataObject), - poolsData, - volumePlot, - liquidityPlot - }) - ) - const unknownTokens = new Set() - Object.keys(data).forEach(poolKey => { - const parsedPoolKey: PoolKey = JSON.parse(poolKey) - - if (!allTokens[parsedPoolKey.tokenX]) { - unknownTokens.add(parsedPoolKey.tokenX) + fullSnap.poolsData.forEach(({ tokenX, tokenY }) => { + if (!allTokens[tokenX]) { + unknownTokens.add(tokenX) } - if (!allTokens[parsedPoolKey.tokenY]) { - unknownTokens.add(parsedPoolKey.tokenY) + if (!allTokens[tokenY]) { + unknownTokens.add(tokenY) } }) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 90ae3ae0..3133c382 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -62,6 +62,7 @@ import { BestTier, CoinGeckoAPIData, FormatNumberThreshold, + FullSnap, PoolSnapshot, PrefixConfig, Token, @@ -1203,3 +1204,9 @@ export const getCoingeckoPricesData = async (): Promise => { return data } + +export const getFullSnap = async (name: string): Promise => { + const { data } = await axios.get(`https://stats.invariant.app/a0/full_snap/${name}`) + + return data +}