📄 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