Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions packages/wallet/src/chainRegistry.test.ts

This file was deleted.

38 changes: 0 additions & 38 deletions packages/wallet/src/chainRegistry.ts

This file was deleted.

18 changes: 14 additions & 4 deletions packages/wallet/src/confirmation/confirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
});
}
Expand All @@ -117,7 +122,8 @@ export default class ConfirmationScreen {
requestMessageConfirmation(
messageID: string,
etherAddress: string,
messageType?: MessageType,
messageType: MessageType,
chainId: string,
): Promise<ConfirmationResult> {
return new Promise((resolve, reject) => {
const messageHandler = ({ data, origin }: MessageEvent) => {
Expand Down Expand Up @@ -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);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/connectWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
12 changes: 9 additions & 3 deletions packages/wallet/src/connectWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet/src/guardian/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export default class GuardianClient {
messageId,
user.zkEvm.ethAddress,
'eip712',
chainID,
);

if (!confirmationResult.confirmed) {
Expand Down Expand Up @@ -286,6 +287,7 @@ export default class GuardianClient {
messageId,
user.zkEvm.ethAddress,
'erc191',
String(chainID),
);

if (!confirmationResult.confirmed) {
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
64 changes: 64 additions & 0 deletions packages/wallet/src/network/chainRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
74 changes: 74 additions & 0 deletions packages/wallet/src/network/chainRegistry.ts
Original file line number Diff line number Diff line change
@@ -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, Record<Environment, ChainConfig>> = {
[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<number, EvmChain> {
const map: Record<number, EvmChain> = {};
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<EvmChain, EvmChain.ZKEVM>,
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;
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down