Skip to content
Merged
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
64 changes: 64 additions & 0 deletions lib/routes/polymarket/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';

import type { Event } from './types';
import { GAMMA_API } from './types';
import { formatOddsDisplay } from './utils';

export const route: Route = {
path: '/event/:slug',
categories: ['finance'],
example: '/polymarket/event/presidential-election-winner-2024',
parameters: {
slug: 'Event slug from the URL (e.g. presidential-election-winner-2024)',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['polymarket.com/event/:slug'],
target: '/event/:slug',
},
],
name: 'Event',
url: 'polymarket.com',
maintainers: ['heqi201255'],
handler,
};

async function handler(ctx) {
const slug = ctx.req.param('slug');

const event = await ofetch<Event>(`${GAMMA_API}/events/slug/${slug}`);

if (!event) {
throw new Error('Event not found');
}

const items = event.markets.map((market) => ({
title: market.question,
description: `
<p><strong>Odds:</strong> ${formatOddsDisplay(market)}</p>
<p><strong>Volume:</strong> $${Number(market.volume || 0).toLocaleString()}</p>
${market.oneDayPriceChange ? `<p><strong>24h Change:</strong> ${(market.oneDayPriceChange * 100).toFixed(1)}%</p>` : ''}
${market.image ? `<img src="${market.image}" alt="${market.question}" style="max-width: 100%;">` : ''}
`,
link: `https://polymarket.com/event/${event.slug}`,
pubDate: market.startDate || event.startDate ? parseDate(market.startDate || event.startDate!) : undefined,
category: event.tags?.map((t) => t.label).filter(Boolean) as string[],
}));

return {
title: event.title,
link: `https://polymarket.com/event/${event.slug}`,
item: items,
description: event.description,
};
}
69 changes: 69 additions & 0 deletions lib/routes/polymarket/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';

import type { EventsPagination } from './types';
import { GAMMA_API } from './types';
import { formatEventDescription } from './utils';

export const route: Route = {
path: '/events/:tagSlug?',
categories: ['finance'],
example: '/polymarket/events',
parameters: {
tagSlug: 'Tag slug to filter events, e.g. politics, sports, crypto. Omit for all events.',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['polymarket.com', 'polymarket.com/:tagSlug'],
target: '/events/:tagSlug',
},
],
name: 'Events',
url: 'polymarket.com',
maintainers: ['heqi201255'],
handler,
};

async function handler(ctx) {
const tagSlug = ctx.req.param('tagSlug');
const limit = 30;

const query: Record<string, unknown> = {
active: true,
closed: false,
limit,
order: 'volume',
ascending: false,
};

if (tagSlug) {
query.tag_slug = tagSlug;
}

const response = await ofetch<EventsPagination>(`${GAMMA_API}/events/pagination`, { query });

const data = response.data || [];

const items = data.map((event) => ({
title: event.title,
description: formatEventDescription(event),
link: `https://polymarket.com/event/${event.slug}`,
pubDate: event.startDate ? parseDate(event.startDate) : undefined,
category: event.tags?.map((t) => t.label).filter(Boolean) as string[],
}));

return {
title: `Polymarket Events${tagSlug ? ` - ${tagSlug}` : ''}`,
link: tagSlug ? `https://polymarket.com/${tagSlug}` : 'https://polymarket.com',
item: items,
};
}
70 changes: 70 additions & 0 deletions lib/routes/polymarket/leaderboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';

import type { LeaderboardEntry } from './types';
import { DATA_API } from './types';

export const route: Route = {
path: '/leaderboard/:category?/:timePeriod?',
categories: ['finance'],
example: '/polymarket/leaderboard',
parameters: {
category: {
description: 'Market category: OVERALL, POLITICS, SPORTS, CRYPTO, CULTURE, MENTIONS, WEATHER, ECONOMICS, TECH, FINANCE',
default: 'OVERALL',
},
timePeriod: {
description: 'Time period: DAY, WEEK, MONTH, ALL',
default: 'DAY',
},
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: 'Leaderboard',
url: 'polymarket.com',
maintainers: ['heqi201255'],
handler,
};

async function handler(ctx) {
const category = ctx.req.param('category') || 'OVERALL';
const timePeriod = ctx.req.param('timePeriod') || 'DAY';

const data = await ofetch<LeaderboardEntry[]>(`${DATA_API}/v1/leaderboard`, {
query: {
category,
timePeriod,
orderBy: 'PNL',
limit: 50,
},
});

const items = data.map((entry) => ({
title: `#${entry.rank} ${entry.userName || entry.proxyWallet}`,
description: `
<p><strong>Rank:</strong> #${entry.rank}</p>
<p><strong>PnL:</strong> $${Number(entry.pnl).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</p>
<p><strong>Volume:</strong> $${Number(entry.vol).toLocaleString()}</p>
${entry.xUsername ? `<p><strong>X:</strong> @${entry.xUsername}</p>` : ''}
${entry.verifiedBadge ? '<p>✅ Verified</p>' : ''}
${entry.profileImage ? `<img src="${entry.profileImage}" alt="${entry.userName || 'Trader'}" style="max-width: 100px; border-radius: 50%;">` : ''}
`,
link: `https://polymarket.com/portfolio?address=${entry.proxyWallet}`,
author: entry.userName || entry.proxyWallet,
}));

const categoryName = category.charAt(0).toUpperCase() + category.slice(1).toLowerCase();
const periodName = timePeriod.charAt(0).toUpperCase() + timePeriod.slice(1).toLowerCase();

return {
title: `Polymarket Leaderboard - ${categoryName} (${periodName})`,
link: 'https://polymarket.com/leaderboard',
item: items,
};
}
8 changes: 8 additions & 0 deletions lib/routes/polymarket/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Namespace } from '@/types';

export const namespace: Namespace = {
name: 'Polymarket',
url: 'polymarket.com',
description: `Polymarket is a prediction market platform where you can bet on real-world events.`,
lang: 'en',
};
78 changes: 78 additions & 0 deletions lib/routes/polymarket/positions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';

import type { Position, PublicProfile } from './types';
import { DATA_API, GAMMA_API } from './types';

export const route: Route = {
path: '/positions/:address',
categories: ['finance'],
example: '/polymarket/positions/0x7c3db723f1d4d8cb9c550095203b686cb11e5c6b',
parameters: {
address: 'Wallet address (0x...)',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: 'User Positions',
url: 'polymarket.com',
maintainers: ['heqi201255'],
handler,
};

async function handler(ctx) {
const address = ctx.req.param('address');

// Fetch profile and positions
let profile: PublicProfile | null = null;
let positions: Position[] = [];

try {
profile = await ofetch<PublicProfile>(`${GAMMA_API}/public-profile`, {
query: { address },
});
} catch {
// Profile not found, continue without it
}

try {
positions = await ofetch<Position[]>(`${DATA_API}/positions`, {
query: {
user: address,
limit: 50,
sortBy: 'CURRENT',
sortDirection: 'DESC',
},
});
} catch {
// Positions not found, continue with empty array
}

const displayName = profile?.name || profile?.pseudonym || address;

const items = positions.map((pos) => ({
title: pos.title || `Position #${pos.conditionId.slice(0, 8)}`,
description: `
<p><strong>Outcome:</strong> ${pos.outcome || `#${pos.outcomeIndex}`}</p>
<p><strong>Size:</strong> ${Number(pos.size).toLocaleString()}</p>
<p><strong>Avg Price:</strong> $${Number(pos.avgPrice).toFixed(4)}</p>
<p><strong>Current Price:</strong> $${Number(pos.curPrice).toFixed(4)}</p>
<p><strong>Current Value:</strong> $${Number(pos.currentValue).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</p>
<p><strong>PnL:</strong> ${pos.cashPnl >= 0 ? '+' : ''}$${Number(pos.cashPnl).toFixed(2)} (${pos.percentPnl >= 0 ? '+' : ''}${Number(pos.percentPnl).toFixed(1)}%)</p>
<img src="${pos.icon}" alt="${pos.title || 'Position'}" style="max-width: 100%;">
`,
link: pos.eventSlug ? `https://polymarket.com/event/${pos.eventSlug}` : pos.slug ? `https://polymarket.com/event/${pos.slug}` : 'https://polymarket.com',
author: displayName,
}));

return {
title: `Polymarket Positions - ${displayName}`,
link: `https://polymarket.com/portfolio?address=${address}`,
item: items,
};
}
53 changes: 53 additions & 0 deletions lib/routes/polymarket/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';

import type { SearchResult } from './types';
import { GAMMA_API } from './types';
import { formatEventDescription } from './utils';

export const route: Route = {
path: '/search/:query',
categories: ['finance'],
example: '/polymarket/search/trump',
parameters: {
query: 'Search query',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: 'Search',
url: 'polymarket.com',
maintainers: ['heqi201255'],
handler,
};

async function handler(ctx) {
const query = ctx.req.param('query');

const data = await ofetch<SearchResult>(`${GAMMA_API}/public-search`, {
query: {
q: query,
limit_per_type: 30,
},
});

const items = (data.events || []).map((event) => ({
title: event.title,
description: formatEventDescription(event),
link: `https://polymarket.com/event/${event.slug}`,
pubDate: event.startDate ? parseDate(event.startDate) : undefined,
category: event.tags?.map((t) => t.label).filter(Boolean) as string[],
}));

return {
title: `Polymarket Search - ${query}`,
link: `https://polymarket.com/search?q=${encodeURIComponent(query)}`,
item: items,
};
}
Loading
Loading