/** * 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(); } } }