diff --git a/app/inspect/lp-leaderboard/page.tsx b/app/inspect/lp-leaderboard/page.tsx new file mode 100644 index 00000000..d960ea9f --- /dev/null +++ b/app/inspect/lp-leaderboard/page.tsx @@ -0,0 +1,6 @@ +import { LeaderboardPage } from '@/pages/leaderboard'; + +// Informs NextJs to not prerender the page in build time +export const dynamic = 'force-dynamic'; + +export default LeaderboardPage; diff --git a/src/pages/leaderboard/api/query-leaderboard.ts b/src/pages/leaderboard/api/query-leaderboard.ts new file mode 100644 index 00000000..d059c07b --- /dev/null +++ b/src/pages/leaderboard/api/query-leaderboard.ts @@ -0,0 +1,27 @@ +import { sql } from 'kysely'; +import { pindexerDb } from '@/shared/database/client'; + +export const queryLeaderboard = async (limit: number) => { + const results = await pindexerDb + .selectFrom('dex_ex_position_executions') + .select((exp) => ([ + // 'context_asset_end', + // 'context_asset_start', + // exp.fn.sum(exp.ref('delta_1'), exp.ref('lambda_1')).as('volume1'), + 'position_id', + sql`sum(${exp.ref('delta_1')} + ${exp.ref('lambda_1')})`.as('volume1'), + sql`sum(${exp.ref('delta_2')} + ${exp.ref('lambda_2')})`.as('volume2'), + sql`sum(${exp.ref('fee_1')})`.as('fees1'), + sql`sum(${exp.ref('fee_2')})`.as('fees2'), + sql`CAST(count(*) AS INTEGER)`.as('executionCount'), + ])) + .groupBy(['position_id']) + .orderBy('executionCount', 'desc') + .limit(limit) + .execute(); + + return results; +}; + +type FromPromise = T extends Promise<(infer U)[]> ? U : T; +export type LeaderboardData = FromPromise>; diff --git a/src/pages/leaderboard/index.ts b/src/pages/leaderboard/index.ts new file mode 100644 index 00000000..2bd750a6 --- /dev/null +++ b/src/pages/leaderboard/index.ts @@ -0,0 +1 @@ +export { LeaderboardPage } from './ui/page'; diff --git a/src/pages/leaderboard/ui/page.tsx b/src/pages/leaderboard/ui/page.tsx new file mode 100644 index 00000000..3b43f817 --- /dev/null +++ b/src/pages/leaderboard/ui/page.tsx @@ -0,0 +1,24 @@ +'use server'; + +import { queryLeaderboard } from '../api/query-leaderboard'; +import { LeaderboardTable } from './table'; +import { serialize } from '@/shared/utils/serializer'; + +export interface LeaderboardPageProps { + searchParams: Promise<{ + limit?: string; + }> +} + +export const LeaderboardPage = async ({ searchParams }: LeaderboardPageProps) => { + const { limit } = await searchParams; + console.log('LOADING', limit); + const data = await queryLeaderboard(limit ? parseInt(limit) : 30); + + return ( +
+

Leaderboard

+ +
+ ); +}; diff --git a/src/pages/leaderboard/ui/table.tsx b/src/pages/leaderboard/ui/table.tsx new file mode 100644 index 00000000..b05d7e59 --- /dev/null +++ b/src/pages/leaderboard/ui/table.tsx @@ -0,0 +1,60 @@ +'use client'; + +import cn from 'clsx'; +import { TableCell } from '@penumbra-zone/ui/TableCell'; +import type { LeaderboardData } from '../api/query-leaderboard'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { PagePath } from '@/shared/const/pages'; +import { Serialized, deserialize } from '@/shared/utils/serializer'; + +export interface LeaderboardTableProps { + data: Serialized; +} + +export const LeaderboardTable = ({ data }: LeaderboardTableProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + const positions = deserialize(data); + + const reload = () => { + const search = new URLSearchParams(searchParams ?? undefined); + search.set('limit', '5'); + router.push(`${PagePath.LpLeaderboard}?${search.toString()}`); + }; + + return ( +
+
+ +
+
+ Time + Executions + Fees + Volume +
+ + {positions.map((position, index) => ( +
+ + ABC + + + {position.executionCount} + + + {position.fees2} + + + {position.volume1} + +
+ ))} +
+ ); +} diff --git a/src/shared/const/pages.ts b/src/shared/const/pages.ts index 33410496..107797b7 100644 --- a/src/shared/const/pages.ts +++ b/src/shared/const/pages.ts @@ -8,6 +8,7 @@ export enum PagePath { Portfolio = '/portfolio', TradePair = '/trade/:primary/:numeraire', InspectLp = '/inspect/lp/:id', + LpLeaderboard = '/inspect/lp-leaderboard', } const basePath: Partial> = {