/** * Fetch token IDs from Gamma events API (temperature markets). * Used as fallback when MarketSnapshot table is empty. */ import { CITY_ALIASES } from '@poly-weather/core'; const GAMMA_BASE = 'https://gamma-api.polymarket.com'; interface GammaEventMarket { id: string; groupItemTitle: string; clobTokenIds: string; closed: boolean; } interface GammaEvent { id: string; title: string; markets: GammaEventMarket[]; } export interface TokenPair { yesTokenId: string; noTokenId: string; } // In-memory cache, refreshed every minute let cache: Map | null = null; let cacheTime = 0; const CACHE_TTL = 60_000; /** * Fetch ALL temperature event markets from Gamma. * Returns a map of "city_slug|bucketLabel" -> { yesTokenId, noTokenId } */ export async function fetchAllGammaTokens(): Promise> { if (cache && Date.now() - cacheTime < CACHE_TTL) { return cache; } const tokenMap = new Map(); let offset = 0; try { while (offset <= 500) { const params = new URLSearchParams({ tag_slug: 'temperature', active: 'true', closed: 'false', limit: '50', offset: offset.toString(), }); const res = await fetch(`${GAMMA_BASE}/events?${params}`, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(15000), }); if (!res.ok) break; const events = (await res.json()) as GammaEvent[]; if (events.length === 0) break; for (const event of events) { // Parse city from event title: "Highest temperature in Atlanta on March 18?" const titleMatch = event.title.match(/temperature in (.+?) on /i); if (!titleMatch) continue; const rawCity = titleMatch[1].trim(); // Use CITY_ALIASES to map Gamma titles (e.g. "NYC") to bot slugs (e.g. "new_york") const citySlug = CITY_ALIASES[rawCity] ?? rawCity.toLowerCase().replace(/\s+/g, '_'); for (const m of event.markets || []) { if (m.closed) continue; const bucketLabel = m.groupItemTitle; if (!bucketLabel) continue; try { const tokenIds = JSON.parse(m.clobTokenIds || '[]'); if (tokenIds.length >= 1) { tokenMap.set(`${citySlug}|${bucketLabel}`, { yesTokenId: tokenIds[0] || '', noTokenId: tokenIds[1] || '', }); } } catch { // skip } } } offset += 50; } } catch (err) { console.error('Gamma events fetch failed:', err); } console.log(`[gamma-tokens] Cache loaded: ${tokenMap.size} buckets`); cache = tokenMap; cacheTime = Date.now(); return tokenMap; } /** * Look up token pair for a specific city + bucket. * Tries MarketSnapshot DB first, then Gamma API fallback. */ export async function lookupTokenPair( city: string, bucket: string, prismaClient: { marketSnapshot: { findFirst: (...args: any[]) => Promise } }, ): Promise { // Try DB first const snapshot = await prismaClient.marketSnapshot.findFirst({ where: { city, bucketLabel: bucket }, orderBy: { createdAt: 'desc' }, select: { yesTokenId: true, noTokenId: true }, }); if (snapshot?.yesTokenId) { return { yesTokenId: snapshot.yesTokenId, noTokenId: snapshot.noTokenId || '', }; } // Fallback to Gamma const gammaTokens = await fetchAllGammaTokens(); return gammaTokens.get(`${city}|${bucket}`) || null; }