get_user_positions
Retrieve a user's current positions from Polymarket's P&L subgraph. Automatically falls back if indexers are lagging.
Instructions
Get a user's current positions from the Slimmed P&L subgraph. Falls back gracefully if indexers are behind.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| account | Yes | Ethereum address of the user (lowercase) |
Implementation Reference
- src/index.ts:209-257 (registration)Tool registration for 'get_user_positions'. Uses server.registerTool to define the tool name, input schema (account as Ethereum address), and handler function. The handler queries the Slimmed P&L subgraph for user positions and cross-references with the Orderbook subgraph for reliability warnings.
server.registerTool( "get_user_positions", { description: "Get a user's current positions from the Slimmed P&L subgraph. Falls back gracefully if indexers are behind.", inputSchema: { account: z.string().describe("Ethereum address of the user (lowercase)"), }, }, async ({ account }) => { try { const addr = account.toLowerCase(); const posQuery = `{ userPositions(where: { user: "${addr}" }, first: 100) { id user tokenId amount avgPrice realizedPnl totalBought } }`; const obQuery = `{ account(id: "${addr}") { id totalVolume tradesQuantity totalFees } }`; const [posData, obData] = await Promise.all([ querySubgraph(SUBGRAPHS.slimmed_pnl.ipfsHash, posQuery), querySubgraph(SUBGRAPHS.orderbook.ipfsHash, obQuery).catch(() => null), ]); const pd = posData as { userPositions?: Array<{ totalBought: string }> }; const totalBought = (pd.userPositions ?? []).reduce( (sum, p) => sum + parseFloat(p.totalBought || "0"), 0 ); const od = obData as { account?: { totalVolume?: string; tradesQuantity?: string } } | null; const obVolume = parseFloat(od?.account?.totalVolume || "0"); const obTrades = parseInt(od?.account?.tradesQuantity || "0"); let reliabilityWarning: string | undefined; if (obVolume > 0 && totalBought === 0) { reliabilityWarning = `⚠ orderbook-only entry — no split collateral detected. OB volume: $${obVolume.toFixed(2)} across ${obTrades} trades. P&L from totalBought/avgPrice fields is unreliable.`; } else if (obVolume > 0 && obVolume > totalBought * 2 && obVolume - totalBought > 1000) { reliabilityWarning = `⚠ mixed entry — OB volume ($${obVolume.toFixed(2)}) significantly exceeds split collateral ($${totalBought.toFixed(2)}). Some positions entered via orderbook buys; P&L may be understated.`; } return textResult({ positions: pd.userPositions ?? [], orderbookAccount: od?.account ?? null, ...(reliabilityWarning ? { reliabilityWarning } : {}), }); } catch (error) { return errorResult(error); } } ); - src/index.ts:218-257 (handler)Handler function for get_user_positions. Queries 'userPositions' from the slimmed_pnl subgraph filtered by the user's address, and also fetches account data from the orderbook subgraph. Computes totalBought from positions, checks OB volume vs split collateral to produce reliability warnings (e.g., 'orderbook-only entry' or 'mixed entry'). Returns positions, orderbook account data, and optional warnings.
async ({ account }) => { try { const addr = account.toLowerCase(); const posQuery = `{ userPositions(where: { user: "${addr}" }, first: 100) { id user tokenId amount avgPrice realizedPnl totalBought } }`; const obQuery = `{ account(id: "${addr}") { id totalVolume tradesQuantity totalFees } }`; const [posData, obData] = await Promise.all([ querySubgraph(SUBGRAPHS.slimmed_pnl.ipfsHash, posQuery), querySubgraph(SUBGRAPHS.orderbook.ipfsHash, obQuery).catch(() => null), ]); const pd = posData as { userPositions?: Array<{ totalBought: string }> }; const totalBought = (pd.userPositions ?? []).reduce( (sum, p) => sum + parseFloat(p.totalBought || "0"), 0 ); const od = obData as { account?: { totalVolume?: string; tradesQuantity?: string } } | null; const obVolume = parseFloat(od?.account?.totalVolume || "0"); const obTrades = parseInt(od?.account?.tradesQuantity || "0"); let reliabilityWarning: string | undefined; if (obVolume > 0 && totalBought === 0) { reliabilityWarning = `⚠ orderbook-only entry — no split collateral detected. OB volume: $${obVolume.toFixed(2)} across ${obTrades} trades. P&L from totalBought/avgPrice fields is unreliable.`; } else if (obVolume > 0 && obVolume > totalBought * 2 && obVolume - totalBought > 1000) { reliabilityWarning = `⚠ mixed entry — OB volume ($${obVolume.toFixed(2)}) significantly exceeds split collateral ($${totalBought.toFixed(2)}). Some positions entered via orderbook buys; P&L may be understated.`; } return textResult({ positions: pd.userPositions ?? [], orderbookAccount: od?.account ?? null, ...(reliabilityWarning ? { reliabilityWarning } : {}), }); } catch (error) { return errorResult(error); } } ); - src/index.ts:212-216 (schema)Input schema for get_user_positions: requires a single parameter 'account' (string, Ethereum address), validated via Zod's z.string().
description: "Get a user's current positions from the Slimmed P&L subgraph. Falls back gracefully if indexers are behind.", inputSchema: { account: z.string().describe("Ethereum address of the user (lowercase)"), },