From b9e7f067c5389e7a0230b5dda570adbd04d864bd Mon Sep 17 00:00:00 2001 From: Kevin J <6829515+kmjones1979@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:01:00 -0700 Subject: [PATCH] fixing endpoints --- .../.cursor/rules/thegraph-se2-agent.mdc | 8 +++ .../action-providers/token-api-provider.ts | 69 +++++++++++++++---- .../utils/chat/agentkit/token-api/schemas.ts | 20 +++++- .../utils/chat/agentkit/token-api/utils.ts | 69 ++++++++++++++++--- 4 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 agent/the-graph-agent-scaffold-eth/.cursor/rules/thegraph-se2-agent.mdc diff --git a/agent/the-graph-agent-scaffold-eth/.cursor/rules/thegraph-se2-agent.mdc b/agent/the-graph-agent-scaffold-eth/.cursor/rules/thegraph-se2-agent.mdc new file mode 100644 index 0000000..6b142d8 --- /dev/null +++ b/agent/the-graph-agent-scaffold-eth/.cursor/rules/thegraph-se2-agent.mdc @@ -0,0 +1,8 @@ +--- +description: +globs: +alwaysApply: true +--- +# Documentation + +Always check here for how to interact with the API [docs](https://thegraph.com/docs/en/token-api/evm/get-balances-evm-by-address/) \ No newline at end of file diff --git a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/action-providers/token-api-provider.ts b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/action-providers/token-api-provider.ts index 7f61f92..5f1c797 100644 --- a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/action-providers/token-api-provider.ts +++ b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/action-providers/token-api-provider.ts @@ -177,7 +177,7 @@ class TokenApiProvider extends ActionProvider { // Use WalletPro @CreateAction({ name: "get-token-transfers", - description: "Fetches token transfers involving a specific address or contract. Can filter by sender, receiver, date range, etc.", + description: "Fetches token transfers involving a specific address or contract. Can filter by sender, receiver, date range, etc. If addressRole is not specified, defaults to showing incoming transfers (receiver). If no time filters are provided, automatically applies a 7-day lookback to prevent API timeouts.", schema: GetTokenTransfersAgentParamsSchema, }) async getTokenTransfers( @@ -186,37 +186,71 @@ class TokenApiProvider extends ActionProvider { // Use WalletPro ): Promise { console.log(`Action: getTokenTransfers, Args: ${JSON.stringify(args)}`); - const { address, addressRole, fromAddress, toAddress, ...otherParams } = args; + const { address, addressRole, fromAddress, toAddress, age, startTime, endTime, ...otherParams } = args; let finalToAddress: string | undefined = toAddress; let finalFromAddress: string | undefined = fromAddress; if (address) { - if (addressRole === "receiver" && !finalToAddress) { + // Default to "receiver" if no role is specified (most common use case) + const role = addressRole || "receiver"; + + if (role === "receiver" && !finalToAddress) { finalToAddress = address; - } else if (addressRole === "sender" && !finalFromAddress) { + } else if (role === "sender" && !finalFromAddress) { finalFromAddress = address; - } else if (addressRole === "either") { - // If role is 'either', and specific from/to are not set, - // this basic setup will use the address for 'to' in fetchTokenTransfers (first arg), - // and potentially for 'from' in its params if finalFromAddress is still undefined. - // A true 'either' might require two API calls or specific backend support. + } else if (role === "either") { + // For "either", we'll default to receiver for now + // TODO: In the future, this could make two API calls and merge results if (!finalToAddress) finalToAddress = address; - if (!finalFromAddress) finalFromAddress = address; + console.log(`📝 Note: Using address as receiver for "either" role. Consider making separate queries for complete results.`); } } - // If after all logic, neither toAddress nor fromAddress is set, and no contract is specified, - // the query might be too broad. The utility has a placeholder for this check. + // More specific error message with guidance if (!finalToAddress && !finalFromAddress && !otherParams.contract) { - return "Error: Token transfers query is too broad. Please specify an address (with role), from/to address, or a contract address."; + return `Error: Please specify one of the following: +- An address with a role (receiver/sender/either) +- A fromAddress (sender) +- A toAddress (receiver) +- A contract address to filter by + +Examples: +- To see incoming transfers: specify addressRole as "receiver" (this is the default) +- To see outgoing transfers: specify addressRole as "sender" +- To see transfers for a specific token: provide the contract address`; + } + + // Handle time filtering - prefer startTime/endTime over age to avoid timeouts + let finalStartTime: number | undefined = startTime; + let finalEndTime: number | undefined = endTime; + let finalAge: number | undefined = age; + + // If age is provided but no explicit start/end times, convert age to timestamps + // This helps avoid API timeouts for large datasets + if (age && !startTime && !endTime) { + const now = Math.floor(Date.now() / 1000); // Current time in seconds + finalEndTime = now; + finalStartTime = now - (age * 24 * 60 * 60); // Convert days to seconds + finalAge = undefined; // Remove age since we're using timestamps + } + + // If NO time filtering is provided at all, default to last 7 days to prevent timeouts + if (!age && !startTime && !endTime) { + const now = Math.floor(Date.now() / 1000); + finalEndTime = now; + finalStartTime = now - (7 * 24 * 60 * 60); // Default to 7 days + console.log(`📅 No time filter provided. Defaulting to last 7 days to prevent API timeout.`); } // Prepare parameters for the fetchTokenTransfers utility // The utility expects `toAddress` as first arg, and other params (including `from`) in the second. const utilityParams: Omit = { - ...otherParams, // network_id, contract, limit, age, etc. + ...otherParams, // network_id, contract, limit, etc. from: finalFromAddress, // This can be undefined, and fetchTokenTransfers handles it + age: finalAge, // Only use age if startTime/endTime are not set + startTime: finalStartTime, + endTime: finalEndTime, }; try { @@ -228,7 +262,12 @@ class TokenApiProvider extends ActionProvider { // Use WalletPro } if (!response.data || !response.data.transfers || response.data.transfers.length === 0) { - return `No token transfers found matching the criteria.`; + const roleText = addressRole || "receiver"; + return `No token transfers found for address ${address} as ${roleText} on ${otherParams.network_id || 'mainnet'}. Try: +- Different addressRole (sender/receiver/either) +- Different network +- Longer time period +- Check if the address has any token activity`; } // The response.data from fetchTokenTransfers includes { transfers: [], pagination: {}, ... } diff --git a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/schemas.ts b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/schemas.ts index a5c5271..2fd275b 100644 --- a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/schemas.ts +++ b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/schemas.ts @@ -123,6 +123,8 @@ export const TokenTransfersParamsSchema = z.object({ low_liquidity: z.boolean().optional(), start_date: z.string().optional().describe("Start date for filtering in ISO format (YYYY-MM-DDTHH:mm:ssZ)"), end_date: z.string().optional().describe("End date for filtering in ISO format (YYYY-MM-DDTHH:mm:ssZ)"), + startTime: z.number().optional().describe("Start timestamp (Unix timestamp in seconds)"), + endTime: z.number().optional().describe("End timestamp (Unix timestamp in seconds)"), include_prices: z.boolean().optional(), }); @@ -177,12 +179,28 @@ export const GetTokenTransfersAgentParamsSchema = TokenTransfersParamsSchema.ext addressRole: z .enum(["sender", "receiver", "either"]) .optional() - .describe("Role of the primary address: sender, receiver, or either."), + .describe( + "Role of the primary address: sender, receiver, or either. Defaults to 'receiver' (incoming transfers) if not specified.", + ), // Allow agent to specify from/to directly, overriding the primary address/role logic if needed. fromAddress: z.string().optional().describe("Filter by sender address."), toAddress: z.string().optional().describe("Filter by receiver address."), + // Time filtering options - prefer startTime/endTime over age for large datasets + startTime: z + .number() + .optional() + .describe( + "Start timestamp (Unix timestamp in seconds). Preferred over age parameter for large datasets to avoid timeouts.", + ), + endTime: z + .number() + .optional() + .describe( + "End timestamp (Unix timestamp in seconds). Preferred over age parameter for large datasets to avoid timeouts.", + ), + // contractAddress is already in TokenTransfersParamsSchema as 'contract' // networkId is already in TokenTransfersParamsSchema as 'network_id' // limit, page, age are also already there. diff --git a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/utils.ts b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/utils.ts index 4a6fa97..7d3d923 100644 --- a/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/utils.ts +++ b/agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/utils.ts @@ -21,8 +21,37 @@ export type TokenBalance = z.infer; -const NEXT_PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; -const API_PROXY_URL = `${NEXT_PUBLIC_BASE_URL}/api/token-proxy`; // Ensure this matches your proxy endpoint +// Determine the correct API proxy URL based on environment +function getApiProxyUrl(): string { + // If we're in a browser environment, use relative URL + if (typeof window !== "undefined") { + return "/api/token-proxy"; + } + + // Server-side: construct absolute URL + const baseUrl = + process.env.NEXT_PUBLIC_BASE_URL || process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000"; + + return `${baseUrl}/api/token-proxy`; +} + +const API_PROXY_URL = getApiProxyUrl(); + +/** + * Helper function to convert days ago to Unix timestamps + * @param daysAgo Number of days to look back from now + * @returns Object with startTime and endTime Unix timestamps + */ +export function convertDaysToTimestamps(daysAgo: number): { startTime: number; endTime: number } { + const now = Math.floor(Date.now() / 1000); // Current time in seconds + const startTime = now - daysAgo * 24 * 60 * 60; // Convert days to seconds + return { + startTime, + endTime: now, + }; +} /** * Fetches token balances from the token API proxy. @@ -197,7 +226,7 @@ export async function fetchTokenTransfers( toAddress: string | undefined, // The address is primarily used as the 'to' parameter params?: Omit, // Params excluding 'to', as toAddress takes precedence ): Promise { - const endpoint = "transfers/evm"; // Based on useTokenTransfers hook + const endpoint = "transfers/evm"; // Base endpoint as per The Graph documentation const queryParams = new URLSearchParams(); queryParams.append("path", endpoint); @@ -207,8 +236,9 @@ export async function fetchTokenTransfers( ...params, // Spread other parameters like from, contract, limit, age etc. }; + // Set the main address as 'to' parameter if provided if (toAddress) { - apiParams.to = toAddress; // Set the 'to' parameter from the main address argument + apiParams.to = toAddress; } // It's crucial that network_id is passed if available in params @@ -216,17 +246,40 @@ export async function fetchTokenTransfers( apiParams.network_id = params.network_id; } + // Handle time filtering - prioritize startTime/endTime over age + if (params?.startTime) { + apiParams.startTime = params.startTime; + } + + if (params?.endTime) { + apiParams.endTime = params.endTime; + } + + // Only include age if startTime/endTime are not provided to avoid conflicts + if (params?.age && !params?.startTime && !params?.endTime) { + apiParams.age = params.age; + } + + // Add default ordering as per The Graph documentation + if (!apiParams.orderBy) { + apiParams.orderBy = "timestamp"; + } + + if (!apiParams.orderDirection) { + apiParams.orderDirection = "desc"; + } + + console.log(`🔍 API params being sent:`, JSON.stringify(apiParams, null, 2)); + Object.entries(apiParams).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { queryParams.append(key, String(value)); } }); - // If no 'to' address is effectively provided (neither toAddress nor params.to) and endpoint requires it, - // the API might error or return broad results. Consider adding a check if toAddress is mandatory. + // Validate that we have some filtering criteria to avoid overly broad queries if (!apiParams.to && !apiParams.from && !apiParams.contract) { - // Example check: if the query is too broad without a primary subject (to/from/contract) - // return { error: { message: "Address or contract parameter is required for token transfers", status: 400 } }; + return { error: { message: "At least one of 'to', 'from', or 'contract' address must be specified", status: 400 } }; } const url = `${API_PROXY_URL}?${queryParams.toString()}`;