Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions backend-abi-registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Automated Smart Contract ABI Registry and Parser

This is a standalone "dummy" implementation of the ABI Registry and Parser, built to fulfill the MVP-critical feature #441 for the backend. As per the architectural guidelines, this directory encapsulates the entire functionality and can be moved/integrated into the main backend (e.g. `indexer` or `keeper`) when needed.

## Features Overview
- **Continuous Monitoring:** Resilient polling system (`monitor.js`) to track newly deployed smart contracts.
- **ABI Extraction:** Decoupled parsing logic (`parser.js`) to extract and standardize ABIs from raw bytecode.
- **Searchable Registry:** Fast in-memory map (`registry.js`) allowing searches by function name and retrieval by contract address.
- **Fault-Tolerant Pipeline:** Fully wrapped with an `ErrorHandler` to catch, log, and recover from simulated node failures or parse errors without crashing the main service.

## Architecture Boundaries
1. **Separation of Concerns:** Each subsystem (monitor, parser, registry) is completely decoupled.
2. **Stateless Operations:** The parser operates statelessly.
3. **Resiliency:** Unhandled errors during polling or processing are caught and stored for fallback procedures.

## How to Test
This package has strict >90% test coverage requirements. To run the tests, use:
```bash
npm install
npm test
```

## Integration
To integrate into the main project:
1. Move the desired components into the target backend folder (e.g. `/indexer`).
2. Instantiate `ABIRegistryService` from `index.js`.
3. Call `start()` to begin monitoring.
4. Access the searchable registry via `getRegistry()`.
27 changes: 27 additions & 0 deletions backend-abi-registry/errorHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class ErrorHandler {
constructor() {
this.errors = [];
}

logError(context, error) {
const errorRecord = {
timestamp: new Date().toISOString(),
context,
message: error.message || error,
stack: error.stack || null,
};
this.errors.push(errorRecord);
console.error(`[ErrorHandler] [${context}]`, error.message || error);
// In a real implementation, this might send to Sentry, Datadog, etc.
}

getRecentErrors() {
return this.errors.slice(-100);
}

clearErrors() {
this.errors = [];
}
}

module.exports = new ErrorHandler();
30 changes: 30 additions & 0 deletions backend-abi-registry/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const parser = require('./parser');
const registry = require('./registry');
const errorHandler = require('./errorHandler');
const Monitor = require('./monitor');

class ABIRegistryService {
constructor() {
this.monitor = new Monitor(parser, registry);
}

start() {
console.log('[ABIRegistryService] Initializing ABI Registry and Parser Service...');
this.monitor.start();
}

stop() {
console.log('[ABIRegistryService] Shutting down ABI Registry and Parser Service...');
this.monitor.stop();
}

getRegistry() {
return registry;
}

getErrorHandler() {
return errorHandler;
}
}

module.exports = new ABIRegistryService();
68 changes: 68 additions & 0 deletions backend-abi-registry/monitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const errorHandler = require('./errorHandler');

class Monitor {
constructor(parser, registry) {
this.parser = parser;
this.registry = registry;
this.isMonitoring = false;
this.intervalId = null;
this.pollingInterval = 5000;
}

/**
* Starts the mock polling process
*/
start() {
if (this.isMonitoring) return;
this.isMonitoring = true;
console.log('[Monitor] Starting to monitor deployed contracts...');

this.intervalId = setInterval(() => {
this.poll();
}, this.pollingInterval);
}

stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.isMonitoring = false;
console.log('[Monitor] Stopped monitoring.');
}

/**
* Simulates polling a blockchain node for new contracts
*/
async poll() {
try {
// In a real scenario, fetch new contracts from an RPC node
const mockDeployments = this.fetchMockDeployments();

for (const deployment of mockDeployments) {
const abi = this.parser.extractABI(deployment.data);
if (abi) {
this.registry.addABI(deployment.address, abi);
}
}
} catch (err) {
errorHandler.logError('Monitor', err);
// Implement fallback logic here if needed, e.g., switch RPC node
}
}

fetchMockDeployments() {
// Simulated new deployment
return [
{
address: `C${Math.random().toString(36).substring(2, 15).toUpperCase()}`,
data: {
bytecode: '0x1234',
mockFunctions: [{ name: 'transfer', args: ['to', 'amount'] }]
}
}
];
}
}

module.exports = Monitor;
Loading