diff --git a/src/components/Tokens/TokenDetails/ChartSection.tsx b/src/components/Tokens/TokenDetails/ChartSection.tsx index 2e1240f00..c424335e9 100644 --- a/src/components/Tokens/TokenDetails/ChartSection.tsx +++ b/src/components/Tokens/TokenDetails/ChartSection.tsx @@ -9,16 +9,72 @@ import { startTransition, Suspense, useMemo } from 'react' import { PriceChart } from './PriceChart' import TimePeriodSelector from './TimeSelector' +import { ti } from 'make-plural' -function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined { +function fillMissingTimestamps(priceHistory: any, interval: number, totalTime: number) { + if (!Array.isArray(priceHistory) || priceHistory.length === 0) { + return priceHistory; // Return the original array if invalid or empty + } + + // Sort the array by timestamp to ensure proper filling + priceHistory.sort((a, b) => a.timestamp - b.timestamp); + const currentTimestamp = Date.now() / 1000 + const filledHistory = []; + + for (let i = 0; i < priceHistory.length - 1; i++) { + const current = priceHistory[i]; + const next = priceHistory[i + 1]; + + // Add the current object to the filled array + if(next.timestamp + totalTime >= currentTimestamp) { + if(current.timestamp + totalTime >= currentTimestamp) filledHistory.push(current); + + // Fill missing timestamps + let timestamp = current.timestamp + interval; + while (timestamp < next.timestamp) { + if(timestamp + totalTime >= currentTimestamp) + filledHistory.push({ + ...current, // Copy previous values + timestamp, // Update timestamp + }); + timestamp += interval; + } + } + } + + // Add the last object to the filled array + filledHistory.push(priceHistory[priceHistory.length - 1]); + return filledHistory; +} + +function getTimeInterval(timePeriod:any) { + switch(timePeriod) { + case 0: + return [300, 3600]; + case 1: + return [600, 86400]; + case 2: + return [3600, 604800]; + case 3: + return [3600, 2592000]; + case 4: + return [86400, 31536000]; + } + return [86400, 0]; +} + +function usePriceHistory(tokenPriceData: TokenPriceQuery, timePeriod: any): PricePoint[] | undefined { + + const [timeInterval, totalTime] = getTimeInterval(timePeriod); // Appends the current price to the end of the priceHistory array const priceHistory = useMemo(() => { const market = tokenPriceData.token?.market const priceHistory = market?.priceHistory?.filter(isPricePoint) + // const priceHistory = market?.priceHistory?.filter(isPricePoint) const currentPrice = market?.price?.value if (Array.isArray(priceHistory) && currentPrice !== undefined) { const timestamp = Date.now() / 1000 - return [...priceHistory, { timestamp, value: currentPrice }] + return fillMissingTimestamps([...priceHistory, { timestamp, value: currentPrice }], timeInterval, totalTime) } return priceHistory }, [tokenPriceData]) @@ -55,7 +111,7 @@ function Chart({ }) { // Initializes time period to global & maintain separate time period for subsequent changes const timePeriod = useAtomValue(pageTimePeriodAtom) - const prices = usePriceHistory(tokenPriceQuery) + const prices = usePriceHistory(tokenPriceQuery, timePeriod) return ( @@ -70,4 +126,4 @@ function Chart({ /> ) -} +} \ No newline at end of file diff --git a/src/pages/TokenDetails/index.tsx b/src/pages/TokenDetails/index.tsx index 159ec26ed..18b165399 100644 --- a/src/pages/TokenDetails/index.tsx +++ b/src/pages/TokenDetails/index.tsx @@ -15,7 +15,7 @@ import { Chain, TokenQuery, Currency, TokenQueryData } from 'graphql/data/Token' import { TokenPriceQuery } from 'graphql/data/TokenPrice' const GetTokenInfo = gql` -query GetTokenInfo($tokenAddress: String!) { +query GetTokenInfo($tokenAddress: String!, $days: Int, $hours: Int) { bundles(first: 10) { ethPriceUSD } @@ -29,7 +29,7 @@ query GetTokenInfo($tokenAddress: String!) { derivedETH # Get daily data for more detailed metrics - tokenDayData(first: 365, orderBy: date, orderDirection: asc) { + tokenDayData(first: $days, orderBy: date, orderDirection: desc) { id date priceUSD # The price of the token in USD for the day @@ -37,6 +37,14 @@ query GetTokenInfo($tokenAddress: String!) { volumeUSD } + tokenHourData(first: $hours, orderBy: periodStartUnix, orderDirection: desc) { + id + periodStartUnix + priceUSD # The price of the token in USD for the day + totalValueLockedUSD + volumeUSD + } + # Get all pools involving this token for liquidity details whitelistPools { id @@ -57,11 +65,29 @@ query GetTokenInfo($tokenAddress: String!) { export const pageTimePeriodAtom = atomWithStorage('tokenDetailsTimePeriod', TimePeriod.DAY) +function getQueryParams(timePeriod:any) { + switch(timePeriod) { + case 0: + return [2, 2]; + case 1: + return [2, 24]; + case 2: + return [2, 168]; + case 3: + return [30, 2]; + case 4: + return [365, 2]; + } + return [2, 2]; +} + + export default function TokenDetailsPage() { const { tokenAddress, chainName } = useParams<{ tokenAddress: string; chainName?: string }>() const chain = validateUrlChainParam(chainName) const isNative = tokenAddress === NATIVE_CHAIN_ID const [timePeriod, setTimePeriod] = useAtom(pageTimePeriodAtom) + const [totalDays, totalHours] = getQueryParams(timePeriod); const [address, duration] = useMemo( /* tokenAddress will always be defined in the path for for this page to render, but useParams will always return optional arguments; nullish coalescing operator is present here to appease typechecker */ @@ -82,11 +108,12 @@ export default function TokenDetailsPage() { duration, }, }) - const { data: luxData, loading: luxLoading } = useQuery(GetTokenInfo, { client: luxClient, variables: { tokenAddress: address, + days: totalDays, + hours: totalHours, }, }) if (luxLoading) { @@ -152,28 +179,29 @@ const transformedTokenDetail: TokenQuery = tokenPriceQuery??{ }; const ethPriceUSD = luxData?.bundles[0]?.ethPriceUSD; const ethPrice = luxData?.token?.derivedETH; +const tokenDayData = luxData?.token?.tokenDayData.filter((data: any) => parseFloat(data.priceUSD) !== 0).map((data: any) => ({ + id: `VGltZXN0YW1wZWRBbW91bnQ6MV8x${data.date}_VVNE`, // Encoded version of the timestamped amount + timestamp: data.date, + value: parseFloat(data.priceUSD), +})) +const tokenHourData = luxData?.token?.tokenHourData.filter((data: any) => parseFloat(data.priceUSD) !== 0).map((data: any) => ({ + id: `VGltZXN0YW1wZWRBbW91bnQ6MV8x${data.periodStartUnix}_VVNE`, // Encoded version of the timestamped amount + timestamp: data.periodStartUnix, + value: parseFloat(data.priceUSD), +})) const transformedTokenPriceHistory:TokenPriceQuery = { token: { - __typename: "Token", id: "VG9rZW46RVRIRVJFVU1fMHhhMGI4Njk5MWM2MjE4YjM2YzFkMTlkNGEyZTllYjBjZTM2MDZlYjQ4", // Encoded version of the token ID address: luxData?.token?.id, chain: Chain.Lux, market: { - __typename: "TokenMarket", id: "VG9rZW5NYXJrZXQ6RVRIRVJFVU1fMHhhMGI4Njk5MWM2MjE4YjM2YzFkMTlkNGEyZTllYjBjZTM2MDZlYjQ4X1VTRA==", // Encoded ID for the market price: { - __typename: "Amount", id: "QW1vdW50OjFfVVNE", // Encoded amount ID value: parseFloat(ethPriceUSD) * parseFloat(ethPrice), }, - priceHistory: luxData?.token?.tokenDayData.filter((data: any) => parseFloat(data.priceUSD) !== 0) - .map((data: any) => ({ - __typename: "TimestampedAmount", - id: `VGltZXN0YW1wZWRBbW91bnQ6MV8x${data.date}_VVNE`, // Encoded version of the timestamped amount - timestamp: data.date, - value: parseFloat(data.priceUSD), - })), + priceHistory: timePeriod < 3 ? tokenHourData : tokenDayData, }, }, }; @@ -200,4 +228,4 @@ const renderTokenDetail = (tokenAddress: any, chain: any, tokenQuery: any) => ( } if (!tokenQuery) return return renderTokenDetail(tokenAddress, chain, tokenQuery) -} +} \ No newline at end of file