get_app_positions
Retrieve DeFi positions such as lending, LP shares, and staking for any wallet address. Filter by network or app slug like 'aave-v3' to get specific protocol exposure.
Instructions
DeFi app positions only (Aave lending, Uniswap LP, staking, etc.). Use when the question is about protocol exposure: 'any leveraged positions?', 'Aave borrows?', 'LP positions on Uniswap?'. Optionally filter by app slug.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | Wallet address or ENS name | |
| networks | No | Networks to filter by. Supported: ethereum, base, optimism, arbitrum, polygon, bnb, avalanche, zora. Omit for all networks. | |
| app_slug | No | Filter to a specific app slug, e.g. 'aave-v3', 'uniswap-v3' |
Implementation Reference
- src/zapper.ts:350-371 (handler)Core handler that fetches DeFi app positions from Zapper GraphQL API. Calls APP_POSITIONS_QUERY, parses results and optionally filters by app slug.
export async function fetchAppPositions( apiKey: string, address: string, chainIds?: number[], appSlug?: string, ): Promise<AppPositionsResult> { const data = (await gql(apiKey, APP_POSITIONS_QUERY, { addresses: [address], chainIds: chainIds ?? null, })) as { portfolioV2: { appBalances: { totalBalanceUSD: number; byApp: { edges: Array<{ node: GqlAppNode }> } } } }; const ab = data.portfolioV2.appBalances; let apps = ab.byApp.edges.map(({ node }) => parseAppNode(node)); if (appSlug) { apps = apps.filter((a) => a.slug === appSlug); } return { totalUSD: appSlug ? apps.reduce((s, a) => s + a.balanceUSD, 0) : (ab.totalBalanceUSD ?? 0), apps, }; - src/server.ts:94-123 (registration)Registers the 'get_app_positions' tool on the MCP server with description, input schema (address, networks, app_slug), and invokes fetchAppPositions.
server.registerTool( "get_app_positions", { description: "DeFi app positions only (Aave lending, Uniswap LP, staking, etc.). Use when the question is about protocol exposure: 'any leveraged positions?', 'Aave borrows?', 'LP positions on Uniswap?'. Optionally filter by app slug.", inputSchema: { address: z.string().describe("Wallet address or ENS name"), networks: networksSchema, app_slug: z .string() .optional() .describe("Filter to a specific app slug, e.g. 'aave-v3', 'uniswap-v3'"), }, }, async ({ address, networks, app_slug }) => { try { const result = await fetchAppPositions( apiKey, address, resolveChainIds(networks), app_slug, ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } catch (err) { return errorResponse(err); } }, ); - src/zapper.ts:57-60 (schema)Type interface for the result of fetchAppPositions: totalUSD and an array of AppBalance objects.
export interface AppPositionsResult { totalUSD: number; apps: AppBalance[]; } - src/zapper.ts:49-55 (schema)Type interface for a single app balance entry, containing name, slug, network, balanceUSD, and an array of positions.
export interface AppBalance { name: string; slug: string; network: string; balanceUSD: number; positions: AppPosition[]; } - src/zapper.ts:304-331 (helper)Parses a GraphQL app node into the AppBalance shape, extracting positions with underlying token details.
function parseAppNode(node: GqlAppNode): AppBalance { const positions: AppPosition[] = node.positionBalances.edges.map(({ node: pos }) => { const underlying = (pos.tokens ?? []).map((t) => { const inner = t.token ?? t; return { symbol: inner.symbol ?? "", balance: typeof inner.balance === "string" ? parseFloat(inner.balance) : (inner.balance ?? 0), balanceUSD: inner.balanceUSD ?? 0, }; }); return { type: pos.type === "contract-position" ? "contract_position" : "app_token", symbol: pos.symbol, balance: pos.balance !== undefined ? typeof pos.balance === "string" ? parseFloat(pos.balance) : pos.balance : undefined, balanceUSD: pos.balanceUSD ?? 0, underlying, }; }); return { name: node.app.displayName, slug: node.app.slug, network: node.network.name, balanceUSD: node.balanceUSD ?? 0, positions, }; }