chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
import type { PolymarketActivity } from '@/tools/polymarket/types'
import { buildDataUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetActivityParams {
user: string
limit?: string
offset?: string
market?: string
eventId?: string
type?: string
start?: number
end?: number
sortBy?: string
sortDirection?: string
side?: string
}
export interface PolymarketGetActivityResponse {
success: boolean
output: {
activity: PolymarketActivity[]
}
}
export const polymarketGetActivityTool: ToolConfig<
PolymarketGetActivityParams,
PolymarketGetActivityResponse
> = {
id: 'polymarket_get_activity',
name: 'Get Activity from Polymarket',
description:
'Retrieve on-chain activity for a user including trades, splits, merges, redemptions, rewards, and conversions',
version: '1.0.0',
params: {
user: {
type: 'string',
required: true,
description: 'User wallet address (0x-prefixed)',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Maximum results to return (e.g., "50"). Default: 100, max: 500.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description:
'Number of results to skip for pagination (e.g., "100"). Default: 0, max: 10000.',
visibility: 'user-or-llm',
},
market: {
type: 'string',
required: false,
description:
'Comma-separated condition IDs (e.g., "0x1234...abcd,0x5678...efgh"). Mutually exclusive with eventId.',
visibility: 'user-or-llm',
},
eventId: {
type: 'string',
required: false,
description:
'Comma-separated event IDs (e.g., "12345,67890"). Mutually exclusive with market.',
visibility: 'user-or-llm',
},
type: {
type: 'string',
required: false,
description:
'Activity type filter: TRADE, SPLIT, MERGE, REDEEM, REWARD, CONVERSION, MAKER_REBATE, REFERRAL_REWARD',
visibility: 'user-or-llm',
},
start: {
type: 'number',
required: false,
description: 'Start timestamp (Unix seconds)',
visibility: 'user-or-llm',
},
end: {
type: 'number',
required: false,
description: 'End timestamp (Unix seconds)',
visibility: 'user-or-llm',
},
sortBy: {
type: 'string',
required: false,
description: 'Sort field: TIMESTAMP, TOKENS, or CASH (default: TIMESTAMP)',
visibility: 'user-or-llm',
},
sortDirection: {
type: 'string',
required: false,
description: 'Sort direction: ASC or DESC (default: DESC)',
visibility: 'user-or-llm',
},
side: {
type: 'string',
required: false,
description: 'Trade side filter: BUY or SELL (only applies to trades)',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('user', params.user)
if (params.limit) queryParams.append('limit', params.limit)
if (params.offset) queryParams.append('offset', params.offset)
if (params.market) queryParams.append('market', params.market)
if (params.eventId) queryParams.append('eventId', params.eventId)
if (params.type) queryParams.append('type', params.type)
if (params.start != null && !Number.isNaN(params.start))
queryParams.append('start', String(params.start))
if (params.end != null && !Number.isNaN(params.end))
queryParams.append('end', String(params.end))
if (params.sortBy) queryParams.append('sortBy', params.sortBy)
if (params.sortDirection) queryParams.append('sortDirection', params.sortDirection)
if (params.side) queryParams.append('side', params.side)
return `${buildDataUrl('/activity')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_activity')
}
const activityList = Array.isArray(data) ? data : []
const activity: PolymarketActivity[] = activityList.map((a: any) => ({
proxyWallet: a.proxyWallet ?? null,
timestamp: a.timestamp ?? 0,
conditionId: a.conditionId ?? '',
type: a.type ?? '',
size: a.size ?? 0,
usdcSize: a.usdcSize ?? 0,
transactionHash: a.transactionHash ?? null,
price: a.price ?? null,
asset: a.asset ?? null,
side: a.side ?? null,
outcomeIndex: a.outcomeIndex ?? null,
title: a.title ?? null,
slug: a.slug ?? null,
icon: a.icon ?? null,
eventSlug: a.eventSlug ?? null,
outcome: a.outcome ?? null,
name: a.name ?? null,
pseudonym: a.pseudonym ?? null,
bio: a.bio ?? null,
profileImage: a.profileImage ?? null,
profileImageOptimized: a.profileImageOptimized ?? null,
}))
return {
success: true,
output: {
activity,
},
}
},
outputs: {
activity: {
type: 'array',
description: 'Array of activity entries',
items: {
type: 'object',
properties: {
proxyWallet: { type: 'string', description: 'User proxy wallet address' },
timestamp: { type: 'number', description: 'Unix timestamp of activity' },
conditionId: { type: 'string', description: 'Market condition ID' },
type: {
type: 'string',
description: 'Activity type (TRADE, SPLIT, MERGE, REDEEM, REWARD, CONVERSION)',
},
size: { type: 'number', description: 'Size in tokens' },
usdcSize: { type: 'number', description: 'Size in USDC' },
transactionHash: { type: 'string', description: 'Blockchain transaction hash' },
price: { type: 'number', description: 'Price (for trades)' },
asset: { type: 'string', description: 'Asset/token ID' },
side: { type: 'string', description: 'Trade side (BUY/SELL)' },
outcomeIndex: { type: 'number', description: 'Outcome index' },
title: { type: 'string', description: 'Market title' },
slug: { type: 'string', description: 'Market slug' },
icon: { type: 'string', description: 'Market icon URL' },
eventSlug: { type: 'string', description: 'Event slug' },
outcome: { type: 'string', description: 'Outcome name' },
name: { type: 'string', description: 'User display name' },
pseudonym: { type: 'string', description: 'User pseudonym' },
bio: { type: 'string', description: 'User bio' },
profileImage: { type: 'string', description: 'User profile image URL' },
profileImageOptimized: { type: 'string', description: 'Optimized profile image URL' },
},
},
},
},
}
+96
View File
@@ -0,0 +1,96 @@
import type { PolymarketEvent } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetEventParams {
eventId?: string // Event ID
slug?: string // Event slug (alternative to ID)
}
export interface PolymarketGetEventResponse {
success: boolean
output: {
event: PolymarketEvent
}
}
export const polymarketGetEventTool: ToolConfig<
PolymarketGetEventParams,
PolymarketGetEventResponse
> = {
id: 'polymarket_get_event',
name: 'Get Event from Polymarket',
description: 'Retrieve details of a specific event by ID or slug',
version: '1.0.0',
params: {
eventId: {
type: 'string',
required: false,
description: 'The numeric event ID (e.g., "12345"). Required if slug is not provided.',
visibility: 'user-or-llm',
},
slug: {
type: 'string',
required: false,
description:
'The event slug (e.g., "2024-presidential-election"). URL-friendly identifier. Required if eventId is not provided.',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
if (params.slug) {
return buildGammaUrl(`/events/slug/${params.slug.trim()}`)
}
return buildGammaUrl(`/events/${params.eventId?.trim()}`)
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_event')
}
return {
success: true,
output: {
event: data,
},
}
},
outputs: {
event: {
type: 'object',
description: 'Event object with details',
properties: {
id: { type: 'string', description: 'Event ID' },
ticker: { type: 'string', description: 'Event ticker' },
slug: { type: 'string', description: 'Event slug' },
title: { type: 'string', description: 'Event title' },
description: { type: 'string', description: 'Event description' },
startDate: { type: 'string', description: 'Start date' },
creationDate: { type: 'string', description: 'Creation date' },
endDate: { type: 'string', description: 'End date' },
image: { type: 'string', description: 'Event image URL' },
icon: { type: 'string', description: 'Event icon URL' },
active: { type: 'boolean', description: 'Whether event is active' },
closed: { type: 'boolean', description: 'Whether event is closed' },
archived: { type: 'boolean', description: 'Whether event is archived' },
liquidity: { type: 'number', description: 'Total liquidity' },
volume: { type: 'number', description: 'Total volume' },
openInterest: { type: 'number', description: 'Open interest' },
commentCount: { type: 'number', description: 'Comment count' },
markets: { type: 'array', description: 'Array of markets in this event' },
},
},
},
}
+129
View File
@@ -0,0 +1,129 @@
import type { PolymarketEvent, PolymarketPaginationParams } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetEventsParams extends PolymarketPaginationParams {
closed?: string
order?: string
ascending?: string
tagId?: string
}
export interface PolymarketGetEventsResponse {
success: boolean
output: {
events: PolymarketEvent[]
}
}
export const polymarketGetEventsTool: ToolConfig<
PolymarketGetEventsParams,
PolymarketGetEventsResponse
> = {
id: 'polymarket_get_events',
name: 'Get Events from Polymarket',
description: 'Retrieve a list of events from Polymarket with optional filtering',
version: '1.0.0',
params: {
closed: {
type: 'string',
required: false,
description: 'Filter by closed status (true/false). Use false for open events only.',
visibility: 'user-or-llm',
},
order: {
type: 'string',
required: false,
description: 'Sort field (e.g., volume, liquidity, startDate, endDate, createdAt)',
visibility: 'user-or-llm',
},
ascending: {
type: 'string',
required: false,
description: 'Sort direction (true for ascending, false for descending)',
visibility: 'user-or-llm',
},
tagId: {
type: 'string',
required: false,
description: 'Filter by tag ID',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "25"). Max: 50.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "50").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.closed) queryParams.append('closed', params.closed)
if (params.order) queryParams.append('order', params.order)
if (params.ascending) queryParams.append('ascending', params.ascending)
if (params.tagId) queryParams.append('tag_id', params.tagId)
queryParams.append('limit', params.limit || '50')
if (params.offset) queryParams.append('offset', params.offset)
const url = buildGammaUrl('/events')
return `${url}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_events')
}
const events = Array.isArray(data) ? data : []
return {
success: true,
output: {
events,
},
}
},
outputs: {
events: {
type: 'array',
description: 'Array of event objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Event ID' },
ticker: { type: 'string', description: 'Event ticker' },
slug: { type: 'string', description: 'Event slug' },
title: { type: 'string', description: 'Event title' },
description: { type: 'string', description: 'Event description' },
startDate: { type: 'string', description: 'Start date' },
endDate: { type: 'string', description: 'End date' },
image: { type: 'string', description: 'Event image URL' },
icon: { type: 'string', description: 'Event icon URL' },
active: { type: 'boolean', description: 'Whether event is active' },
closed: { type: 'boolean', description: 'Whether event is closed' },
archived: { type: 'boolean', description: 'Whether event is archived' },
liquidity: { type: 'number', description: 'Total liquidity' },
volume: { type: 'number', description: 'Total volume' },
markets: { type: 'array', description: 'Array of markets in this event' },
},
},
},
},
}
+135
View File
@@ -0,0 +1,135 @@
import type { PolymarketMarketHolders } from '@/tools/polymarket/types'
import { buildDataUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetHoldersParams {
market: string
limit?: string
minBalance?: string
}
export interface PolymarketGetHoldersResponse {
success: boolean
output: {
holders: PolymarketMarketHolders[]
}
}
export const polymarketGetHoldersTool: ToolConfig<
PolymarketGetHoldersParams,
PolymarketGetHoldersResponse
> = {
id: 'polymarket_get_holders',
name: 'Get Market Holders from Polymarket',
description: 'Retrieve top holders of a specific market token',
version: '1.0.0',
params: {
market: {
type: 'string',
required: true,
description:
'Comma-separated list of condition IDs (e.g., "0x1234...abcd" or "0x1234...abcd,0x5678...efgh").',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of holders to return (e.g., "10"). Range: 0-20, default: 20.',
visibility: 'user-or-llm',
},
minBalance: {
type: 'string',
required: false,
description: 'Minimum balance threshold (default: 1)',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('market', params.market)
if (params.limit) queryParams.append('limit', params.limit)
if (params.minBalance) queryParams.append('minBalance', params.minBalance)
return `${buildDataUrl('/holders')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_holders')
}
const marketHolders = Array.isArray(data) ? data : []
const holders: PolymarketMarketHolders[] = marketHolders.map((mh: any) => ({
token: mh.token ?? '',
holders: (mh.holders ?? []).map((h: any) => ({
proxyWallet: h.proxyWallet ?? '',
bio: h.bio ?? null,
asset: h.asset ?? '',
pseudonym: h.pseudonym ?? null,
amount: h.amount ?? 0,
displayUsernamePublic: h.displayUsernamePublic ?? false,
outcomeIndex: h.outcomeIndex ?? 0,
name: h.name ?? null,
profileImage: h.profileImage ?? null,
profileImageOptimized: h.profileImageOptimized ?? null,
verified: h.verified ?? false,
})),
}))
return {
success: true,
output: {
holders,
},
}
},
outputs: {
holders: {
type: 'array',
description: 'Array of market holder groups by token',
items: {
type: 'object',
properties: {
token: { type: 'string', description: 'Token/asset ID' },
holders: {
type: 'array',
description: 'Array of holders for this token',
items: {
type: 'object',
properties: {
proxyWallet: { type: 'string', description: 'Holder wallet address' },
bio: { type: 'string', description: 'Holder bio' },
asset: { type: 'string', description: 'Asset ID' },
pseudonym: { type: 'string', description: 'Holder pseudonym' },
amount: { type: 'number', description: 'Amount held' },
displayUsernamePublic: {
type: 'boolean',
description: 'Whether username is publicly displayed',
},
outcomeIndex: { type: 'number', description: 'Outcome index' },
name: { type: 'string', description: 'Holder display name' },
profileImage: { type: 'string', description: 'Profile image URL' },
profileImageOptimized: {
type: 'string',
description: 'Optimized profile image URL',
},
verified: { type: 'boolean', description: 'Whether the holder is verified' },
},
},
},
},
},
},
},
}
@@ -0,0 +1,73 @@
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetLastTradePriceParams {
tokenId: string // The token ID (CLOB token ID from market)
}
export interface PolymarketGetLastTradePriceResponse {
success: boolean
output: {
price: string
side: string
}
}
export const polymarketGetLastTradePriceTool: ToolConfig<
PolymarketGetLastTradePriceParams,
PolymarketGetLastTradePriceResponse
> = {
id: 'polymarket_get_last_trade_price',
name: 'Get Last Trade Price from Polymarket',
description: 'Retrieve the last trade price for a specific token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
return `${buildClobUrl('/last-trade-price')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_last_trade_price')
}
return {
success: true,
output: {
price: data.price ?? '',
side: data.side ?? '',
},
}
},
outputs: {
price: {
type: 'string',
description: 'Last trade price',
},
side: {
type: 'string',
description: 'Side of the last trade (BUY or SELL)',
},
},
}
@@ -0,0 +1,144 @@
import type { PolymarketLeaderboardEntry } from '@/tools/polymarket/types'
import { buildDataUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetLeaderboardParams {
category?: string
timePeriod?: string
orderBy?: string
limit?: string
offset?: string
user?: string
userName?: string
}
export interface PolymarketGetLeaderboardResponse {
success: boolean
output: {
leaderboard: PolymarketLeaderboardEntry[]
}
}
export const polymarketGetLeaderboardTool: ToolConfig<
PolymarketGetLeaderboardParams,
PolymarketGetLeaderboardResponse
> = {
id: 'polymarket_get_leaderboard',
name: 'Get Leaderboard from Polymarket',
description: 'Retrieve trader leaderboard rankings by profit/loss or volume',
version: '1.0.0',
params: {
category: {
type: 'string',
required: false,
description:
'Category filter: OVERALL, POLITICS, SPORTS, CRYPTO, CULTURE, MENTIONS, WEATHER, ECONOMICS, TECH, FINANCE (default: OVERALL)',
visibility: 'user-or-llm',
},
timePeriod: {
type: 'string',
required: false,
description: 'Time period: DAY, WEEK, MONTH, ALL (default: DAY)',
visibility: 'user-or-llm',
},
orderBy: {
type: 'string',
required: false,
description: 'Order by: PNL or VOL (default: PNL)',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of results to return (e.g., "10"). Range: 1-50, default: 25.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description:
'Number of results to skip for pagination (e.g., "25"). Range: 0-1000, default: 0.',
visibility: 'user-or-llm',
},
user: {
type: 'string',
required: false,
description: 'Filter by specific user wallet address',
visibility: 'user-or-llm',
},
userName: {
type: 'string',
required: false,
description: 'Filter by username',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.category) queryParams.append('category', params.category)
if (params.timePeriod) queryParams.append('timePeriod', params.timePeriod)
if (params.orderBy) queryParams.append('orderBy', params.orderBy)
if (params.limit) queryParams.append('limit', params.limit)
if (params.offset) queryParams.append('offset', params.offset)
if (params.user) queryParams.append('user', params.user)
if (params.userName) queryParams.append('userName', params.userName)
const query = queryParams.toString()
return query ? `${buildDataUrl('/v1/leaderboard')}?${query}` : buildDataUrl('/v1/leaderboard')
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_leaderboard')
}
const entries = Array.isArray(data) ? data : []
const leaderboard: PolymarketLeaderboardEntry[] = entries.map((entry: any) => ({
rank: entry.rank ?? '',
proxyWallet: entry.proxyWallet ?? '',
userName: entry.userName ?? null,
vol: entry.vol ?? 0,
pnl: entry.pnl ?? 0,
profileImage: entry.profileImage ?? null,
xUsername: entry.xUsername ?? null,
verifiedBadge: entry.verifiedBadge ?? false,
}))
return {
success: true,
output: {
leaderboard,
},
}
},
outputs: {
leaderboard: {
type: 'array',
description: 'Array of leaderboard entries',
items: {
type: 'object',
properties: {
rank: { type: 'string', description: 'Leaderboard rank position' },
proxyWallet: { type: 'string', description: 'User proxy wallet address' },
userName: { type: 'string', description: 'User display name' },
vol: { type: 'number', description: 'Trading volume' },
pnl: { type: 'number', description: 'Profit and loss' },
profileImage: { type: 'string', description: 'User profile image URL' },
xUsername: { type: 'string', description: 'Twitter/X username' },
verifiedBadge: { type: 'boolean', description: 'Whether user has verified badge' },
},
},
},
},
}
+101
View File
@@ -0,0 +1,101 @@
import type { PolymarketMarket } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetMarketParams {
marketId?: string // Market ID
slug?: string // Market slug (alternative to ID)
}
export interface PolymarketGetMarketResponse {
success: boolean
output: {
market: PolymarketMarket
}
}
export const polymarketGetMarketTool: ToolConfig<
PolymarketGetMarketParams,
PolymarketGetMarketResponse
> = {
id: 'polymarket_get_market',
name: 'Get Market from Polymarket',
description: 'Retrieve details of a specific prediction market by ID or slug',
version: '1.0.0',
params: {
marketId: {
type: 'string',
required: false,
description:
'The numeric market ID (e.g., "253591"). Required if slug is not provided. Not the condition ID.',
visibility: 'user-or-llm',
},
slug: {
type: 'string',
required: false,
description:
'The market slug (e.g., "will-trump-win"). URL-friendly identifier. Required if marketId is not provided.',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
if (params.slug) {
return buildGammaUrl(`/markets/slug/${params.slug.trim()}`)
}
return buildGammaUrl(`/markets/${params.marketId?.trim()}`)
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_market')
}
return {
success: true,
output: {
market: data,
},
}
},
outputs: {
market: {
type: 'object',
description: 'Market object with details',
properties: {
id: { type: 'string', description: 'Market ID' },
question: { type: 'string', description: 'Market question' },
conditionId: { type: 'string', description: 'Condition ID' },
slug: { type: 'string', description: 'Market slug' },
resolutionSource: { type: 'string', description: 'Resolution source' },
endDate: { type: 'string', description: 'End date' },
startDate: { type: 'string', description: 'Start date' },
image: { type: 'string', description: 'Market image URL' },
icon: { type: 'string', description: 'Market icon URL' },
description: { type: 'string', description: 'Market description' },
outcomes: { type: 'string', description: 'Outcomes JSON string' },
outcomePrices: { type: 'string', description: 'Outcome prices JSON string' },
volume: { type: 'string', description: 'Total volume' },
liquidity: { type: 'string', description: 'Total liquidity' },
active: { type: 'boolean', description: 'Whether market is active' },
closed: { type: 'boolean', description: 'Whether market is closed' },
archived: { type: 'boolean', description: 'Whether market is archived' },
volumeNum: { type: 'number', description: 'Volume as number' },
liquidityNum: { type: 'number', description: 'Liquidity as number' },
clobTokenIds: { type: 'array', description: 'CLOB token IDs' },
acceptingOrders: { type: 'boolean', description: 'Whether accepting orders' },
negRisk: { type: 'boolean', description: 'Whether negative risk' },
},
},
},
}
+130
View File
@@ -0,0 +1,130 @@
import type { PolymarketMarket, PolymarketPaginationParams } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetMarketsParams extends PolymarketPaginationParams {
closed?: string
order?: string
ascending?: string
tagId?: string
}
export interface PolymarketGetMarketsResponse {
success: boolean
output: {
markets: PolymarketMarket[]
}
}
export const polymarketGetMarketsTool: ToolConfig<
PolymarketGetMarketsParams,
PolymarketGetMarketsResponse
> = {
id: 'polymarket_get_markets',
name: 'Get Markets from Polymarket',
description: 'Retrieve a list of prediction markets from Polymarket with optional filtering',
version: '1.0.0',
params: {
closed: {
type: 'string',
required: false,
description: 'Filter by closed status (true/false). Use false for open markets only.',
visibility: 'user-or-llm',
},
order: {
type: 'string',
required: false,
description: 'Sort field (e.g., volumeNum, liquidityNum, startDate, endDate, createdAt)',
visibility: 'user-or-llm',
},
ascending: {
type: 'string',
required: false,
description: 'Sort direction (true for ascending, false for descending)',
visibility: 'user-or-llm',
},
tagId: {
type: 'string',
required: false,
description: 'Filter by tag ID',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "25"). Max: 50.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "50").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.closed) queryParams.append('closed', params.closed)
if (params.order) queryParams.append('order', params.order)
if (params.ascending) queryParams.append('ascending', params.ascending)
if (params.tagId) queryParams.append('tag_id', params.tagId)
queryParams.append('limit', params.limit || '50')
if (params.offset) queryParams.append('offset', params.offset)
const url = buildGammaUrl('/markets')
return `${url}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_markets')
}
// Response is an array of markets
const markets = Array.isArray(data) ? data : []
return {
success: true,
output: {
markets,
},
}
},
outputs: {
markets: {
type: 'array',
description: 'Array of market objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Market ID' },
question: { type: 'string', description: 'Market question' },
conditionId: { type: 'string', description: 'Condition ID' },
slug: { type: 'string', description: 'Market slug' },
endDate: { type: 'string', description: 'End date' },
image: { type: 'string', description: 'Market image URL' },
outcomes: { type: 'string', description: 'Outcomes JSON string' },
outcomePrices: { type: 'string', description: 'Outcome prices JSON string' },
volume: { type: 'string', description: 'Total volume' },
liquidity: { type: 'string', description: 'Total liquidity' },
active: { type: 'boolean', description: 'Whether market is active' },
closed: { type: 'boolean', description: 'Whether market is closed' },
volumeNum: { type: 'number', description: 'Volume as number' },
liquidityNum: { type: 'number', description: 'Liquidity as number' },
clobTokenIds: { type: 'array', description: 'CLOB token IDs' },
},
},
},
},
}
+68
View File
@@ -0,0 +1,68 @@
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetMidpointParams {
tokenId: string // The token ID (CLOB token ID from market)
}
export interface PolymarketGetMidpointResponse {
success: boolean
output: {
midpoint: string
}
}
export const polymarketGetMidpointTool: ToolConfig<
PolymarketGetMidpointParams,
PolymarketGetMidpointResponse
> = {
id: 'polymarket_get_midpoint',
name: 'Get Midpoint Price from Polymarket',
description: 'Retrieve the midpoint price for a specific token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
return `${buildClobUrl('/midpoint')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_midpoint')
}
// CLOB /midpoint returns { mid: "0.52" } (docs label it mid_price — handle both)
return {
success: true,
output: {
midpoint: String(data.mid ?? data.mid_price ?? data.midpoint ?? ''),
},
}
},
outputs: {
midpoint: {
type: 'string',
description: 'Midpoint price',
},
},
}
+113
View File
@@ -0,0 +1,113 @@
import type { PolymarketOrderBook } from '@/tools/polymarket/types'
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetOrderbookParams {
tokenId: string // The token ID (CLOB token ID from market)
}
export interface PolymarketGetOrderbookResponse {
success: boolean
output: {
orderbook: PolymarketOrderBook
}
}
export const polymarketGetOrderbookTool: ToolConfig<
PolymarketGetOrderbookParams,
PolymarketGetOrderbookResponse
> = {
id: 'polymarket_get_orderbook',
name: 'Get Orderbook from Polymarket',
description: 'Retrieve the order book summary for a specific token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
return `${buildClobUrl('/book')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_orderbook')
}
const orderbook: PolymarketOrderBook = {
market: data.market ?? '',
asset_id: data.asset_id ?? '',
hash: data.hash ?? '',
timestamp: data.timestamp ?? '',
bids: data.bids ?? [],
asks: data.asks ?? [],
min_order_size: data.min_order_size ?? '0',
tick_size: data.tick_size ?? '0',
neg_risk: data.neg_risk ?? false,
last_trade_price: String(data.last_trade_price ?? ''),
}
return {
success: true,
output: {
orderbook,
},
}
},
outputs: {
orderbook: {
type: 'object',
description: 'Order book with bids and asks arrays',
properties: {
market: { type: 'string', description: 'Market identifier' },
asset_id: { type: 'string', description: 'Asset token ID' },
hash: { type: 'string', description: 'Order book hash' },
timestamp: { type: 'string', description: 'Timestamp' },
bids: {
type: 'array',
description: 'Bid orders',
items: {
type: 'object',
properties: {
price: { type: 'string', description: 'Bid price' },
size: { type: 'string', description: 'Bid size' },
},
},
},
asks: {
type: 'array',
description: 'Ask orders',
items: {
type: 'object',
properties: {
price: { type: 'string', description: 'Ask price' },
size: { type: 'string', description: 'Ask size' },
},
},
},
min_order_size: { type: 'string', description: 'Minimum order size' },
tick_size: { type: 'string', description: 'Tick size' },
neg_risk: { type: 'boolean', description: 'Whether negative risk' },
last_trade_price: { type: 'string', description: 'Last trade price' },
},
},
},
}
+209
View File
@@ -0,0 +1,209 @@
import type { PolymarketPosition } from '@/tools/polymarket/types'
import { buildDataUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetPositionsParams {
user: string
market?: string
eventId?: string
sizeThreshold?: string
redeemable?: string
mergeable?: string
sortBy?: string
sortDirection?: string
title?: string
limit?: string
offset?: string
}
export interface PolymarketGetPositionsResponse {
success: boolean
output: {
positions: PolymarketPosition[]
}
}
export const polymarketGetPositionsTool: ToolConfig<
PolymarketGetPositionsParams,
PolymarketGetPositionsResponse
> = {
id: 'polymarket_get_positions',
name: 'Get Positions from Polymarket',
description: 'Retrieve user positions from Polymarket',
version: '1.0.0',
params: {
user: {
type: 'string',
required: true,
description: 'User wallet address',
visibility: 'user-or-llm',
},
market: {
type: 'string',
required: false,
description:
'Condition IDs to filter positions (e.g., "0x1234...abcd,0x5678...efgh"). Mutually exclusive with eventId.',
visibility: 'user-or-llm',
},
eventId: {
type: 'string',
required: false,
description: 'Event ID to filter positions (e.g., "12345"). Mutually exclusive with market.',
visibility: 'user-or-llm',
},
sizeThreshold: {
type: 'string',
required: false,
description: 'Minimum position size threshold (default: 1)',
visibility: 'user-or-llm',
},
redeemable: {
type: 'string',
required: false,
description: 'Filter for redeemable positions only (true/false)',
visibility: 'user-or-llm',
},
mergeable: {
type: 'string',
required: false,
description: 'Filter for mergeable positions only (true/false)',
visibility: 'user-or-llm',
},
sortBy: {
type: 'string',
required: false,
description:
'Sort field (TOKENS, CURRENT, INITIAL, CASHPNL, PERCENTPNL, TITLE, RESOLVING, PRICE, AVGPRICE)',
visibility: 'user-or-llm',
},
sortDirection: {
type: 'string',
required: false,
description: 'Sort direction (ASC or DESC)',
visibility: 'user-or-llm',
},
title: {
type: 'string',
required: false,
description: 'Search filter by title',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "25").',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "50").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('user', params.user)
if (params.market) queryParams.append('market', params.market)
if (params.eventId) queryParams.append('eventId', params.eventId)
if (params.sizeThreshold) queryParams.append('sizeThreshold', params.sizeThreshold)
if (params.redeemable) queryParams.append('redeemable', params.redeemable)
if (params.mergeable) queryParams.append('mergeable', params.mergeable)
if (params.sortBy) queryParams.append('sortBy', params.sortBy)
if (params.sortDirection) queryParams.append('sortDirection', params.sortDirection)
if (params.title) queryParams.append('title', params.title)
if (params.limit) queryParams.append('limit', params.limit)
if (params.offset) queryParams.append('offset', params.offset)
return `${buildDataUrl('/positions')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_positions')
}
const rawPositions = Array.isArray(data) ? data : []
const positions: PolymarketPosition[] = rawPositions.map((p: Record<string, unknown>) => ({
proxyWallet: (p.proxyWallet as string) ?? null,
asset: (p.asset as string) ?? '',
conditionId: (p.conditionId as string) ?? '',
size: (p.size as number) ?? 0,
avgPrice: (p.avgPrice as number) ?? 0,
initialValue: (p.initialValue as number) ?? 0,
currentValue: (p.currentValue as number) ?? 0,
cashPnl: (p.cashPnl as number) ?? 0,
percentPnl: (p.percentPnl as number) ?? 0,
totalBought: (p.totalBought as number) ?? 0,
realizedPnl: (p.realizedPnl as number) ?? 0,
percentRealizedPnl: (p.percentRealizedPnl as number) ?? 0,
curPrice: (p.curPrice as number) ?? 0,
redeemable: (p.redeemable as boolean) ?? false,
mergeable: (p.mergeable as boolean) ?? false,
title: (p.title as string) ?? null,
slug: (p.slug as string) ?? null,
icon: (p.icon as string) ?? null,
eventSlug: (p.eventSlug as string) ?? null,
outcome: (p.outcome as string) ?? null,
outcomeIndex: (p.outcomeIndex as number) ?? null,
oppositeOutcome: (p.oppositeOutcome as string) ?? null,
oppositeAsset: (p.oppositeAsset as string) ?? null,
endDate: (p.endDate as string) ?? null,
negativeRisk: (p.negativeRisk as boolean) ?? false,
}))
return {
success: true,
output: {
positions,
},
}
},
outputs: {
positions: {
type: 'array',
description: 'Array of position objects',
items: {
type: 'object',
properties: {
proxyWallet: { type: 'string', description: 'Proxy wallet address' },
asset: { type: 'string', description: 'Asset token ID' },
conditionId: { type: 'string', description: 'Condition ID' },
size: { type: 'number', description: 'Position size' },
avgPrice: { type: 'number', description: 'Average price' },
initialValue: { type: 'number', description: 'Initial value' },
currentValue: { type: 'number', description: 'Current value' },
cashPnl: { type: 'number', description: 'Cash profit/loss' },
percentPnl: { type: 'number', description: 'Percent profit/loss' },
totalBought: { type: 'number', description: 'Total bought' },
realizedPnl: { type: 'number', description: 'Realized profit/loss' },
percentRealizedPnl: { type: 'number', description: 'Percent realized profit/loss' },
curPrice: { type: 'number', description: 'Current price' },
redeemable: { type: 'boolean', description: 'Whether position is redeemable' },
mergeable: { type: 'boolean', description: 'Whether position is mergeable' },
title: { type: 'string', description: 'Market title' },
slug: { type: 'string', description: 'Market slug' },
icon: { type: 'string', description: 'Market icon URL' },
eventSlug: { type: 'string', description: 'Event slug' },
outcome: { type: 'string', description: 'Outcome name' },
outcomeIndex: { type: 'number', description: 'Outcome index' },
oppositeOutcome: { type: 'string', description: 'Opposite outcome name' },
oppositeAsset: { type: 'string', description: 'Opposite asset token ID' },
endDate: { type: 'string', description: 'End date' },
negativeRisk: { type: 'boolean', description: 'Whether negative risk' },
},
},
},
},
}
+76
View File
@@ -0,0 +1,76 @@
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetPriceParams {
tokenId: string // The token ID (CLOB token ID from market)
side: string // 'buy' or 'sell'
}
export interface PolymarketGetPriceResponse {
success: boolean
output: {
price: string
}
}
export const polymarketGetPriceTool: ToolConfig<
PolymarketGetPriceParams,
PolymarketGetPriceResponse
> = {
id: 'polymarket_get_price',
name: 'Get Price from Polymarket',
description: 'Retrieve the market price for a specific token and side',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
side: {
type: 'string',
required: true,
description: 'Order side: "buy" or "sell".',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
queryParams.append('side', params.side.toUpperCase())
return `${buildClobUrl('/price')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_price')
}
// API returns { price: 0.45 }
return {
success: true,
output: {
price: String(data.price ?? ''),
},
}
},
outputs: {
price: {
type: 'string',
description: 'Market price',
},
},
}
@@ -0,0 +1,113 @@
import type { PolymarketPriceHistoryEntry } from '@/tools/polymarket/types'
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetPriceHistoryParams {
tokenId: string
interval?: string
fidelity?: number
startTs?: number
endTs?: number
}
export interface PolymarketGetPriceHistoryResponse {
success: boolean
output: {
history: PolymarketPriceHistoryEntry[]
}
}
export const polymarketGetPriceHistoryTool: ToolConfig<
PolymarketGetPriceHistoryParams,
PolymarketGetPriceHistoryResponse
> = {
id: 'polymarket_get_price_history',
name: 'Get Price History from Polymarket',
description: 'Retrieve historical price data for a specific market token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
interval: {
type: 'string',
required: false,
description:
'Duration ending at current time (1m, 1h, 6h, 1d, 1w, max). Mutually exclusive with startTs/endTs.',
visibility: 'user-or-llm',
},
fidelity: {
type: 'number',
required: false,
description: 'Data resolution in minutes (e.g., 60 for hourly)',
visibility: 'user-or-llm',
},
startTs: {
type: 'number',
required: false,
description: 'Start timestamp (Unix seconds UTC)',
visibility: 'user-or-llm',
},
endTs: {
type: 'number',
required: false,
description: 'End timestamp (Unix seconds UTC)',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('market', params.tokenId)
if (params.interval) queryParams.append('interval', params.interval)
if (params.fidelity != null && !Number.isNaN(params.fidelity))
queryParams.append('fidelity', String(params.fidelity))
if (params.startTs != null && !Number.isNaN(params.startTs))
queryParams.append('startTs', String(params.startTs))
if (params.endTs != null && !Number.isNaN(params.endTs))
queryParams.append('endTs', String(params.endTs))
return `${buildClobUrl('/prices-history')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_price_history')
}
const history = data.history || (Array.isArray(data) ? data : [])
return {
success: true,
output: {
history,
},
}
},
outputs: {
history: {
type: 'array',
description: 'Array of price history entries',
items: {
type: 'object',
properties: {
t: { type: 'number', description: 'Unix timestamp' },
p: { type: 'number', description: 'Price at timestamp' },
},
},
},
},
}
+122
View File
@@ -0,0 +1,122 @@
import type { PolymarketPaginationParams, PolymarketSeries } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetSeriesParams extends PolymarketPaginationParams {}
export interface PolymarketGetSeriesResponse {
success: boolean
output: {
series: PolymarketSeries[]
}
}
export const polymarketGetSeriesTool: ToolConfig<
PolymarketGetSeriesParams,
PolymarketGetSeriesResponse
> = {
id: 'polymarket_get_series',
name: 'Get Series from Polymarket',
description: 'Retrieve series (related market groups) from Polymarket',
version: '1.0.0',
params: {
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "25"). Max: 50.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "50").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
// Default limit to 50 to prevent browser crashes from large data sets
queryParams.append('limit', params.limit || '50')
if (params.offset) queryParams.append('offset', params.offset)
const query = queryParams.toString()
const url = buildGammaUrl('/series')
return `${url}?${query}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_series')
}
// Response is an array of series - each series can contain thousands of nested events
// Strip the events array to prevent browser crashes (use get_events to fetch events separately)
const series = Array.isArray(data)
? data.map((s: any) => ({
id: s.id,
ticker: s.ticker,
slug: s.slug,
title: s.title,
seriesType: s.seriesType,
recurrence: s.recurrence,
image: s.image,
icon: s.icon,
active: s.active,
closed: s.closed,
archived: s.archived,
featured: s.featured,
restricted: s.restricted,
createdAt: s.createdAt,
updatedAt: s.updatedAt,
volume: s.volume,
liquidity: s.liquidity,
commentCount: s.commentCount,
eventCount: s.events?.length || 0, // Include count instead of full array
}))
: []
return {
success: true,
output: {
series,
},
}
},
outputs: {
series: {
type: 'array',
description: 'Array of series objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Series ID' },
ticker: { type: 'string', description: 'Series ticker' },
slug: { type: 'string', description: 'Series slug' },
title: { type: 'string', description: 'Series title' },
seriesType: { type: 'string', description: 'Series type' },
recurrence: { type: 'string', description: 'Recurrence pattern' },
image: { type: 'string', description: 'Series image URL' },
icon: { type: 'string', description: 'Series icon URL' },
active: { type: 'boolean', description: 'Whether series is active' },
closed: { type: 'boolean', description: 'Whether series is closed' },
archived: { type: 'boolean', description: 'Whether series is archived' },
featured: { type: 'boolean', description: 'Whether series is featured' },
volume: { type: 'number', description: 'Total volume' },
liquidity: { type: 'number', description: 'Total liquidity' },
eventCount: { type: 'number', description: 'Number of events in series' },
},
},
},
},
}
@@ -0,0 +1,82 @@
import type { PolymarketSeries } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetSeriesByIdParams {
seriesId: string // Series ID (required)
}
export interface PolymarketGetSeriesByIdResponse {
success: boolean
output: {
series: PolymarketSeries
}
}
export const polymarketGetSeriesByIdTool: ToolConfig<
PolymarketGetSeriesByIdParams,
PolymarketGetSeriesByIdResponse
> = {
id: 'polymarket_get_series_by_id',
name: 'Get Series by ID from Polymarket',
description: 'Retrieve a specific series (related market group) by ID from Polymarket',
version: '1.0.0',
params: {
seriesId: {
type: 'string',
required: true,
description: 'The numeric series ID (e.g., "12345").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => buildGammaUrl(`/series/${params.seriesId.trim()}`),
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_series_by_id')
}
return {
success: true,
output: {
series: data,
},
}
},
outputs: {
series: {
type: 'object',
description: 'Series object with details',
properties: {
id: { type: 'string', description: 'Series ID' },
ticker: { type: 'string', description: 'Series ticker' },
slug: { type: 'string', description: 'Series slug' },
title: { type: 'string', description: 'Series title' },
seriesType: { type: 'string', description: 'Series type' },
recurrence: { type: 'string', description: 'Recurrence pattern' },
image: { type: 'string', description: 'Series image URL' },
icon: { type: 'string', description: 'Series icon URL' },
active: { type: 'boolean', description: 'Whether series is active' },
closed: { type: 'boolean', description: 'Whether series is closed' },
archived: { type: 'boolean', description: 'Whether series is archived' },
featured: { type: 'boolean', description: 'Whether series is featured' },
volume: { type: 'number', description: 'Total volume' },
liquidity: { type: 'number', description: 'Total liquidity' },
commentCount: { type: 'number', description: 'Comment count' },
eventCount: { type: 'number', description: 'Number of events in series' },
events: { type: 'array', description: 'Array of events in this series' },
},
},
},
}
+73
View File
@@ -0,0 +1,73 @@
import type { PolymarketSpread } from '@/tools/polymarket/types'
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetSpreadParams {
tokenId: string // The token ID (CLOB token ID from market)
}
export interface PolymarketGetSpreadResponse {
success: boolean
output: {
spread: PolymarketSpread
}
}
export const polymarketGetSpreadTool: ToolConfig<
PolymarketGetSpreadParams,
PolymarketGetSpreadResponse
> = {
id: 'polymarket_get_spread',
name: 'Get Spread from Polymarket',
description: 'Retrieve the bid-ask spread for a specific token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
return `${buildClobUrl('/spread')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_spread')
}
return {
success: true,
output: {
spread: {
spread: data.spread ?? '',
},
},
}
},
outputs: {
spread: {
type: 'object',
description: 'Spread value between bid and ask',
properties: {
spread: { type: 'string', description: 'The spread value' },
},
},
},
}
+87
View File
@@ -0,0 +1,87 @@
import type { PolymarketPaginationParams, PolymarketTag } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetTagsParams extends PolymarketPaginationParams {}
export interface PolymarketGetTagsResponse {
success: boolean
output: {
tags: PolymarketTag[]
}
}
export const polymarketGetTagsTool: ToolConfig<PolymarketGetTagsParams, PolymarketGetTagsResponse> =
{
id: 'polymarket_get_tags',
name: 'Get Tags from Polymarket',
description: 'Retrieve available tags for filtering markets from Polymarket',
version: '1.0.0',
params: {
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "25"). Max: 50.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "50").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
// Default limit to 50 to prevent browser crashes from large data sets
queryParams.append('limit', params.limit || '50')
if (params.offset) queryParams.append('offset', params.offset)
const query = queryParams.toString()
const url = buildGammaUrl('/tags')
return `${url}?${query}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_tags')
}
// Response is an array of tags
const tags = Array.isArray(data) ? data : []
return {
success: true,
output: {
tags,
},
}
},
outputs: {
tags: {
type: 'array',
description: 'Array of tag objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tag ID' },
label: { type: 'string', description: 'Tag label' },
slug: { type: 'string', description: 'Tag slug' },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
},
},
},
},
}
@@ -0,0 +1,71 @@
import { buildClobUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetTickSizeParams {
tokenId: string // The token ID (CLOB token ID from market)
}
export interface PolymarketGetTickSizeResponse {
success: boolean
output: {
tickSize: string
}
}
export const polymarketGetTickSizeTool: ToolConfig<
PolymarketGetTickSizeParams,
PolymarketGetTickSizeResponse
> = {
id: 'polymarket_get_tick_size',
name: 'Get Tick Size from Polymarket',
description: 'Retrieve the minimum tick size for a specific token',
version: '1.0.0',
params: {
tokenId: {
type: 'string',
required: true,
description:
'The CLOB token ID from market clobTokenIds array (e.g., "71321045679252212594626385532706912750332728571942532289631379312455583992563").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('token_id', params.tokenId)
return `${buildClobUrl('/tick-size')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_tick_size')
}
// API returns { minimum_tick_size: "0.01" }
const tickSize =
typeof data === 'string' ? data : data.minimum_tick_size || data.tick_size || ''
return {
success: true,
output: {
tickSize: String(tickSize),
},
}
},
outputs: {
tickSize: {
type: 'string',
description: 'Minimum tick size',
},
},
}
+181
View File
@@ -0,0 +1,181 @@
import type { PolymarketTrade } from '@/tools/polymarket/types'
import { buildDataUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketGetTradesParams {
user?: string
market?: string
eventId?: string
side?: string
takerOnly?: string
filterType?: string
filterAmount?: string
limit?: string
offset?: string
}
export interface PolymarketGetTradesResponse {
success: boolean
output: {
trades: PolymarketTrade[]
}
}
export const polymarketGetTradesTool: ToolConfig<
PolymarketGetTradesParams,
PolymarketGetTradesResponse
> = {
id: 'polymarket_get_trades',
name: 'Get Trades from Polymarket',
description: 'Retrieve trade history from Polymarket',
version: '1.0.0',
params: {
user: {
type: 'string',
required: false,
description: 'User wallet address to filter trades',
visibility: 'user-or-llm',
},
market: {
type: 'string',
required: false,
description:
'Market/condition ID to filter trades (e.g., "0x1234...abcd"). Mutually exclusive with eventId.',
visibility: 'user-or-llm',
},
eventId: {
type: 'string',
required: false,
description: 'Event ID to filter trades (e.g., "12345"). Mutually exclusive with market.',
visibility: 'user-or-llm',
},
side: {
type: 'string',
required: false,
description: 'Trade direction filter (BUY or SELL)',
visibility: 'user-or-llm',
},
takerOnly: {
type: 'string',
required: false,
description: 'Filter for taker trades only (true/false, default: true)',
visibility: 'user-or-llm',
},
filterType: {
type: 'string',
required: false,
description: 'Filter type (CASH or TOKENS) - requires filterAmount',
visibility: 'user-or-llm',
},
filterAmount: {
type: 'string',
required: false,
description: 'Filter amount threshold - requires filterType',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Number of results per page (e.g., "50"). Default: 100, max: 10000.',
visibility: 'user-or-llm',
},
offset: {
type: 'string',
required: false,
description: 'Number of results to skip for pagination (e.g., "100").',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.user) queryParams.append('user', params.user)
if (params.market) queryParams.append('market', params.market)
if (params.eventId) queryParams.append('eventId', params.eventId)
if (params.side) queryParams.append('side', params.side.toUpperCase())
if (params.takerOnly) queryParams.append('takerOnly', params.takerOnly)
if (params.filterType) queryParams.append('filterType', params.filterType.toUpperCase())
if (params.filterAmount) queryParams.append('filterAmount', params.filterAmount)
if (params.limit) queryParams.append('limit', params.limit)
if (params.offset) queryParams.append('offset', params.offset)
const url = buildDataUrl('/trades')
return `${url}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'get_trades')
}
const rawTrades = Array.isArray(data) ? data : []
const trades: PolymarketTrade[] = rawTrades.map((t: Record<string, unknown>) => ({
proxyWallet: (t.proxyWallet as string) ?? null,
side: (t.side as string) ?? '',
asset: (t.asset as string) ?? '',
conditionId: (t.conditionId as string) ?? '',
size: (t.size as number) ?? 0,
price: (t.price as number) ?? 0,
timestamp: (t.timestamp as number) ?? 0,
title: (t.title as string) ?? null,
slug: (t.slug as string) ?? null,
icon: (t.icon as string) ?? null,
eventSlug: (t.eventSlug as string) ?? null,
outcome: (t.outcome as string) ?? null,
outcomeIndex: (t.outcomeIndex as number) ?? null,
name: (t.name as string) ?? null,
pseudonym: (t.pseudonym as string) ?? null,
bio: (t.bio as string) ?? null,
profileImage: (t.profileImage as string) ?? null,
profileImageOptimized: (t.profileImageOptimized as string) ?? null,
transactionHash: (t.transactionHash as string) ?? null,
}))
return {
success: true,
output: {
trades,
},
}
},
outputs: {
trades: {
type: 'array',
description: 'Array of trade objects',
items: {
type: 'object',
properties: {
proxyWallet: { type: 'string', description: 'Proxy wallet address' },
side: { type: 'string', description: 'Trade side (BUY or SELL)' },
asset: { type: 'string', description: 'Asset token ID' },
conditionId: { type: 'string', description: 'Condition ID' },
size: { type: 'number', description: 'Trade size' },
price: { type: 'number', description: 'Trade price' },
timestamp: { type: 'number', description: 'Unix timestamp' },
title: { type: 'string', description: 'Market title' },
slug: { type: 'string', description: 'Market slug' },
icon: { type: 'string', description: 'Market icon URL' },
eventSlug: { type: 'string', description: 'Event slug' },
outcome: { type: 'string', description: 'Outcome name' },
outcomeIndex: { type: 'number', description: 'Outcome index' },
name: { type: 'string', description: 'Trader name' },
pseudonym: { type: 'string', description: 'Trader pseudonym' },
bio: { type: 'string', description: 'Trader bio' },
profileImage: { type: 'string', description: 'Profile image URL' },
profileImageOptimized: { type: 'string', description: 'Optimized profile image URL' },
transactionHash: { type: 'string', description: 'Transaction hash' },
},
},
},
},
}
+20
View File
@@ -0,0 +1,20 @@
export { polymarketGetActivityTool } from './get_activity'
export { polymarketGetEventTool } from './get_event'
export { polymarketGetEventsTool } from './get_events'
export { polymarketGetHoldersTool } from './get_holders'
export { polymarketGetLastTradePriceTool } from './get_last_trade_price'
export { polymarketGetLeaderboardTool } from './get_leaderboard'
export { polymarketGetMarketTool } from './get_market'
export { polymarketGetMarketsTool } from './get_markets'
export { polymarketGetMidpointTool } from './get_midpoint'
export { polymarketGetOrderbookTool } from './get_orderbook'
export { polymarketGetPositionsTool } from './get_positions'
export { polymarketGetPriceTool } from './get_price'
export { polymarketGetPriceHistoryTool } from './get_price_history'
export { polymarketGetSeriesTool } from './get_series'
export { polymarketGetSeriesByIdTool } from './get_series_by_id'
export { polymarketGetSpreadTool } from './get_spread'
export { polymarketGetTagsTool } from './get_tags'
export { polymarketGetTickSizeTool } from './get_tick_size'
export { polymarketGetTradesTool } from './get_trades'
export { polymarketSearchTool } from './search'
+175
View File
@@ -0,0 +1,175 @@
import type { PolymarketSearchResult } from '@/tools/polymarket/types'
import { buildGammaUrl, handlePolymarketError } from '@/tools/polymarket/types'
import type { ToolConfig } from '@/tools/types'
export interface PolymarketSearchParams {
query: string
limit?: string
page?: string
cache?: string
eventsStatus?: string
eventsTag?: string
sort?: string
ascending?: string
searchTags?: string
searchProfiles?: string
recurrence?: string
excludeTagId?: string
keepClosedMarkets?: string
}
export interface PolymarketSearchResponse {
success: boolean
output: {
results: PolymarketSearchResult
}
}
export const polymarketSearchTool: ToolConfig<PolymarketSearchParams, PolymarketSearchResponse> = {
id: 'polymarket_search',
name: 'Search Polymarket',
description: 'Search for markets, events, and profiles on Polymarket',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
description: 'Search query term (e.g., "presidential election", "bitcoin price").',
visibility: 'user-or-llm',
},
limit: {
type: 'string',
required: false,
description: 'Maximum number of results per type (events, tags, profiles). Default: 50.',
visibility: 'user-or-llm',
},
page: {
type: 'string',
required: false,
description: 'Page number for pagination (e.g., "2"). 1-indexed.',
visibility: 'user-or-llm',
},
cache: {
type: 'string',
required: false,
description: 'Enable caching (true/false)',
visibility: 'user-or-llm',
},
eventsStatus: {
type: 'string',
required: false,
description: 'Filter events by status',
visibility: 'user-or-llm',
},
eventsTag: {
type: 'string',
required: false,
description: 'Filter by event tags (comma-separated)',
visibility: 'user-or-llm',
},
sort: {
type: 'string',
required: false,
description: 'Sort field',
visibility: 'user-or-llm',
},
ascending: {
type: 'string',
required: false,
description: 'Sort direction (true for ascending, false for descending)',
visibility: 'user-or-llm',
},
searchTags: {
type: 'string',
required: false,
description: 'Include tags in search results (true/false)',
visibility: 'user-or-llm',
},
searchProfiles: {
type: 'string',
required: false,
description: 'Include profiles in search results (true/false)',
visibility: 'user-or-llm',
},
recurrence: {
type: 'string',
required: false,
description: 'Filter by recurrence type',
visibility: 'user-or-llm',
},
excludeTagId: {
type: 'string',
required: false,
description: 'Exclude events with these tag IDs (comma-separated)',
visibility: 'user-or-llm',
},
keepClosedMarkets: {
type: 'string',
required: false,
description: 'Include closed markets in results (0 or 1)',
visibility: 'user-or-llm',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
queryParams.append('q', params.query)
// /public-search caps results via limit_per_type; a plain `limit` is ignored
queryParams.append('limit_per_type', params.limit || '50')
if (params.page) queryParams.append('page', params.page)
if (params.cache) queryParams.append('cache', params.cache)
if (params.eventsStatus) queryParams.append('events_status', params.eventsStatus)
if (params.eventsTag) queryParams.append('events_tag', params.eventsTag)
if (params.sort) queryParams.append('sort', params.sort)
if (params.ascending) queryParams.append('ascending', params.ascending)
if (params.searchTags) queryParams.append('search_tags', params.searchTags)
if (params.searchProfiles) queryParams.append('search_profiles', params.searchProfiles)
if (params.recurrence) queryParams.append('recurrence', params.recurrence)
if (params.excludeTagId) queryParams.append('exclude_tag_id', params.excludeTagId)
if (params.keepClosedMarkets)
queryParams.append('keep_closed_markets', params.keepClosedMarkets)
return `${buildGammaUrl('/public-search')}?${queryParams.toString()}`
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
handlePolymarketError(data, response.status, 'search')
}
// /public-search returns events, tags, and profiles (markets are nested within events)
const results: PolymarketSearchResult = {
events: data.events ?? [],
tags: data.tags ?? [],
profiles: data.profiles ?? [],
}
return {
success: true,
output: {
results,
},
}
},
outputs: {
results: {
type: 'object',
description: 'Search results containing events, tags, and profiles arrays',
properties: {
events: { type: 'array', description: 'Array of matching event objects (markets nested)' },
tags: { type: 'array', description: 'Array of matching tag objects' },
profiles: { type: 'array', description: 'Array of matching profile objects' },
},
},
},
}
+278
View File
@@ -0,0 +1,278 @@
const POLYMARKET_GAMMA_URL = 'https://gamma-api.polymarket.com'
export const POLYMARKET_CLOB_URL = 'https://clob.polymarket.com'
export const POLYMARKET_DATA_URL = 'https://data-api.polymarket.com'
export function buildGammaUrl(path: string): string {
return `${POLYMARKET_GAMMA_URL}${path}`
}
export function buildClobUrl(path: string): string {
return `${POLYMARKET_CLOB_URL}${path}`
}
export function buildDataUrl(path: string): string {
return `${POLYMARKET_DATA_URL}${path}`
}
export interface PolymarketPaginationParams {
limit?: string
offset?: string
}
interface PolymarketPagingInfo {
limit: number
offset: number
count: number
}
export interface PolymarketMarket {
id: string
question: string
conditionId: string
slug: string
resolutionSource: string
endDate: string
liquidity: string
startDate: string
image: string
icon: string
description: string
outcomes: string
outcomePrices: string
volume: string
active: boolean
closed: boolean
marketMakerAddress: string
createdAt: string
updatedAt: string
new: boolean
featured: boolean
submitted_by: string
archived: boolean
resolvedBy: string
restricted: boolean
groupItemTitle: string
groupItemThreshold: string
questionID: string
enableOrderBook: boolean
orderPriceMinTickSize: number
orderMinSize: number
volumeNum: number
liquidityNum: number
clobTokenIds: string[]
acceptingOrders: boolean
negRisk: boolean
}
export interface PolymarketEvent {
id: string
ticker: string
slug: string
title: string
description: string
startDate: string
creationDate: string
endDate: string
image: string
icon: string
active: boolean
closed: boolean
archived: boolean
new: boolean
featured: boolean
restricted: boolean
liquidity: number
volume: number
openInterest: number
commentCount: number
markets: PolymarketMarket[]
}
export interface PolymarketTag {
id: string
label: string
slug: string
createdAt?: string
updatedAt?: string
forceShow?: boolean
forceHide?: boolean
isCarousel?: boolean
}
interface PolymarketOrderBookEntry {
price: string
size: string
}
export interface PolymarketOrderBook {
market: string
asset_id: string
hash: string
timestamp: string
bids: PolymarketOrderBookEntry[]
asks: PolymarketOrderBookEntry[]
min_order_size: string
tick_size: string
neg_risk: boolean
last_trade_price: string
}
interface PolymarketPrice {
price: string
}
export interface PolymarketPriceHistoryEntry {
t: number
p: number
}
export interface PolymarketSeries {
id: string
ticker: string
slug: string
title: string
seriesType: string
recurrence: string
image: string
icon: string
active: boolean
closed: boolean
archived: boolean
featured: boolean
restricted: boolean
createdAt: string
updatedAt: string
volume: number
liquidity: number
commentCount: number
eventCount: number
}
export interface PolymarketSearchResult {
events: PolymarketEvent[]
tags: PolymarketTag[]
profiles: PolymarketProfile[]
}
interface PolymarketProfile {
id: string
name: string | null
pseudonym: string | null
bio: string | null
profileImage: string | null
profileImageOptimized: string | null
walletAddress: string
}
export interface PolymarketSpread {
spread: string
}
export interface PolymarketPosition {
proxyWallet: string | null
asset: string
conditionId: string
size: number
avgPrice: number
initialValue: number
currentValue: number
cashPnl: number
percentPnl: number
totalBought: number
realizedPnl: number
percentRealizedPnl: number
curPrice: number
redeemable: boolean
mergeable: boolean
title: string | null
slug: string | null
icon: string | null
eventSlug: string | null
outcome: string | null
outcomeIndex: number | null
oppositeOutcome: string | null
oppositeAsset: string | null
endDate: string | null
negativeRisk: boolean
}
export interface PolymarketTrade {
proxyWallet: string | null
side: string
asset: string
conditionId: string
size: number
price: number
timestamp: number
title: string | null
slug: string | null
icon: string | null
eventSlug: string | null
outcome: string | null
outcomeIndex: number | null
name: string | null
pseudonym: string | null
bio: string | null
profileImage: string | null
profileImageOptimized: string | null
transactionHash: string | null
}
export interface PolymarketActivity {
proxyWallet: string | null
timestamp: number
conditionId: string
type: string
size: number
usdcSize: number
transactionHash: string | null
price: number | null
asset: string | null
side: string | null
outcomeIndex: number | null
title: string | null
slug: string | null
icon: string | null
eventSlug: string | null
outcome: string | null
name: string | null
pseudonym: string | null
bio: string | null
profileImage: string | null
profileImageOptimized: string | null
}
export interface PolymarketLeaderboardEntry {
rank: string
proxyWallet: string
userName: string | null
vol: number
pnl: number
profileImage: string | null
xUsername: string | null
verifiedBadge: boolean
}
interface PolymarketHolder {
proxyWallet: string
bio: string | null
asset: string
pseudonym: string | null
amount: number
displayUsernamePublic: boolean
outcomeIndex: number
name: string | null
profileImage: string | null
profileImageOptimized: string | null
verified: boolean
}
export interface PolymarketMarketHolders {
token: string
holders: PolymarketHolder[]
}
export function handlePolymarketError(data: any, status: number, operation: string): never {
const errorMessage = data?.message || data?.error || `Unknown error during ${operation}`
throw new Error(`Polymarket API error (${status}): ${errorMessage}`)
}