diff --git a/src/api/categories/protocols/index.ts b/src/api/categories/protocols/index.ts index 1da49fb67..9110b5728 100644 --- a/src/api/categories/protocols/index.ts +++ b/src/api/categories/protocols/index.ts @@ -26,6 +26,7 @@ import { } from '~/api/categories/adaptors' import { getPeggedAssets } from '../stablecoins' import { formatProtocolsList } from '~/hooks/data/defi' +import { stringify } from 'querystring' export const getProtocolsRaw = () => fetch(PROTOCOLS_API).then((r) => r.json()) @@ -735,3 +736,99 @@ export async function getLSDPageData() { } } } + +export async function getROIData() { + const dateNow = Math.floor(Date.now() / 1000) + const secPerDay = 60 * 60 * 24 + const date1d = dateNow - secPerDay + const date7d = dateNow - secPerDay * 7 + const date30d = dateNow - secPerDay * 30 + + const dates = [dateNow, date1d, date7d, date30d] + + const url = 'https://coins.llama.fi/batchHistorical' + + // top 1k cg tokens + const tokenList = (await fetch('https://api.llama.fi/sortedTokenlist?a').then((r) => r.json())).map( + (t) => `coingecko:${t.id}` + ) + + // pull prices + const size = 80 + const pages = Math.ceil(tokenList.length / size) + + const prices = await Promise.all( + [...Array(pages)].map(async (p, i) => { + const tokens = tokenList.slice(i * size, size * (i + 1)) + + const query = tokens.reduce((acc, t) => { + acc[t] = dates + return acc + }, {}) + + const queryString = stringify({ + coins: JSON.stringify(query), + searchWidth: 600 + }) + + return (await fetch(`${url}?${queryString}`).then((r) => r.json())).coins + }) + ) + // pull btc and eth separately + const queryStringBtcEth = stringify({ + coins: JSON.stringify({ 'coingecko:bitcoin': dates, 'coingecko:ethereum': dates }), + searchWidth: 600 + }) + const pricesBtcEth = (await fetch(`${url}?${queryStringBtcEth}`).then((r) => r.json())).coins + + // flatten + const data = { ...pricesBtcEth, ...Object.assign({}, ...prices.flat()) } + + const [btc, eth] = [data['coingecko:bitcoin']?.prices, data['coingecko:ethereum']?.prices].map((p) => + p?.sort((a, b) => b.timestamp - a.timestamp) + ) + const [btcNow, ethNow] = [btc[0]?.price, eth[0]?.price] + + const res = Object.values(data).map(({ prices, symbol }) => { + const sortedPrices = prices.sort((a, b) => b.timestamp - a.timestamp) + const priceNow = sortedPrices[0]?.price + + const [usdDeltas, btcDeltas, ethDeltas] = [[], [], []] + + for (let i = 0; i < sortedPrices.length - 1; i++) { + const priceOld = sortedPrices[i + 1]?.price + const usdDelta = ((priceNow - priceOld) / priceOld) * 100 + + const btcRatioOld = priceOld / btc[i + 1]?.price + const btcRatioNow = priceNow / btcNow + const btcDelta = ((btcRatioNow - btcRatioOld) / btcRatioOld) * 100 + + const ethRatioOld = priceOld / eth[i + 1]?.price + const ethRatioNow = priceNow / ethNow + const ethDelta = ((ethRatioNow - ethRatioOld) / ethRatioOld) * 100 + + usdDeltas.push(usdDelta) + btcDeltas.push(btcDelta) + ethDeltas.push(ethDelta) + } + + return { + symbol, + usd1d: usdDeltas[0] ?? null, + usd7d: usdDeltas[1] ?? null, + usd30d: usdDeltas[2] ?? null, + btc1d: btcDeltas[0] ?? null, + btc7d: btcDeltas[1] ?? null, + btc30d: btcDeltas[2] ?? null, + eth1d: ethDeltas[0] ?? null, + eth7d: ethDeltas[1] ?? null, + eth30d: ethDeltas[2] ?? null + } + }) + + return { + props: { + priceData: res + } + } +} diff --git a/src/components/ECharts/BarChart/index.tsx b/src/components/ECharts/BarChart/index.tsx index e082beff9..26eefb11c 100644 --- a/src/components/ECharts/BarChart/index.tsx +++ b/src/components/ECharts/BarChart/index.tsx @@ -65,12 +65,12 @@ export default function BarChart({ itemStyle: { color: chartColor }, - data: [] + data: chartData.map((i) => i[1]) } - chartData.forEach(([date, value]) => { - series.data.push([getUtcDateObject(date), value]) - }) + // chartData.forEach(([date, value]) => { + // series.data.push([getUtcDateObject(date), value]) + // }) return series } else { @@ -100,11 +100,12 @@ export default function BarChart({ } }) - chartData.forEach(({ date, ...item }) => { - selectedStacks.forEach((stack) => { - series.find((t) => t.name === stack)?.data.push([getUtcDateObject(date), item[stack] || 0]) - }) - }) + // chartData.forEach(({ date, ...item }) => { + // selectedStacks.forEach((stack) => { + // series.find((t) => t.name === stack)?.data.push([getUtcDateObject(date), item[stack] || 0]) + // // series.find((t) => t.name === stack)?.data.push([item[stack] || 0]) + // }) + // }) return series } diff --git a/src/components/Table/Defi/columns.tsx b/src/components/Table/Defi/columns.tsx index 8da262cc3..da648dd1a 100644 --- a/src/components/Table/Defi/columns.tsx +++ b/src/components/Table/Defi/columns.tsx @@ -19,7 +19,7 @@ import { } from '~/utils' import { AccordionButton, Name } from '../shared' import { formatColumnOrder } from '../utils' -import type { ICategoryRow, IChainsRow, IForksRow, IOraclesRow, ILSDRow } from './types' +import type { ICategoryRow, IChainsRow, IForksRow, IOraclesRow, ILSDRow, IROIRow } from './types' export const oraclesColumn: ColumnDef[] = [ { @@ -782,6 +782,106 @@ export const LSDColumn: ColumnDef[] = [ } ] +export const ROIColumn: ColumnDef[] = [ + { + header: 'Coin', + accessorKey: 'symbol', + enableSorting: false, + cell: ({ getValue, row, table }) => { + const index = row.depth === 0 ? table.getSortedRowModel().rows.findIndex((x) => x.id === row.id) : row.index + + return ( + + {index + 1} + {getValue()} + + ) + }, + size: 280 + }, + { + header: '1d USD', + accessorKey: 'usd1d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '7d USD', + accessorKey: 'usd7d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '30d USD', + accessorKey: 'usd30d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '1d ETH', + accessorKey: 'eth1d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '7d ETH', + accessorKey: 'eth7d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '30d ETH', + accessorKey: 'eth30d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '1d BTC', + accessorKey: 'btc1d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '7d BTC', + accessorKey: 'btc7d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + }, + { + header: '30d BTC', + accessorKey: 'btc30d', + cell: ({ getValue }) => <>{formattedPercent(getValue())}, + meta: { + align: 'end' + }, + size: 110 + } +] + function formatCexInflows(value) { let x = value let isNegative = false diff --git a/src/components/Table/Defi/index.tsx b/src/components/Table/Defi/index.tsx index 410786310..6a8d76686 100644 --- a/src/components/Table/Defi/index.tsx +++ b/src/components/Table/Defi/index.tsx @@ -16,9 +16,10 @@ import { chainsTableColumnOrders, forksColumn, oraclesColumn, - LSDColumn + LSDColumn, + ROIColumn } from './columns' -import type { IOraclesRow, IForksRow, ICategoryRow, IChainsRow, ILSDRow } from './types' +import type { IOraclesRow, IForksRow, ICategoryRow, IChainsRow, ILSDRow, IROIRow } from './types' import useWindowSize from '~/hooks/useWindowSize' export default function DefiProtocolsTable({ data, columns }) { @@ -53,6 +54,7 @@ export const ProtocolsCategoriesTable = ({ data }: { data: Array } ) export const LSDTable = ({ data }: { data: Array }) => +export const ROITable = ({ data }: { data: Array }) => export function DefiChainsTable({ data }) { const [sorting, setSorting] = React.useState([]) diff --git a/src/components/Table/Defi/types.ts b/src/components/Table/Defi/types.ts index 4a5e0d4dd..fc14af2f2 100644 --- a/src/components/Table/Defi/types.ts +++ b/src/components/Table/Defi/types.ts @@ -44,3 +44,11 @@ export interface ILSDRow { logo: string mcap: number } + +export interface IROIRow { + symbol: string + usd7d: number + btc7d: number + eth7d: number + logo: string +} diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 5191d3a64..dc77357ea 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -7,7 +7,7 @@ export { NftChainsTable, NftMarketplacesTable } from './Nfts/ChainsAndMarketplaces' -export { OraclesTable, ForksTable, ProtocolsCategoriesTable, LSDTable } from './Defi' +export { OraclesTable, ForksTable, ProtocolsCategoriesTable, LSDTable, ROITable } from './Defi' export { FeesTable } from './Fees' export { DexsTable, VolumeByChainsTable } from './Dexs' export { OverviewTable } from './Adaptors' diff --git a/src/pages/prices.tsx b/src/pages/prices.tsx new file mode 100644 index 000000000..0a28badfb --- /dev/null +++ b/src/pages/prices.tsx @@ -0,0 +1,51 @@ +import * as React from 'react' +import dynamic from 'next/dynamic' +import Layout from '~/layout' +import { ROITable } from '~/components/Table' +import { ProtocolsChainsSearch } from '~/components/Search' +import { maxAgeForNext } from '~/api' +import { getROIData } from '~/api/categories/protocols' + +import type { IBarChartProps } from '~/components/ECharts/types' + +const BarChart = dynamic(() => import('~/components/ECharts/BarChart'), { + ssr: false +}) as React.FC + +export async function getStaticProps() { + const data = await getROIData() + + return { + ...data, + revalidate: maxAgeForNext([22]) + } +} + +const PageView = ({ priceData }) => { + const chartData = priceData.map((p) => [p.symbol, p?.usd1d]).sort((a, b) => b[1] - a[1]) + + const options = { + xAxis: { + type: 'category', + data: chartData.map((i) => i[0]) + } + } + + return ( + <> + + + + + + + ) +} + +export default function PriceROI(props) { + return ( + + + + ) +}