diff --git a/src/hooks/useKeyTwap.ts b/src/hooks/useKeyTwap.ts index 6dd8f504..c6f2b8d7 100644 --- a/src/hooks/useKeyTwap.ts +++ b/src/hooks/useKeyTwap.ts @@ -2,12 +2,15 @@ import { useQuery } from '@tanstack/react-query'; import { queryKeys } from '@/lib/queryKeys'; import { courseService } from '@/services/course.service'; -export function useKeyTwap(keyId: string) { +export type KeyTwapWindow = '1h' | '24h'; + +export function useKeyTwap(keyId: string, window: KeyTwapWindow = '24h') { return useQuery({ - queryKey: queryKeys.creators.twap(keyId), - queryFn: () => courseService.getKeyTwap(keyId), + queryKey: queryKeys.creators.twap(keyId, window), + queryFn: () => courseService.getKeyTwap(keyId, window), enabled: !!keyId, staleTime: 60_000, + refetchInterval: 60_000, retry: false, }); } diff --git a/src/lib/queryKeys.ts b/src/lib/queryKeys.ts index 34fdbc66..3a17e411 100644 --- a/src/lib/queryKeys.ts +++ b/src/lib/queryKeys.ts @@ -7,7 +7,7 @@ * queryKeys.creatorProfile.all() })`). */ -import type { GetCoursesParams } from '@/services/course.service'; +import type { GetCoursesParams, KeyTwapWindow } from '@/services/course.service'; import type { VolumeWindow } from '@/services/leaderboard.service'; export const queryKeys = { @@ -31,8 +31,8 @@ export const queryKeys = { ['creators', creatorId, 'holders'] as const, activity: (creatorId: string) => ['creators', creatorId, 'activity'] as const, - twap: (creatorId: string) => - ['creators', creatorId, 'twap', '24h'] as const, + twap: (creatorId: string, window: KeyTwapWindow = '24h') => + ['creators', creatorId, 'twap', window] as const, stats: (creatorId: string) => ['creators', creatorId, 'stats'] as const, auctionBids: (creatorId: string) => ['creators', creatorId, 'auction-bids'] as const, diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index 2d6aa9d6..635ee902 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -437,6 +437,10 @@ function CreatorDetailPageContent() { const twapDelta = twapPrice != null && spotPrice != null ? twapPrice - spotPrice : null; + const twapDeviationPercent = + twapPrice != null && spotPrice != null && spotPrice > 0 + ? ((twapPrice - spotPrice) / spotPrice) * 100 + : null; const hasRealStakingData = creator.stakingPoolBalance != null || @@ -766,6 +770,51 @@ function CreatorDetailPageContent() { )} + {isTwapLoading ? ( +
+ + +
+ ) : twapPrice != null ? ( +
+
+ {formatDisplayKeyPrice(twapPrice)} +
+ {twapDelta != null && ( + + {twapDelta < 0 ? '▼' : '▲'}{' '} + {formatDisplayKeyPrice(Math.abs(twapDelta))} vs + spot + + )} + {twapDeviationPercent != null && ( +
+ {twapDeviationPercent >= 0 ? '+' : ''} + {twapDeviationPercent.toFixed(2)}% from spot +
+ )} +
+ ) : ( +
+ TWAP unavailable +
+ )} ) : null} diff --git a/src/services/course.service.ts b/src/services/course.service.ts index 1313be2f..59edb3e6 100644 --- a/src/services/course.service.ts +++ b/src/services/course.service.ts @@ -307,7 +307,10 @@ export interface PerformanceBond { } export type CourseSortOption = - 'volume_desc' | 'price_asc' | 'price_desc' | 'newest'; + | 'volume_desc' + | 'price_asc' + | 'price_desc' + | 'newest'; export interface GetCoursesParams { page?: number; @@ -362,10 +365,12 @@ export interface KeyHoldersPage { nextCursor: string | null; } +export type KeyTwapWindow = '1h' | '24h'; + export interface KeyTwap { - /** 24-hour time-weighted average price in stroops. */ + /** Time-weighted average price in stroops for the requested window. */ priceStroops: number | null; - window?: string; + window?: KeyTwapWindow; } export interface KeyBuybackInfo { @@ -546,7 +551,10 @@ class CourseService extends BaseApiService { } // Get the time-weighted average price - GET /keys/:keyId/twap - async getKeyTwap(keyId: string, window = '24h'): Promise { + async getKeyTwap( + keyId: string, + window: KeyTwapWindow = '24h' + ): Promise { try { const response = await this.api.get>( `/keys/${keyId}/twap`,