📄 src/web3/client/registry.ts
D-OPEN SOVEREIGN
1import { EvmClient } from './EvmClient';
2import { EVM_CHAINS } from '../network/chains';
3import type { ReadOnlyDomainAPI } from './types';
4import { addressLoader } from '../registry/loader';
5
6export const readOnlyClients: Record<number, ReadOnlyDomainAPI> = {};
7export const readOnlyStagingClients: Record<number, ReadOnlyDomainAPI> = {};
8
9const evmClients: Record<number, EvmClient> = {};
10const evmStagingClients: Record<number, EvmClient> = {};
11
12for (const [id, config] of Object.entries(EVM_CHAINS)) {
13  const chainId = Number(id);
14  if (config.deployed) {
15    const client = new EvmClient(chainId, 'production');
16    evmClients[chainId] = client;
17    readOnlyClients[chainId] = client;
18  }
19  if (config.stagingDeployed) {
20    const client = new EvmClient(chainId, 'staging');
21    evmStagingClients[chainId] = client;
22    readOnlyStagingClients[chainId] = client;
23  }
24}
25
26// Drop-in replacement for getReadonlyApiFromTech(tech, chainId) / getReadonlyAdapterByBrand(brand, chainId).
27// Returns ReadOnlyDomainAPI — callers never depend on EvmClient specifics.
28export function getReadClient(
29  chainId: number,
30  environment: 'production' | 'staging' = 'production'
31): ReadOnlyDomainAPI {
32  const map = environment === 'staging' ? readOnlyStagingClients : readOnlyClients;
33  const client = map[chainId];
34  if (!client) throw new Error(`No read client for chain ${chainId} (${environment})`);
35  return client;
36}
37
38// Internal — the write path (session/writeClient.ts) needs the concrete EvmClient
39// (chain, publicClient) to construct an EvmWriteClient, not the adapter-agnostic
40// ReadOnlyDomainAPI. Not part of the adapter-agnostic public contract.
41export function getEvmClient(
42  chainId: number,
43  environment: 'production' | 'staging' = 'production'
44): EvmClient {
45  const map = environment === 'staging' ? evmStagingClients : evmClients;
46  const client = map[chainId];
47  if (!client) throw new Error(`No EVM client for chain ${chainId} (${environment})`);
48  return client;
49}
50
51// client.ready only guarantees addresses are resolved — the manifest's ABI fetch
52// runs fire-and-forget in the background (by design, for progressive browser
53// loading via subscribe()/getLoadingStatus()). A one-shot caller that reads/writes
54// immediately (an admin tool, a CLI script) instead of rendering a loading state
55// needs ABIs resolved too before its first call, or it silently gets empty/failed
56// reads. This is the fully-ready equivalent for that use case.
57export async function getReadyEvmClient(
58  chainId: number,
59  environment: 'production' | 'staging' = 'production'
60): Promise<EvmClient> {
61  const client = getEvmClient(chainId, environment);
62  await client.ready;
63  await addressLoader.waitForAbis(chainId, environment);
64  return client;
65}
66