/** * Load token mapping from config/token-mapping.json and config/token-mapping-multichain.json. * Used by relay service, bridge/LP tooling, and docs. Safe to publish (no secrets). * * Usage: * const { getRelayTokenMapping, getTokenMappingForPair } = require('../config/token-mapping-loader.cjs'); * const map = getRelayTokenMapping(); // 138 -> Mainnet (chain138Address -> mainnetAddress) * const pair = getTokenMappingForPair(138, 651940); // { tokens, addressMapFromTo, addressMapToFrom } * * @version 2026-02-16 */ const path = require('path'); const fs = require('fs'); const DEFAULT_JSON_PATH = path.resolve(__dirname, 'token-mapping.json'); const DEFAULT_MULTICHAIN_JSON_PATH = path.resolve(__dirname, 'token-mapping-multichain.json'); let _cache = null; let _multichainCache = null; function loadTokenMappingJson(jsonPath = DEFAULT_JSON_PATH) { if (_cache && _cache.path === jsonPath) return _cache.data; try { const raw = fs.readFileSync(jsonPath, 'utf8'); const data = JSON.parse(raw); _cache = { path: jsonPath, data }; return data; } catch (e) { return null; } } function loadTokenMappingMultichainJson(jsonPath = DEFAULT_MULTICHAIN_JSON_PATH) { if (_multichainCache && _multichainCache.path === jsonPath) return _multichainCache.data; try { const raw = fs.readFileSync(jsonPath, 'utf8'); const data = JSON.parse(raw); _multichainCache = { path: jsonPath, data }; return data; } catch (e) { return null; } } /** * Build object suitable for relay config.tokenMapping: Chain 138 address -> Mainnet address. * Only includes tokens that have a mainnetAddress (canonical or wrapped). * * @param {string} [jsonPath] * @returns {{ [chain138Address: string]: string }} */ function getRelayTokenMapping(jsonPath) { const data = loadTokenMappingJson(jsonPath); if (!data || !Array.isArray(data.tokens)) return {}; const out = {}; for (const t of data.tokens) { if (t.chain138Address && t.mainnetAddress) { out[t.chain138Address] = t.mainnetAddress; } } return out; } /** * Get full token list with relaySupported and mainnet info. * * @param {string} [jsonPath] * @returns {Array<{ key: string, name: string, chain138Address: string, mainnetAddress: string|null, relaySupported: boolean, notes: string }>} */ function getTokenList(jsonPath) { const data = loadTokenMappingJson(jsonPath); if (!data || !Array.isArray(data.tokens)) return []; return data.tokens; } /** * Get token mapping for a chain pair from token-mapping-multichain.json. * Tries (fromChainId, toChainId) then (toChainId, fromChainId) and returns tokens in from→to order. * * @param {number|string} fromChainId * @param {number|string} toChainId * @param {string} [jsonPath] * @returns {{ tokens: Array<{ key: string, name: string, addressFrom: string, addressTo: string, notes?: string }>, addressMapFromTo: Record, addressMapToFrom: Record } | null} */ function getTokenMappingForPair(fromChainId, toChainId, jsonPath) { const data = loadTokenMappingMultichainJson(jsonPath); if (!data || !Array.isArray(data.pairs)) return null; const from = Number(fromChainId); const to = Number(toChainId); let pair = data.pairs.find((p) => p.fromChainId === from && p.toChainId === to); let reverse = false; if (!pair) { pair = data.pairs.find((p) => p.fromChainId === to && p.toChainId === from); reverse = true; } if (!pair || !Array.isArray(pair.tokens)) return null; const tokens = reverse ? pair.tokens.map((t) => ({ key: t.key, name: t.name, addressFrom: t.addressTo, addressTo: t.addressFrom, notes: t.notes })) : pair.tokens; const addressMapFromTo = {}; const addressMapToFrom = {}; for (const t of tokens) { if (t.addressFrom && t.addressTo) { addressMapFromTo[t.addressFrom.toLowerCase()] = t.addressTo; addressMapToFrom[t.addressTo.toLowerCase()] = t.addressFrom; } } return { tokens, addressMapFromTo, addressMapToFrom }; } /** * Get all chain pairs defined in token-mapping-multichain.json. * * @param {string} [jsonPath] * @returns {Array<{ fromChainId: number, toChainId: number, notes?: string }>} */ function getAllMultichainPairs(jsonPath) { const data = loadTokenMappingMultichainJson(jsonPath); if (!data || !Array.isArray(data.pairs)) return []; return data.pairs.map((p) => ({ fromChainId: p.fromChainId, toChainId: p.toChainId, notes: p.notes })); } /** * Resolve token address on target chain from source chain address using multichain mapping. * * @param {number|string} fromChainId * @param {number|string} toChainId * @param {string} tokenAddressOnSource - address on fromChainId * @param {string} [jsonPath] * @returns {string|undefined} address on toChainId, or undefined if not mapped */ function getMappedAddress(fromChainId, toChainId, tokenAddressOnSource, jsonPath) { const result = getTokenMappingForPair(fromChainId, toChainId, jsonPath); if (!result) return undefined; return result.addressMapFromTo[String(tokenAddressOnSource).toLowerCase()]; } module.exports = { loadTokenMappingJson, loadTokenMappingMultichainJson, getRelayTokenMapping, getTokenList, getTokenMappingForPair, getAllMultichainPairs, getMappedAddress, DEFAULT_JSON_PATH, DEFAULT_MULTICHAIN_JSON_PATH };