Skip to main content
Glama

MCP Ethers Wallet

rateLimiter.ts5.5 kB
/** * @file Rate Limiter * @version 1.0.0 * @status STABLE - DO NOT MODIFY WITHOUT TESTS * @lastModified 2024-06-07 * * Simple in-memory rate limiter to prevent API abuse * * IMPORTANT: * - Keep memory usage reasonable * - Use sliding window algorithm for accurate rate limiting * * Functionality: * - Request rate limiting * - Different limits for different operations * - Sliding window algorithm */ interface RateLimitOptions { windowMs: number; // Time window in milliseconds maxRequests: number; // Maximum requests per window } interface RateLimitBucket { timestamps: number[]; // Array of request timestamps blockedUntil?: number; // Timestamp until which requests are blocked } export class RateLimiter { private limits: Map<string, RateLimitOptions> = new Map(); private buckets: Map<string, RateLimitBucket> = new Map(); /** * Create a new rate limiter */ constructor() { // Set default limits for different operations this.setLimit('default', { windowMs: 60000, maxRequests: 120 }); // 120 requests per minute this.setLimit('contract', { windowMs: 60000, maxRequests: 60 }); // 60 contract calls per minute this.setLimit('transaction', { windowMs: 60000, maxRequests: 20 }); // 20 transactions per minute // Clean up old entries periodically setInterval(() => this.cleanup(), 60000); // Every minute } /** * Set a rate limit for a specific operation * @param operation Operation name * @param options Rate limit options */ public setLimit(operation: string, options: RateLimitOptions): void { this.limits.set(operation, options); } /** * Check if an operation for a specific identity is rate limited * @param operation Operation name * @param identity Identity string (e.g., IP, user ID) * @returns True if the operation is allowed, false if rate limited */ public isAllowed(operation: string, identity: string): boolean { const key = `${operation}:${identity}`; const limit = this.limits.get(operation) || this.limits.get('default')!; // Create bucket if it doesn't exist if (!this.buckets.has(key)) { this.buckets.set(key, { timestamps: [] }); } const bucket = this.buckets.get(key)!; // Check if currently blocked if (bucket.blockedUntil && Date.now() < bucket.blockedUntil) { return false; } // Remove timestamps outside the current window const now = Date.now(); const windowStart = now - limit.windowMs; bucket.timestamps = bucket.timestamps.filter(time => time >= windowStart); // Check if under the limit if (bucket.timestamps.length < limit.maxRequests) { bucket.timestamps.push(now); return true; } // Rate limited return false; } /** * Check if an operation is allowed and record it * @param operation Operation name * @param identity Identity string * @returns True if allowed, false if rate limited */ public consume(operation: string, identity: string): boolean { if (this.isAllowed(operation, identity)) { return true; } // If not allowed, update the blocked until time const key = `${operation}:${identity}`; const bucket = this.buckets.get(key)!; const limit = this.limits.get(operation) || this.limits.get('default')!; // Block for the duration of the window bucket.blockedUntil = Date.now() + limit.windowMs; return false; } /** * Get remaining requests for an operation and identity * @param operation Operation name * @param identity Identity string * @returns Number of remaining requests in the current window */ public getRemainingRequests(operation: string, identity: string): number { const key = `${operation}:${identity}`; const limit = this.limits.get(operation) || this.limits.get('default')!; if (!this.buckets.has(key)) { return limit.maxRequests; } const bucket = this.buckets.get(key)!; // Check if currently blocked if (bucket.blockedUntil && Date.now() < bucket.blockedUntil) { return 0; } // Remove timestamps outside the current window const now = Date.now(); const windowStart = now - limit.windowMs; bucket.timestamps = bucket.timestamps.filter(time => time >= windowStart); return Math.max(0, limit.maxRequests - bucket.timestamps.length); } /** * Reset rate limit for an operation and identity * @param operation Operation name * @param identity Identity string */ public reset(operation: string, identity: string): void { const key = `${operation}:${identity}`; this.buckets.delete(key); } /** * Clean up old entries to prevent memory leaks */ private cleanup(): void { const now = Date.now(); for (const [key, bucket] of this.buckets.entries()) { const [operation] = key.split(':'); const limit = this.limits.get(operation) || this.limits.get('default')!; // If bucket is empty or all timestamps are old, remove it const windowStart = now - limit.windowMs; bucket.timestamps = bucket.timestamps.filter(time => time >= windowStart); if ( bucket.timestamps.length === 0 && (!bucket.blockedUntil || bucket.blockedUntil < now) ) { this.buckets.delete(key); } } } } // Export singleton instance export const rateLimiter = new RateLimiter();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/crazyrabbitLTC/mcp-ethers-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server