Skip to content

Commit 2b06d86

Browse files
authored
Merge pull request #11 from kmjones1979/main
fixing endpoints
2 parents e9b17c9 + b9e7f06 commit 2b06d86

File tree

4 files changed

+142
-24
lines changed

4 files changed

+142
-24
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
# Documentation
7+
8+
Always check here for how to interact with the API [docs](https://thegraph.com/docs/en/token-api/evm/get-balances-evm-by-address/)

agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/action-providers/token-api-provider.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class TokenApiProvider extends ActionProvider<WalletProvider> { // Use WalletPro
177177

178178
@CreateAction({
179179
name: "get-token-transfers",
180-
description: "Fetches token transfers involving a specific address or contract. Can filter by sender, receiver, date range, etc.",
180+
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.",
181181
schema: GetTokenTransfersAgentParamsSchema,
182182
})
183183
async getTokenTransfers(
@@ -186,37 +186,71 @@ class TokenApiProvider extends ActionProvider<WalletProvider> { // Use WalletPro
186186
): Promise<string> {
187187
console.log(`Action: getTokenTransfers, Args: ${JSON.stringify(args)}`);
188188

189-
const { address, addressRole, fromAddress, toAddress, ...otherParams } = args;
189+
const { address, addressRole, fromAddress, toAddress, age, startTime, endTime, ...otherParams } = args;
190190

191191
let finalToAddress: string | undefined = toAddress;
192192
let finalFromAddress: string | undefined = fromAddress;
193193

194194
if (address) {
195-
if (addressRole === "receiver" && !finalToAddress) {
195+
// Default to "receiver" if no role is specified (most common use case)
196+
const role = addressRole || "receiver";
197+
198+
if (role === "receiver" && !finalToAddress) {
196199
finalToAddress = address;
197-
} else if (addressRole === "sender" && !finalFromAddress) {
200+
} else if (role === "sender" && !finalFromAddress) {
198201
finalFromAddress = address;
199-
} else if (addressRole === "either") {
200-
// If role is 'either', and specific from/to are not set,
201-
// this basic setup will use the address for 'to' in fetchTokenTransfers (first arg),
202-
// and potentially for 'from' in its params if finalFromAddress is still undefined.
203-
// A true 'either' might require two API calls or specific backend support.
202+
} else if (role === "either") {
203+
// For "either", we'll default to receiver for now
204+
// TODO: In the future, this could make two API calls and merge results
204205
if (!finalToAddress) finalToAddress = address;
205-
if (!finalFromAddress) finalFromAddress = address;
206+
console.log(`📝 Note: Using address as receiver for "either" role. Consider making separate queries for complete results.`);
206207
}
207208
}
208209

209-
// If after all logic, neither toAddress nor fromAddress is set, and no contract is specified,
210-
// the query might be too broad. The utility has a placeholder for this check.
210+
// More specific error message with guidance
211211
if (!finalToAddress && !finalFromAddress && !otherParams.contract) {
212-
return "Error: Token transfers query is too broad. Please specify an address (with role), from/to address, or a contract address.";
212+
return `Error: Please specify one of the following:
213+
- An address with a role (receiver/sender/either)
214+
- A fromAddress (sender)
215+
- A toAddress (receiver)
216+
- A contract address to filter by
217+
218+
Examples:
219+
- To see incoming transfers: specify addressRole as "receiver" (this is the default)
220+
- To see outgoing transfers: specify addressRole as "sender"
221+
- To see transfers for a specific token: provide the contract address`;
222+
}
223+
224+
// Handle time filtering - prefer startTime/endTime over age to avoid timeouts
225+
let finalStartTime: number | undefined = startTime;
226+
let finalEndTime: number | undefined = endTime;
227+
let finalAge: number | undefined = age;
228+
229+
// If age is provided but no explicit start/end times, convert age to timestamps
230+
// This helps avoid API timeouts for large datasets
231+
if (age && !startTime && !endTime) {
232+
const now = Math.floor(Date.now() / 1000); // Current time in seconds
233+
finalEndTime = now;
234+
finalStartTime = now - (age * 24 * 60 * 60); // Convert days to seconds
235+
finalAge = undefined; // Remove age since we're using timestamps
236+
}
237+
238+
// If NO time filtering is provided at all, default to last 7 days to prevent timeouts
239+
if (!age && !startTime && !endTime) {
240+
const now = Math.floor(Date.now() / 1000);
241+
finalEndTime = now;
242+
finalStartTime = now - (7 * 24 * 60 * 60); // Default to 7 days
243+
console.log(`📅 No time filter provided. Defaulting to last 7 days to prevent API timeout.`);
213244
}
214245

215246
// Prepare parameters for the fetchTokenTransfers utility
216247
// The utility expects `toAddress` as first arg, and other params (including `from`) in the second.
217248
const utilityParams: Omit<TokenTransfersParams, 'to'> = {
218-
...otherParams, // network_id, contract, limit, age, etc.
249+
...otherParams, // network_id, contract, limit, etc.
219250
from: finalFromAddress, // This can be undefined, and fetchTokenTransfers handles it
251+
age: finalAge, // Only use age if startTime/endTime are not set
252+
startTime: finalStartTime,
253+
endTime: finalEndTime,
220254
};
221255

222256
try {
@@ -228,7 +262,12 @@ class TokenApiProvider extends ActionProvider<WalletProvider> { // Use WalletPro
228262
}
229263

230264
if (!response.data || !response.data.transfers || response.data.transfers.length === 0) {
231-
return `No token transfers found matching the criteria.`;
265+
const roleText = addressRole || "receiver";
266+
return `No token transfers found for address ${address} as ${roleText} on ${otherParams.network_id || 'mainnet'}. Try:
267+
- Different addressRole (sender/receiver/either)
268+
- Different network
269+
- Longer time period
270+
- Check if the address has any token activity`;
232271
}
233272

234273
// The response.data from fetchTokenTransfers includes { transfers: [], pagination: {}, ... }

agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/schemas.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ export const TokenTransfersParamsSchema = z.object({
123123
low_liquidity: z.boolean().optional(),
124124
start_date: z.string().optional().describe("Start date for filtering in ISO format (YYYY-MM-DDTHH:mm:ssZ)"),
125125
end_date: z.string().optional().describe("End date for filtering in ISO format (YYYY-MM-DDTHH:mm:ssZ)"),
126+
startTime: z.number().optional().describe("Start timestamp (Unix timestamp in seconds)"),
127+
endTime: z.number().optional().describe("End timestamp (Unix timestamp in seconds)"),
126128
include_prices: z.boolean().optional(),
127129
});
128130

@@ -177,12 +179,28 @@ export const GetTokenTransfersAgentParamsSchema = TokenTransfersParamsSchema.ext
177179
addressRole: z
178180
.enum(["sender", "receiver", "either"])
179181
.optional()
180-
.describe("Role of the primary address: sender, receiver, or either."),
182+
.describe(
183+
"Role of the primary address: sender, receiver, or either. Defaults to 'receiver' (incoming transfers) if not specified.",
184+
),
181185

182186
// Allow agent to specify from/to directly, overriding the primary address/role logic if needed.
183187
fromAddress: z.string().optional().describe("Filter by sender address."),
184188
toAddress: z.string().optional().describe("Filter by receiver address."),
185189

190+
// Time filtering options - prefer startTime/endTime over age for large datasets
191+
startTime: z
192+
.number()
193+
.optional()
194+
.describe(
195+
"Start timestamp (Unix timestamp in seconds). Preferred over age parameter for large datasets to avoid timeouts.",
196+
),
197+
endTime: z
198+
.number()
199+
.optional()
200+
.describe(
201+
"End timestamp (Unix timestamp in seconds). Preferred over age parameter for large datasets to avoid timeouts.",
202+
),
203+
186204
// contractAddress is already in TokenTransfersParamsSchema as 'contract'
187205
// networkId is already in TokenTransfersParamsSchema as 'network_id'
188206
// limit, page, age are also already there.

agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/agentkit/token-api/utils.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,37 @@ export type TokenBalance = z.infer<typeof import("./schemas").TokenBalanceSchema
2121
// Define TokenInfo type from schema
2222
export type TokenInfo = z.infer<typeof TokenInfoSchema>;
2323

24-
const NEXT_PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
25-
const API_PROXY_URL = `${NEXT_PUBLIC_BASE_URL}/api/token-proxy`; // Ensure this matches your proxy endpoint
24+
// Determine the correct API proxy URL based on environment
25+
function getApiProxyUrl(): string {
26+
// If we're in a browser environment, use relative URL
27+
if (typeof window !== "undefined") {
28+
return "/api/token-proxy";
29+
}
30+
31+
// Server-side: construct absolute URL
32+
const baseUrl =
33+
process.env.NEXT_PUBLIC_BASE_URL || process.env.VERCEL_URL
34+
? `https://${process.env.VERCEL_URL}`
35+
: "http://localhost:3000";
36+
37+
return `${baseUrl}/api/token-proxy`;
38+
}
39+
40+
const API_PROXY_URL = getApiProxyUrl();
41+
42+
/**
43+
* Helper function to convert days ago to Unix timestamps
44+
* @param daysAgo Number of days to look back from now
45+
* @returns Object with startTime and endTime Unix timestamps
46+
*/
47+
export function convertDaysToTimestamps(daysAgo: number): { startTime: number; endTime: number } {
48+
const now = Math.floor(Date.now() / 1000); // Current time in seconds
49+
const startTime = now - daysAgo * 24 * 60 * 60; // Convert days to seconds
50+
return {
51+
startTime,
52+
endTime: now,
53+
};
54+
}
2655

2756
/**
2857
* Fetches token balances from the token API proxy.
@@ -197,7 +226,7 @@ export async function fetchTokenTransfers(
197226
toAddress: string | undefined, // The address is primarily used as the 'to' parameter
198227
params?: Omit<TokenTransfersParams, "to">, // Params excluding 'to', as toAddress takes precedence
199228
): Promise<TokenTransfersApiResponse> {
200-
const endpoint = "transfers/evm"; // Based on useTokenTransfers hook
229+
const endpoint = "transfers/evm"; // Base endpoint as per The Graph documentation
201230

202231
const queryParams = new URLSearchParams();
203232
queryParams.append("path", endpoint);
@@ -207,26 +236,50 @@ export async function fetchTokenTransfers(
207236
...params, // Spread other parameters like from, contract, limit, age etc.
208237
};
209238

239+
// Set the main address as 'to' parameter if provided
210240
if (toAddress) {
211-
apiParams.to = toAddress; // Set the 'to' parameter from the main address argument
241+
apiParams.to = toAddress;
212242
}
213243

214244
// It's crucial that network_id is passed if available in params
215245
if (params?.network_id) {
216246
apiParams.network_id = params.network_id;
217247
}
218248

249+
// Handle time filtering - prioritize startTime/endTime over age
250+
if (params?.startTime) {
251+
apiParams.startTime = params.startTime;
252+
}
253+
254+
if (params?.endTime) {
255+
apiParams.endTime = params.endTime;
256+
}
257+
258+
// Only include age if startTime/endTime are not provided to avoid conflicts
259+
if (params?.age && !params?.startTime && !params?.endTime) {
260+
apiParams.age = params.age;
261+
}
262+
263+
// Add default ordering as per The Graph documentation
264+
if (!apiParams.orderBy) {
265+
apiParams.orderBy = "timestamp";
266+
}
267+
268+
if (!apiParams.orderDirection) {
269+
apiParams.orderDirection = "desc";
270+
}
271+
272+
console.log(`🔍 API params being sent:`, JSON.stringify(apiParams, null, 2));
273+
219274
Object.entries(apiParams).forEach(([key, value]) => {
220275
if (value !== undefined && value !== null && value !== "") {
221276
queryParams.append(key, String(value));
222277
}
223278
});
224279

225-
// If no 'to' address is effectively provided (neither toAddress nor params.to) and endpoint requires it,
226-
// the API might error or return broad results. Consider adding a check if toAddress is mandatory.
280+
// Validate that we have some filtering criteria to avoid overly broad queries
227281
if (!apiParams.to && !apiParams.from && !apiParams.contract) {
228-
// Example check: if the query is too broad without a primary subject (to/from/contract)
229-
// return { error: { message: "Address or contract parameter is required for token transfers", status: 400 } };
282+
return { error: { message: "At least one of 'to', 'from', or 'contract' address must be specified", status: 400 } };
230283
}
231284

232285
const url = `${API_PROXY_URL}?${queryParams.toString()}`;

0 commit comments

Comments
 (0)