@bronlabs/intents-sdk
Version:
SDK for Intents DeFi smart contracts
130 lines • 5.29 kB
JavaScript
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