eip7702-delegation-tracker
Version:
CLI tool for tracking and sweeping EIP-7702 delegation setup transactions on EVM chains
295 lines (251 loc) • 8.07 kB
JavaScript
const EventEmitter = require('events');
const { EIP7702Scanner } = require('./scanner');
/**
* MultiNetworkScanner - Monitor multiple networks simultaneously
*
* A stateless wrapper that manages multiple network scanners
* and reports all findings via unified events
*/
class MultiNetworkScanner extends EventEmitter {
constructor(networks = ['ethereum'], customConfigs = {}) {
super();
// Store scanner instances
this.scanners = new Map();
this.isMonitoring = false;
// Initialize scanners for each network
this.initializeNetworks(networks, customConfigs);
}
/**
* Initialize scanners for specified networks
*/
initializeNetworks(networks, customConfigs) {
// Handle different input formats
const networkList = Array.isArray(networks) ? networks : [networks];
networkList.forEach(network => {
const networkName = typeof network === 'string' ? network : network.name;
const config = customConfigs[networkName] || {};
try {
const scanner = new EIP7702Scanner(
networkName,
config.rpcUrl || (typeof network === 'object' ? network.rpcUrl : null),
config.wsUrl || (typeof network === 'object' ? network.wsUrl : null)
);
// Forward events with network context
this.setupEventForwarding(scanner, networkName);
this.scanners.set(networkName, scanner);
console.log(`✅ Initialized scanner for ${networkName}`);
} catch (error) {
console.error(`❌ Failed to initialize ${networkName}: ${error.message}`);
this.emit('error', { network: networkName, error });
}
});
}
/**
* Setup event forwarding from individual scanner to multi-scanner
*/
setupEventForwarding(scanner, network) {
// Forward delegation events with network info
scanner.on('delegation', (delegation) => {
this.emit('delegation', {
...delegation,
network // Ensure network is included
});
});
// Forward errors with network context
scanner.on('error', (error) => {
this.emit('error', {
network,
error,
message: `[${network}] ${error.message || error}`
});
});
// Forward connection events
scanner.on('connected', () => {
this.emit('connected', { network });
});
scanner.on('disconnected', () => {
this.emit('disconnected', { network });
});
}
/**
* Add a new network to monitor
*/
addNetwork(network, rpcUrl, wsUrl) {
if (this.scanners.has(network)) {
console.log(`Network ${network} already exists`);
return false;
}
try {
const scanner = new EIP7702Scanner(network, rpcUrl, wsUrl);
this.setupEventForwarding(scanner, network);
this.scanners.set(network, scanner);
// If already monitoring, start this scanner too
if (this.isMonitoring) {
scanner.watchBlocks().catch(err => {
this.emit('error', { network, error: err });
});
}
console.log(`✅ Added network: ${network}`);
return true;
} catch (error) {
this.emit('error', { network, error });
return false;
}
}
/**
* Remove a network from monitoring
*/
async removeNetwork(network) {
const scanner = this.scanners.get(network);
if (!scanner) {
console.log(`Network ${network} not found`);
return false;
}
try {
scanner.stop();
this.scanners.delete(network);
console.log(`✅ Removed network: ${network}`);
return true;
} catch (error) {
this.emit('error', { network, error });
return false;
}
}
/**
* Start monitoring all networks
*/
async startMonitoring(fromBlock) {
if (this.isMonitoring) {
console.log('Already monitoring');
return;
}
this.isMonitoring = true;
const promises = [];
for (const [network, scanner] of this.scanners) {
console.log(`Starting monitor for ${network}...`);
promises.push(
scanner.watchBlocks().catch(err => {
console.error(`Failed to start ${network}: ${err.message}`);
this.emit('error', { network, error: err });
})
);
}
await Promise.allSettled(promises);
console.log(`✅ Monitoring ${this.scanners.size} networks`);
}
/**
* Stop monitoring all networks
*/
async stopMonitoring() {
this.isMonitoring = false;
for (const [network, scanner] of this.scanners) {
try {
scanner.stop();
} catch (err) {
this.emit('error', { network, error: err });
}
}
console.log('✅ Stopped all monitors');
}
/**
* Scan a specific block across all networks
*/
async scanBlock(blockNumber) {
const results = new Map();
const promises = [];
for (const [network, scanner] of this.scanners) {
promises.push(
scanner.scanBlock(blockNumber)
.then(delegations => {
// Add network info to each delegation
const networkDelegations = delegations.map(d => ({
...d,
network
}));
results.set(network, networkDelegations);
})
.catch(err => {
console.error(`Error scanning ${network} block ${blockNumber}: ${err.message}`);
results.set(network, []);
})
);
}
await Promise.allSettled(promises);
// Flatten results
const allDelegations = [];
for (const delegations of results.values()) {
allDelegations.push(...delegations);
}
return allDelegations;
}
/**
* Scan a range of blocks across all networks
*/
async scanBlocks(startBlock, endBlock) {
const allDelegations = [];
for (let block = startBlock; block <= endBlock; block++) {
const blockDelegations = await this.scanBlock(block);
allDelegations.push(...blockDelegations);
}
return allDelegations;
}
/**
* Get delegation history for an address across all networks
*/
async getDelegationHistory(address, limit = 10) {
const results = new Map();
const promises = [];
for (const [network, scanner] of this.scanners) {
promises.push(
scanner.getDelegationHistory(address, limit)
.then(history => {
// Add network info to each entry
const networkHistory = history.map(h => ({
...h,
network
}));
results.set(network, networkHistory);
})
.catch(err => {
console.error(`Error getting history for ${address} on ${network}: ${err.message}`);
results.set(network, []);
})
);
}
await Promise.allSettled(promises);
// Combine and sort by block number
const allHistory = [];
for (const history of results.values()) {
allHistory.push(...history);
}
return allHistory.sort((a, b) => b.blockNumber - a.blockNumber).slice(0, limit);
}
/**
* Get list of active networks
*/
getNetworks() {
return Array.from(this.scanners.keys());
}
/**
* Get scanner for specific network
*/
getScanner(network) {
return this.scanners.get(network);
}
/**
* Get monitoring status
*/
getStatus() {
const status = {};
for (const [network, scanner] of this.scanners) {
status[network] = {
initialized: true,
monitoring: scanner.isMonitoring || false,
chainId: scanner.chainId,
explorer: scanner.explorer
};
}
return status;
}
}
module.exports = { MultiNetworkScanner };