Holder Changes
holder_changesRetrieve recent changes to token holder records since a given timestamp. Returns up to 100 results per query.
Instructions
Get recent changes to token holder data since a given timestamp. Cost: $0.02 per query. Source: On-chain token analytics.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| since | Yes | ISO 8601 timestamp to get changes since (e.g. 2026-03-01T00:00:00Z) | |
| limit | No | Maximum results (default 50) |
Implementation Reference
- src/tools/holders.ts:157-183 (handler)The async handler function for the 'holder_changes' tool. Calls the API endpoint /api/v1/holders/changes with 'since' and 'limit' parameters, formats the response text with count summary and JSON data.
async ({ since, limit }) => { const res = await apiGet<HolderQueryResponse>("/api/v1/holders/changes", { since, limit: limit ?? 50, }); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } const { count, data } = res.data; const warn = stalenessWarning(res); const summary = `${warn}Found ${count} holder change(s) since ${since}.`; const json = JSON.stringify(data, null, 2); return { content: [{ type: "text" as const, text: `${summary}\n\n${json}` }], }; }, - src/tools/holders.ts:144-155 (schema)Input schema for holder_changes: 'since' (ISO 8601 timestamp, required) and 'limit' (integer 1-100, optional, default 50).
inputSchema: { since: z .string() .describe("ISO 8601 timestamp to get changes since (e.g. 2026-03-01T00:00:00Z)"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results (default 50)"), }, - src/tools/holders.ts:137-184 (registration)Registration call: server.registerTool('holder_changes', ...) with title 'Holder Changes' and description about cost ($0.02) and source (on-chain token analytics).
server.registerTool( "holder_changes", { title: "Holder Changes", description: "Get recent changes to token holder data since a given timestamp. " + "Cost: $0.02 per query. Source: On-chain token analytics.", inputSchema: { since: z .string() .describe("ISO 8601 timestamp to get changes since (e.g. 2026-03-01T00:00:00Z)"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results (default 50)"), }, }, async ({ since, limit }) => { const res = await apiGet<HolderQueryResponse>("/api/v1/holders/changes", { since, limit: limit ?? 50, }); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } const { count, data } = res.data; const warn = stalenessWarning(res); const summary = `${warn}Found ${count} holder change(s) since ${since}.`; const json = JSON.stringify(data, null, 2); return { content: [{ type: "text" as const, text: `${summary}\n\n${json}` }], }; }, ); - src/tools/holders.ts:28-218 (registration)The export function registerHolderTools(server) which is called from index.ts to register all holder tools including holder_changes.
export function registerHolderTools(server: McpServer): void { // ── Query top holders ───────────────────────────────────────────────── server.registerTool( "query_holders", { title: "Query Top Holders", description: "Get the top holders for a token contract address. Returns wallet addresses, " + "balances, percentage of supply, and holder labels (exchange, whale, contract). " + "Cost: $0.04 per query. Source: On-chain token analytics.", inputSchema: { token: z .string() .describe("Token contract address (e.g. 0xdAC17F958D2ee523a2206206994597C13D831ec7)"), chain: z .enum(["ethereum", "arbitrum", "polygon", "base", "bsc"]) .optional() .describe("Blockchain network (default: ethereum)"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results (default 25)"), }, }, async ({ token, chain, limit }) => { const res = await apiGet<HolderQueryResponse>( `/api/v1/holders/${encodeURIComponent(token)}`, { chain, limit: limit ?? 25, }, ); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } const { count, data } = res.data; const warn = stalenessWarning(res); const summary = `${warn}Found ${count} holder(s) for token ${token}.`; const json = JSON.stringify(data, null, 2); return { content: [{ type: "text" as const, text: `${summary}\n\n${json}` }], }; }, ); // ── Holder concentration ────────────────────────────────────────────── server.registerTool( "holder_concentration", { title: "Holder Concentration", description: "Get Gini coefficient and distribution metrics for a token. Shows concentration " + "risk, top-10/top-50 holder percentages, and supply distribution buckets. " + "Cost: $0.02 per query. Source: On-chain token analytics.", inputSchema: { token: z .string() .describe("Token contract address"), chain: z .enum(["ethereum", "arbitrum", "polygon", "base", "bsc"]) .optional() .describe("Blockchain network (default: ethereum)"), }, }, async ({ token, chain }) => { const res = await apiGet<{ dataset: string; data: Record<string, unknown> }>( `/api/v1/holders/${encodeURIComponent(token)}/concentration`, { chain }, ); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } const warn = stalenessWarning(res); return { content: [ { type: "text" as const, text: `${warn}${JSON.stringify(res.data.data, null, 2)}` }, ], }; }, ); // ── Change feed ─────────────────────────────────────────────────────── server.registerTool( "holder_changes", { title: "Holder Changes", description: "Get recent changes to token holder data since a given timestamp. " + "Cost: $0.02 per query. Source: On-chain token analytics.", inputSchema: { since: z .string() .describe("ISO 8601 timestamp to get changes since (e.g. 2026-03-01T00:00:00Z)"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results (default 50)"), }, }, async ({ since, limit }) => { const res = await apiGet<HolderQueryResponse>("/api/v1/holders/changes", { since, limit: limit ?? 50, }); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } const { count, data } = res.data; const warn = stalenessWarning(res); const summary = `${warn}Found ${count} holder change(s) since ${since}.`; const json = JSON.stringify(data, null, 2); return { content: [{ type: "text" as const, text: `${summary}\n\n${json}` }], }; }, ); // ── Dataset stats ───────────────────────────────────────────────────── server.registerTool( "holder_stats", { title: "Holder Dataset Statistics", description: "Get statistics about the token holder dataset: total tokens tracked, " + "total holder records, chains covered, last updated. Free endpoint.", inputSchema: {}, }, async () => { const res = await apiGet<HolderStatsResponse>("/api/v1/holders/stats"); if (!res.ok) { return { content: [ { type: "text" as const, text: `API error (${res.status}): ${JSON.stringify(res.data)}`, }, ], isError: true, }; } return { content: [ { type: "text" as const, text: JSON.stringify(res.data, null, 2) }, ], }; }, ); - src/client.ts:44-76 (helper)The apiGet helper function used by the holder_changes handler to make HTTP GET requests to the Verilex API.
export async function apiGet<T = unknown>( path: string, params?: Record<string, string | number | undefined>, ): Promise<ApiResponse<T>> { const url = buildUrl(path, params); const headers: Record<string, string> = { Accept: "application/json", "User-Agent": "verilex-mcp-server/0.1.0", }; // Forward x402 payment token if present in env (for paid endpoints) const paymentToken = process.env.VERILEX_PAYMENT_TOKEN; if (paymentToken) { headers["X-Payment-Token"] = paymentToken; } const res = await fetch(url, { headers }); const data = (await res.json()) as T; const stale = res.headers.get("X-Data-Stale"); const lastUpdated = res.headers.get("X-Data-Last-Updated"); const ageSeconds = res.headers.get("X-Data-Age-Seconds"); return { ok: res.ok, status: res.status, data, stale: stale === "true", lastUpdated: lastUpdated ?? undefined, ageSeconds: ageSeconds ? Number(ageSeconds) : undefined, }; }