get_recent_activity
Retrieve recent splits, merges, and redemptions from Polymarket's Activity subgraph, with optional filter by Ethereum address.
Instructions
Get recent splits, merges, and redemptions from the Activity subgraph
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| first | No | Number of events to return | |
| account | No | Optional: filter by Ethereum address |
Implementation Reference
- src/index.ts:260-344 (handler)The get_recent_activity tool handler: queries the Activity subgraph for splits, merges, and redemptions, and the Orderbook subgraph for order fills (with optional account filtering). Deduplicates fills and sorts all events by timestamp, returning a unified feed.
// Tool 7: get_recent_activity // --------------------------------------------------------------------------- server.registerTool( "get_recent_activity", { description: "Get recent splits, merges, and redemptions from the Activity subgraph", inputSchema: { first: z.number().min(1).max(100).default(20).describe("Number of events to return"), account: z.string().optional().describe("Optional: filter by Ethereum address"), }, }, async ({ first, account }) => { try { const addr = account?.toLowerCase(); const stakeholderWhere = addr ? `, where: { stakeholder: "${addr}" }` : ""; const redeemerWhere = addr ? `, where: { redeemer: "${addr}" }` : ""; const activityQuery = `{ splits(first: ${first}, orderBy: timestamp, orderDirection: desc${stakeholderWhere}) { id stakeholder amount timestamp } merges(first: ${first}, orderBy: timestamp, orderDirection: desc${stakeholderWhere}) { id stakeholder amount timestamp } redemptions(first: ${first}, orderBy: timestamp, orderDirection: desc${redeemerWhere}) { id redeemer payout indexSets timestamp } }`; // Fetch OB fills: if account-filtered, query as maker + taker separately; else recent fills const obQuery = addr ? `{ makerFills: orderFilledEvents(first: ${first}, orderBy: timestamp, orderDirection: desc, where: { maker: "${addr}" }) { id maker taker price side fee makerAmountFilled takerAmountFilled timestamp } takerFills: orderFilledEvents(first: ${first}, orderBy: timestamp, orderDirection: desc, where: { taker: "${addr}" }) { id maker taker price side fee makerAmountFilled takerAmountFilled timestamp } }` : `{ orderFilledEvents(first: ${first}, orderBy: timestamp, orderDirection: desc) { id maker taker price side fee makerAmountFilled takerAmountFilled timestamp } }`; const [actData, obData] = await Promise.all([ querySubgraph(SUBGRAPHS.activity.ipfsHash, activityQuery), querySubgraph(SUBGRAPHS.orderbook.ipfsHash, obQuery).catch(() => null), ]); type EventRecord = { eventType: string; timestamp: string; [key: string]: unknown }; const ad = actData as { splits?: Array<{ id: string; stakeholder: string; amount: string; timestamp: string }>; merges?: Array<{ id: string; stakeholder: string; amount: string; timestamp: string }>; redemptions?: Array<{ id: string; redeemer: string; payout: string; timestamp: string }>; }; const od = obData as { orderFilledEvents?: Array<{ id: string; timestamp: string }>; makerFills?: Array<{ id: string; timestamp: string }>; takerFills?: Array<{ id: string; timestamp: string }>; } | null; const events: EventRecord[] = [ ...(ad.splits ?? []).map((e) => ({ eventType: "split", ...e })), ...(ad.merges ?? []).map((e) => ({ eventType: "merge", ...e })), ...(ad.redemptions ?? []).map((e) => ({ eventType: "redemption", ...e })), ]; const rawFills = od ? addr ? [ ...(od.makerFills ?? []).map((e) => ({ eventType: "ob_fill_maker", ...e })), ...(od.takerFills ?? []).map((e) => ({ eventType: "ob_fill_taker", ...e })), ] : (od.orderFilledEvents ?? []).map((e) => ({ eventType: "ob_fill", ...e })) : []; // Deduplicate fills (same fill can appear as both maker and taker) const seen = new Set<string>(); for (const e of rawFills) { if (!seen.has(e.id as string)) { seen.add(e.id as string); events.push(e as EventRecord); } } events.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp)); return textResult({ feed: events.slice(0, first * 2) }); } catch (error) { return errorResult(error); } } ); - src/index.ts:260-269 (schema)Input schema for get_recent_activity: 'first' (1-100, default 20) controls event count, 'account' (optional Ethereum address) filters by stakeholder/redeemer.
// Tool 7: get_recent_activity // --------------------------------------------------------------------------- server.registerTool( "get_recent_activity", { description: "Get recent splits, merges, and redemptions from the Activity subgraph", inputSchema: { first: z.number().min(1).max(100).default(20).describe("Number of events to return"), account: z.string().optional().describe("Optional: filter by Ethereum address"), }, - src/index.ts:262-263 (registration)Tool registration call: server.registerTool('get_recent_activity', ...) registers the tool with the MCP server. The handler (line 271) and schema (lines 264-269) are provided inline.
server.registerTool( "get_recent_activity", - src/subgraphs.ts:45-57 (helper)Subgraph configuration for 'activity' subgraph (ipfsHash Qmf3qPUsfQ8et6E3QNBmuXXKqUJi91mo5zbsaTkQrSnMAP) used by get_recent_activity to query Split, Merge, and Redemption entities. Also uses the orderbook subgraph (lines 58-70).
activity: { name: "Activity", ipfsHash: "Qmf3qPUsfQ8et6E3QNBmuXXKqUJi91mo5zbsaTkQrSnMAP", description: "Event log for position management operations. Tracks splits (minting outcome tokens), merges (combining tokens back to collateral), and redemptions (claiming payouts from resolved markets). Best for: monitoring position lifecycle events, tracking when users enter/exit markets, and auditing collateral flows.", keyEntities: [ "Split (stakeholder, condition, amount, timestamp)", "Merge (stakeholder, condition, amount, timestamp)", "Redemption (redeemer, condition, payout, indexSets)", "NegRiskConversion", "NegRiskEvent", ], }, - src/graphClient.ts:12-59 (helper)The querySubgraph helper used by the handler to execute GraphQL queries against The Graph Network gateway, passing the ipfsHash and query string.
export async function querySubgraph( ipfsHash: string, query: string, variables?: Record<string, unknown> ): Promise<unknown> { const apiKey = process.env.GRAPH_API_KEY; if (!apiKey) { throw new GraphClientError( "GRAPH_API_KEY environment variable is required. " + "Get one at https://thegraph.com/studio/apikeys/" ); } const url = `https://gateway.thegraph.com/api/${apiKey}/deployments/id/${ipfsHash}`; const body: Record<string, unknown> = { query }; if (variables && Object.keys(variables).length > 0) { body.variables = variables; } const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) { throw new GraphClientError( `Graph API returned HTTP ${response.status}: ${response.statusText}`, response.status ); } const json = (await response.json()) as { data?: unknown; errors?: unknown[]; }; if (json.errors && json.errors.length > 0) { throw new GraphClientError( `GraphQL errors: ${JSON.stringify(json.errors)}`, undefined, json.errors ); } return json.data; }