UNPKG

@bronlabs/intents-sdk

Version:
130 lines 5.29 kB
import { ethers } from 'ethers'; import { sleep, log } from './utils.js'; import { EventQueue } from './eventQueue.js'; import { initOrderEngine } from './contracts.js'; export class OrderIndexer { constructor(config) { this.config = config; this.provider = new ethers.JsonRpcProvider(config.rpcUrl); this.orderEngine = initOrderEngine(config.orderEngineAddress, this.provider); this.eventQueue = new EventQueue(); this.processors = []; this.isRunning = false; this.lastProcessedBlock = 0; } addProcessor(processor) { this.processors.push(processor); } async start() { if (this.isRunning) { log.warn('Indexer is already running'); return; } this.isRunning = true; log.info(`Starting indexer from block ${this.lastProcessedBlock}`); try { await this.startIndexingLoop(); } catch (error) { log.error('Error starting indexer:', error); this.isRunning = false; } } async stop() { if (!this.isRunning) { log.warn('Indexer is not running'); return; } log.info('Stopping indexer...'); this.isRunning = false; const maxWaitTime = 30000; const startTime = Date.now(); while (!this.eventQueue.isEmpty() && (Date.now() - startTime) < maxWaitTime) { log.info(`Waiting for ${this.eventQueue.size()} events to be processed before stopping`); await this.processEventQueue(); await sleep(1000); } if (!this.eventQueue.isEmpty()) { log.warn(`Stopping indexer with ${this.eventQueue.size()} unprocessed events after timeout`); } log.info('Indexer stopped'); } async startIndexingLoop() { if (!this.isRunning) return; try { const currentBlock = await this.provider.getBlockNumber(); if (!this.lastProcessedBlock) { this.lastProcessedBlock = currentBlock - this.config.startBlockOffset; } if (currentBlock > this.lastProcessedBlock) { // Fetch historical events in chunks to avoid RPC limitations const chunkSize = 500; for (let fromBlock = this.lastProcessedBlock + 1; fromBlock <= currentBlock; fromBlock += chunkSize) { const toBlock = Math.min(fromBlock + chunkSize - 1, currentBlock); const events = await this.orderEngine.queryFilter(this.orderEngine.filters.OrderStatusChanged(), fromBlock, toBlock); if (events.length > 0) { log.info(`Found ${events.length} events between blocks ${fromBlock} and ${toBlock}`); } for (const event of events) { const { args: { orderId, status } } = this.orderEngine.interface.parseLog({ topics: event.topics, data: event.data }); this.eventQueue.add({ type: 'OrderStatusChanged', data: { orderId, status: BigInt(status) }, event: event, retries: 0 }); } this.lastProcessedBlock = toBlock; if (this.isRunning) { await this.processEventQueue(); } } } } catch (error) { log.error('Error in indexing loop: ', error); } // Schedule next iteration if (this.isRunning) { setTimeout(() => this.startIndexingLoop(), this.config.pollingInterval); } } async processEventQueue() { if (this.eventQueue.isEmpty() || this.processors.length === 0) return; log.info(`Processing event queue with ${this.eventQueue.size()} events`); while (!this.eventQueue.isEmpty()) { const event = this.eventQueue.peek(); if (!event) continue; try { // Process the event with all registered processors for (const processor of this.processors) { await processor(event); } this.eventQueue.remove(); } catch (error) { log.error(`Error processing event:`, error); // Retry logic if (event.retries < this.config.maxRetries) { event.retries++; log.info(`Retrying event processing (${event.retries}/${this.config.maxRetries})`); await sleep(this.config.retryDelay); } else { log.error(`Max retries reached for event, removing from queue:`, event); this.eventQueue.remove(); // Could add dead letter queue here for manual inspection } } } } } //# sourceMappingURL=orderIndexer.js.map