Skip to content

Commit 3716bb1

Browse files
committed
feat: display 24h key price twap
1 parent 13ac4c6 commit 3716bb1

4 files changed

Lines changed: 60 additions & 0 deletions

File tree

src/hooks/useKeyTwap.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { queryKeys } from '@/lib/queryKeys';
3+
import { courseService } from '@/services/course.service';
4+
5+
export function useKeyTwap(keyId: string) {
6+
return useQuery({
7+
queryKey: queryKeys.creators.twap(keyId),
8+
queryFn: () => courseService.getKeyTwap(keyId),
9+
enabled: !!keyId,
10+
staleTime: 60_000,
11+
retry: false,
12+
});
13+
}

src/lib/queryKeys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const queryKeys = {
2525
['creators', creatorId, 'holders'] as const,
2626
activity: (creatorId: string) =>
2727
['creators', creatorId, 'activity'] as const,
28+
twap: (creatorId: string) =>
29+
['creators', creatorId, 'twap', '24h'] as const,
2830
},
2931
wallet: {
3032
holdings: (address: string) => ['wallet', address, 'holdings'] as const,

src/pages/CreatorDetailPage.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import KeyDetailPageErrorBoundary from '@/components/common/KeyDetailPageErrorBo
1717
import { ApiError } from '@/services/api.service';
1818
import { useKeyHolders } from '@/hooks/useKeyHolders';
1919
import { useNavigationTiming } from '@/hooks/useNavigationTiming';
20+
import { useKeyTwap } from '@/hooks/useKeyTwap';
21+
import Skeleton from '@/components/ui/skeleton';
22+
import { Tooltip } from '@/components/ui/tooltip';
2023

2124
function CreatorDetailPageContent() {
2225
const { id } = useParams<{ id: string }>();
@@ -35,6 +38,7 @@ function CreatorDetailPageContent() {
3538
isFetchingNextPage,
3639
fetchNextPage,
3740
} = useKeyHolders(id || '');
41+
const { data: twap, isLoading: isTwapLoading } = useKeyTwap(id || '');
3842

3943
// Track stale data indicator
4044
const { shouldShowBadge, handleRefetch } = useCreatorProfileStaleIndicator(
@@ -100,6 +104,9 @@ function CreatorDetailPageContent() {
100104
supply: (index + 1) * 20,
101105
priceXLM: priceStroops / 10_000_000,
102106
}));
107+
const spotPrice = resolveCreatorKeyPriceStroops(creator);
108+
const twapPrice = twap?.priceStroops ?? null;
109+
const twapDelta = twapPrice != null && spotPrice != null ? twapPrice - spotPrice : null;
103110

104111
const defaultHolders = [
105112
{ id: 'h1', displayName: 'Early Adopter', keyCount: 25, stakedQuantity: 18 },
@@ -152,6 +159,25 @@ function CreatorDetailPageContent() {
152159
<CreatorProfileStatRow items={statItems} />
153160
</div>
154161

162+
<div className="rounded-2xl border border-white/10 bg-white/[0.03] px-5 py-4" data-testid="twap-price">
163+
{isTwapLoading ? (
164+
<div aria-label="Loading 24 hour TWAP" role="status"><Skeleton className="h-3 w-24" /><Skeleton className="mt-2 h-6 w-32" /></div>
165+
) : twapPrice != null ? (
166+
<div className="flex items-center justify-between gap-4">
167+
<div>
168+
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-white/55">
169+
<span>TWAP (24h)</span>
170+
<Tooltip content="Time-weighted average key price over the last 24 hours.">
171+
<button type="button" aria-label="What is 24 hour TWAP?" className="text-white/50"></button>
172+
</Tooltip>
173+
</div>
174+
<div className="mt-1 text-xl font-bold text-white">{formatDisplayKeyPrice(twapPrice)}</div>
175+
</div>
176+
{twapDelta != null && <span className={twapDelta >= 0 ? 'text-sm font-semibold text-emerald-400' : 'text-sm font-semibold text-rose-400'}>{twapDelta >= 0 ? '▲' : '▼'} {formatDisplayKeyPrice(Math.abs(twapDelta))} vs spot</span>}
177+
</div>
178+
) : null}
179+
</div>
180+
155181
{/* Staking Rewards */}
156182
<StakingRewardsSection
157183
{...stakingStats}

src/services/course.service.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ export interface KeyHoldersPage {
101101
nextCursor: string | null;
102102
}
103103

104+
export interface KeyTwap {
105+
/** 24-hour time-weighted average price in stroops. */
106+
priceStroops: number | null;
107+
window?: string;
108+
}
109+
104110
class CourseService extends BaseApiService {
105111
private readonly PROFILE_CACHE_TTL = 30000; // 30 seconds
106112

@@ -204,6 +210,19 @@ class CourseService extends BaseApiService {
204210
}
205211
}
206212

213+
// Get the time-weighted average price - GET /keys/:keyId/twap
214+
async getKeyTwap(keyId: string, window = '24h'): Promise<KeyTwap> {
215+
try {
216+
const response = await this.api.get<APIResponse<KeyTwap>>(
217+
`/keys/${keyId}/twap`,
218+
{ params: { window } }
219+
);
220+
return response.data.data;
221+
} catch (error) {
222+
throw this.handleError(error);
223+
}
224+
}
225+
207226
// Get enrolled courses - GET /courses/enrolled
208227
async getEnrolledCourses(): Promise<Course[]> {
209228
try {

0 commit comments

Comments
 (0)