diff --git a/packages/wallet/src/chainRegistry.test.ts b/packages/wallet/src/chainRegistry.test.ts deleted file mode 100644 index 02972e91b2..0000000000 --- a/packages/wallet/src/chainRegistry.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Environment } from '@imtbl/config'; -import { getChainConfig } from './chainRegistry'; -import { EvmChain } from './types'; -import { ARBITRUM_ONE_CHAIN, ARBITRUM_SEPOLIA_CHAIN } from './presets'; - -describe('chainRegistry', () => { - describe('getChainConfig', () => { - it('returns Arbitrum One mainnet config for PRODUCTION', () => { - const config = getChainConfig(EvmChain.ARBITRUM_ONE, Environment.PRODUCTION); - - expect(config).toEqual(ARBITRUM_ONE_CHAIN); - expect(config.chainId).toBe(42161); - expect(config.name).toBe('Arbitrum One'); - }); - - it('returns Arbitrum Sepolia config for SANDBOX', () => { - const config = getChainConfig(EvmChain.ARBITRUM_ONE, Environment.SANDBOX); - - expect(config).toEqual(ARBITRUM_SEPOLIA_CHAIN); - expect(config.chainId).toBe(421614); - expect(config.name).toBe('Arbitrum Sepolia'); - }); - - it('throws error for unsupported chain', () => { - expect(() => { - getChainConfig('unsupported_chain' as any, Environment.PRODUCTION); - }).toThrow('Chain unsupported_chain is not supported'); - }); - }); -}); diff --git a/packages/wallet/src/chainRegistry.ts b/packages/wallet/src/chainRegistry.ts deleted file mode 100644 index 41d935dffd..0000000000 --- a/packages/wallet/src/chainRegistry.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Environment } from '@imtbl/config'; -import { ChainConfig, EvmChain } from './types'; -import { - ARBITRUM_ONE_CHAIN, - ARBITRUM_SEPOLIA_CHAIN, -} from './presets'; - -/** - * Registry mapping (EvmChain, Environment) to ChainConfig - * Add new chains here - no changes needed in Passport.ts - */ -const CHAIN_REGISTRY: Record, Record> = { - [EvmChain.ARBITRUM_ONE]: { - [Environment.PRODUCTION]: ARBITRUM_ONE_CHAIN, - [Environment.SANDBOX]: ARBITRUM_SEPOLIA_CHAIN, - }, -}; - -/** - * Get chain config for non-zkEVM chains - * @throws Error if chain is not in registry - */ -export function getChainConfig( - chain: Exclude, - environment: Environment, -): ChainConfig { - const envConfigs = CHAIN_REGISTRY[chain]; - if (!envConfigs) { - throw new Error(`Chain ${chain} is not supported`); - } - - const config = envConfigs[environment]; - if (!config) { - throw new Error(`Chain ${chain} is not configured for environment ${environment}`); - } - - return config; -} diff --git a/packages/wallet/src/confirmation/confirmation.ts b/packages/wallet/src/confirmation/confirmation.ts index 721e6adb90..d8cbdeea4b 100644 --- a/packages/wallet/src/confirmation/confirmation.ts +++ b/packages/wallet/src/confirmation/confirmation.ts @@ -10,6 +10,8 @@ import { import { openPopupCenter } from './popup'; import { IAuthConfiguration } from '@imtbl/auth'; import ConfirmationOverlay from '../overlay/confirmationOverlay'; +import { getEvmChainFromChainId } from '../network/chainRegistry'; +import { EvmChain } from '../types'; const CONFIRMATION_WINDOW_TITLE = 'Confirm this transaction'; const CONFIRMATION_WINDOW_HEIGHT = 720; @@ -105,7 +107,10 @@ export default class ConfirmationScreen { if (chainType === GeneratedClients.mr.TransactionApprovalRequestChainTypeEnum.Starkex) { href = this.getHref('transaction', { transactionId, etherAddress, chainType }); } else { - href = this.getHref('zkevm/transaction', { + // Get chain path from chainId (e.g., 'zkevm', 'arbitrum-one') + const chain = chainId ? getEvmChainFromChainId(chainId) : EvmChain.ZKEVM; + const chainPath = chain.replace('_', '-'); + href = this.getHref(`${chainPath}/transaction`, { transactionID: transactionId, etherAddress, chainType, chainID: chainId, }); } @@ -117,7 +122,8 @@ export default class ConfirmationScreen { requestMessageConfirmation( messageID: string, etherAddress: string, - messageType?: MessageType, + messageType: MessageType, + chainId: string, ): Promise { return new Promise((resolve, reject) => { const messageHandler = ({ data, origin }: MessageEvent) => { @@ -157,10 +163,14 @@ export default class ConfirmationScreen { }; window.addEventListener('message', messageHandler); - const href = this.getHref('zkevm/message', { + // Get chain path from chainId (e.g., 'zkevm', 'arbitrum-one') + const chain = getEvmChainFromChainId(chainId); + const chainPath = chain.replace('_', '-'); + const href = this.getHref(`${chainPath}/message`, { messageID, etherAddress, - ...(messageType ? { messageType } : {}), + chainID: chainId, + messageType, }); this.showConfirmationScreen(href, messageHandler, resolve); }); diff --git a/packages/wallet/src/connectWallet.test.ts b/packages/wallet/src/connectWallet.test.ts index 7ca336d5f9..6efd022c19 100644 --- a/packages/wallet/src/connectWallet.test.ts +++ b/packages/wallet/src/connectWallet.test.ts @@ -122,10 +122,10 @@ describe('connectWallet', () => { expect(SequenceProvider).not.toHaveBeenCalled(); }); - it('uses ZkEvmProvider for chain with magic config', async () => { + it('uses ZkEvmProvider for zkEVM devnet chain', async () => { const auth = createAuthStub(); const devChain = { - chainId: 99999, // unknown chainId + chainId: 15003, // zkEVM devnet chainId rpcUrl: 'https://rpc.dev.immutable.com', relayerUrl: 'https://relayer.dev.immutable.com', apiUrl: 'https://api.dev.immutable.com', diff --git a/packages/wallet/src/connectWallet.ts b/packages/wallet/src/connectWallet.ts index 92037b690c..ae4144aa4b 100644 --- a/packages/wallet/src/connectWallet.ts +++ b/packages/wallet/src/connectWallet.ts @@ -18,11 +18,12 @@ import { WalletConfiguration } from './config'; import GuardianClient from './guardian'; import MagicTEESigner from './magic/magicTEESigner'; import { announceProvider, passportProviderInfo } from './provider/eip6963'; -import { DEFAULT_CHAINS } from './presets'; +import { DEFAULT_CHAINS } from './network/presets'; import { MAGIC_CONFIG, IMMUTABLE_ZKEVM_TESTNET_CHAIN_ID, } from './constants'; +import { ChainId } from './network/chains'; /** * Type guard to check if chainId is a valid key for MAGIC_CONFIG @@ -70,9 +71,14 @@ const DEFAULT_REDIRECT_FALLBACK = 'https://auth.immutable.com/im-logged-in'; const DEFAULT_AUTHENTICATION_DOMAIN = 'https://auth.immutable.com'; const SANDBOX_DOMAIN_REGEX = /(sandbox|testnet)/i; +const ZKEVM_CHAIN_IDS = [ + ChainId.IMTBL_ZKEVM_MAINNET, + ChainId.IMTBL_ZKEVM_TESTNET, + ChainId.IMTBL_ZKEVM_DEVNET, +]; + function isZkEvmChain(chain: ChainConfig): boolean { - // Only zkEVM chains use magic - return !!chain.magicPublishableApiKey || isValidMagicChainId(chain.chainId); + return ZKEVM_CHAIN_IDS.includes(chain.chainId); } function isSandboxChain(chain: ChainConfig): boolean { diff --git a/packages/wallet/src/guardian/index.ts b/packages/wallet/src/guardian/index.ts index 889dd16b6d..00036d958f 100644 --- a/packages/wallet/src/guardian/index.ts +++ b/packages/wallet/src/guardian/index.ts @@ -232,6 +232,7 @@ export default class GuardianClient { messageId, user.zkEvm.ethAddress, 'eip712', + chainID, ); if (!confirmationResult.confirmed) { @@ -286,6 +287,7 @@ export default class GuardianClient { messageId, user.zkEvm.ethAddress, 'erc191', + String(chainID), ); if (!confirmationResult.confirmed) { diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 2298ce6c8a..8e11096aca 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -23,10 +23,10 @@ export { ARBITRUM_SEPOLIA, ARBITRUM_ONE_CHAIN, ARBITRUM_SEPOLIA_CHAIN, -} from './presets'; +} from './network/presets'; // Export chain registry for looking up chain configs -export { getChainConfig } from './chainRegistry'; +export { getChainConfig, getEvmChainFromChainId } from './network/chainRegistry'; // Export main wallet providers export { ZkEvmProvider } from './zkEvm/zkEvmProvider'; diff --git a/packages/wallet/src/network/chainRegistry.test.ts b/packages/wallet/src/network/chainRegistry.test.ts new file mode 100644 index 0000000000..7a75e1bace --- /dev/null +++ b/packages/wallet/src/network/chainRegistry.test.ts @@ -0,0 +1,64 @@ +import { Environment } from '@imtbl/config'; +import { getChainConfig, getEvmChainFromChainId } from './chainRegistry'; +import { EvmChain } from '../types'; +import { ARBITRUM_ONE_CHAIN, ARBITRUM_SEPOLIA_CHAIN } from './presets'; + +describe('chainRegistry', () => { + describe('getChainConfig', () => { + it('returns Arbitrum One mainnet config for PRODUCTION', () => { + const config = getChainConfig(EvmChain.ARBITRUM_ONE, Environment.PRODUCTION); + + expect(config).toEqual(ARBITRUM_ONE_CHAIN); + expect(config.chainId).toBe(42161); + expect(config.name).toBe('Arbitrum One'); + }); + + it('returns Arbitrum Sepolia config for SANDBOX', () => { + const config = getChainConfig(EvmChain.ARBITRUM_ONE, Environment.SANDBOX); + + expect(config).toEqual(ARBITRUM_SEPOLIA_CHAIN); + expect(config.chainId).toBe(421614); + expect(config.name).toBe('Arbitrum Sepolia'); + }); + + it('throws error for unsupported chain', () => { + expect(() => { + getChainConfig('unsupported_chain' as any, Environment.PRODUCTION); + }).toThrow('Chain unsupported_chain is not supported'); + }); + }); + + describe('getEvmChainFromChainId', () => { + it('returns ZKEVM for mainnet chainId', () => { + expect(getEvmChainFromChainId(13371)).toBe(EvmChain.ZKEVM); + }); + + it('returns ZKEVM for testnet chainId', () => { + expect(getEvmChainFromChainId(13473)).toBe(EvmChain.ZKEVM); + }); + + it('returns ZKEVM for devnet chainId', () => { + expect(getEvmChainFromChainId(15003)).toBe(EvmChain.ZKEVM); + }); + + it('returns ARBITRUM_ONE for Arbitrum mainnet chainId', () => { + expect(getEvmChainFromChainId(42161)).toBe(EvmChain.ARBITRUM_ONE); + }); + + it('returns ARBITRUM_ONE for Arbitrum Sepolia chainId', () => { + expect(getEvmChainFromChainId(421614)).toBe(EvmChain.ARBITRUM_ONE); + }); + + it('handles string chainId', () => { + expect(getEvmChainFromChainId('42161')).toBe(EvmChain.ARBITRUM_ONE); + }); + + it('handles eip155 format chainId', () => { + expect(getEvmChainFromChainId('eip155:42161')).toBe(EvmChain.ARBITRUM_ONE); + }); + + it('defaults to ZKEVM for unknown chainId', () => { + expect(getEvmChainFromChainId(99999)).toBe(EvmChain.ZKEVM); + }); + }); +}); diff --git a/packages/wallet/src/network/chainRegistry.ts b/packages/wallet/src/network/chainRegistry.ts new file mode 100644 index 0000000000..04899fd84a --- /dev/null +++ b/packages/wallet/src/network/chainRegistry.ts @@ -0,0 +1,74 @@ +import { Environment } from '@imtbl/config'; +import { ChainConfig, EvmChain } from '../types'; +import { + IMMUTABLE_ZKEVM_MAINNET_CHAIN, + IMMUTABLE_ZKEVM_TESTNET_CHAIN, + ARBITRUM_ONE_CHAIN, + ARBITRUM_SEPOLIA_CHAIN, +} from './presets'; +import { ChainId } from './chains'; + +/** + * Registry mapping (EvmChain, Environment) to ChainConfig + */ +const CHAIN_REGISTRY: Record> = { + [EvmChain.ZKEVM]: { + [Environment.PRODUCTION]: IMMUTABLE_ZKEVM_MAINNET_CHAIN, + [Environment.SANDBOX]: IMMUTABLE_ZKEVM_TESTNET_CHAIN, + }, + [EvmChain.ARBITRUM_ONE]: { + [Environment.PRODUCTION]: ARBITRUM_ONE_CHAIN, + [Environment.SANDBOX]: ARBITRUM_SEPOLIA_CHAIN, + }, +}; + +/** + * Build chainId → EvmChain mapping from CHAIN_REGISTRY (derived, not manual) + */ +function buildChainIdToEvmChainMap(): Record { + const map: Record = {}; + for (const [evmChain, envConfigs] of Object.entries(CHAIN_REGISTRY)) { + for (const config of Object.values(envConfigs)) { + map[config.chainId] = evmChain as EvmChain; + } + } + // Devnet doesn't have a preset + map[ChainId.IMTBL_ZKEVM_DEVNET] = EvmChain.ZKEVM; + return map; +} + +const CHAIN_ID_TO_EVM_CHAIN = buildChainIdToEvmChainMap(); + +/** + * Get chain config for non-zkEVM chains + * @throws Error if chain is not in registry + */ +export function getChainConfig( + chain: Exclude, + environment: Environment, +): ChainConfig { + const envConfigs = CHAIN_REGISTRY[chain]; + if (!envConfigs) { + throw new Error(`Chain ${chain} is not supported`); + } + + const config = envConfigs[environment]; + if (!config) { + throw new Error(`Chain ${chain} is not configured for environment ${environment}`); + } + + return config; +} + +/** + * Get EvmChain from chainId + * @param chainId - Chain ID (can be number or string like "eip155:42161") + * @returns EvmChain enum value, defaults to ZKEVM if not found + */ +export function getEvmChainFromChainId(chainId: string | number): EvmChain { + const numericChainId = typeof chainId === 'string' + ? parseInt(chainId.includes(':') ? chainId.split(':')[1] : chainId, 10) + : chainId; + + return CHAIN_ID_TO_EVM_CHAIN[numericChainId] ?? EvmChain.ZKEVM; +} diff --git a/packages/wallet/src/presets.ts b/packages/wallet/src/network/presets.ts similarity index 98% rename from packages/wallet/src/presets.ts rename to packages/wallet/src/network/presets.ts index 70c1289719..d44c4b8a62 100644 --- a/packages/wallet/src/presets.ts +++ b/packages/wallet/src/network/presets.ts @@ -1,11 +1,11 @@ -import { ChainConfig } from './types'; +import { ChainConfig } from '../types'; import { IMMUTABLE_ZKEVM_MAINNET_CHAIN_ID, IMMUTABLE_ZKEVM_TESTNET_CHAIN_ID, ARBITRUM_ONE_CHAIN_ID, ARBITRUM_SEPOLIA_CHAIN_ID, MAGIC_CONFIG, -} from './constants'; +} from '../constants'; /** * Immutable zkEVM Mainnet chain configuration