Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/components/common/BondingCurveChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,52 @@
const displayHeight = typeof height === 'number' ? `${height}px` : height;
const displayWidth = typeof width === 'number' ? `${width}px` : width;

const renderTwapTooltip = ({

Check failure on line 141 in src/components/common/BondingCurveChart.tsx

View workflow job for this annotation

GitHub Actions / verify

'renderTwapTooltip' is assigned a value but never used
active,
payload,
label,
}: {
active?: boolean;
payload?: Array<{
dataKey?: string;
value?: number | string;
color?: string;
}>;
label?: string | number;
}) => {
if (!active || !payload || payload.length === 0) return null;

const spotEntry = payload.find(entry => entry.dataKey === 'priceXLM');
const twapEntry = payload.find(entry => entry.dataKey === 'twapPriceXLM');
const spotValue = Number(spotEntry?.value ?? 0);
const twapValue = Number(twapEntry?.value ?? 0);
const deviation =
spotValue > 0 ? ((twapValue - spotValue) / spotValue) * 100 : 0;

return (
<div className="rounded-lg border border-white/10 bg-[#171717] p-3 text-sm text-white shadow-lg">
<div className="mb-1 text-xs uppercase tracking-[0.2em] text-white/55">
{twapLabel}
</div>
<div className="space-y-1">
<div>Spot: {spotValue.toFixed(4)} XLM</div>
<div>TWAP: {twapValue.toFixed(4)} XLM</div>
<div
className={
deviation >= 0 ? 'text-amber-300' : 'text-emerald-300'
}
>
Deviation: {deviation >= 0 ? '+' : ''}
{deviation.toFixed(2)}%
</div>
{label !== undefined && (
<div className="text-white/55">Supply: {label}</div>
)}
</div>
</div>
);
};

return (
<div className={cn('w-full', className)}>
<div style={{ height: displayHeight, width: displayWidth }}>
Expand Down
9 changes: 6 additions & 3 deletions src/hooks/useKeyTwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
45 changes: 45 additions & 0 deletions src/pages/CreatorDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,51 @@ function CreatorDetailPageContent() {
</span>
)}
</div>
{isTwapLoading ? (
<div
aria-label="Loading TWAP"
role="status"
className="mt-2"
>
<Skeleton className="h-3 w-24" />
<Skeleton className="mt-2 h-6 w-32" />
</div>
) : twapPrice != null ? (
<div className="text-right">
<div className="mt-1 text-xl font-bold text-white">
{formatDisplayKeyPrice(twapPrice)}
</div>
{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>
)}
{twapDeviationPercent != null && (
<div
className={
twapDeviationPercent < 0
? 'mt-1 text-xs text-emerald-300'
: 'mt-1 text-xs text-rose-300'
}
>
{twapDeviationPercent >= 0 ? '+' : ''}
{twapDeviationPercent.toFixed(2)}% from spot
</div>
)}
</div>
) : (
<div className="text-right text-sm text-white/45">
TWAP unavailable
</div>
)}
</div>
) : null}

Expand Down
16 changes: 12 additions & 4 deletions src/services/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<KeyTwap> {
async getKeyTwap(
keyId: string,
window: KeyTwapWindow = '24h'
): Promise<KeyTwap> {
try {
const response = await this.api.get<APIResponse<KeyTwap>>(
`/keys/${keyId}/twap`,
Expand Down
Loading