const { ethers } = require('ethers') const { prettyPrintTS } = require('./utils/prettyPrintTs.cjs') const { fetchRfqData } = require('./utils/fetchRfqData.cjs') // Provider URLs const providers = require('./data/providers.json') // List of ignored bridge symbols const ignoredBridgeSymbols = require('./data/ignoredBridgeSymbols.json') // Symbol overrides (for tokens with incorrect on-chain symbols) const symbolOverrides = require('./data/symbolOverrides.json') // Contract ABIs const SynapseRouterABI = require('./abi/SynapseRouter.json') const SynapseCCTPABI = require('./abi/SynapseCCTP.json') const SynapseCCTPRouterABI = require('./abi/SynapseCCTPRouter.json') const SwapQuoterABI = require('./abi/SwapQuoter.json') const ERC20ABI = require('./abi/IERC20Metadata.json') const DefaultPoolABI = require('./abi/IDefaultPool.json') // const rfqResponse = require('./data/rfqResponse.json') // ETH address const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // Format is { chainId: providerUrl } // Replace providerUrl with new ethers.providers.JsonRpcProvider(providerUrl) Object.keys(providers).forEach((chainId) => { providers[chainId] = new ethers.providers.JsonRpcProvider(providers[chainId]) }) // Contract addresses const SynapseRouterAddress = '0x7e7a0e201fd38d3adaa9523da6c109a07118c96a' const SynapseCCTPRouterAddress = '0xd5a597d6e7ddf373a92C8f477DAAA673b0902F48' const SynapseCCTPAddress = '0x12715a66773BD9C54534a01aBF01d05F6B4Bd35E' // Chain IDs where SynapseBridge is allowed const allowedChainIdsForSynapseBridge = [ 1, 10, 25, 56, 137, 250, 288, 1088, 1284, 1285, 2000, 7700, 8217, 8453, 81457, 42161, 43114, 53935, 1313161554, 1666600000, ] // Chain IDs where SynapseCCTPRouter is allowed const allowedChainIdsForSynapseCCTPRouter = [1, 10, 137, 8453, 42161, 43114] // Chain IDs where RFQ is allowed const allowedChainIdsForRfq = [1, 10, 56, 130, 8453, 42161, 59144, 80094, 81457, 534352,] // Get SynapseRouter contract instances for each chain const SynapseRouters = {} const SwapQuoters = {} allowedChainIdsForSynapseBridge.forEach((chainId) => { SynapseRouters[chainId] = new ethers.Contract( SynapseRouterAddress, SynapseRouterABI, providers[chainId] ) SwapQuoters[chainId] = new ethers.Contract( SynapseRouters[chainId].swapQuoter(), SwapQuoterABI, providers[chainId] ) }) // Get SynapseCCTPRouter contract instances for each chain // Only include chains where SynapseCCTPRouter is allowed const SynapseCCTPRouters = {} const SynapseCCTPs = {} allowedChainIdsForSynapseCCTPRouter.forEach((chainId) => { SynapseCCTPRouters[chainId] = new ethers.Contract( SynapseCCTPRouterAddress, SynapseCCTPRouterABI, providers[chainId] ) SynapseCCTPs[chainId] = new ethers.Contract( SynapseCCTPAddress, SynapseCCTPABI, providers[chainId] ) }) // Function to get list of tokens that could be swapped // into SynapseBridge tokens for a given chain. const getBridgeOriginMap = async (chainId) => { if (!SwapQuoters[chainId]) { return { originMap: {}, poolSets: [], } } // Get WETH address const weth = await SwapQuoters[chainId].weth() // Get list of supported tokens let bridgeTokens = await SynapseRouters[chainId].bridgeTokens() const pools = await SynapseRouters[chainId].allPools() // Collect map from bridge token to symbols by doing tokenToSymbol for each bridge token const allTokenSymbols = {} await Promise.all( bridgeTokens.map(async (bridgeToken) => { const symbol = await SynapseRouters[chainId].tokenToSymbol(bridgeToken) // Skip if symbol is in ignoredBridgeSymbols if (!ignoredBridgeSymbols.includes(symbol)) { allTokenSymbols[bridgeToken] = new Set([symbol]) } }) ) // List of bridge tokens without ignored symbols bridgeTokens = Object.keys(allTokenSymbols) // Collect map from supported tokens into set of bridge token symbols const tokensToSymbols = {} // Add all bridge tokens to tokensToSymbols Object.keys(allTokenSymbols).forEach((bridgeToken) => { tokensToSymbols[bridgeToken] = new Set(allTokenSymbols[bridgeToken]) }) // List of sets of tokens in each pool const poolSets = [] pools.forEach((pool) => { // Collect set of bridge token symbols that are present in the pool const bridgeSymbols = getBridgeSymbolsSet( allTokenSymbols, pool.tokens.map((token) => token.token).concat(pool.lpToken) ) const poolSet = new Set() // Add collected set to tokensToSymbols for each token in the pool pool.tokens.forEach((token) => { addSetToMap(tokensToSymbols, token.token, bridgeSymbols) poolSet.add(token.token) }) // Add collected set to tokensToSymbols for the pool lpToken (if it is a bridge token) if (bridgeTokens.includes(pool.lpToken)) { addSetToMap(tokensToSymbols, pool.lpToken, bridgeSymbols) poolSet.add(pool.lpToken) } // Save set of tokens in the pool poolSets.push(poolSet) }) // If WETH is present in the map, add ETH with the same set of bridge token symbols if (weth in tokensToSymbols) { addSetToMap(tokensToSymbols, ETH, tokensToSymbols[weth]) poolSets.forEach((poolSet) => { if (poolSet.has(weth)) { poolSet.add(ETH) } }) } return { originMap: tokensToSymbols, poolSets, } } // Function to get list of tokens that could be swapped // into SynapseCCTP tokens for a given chain. const getCCTPOriginMap = async (chainId) => { // Return empty map if CCTP is not supported on the chain if (!SynapseCCTPs[chainId]) { return {} } // Get map from token to set of bridge token symbols const cctpTokenSymbols = await getCCTPBridgeSymbols(chainId) const tokensToSymbols = {} // Add all bridge tokens to tokensToSymbols Object.keys(cctpTokenSymbols).forEach((token) => { tokensToSymbols[token] = new Set([cctpTokenSymbols[token]]) }) // Add tokens from whitelisted pools await Promise.all( Object.keys(cctpTokenSymbols).map(async (cctpToken) => { const pool = await SynapseCCTPs[chainId].circleTokenPool(cctpToken) const tokens = await getPoolTokens(chainId, pool) tokens.forEach((token) => { addSetToMap( tokensToSymbols, token, new Set([cctpTokenSymbols[cctpToken]]) ) }) }) ) return tokensToSymbols } // Function to get a list of bridge token symbols that could be swapped // into a token on a destination chain. const getDestinationBridgeSymbols = async (chainId, token) => { // Get list of connected bridge tokens: (symbol, token) pairs const connectedBridgeTokens = await SynapseRouters[ chainId ].getConnectedBridgeTokens(token) const symbolSet = new Set() connectedBridgeTokens.forEach((bridgeToken) => { symbolSet.add(bridgeToken.symbol) }) // Get a list of bridge token symbols from CCTP if CCTP is supported on the chain if (SynapseCCTPRouters[chainId]) { const connectedCctpTokens = await SynapseCCTPRouters[ chainId ].getConnectedBridgeTokens(token) connectedCctpTokens.forEach((bridgeToken) => { symbolSet.add(bridgeToken.symbol) }) } return Array.from(symbolSet).sort() } // Function to get a map from CCTP tokens to bridge token symbols const getCCTPBridgeSymbols = async (chainId) => { // Return empty map if CCTP is not supported on the chain if (!SynapseCCTPs[chainId]) { return {} } // Get a list of bridge tokens // List entries are (symbol, token) pairs const cctpTokens = await SynapseCCTPs[chainId].getBridgeTokens() // Return map from token to bridge symbol const cctpTokenToSymbol = {} cctpTokens.forEach((bridgeToken) => { cctpTokenToSymbol[bridgeToken.token] = bridgeToken.symbol }) return cctpTokenToSymbol } // Function to get a list of tokens in a pool const getPoolTokens = async (chainId, poolAddress) => { const pool = new ethers.Contract( poolAddress, DefaultPoolABI, providers[chainId] ) // To get a list of tokens we do getToken(i) calls until we get a revert const tokens = [] for (let i = 0; ; i++) { try { tokens.push(await pool.getToken(i)) } catch (e) { break } } return tokens } // Gets a set of bridge token symbols from a list of tokens const getBridgeSymbolsSet = (allTokenSymbols, tokenAddresses) => { const bridgeSymbols = new Set() tokenAddresses.forEach((token) => { if (token in allTokenSymbols) { // Add all values from allTokenSymbols[token] to bridgeSymbols allTokenSymbols[token].forEach((value) => { bridgeSymbols.add(value) }) } }) return bridgeSymbols } // Adds values from set to map[key] const addSetToMap = (map, key, set) => { if (!map[key]) { map[key] = new Set() } set.forEach((value) => { map[key].add(value) }) } const sortMapByKeys = (map) => { const sortedMap = {} Object.keys(map) .sort() .forEach((key) => { sortedMap[key] = map[key] }) return sortedMap } const printMaps = async () => { const bridgeMap = {} const bridgeSymbolsMap = {} console.log('Starting on chains: ', Object.keys(providers)) const rfqResponse = await fetchRfqData() await Promise.all( Object.keys(providers).map(async (chainId) => { // Get map from token to set of bridge token symbols const { originMap, poolSets } = await getBridgeOriginMap(chainId) // Add tokens from CCTP originMap to global originMap const cctpOriginMap = await getCCTPOriginMap(chainId) Object.keys(cctpOriginMap).forEach((token) => { addSetToMap(originMap, token, cctpOriginMap[token]) }) // Add RFQ.ETH and RFQ.USDC to origin map of tokens bridgeable into nETH or CCTP.USDC respectively if (allowedChainIdsForRfq.includes(Number(chainId))) { Object.keys(originMap).forEach((token) => { if (originMap[token].has('nETH')) { originMap[token].add('RFQ.ETH') } if (originMap[token].has('CCTP.USDC')) { originMap[token].add('RFQ.USDC') } }) } const tokens = {} await Promise.all( Object.keys(originMap).map(async (token) => { tokens[token] = { decimals: await getTokenDecimals(chainId, token), symbol: await getTokenSymbol(chainId, token), origin: Array.from(originMap[token]).sort(), destination: await getDestinationBridgeSymbols(chainId, token), swappable: extractSwappable(poolSets, token), } }) ) if (allowedChainIdsForRfq.includes(Number(chainId))) { await Promise.all( rfqResponse.map(async (quote) => { const { origin_chain_id, origin_token_addr, dest_chain_id, dest_token_addr, } = quote const normalizedOriginAddress = ethers.utils.getAddress(origin_token_addr) const normalizedDestAddress = ethers.utils.getAddress(dest_token_addr) if (origin_chain_id === Number(chainId)) { const originTokenSymbol = await getTokenSymbol( origin_chain_id, normalizedOriginAddress ) const rfqOriginSymbol = getRFQSymbol(originTokenSymbol) if (!tokens[normalizedOriginAddress]) { tokens[normalizedOriginAddress] = { origin: [], destination: [], swappable: [], // poolSets are handled during SynapseBridge portion symbol: originTokenSymbol, decimals: await getTokenDecimals( origin_chain_id, normalizedOriginAddress ), } } // Add RFQ symbol to origin list if not already present if ( !tokens[normalizedOriginAddress].origin.includes( rfqOriginSymbol ) ) { tokens[normalizedOriginAddress].origin.push(rfqOriginSymbol) } // Add RFQ symbol to destination list if not already present if ( !tokens[normalizedOriginAddress].destination.includes( rfqOriginSymbol ) ) { tokens[normalizedOriginAddress].destination.push( rfqOriginSymbol ) } } }) ) } bridgeMap[chainId] = sortMapByKeys(tokens) bridgeSymbolsMap[chainId] = sortMapByKeys(extractBridgeSymbolsMap(tokens)) console.log('Finished chain: ', chainId) }) ) prettyPrintTS(bridgeMap, 'BRIDGE_MAP', './constants/bridgeMap.ts') } // Extracts the list of tokens that can be swapped into a token via single on-chain swap const extractSwappable = (poolSets, token) => { const tokenSet = new Set() poolSets.forEach((poolSet) => { // If token is in the pool, add all tokens except token to tokenSet if (poolSet.has(token)) { poolSet.forEach((poolToken) => { if (poolToken !== token) { tokenSet.add(poolToken) } }) } }) return Array.from(tokenSet).sort() } const extractBridgeSymbolsMap = (tokens) => { const bridgeSymbolsOriginSets = {} const bridgeSymbolsDestinationSets = {} // Add all bridge symbols that be swapped to/from each token Object.keys(tokens).forEach((token) => { tokens[token].origin.forEach((symbol) => { addSetToMap(bridgeSymbolsOriginSets, symbol, new Set([token])) }) tokens[token].destination.forEach((symbol) => { addSetToMap(bridgeSymbolsDestinationSets, symbol, new Set([token])) }) }) // Bridge symbols are keys that are present in either map const bridgeSymbols = new Set([ ...Object.keys(bridgeSymbolsOriginSets), ...Object.keys(bridgeSymbolsDestinationSets), ]) const bridgeSymbolsMap = {} bridgeSymbols.forEach((symbol) => { bridgeSymbolsMap[symbol] = { origin: Array.from(bridgeSymbolsOriginSets[symbol]).sort(), destination: Array.from(bridgeSymbolsDestinationSets[symbol]).sort(), } }) return bridgeSymbolsMap } const getTokenSymbol = async (chainId, token) => { // Check if token is ETH if (token === ETH) { if (!SwapQuoters[chainId]) { return 'ETH' } // Get WETH address from SwapQuoter const weth = await SwapQuoters[chainId].weth() // Return "WETH" symbol without first character return getTokenSymbol(chainId, weth).then((symbol) => symbol.slice(1)) } // Check if {chainId: {token: {symbol}}} is in symbolOverrides if (chainId in symbolOverrides && token in symbolOverrides[chainId]) { return symbolOverrides[chainId][token] } // Otherwise return symbol from ERC20 contract const symbol = await new ethers.Contract( token, ERC20ABI, providers[chainId] ).symbol() return symbol } const getTokenDecimals = async (chainId, token) => { // Check if token is ETH if (token === ETH) { return 18 } // Otherwise return decimals from ERC20 contract const decimals = await new ethers.Contract( token, ERC20ABI, providers[chainId] ).decimals() return decimals } const getRFQSymbol = (symbol) => { return `RFQ.${symbol}` } printMaps()