Files
smom-dbis-138/services/relay/src/config.js
defiQUG 11c97777d4
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m11s
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m4s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 31s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 29s
Verify Deployment / Verify Deployment (push) Failing after 57s
feat(chain138): Monad CCIP, token aggregation OMNL gates, HYBX client, and PMM deploy updates.
Relay router, reserve system, oracle publisher, token-aggregation compliance middleware, and Monad deployment scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 00:11:33 -07:00

369 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Configuration for CCIP Relay Service
*/
import dotenv from 'dotenv';
import path from 'path';
import { existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { parseRpcPool } from './rpcPool.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Load project root first so PRIVATE_KEY is set, then relay .env
const projectEnv = path.resolve(__dirname, '../../../.env');
const relayEnv = path.resolve(__dirname, '../.env');
dotenv.config({ path: projectEnv });
dotenv.config({ path: relayEnv });
const DEFAULT_SOURCE_CHAIN_ID = Number(process.env.SOURCE_CHAIN_ID || '138');
const DEFAULT_DEST_CHAIN_ID = Number(process.env.DEST_CHAIN_ID || '1');
// Fill contract addresses from master JSON (config/smart-contracts-master.json) when not set in .env
const repoRoots = [
path.resolve(__dirname, '../../../'),
path.resolve(__dirname, '../../../../')
];
function resolveConfigFile(fileName) {
for (const root of repoRoots) {
const candidate = path.join(root, 'config', fileName);
if (existsSync(candidate)) {
return candidate;
}
}
return path.join(repoRoots[0], 'config', fileName);
}
const contractsLoaderPath = resolveConfigFile('contracts-loader.cjs');
const tokenMappingLoaderPath = resolveConfigFile('token-mapping-loader.cjs');
try {
const require = createRequire(import.meta.url);
const { loadContractsIntoProcessEnv } = require(contractsLoaderPath);
if (typeof loadContractsIntoProcessEnv === 'function') {
loadContractsIntoProcessEnv([DEFAULT_SOURCE_CHAIN_ID, DEFAULT_DEST_CHAIN_ID]);
}
} catch (_) { /* run from smom-dbis-138 only: loader not found */ }
function getExplicitTokenMappingOverrides() {
const overrides = {};
const destinationWeth9 =
process.env.DEST_WETH9_ADDRESS ||
process.env.DEST_WETH_ADDRESS ||
'';
const destinationLink = process.env.DEST_LINK_ADDRESS || '';
if (destinationWeth9 && !destinationWeth9.includes('${')) {
overrides['0xc02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase()] = destinationWeth9;
const sourceWmon =
process.env.SOURCE_WMON_ADDRESS ||
process.env.SOURCE_WETH9_ADDRESS ||
'';
if (sourceWmon && !sourceWmon.includes('${')) {
overrides[sourceWmon.toLowerCase()] = destinationWeth9;
}
}
if (destinationLink && !destinationLink.includes('${')) {
overrides['0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03'.toLowerCase()] = destinationLink;
}
return overrides;
}
function withExplicitTokenMappingOverrides(mapping) {
return {
...(mapping || {}),
...getExplicitTokenMappingOverrides()
};
}
// Token mapping for the active source/destination pair: prefer multichain mapping when available.
function getTokenMapping() {
const sourceChainId = Number(process.env.SOURCE_CHAIN_ID || '138');
const destinationChainId = Number(process.env.DEST_CHAIN_ID || '1');
try {
const require = createRequire(import.meta.url);
const { getTokenMappingForPair, getRelayTokenMapping } = require(tokenMappingLoaderPath);
const pair = getTokenMappingForPair && getTokenMappingForPair(sourceChainId, destinationChainId);
if (pair && pair.addressMapFromTo && Object.keys(pair.addressMapFromTo).length > 0) {
return withExplicitTokenMappingOverrides(pair.addressMapFromTo);
}
const fromFile = getRelayTokenMapping && getRelayTokenMapping();
if (sourceChainId === 138 && destinationChainId === 1 && fromFile && Object.keys(fromFile).length > 0) {
return withExplicitTokenMappingOverrides(fromFile);
}
} catch (_) { /* config not available */ }
const destinationWeth9 = process.env.DEST_WETH9_ADDRESS || process.env.DEST_WETH_ADDRESS || '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
// Fallback keeps WETH and LINK mapping for legacy relay profiles.
return withExplicitTokenMappingOverrides({
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': destinationWeth9,
'0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03': process.env.DEST_LINK_ADDRESS || '0x514910771AF9Ca656af840dff83E8264EcF986CA'
});
}
// If PRIVATE_KEY still missing, try cwd-relative paths (e.g. run from repo root or relay dir)
if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY.includes('${')) {
const cwd = process.cwd();
const tries = [
path.resolve(cwd, '../../.env'), // cwd is services/relay
path.resolve(cwd, 'smom-dbis-138/.env') // cwd is project root (proxmox)
];
for (const p of tries) {
if (p !== projectEnv && p !== relayEnv) dotenv.config({ path: p });
}
}
function getEffectivePrivateKey() {
const v = process.env.RELAYER_PRIVATE_KEY || process.env.PRIVATE_KEY || '';
return (v && !v.includes('${')) ? v : (process.env.PRIVATE_KEY || '');
}
// Build mainnet RPC URL; use INFURA_PROJECT_SECRET (or METAMASK_SECRET) for Basic Auth when using Infura
function getBscRelayRpcUrl() {
const relay = process.env.BSC_RELAY_RPC_URL || '';
if (relay && !relay.includes('${')) return relay;
const dest = process.env.DEST_RPC_URL || '';
if (dest && !dest.includes('${')) return dest;
const fallback = process.env.BSC_RPC_URL || process.env.BSC_MAINNET_RPC || '';
if (fallback && !fallback.includes('${')) return fallback;
return '';
}
function getDestinationRpcPoolUrls() {
const destChainId = process.env.DEST_CHAIN_ID ? parseInt(process.env.DEST_CHAIN_ID, 10) : 1;
const poolFromEnv = parseRpcPool(
process.env.DEST_RPC_URL_POOL,
process.env.RPC_URL_MAINNET_POOL,
process.env.DEST_RPC_URL
);
if (poolFromEnv.length > 0) return poolFromEnv;
if (destChainId === 56) {
const bscRelay = getBscRelayRpcUrl();
if (bscRelay) return parseRpcPool(bscRelay);
}
const explicit = process.env.DEST_RPC_URL || '';
if (explicit && !explicit.includes('${')) return parseRpcPool(explicit);
return parseRpcPool(getMainnetRpcUrl());
}
function getDestinationRpcUrl() {
const pool = getDestinationRpcPoolUrls();
return pool[0] || getMainnetRpcUrl();
}
/** Optional protected submit endpoint (Blink / MEV Blocker) for destination relay txs. */
function getDestinationSubmitRpcUrl() {
const explicit =
process.env.RELAY_DEST_SUBMIT_RPC_URL ||
process.env.BLINK_RPC_URL ||
process.env.MEV_BLOCKER_RPC_URL ||
'';
if (explicit && !explicit.includes('${')) return explicit;
return '';
}
function getMainnetRpcUrl() {
let raw = process.env.RPC_URL_MAINNET || process.env.ETHEREUM_MAINNET_RPC || '';
if (!raw && process.env.INFURA_PROJECT_ID && !String(process.env.INFURA_PROJECT_ID).includes('${')) {
raw = `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`;
}
if (!raw) raw = 'https://ethereum.publicnode.com';
const secret = process.env.INFURA_PROJECT_SECRET || process.env.METAMASK_SECRET || '';
const infuraMatch = raw.match(/^https:\/\/mainnet\.infura\.io\/v3\/([a-f0-9]+)$/i);
if (infuraMatch && secret && !secret.includes('${')) {
const projectId = infuraMatch[1];
return `https://${encodeURIComponent(projectId)}:${encodeURIComponent(secret)}@mainnet.infura.io/v3/${projectId}`;
}
return raw;
}
function getSourceRpcUrl() {
return (
process.env.SOURCE_RPC_URL ||
process.env.RPC_URL_138_PUBLIC ||
process.env.CHAIN138_RPC_URL_PUBLIC ||
process.env.RPC_URL_138 ||
process.env.RPC_URL ||
'https://rpc.public-0138.defi-oracle.io'
);
}
function getDestinationRelayBridgeAddress() {
if (Object.prototype.hasOwnProperty.call(process.env, 'DEST_RELAY_BRIDGE')) {
return process.env.DEST_RELAY_BRIDGE || '';
}
return (
process.env.CCIP_RELAY_BRIDGE_MAINNET ||
process.env.RELAY_BRIDGE_MAINNET ||
'0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939'
);
}
function getDestinationRelayBridgeLinkAddress() {
return (
process.env.DEST_RELAY_BRIDGE_LINK ||
process.env.CCIP_RELAY_BRIDGE_LINK_MAINNET ||
''
);
}
function getSkipMessageIds() {
return new Set(
String(process.env.RELAY_SKIP_MESSAGE_IDS || '')
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean)
);
}
export const config = {
// Source chain: defaults to Chain 138 but can be overridden for reverse relay profiles.
sourceChain: {
name: process.env.SOURCE_CHAIN_NAME || 'Chain 138',
chainId: DEFAULT_SOURCE_CHAIN_ID,
rpcUrl: getSourceRpcUrl(),
routerAddress:
process.env.SOURCE_ROUTER_ADDRESS ||
process.env.SOURCE_ROUTER_ADDRESS ||
process.env.CCIP_ROUTER_CHAIN138 ||
process.env.CCIP_ROUTER ||
'0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817',
bridgeAddress:
process.env.SOURCE_BRIDGE_ADDRESS ||
process.env.CCIPWETH9_BRIDGE_CHAIN138 ||
process.env.CCIPWETH9_BRIDGE_CHAIN138_LINK ||
'0xcacfd227A040002e49e2e01626363071324f820a'
},
// Destination chain: defaults to Ethereum Mainnet for backward compatibility.
// Override for other chains (e.g. BSC/AVAX) with DEST_* env vars.
destinationChain: {
name: process.env.DEST_CHAIN_NAME || 'Ethereum Mainnet',
chainId: process.env.DEST_CHAIN_ID ? parseInt(process.env.DEST_CHAIN_ID) : 1,
rpcUrl: getDestinationRpcUrl(),
rpcPoolUrls: getDestinationRpcPoolUrls(),
submitRpcUrl: getDestinationSubmitRpcUrl(),
// Upstream BSC mainnet RPC for operator tooling (casts, fee probes); relay lane may use ngrok.
upstreamRpcUrl:
process.env.DEST_RPC_UPSTREAM_URL ||
process.env.BSC_RPC_URL ||
process.env.BSC_MAINNET_RPC ||
'',
relayRouterAddress:
process.env.DEST_RELAY_ROUTER ||
process.env.CCIP_RELAY_ROUTER_MAINNET ||
process.env.RELAY_ROUTER_MAINNET ||
'0x416564Ab73Ad5710855E98dC7bC7Bff7387285BA',
relayBridgeAddress:
getDestinationRelayBridgeAddress(),
relayBridgeLinkAddress:
getDestinationRelayBridgeLinkAddress(),
deliveryMode: process.env.DEST_DELIVERY_MODE || 'router',
// Optional CSV allowlist for per-message receiver routing.
// When set, relay will only forward to bridges in this list.
relayBridgeAllowlist: (process.env.DEST_RELAY_BRIDGE_ALLOWLIST || '')
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean),
chainSelector: BigInt(process.env.DEST_CHAIN_SELECTOR || '5009297550715157269'),
weth9Address: process.env.DEST_WETH9_ADDRESS || '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
},
// Token address mapping for the active relay pair.
tokenMapping: getTokenMapping(),
// Relay profiles use explicit selectors; default source selector remains 138 for legacy profiles.
sourceChainSelector: BigInt(process.env.SOURCE_CHAIN_SELECTOR || '138'),
// Relayer configuration (dotenv does not expand ${PRIVATE_KEY}; getter uses effective key)
relayer: {
get privateKey() {
return getEffectivePrivateKey();
},
address: process.env.RELAYER_ADDRESS || ''
},
// Monitoring configuration
monitoring: {
startBlock: (() => {
const raw = process.env.START_BLOCK;
if (raw === undefined || raw === '') return 'latest';
const s = String(raw).trim().toLowerCase();
if (s === 'latest') return 'latest';
const n = parseInt(raw, 10);
return Number.isFinite(n) ? n : 'latest';
})(),
pollInterval: process.env.POLL_INTERVAL ? parseInt(process.env.POLL_INTERVAL) : 5000, // 5 seconds
confirmationBlocks: process.env.CONFIRMATION_BLOCKS ? parseInt(process.env.CONFIRMATION_BLOCKS) : 1,
finalityDelayBlocks: process.env.FINALITY_DELAY_BLOCKS ? parseInt(process.env.FINALITY_DELAY_BLOCKS) : 2,
replayWindowBlocks: process.env.REPLAY_WINDOW_BLOCKS ? parseInt(process.env.REPLAY_WINDOW_BLOCKS) : 32
},
// Retry configuration
retry: {
maxRetries: process.env.MAX_RETRIES ? parseInt(process.env.MAX_RETRIES) : 3,
retryDelay: process.env.RETRY_DELAY ? parseInt(process.env.RETRY_DELAY) : 5000 // 5 seconds
},
// Parallel destination delivery (NonceManager on submit signer; default 1 = legacy serial)
concurrency: {
maxConcurrent: (() => {
const raw = parseInt(process.env.RELAY_MAX_CONCURRENT || '1', 10);
if (!Number.isFinite(raw) || raw < 1) return 1;
return Math.min(raw, 12);
})()
},
queuePersistence: {
path: (() => {
const raw = process.env.RELAY_QUEUE_STATE_PATH;
if (!raw) return path.resolve(__dirname, '../data/queue-state.json');
return path.isAbsolute(raw) ? raw : path.resolve(__dirname, '..', raw);
})()
},
skipMessageIds: getSkipMessageIds()
};
/**
* Relay shedding: pause **destination-chain** relay txs (saves Mainnet gas) while still polling
* the source router so messages can queue until you turn delivery back on.
*
* Toggle via env (restart relay service after edits, or rely on your process managers reload):
* - `RELAY_SHEDDING=1` | `true` | `yes` | `on` → shedding **on** (no `relayMessage` / direct `ccipReceive`)
* - `RELAY_DELIVERY_ENABLED=0` | `false` | `no` | `off` → same as shedding on
*
* Default: shedding **off** (normal operation).
*
* Re-reads `process.env` on each call so a future SIGHUP/full restart pattern stays simple.
*/
export function isRelayShedding() {
const shed = String(process.env.RELAY_SHEDDING || '').trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(shed)) return true;
const del = String(process.env.RELAY_DELIVERY_ENABLED ?? '1').trim().toLowerCase();
if (['0', 'false', 'no', 'off'].includes(del)) return true;
return false;
}
/** Source `eth_getLogs` poll interval while shedding (ms). Min 5000. Default 60000. */
export function getRelaySheddingSourcePollIntervalMs() {
const v = parseInt(process.env.RELAY_SHEDDING_SOURCE_POLL_INTERVAL_MS || '60000', 10);
return Number.isFinite(v) && v >= 5000 ? v : 60000;
}
/** Sleep between queue-processor iterations while shedding (ms). Min 1000. Default 5000. */
export function getRelaySheddingQueueIdleMs() {
const v = parseInt(process.env.RELAY_SHEDDING_QUEUE_POLL_MS || '5000', 10);
return Number.isFinite(v) && v >= 1000 ? v : 5000;
}
// Validate required configuration (use same helper so we validate the effective key)
if (!getEffectivePrivateKey()) {
throw new Error('RELAYER_PRIVATE_KEY or PRIVATE_KEY environment variable is required. Set PRIVATE_KEY in smom-dbis-138/.env or RELAYER_PRIVATE_KEY in services/relay/.env');
}
// Validate relay addresses (warn but don't fail - they may be set later)
if (!config.destinationChain.relayRouterAddress) {
console.warn('Warning: Relay router address not configured. Service will not start until configured.');
}