import { useState, useEffect, useCallback } from 'react';
// --- Types ---
export interface VolumeEntry {
dt: string;
volume: number;
activeUsers: number;
rank: string;
}
export interface BuilderTrade {
id: string;
tradeType: 'MAKER' | 'TAKER';
market: string;
assetId: string;
side: 'BUY' | 'SELL';
size: string;
sizeUsdc: string;
price: string;
fee: string;
feeUsdc: string;
builderFee: string;
outcome: string;
maker: string;
owner: string;
transactionHash: string;
matchTime: string;
createdAt: string;
}
export interface RankEntry {
rank: string;
builder: string;
builderCode: string;
volume: number;
activeUsers: number;
verified: boolean;
}
export interface AnalyticsTotals {
totalVolume: number;
totalFees: number; // platform fee (Polymarket) — sum of feeUsdc
myBuilderFee: number; // our builder revenue — sum of builderFee
makerFee: number; // builder revenue from MAKER fills
takerFee: number; // builder revenue from TAKER fills
totalTrades: number;
activeUsers: number;
rank: string;
}
// --- Hook ---
export interface PayoutEntry {
amount: number;
timestamp: string;
hash: string;
token: string;
}
export function useAnalytics() {
const [volume, setVolume] = useState<VolumeEntry[]>([]);
const [trades, setTrades] = useState<BuilderTrade[]>([]);
const [rank, setRank] = useState<RankEntry | null>(null);
const [paidTotal, setPaidTotal] = useState(0);
const [payouts, setPayouts] = useState<PayoutEntry[]>([]);
const [totals, setTotals] = useState<AnalyticsTotals>({
totalVolume: 0, totalFees: 0, myBuilderFee: 0, makerFee: 0, takerFee: 0,
totalTrades: 0, activeUsers: 0, rank: '-',
});
const [loading, setLoading] = useState(true);
const fetchVolume = useCallback(async () => {
try {
const res = await fetch('/api/analytics/volume?period=ALL');
const d = await res.json();
if (d.success) setVolume(d.data ?? []);
} catch { /* ignore */ }
}, []);
const fetchTrades = useCallback(async () => {
try {
// Walk all CLOB pages so totals stay accurate past one page (limit 300/page).
// CLOB signals end with next_cursor 'LTE=' (base64 "-1"). Cap at 50 pages (15K trades).
const all: BuilderTrade[] = [];
let cursor: string | undefined;
for (let page = 0; page < 50; page++) {
const qs = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
const res = await fetch(`/api/analytics/trades${qs}`);
const d = await res.json();
if (!d.success) break;
const items: BuilderTrade[] = d.data?.data ?? [];
all.push(...items);
const next: string | undefined = d.data?.next_cursor;
if (!next || next === 'LTE=' || items.length === 0) break;
cursor = next;
}
setTrades(all);
// Compute totals from ALL trades (leaderboard omits us until top-50).
// totalFees = platform fee (feeUsdc); myBuilderFee = our revenue (builderFee).
const totalFees = all.reduce((sum, t) => sum + (parseFloat(t.feeUsdc) || 0), 0);
const myBuilderFee = all.reduce((sum, t) => sum + (parseFloat(t.builderFee) || 0), 0);
const makerFee = all.reduce((sum, t) => sum + (t.tradeType === 'MAKER' ? parseFloat(t.builderFee) || 0 : 0), 0);
const takerFee = all.reduce((sum, t) => sum + (t.tradeType === 'TAKER' ? parseFloat(t.builderFee) || 0 : 0), 0);
const totalVolume = all.reduce((sum, t) => sum + (parseFloat(t.sizeUsdc) || 0), 0);
const activeUsers = new Set(all.map(t => t.owner).filter(Boolean)).size;
setTotals(prev => ({
...prev, totalFees, myBuilderFee, makerFee, takerFee, totalVolume, activeUsers, totalTrades: all.length,
}));
} catch { /* ignore */ }
}, []);
const fetchRank = useCallback(async () => {
try {
const res = await fetch('/api/analytics/rank?period=ALL');
const d = await res.json();
if (d.success && d.data) {
const entry = d.data as RankEntry;
setRank(entry);
setTotals(prev => ({
...prev,
totalVolume: entry.volume ?? 0,
activeUsers: entry.activeUsers ?? 0,
rank: entry.rank ?? '-',
}));
}
} catch { /* ignore */ }
}, []);
const fetchPayouts = useCallback(async () => {
try {
const res = await fetch('/api/analytics/payouts');
const d = await res.json();
if (d.success && d.data) {
setPaidTotal(d.data.paidTotal ?? 0);
setPayouts(d.data.payouts ?? []);
}
} catch { /* ignore */ }
}, []);
const refresh = useCallback(async () => {
setLoading(true);
await Promise.all([fetchVolume(), fetchTrades(), fetchRank(), fetchPayouts()]);
setLoading(false);
}, [fetchVolume, fetchTrades, fetchRank, fetchPayouts]);
useEffect(() => {
const t = setTimeout(refresh, 0);
const interval = setInterval(refresh, 5 * 60_000); // 5 min auto-refresh
return () => { clearTimeout(t); clearInterval(interval); };
}, [refresh]);
return { volume, trades, rank, totals, paidTotal, payouts, loading, refresh };
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...