chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
62
services/etherlink-relay/src/EtherlinkRelayService.js
Normal file
62
services/etherlink-relay/src/EtherlinkRelayService.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Etherlink custom relay: monitor source for messages to 42793, queue, call EtherlinkRelayReceiver.relayMintOrUnlock.
|
||||
* When CCIP does not support Etherlink. See docs/bridge/ETHERLINK_RELAY_RUNBOOK.md.
|
||||
*/
|
||||
import { ethers } from 'ethers';
|
||||
import { config } from './config.js';
|
||||
import { EtherlinkRelayReceiverABI } from './abis.js';
|
||||
import * as metrics from './metrics.js';
|
||||
|
||||
export class EtherlinkRelayService {
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
this.queue = [];
|
||||
this.inFlight = 0;
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (!config.etherlinkRelayBridge) {
|
||||
throw new Error('ETHERLINK_RELAY_BRIDGE required');
|
||||
}
|
||||
if (!config.relayPrivateKey) {
|
||||
throw new Error('ETHERLINK_RELAY_PRIVATE_KEY or PRIVATE_KEY required');
|
||||
}
|
||||
this.etherlinkProvider = new ethers.JsonRpcProvider(config.etherlinkRpcUrl);
|
||||
this.signer = new ethers.Wallet(config.relayPrivateKey, this.etherlinkProvider);
|
||||
this.receiver = new ethers.Contract(config.etherlinkRelayBridge, EtherlinkRelayReceiverABI, this.signer);
|
||||
this.logger.info('Etherlink relay started', { receiver: config.etherlinkRelayBridge });
|
||||
setInterval(() => this.processQueue(), config.pollIntervalMs);
|
||||
}
|
||||
|
||||
async pushMessage(messageId, token, recipient, amount) {
|
||||
if (this.queue.length + this.inFlight >= config.queueDepthLimit) {
|
||||
this.logger.warn('Queue depth limit reached', { limit: config.queueDepthLimit });
|
||||
return;
|
||||
}
|
||||
this.queue.push({ messageId, token, recipient, amount });
|
||||
metrics.incrementDetected();
|
||||
metrics.setQueueDepth(this.queue.length + this.inFlight);
|
||||
}
|
||||
|
||||
async processQueue() {
|
||||
while (this.queue.length > 0 && this.inFlight < config.maxConcurrent) {
|
||||
const msg = this.queue.shift();
|
||||
this.inFlight++;
|
||||
metrics.setQueueDepth(this.queue.length + this.inFlight);
|
||||
this.submit(msg).finally(() => {
|
||||
this.inFlight--;
|
||||
metrics.setQueueDepth(this.queue.length + this.inFlight);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async submit({ messageId, token, recipient, amount }) {
|
||||
try {
|
||||
await this.receiver.relayMintOrUnlock(messageId, token, recipient, amount);
|
||||
metrics.incrementSuccess();
|
||||
} catch (err) {
|
||||
this.logger.error('relayMintOrUnlock failed', { messageId, error: err.message });
|
||||
metrics.incrementFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
services/etherlink-relay/src/abis.js
Normal file
4
services/etherlink-relay/src/abis.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const EtherlinkRelayReceiverABI = [
|
||||
"function relayMintOrUnlock(bytes32 messageId, address token, address recipient, uint256 amount) external",
|
||||
"event RelayMintOrUnlock(bytes32 indexed messageId, address indexed token, address indexed recipient, uint256 amount)",
|
||||
];
|
||||
21
services/etherlink-relay/src/config.js
Normal file
21
services/etherlink-relay/src/config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
// Load PRIVATE_KEY from dotenv: smom-dbis-138/.env then service .env
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||
const cwd = process.cwd();
|
||||
dotenv.config({ path: path.resolve(cwd, 'smom-dbis-138/.env') });
|
||||
dotenv.config({ path: path.resolve(cwd, '.env') });
|
||||
|
||||
export const config = {
|
||||
sourceChain: { rpcUrl: process.env.RPC_URL_138 || process.env.RPC_URL || "http://127.0.0.1:8545" },
|
||||
etherlinkRpcUrl: process.env.ETHERLINK_RPC_URL || "https://node.mainnet.etherlink.com",
|
||||
etherlinkRelayBridge: process.env.ETHERLINK_RELAY_BRIDGE || "",
|
||||
relayPrivateKey: process.env.ETHERLINK_RELAY_PRIVATE_KEY || process.env.PRIVATE_KEY,
|
||||
pollIntervalMs: parseInt(process.env.POLL_INTERVAL_MS || "5000", 10),
|
||||
maxConcurrent: parseInt(process.env.ETHERLINK_RELAY_MAX_CONCURRENT || "5", 10),
|
||||
queueDepthLimit: parseInt(process.env.ETHERLINK_RELAY_QUEUE_DEPTH || "100", 10),
|
||||
};
|
||||
11
services/etherlink-relay/src/metrics.js
Normal file
11
services/etherlink-relay/src/metrics.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const metrics = {
|
||||
etherlink_relay_messages_detected_total: 0,
|
||||
etherlink_relay_submissions_success_total: 0,
|
||||
etherlink_relay_submissions_failed_total: 0,
|
||||
etherlink_relay_queue_depth: 0,
|
||||
};
|
||||
export function incrementDetected() { metrics.etherlink_relay_messages_detected_total++; }
|
||||
export function incrementSuccess() { metrics.etherlink_relay_submissions_success_total++; }
|
||||
export function incrementFailed() { metrics.etherlink_relay_submissions_failed_total++; }
|
||||
export function setQueueDepth(n) { metrics.etherlink_relay_queue_depth = n; }
|
||||
export function getMetrics() { return { ...metrics }; }
|
||||
Reference in New Issue
Block a user