Files
smom-dbis-138/services/relay/src/config.js
defiQUG 4f7b335a4b relay(BSC): adaptive source logs, START_BLOCK parsing, docs, env example
- RelayService: chunked eth_getLogs + adaptive split for strict RPCs
- config: explicit START_BLOCK=latest vs numeric
- README: topology, START_BLOCK, fund script, cast examples
- .env.bsc.example: committed template (secrets stay in .env.bsc)

Made-with: Cursor
2026-03-24 16:17:48 -07:00

245 lines
10 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';
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 */ }
// 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 pair.addressMapFromTo;
}
const fromFile = getRelayTokenMapping && getRelayTokenMapping();
if (sourceChainId === 138 && destinationChainId === 1 && fromFile && Object.keys(fromFile).length > 0) {
return 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 {
'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 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'
);
}
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: process.env.DEST_RPC_URL || getMainnetRpcUrl(),
relayRouterAddress:
process.env.DEST_RELAY_ROUTER ||
process.env.CCIP_RELAY_ROUTER_MAINNET ||
process.env.RELAY_ROUTER_MAINNET ||
'0xAd9A228CcEB4cbB612cD165FFB72fE090ff10Afb',
relayBridgeAddress:
getDestinationRelayBridgeAddress(),
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
}
};
/**
* 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.');
}