📄 src/web3/client/EvmClient.test.ts
D-OPEN SOVEREIGN
1import { describe, it, expect } from 'vitest';
2import { EvmClient } from './EvmClient';
3import { addressLoader } from '../registry/loader';
4import { EVM_CHAINS } from '../network/chains';
5
6// Mock localStorage for Node environment if jsdom doesn't handle it perfectly
7const localStorageMock = (() => {
8 let store: Record<string, string> = {};
9 return {
10 getItem: (key: string) => store[key] || null,
11 setItem: (key: string, value: string) => {
12 store[key] = value.toString();
13 },
14 clear: () => {
15 store = {};
16 },
17 removeItem: (key: string) => {
18 delete store[key];
19 },
20 length: 0,
21 key: (index: number) => null,
22 };
23})();
24
25if (typeof window !== 'undefined') {
26 Object.defineProperty(window, 'localStorage', { value: localStorageMock });
27}
28Object.defineProperty(global, 'localStorage', { value: localStorageMock });
29
30describe('EvmClient Registry Integration', () => {
31 const evmChains = Object.keys(EVM_CHAINS).filter(id => {
32 const chainId = parseInt(id, 10);
33 return EVM_CHAINS[chainId].deployed || EVM_CHAINS[chainId].stagingDeployed;
34 });
35
36 it.each(evmChains)('should hydrate all contract addresses and ABIs for chain %s', async (id) => {
37 const chainId = parseInt(id, 10);
38 const api = new EvmClient(chainId, EVM_CHAINS[chainId].stagingDeployed ? 'staging' : 'production');
39
40 console.log(`[Test] Awaiting API readiness for chain ${chainId} (${EVM_CHAINS[chainId].name})...`);
41 await api.ready;
42 console.log(`[Test] API ready for ${chainId}. Hydrated addresses:`, JSON.stringify(api.config.addresses, null, 2));
43
44 expect(api.config.addresses).toBeDefined();
45 expect(api.config.addresses?.addressRegistry || api.config.addresses?.stagingAddressRegistry).toBeDefined();
46
47 // Required critical contracts for the app
48 const requiredContracts = ['factory', 'backupStorage', 'bouncerStorage', 'projectManagerStorage'];
49 // We get the resolved addresses (which will include the populated ones from the registry)
50 const resolvedAddresses = (api as any)._contractAddresses;
51
52 for (const req of requiredContracts) {
53 if (!resolvedAddresses[req]) {
54 console.error(`[Test] FAILED: Missing required contract '${req}' on chain ${chainId}`);
55 }
56 expect(resolvedAddresses[req]).toBeDefined();
57 expect(resolvedAddresses[req]).toMatch(/^0x[a-fA-F0-9]{40}$/);
58 }
59
60 console.log(`\n[Test] Deployed Contracts Detail for ${chainId}:`);
61 console.log('--------------------------------------------------');
62
63 for (const name of Object.keys(resolvedAddresses)) {
64 const address = resolvedAddresses[name];
65 let abiUrl = 'N/A';
66
67 try {
68 const [_, fetchedUrl] = await api.addressRegistry.getLatestContract(name);
69 abiUrl = fetchedUrl || 'EMPTY';
70 } catch (e) {
71 if (name !== 'addressRegistry' && name !== 'stagingAddressRegistry') {
72 console.warn(`[Test] Could not fetch registry info for ${name}`);
73 }
74 }
75
76 console.log(`${name.padEnd(25)} | ${address} | ABI: ${abiUrl}`);
77
78 if (name === 'addressRegistry' || name === 'stagingAddressRegistry') continue;
79
80 expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/);
81
82 // Poll for background ABI hydration
83 let abi = null;
84 for (let attempt = 0; attempt < 30; attempt++) {
85 abi = addressLoader.getAbi(chainId, name, api.environment);
86 if (abi) break;
87 await new Promise(resolve => setTimeout(resolve, 500));
88 }
89
90 if (!abi) {
91 console.error(`[Test] FAILED: No ABI found for ${name} on chain ${chainId}`);
92 }
93 expect(abi).toBeDefined();
94 expect(Array.isArray(abi)).toBe(true);
95 expect(abi.length).toBeGreaterThan(0);
96 }
97 console.log('--------------------------------------------------\n');
98 }, 60000); // 1 minute timeout per chain
99});
100