UNPKG

@elizaos/plugin-zapper

Version:

ElizaOS plugin for Zapper protocol integration - portfolio tracking, DeFi analytics, and blockchain data across 50+ networks

1,840 lines (1,831 loc) 290 kB
import "./chunk-7D4SUZUM.js"; // src/actions/getPortfolio.ts import { ModelType, logger, parseKeyValueXml } from "@elizaos/core"; // src/utils/zapperGraphQL.ts import { elizaLogger as elizaLogger3 } from "@elizaos/core"; // src/utils/queryCache.ts import { elizaLogger } from "@elizaos/core"; var ZapperQueryCache = class { cache = /* @__PURE__ */ new Map(); stats = { totalRequests: 0, cacheHits: 0, cacheMisses: 0, hitRate: 0, entryCount: 0, totalMemoryUsage: 0, oldestEntry: 0, newestEntry: 0 }; config; cleanupTimer = null; constructor(config = {}) { this.config = { maxSize: 1e3, defaultTTL: 3e5, // 5 minutes cleanupInterval: 6e4, // 1 minute enableStats: true, ...config }; this.startCleanupTimer(); } /** * Generate cache key from query and variables */ generateCacheKey(query, variables) { const variablesStr = variables ? JSON.stringify(variables) : ""; const combined = query + variablesStr; let hash = 0; for (let i = 0; i < combined.length; i++) { const char = combined.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return `query_${Math.abs(hash)}_${combined.length}`; } /** * Get cached response */ get(query, variables) { const key = this.generateCacheKey(query, variables); const entry = this.cache.get(key); if (this.config.enableStats) { this.stats.totalRequests++; } if (!entry) { if (this.config.enableStats) { this.stats.cacheMisses++; this.updateHitRate(); } return null; } const now = Date.now(); if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); if (this.config.enableStats) { this.stats.cacheMisses++; this.stats.entryCount--; this.updateHitRate(); } return null; } entry.accessCount++; entry.lastAccessed = now; if (this.config.enableStats) { this.stats.cacheHits++; this.updateHitRate(); } return entry.data; } /** * Set cached response */ set(query, variables, response, ttl) { const key = this.generateCacheKey(query, variables); const now = Date.now(); while (this.cache.size >= this.config.maxSize) { this.evictLRU(); } const entry = { data: response, timestamp: now, ttl: ttl || this.config.defaultTTL, accessCount: 1, lastAccessed: now, originalQuery: query, variables }; this.cache.set(key, entry); if (this.config.enableStats) { this.stats.entryCount++; this.updateMemoryUsage(); this.updateTimestamps(); } } /** * Check if query result is cached */ has(query, variables) { const key = this.generateCacheKey(query, variables); const entry = this.cache.get(key); if (!entry) return false; const now = Date.now(); if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); if (this.config.enableStats) { this.stats.entryCount--; } return false; } return true; } /** * Invalidate specific cache entry */ invalidate(query, variables) { const key = this.generateCacheKey(query, variables); const deleted = this.cache.delete(key); if (deleted && this.config.enableStats) { this.stats.entryCount--; } return deleted; } /** * Invalidate cache entries by pattern */ invalidatePattern(pattern) { let deletedCount = 0; for (const [key, entry] of this.cache) { if (entry.originalQuery.includes(pattern) || key.includes(pattern)) { this.cache.delete(key); deletedCount++; } } if (this.config.enableStats) { this.stats.entryCount -= deletedCount; } return deletedCount; } /** * Clear all cache entries */ clear() { this.cache.clear(); if (this.config.enableStats) { this.stats.entryCount = 0; this.stats.totalMemoryUsage = 0; this.stats.oldestEntry = 0; this.stats.newestEntry = 0; } } /** * Get cache statistics */ getStats() { if (this.config.enableStats) { this.updateMemoryUsage(); this.updateTimestamps(); } return { ...this.stats }; } /** * Update cache configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; if (config.cleanupInterval !== void 0) { this.stopCleanupTimer(); this.startCleanupTimer(); } } /** * Optimize cache performance */ optimize() { const initialSize = this.cache.size; const initialMemory = this.stats.totalMemoryUsage; const now = Date.now(); for (const [key, entry] of this.cache) { if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); } } while (this.cache.size > this.config.maxSize * 0.8) { this.evictLRU(); } const finalSize = this.cache.size; const finalMemory = this.config.enableStats ? this.calculateMemoryUsage() : 0; if (this.config.enableStats) { this.stats.entryCount = finalSize; this.stats.totalMemoryUsage = finalMemory; } return { entriesRemoved: initialSize - finalSize, memoryFreed: initialMemory - finalMemory }; } /** * Get cache entries for debugging */ getEntries() { return Array.from(this.cache.entries()).map(([key, entry]) => ({ key, entry: { ...entry } })); } /** * Destroy cache and cleanup resources */ destroy() { this.stopCleanupTimer(); this.clear(); } /** * Start cleanup timer */ startCleanupTimer() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.cleanupTimer = setInterval(() => { this.cleanupExpiredEntries(); }, this.config.cleanupInterval); } /** * Stop cleanup timer */ stopCleanupTimer() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = null; } } /** * Clean up expired entries */ cleanupExpiredEntries() { const now = Date.now(); let removedCount = 0; for (const [key, entry] of this.cache) { if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); removedCount++; } } if (removedCount > 0) { elizaLogger.debug(`Cache cleanup: removed ${removedCount} expired entries`); if (this.config.enableStats) { this.stats.entryCount -= removedCount; } } } /** * Evict least recently used entry */ evictLRU() { let oldestKey = null; let oldestTime = Date.now(); for (const [key, entry] of this.cache) { if (entry.lastAccessed < oldestTime) { oldestTime = entry.lastAccessed; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); if (this.config.enableStats) { this.stats.entryCount--; } } } /** * Update hit rate statistic */ updateHitRate() { if (this.stats.totalRequests > 0) { this.stats.hitRate = this.stats.cacheHits / this.stats.totalRequests * 100; } } /** * Update memory usage estimate */ updateMemoryUsage() { this.stats.totalMemoryUsage = this.calculateMemoryUsage(); } /** * Calculate memory usage estimate */ calculateMemoryUsage() { let totalSize = 0; for (const [key, entry] of this.cache) { totalSize += key.length * 2; totalSize += JSON.stringify(entry.data).length * 2; totalSize += 64; } return totalSize; } /** * Update timestamp statistics */ updateTimestamps() { if (this.cache.size === 0) { this.stats.oldestEntry = 0; this.stats.newestEntry = 0; return; } let oldest = Date.now(); let newest = 0; for (const entry of this.cache.values()) { if (entry.timestamp < oldest) { oldest = entry.timestamp; } if (entry.timestamp > newest) { newest = entry.timestamp; } } this.stats.oldestEntry = oldest; this.stats.newestEntry = newest; } }; var QueryOptimizer = class { /** * Optimize GraphQL query by removing unnecessary fields */ static removeUnusedFields(query, usedFields) { let optimized = query; optimized = optimized.replace(/\s*#.*$/gm, ""); optimized = optimized.replace(/\s+/g, " ").trim(); return optimized; } /** * Merge multiple queries into a single batch query */ static mergeQueries(queries) { const mergedFields = queries.map(({ alias, query }) => { const match = query.match(/{\s*([^}]+)\s*}/); const body = match ? match[1] : ""; return `${alias}: ${body}`; }).join("\n "); return `query BatchQuery { ${mergedFields} }`; } /** * Optimize query variables */ static optimizeVariables(variables) { const optimized = {}; for (const [key, value] of Object.entries(variables)) { if (value != null) { optimized[key] = value; } } return optimized; } /** * Calculate query complexity score */ static calculateComplexity(query) { let complexity = 0; const maxDepth = (query.match(/{/g) || []).length; complexity += maxDepth * 2; const fieldCount = (query.match(/\w+(?=\s*[:{])/g) || []).length; complexity += fieldCount; const variableCount = (query.match(/\$\w+/g) || []).length; complexity += variableCount; return complexity; } /** * Suggest query optimizations */ static suggestOptimizations(query) { const suggestions = []; if (query.includes("...")) { suggestions.push("Consider using fragments to reduce query duplication"); } if (query.length > 2e3) { suggestions.push("Query is quite large - consider splitting into smaller queries"); } if (this.calculateComplexity(query) > 50) { suggestions.push("Query complexity is high - consider reducing nesting levels"); } if (query.includes("*")) { suggestions.push("Avoid wildcard selections - specify only needed fields"); } return suggestions; } }; // src/utils/errorHandler.ts import { elizaLogger as elizaLogger2 } from "@elizaos/core"; var ErrorType = /* @__PURE__ */ ((ErrorType2) => { ErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR"; ErrorType2["TIMEOUT_ERROR"] = "TIMEOUT_ERROR"; ErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR"; ErrorType2["AUTHENTICATION_ERROR"] = "AUTHENTICATION_ERROR"; ErrorType2["GRAPHQL_ERROR"] = "GRAPHQL_ERROR"; ErrorType2["VALIDATION_ERROR"] = "VALIDATION_ERROR"; ErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; return ErrorType2; })(ErrorType || {}); var ErrorSeverity = /* @__PURE__ */ ((ErrorSeverity2) => { ErrorSeverity2["LOW"] = "LOW"; ErrorSeverity2["MEDIUM"] = "MEDIUM"; ErrorSeverity2["HIGH"] = "HIGH"; ErrorSeverity2["CRITICAL"] = "CRITICAL"; return ErrorSeverity2; })(ErrorSeverity || {}); var ZapperErrorHandler = class { config; metrics; circuitState = "CLOSED" /* CLOSED */; circuitFailureCount = 0; circuitLastFailureTime = 0; circuitNextRetryTime = 0; constructor(config = {}) { this.config = { enableLogging: true, logLevel: "error", enableMetrics: true, enableCircuitBreaker: false, circuitBreakerConfig: { failureThreshold: 5, resetTimeout: 6e4, monitoringWindow: 3e5 }, ...config }; this.metrics = { totalErrors: 0, errorsByType: {}, errorsBySeverity: {}, retryAttempts: 0, successfulRetries: 0, circuitBreakerTrips: 0, averageRetryDelay: 0, lastErrorTime: 0 }; Object.values(ErrorType).forEach((type) => { this.metrics.errorsByType[type] = 0; }); Object.values(ErrorSeverity).forEach((severity) => { this.metrics.errorsBySeverity[severity] = 0; }); } /** * Process and classify an error */ processError(error, context) { const zapperError = this.classifyError(error, context); if (this.config.enableMetrics) { this.updateMetrics(zapperError); } if (this.config.enableLogging) { this.logError(zapperError); } return zapperError; } /** * Execute function with retry logic */ async executeWithRetry(fn, retryConfig = {}, context) { const config = { maxAttempts: 3, baseDelay: 1e3, maxDelay: 3e4, backoffMultiplier: 2, jitter: true, retryableErrors: [ "NETWORK_ERROR" /* NETWORK_ERROR */, "TIMEOUT_ERROR" /* TIMEOUT_ERROR */, "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ ], ...retryConfig }; if (this.config.enableCircuitBreaker && this.circuitState === "OPEN" /* OPEN */) { if (Date.now() < this.circuitNextRetryTime) { throw new Error( `Circuit breaker is OPEN. Next retry at ${new Date(this.circuitNextRetryTime).toISOString()}` ); } else { this.circuitState = "HALF_OPEN" /* HALF_OPEN */; this.logCircuitBreakerState("HALF_OPEN"); } } let lastError = null; let totalDelay = 0; for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { try { const result = await fn(); if (this.config.enableCircuitBreaker && this.circuitState === "HALF_OPEN" /* HALF_OPEN */) { this.circuitState = "CLOSED" /* CLOSED */; this.circuitFailureCount = 0; this.logCircuitBreakerState("CLOSED"); } if (attempt > 1 && this.config.enableMetrics) { this.metrics.successfulRetries++; this.metrics.averageRetryDelay = totalDelay / (attempt - 1); } return result; } catch (error) { lastError = this.processError(error, { ...context, attempt, maxAttempts: config.maxAttempts }); if (!config.retryableErrors.includes(lastError.type)) { this.handleCircuitBreakerOnError(lastError); throw this.createEnhancedError(lastError); } if (attempt === config.maxAttempts) { this.handleCircuitBreakerOnError(lastError); break; } const delay = this.calculateRetryDelay(attempt, config); totalDelay += delay; if (this.config.enableMetrics) { this.metrics.retryAttempts++; } if (this.config.enableLogging) { elizaLogger2.warn( `Retry attempt ${attempt}/${config.maxAttempts} failed, retrying in ${delay}ms`, { error: lastError.message, delay, attempt, maxAttempts: config.maxAttempts } ); } await this.delay(delay); } } if (lastError) { this.handleCircuitBreakerOnError(lastError); throw this.createEnhancedError(lastError); } throw new Error("Execution failed after all retry attempts"); } /** * Get error metrics */ getMetrics() { return { ...this.metrics }; } /** * Reset error metrics */ resetMetrics() { this.metrics = { totalErrors: 0, errorsByType: {}, errorsBySeverity: {}, retryAttempts: 0, successfulRetries: 0, circuitBreakerTrips: 0, averageRetryDelay: 0, lastErrorTime: 0 }; Object.values(ErrorType).forEach((type) => { this.metrics.errorsByType[type] = 0; }); Object.values(ErrorSeverity).forEach((severity) => { this.metrics.errorsBySeverity[severity] = 0; }); } /** * Get circuit breaker status */ getCircuitBreakerStatus() { return { state: this.circuitState, failureCount: this.circuitFailureCount, nextRetryTime: this.circuitNextRetryTime, isOpen: this.circuitState === "OPEN" /* OPEN */ }; } /** * Manually reset circuit breaker */ resetCircuitBreaker() { this.circuitState = "CLOSED" /* CLOSED */; this.circuitFailureCount = 0; this.circuitNextRetryTime = 0; this.logCircuitBreakerState("CLOSED"); } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; } /** * Classify error type and severity */ classifyError(error, context) { const timestamp = Date.now(); let type; let severity; let message; let statusCode; let retryAfter; let suggestion; if (error.errors && Array.isArray(error.errors)) { type = "GRAPHQL_ERROR" /* GRAPHQL_ERROR */; severity = "MEDIUM" /* MEDIUM */; message = error.errors.map((e) => e.message).join("; "); suggestion = this.getGraphQLErrorSuggestion(error.errors); } else if (error.name === "AbortError" || error.message?.includes("timeout")) { type = "TIMEOUT_ERROR" /* TIMEOUT_ERROR */; severity = "MEDIUM" /* MEDIUM */; message = error.message || "Request timed out"; suggestion = "Consider increasing timeout or checking network connectivity"; } else if (error.status || error.statusCode) { statusCode = error.status || error.statusCode; if (statusCode === 401 || statusCode === 403) { type = "AUTHENTICATION_ERROR" /* AUTHENTICATION_ERROR */; severity = "HIGH" /* HIGH */; message = "Authentication failed - check API key"; suggestion = "Verify your API key is valid and has the required permissions"; } else if (statusCode === 429) { type = "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */; severity = "MEDIUM" /* MEDIUM */; message = "Rate limit exceeded"; retryAfter = this.extractRetryAfter(error); suggestion = retryAfter ? `Wait ${retryAfter}ms before retrying` : "Reduce request frequency or implement exponential backoff"; } else if (statusCode >= 500) { type = "NETWORK_ERROR" /* NETWORK_ERROR */; severity = "HIGH" /* HIGH */; message = `Server error: ${statusCode}`; suggestion = "Server is experiencing issues, retry with exponential backoff"; } else { type = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */; severity = "MEDIUM" /* MEDIUM */; message = error.message || `HTTP error: ${statusCode}`; } } else if (error.name === "ValidationError" || error.message?.includes("validation")) { type = "VALIDATION_ERROR" /* VALIDATION_ERROR */; severity = "LOW" /* LOW */; message = error.message || "Validation failed"; suggestion = "Check input parameters and format"; } else if (error.code === "ECONNRESET" || error.code === "ENOTFOUND" || error.code === "ETIMEDOUT") { type = "NETWORK_ERROR" /* NETWORK_ERROR */; severity = "HIGH" /* HIGH */; message = `Network error: ${error.code}`; suggestion = "Check network connectivity and DNS resolution"; } else { type = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */; severity = "MEDIUM" /* MEDIUM */; message = error.message || "Unknown error occurred"; } return { type, severity, message, originalError: error instanceof Error ? error : void 0, statusCode, retryAfter, timestamp, context, retryable: this.isRetryable(type, statusCode), suggestion }; } /** * Check if error is retryable */ isRetryable(type, statusCode) { const retryableTypes = [ "NETWORK_ERROR" /* NETWORK_ERROR */, "TIMEOUT_ERROR" /* TIMEOUT_ERROR */, "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ ]; if (retryableTypes.includes(type)) { return true; } if (statusCode && statusCode >= 500) { return true; } return false; } /** * Update error metrics */ updateMetrics(error) { this.metrics.totalErrors++; this.metrics.errorsByType[error.type]++; this.metrics.errorsBySeverity[error.severity]++; this.metrics.lastErrorTime = error.timestamp; } /** * Log error based on configuration */ logError(error) { const logData = { type: error.type, severity: error.severity, message: error.message, statusCode: error.statusCode, retryable: error.retryable, suggestion: error.suggestion, context: error.context }; switch (this.config.logLevel) { case "debug": elizaLogger2.debug("Zapper error occurred", logData); break; case "info": elizaLogger2.info("Zapper error occurred", logData); break; case "warn": elizaLogger2.warn("Zapper error occurred", logData); break; case "error": default: elizaLogger2.error("Zapper error occurred", logData); break; } } /** * Handle circuit breaker on error */ handleCircuitBreakerOnError(error) { if (!this.config.enableCircuitBreaker) return; const now = Date.now(); const config = this.config.circuitBreakerConfig; if (error.severity === "HIGH" /* HIGH */ || error.severity === "CRITICAL" /* CRITICAL */) { this.circuitFailureCount++; this.circuitLastFailureTime = now; if (this.circuitFailureCount >= config.failureThreshold) { this.circuitState = "OPEN" /* OPEN */; this.circuitNextRetryTime = now + config.resetTimeout; this.metrics.circuitBreakerTrips++; this.logCircuitBreakerState("OPEN"); } } } /** * Log circuit breaker state changes */ logCircuitBreakerState(state) { if (this.config.enableLogging) { elizaLogger2.info(`Circuit breaker state changed to ${state}`, { failureCount: this.circuitFailureCount, nextRetryTime: this.circuitNextRetryTime, threshold: this.config.circuitBreakerConfig?.failureThreshold }); } } /** * Calculate retry delay with exponential backoff and jitter */ calculateRetryDelay(attempt, config) { let delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1); delay = Math.min(delay, config.maxDelay); if (config.jitter) { delay *= 0.5 + Math.random() * 0.5; } return Math.floor(delay); } /** * Extract retry-after header value */ extractRetryAfter(error) { const retryAfter = error.headers?.["retry-after"] || error.retryAfter; if (retryAfter) { const parsed = parseInt(retryAfter, 10); return isNaN(parsed) ? void 0 : parsed * 1e3; } return void 0; } /** * Get suggestion for GraphQL errors */ getGraphQLErrorSuggestion(errors) { if (!errors || errors.length === 0) return void 0; const firstError = errors[0]; const message = firstError.message?.toLowerCase() || ""; if (message.includes("field") && message.includes("not exist")) { return "Check field names in your GraphQL query"; } if (message.includes("variable") && message.includes("required")) { return "Ensure all required variables are provided"; } if (message.includes("syntax")) { return "Check GraphQL query syntax"; } if (message.includes("depth") || message.includes("complexity")) { return "Reduce query complexity or nesting depth"; } return "Check GraphQL query structure and variables"; } /** * Create enhanced error with additional context */ createEnhancedError(zapperError) { const enhancedError = new Error(zapperError.message); enhancedError.name = `ZapperError_${zapperError.type}`; Object.assign(enhancedError, { type: zapperError.type, severity: zapperError.severity, statusCode: zapperError.statusCode, retryable: zapperError.retryable, suggestion: zapperError.suggestion, context: zapperError.context, timestamp: zapperError.timestamp }); return enhancedError; } /** * Delay utility for retry logic */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; var defaultErrorHandler = new ZapperErrorHandler(); // src/utils/rateLimiter.ts var TokenBucketRateLimiter = class { tokens; lastRefill; config; constructor(config) { this.config = config; this.tokens = config.maxRequests; this.lastRefill = Date.now(); } /** * Check if request is allowed */ isAllowed(tokensRequested = 1) { this.refillTokens(); const allowed = this.tokens >= tokensRequested; if (allowed) { this.tokens -= tokensRequested; } return { allowed, remaining: Math.max(0, this.tokens), resetTime: this.getNextResetTime(), retryAfter: allowed ? void 0 : this.calculateRetryAfter() }; } /** * Refill tokens based on time elapsed */ refillTokens() { const now = Date.now(); const elapsed = now - this.lastRefill; const refillRate = this.config.refillRate || this.config.maxRequests; const tokensToAdd = Math.floor(elapsed / this.config.windowMs * refillRate); if (tokensToAdd > 0) { this.tokens = Math.min(this.config.maxRequests, this.tokens + tokensToAdd); this.lastRefill = now; } } /** * Get next reset time */ getNextResetTime() { const refillRate = this.config.refillRate || this.config.maxRequests; const timeToFull = (this.config.maxRequests - this.tokens) / refillRate * this.config.windowMs; return Date.now() + timeToFull; } /** * Calculate retry after time */ calculateRetryAfter() { const refillRate = this.config.refillRate || this.config.maxRequests; return Math.ceil(this.config.windowMs / refillRate); } /** * Reset the bucket */ reset() { this.tokens = this.config.maxRequests; this.lastRefill = Date.now(); } }; var SlidingWindowRateLimiter = class { requests = []; config; constructor(config) { this.config = config; } /** * Check if request is allowed */ isAllowed() { const now = Date.now(); const windowStart = now - this.config.windowMs; this.requests = this.requests.filter((timestamp) => timestamp > windowStart); const allowed = this.requests.length < this.config.maxRequests; if (allowed) { this.requests.push(now); } return { allowed, remaining: Math.max(0, this.config.maxRequests - this.requests.length), resetTime: this.getNextResetTime(), retryAfter: allowed ? void 0 : this.calculateRetryAfter() }; } /** * Get next reset time */ getNextResetTime() { if (this.requests.length === 0) { return Date.now() + this.config.windowMs; } return this.requests[0] + this.config.windowMs; } /** * Calculate retry after time */ calculateRetryAfter() { if (this.requests.length === 0) { return 0; } const oldestRequest = this.requests[0]; return Math.max(0, oldestRequest + this.config.windowMs - Date.now()); } /** * Reset the window */ reset() { this.requests = []; } }; var FixedWindowRateLimiter = class { requestCount = 0; windowStart; config; constructor(config) { this.config = config; this.windowStart = Date.now(); } /** * Check if request is allowed */ isAllowed() { const now = Date.now(); if (now - this.windowStart >= this.config.windowMs) { this.requestCount = 0; this.windowStart = now; } const allowed = this.requestCount < this.config.maxRequests; if (allowed) { this.requestCount++; } return { allowed, remaining: Math.max(0, this.config.maxRequests - this.requestCount), resetTime: this.windowStart + this.config.windowMs, retryAfter: allowed ? void 0 : this.windowStart + this.config.windowMs - now }; } /** * Reset the window */ reset() { this.requestCount = 0; this.windowStart = Date.now(); } }; var RequestQueue = class { queue = []; processing = false; config; rateLimiter; constructor(config, rateLimiter) { this.config = config; this.rateLimiter = rateLimiter; } /** * Add request to queue */ async enqueue() { return new Promise((resolve, reject) => { const queueSize = this.config.queueSize || 100; if (this.queue.length >= queueSize) { reject(new Error("Queue is full")); return; } this.queue.push({ resolve, reject, timestamp: Date.now(), id: Math.random().toString(36).substring(7) }); this.processQueue(); }); } /** * Process queued requests */ async processQueue() { if (this.processing || this.queue.length === 0) { return; } this.processing = true; while (this.queue.length > 0) { const request = this.queue[0]; const result = this.rateLimiter.isAllowed(); if (result.allowed) { this.queue.shift(); request.resolve(result); } else { if (result.retryAfter) { await this.delay(result.retryAfter); } else { await this.delay(1e3); } } } this.processing = false; } /** * Get queue status */ getQueueStatus() { const now = Date.now(); const averageWaitTime = this.queue.length > 0 ? this.queue.reduce((sum, req) => sum + (now - req.timestamp), 0) / this.queue.length : 0; return { queueLength: this.queue.length, processing: this.processing, averageWaitTime }; } /** * Clear queue */ clear() { this.queue.forEach((req) => { req.reject(new Error("Queue cleared")); }); this.queue = []; this.processing = false; } /** * Delay utility */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; var ZapperRateLimiter = class { rateLimiters = /* @__PURE__ */ new Map(); queues = /* @__PURE__ */ new Map(); metrics = /* @__PURE__ */ new Map(); config; constructor(config) { this.config = { enableQueue: false, enableMetrics: true, keyGenerator: (context) => context?.key || "default", ...config }; } /** * Check if request is allowed */ async checkLimit(context) { const key = this.config.keyGenerator(context); const rateLimiter = this.getRateLimiter(key); if (this.config.enableMetrics) { this.updateMetrics(key, "request"); } let result; if (this.config.enableQueue) { const preCheck = rateLimiter.isAllowed(); if (!preCheck.allowed) { const queue = this.getQueue(key, rateLimiter); result = await queue.enqueue(); if (this.config.enableMetrics) { this.updateMetrics(key, "queued"); } } else { result = preCheck; } } else { result = rateLimiter.isAllowed(); } if (this.config.enableMetrics) { this.updateMetrics(key, result.allowed ? "allowed" : "rejected"); } return result; } /** * Get rate limiter for key */ getRateLimiter(key) { if (!this.rateLimiters.has(key)) { let rateLimiter; switch (this.config.algorithm) { case "TOKEN_BUCKET" /* TOKEN_BUCKET */: rateLimiter = new TokenBucketRateLimiter(this.config); break; case "SLIDING_WINDOW" /* SLIDING_WINDOW */: rateLimiter = new SlidingWindowRateLimiter(this.config); break; case "FIXED_WINDOW" /* FIXED_WINDOW */: rateLimiter = new FixedWindowRateLimiter(this.config); break; default: rateLimiter = new TokenBucketRateLimiter(this.config); } this.rateLimiters.set(key, rateLimiter); } return this.rateLimiters.get(key); } /** * Get queue for key */ getQueue(key, rateLimiter) { if (!this.queues.has(key)) { this.queues.set(key, new RequestQueue(this.config, rateLimiter)); } return this.queues.get(key); } /** * Update metrics */ updateMetrics(key, eventType) { if (!this.metrics.has(key)) { this.metrics.set(key, { totalRequests: 0, allowedRequests: 0, rejectedRequests: 0, queuedRequests: 0, averageWaitTime: 0, peakUsage: 0, currentUsage: 0, lastResetTime: Date.now() }); } const metrics = this.metrics.get(key); switch (eventType) { case "request": metrics.totalRequests++; break; case "allowed": metrics.allowedRequests++; break; case "rejected": metrics.rejectedRequests++; break; case "queued": metrics.queuedRequests++; break; } const rateLimiter = this.rateLimiters.get(key); if (rateLimiter && eventType === "allowed") { if (rateLimiter instanceof TokenBucketRateLimiter) { metrics.currentUsage = this.config.maxRequests - rateLimiter.tokens; } else { metrics.currentUsage = Math.min(metrics.currentUsage + 1, this.config.maxRequests); } metrics.peakUsage = Math.max(metrics.peakUsage, metrics.currentUsage); } } /** * Get metrics for key */ getMetrics(key) { if (key) { return this.metrics.get(key) || { totalRequests: 0, allowedRequests: 0, rejectedRequests: 0, queuedRequests: 0, averageWaitTime: 0, peakUsage: 0, currentUsage: 0, lastResetTime: Date.now() }; } return this.metrics; } /** * Reset rate limiter for key */ reset(key) { if (key) { const rateLimiter = this.rateLimiters.get(key); if (rateLimiter) { rateLimiter.reset(); } const queue = this.queues.get(key); if (queue) { queue.clear(); } this.metrics.delete(key); } else { this.rateLimiters.forEach((rateLimiter) => rateLimiter.reset()); this.queues.forEach((queue) => queue.clear()); this.metrics.clear(); } } /** * Get queue status for key */ getQueueStatus(key) { if (key) { const queue = this.queues.get(key); return queue ? queue.getQueueStatus() : null; } const status = {}; this.queues.forEach((queue, k) => { status[k] = queue.getQueueStatus(); }); return status; } /** * Update configuration */ updateConfig(config) { Object.assign(this.config, config); } /** * Get configuration */ getConfig() { return { ...this.config }; } }; var APIQuotaManager = class { quotas = /* @__PURE__ */ new Map(); config; constructor(config) { this.config = { enableMetrics: true, keyGenerator: (context) => context?.key || "default", ...config }; } /** * Check quota availability */ checkQuota(context) { const key = this.config.keyGenerator(context); const quota = this.getQuota(key); this.resetQuotasIfNeeded(quota); const dailyAllowed = quota.used.daily < quota.daily; const monthlyAllowed = quota.used.monthly < quota.monthly; const allowed = dailyAllowed && monthlyAllowed; return { allowed, remaining: { daily: Math.max(0, quota.daily - quota.used.daily), monthly: Math.max(0, quota.monthly - quota.used.monthly) }, resetTimes: quota.resetTimes }; } /** * Consume quota */ consumeQuota(amount = 1, context) { const key = this.config.keyGenerator(context); const quota = this.getQuota(key); this.resetQuotasIfNeeded(quota); const canConsume = quota.used.daily + amount <= quota.daily && quota.used.monthly + amount <= quota.monthly; if (canConsume) { quota.used.daily += amount; quota.used.monthly += amount; return true; } return false; } /** * Get quota for key */ getQuota(key) { if (!this.quotas.has(key)) { const now = Date.now(); this.quotas.set(key, { daily: this.config.dailyQuota, monthly: this.config.monthlyQuota, used: { daily: 0, monthly: 0 }, resetTimes: { daily: this.getNextDayReset(), monthly: this.getNextMonthReset() } }); } return this.quotas.get(key); } /** * Reset quotas if needed */ resetQuotasIfNeeded(quota) { const now = Date.now(); if (now >= quota.resetTimes.daily) { quota.used.daily = 0; quota.resetTimes.daily = this.getNextDayReset(); } if (now >= quota.resetTimes.monthly) { quota.used.monthly = 0; quota.resetTimes.monthly = this.getNextMonthReset(); } } /** * Get next day reset time */ getNextDayReset() { const tomorrow = /* @__PURE__ */ new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); return tomorrow.getTime(); } /** * Get next month reset time */ getNextMonthReset() { const nextMonth = /* @__PURE__ */ new Date(); nextMonth.setMonth(nextMonth.getMonth() + 1); nextMonth.setDate(1); nextMonth.setHours(0, 0, 0, 0); return nextMonth.getTime(); } /** * Get quota status */ getQuotaStatus(key) { if (key) { const quota = this.quotas.get(key); return quota ? { daily: { limit: quota.daily, used: quota.used.daily, remaining: quota.daily - quota.used.daily }, monthly: { limit: quota.monthly, used: quota.used.monthly, remaining: quota.monthly - quota.used.monthly }, resetTimes: quota.resetTimes } : null; } const status = {}; this.quotas.forEach((quota, k) => { status[k] = { daily: { limit: quota.daily, used: quota.used.daily, remaining: quota.daily - quota.used.daily }, monthly: { limit: quota.monthly, used: quota.used.monthly, remaining: quota.monthly - quota.used.monthly }, resetTimes: quota.resetTimes }; }); return status; } /** * Reset quotas */ resetQuota(key) { if (key) { const quota = this.quotas.get(key); if (quota) { quota.used.daily = 0; quota.used.monthly = 0; } } else { this.quotas.forEach((quota) => { quota.used.daily = 0; quota.used.monthly = 0; }); } } }; var RateLimitMiddleware = class { rateLimiter; quotaManager; constructor(rateLimitConfig, quotaConfig) { this.rateLimiter = new ZapperRateLimiter(rateLimitConfig); this.quotaManager = new APIQuotaManager(quotaConfig); } /** * Check if request is allowed */ async checkRequest(context) { const [rateLimit, quota] = await Promise.all([ this.rateLimiter.checkLimit(context), Promise.resolve(this.quotaManager.checkQuota(context)) ]); const allowed = rateLimit.allowed && quota.allowed; if (allowed) { this.quotaManager.consumeQuota(1, context); } return { allowed, rateLimit, quota }; } /** * Get comprehensive status */ getStatus() { return { rateLimit: { metrics: this.rateLimiter.getMetrics(), queueStatus: this.rateLimiter.getQueueStatus(), config: this.rateLimiter.getConfig() }, quota: this.quotaManager.getQuotaStatus() }; } /** * Reset all limits */ reset() { this.rateLimiter.reset(); this.quotaManager.resetQuota(); } }; // src/utils/fetchWithPolyfill.ts var fetchPolyfillLoaded = false; async function fetchWithPolyfill(url, options) { if (!globalThis.fetch && !fetchPolyfillLoaded) { try { const { default: fetch2, Headers, Request, Response } = await import("./src-J6JBKF5P.js"); globalThis.fetch = fetch2; globalThis.Headers = Headers; globalThis.Request = Request; globalThis.Response = Response; fetchPolyfillLoaded = true; } catch (error) { console.warn("Failed to load fetch polyfill:", error); } } return globalThis.fetch(url, options); } // src/utils/zapperGraphQL.ts var ZapperQueryBuilder = class { query = ""; variables = {}; fragments = /* @__PURE__ */ new Map(); /** * Set the main query operation */ setQuery(query) { this.query = query; return this; } /** * Add variables to the query */ addVariables(variables) { this.variables = { ...this.variables, ...variables }; return this; } /** * Add a fragment to the query */ addFragment(name, fragment) { this.fragments.set(name, fragment); return this; } /** * Build the complete GraphQL query with fragments */ build() { let builtQuery = this.query; if (this.fragments.size > 0) { const fragmentsString = Array.from(this.fragments.values()).join("\n\n"); builtQuery = `${builtQuery} ${fragmentsString}`; } return { query: builtQuery, variables: this.variables }; } /** * Clear the builder state */ clear() { this.query = ""; this.variables = {}; this.fragments.clear(); return this; } }; var ZapperGraphQLClient = class { config; requestCount = 0; lastRequestTime = 0; cache = null; errorHandler; rateLimitMiddleware = null; constructor(config) { this.config = { apiUrl: "https://public.zapper.xyz/graphql", timeout: 3e4, retryAttempts: 3, retryDelay: 1e3, debug: false, enableCaching: true, cacheConfig: { maxSize: 1e3, defaultTTL: 3e5, cleanupInterval: 6e4 }, errorHandlerConfig: {}, rateLimitConfig: { algorithm: "TOKEN_BUCKET" /* TOKEN_BUCKET */, maxRequests: 100, windowMs: 6e4, enableQueue: true, enableMetrics: true }, quotaConfig: { dailyQuota: 1e4, monthlyQuota: 1e5, enableMetrics: true }, ...config }; if (this.config.enableCaching) { this.cache = new ZapperQueryCache(this.config.cacheConfig); } this.errorHandler = new ZapperErrorHandler(this.config.errorHandlerConfig); if (this.config.rateLimitConfig && this.config.quotaConfig) { this.rateLimitMiddleware = new RateLimitMiddleware( this.config.rateLimitConfig, { dailyQuota: this.config.quotaConfig.dailyQuota || 1e4, monthlyQuota: this.config.quotaConfig.monthlyQuota || 1e5, enableMetrics: this.config.quotaConfig.enableMetrics || true } ); } } /** * Execute a GraphQL query with retry logic and caching */ async query(query, variables, options) { const mergedOptions = { timeout: this.config.timeout, retryAttempts: this.config.retryAttempts, retryDelay: this.config.retryDelay, useCache: this.config.enableCaching, cacheTTL: this.config.cacheConfig?.defaultTTL, ...options }; if (mergedOptions.useCache && this.cache) { const cachedResult = this.cache.get(query, variables); if (cachedResult) { if (this.config.debug) { elizaLogger3.debug("GraphQL query served from cache", { query: query.substring(0, 100) + "...", variables }); } return cachedResult; } } if (this.rateLimitMiddleware) { const limitCheck = await this.rateLimitMiddleware.checkRequest({ query: query.substring(0, 100) + "...", variables }); if (!limitCheck.allowed) { const error = new Error("Rate limit exceeded"); error.retryAfter = limitCheck.rateLimit.retryAfter; error.remaining = limitCheck.rateLimit.remaining; error.resetTime = limitCheck.rateLimit.resetTime; error.quotaRemaining = limitCheck.quota.remaining; throw error; } if (this.config.debug) { elizaLogger3.debug("Rate limit check passed", { remaining: limitCheck.rateLimit.remaining, quotaRemaining: limitCheck.quota.remaining }); } } const optimizedQuery = QueryOptimizer.removeUnusedFields(query, []); const optimizedVariables = QueryOptimizer.optimizeVariables(variables || {}); return this.errorHandler.executeWithRetry( async () => { if (this.config.debug) { elizaLogger3.debug("Executing GraphQL query", { query: optimizedQuery.substring(0, 100) + "...", variables: optimizedVariables, fromCache: false }); } const response = await this.executeQuery( optimizedQuery, optimizedVariables, mergedOptions.timeout ); if (response.errors && response.errors.length > 0) { elizaLogger3.warn("GraphQL query returned errors", { errors: response.errors }); throw response; } if (mergedOptions.useCache && this.cache && response.data) { this.cache.set(query, variables, response, mergedOptions.cacheTTL); } return response; }, { maxAttempts: mergedOptions.retryAttempts, baseDelay: mergedOptions.retryDelay, backoffMultiplier: 2, jitter: true }, { query: optimizedQuery.substring(0, 100) + "...", variables: optimizedVariables, useCache: mergedOptions.useCache } ); } /** * Execute a GraphQL mutation */ async mutate(mutation, variables, options) { return this.query(mutation, variables, options); } /** * Execute multiple queries in parallel */ async batchQuery(queries, options) { const promises = queries.map( ({ query, variables }) => this.query(query, variables, options) ); return Promise.all(promises); } /** * Get client statistics */ getStats() { const stats = { requestCount: this.requestCount, lastRequestTime: this.lastRequestTime, avgRequestTime: this.lastRequestTime / Math.max(this.requestCount, 1) }; const result = { ...stats }; if (this.cache) { result.cacheStats = this.cache.getStats(); } if (this.errorHandler) { result.errorStats = this.errorHandler.getMetrics(); } if (this.rateLimitMiddleware) { result.rateLimitStats = this.rateLimitMiddleware.getStatus(); } return result; } /** * Clear cache */ clearCache() { if (this.cache) { this.cache.clear(); } } /** * Invalidate cache entry */ invalidateCache(query, variables) { if (this.cache) { return this.cache.invalidate(query, variables); } return false; } /** * Invalidate cache entries by pattern */ invalidateCachePattern(pattern) { if (this.cache) { return this.cache.invalidatePattern(pattern); } return 0; } /** * Optimize cache */ optimizeCache() { if (this.cache) { return this.cache.optimize(); } return { entriesRemoved: 0, memoryFreed: 0 }; } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; if (config.enableCaching !== void 0 || config.cacheConfig) { if (this.cache) { this.cache.destroy(); } if (this.config.enableCaching) { this.cache = new ZapperQueryCache(this.config.cacheConfig); } else { this.cache = null; } } } /** * Get error handler metrics */ getErrorMetrics() { return this.errorHandler.getMetrics(); } /** * Reset error metrics */ resetErrorMetrics() { this.errorHandler.resetMetrics(); } /** * Get circuit breaker status */ getCircuitBreakerStatus() { return this.errorHandler.getCircuitBreakerStatus(); } /** * Reset circuit breaker */ resetCircuitBreaker() { this.errorHandler.resetCircuitBreaker(); } /** * Get rate limit status */ getRateLimitStatus() { return this.rateLimitMiddleware?.getStatus() || null; } /** * Reset rate limits */ resetRateLimits() { if (this.rateLimitMiddleware) { this.rateLimitMiddleware.reset(); } } /** * Destroy client and cleanup resources */ destroy() { if (this.cache) { this.cache.destroy(); this.cache = null; } } /** * Execute the actual GraphQL query */ async executeQuery(query, variables, timeout) { const startTime = Date.now(); this.requestCount++; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout || this.config.timeout); try { const response = await fetchWithPolyfill(this.config.apiUrl, { method: "POST", headers: { "Content-Type": "application/json", // CRITICAL: Use 'x-zapper-api-key' header, NOT 'Authorization: Bearer' // Bearer token format causes 401 authentication errors "x-zapper-api-key": this.config.apiKey, "User-Agent": "ElizaOS-ZapperPlugin/1.0.0" }, body: JSON.stringify({ query, variables: variables || {} }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`); } const result = await response.json(); this.lastRequestTime = Date.now() - startTime; if (this.config.debug) { elizaLogger3.debug("GraphQL query completed", { status: response.status, duration: this.lastRequestTime, hasErrors: !!(result.errors && result.errors.length > 0) }); } return result; } catch (error) { clearTimeout(timeoutId); if (error.name === "AbortError") { throw new Error(`Query timeout after ${timeout || this.config.timeout}ms`); } throw error; } } /** * Delay utility for retry logic */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; var ZapperGraphQLUtils = { /** * Create a new query builder instance */ createQueryBuilder() { return new ZapperQueryBuilder(); }, /** * Create a new GraphQL client instance */ createClient(config) {