diff --git a/src/App.tsx b/src/App.tsx index 1a48c6738..2a144e82a 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import { PoolsPage, SwapPage, AnalyticsTokenDetails, + AnalyticsPairDetails, } from 'pages'; import { PageLayout } from 'layouts'; import { getLibrary } from 'utils'; @@ -133,6 +134,11 @@ const App: React.FC = () => { + + + + + diff --git a/src/apollo/client.js b/src/apollo/client.js index d0554476d..a1df72408 100755 --- a/src/apollo/client.js +++ b/src/apollo/client.js @@ -18,6 +18,14 @@ export const healthClient = new ApolloClient({ shouldBatch: true, }); +export const txClient = new ApolloClient({ + link: new HttpLink({ + uri: 'https://polygon.furadao.org/subgraphs/name/quickswap', + }), + cache: new InMemoryCache(), + shouldBatch: true, +}); + export const v1Client = new ApolloClient({ link: new HttpLink({ uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap', diff --git a/src/apollo/queries.ts b/src/apollo/queries.ts index ec133668f..1547b89b6 100755 --- a/src/apollo/queries.ts +++ b/src/apollo/queries.ts @@ -127,10 +127,46 @@ export const TOKEN_CHART = gql` } `; +export const PAIR_CHART = gql` + query pairDayDatas($pairAddress: Bytes!, $skip: Int!, $startTime: Int!) { + pairDayDatas( + first: 1000 + skip: $skip + orderBy: date + orderDirection: asc + where: { pairAddress: $pairAddress, date_gt: $startTime } + ) { + id + date + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + reserveUSD + } + } +`; + +export const HOURLY_PAIR_RATES = (pairAddress: string, blocks: any[]) => { + let queryString = 'query blocks {'; + queryString += blocks.map( + (block) => ` + t${block.timestamp}: pair(id:"${pairAddress}", block: { number: ${block.number} }) { + token0Price + token1Price + } + `, + ); + + queryString += '}'; + return gql(queryString); +}; + const PairFields = ` fragment PairFields on Pair { id trackedReserveETH + reserve0 + reserve1 volumeUSD reserveUSD totalSupply @@ -138,11 +174,13 @@ const PairFields = ` symbol id decimals + derivedETH } token1 { symbol id decimals + derivedETH } } `; @@ -487,3 +525,88 @@ export const GET_BLOCKS: any = (timestamps: number[]) => { queryString += '}'; return gql(queryString); }; + +export const FILTERED_TRANSACTIONS = gql` + query($allPairs: [Bytes]!) { + mints( + first: 20 + where: { pair_in: $allPairs } + orderBy: timestamp + orderDirection: desc + ) { + transaction { + id + timestamp + } + pair { + token0 { + id + symbol + } + token1 { + id + symbol + } + } + to + liquidity + amount0 + amount1 + amountUSD + } + burns( + first: 20 + where: { pair_in: $allPairs } + orderBy: timestamp + orderDirection: desc + ) { + transaction { + id + timestamp + } + pair { + token0 { + id + symbol + } + token1 { + id + symbol + } + } + sender + liquidity + amount0 + amount1 + amountUSD + } + swaps( + first: 30 + where: { pair_in: $allPairs } + orderBy: timestamp + orderDirection: desc + ) { + transaction { + id + timestamp + } + id + pair { + token0 { + id + symbol + } + token1 { + id + symbol + } + } + amount0In + amount0Out + amount1In + amount1Out + amountUSD + to + } + } +`; diff --git a/src/components/CurrencySearchModal/CurrencySearch.tsx b/src/components/CurrencySearchModal/CurrencySearch.tsx index 1a248ed51..3d6519a08 100755 --- a/src/components/CurrencySearchModal/CurrencySearch.tsx +++ b/src/components/CurrencySearchModal/CurrencySearch.tsx @@ -37,7 +37,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ flexDirection: 'column', background: palette.background.paper, backdropFilter: 'blur(9.9px)', - border: '1px solid #3e4252', + border: `1px solid ${palette.grey.A400}`, [breakpoints.down('xs')]: { height: '90vh', }, diff --git a/src/components/FarmDualCard/FarmDualCard.tsx b/src/components/FarmDualCard/FarmDualCard.tsx index 08bcda032..0e6faab1f 100755 --- a/src/components/FarmDualCard/FarmDualCard.tsx +++ b/src/components/FarmDualCard/FarmDualCard.tsx @@ -64,7 +64,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ }, }, buttonToken: { - backgroundColor: '#3e4252', + backgroundColor: palette.grey.A400, borderRadius: '10px', height: '50px', display: 'flex', diff --git a/src/components/FarmLPCard/FarmLPCard.tsx b/src/components/FarmLPCard/FarmLPCard.tsx index 98ee490d5..57b5e4823 100755 --- a/src/components/FarmLPCard/FarmLPCard.tsx +++ b/src/components/FarmLPCard/FarmLPCard.tsx @@ -53,7 +53,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ }, }, buttonToken: { - backgroundColor: '#3e4252', + backgroundColor: palette.grey.A400, borderRadius: '10px', height: '50px', display: 'flex', diff --git a/src/components/FarmLPCard/FarmLPCardDetails.tsx b/src/components/FarmLPCard/FarmLPCardDetails.tsx index 1f880192f..d77516fdb 100644 --- a/src/components/FarmLPCard/FarmLPCardDetails.tsx +++ b/src/components/FarmLPCard/FarmLPCardDetails.tsx @@ -60,7 +60,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ }, }, buttonToken: { - backgroundColor: '#3e4252', + backgroundColor: palette.grey.A400, borderRadius: '10px', height: '50px', display: 'flex', diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 1e0677799..40587c6cd 100755 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -134,7 +134,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ }, }, accountDetails: { - border: `solid 1px #3e4252`, + border: `solid 1px ${palette.grey.A400}`, padding: '0 16px', height: 36, cursor: 'pointer', diff --git a/src/components/PairsTable/PairsTable.tsx b/src/components/PairsTable/PairsTable.tsx index e69136aff..18e26ef2f 100644 --- a/src/components/PairsTable/PairsTable.tsx +++ b/src/components/PairsTable/PairsTable.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Box, Typography, Divider } from '@material-ui/core'; +import { Link } from 'react-router-dom'; import { ChainId, Token } from '@uniswap/sdk'; import { getAddress } from '@ethersproject/address'; import { DoubleCurrencyLogo, CustomTable } from 'components'; @@ -102,12 +103,23 @@ const PairTable: React.FC = ({ data }) => { )} - - - - {token0.symbol} / {token1.symbol} - - + + + + + + {token0.symbol} / {token1.symbol} + + + + = ({ data }) => { const liquidity = pair.trackedReserveUSD ? pair.trackedReserveUSD : pair.reserveUSD; - const oneDayVolume = pair.oneDayVolumeUSD - ? pair.oneDayVolumeUSD - : pair.oneDayVolumeUntracked; - const oneWeekVolume = pair.oneWeekVolumeUSD - ? pair.oneWeekVolumeUSD - : pair.oneWeekVolumeUntracked; + const oneDayVolume = + pair.oneDayVolumeUSD && !isNaN(pair.oneDayVolumeUSD) + ? pair.oneDayVolumeUSD + : pair.oneDayVolumeUntracked && !isNaN(pair.oneDayVolumeUntracked) + ? pair.oneDayVolumeUntracked + : 0; + const oneWeekVolume = + pair.oneWeekVolumeUSD && !isNaN(pair.oneWeekVolumeUSD) + ? pair.oneWeekVolumeUSD + : pair.oneWeekVolumeUntracked && !isNaN(pair.oneWeekVolumeUntracked) + ? pair.oneWeekVolumeUntracked + : 0; const oneDayFee = (Number(oneDayVolume) * 0.003).toLocaleString(); return [ { @@ -203,16 +221,23 @@ const PairTable: React.FC = ({ data }) => { )} - - - - {token0.symbol} / {token1.symbol} - - + + + + + + {token0.symbol} / {token1.symbol} + + + + ), }, diff --git a/src/components/SyrupCard/SyrupCard.tsx b/src/components/SyrupCard/SyrupCard.tsx index c15873147..a897e3d6b 100755 --- a/src/components/SyrupCard/SyrupCard.tsx +++ b/src/components/SyrupCard/SyrupCard.tsx @@ -230,7 +230,7 @@ const SyrupCard: React.FC<{ syrup: SyrupInfo }> = ({ syrup }) => { = ({ data }) => { to={`/analytics/token/${tokenCurrency.address}`} style={{ textDecoration: 'none' }} > - + = ({ data }) => { to={`/analytics/token/${tokenCurrency.address}`} style={{ textDecoration: 'none' }} > - + ({ + priceChangeWrapper: { + height: 25, + padding: '0 12px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 16, + }, + mobileRow: { + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + margin: '8px 0', + }, +})); + +interface TransactionsTableProps { + data: any[]; +} + +const headCells = ( + txFilter: string, + setTxFilter: (txFilter: string) => void, +) => [ + { + id: 'description', + numeric: false, + label: ( + + setTxFilter('')} + > + All + + setTxFilter('Swap')} + style={{ marginLeft: 12 }} + > + Swap + + setTxFilter('Add')} + style={{ marginLeft: 12 }} + > + Add + + setTxFilter('Remove')} + style={{ marginLeft: 12 }} + > + Remove + + + ), + sortDisabled: true, + }, + { + id: 'totalvalue', + numeric: false, + label: 'Total Value', + sortKey: (item: any) => Number(item.amountUSD), + }, + { + id: 'tokenamount1', + numeric: false, + label: 'Token Amount', + sortKey: (item: any) => Number(item.amount1), + }, + { + id: 'tokenamount2', + numeric: false, + label: 'Token Amount', + sortKey: (item: any) => Number(item.amount0), + }, + { + id: 'txn', + numeric: false, + label: 'TXN', + sortKey: (item: any) => item.transaction.id, + }, + { + id: 'time', + numeric: false, + label: 'Time', + sortKey: (item: any) => Number(item.transaction.timestamp) * -1, + }, +]; + +const TransactionsTable: React.FC = ({ data }) => { + const [txFilter, setTxFilter] = useState(''); + const tokenHeadCells = headCells(txFilter, setTxFilter); + const classes = useStyles(); + const { chainId } = useActiveWeb3React(); + const { palette, breakpoints } = useTheme(); + const isMobile = useMediaQuery(breakpoints.down('xs')); + const mobileHTML = (txn: any, index: number) => { + return ( + + + {chainId ? ( + + + {txn.type} {txn.pair.token0.symbol}{' '} + {txn.type === 'Swap' ? 'for' : 'and'} {txn.pair.token1.symbol} + + + ) : ( + + {txn.type} {txn.pair.token0.symbol}{' '} + {txn.type === 'Swap' ? 'for' : 'and'} {txn.pair.token1.symbol} + + )} + + + + Total Value + + ${Number(txn.amountUSD).toLocaleString()} + + + + Token Amount + + {Number(txn.amount1) < 0.0001 + ? '< 0.0001' + : Number(txn.amount1).toFixed( + Number(txn.amount1) < 1 ? 4 : 2, + )}{' '} + {txn.pair.token1.symbol} + + + + Token Amount + + {Number(txn.amount0) < 0.0001 + ? '< 0.0001' + : Number(txn.amount0).toFixed( + Number(txn.amount0) < 1 ? 4 : 2, + )}{' '} + {txn.pair.token0.symbol} + + + + TXN + {chainId ? ( + + + {txn.transaction.id.substring(0, 6)}... + {txn.transaction.id.substring(txn.transaction.id.length - 4)} + + + ) : ( + + {txn.transaction.id.substring(0, 6)}... + {txn.transaction.id.substring(txn.transaction.id.length - 4)} + + )} + + + Time + + {moment(Number(txn.transaction.timestamp) * 1000).fromNow()} + + + + ); + }; + + const desktopHTML = (txn: any) => { + return [ + { + html: chainId ? ( + + + {txn.type} {txn.pair.token0.symbol}{' '} + {txn.type === 'Swap' ? 'for' : 'and'} {txn.pair.token1.symbol} + + + ) : ( + + {txn.type} {txn.pair.token0.symbol}{' '} + {txn.type === 'Swap' ? 'for' : 'and'} {txn.pair.token1.symbol} + + ), + }, + { + html: ( + + ${Number(txn.amountUSD).toLocaleString()} + + ), + }, + { + html: ( + + {Number(txn.amount1) < 0.0001 + ? '< 0.0001' + : Number(txn.amount1).toFixed( + Number(txn.amount1) < 1 ? 4 : 2, + )}{' '} + {txn.pair.token1.symbol} + + ), + }, + { + html: ( + + {Number(txn.amount0) < 0.0001 + ? '< 0.0001' + : Number(txn.amount0).toFixed( + Number(txn.amount0) < 1 ? 4 : 2, + )}{' '} + {txn.pair.token0.symbol} + + ), + }, + { + html: chainId ? ( + + + {txn.transaction.id.substring(0, 6)}... + {txn.transaction.id.substring(txn.transaction.id.length - 4)} + + + ) : ( + + {txn.transaction.id.substring(0, 6)}... + {txn.transaction.id.substring(txn.transaction.id.length - 4)} + + ), + }, + { + html: ( + + {moment(Number(txn.transaction.timestamp) * 1000).fromNow()} + + ), + }, + ]; + }; + + return ( + + {isMobile && ( + + setTxFilter('')}> + + All + + + setTxFilter('Swap')}> + + Swap + + + setTxFilter('Add')}> + + Add + + + setTxFilter('Remove')}> + + Remove + + + + )} + 10} + headCells={tokenHeadCells} + rowsPerPage={10} + data={data.filter((item) => + txFilter === '' ? true : item.type === txFilter, + )} + mobileHTML={mobileHTML} + desktopHTML={desktopHTML} + /> + + ); +}; + +export default TransactionsTable; diff --git a/src/components/TransactionsTable/index.ts b/src/components/TransactionsTable/index.ts new file mode 100644 index 000000000..c7801e020 --- /dev/null +++ b/src/components/TransactionsTable/index.ts @@ -0,0 +1 @@ +export { default } from './TransactionsTable'; diff --git a/src/components/index.ts b/src/components/index.ts index f8c86ac8e..43590fa9d 100755 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -49,3 +49,4 @@ export { default as SwapTokenDetails } from './SwapTokenDetails'; export { default as TopMovers } from './TopMovers'; export { default as TokensTable } from './TokensTable'; export { default as PairTable } from './PairsTable'; +export { default as TransactionsTable } from './TransactionsTable'; diff --git a/src/constants/index.ts b/src/constants/index.ts index 01ec5d925..9a1d277bf 100755 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1059,8 +1059,8 @@ export const KOM = new Token( ); export const UST = new Token( ChainId.MATIC, - '0x692597b009d13C4049a947CAB2239b7d6517875F', - 18, + '0xE6469Ba6D2fD6130788E0eA9C0a0515900563b59', + 6, 'UST', 'Wrapped UST Token', ); @@ -1890,4 +1890,4 @@ export const BETTER_TRADE_LINK_THRESHOLD = new Percent( // the Uniswap Default token list lives here export const DEFAULT_TOKEN_LIST_URL = - 'https://unpkg.com/quickswap-default-token-list@1.2.17/build/quickswap-default.tokenlist.json'; + 'https://unpkg.com/quickswap-default-token-list@1.2.18/build/quickswap-default.tokenlist.json'; diff --git a/src/constants/lists.ts b/src/constants/lists.ts index d499df16f..4650ca0d6 100755 --- a/src/constants/lists.ts +++ b/src/constants/lists.ts @@ -1,5 +1,5 @@ // the Quickswap Default token list lives here export const DEFAULT_TOKEN_LIST_URL = - 'https://unpkg.com/quickswap-default-token-list@1.2.17/build/quickswap-default.tokenlist.json'; + 'https://unpkg.com/quickswap-default-token-list@1.2.18/build/quickswap-default.tokenlist.json'; export const DEFAULT_LIST_OF_LISTS: string[] = [DEFAULT_TOKEN_LIST_URL]; diff --git a/src/pages/AnalyticsPage/AnalyticsInfo.tsx b/src/pages/AnalyticsPage/AnalyticsInfo.tsx index dce07df7f..8216282c4 100644 --- a/src/pages/AnalyticsPage/AnalyticsInfo.tsx +++ b/src/pages/AnalyticsPage/AnalyticsInfo.tsx @@ -1,10 +1,12 @@ import React, { useEffect } from 'react'; import { Box, Typography } from '@material-ui/core'; +import { useTheme } from '@material-ui/core/styles'; import { useGlobalData, useEthPrice } from 'state/application/hooks'; import { getEthPrice, getGlobalData } from 'utils'; import { Skeleton } from '@material-ui/lab'; const AnalyticsInfo: React.FC = () => { + const { palette } = useTheme(); const { ethPrice, updateEthPrice } = useEthPrice(); const { globalData, updateGlobalData } = useGlobalData(); useEffect(() => { @@ -28,7 +30,7 @@ const AnalyticsInfo: React.FC = () => { ({ panel: { - background: '#1b1d26', + background: palette.grey.A700, borderRadius: 20, }, volumeType: { @@ -353,7 +353,9 @@ const AnalyticsOverview: React.FC = ({ setVolumeIndex(0)} > D @@ -361,7 +363,9 @@ const AnalyticsOverview: React.FC = ({ setVolumeIndex(1)} > W diff --git a/src/pages/AnalyticsPage/AnalyticsPage.tsx b/src/pages/AnalyticsPage/AnalyticsPage.tsx index eae0442f1..1ad39640d 100755 --- a/src/pages/AnalyticsPage/AnalyticsPage.tsx +++ b/src/pages/AnalyticsPage/AnalyticsPage.tsx @@ -38,7 +38,7 @@ const useStyles = makeStyles(({ palette }) => ({ display: 'flex', alignItems: 'center', padding: '0 16px', - background: '#1b1d26', + background: palette.grey.A700, height: 46, borderRadius: 10, margin: '12px 0', @@ -55,7 +55,7 @@ const useStyles = makeStyles(({ palette }) => ({ searchContent: { position: 'absolute', width: '100%', - background: '#1b1d26', + background: palette.grey.A700, borderRadius: 10, padding: 12, zIndex: 2, @@ -332,7 +332,14 @@ const AnalyticsPage: React.FC = () => { val.token1.decimals, ); return ( - + history.push(`/analytics/pair/${val.id}`)} + > ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ tokensFilter: { cursor: 'pointer', display: 'flex', }, panel: { - background: '#1b1d26', + background: palette.grey.A700, borderRadius: 20, padding: 24, [breakpoints.down('xs')]: { diff --git a/src/pages/AnalyticsPage/AnalyticsTokens.tsx b/src/pages/AnalyticsPage/AnalyticsTokens.tsx index 53feb26d2..3426a0c82 100644 --- a/src/pages/AnalyticsPage/AnalyticsTokens.tsx +++ b/src/pages/AnalyticsPage/AnalyticsTokens.tsx @@ -6,14 +6,14 @@ import { useTopTokens, useBookmarkTokens } from 'state/application/hooks'; import { getEthPrice, getTopTokens } from 'utils'; import { Skeleton } from '@material-ui/lab'; -const useStyles = makeStyles(({ breakpoints }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ tokensFilter: { cursor: 'pointer', display: 'flex', margin: '8px 16px 8px 0', }, panel: { - background: '#1b1d26', + background: palette.grey.A700, borderRadius: 20, padding: 24, [breakpoints.down('xs')]: { @@ -57,7 +57,7 @@ const AnalyticsTokens: React.FC = () => { return ( <> - + ({ + panel: { + background: palette.grey.A700, + borderRadius: 20, + padding: 24, + [breakpoints.down('xs')]: { + padding: 12, + }, + }, + breadcrumb: { + display: 'flex', + alignItems: 'center', + color: palette.text.hint, + marginBottom: 50, + '& svg': { + width: 12, + margin: '0 6px', + }, + }, + link: { + cursor: 'pointer', + '&:hover': { + textDecoration: 'underline', + }, + }, + heading1: { + fontSize: 32, + fontWeight: 'bold', + color: palette.text.primary, + lineHeight: 1, + [breakpoints.down('xs')]: { + fontSize: 22, + fontWeight: 600, + }, + }, + heading2: { + fontSize: 32, + lineHeight: 1.2, + fontWeight: 600, + color: palette.text.primary, + marginLeft: 6, + [breakpoints.down('xs')]: { + fontSize: 18, + }, + }, + priceChangeWrapper: { + height: 25, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 16, + padding: '0 8px', + }, + button: { + display: 'flex', + alignItems: 'center', + height: 40, + padding: '0 28px', + borderRadius: 10, + color: palette.text.primary, + cursor: 'pointer', + }, + filledButton: { + background: 'linear-gradient(279deg, rgb(0, 76, 230), rgb(61, 113, 255))', + }, + chartType: { + height: 20, + padding: '0 6px', + borderRadius: 10, + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + }, +})); + +const AnalyticsPairDetails: React.FC = () => { + const classes = useStyles(); + const { palette, breakpoints } = useTheme(); + const isMobile = useMediaQuery(breakpoints.down('xs')); + const history = useHistory(); + const match = useRouteMatch<{ id: string }>(); + const pairAddress = match.params.id; + const [pairData, setPairData] = useState(null); + const [pairChartData, setPairChartData] = useState(null); + const [pairTransactions, setPairTransactions] = useState(null); + const pairTransactionsList = useMemo(() => { + if (pairTransactions) { + const mints = pairTransactions.mints.map((item: any) => { + return { ...item, type: 'Add' }; + }); + const swaps = pairTransactions.mints.map((item: any) => { + return { ...item, type: 'Swap' }; + }); + const burns = pairTransactions.mints.map((item: any) => { + return { ...item, type: 'Remove' }; + }); + return mints.concat(swaps).concat(burns); + } else { + return null; + } + }, [pairTransactions]); + const { chainId } = useActiveWeb3React(); + const currency0 = pairData + ? new Token( + ChainId.MATIC, + getAddress(pairData.token0.id), + pairData.token0.decimals, + ) + : undefined; + const currency1 = pairData + ? new Token( + ChainId.MATIC, + getAddress(pairData.token1.id), + pairData.token1.decimals, + ) + : undefined; + + const token0Rate = + pairData && pairData.reserve0 && pairData.reserve1 + ? Number(pairData.reserve1) / Number(pairData.reserve0) >= 0.0001 + ? (Number(pairData.reserve1) / Number(pairData.reserve0)).toFixed( + Number(pairData.reserve1) / Number(pairData.reserve0) > 1 ? 2 : 4, + ) + : '< 0.0001' + : '-'; + const token1Rate = + pairData && pairData.reserve0 && pairData.reserve1 + ? Number(pairData.reserve0) / Number(pairData.reserve1) >= 0.0001 + ? (Number(pairData.reserve0) / Number(pairData.reserve1)).toFixed( + Number(pairData.reserve0) / Number(pairData.reserve1) > 1 ? 2 : 4, + ) + : '< 0.0001' + : '-'; + const usingUtVolume = + pairData && + pairData.oneDayVolumeUSD === 0 && + !!pairData.oneDayVolumeUntracked; + const fees = + pairData && (pairData.oneDayVolumeUSD || pairData.oneDayVolumeUSD === 0) + ? usingUtVolume + ? (Number(pairData.oneDayVolumeUntracked) * 0.003).toLocaleString() + : (Number(pairData.oneDayVolumeUSD) * 0.003).toLocaleString() + : '-'; + const [chartIndex, setChartIndex] = useState(0); + + useEffect(() => { + async function checkEthPrice() { + const [newPrice] = await getEthPrice(); + const pairInfo = await getBulkPairData([pairAddress], newPrice); + if (pairInfo && pairInfo.length > 0) { + setPairData(pairInfo[0]); + } + } + async function fetchTransctions() { + const transactions = await getPairTransactions(pairAddress); + if (transactions) { + setPairTransactions(transactions); + } + } + checkEthPrice(); + fetchTransctions(); + }, [pairAddress]); + + const chartData = useMemo(() => { + if (pairChartData) { + return pairChartData.map((item: any) => + chartIndex === 0 + ? Number(item.dailyVolumeUSD) + : chartIndex === 1 + ? Number(item.reserveUSD) + : Number(item.dailyVolumeUSD) * 0.003, + ); + } else { + return null; + } + }, [pairChartData, chartIndex]); + + const yAxisValues = useMemo(() => { + if (chartData) { + const minValue = Math.min(...chartData) * 0.99; + const maxValue = Math.max(...chartData) * 1.01; + const step = (maxValue - minValue) / 8; + const values = []; + for (let i = 0; i < 9; i++) { + values.push(maxValue - i * step); + } + return values; + } else { + return undefined; + } + }, [chartData]); + + const chartDates = useMemo(() => { + if (pairChartData) { + const dates: string[] = []; + pairChartData.forEach((value: any, ind: number) => { + const month = moment(Number(value.date) * 1000).format('MMM'); + const monthLastDate = + ind > 0 + ? moment(Number(pairChartData[ind - 1].date) * 1000).format('MMM') + : ''; + if (monthLastDate !== month) { + dates.push(month); + } + const dateStr = moment(Number(value.date) * 1000).format('D'); + if (Number(dateStr) % 7 === 0) { + dates.push(dateStr); + } + }); + return dates; + } else { + return []; + } + }, [pairChartData]); + + const currentData = useMemo( + () => + chartData && chartData.length > 1 + ? chartData[chartData.length - 1] + : null, + [chartData], + ); + const currentPercent = useMemo(() => { + if (chartData && chartData.length > 1) { + const prevData = chartData[chartData.length - 2]; + const nowData = chartData[chartData.length - 1]; + return (nowData - prevData) / prevData; + } else { + return null; + } + }, [chartData]); + + useEffect(() => { + async function fetchPairChartData() { + const chartData = await getPairChartData(pairAddress); + if (chartData && chartData.length > 0) { + setPairChartData(chartData); + } + } + fetchPairChartData(); + }, [pairAddress]); + + return ( + <> + {pairData ? ( + <> + + { + history.push('/analytics'); + }} + > + Analytics + + + { + history.push('/analytics?tabIndex=2'); + }} + > + Pairs + + + + + {pairData.token0.symbol}/{pairData.token1.symbol} + + ({shortenAddress(pairAddress)}) + + + + + + + + + {pairData.token0.symbol} / {pairData.token1.symbol} + + + + + + + + 1 {pairData.token0.symbol} = {token0Rate}{' '} + {pairData.token1.symbol} + + + + + + 1 {pairData.token1.symbol} = {token1Rate}{' '} + {pairData.token0.symbol} + + + + + + { + history.push( + `/pools?currency0=${pairData.token0.id}¤cy1=${pairData.token1.id}`, + ); + }} + > + Add Liquidity + + { + history.push( + `/swap?currency0=${pairData.token0.id}¤cy1=${pairData.token1.id}`, + ); + }} + > + Swap + + + + + + + + + + {chartIndex === 0 + ? 'Volume' + : chartIndex === 1 + ? 'Liquidity' + : 'Price'} + + + {chartData && currentData ? ( + <> + + + $ + {currentData > 100000 + ? formatCompact(currentData) + : currentData.toLocaleString()} + + 0 + ? 'rgba(15, 198, 121, 0.1)' + : Number(currentPercent) < 0 + ? 'rgba(255, 82, 82, 0.1)' + : 'rgba(99, 103, 128, 0.1)' + } + color={ + Number(currentPercent) > 0 + ? 'rgb(15, 198, 121)' + : Number(currentPercent) < 0 + ? 'rgb(255, 82, 82)' + : 'rgb(99, 103, 128)' + } + > + + {Number(currentPercent) < 0.001 && + Number(currentPercent) > 0 + ? '<0.001' + : Number(currentPercent) > -0.001 && + Number(currentPercent) < 0 + ? '>-0.001' + : (Number(currentPercent) > 0 ? '+' : '') + + Number(currentPercent).toLocaleString()} + % + + + + + + {moment().format('MMM DD, YYYY')} + + + + ) : ( + + )} + + + + setChartIndex(0)} + > + Volume + + setChartIndex(1)} + > + Liquidity + + setChartIndex(2)} + > + Fees + + + + + {chartData && pairChartData ? ( + value.date)} + width='100%' + height={240} + categories={chartDates} + /> + ) : ( + + )} + + + + + + + + + TOTAL TOKENS LOCKED + + + + + + + {pairData.token0.symbol} : + + + + {Number(pairData.reserve0).toLocaleString()} + + + + + + + {pairData.token1.symbol} : + + + + {Number(pairData.reserve1).toLocaleString()} + + + + + + + 7d Trading Vol + + + ${pairData.oneWeekVolumeUSD.toLocaleString()} + + + + + 24h FEES + + + ${fees} + + + + + + TOTAL LIQUIDITY + + + $ + {Number( + pairData.reserveUSD + ? pairData.reserveUSD + : pairData.trackedReserveUSD, + ).toLocaleString()} + + + + 24h Trading Vol + + + ${pairData.oneDayVolumeUSD.toLocaleString()} + + + + + Contract Address + + + {chainId ? ( + + {shortenAddress(pairData.id)} + + ) : ( + shortenAddress(pairData.id) + )} + + + + + + + + + + Transactions + + + {pairTransactionsList ? ( + + ) : ( + + )} + + + ) : ( + + )} + + ); +}; + +export default AnalyticsPairDetails; diff --git a/src/pages/AnalyticsPairDetails/index.ts b/src/pages/AnalyticsPairDetails/index.ts new file mode 100644 index 000000000..9d23d20b0 --- /dev/null +++ b/src/pages/AnalyticsPairDetails/index.ts @@ -0,0 +1 @@ +export { default } from './AnalyticsPairDetails'; diff --git a/src/pages/AnalyticsTokenDetails/AnalyticsTokenDetails.tsx b/src/pages/AnalyticsTokenDetails/AnalyticsTokenDetails.tsx index 844f7913d..201b17f67 100644 --- a/src/pages/AnalyticsTokenDetails/AnalyticsTokenDetails.tsx +++ b/src/pages/AnalyticsTokenDetails/AnalyticsTokenDetails.tsx @@ -10,11 +10,7 @@ import cx from 'classnames'; import { shortenAddress, getEtherscanLink, formatCompact } from 'utils'; import { useActiveWeb3React } from 'hooks'; import { CurrencyLogo, PairTable, AreaChart } from 'components'; -import { - useTokenPairs, - useBookmarkTokens, - useTokenChartData, -} from 'state/application/hooks'; +import { useBookmarkTokens } from 'state/application/hooks'; import { getTokenInfo, getEthPrice, @@ -28,7 +24,7 @@ import { getAddress } from '@ethersproject/address'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ panel: { - background: '#1b1d26', + background: palette.grey.A700, borderRadius: 20, padding: 24, [breakpoints.down('xs')]: { @@ -113,10 +109,14 @@ const AnalyticsTokenDetails: React.FC = () => { const currency = token ? new Token(ChainId.MATIC, getAddress(token.id), token.decimals) : undefined; - const { tokenChartData, updateTokenChartData } = useTokenChartData(); + const [tokenChartData, updateTokenChartData] = useState(null); const [chartIndex, setChartIndex] = useState(0); - const { tokenPairs, updateTokenPairs } = useTokenPairs(); - const { bookmarkTokens } = useBookmarkTokens(); + const [tokenPairs, updateTokenPairs] = useState(null); + const { + bookmarkTokens, + addBookmarkToken, + removeBookmarkToken, + } = useBookmarkTokens(); useEffect(() => { async function checkEthPrice() { @@ -211,7 +211,6 @@ const AnalyticsTokenDetails: React.FC = () => { } async function fetchTokenPairs() { const [newPrice] = await getEthPrice(); - updateTokenPairs({ data: null }); const tokenPairs = await getTokenPairs2(tokenAddress); const formattedPairs = tokenPairs ? tokenPairs.map((pair: any) => { @@ -220,7 +219,7 @@ const AnalyticsTokenDetails: React.FC = () => { : []; const pairData = await getBulkPairData(formattedPairs, newPrice); if (pairData) { - updateTokenPairs({ data: pairData }); + updateTokenPairs(pairData); } } fetchTokenPairs(); @@ -277,9 +276,11 @@ const AnalyticsTokenDetails: React.FC = () => { {bookmarkTokens.includes(token.id) ? ( - + removeBookmarkToken(token.id)} + /> ) : ( - + addBookmarkToken(token.id)} /> )} @@ -417,7 +418,9 @@ const AnalyticsTokenDetails: React.FC = () => { setChartIndex(0)} > @@ -425,14 +428,18 @@ const AnalyticsTokenDetails: React.FC = () => { setChartIndex(1)} > Liquidity setChartIndex(2)} > diff --git a/src/pages/index.ts b/src/pages/index.ts index 28b559eea..a086e6e75 100755 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -5,3 +5,4 @@ export { default as FarmPage } from './FarmPage'; export { default as DragonPage } from './DragonPage'; export { default as AnalyticsPage } from './AnalyticsPage'; export { default as AnalyticsTokenDetails } from './AnalyticsTokenDetails'; +export { default as AnalyticsPairDetails } from './AnalyticsPairDetails'; diff --git a/src/state/application/actions.ts b/src/state/application/actions.ts index d34a3f14d..42fa07056 100755 --- a/src/state/application/actions.ts +++ b/src/state/application/actions.ts @@ -100,7 +100,3 @@ export const updateBookmarkPairs = createAction( ); export const updateTopPairs = createAction('application/updateTopPairs'); - -export const updateTokenChartData = createAction( - 'application/updateTokenChartData', -); diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts index e82d68e6a..081e4d27f 100755 --- a/src/state/application/hooks.ts +++ b/src/state/application/hooks.ts @@ -21,7 +21,6 @@ import { updateTopPairs, addBookMarkPair, removeBookmarkPair, - updateTokenChartData, } from './actions'; export function useBlockNumber(): number | undefined { @@ -315,20 +314,3 @@ export function useTopPairs(): { ); return { topPairs, updateTopPairs: _updateTopPairs }; } - -export function useTokenChartData(): { - tokenChartData: any; - updateTokenChartData: (data: any) => void; -} { - const tokenChartData = useSelector( - (state: AppState) => state.application.tokenChartData, - ); - const dispatch = useDispatch(); - const _updateTokenChartData = useCallback( - (data) => { - dispatch(updateTokenChartData(data)); - }, - [dispatch], - ); - return { tokenChartData, updateTokenChartData: _updateTokenChartData }; -} diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts index 80303d51d..4099c7960 100755 --- a/src/state/application/reducer.ts +++ b/src/state/application/reducer.ts @@ -20,7 +20,6 @@ import { addBookMarkPair, removeBookmarkPair, updateBookmarkPairs, - updateTokenChartData, } from './actions'; type PopupList = Array<{ @@ -179,8 +178,5 @@ export default createReducer(initialState, (builder) => }) .addCase(updateBookmarkPairs, (state, { payload }) => { state.bookmarkedPairs = payload; - }) - .addCase(updateTokenChartData, (state, { payload }) => { - state.tokenChartData = payload; }), ); diff --git a/src/state/lists/reducer.test.ts b/src/state/lists/reducer.test.ts index 3d1e462fe..007ec9b3a 100755 --- a/src/state/lists/reducer.test.ts +++ b/src/state/lists/reducer.test.ts @@ -391,7 +391,7 @@ describe('list reducer', () => { expect(store.getState()).toEqual({ byUrl: {}, selectedListUrl: - 'https://unpkg.com/quickswap-default-token-list@1.2.17/build/quickswap-default.tokenlist.json', + 'https://unpkg.com/quickswap-default-token-list@1.2.18/build/quickswap-default.tokenlist.json', }); }); }); diff --git a/src/state/stake/hooks.ts b/src/state/stake/hooks.ts index ef0b9b6d2..6a38468b3 100755 --- a/src/state/stake/hooks.ts +++ b/src/state/stake/hooks.ts @@ -267,6 +267,16 @@ export const SYRUP_REWARDS_INFO: { }[]; } = { [ChainId.MATIC]: [ + { + token: SNE, + stakingRewardAddress: '0xf6Fe46F0001FDeFAde6b5E08635ED303f2E0a3aA', + ended: false, + lp: '', + name: '', + baseToken: USDC, + rate: 160000, + ending: 1643302635, + }, { token: POLYPUG, stakingRewardAddress: '0xA206A97b30343a0802553dB48d71af349AbF563A', @@ -347,16 +357,6 @@ export const SYRUP_REWARDS_INFO: { rate: 40610.66, ending: 1644090690, }, - { - token: UFI, - stakingRewardAddress: '0xE707bB8513873c2360811F01BfBd0e9EBFd96b0D', - ended: false, - lp: '', - name: '', - baseToken: MATIC, - rate: 18315, - ending: 1640485140, - }, { token: WSG, stakingRewardAddress: '0x2b91d985AEb645cc580E35BdF52DF2694e742ADF', @@ -483,6 +483,16 @@ export const OLD_SYRUP_REWARDS_INFO: { }[]; } = { [ChainId.MATIC]: [ + { + token: UFI, + stakingRewardAddress: '0xE707bB8513873c2360811F01BfBd0e9EBFd96b0D', + ended: true, + lp: '', + name: '', + baseToken: MATIC, + rate: 18315, + ending: 1640485140, + }, { token: UCO, stakingRewardAddress: '0xC328d6eC46d11a6ABdA3C02434861beA14739E1f', diff --git a/src/theme.ts b/src/theme.ts index 42fcf58cd..5e440c8ad 100755 --- a/src/theme.ts +++ b/src/theme.ts @@ -22,6 +22,8 @@ const textDisabled = '#626680'; const textHint = '#636780'; const bgColor = '#12131a'; const bgPalette = '#1b1e29'; +const greyBgLight = '#3e4252'; +const greyBg = '#1b1d26'; const successMain = '#0fc679'; const successDark = '#1DB2D5'; @@ -60,6 +62,10 @@ export const mainTheme = responsiveFontSizes( main: primary, dark: primaryDark, }, + grey: { + A400: greyBgLight, + A700: greyBg, + }, secondary: { main: secondary, light: secondaryLight, diff --git a/src/utils/index.ts b/src/utils/index.ts index b030d886c..8832225fd 100755 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,7 +4,7 @@ import { Contract } from '@ethersproject/contracts'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import weekOfYear from 'dayjs/plugin/weekOfYear'; -import { blockClient, client } from 'apollo/client'; +import { blockClient, client, txClient } from 'apollo/client'; import { GET_BLOCK, GLOBAL_DATA, @@ -17,6 +17,7 @@ import { TOKEN_DATA, TOKEN_DATA1, TOKEN_DATA2, + PAIR_CHART, PAIR_DATA, PAIRS_BULK1, PAIRS_HISTORICAL_BULK, @@ -26,6 +27,8 @@ import { ALL_TOKENS, TOKEN_INFO, TOKEN_INFO_OLD, + FILTERED_TRANSACTIONS, + HOURLY_PAIR_RATES, } from 'apollo/queries'; import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'; import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'; @@ -720,6 +723,26 @@ export const getIntervalTokenData = async ( } }; +export const getPairTransactions = async (pairAddress: string) => { + try { + const result = await txClient.query({ + query: FILTERED_TRANSACTIONS, + variables: { + allPairs: [pairAddress], + }, + fetchPolicy: 'no-cache', + }); + return { + mints: result.data.mints, + burns: result.data.burns, + swaps: result.data.swaps, + }; + } catch (e) { + console.log(e); + return null; + } +}; + export const getTokenChartData = async (tokenAddress: string) => { let data: any[] = []; const utcEndTime = dayjs.utc(); @@ -788,6 +811,154 @@ export const getTokenChartData = async (tokenAddress: string) => { return data; }; +export const getPairChartData = async (pairAddress: string) => { + let data: any[] = []; + const utcEndTime = dayjs.utc(); + const utcStartTime = utcEndTime.subtract(2, 'month'); + const startTime = utcStartTime.unix() - 1; + try { + let allFound = false; + let skip = 0; + while (!allFound) { + const result = await client.query({ + query: PAIR_CHART, + variables: { + startTime: startTime, + pairAddress: pairAddress, + skip, + }, + fetchPolicy: 'cache-first', + }); + skip += 1000; + data = data.concat(result.data.pairDayDatas); + if (result.data.pairDayDatas.length < 1000) { + allFound = true; + } + } + + const dayIndexSet = new Set(); + const dayIndexArray: any[] = []; + const oneDay = 24 * 60 * 60; + data.forEach((dayData, i) => { + // add the day index to the set of days + dayIndexSet.add((data[i].date / oneDay).toFixed(0)); + dayIndexArray.push(data[i]); + dayData.dailyVolumeUSD = parseFloat(dayData.dailyVolumeUSD); + dayData.reserveUSD = parseFloat(dayData.reserveUSD); + }); + + if (data[0]) { + // fill in empty days + let timestamp = data[0].date ? data[0].date : startTime; + let latestLiquidityUSD = data[0].reserveUSD; + let index = 1; + while (timestamp < utcEndTime.unix() - oneDay) { + const nextDay = timestamp + oneDay; + const currentDayIndex = (nextDay / oneDay).toFixed(0); + if (!dayIndexSet.has(currentDayIndex)) { + data.push({ + date: nextDay, + dayString: nextDay, + dailyVolumeUSD: 0, + reserveUSD: latestLiquidityUSD, + }); + } else { + latestLiquidityUSD = dayIndexArray[index].reserveUSD; + index = index + 1; + } + timestamp = nextDay; + } + } + + data = data.sort((a, b) => (parseInt(a.date) > parseInt(b.date) ? 1 : -1)); + } catch (e) { + console.log(e); + } + + return data; +}; + +export const getHourlyRateData = async ( + pairAddress: string, + latestBlock: number, +) => { + try { + const utcEndTime = dayjs.utc(); + const utcStartTime = utcEndTime.subtract(2, 'month'); + const startTime = utcStartTime.unix() - 1; + let time = startTime; + + // create an array of hour start times until we reach current hour + const timestamps = []; + while (time <= utcEndTime.unix()) { + timestamps.push(time); + time += 3600 * 24; + } + + // backout if invalid timestamp format + if (timestamps.length === 0) { + return []; + } + + // once you have all the timestamps, get the blocks for each timestamp in a bulk query + let blocks; + + blocks = await getBlocksFromTimestamps(timestamps, 100); + + // catch failing case + if (!blocks || blocks?.length === 0) { + return []; + } + + if (latestBlock) { + blocks = blocks.filter((b) => { + return parseFloat(b.number) <= latestBlock; + }); + } + + const result = await splitQuery( + HOURLY_PAIR_RATES, + client, + [pairAddress], + blocks, + 100, + ); + + // format token ETH price results + const values = []; + for (const row in result) { + const timestamp = row.split('t')[1]; + if (timestamp) { + values.push({ + timestamp, + rate0: parseFloat(result[row]?.token0Price), + rate1: parseFloat(result[row]?.token1Price), + }); + } + } + + const formattedHistoryRate0 = []; + const formattedHistoryRate1 = []; + + // for each hour, construct the open and close price + for (let i = 0; i < values.length - 1; i++) { + formattedHistoryRate0.push({ + timestamp: values[i].timestamp, + rate: values[i].rate0, + }); + formattedHistoryRate1.push({ + timestamp: values[i].timestamp, + rate: values[i].rate1, + }); + } + + return [formattedHistoryRate0, formattedHistoryRate1]; + } catch (e) { + console.log(e); + return [[], []]; + } +}; + export const getBulkPairData: ( pairList: any, ethPrice: any,