diff --git a/.env.sample b/.env.sample index 406a4dd7..4de7c49d 100644 --- a/.env.sample +++ b/.env.sample @@ -2,5 +2,7 @@ INFURA_KEY= MAINNET_RPC="https://mainnet.infura.io/v3/${INFURA_KEY}" GOERLI_RPC="https://goerli.infura.io/v3/${INFURA_KEY}" SEPOLIA_RPC="https://sepolia.infura.io/v3/${INFURA_KEY}" +ARB_ONE_RPC="https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}" +ARB_SEPOLIA_RPC="https://arbitrum-sepolia.infura.io/v3/${INFURA_KEY}" l2NetworkID=42161 -PORT=3000 \ No newline at end of file +PORT=3000 diff --git a/package.json b/package.json index 43b6515c..8e60386d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "update": "yarn ts-node src/main.ts update", "arbify": "yarn ts-node src/main.ts arbify", "permit": "yarn ts-node src/main.ts permit", + "crossChain": "yarn ts-node src/main.ts crosschain --crossChain", "fullList": "yarn ts-node src/main.ts full --tokenList full --ignorePreviousList", "allTokensList": "yarn ts-node src/main.ts alltokenslist --tokenList full", "updateNova": "yarn ts-node src/main.ts update --l2NetworkID 42170", diff --git a/src/PermitTokens/permitSignature.ts b/src/PermitTokens/permitSignature.ts index cd98df17..99862e93 100644 --- a/src/PermitTokens/permitSignature.ts +++ b/src/PermitTokens/permitSignature.ts @@ -152,9 +152,10 @@ enum PermitTypes { export const addPermitTags = async ( tokenList: ArbTokenList, + l2ChainId: number, ): Promise => { console.log('Adding permit tags'); - const { l1, l2 } = await getNetworkConfig(); + const { l1, l2 } = await getNetworkConfig(l2ChainId); const value = utils.parseUnits('1.0', 18); const deadline = constants.MaxUint256; diff --git a/src/commands/allTokensList.ts b/src/commands/allTokensList.ts index d1f5eefb..60cd74f4 100644 --- a/src/commands/allTokensList.ts +++ b/src/commands/allTokensList.ts @@ -8,9 +8,18 @@ export const command = Action.AllTokensList; export const describe = 'All tokens list'; -export const handler = async (argvs: Args) => { - let tokenList: ArbTokenList = await generateFullListFormatted(); - if (argvs.includePermitTags) tokenList = await addPermitTags(tokenList); - writeToFile(tokenList, argvs.newArbifiedList); +export const handler = async ({ + includePermitTags, + l2NetworkID, + newArbifiedList, +}: Args) => { + if (!l2NetworkID) { + throw new Error('l2NetworkID is required'); + } + let tokenList: ArbTokenList = await generateFullListFormatted(l2NetworkID); + if (includePermitTags) { + tokenList = await addPermitTags(tokenList, l2NetworkID); + } + writeToFile(tokenList, newArbifiedList); return tokenList; }; diff --git a/src/commands/arbify.ts b/src/commands/arbify.ts index 665f5c4e..ac141442 100644 --- a/src/commands/arbify.ts +++ b/src/commands/arbify.ts @@ -3,22 +3,37 @@ import { addPermitTags } from '../PermitTokens/permitSignature'; import { writeToFile } from '../lib/store'; import { Action, Args } from '../lib/options'; import { arbifyL1List } from '../lib/token_list_gen'; +import { getTokenListObj, removeInvalidTokensFromList } from '../lib/utils'; export const command = Action.Arbify; export const describe = 'Arbify'; -export const handler = async (argvs: Args) => { - const includeOldDataFields = !!argvs.includeOldDataFields; +export const handler = async ({ + ignorePreviousList, + includeOldDataFields, + includePermitTags, + l2NetworkID, + newArbifiedList, + prevArbifiedList, + tokenList: tokenListPath, +}: Args) => { + if (!l2NetworkID) { + throw new Error('l2NetworkID is required'); + } - const { newList } = await arbifyL1List(argvs.tokenList, { - includeOldDataFields, - ignorePreviousList: argvs.ignorePreviousList, - prevArbifiedList: argvs.prevArbifiedList, + const l1TokenList = await getTokenListObj(tokenListPath); + removeInvalidTokensFromList(l1TokenList); + const newList = await arbifyL1List(tokenListPath, l1TokenList, l2NetworkID, { + includeOldDataFields: !!includeOldDataFields, + ignorePreviousList, + prevArbifiedList, }); let tokenList: ArbTokenList = newList; - if (argvs.includePermitTags) tokenList = await addPermitTags(tokenList); - writeToFile(tokenList, argvs.newArbifiedList); + if (includePermitTags) { + tokenList = await addPermitTags(tokenList, l2NetworkID); + } + writeToFile(tokenList, newArbifiedList); return tokenList; }; diff --git a/src/commands/crossChain.ts b/src/commands/crossChain.ts new file mode 100644 index 00000000..b6d1f1db --- /dev/null +++ b/src/commands/crossChain.ts @@ -0,0 +1,223 @@ +import { ArbTokenInfo, ArbTokenList } from '../lib/types'; +import { addPermitTags } from '../PermitTokens/permitSignature'; +import { writeToFile } from '../lib/store'; +import { Action, Args } from '../lib/options'; +import { arbifyL1List } from '../lib/token_list_gen'; +import { getTokenListObj, removeInvalidTokensFromList } from '../lib/utils'; +import { ChainId } from '../lib/constants'; + +export const command = Action.CrossChain; + +export const describe = 'Cross-chain'; + +const L2_CHAIN_IDS = [ + ChainId.ArbitrumOne, + ChainId.ArbitrumNova, + ChainId.ArbitrumGoerli, + ChainId.ArbitrumSepolia, +]; +function isValidL1(chainId: number) { + return [ChainId.Ethereum, ChainId.Goerli, ChainId.Sepolia].includes(chainId); +} +function isValidL2(chainId: number) { + return L2_CHAIN_IDS.includes(chainId); +} +function isValidL3(chainId: number) { + return !isValidL1(chainId) && !isValidL2(chainId); +} +function getL2ParentChain(chainId: number) { + return { + [ChainId.ArbitrumOne]: ChainId.Ethereum, + [ChainId.ArbitrumNova]: ChainId.Ethereum, + [ChainId.ArbitrumSepolia]: ChainId.Sepolia, + [ChainId.ArbitrumGoerli]: ChainId.Goerli, + }[chainId]; +} +function getL3ParentChain(chainId: number) { + return { + [ChainId.Xai]: ChainId.ArbitrumOne, + [ChainId.XaiTestnet]: ChainId.ArbitrumGoerli, + [ChainId.Rari]: ChainId.ArbitrumOne, + }[chainId]; +} + +type TokensMap = Map; +function getMapKey(chainId: number | string, tokenAddress: string) { + return `${chainId}_${tokenAddress.toLowerCase()}`; +} + +// Update token in tokensMap with new bridgeInfo +function updateBridgeInfo( + mapKey: string, + tokensMap: TokensMap, + bridgeInfo: { + [chainId: number]: { + tokenAddress: string; + originBridgeAddress: string; + destBridgeAddress: string; + }; + }, +) { + const existingToken = tokensMap.get(mapKey); + if (!existingToken) { + console.log('Token not found in TokensMap', mapKey); + return; + } + + const newToken: ArbTokenInfo = { + ...existingToken, + extensions: { + bridgeInfo: { + ...(existingToken.extensions?.bridgeInfo as object), + ...bridgeInfo, + }, + }, + }; + tokensMap.set(mapKey, newToken); + return newToken; +} + +function updateTokensMap( + tokens: ArbTokenInfo[], + tokensMap: TokensMap, + chainId: number, +) { + // For each L2/L3 tokens, update the parents chain list up to the L1 + return tokens.forEach((token) => { + const originChainId = Object.keys(token.extensions?.bridgeInfo || {})[0]; + + if (!originChainId) { + console.log('No origin chain id found for token', token.address); + return; + } + + const bridgeInfo = token.extensions?.bridgeInfo[originChainId]; + if (!bridgeInfo) { + console.log('No bridge info found for token', token.address); + return; + } + + // Update the direct parent chain's bridgeInfo + const updatedToken = updateBridgeInfo( + getMapKey(originChainId, bridgeInfo.tokenAddress), + tokensMap, + { + [chainId]: { + tokenAddress: token.address, + destBridgeAddress: bridgeInfo.destBridgeAddress, + originBridgeAddress: bridgeInfo.originBridgeAddress, + }, + }, + ); + + if (!updatedToken || !isValidL3(chainId)) { + return; + } + + const l1ChainId = getL2ParentChain(Number(originChainId)); + if (!l1ChainId) { + // This should never happen + throw new Error(`No L1 chain found for L3 token ${token.address}`); + } + + const l1Address = + updatedToken.extensions!.bridgeInfo[l1ChainId].tokenAddress; + // Update the L3 bridgeInfo with L1 information + updateBridgeInfo(getMapKey(chainId, token.address), tokensMap, { + [l1ChainId]: { + tokenAddress: l1Address, + destBridgeAddress: 'N/A', + originBridgeAddress: 'N/A', + }, + }); + + // Update the L1 bridgeInfo with L3 information + updateBridgeInfo(getMapKey(l1ChainId, l1Address), tokensMap, { + [chainId]: { + tokenAddress: token.address, + destBridgeAddress: 'N/A', + originBridgeAddress: 'N/A', + }, + }); + }); +} +export const handler = async (argvs: Args) => { + const includeOldDataFields = !!argvs.includeOldDataFields; + + const l1TokenList = await getTokenListObj(argvs.tokenList); + removeInvalidTokensFromList(l1TokenList); + + // Store all L1 tokens in a map, so we can easily retrieve and update them + const tokensMap = new Map( + l1TokenList.tokens + .filter((token) => isValidL1(token.chainId)) + .map((token) => [ + getMapKey(token.chainId, token.address), + token as ArbTokenInfo, + ]), + ); + + const l2Lists = new Map(); + await Promise.all( + L2_CHAIN_IDS.map(async (l2ChainId) => { + let newList = await arbifyL1List( + argvs.tokenList, + l1TokenList, + l2ChainId, + { + includeOldDataFields, + ignorePreviousList: argvs.ignorePreviousList, + prevArbifiedList: argvs.prevArbifiedList, + }, + ); + + // Update L1 bridgeInfo for each L2 tokens + updateTokensMap(newList.tokens, tokensMap, l2ChainId); + // Add the token to tokensMap, so we can update it later with L3 bridgeInfo + newList.tokens.forEach((token) => { + tokensMap.set(getMapKey(l2ChainId, token.address), token); + }); + + l2Lists.set(l2ChainId, newList); + if (argvs.includePermitTags) { + newList = await addPermitTags(tokenList, l2ChainId); + } + }), + ); + + const l3Lists = new Map(); + await Promise.all( + [ChainId.Xai, ChainId.XaiTestnet, ChainId.Rari].map(async (l3ChainId) => { + const parentChain = getL3ParentChain(l3ChainId)!; + const l2TokenList = l2Lists.get(parentChain); + if (!l2TokenList) { + throw new Error(`No L2 list to arbify for L3 chain: ${l3ChainId}`); + } + + const newList = await arbifyL1List( + argvs.tokenList, + l2TokenList, + l3ChainId, + { + includeOldDataFields, + ignorePreviousList: argvs.ignorePreviousList, + prevArbifiedList: argvs.prevArbifiedList, + }, + ); + newList.tokens.forEach((token) => { + tokensMap.set(getMapKey(l3ChainId, token.address), token); + }); + // Update L1 and L2 bridgeInfo for each L3 tokens + l3Lists.set(l3ChainId, newList); + updateTokensMap(newList.tokens, tokensMap, l3ChainId); + }), + ); + + const tokenList: ArbTokenList = { + ...l1TokenList, + tokens: Array.from(tokensMap.values()).filter((token) => token.extensions), + }; + + writeToFile(tokenList, argvs.newArbifiedList); + return tokenList; +}; diff --git a/src/commands/full.ts b/src/commands/full.ts index a5bacd44..40d4363f 100644 --- a/src/commands/full.ts +++ b/src/commands/full.ts @@ -6,12 +6,19 @@ export const command = Action.Full; export const describe = 'Full'; -export const handler = async (argvs: Args) => { - if (argvs.tokenList !== 'full') - throw new Error("expected --tokenList 'full'"); - if (argvs.includePermitTags) +export const handler = async ({ + includePermitTags, + l2NetworkID, + newArbifiedList, + tokenList: tokenListPath, +}: Args) => { + if (tokenListPath !== 'full') throw new Error("expected --tokenList 'full'"); + if (includePermitTags) throw new Error('full list mode does not support permit tagging'); - const tokenList = await generateFullList(); - writeToFile(tokenList, argvs.newArbifiedList); + if (!l2NetworkID) { + throw new Error('l2NetworkID is required'); + } + const tokenList = await generateFullList(l2NetworkID); + writeToFile(tokenList, newArbifiedList); return tokenList; }; diff --git a/src/commands/update.ts b/src/commands/update.ts index 093789d3..fee2e976 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -8,16 +8,28 @@ export const command = Action.Update; export const describe = 'Update'; -export const handler = async (argvs: Args) => { - const includeOldDataFields = !!argvs.includeOldDataFields; - const { newList } = await updateArbifiedList(argvs.tokenList, { - includeOldDataFields, - ignorePreviousList: argvs.ignorePreviousList, - prevArbifiedList: argvs.prevArbifiedList, +export const handler = async ({ + ignorePreviousList, + includeOldDataFields, + includePermitTags, + l2NetworkID, + newArbifiedList, + prevArbifiedList, + tokenList: tokenListPath, +}: Args) => { + if (!l2NetworkID) { + throw new Error('l2NetworkID is required'); + } + const { newList } = await updateArbifiedList(tokenListPath, l2NetworkID, { + includeOldDataFields: !!includeOldDataFields, + ignorePreviousList, + prevArbifiedList, }); let tokenList: ArbTokenList = newList; - if (argvs.includePermitTags) tokenList = await addPermitTags(tokenList); - writeToFile(tokenList, argvs.newArbifiedList); + if (includePermitTags) { + tokenList = await addPermitTags(tokenList, l2NetworkID); + } + writeToFile(tokenList, newArbifiedList); return tokenList; }; diff --git a/src/customNetworks.ts b/src/customNetworks.ts index 919995e4..c2ce82c1 100644 --- a/src/customNetworks.ts +++ b/src/customNetworks.ts @@ -43,6 +43,45 @@ const xai: L2Network = { blockTime: arbConstants.ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, }; +const xaiTestnet: L2Network = { + chainID: 47279324479, + confirmPeriodBlocks: 20, + ethBridge: { + bridge: '0xf958e56d431eA78C7444Cf6A6184Af732Ae6a8A3', + inbox: '0x8b842ad88AAffD63d52EC54f6428fb7ff83060a8', + outbox: '0xDfe36Bea935F11260b0159dCA255b6668925d743', + rollup: '0x082742561295f6e1b43c4f5d1e2d52d7FfE082f1', + sequencerInbox: '0x5fD0cCc5D31748A44b43cf8DFBFA0FAA32665464', + }, + explorerUrl: 'https://testnet-explorer.xai-chain.net', + isArbitrum: true, + isCustom: true, + name: 'Xai Orbit Testnet', + partnerChainID: 421613, + partnerChainIDs: [], + retryableLifetimeSeconds: 604800, + tokenBridge: { + l1CustomGateway: '0xdBbDc3EE848C05792CC93EA140c59731f920c3F2', + l1ERC20Gateway: '0xC033fBAFd978440460d943efe6A3bF6A1a990e80', + l1GatewayRouter: '0xCb0Fe28c36a60Cf6254f4dd74c13B0fe98FFE5Db', + l1MultiCall: '0x21779e0950A87DDD57E341d54fc12Ab10F6eE167', + l1ProxyAdmin: '0xc80853e91f8Ac0AaD6ff939F3861600Ab34Dfe12', + l1Weth: '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3', + l1WethGateway: '0x58ea20BE21b971Fa282905EdA74bA46540eEd977', + l2CustomGateway: '0xc60622D1FbDD63Cf9c173D1b69715Ef2B725D792', + l2ERC20Gateway: '0x47ab2DfD627360fC6ac4Ae2fB9fa6f3539aFfeCc', + l2GatewayRouter: '0x75c2848D0B2116d6832Ff3758df09D4209b4b7ce', + l2Multicall: '0xE2fBe979bD0df59554Fded36f3A3BF5206f287a2', + l2ProxyAdmin: '0x81DeEc20158a367f7039ab3a563C1eB63cc2b3D6', + l2Weth: '0xea77c06A6703A781f9442EFa083e21F3F75907F8', + l2WethGateway: '0x927b59cCde7a92acDa085514FdEA39f0c4D1a2DC', + }, + nitroGenesisBlock: 0, + nitroGenesisL1Block: 0, + depositTimeout: 1800000, + blockTime: arbConstants.ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, +}; + const rari: L2Network = { chainID: 1380012617, confirmPeriodBlocks: 45818, @@ -160,6 +199,7 @@ const proofOfPlayApex: L2Network = { }; addCustomNetwork({ customL2Network: xai }); +addCustomNetwork({ customL2Network: xaiTestnet }); addCustomNetwork({ customL2Network: rari }); addCustomNetwork({ customL2Network: muster }); addCustomNetwork({ customL2Network: proofOfPlayApex }); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index c3be3f5b..45849448 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -97,3 +97,24 @@ export const BridgedUSDCContractAddressArb1 = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8'; export const SEVEN_DAYS_IN_SECONDS = 7 * 24 * 60 * 60; + +export enum ChainId { + // L1 + Ethereum = 1, + // L1 Testnets + Goerli = 5, + Sepolia = 11155111, + // L2 + ArbitrumOne = 42161, + ArbitrumNova = 42170, + // L2 Testnets + ArbitrumGoerli = 421613, + ArbitrumSepolia = 421614, + // Orbit + XaiTestnet = 47279324479, + Xai = 660279, + Rari = 1380012617, + StylusTestnet = 23011913, + Muster = 4078, + ProofOfPlayApex = 70700, +} diff --git a/src/lib/graph.ts b/src/lib/graph.ts index 428b4dc1..32598f47 100644 --- a/src/lib/graph.ts +++ b/src/lib/graph.ts @@ -49,9 +49,10 @@ const graphGatewayBlockNumField = (networkID: string | number) => { export const getTokens = async ( tokenList: { addr: string; logo: string | undefined }[], + l2NetworkID: number, _networkID: string | number, ): Promise> => { - const { isNova } = isNetwork(); + const { isNova } = isNetwork(l2NetworkID); if (isNova) { console.warn('empty subgraph for nova'); return []; diff --git a/src/lib/instantiate_bridge.ts b/src/lib/instantiate_bridge.ts index bba21345..ef2e7a0f 100644 --- a/src/lib/instantiate_bridge.ts +++ b/src/lib/instantiate_bridge.ts @@ -1,20 +1,20 @@ import { providers } from 'ethers'; import { getL2Network, MultiCaller } from '@arbitrum/sdk'; -import { getArgvs } from './options'; +import { ChainId } from './constants'; -export const getNetworkConfig = async () => { - const argv = getArgvs(); - const networkID = argv.l2NetworkID; +export const getNetworkConfig = async (networkID: number) => { console.log('Using L2 networkID:', networkID); const childRpc = { - 42161: 'https://arb1.arbitrum.io/rpc', - 42170: 'https://nova.arbitrum.io/rpc', - 421614: 'https://sepolia-rollup.arbitrum.io/rpc', - 660279: 'https://xai-chain.net/rpc', - 1380012617: 'https://mainnet.rpc.rarichain.org/http', - 4078: 'https://muster.alt.technology', - 70700: 'https://rpc.apex.proofofplay.com', + [ChainId.ArbitrumOne]: 'https://arb1.arbitrum.io/rpc', + [ChainId.ArbitrumNova]: 'https://nova.arbitrum.io/rpc', + [ChainId.ArbitrumSepolia]: 'https://sepolia-rollup.arbitrum.io/rpc', + [ChainId.Xai]: 'https://xai-chain.net/rpc', + [ChainId.XaiTestnet]: 'https://testnet.xai-chain.net/rpc', + [ChainId.Rari]: 'https://mainnet.rpc.rarichain.org/http', + [ChainId.StylusTestnet]: 'https://stylus-testnet.arbitrum.io/rpc', + [ChainId.Muster]: 'https://muster.alt.technology', + [ChainId.ProofOfPlayApex]: 'https://rpc.apex.proofofplay.com', }[networkID]; if (!childRpc) { @@ -28,6 +28,7 @@ export const getNetworkConfig = async () => { if (childNetwork.partnerChainID === 1) return 'MAINNET_RPC'; else if (childNetwork.partnerChainID === 11155111) return 'SEPOLIA_RPC'; else if (childNetwork.partnerChainID === 42161) return 'ARB_ONE_RPC'; + else if (childNetwork.partnerChainID === 421613) return 'ARB_SEPOLIA_RPC'; throw new Error('No parent chain RPC detected'); })(); const parentRpc = process.env[expectedEnv]; diff --git a/src/lib/options.ts b/src/lib/options.ts index 94317618..ddee71a3 100644 --- a/src/lib/options.ts +++ b/src/lib/options.ts @@ -24,11 +24,16 @@ enum Action { Full = 'full', Permit = 'permit', Update = 'update', + CrossChain = 'crosschain', } const options = { l2NetworkID: { type: 'number', - demandOption: true, + demandOption: false, // Only optional for cross-chain lists + }, + crossChain: { + type: 'boolean', + demandOption: false, }, tokenList: { type: 'string', @@ -69,6 +74,17 @@ const yargsInstance = yargs(hideBin(process.argv)) return true; } + return false; + }) + .check(({ l2NetworkID, crossChain }) => { + if (l2NetworkID && !crossChain) { + return true; + } + + if (!l2NetworkID && crossChain) { + return true; + } + return false; }); diff --git a/src/lib/token_list_gen.ts b/src/lib/token_list_gen.ts index cc72f8a6..eec35542 100644 --- a/src/lib/token_list_gen.ts +++ b/src/lib/token_list_gen.ts @@ -30,30 +30,32 @@ import { constants as arbConstants } from '@arbitrum/sdk'; import { getNetworkConfig } from './instantiate_bridge'; import { getPrevList } from './store'; import { getArgvs } from './options'; -import { BridgedUSDCContractAddressArb1 } from './constants'; +import { BridgedUSDCContractAddressArb1, ChainId } from './constants'; import { getVersion } from './getVersion'; export interface ArbificationOptions { overwriteCurrentList: boolean; } +type GenerateTokenListOptions = { + /** + * Append all tokens from the original l1TokenList to the output list. + */ + includeAllL1Tokens?: boolean; + /** + * Append all unbridged tokens from original l1TokenList to the output list. + */ + includeUnbridgedL1Tokens?: boolean; + getAllTokensInNetwork?: boolean; + includeOldDataFields?: boolean; + sourceListURL?: string; + preserveListName?: boolean; +}; export const generateTokenList = async ( l1TokenList: TokenList, + l2ChainId: number, prevArbTokenList?: ArbTokenList | null, - options?: { - /** - * Append all tokens from the original l1TokenList to the output list. - */ - includeAllL1Tokens?: boolean; - /** - * Append all unbridged tokens from original l1TokenList to the output list. - */ - includeUnbridgedL1Tokens?: boolean; - getAllTokensInNetwork?: boolean; - includeOldDataFields?: boolean; - sourceListURL?: string; - preserveListName?: boolean; - }, + options?: GenerateTokenListOptions, ) => { if (options?.includeAllL1Tokens && options.includeUnbridgedL1Tokens) { throw new Error( @@ -63,12 +65,14 @@ export const generateTokenList = async ( const name = l1TokenList.name; const mainLogoUri = l1TokenList.logoURI; + const increment = l2ChainId === ChainId.Rari ? 100 : 500; - const { l1, l2 } = await promiseErrorMultiplier(getNetworkConfig(), () => - getNetworkConfig(), + const { l1, l2 } = await promiseErrorMultiplier( + getNetworkConfig(l2ChainId), + () => getNetworkConfig(l2ChainId), ); - const { isNova } = isNetwork(); + const { isNova } = isNetwork(l2ChainId); if (options && options.getAllTokensInNetwork && isNova) throw new Error('Subgraph not enabled for nova'); @@ -89,6 +93,7 @@ export const generateTokenList = async ( })), l2.multiCaller, l2.network, + increment, ), () => getL1TokenAndL2Gateway( @@ -98,6 +103,7 @@ export const generateTokenList = async ( })), l2.multiCaller, l2.network, + increment, ), ); @@ -108,7 +114,7 @@ export const generateTokenList = async ( const intermediatel2AddressesFromL1 = []; const intermediatel2AddressesFromL2 = []; - for (const addrs of getChunks(l1TokenAddresses)) { + for (const addrs of getChunks(l1TokenAddresses, increment)) { const l2AddressesFromL1Temp = await promiseErrorMultiplier( getL2TokenAddressesFromL1( addrs, @@ -152,7 +158,7 @@ export const generateTokenList = async ( const filteredL2AddressesFromL2: string[] = []; tokens.forEach((t, i) => { const l2AddressFromL1 = l2AddressesFromL1[i]; - if (l2AddressFromL1 && l2AddressesFromL1[i] === l2AddressesFromL2[i]) { + if (l2AddressFromL1 && l2AddressFromL1 === l2AddressesFromL2[i]) { filteredTokens.push(t); filteredL2AddressesFromL1.push(l2AddressFromL1); filteredL2AddressesFromL2.push(l2AddressFromL1); @@ -163,7 +169,7 @@ export const generateTokenList = async ( l2AddressesFromL2 = filteredL2AddressesFromL1; const intermediateTokenData = []; - for (const addrs of getChunks(l2AddressesFromL1, 100)) { + for (const addrs of getChunks(l2AddressesFromL1, increment)) { const tokenDataTemp = await promiseErrorMultiplier( l2.multiCaller.getTokenData( addrs.map((t) => t || constants.AddressZero), @@ -192,10 +198,11 @@ export const generateTokenList = async ( (t): t is typeof t & { l2Address: string } => t.l2Address != undefined && t.l2Address !== constants.AddressZero, ) - .map((token) => { + .map(async (token) => { const l2GatewayAddress = token.token.joinTableEntry[0].gateway.gatewayAddr; - const l1GatewayAddress = getL1GatewayAddress(l2GatewayAddress) ?? 'N/A'; + const l1GatewayAddress = + (await getL1GatewayAddress(l2GatewayAddress, l2ChainId)) ?? 'N/A'; let { name: _name, decimals, symbol: _symbol } = token.tokenDatum; @@ -286,7 +293,7 @@ export const generateTokenList = async ( console.log(`List has ${arbifiedTokenList.length} bridged tokens`); const allOtherTokens = l1TokenList.tokens - .filter((l1TokenInfo) => l1TokenInfo.chainId !== l2.network.chainID) + .filter((l1TokenInfo) => l1TokenInfo.chainId === l2.network.partnerChainID) .map((l1TokenInfo) => { return { chainId: +l1TokenInfo.chainId, @@ -357,39 +364,39 @@ export const generateTokenList = async ( export const arbifyL1List = async ( pathOrUrl: string, + l1TokenList: TokenList, + l2ChainId: number, { includeOldDataFields, ignorePreviousList, prevArbifiedList, - }: { - includeOldDataFields: boolean; + ...options + }: GenerateTokenListOptions & { ignorePreviousList: boolean; prevArbifiedList: string | undefined; }, -): Promise<{ - newList: ArbTokenList; - l1ListName: string; -}> => { - const l1TokenList = await getTokenListObj(pathOrUrl); - - removeInvalidTokensFromList(l1TokenList); +): Promise => { const prevArbTokenList = ignorePreviousList ? null : await getPrevList(prevArbifiedList); - const newList = await generateTokenList(l1TokenList, prevArbTokenList, { - includeAllL1Tokens: false, - includeOldDataFields, - sourceListURL: isValidHttpUrl(pathOrUrl) ? pathOrUrl : undefined, - }); + const newList = await generateTokenList( + l1TokenList, + l2ChainId, + prevArbTokenList, + { + includeAllL1Tokens: false, + includeOldDataFields, + sourceListURL: isValidHttpUrl(pathOrUrl) ? pathOrUrl : undefined, + ...options, + }, + ); - return { - newList, - l1ListName: l1TokenList.name, - }; + return newList; }; export const updateArbifiedList = async ( pathOrUrl: string, + l2ChainId: number, { includeOldDataFields, ignorePreviousList, @@ -406,19 +413,24 @@ export const updateArbifiedList = async ( ? null : await getPrevList(prevArbifiedList); - const newList = await generateTokenList(arbTokenList, prevArbTokenList, { - includeAllL1Tokens: true, - sourceListURL: isValidHttpUrl(pathOrUrl) ? pathOrUrl : undefined, - includeOldDataFields, - preserveListName: true, - }); + const newList = await generateTokenList( + arbTokenList, + l2ChainId, + prevArbTokenList, + { + includeAllL1Tokens: true, + sourceListURL: isValidHttpUrl(pathOrUrl) ? pathOrUrl : undefined, + includeOldDataFields, + preserveListName: true, + }, + ); return { newList, }; }; -export const generateFullList = async () => { +export const generateFullList = async (l2ChainId: number) => { const mockList: TokenList = { name: 'Full', logoURI: 'ipfs://QmTvWJ4kmzq9koK74WJQ594ov8Es1HHurHZmMmhU8VY68y', @@ -430,13 +442,13 @@ export const generateFullList = async () => { }, tokens: [], }; - const tokenData = await generateTokenList(mockList, undefined, { + const tokenData = await generateTokenList(mockList, l2ChainId, undefined, { getAllTokensInNetwork: true, }); return arbListtoEtherscanList(tokenData); }; -export const generateFullListFormatted = async () => { +export const generateFullListFormatted = async (l2ChainId: number) => { const mockList: TokenList = { name: 'Full', logoURI: 'ipfs://QmTvWJ4kmzq9koK74WJQ594ov8Es1HHurHZmMmhU8VY68y', @@ -448,7 +460,7 @@ export const generateFullListFormatted = async () => { }, tokens: [], }; - const allTokenList = await generateTokenList(mockList, undefined, { + const allTokenList = await generateTokenList(mockList, l2ChainId, undefined, { getAllTokensInNetwork: true, }); // log for human-readable check diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 18bcd926..22abc9ca 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -13,7 +13,6 @@ import { l2ToL1GatewayAddresses, l2ToL1GatewayAddressesNova, } from './constants'; -import { getArgvs } from './options'; // On failed request, retry with exponential back-off axiosRetry(axios, { @@ -27,12 +26,12 @@ axiosRetry(axios, { }, }); -export const isNetwork = () => { - const argv = getArgvs(); +export const isNetwork = (l2NetworkID: number) => { return { - isArbOne: argv.l2NetworkID === 42161, - isNova: argv.l2NetworkID === 42170, - isSepoliaRollup: argv.l2NetworkID === 421614, + isArbOne: l2NetworkID === 42161, + isNova: l2NetworkID === 42170, + isGoerliRollup: l2NetworkID === 421613, + isSepoliaRollup: l2NetworkID === 421614, }; }; @@ -63,11 +62,13 @@ export const getL1TokenAndL2Gateway = async ( tokenList: { addr: string; logo: string | undefined }[], l2Multicaller: MultiCaller, l2Network: L2Network, + increment = 500, ): Promise> => { const routerData = await getL2GatewayAddressesFromL1Token( tokenList.map((curr) => curr.addr), l2Multicaller, l2Network, + increment, ); return tokenList.map((curr, i) => ({ @@ -98,8 +99,11 @@ export const promiseErrorMultiplier = ( }); }; -export const getL1GatewayAddress = (l2GatewayAddress: string) => { - const { isNova } = isNetwork(); +export const getL1GatewayAddress = ( + l2GatewayAddress: string, + l2NetworkID: number, +) => { + const { isNova } = isNetwork(l2NetworkID); const l2Gateway = isNova ? l2ToL1GatewayAddressesNova[l2GatewayAddress.toLowerCase()] : l2ToL1GatewayAddresses[l2GatewayAddress.toLowerCase()]; @@ -113,10 +117,10 @@ export const getL2GatewayAddressesFromL1Token = async ( l1TokenAddresses: string[], l2Multicaller: MultiCaller, l2Network: L2Network, + increment = 500, ): Promise => { const iFace = L1GatewayRouter__factory.createInterface(); - const INC = 500; let index = 0; console.info( 'getL2GatewayAddressesFromL1Token for', @@ -131,10 +135,13 @@ export const getL2GatewayAddressesFromL1Token = async ( 'Getting tokens', index, 'through', - Math.min(index + INC, l1TokenAddresses.length), + Math.min(index + increment, l1TokenAddresses.length), ); - const l1TokenAddressesSlice = l1TokenAddresses.slice(index, index + INC); + const l1TokenAddressesSlice = l1TokenAddresses.slice( + index, + index + increment, + ); const result = await l2Multicaller.multiCall( l1TokenAddressesSlice.map((addr) => ({ encoder: () => iFace.encodeFunctionData('getGateway', [addr]), @@ -144,13 +151,14 @@ export const getL2GatewayAddressesFromL1Token = async ( })), ); gateways = gateways.concat(result); - index += INC; + index += increment; } for (const curr of gateways) { if (typeof curr === 'undefined') throw new Error('undefined gateway!'); } + console.log('OK'); return gateways as string[]; }; diff --git a/src/main.ts b/src/main.ts index 5387847d..b83d174f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,11 @@ import { describe as describeAllTokensList, handler as handlerAllTokensList, } from './commands/allTokensList'; +import { + command as commandCrossChainList, + describe as describeCrossChainList, + handler as handlerCrossChainList, +} from './commands/crossChain'; import './customNetworks'; const myEnv = dotenv.config(); @@ -56,12 +61,20 @@ const alltokenslist = yargsInstance.command( // @ts-ignore: handler returns list so we can compare the result in test, yargs expect handler to return void handlerAllTokensList, ); +const crossChainList = yargsInstance.command( + commandCrossChainList, + describeCrossChainList, + {}, + // @ts-ignore: handler returns list so we can compare the result in test, yargs expect handler to return void + handlerCrossChainList, +); if (process.env.NODE_ENV !== 'test') { update.parseAsync(); arbify.parseAsync(); full.parseAsync(); alltokenslist.parseAsync(); + crossChainList.parseAsync(); } export { update, yargsInstance };