export interface RateLimiterOptions { interval: number; maxRequests: number; timeout?: number; } export interface QueuedRequest { fn: () => Promise; resolve: (value: T) => void; reject: (error: Error) => void; addedAt: number; } export const DEFAULT_RATE_LIMIT_TIMEOUT = 30000; export class RateLimiter { private queue: QueuedRequest[] = []; private processing = false; private requestHistory: number[] = []; private readonly options: Required; constructor(options: RateLimiterOptions) { this.options = { timeout: DEFAULT_RATE_LIMIT_TIMEOUT, ...options, }; } async add( fn: () => Promise, options?: { timeout?: number } ): Promise { return new Promise((resolve, reject) => { const timeout = options?.timeout ?? this.options.timeout; const addedAt = Date.now(); // set timeout const timeoutId = setTimeout(() => { // remove from queue const index = this.queue.findIndex((req) => req.addedAt === addedAt); if (index !== -1) { this.queue.splice(index, 1); } reject(new Error("Request timeout")); }, timeout); const queuedRequest: QueuedRequest = { fn, resolve: (value: T) => { clearTimeout(timeoutId); resolve(value); }, reject: (error: Error) => { clearTimeout(timeoutId); reject(error); }, addedAt, }; this.queue.push(queuedRequest); this.processQueue(); }); } private async processQueue(): Promise { if (this.processing || this.queue.length === 0) { return; } this.processing = true; while (this.queue.length > 0) { // check rate limit await this.waitIfNeeded(); const request = this.queue.shift(); if (!request) break; try { // record request before execution this.requestHistory.push(Date.now()); const result = await request.fn(); request.resolve(result); } catch (error) { request.reject( error instanceof Error ? error : new Error(String(error)) ); } } this.processing = false; } private async waitIfNeeded(): Promise { const now = Date.now(); // remove expired request history this.requestHistory = this.requestHistory.filter( (timestamp) => now - timestamp < this.options.interval ); // check rate limit if (this.requestHistory.length >= this.options.maxRequests) { const oldestRequest = this.requestHistory[0]; const waitTime = this.options.interval - (now - oldestRequest); if (waitTime > 0) { await this.delay(waitTime); } } } private delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } getQueueSize(): number { return this.queue.length; } getPendingRequests(): number { return this.requestHistory.length; } } export const bitcoinInscriptionsRateLimiter = new RateLimiter({ interval: 1000, maxRequests: 10, timeout: 5000, });