UNPKG

@moonwall/cli

Version:

Testing framework for the Moon family of projects

424 lines (423 loc) 13.3 kB
// src/internal/providerFactories.ts import { ALITH_PRIVATE_KEY, createLogger, deriveViemChain, normalizeUrlToHttps } from "@moonwall/util"; import { ApiPromise, WsProvider } from "@polkadot/api"; import { Wallet, ethers } from "ethers"; import { createClient } from "polkadot-api"; import { getWsProvider } from "polkadot-api/ws-provider"; import { createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { Web3 } from "web3"; import { WebSocketProvider } from "web3-providers-ws"; import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat"; import * as fs from "fs"; import * as path from "path"; var logger = createLogger({ name: "providers" }); var debug = logger.debug.bind(logger); var getMetadataCacheDir = () => { return process.env.MOONWALL_CACHE_DIR || path.join(process.cwd(), "tmp", "metadata-cache"); }; var loadCachedMetadata = () => { const cacheDir = getMetadataCacheDir(); const metadataPath = path.join(cacheDir, "metadata-cache.json"); try { const data = fs.readFileSync(metadataPath, "utf-8"); const cached = JSON.parse(data); debug(`Loaded cached metadata for genesis: ${Object.keys(cached).join(", ")}`); return cached; } catch { return void 0; } }; var saveCachedMetadata = (genesisHash, metadataHex) => { const cacheDir = getMetadataCacheDir(); try { fs.mkdirSync(cacheDir, { recursive: true }); } catch { } const metadataPath = path.join(cacheDir, "metadata-cache.json"); const lockPath = `${metadataPath}.lock`; try { try { fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL); } catch { return; } const data = JSON.stringify({ [genesisHash]: metadataHex }); fs.writeFileSync(metadataPath, data, "utf-8"); debug(`Saved metadata cache for genesis: ${genesisHash}`); } catch (e) { debug(`Failed to save metadata cache: ${e}`); } finally { try { fs.unlinkSync(lockPath); } catch { } } }; var ProviderFactory = class _ProviderFactory { constructor(providerConfig) { this.providerConfig = providerConfig; const endpoint = providerConfig.endpoints[0]; if (endpoint === "AUTO" || endpoint.includes("ENV_VAR")) { this.url = endpoint === "AUTO" ? vitestAutoUrl() : process.env.WSS_URL || "error_missing_WSS_URL_env_var"; } else { this.url = endpoint; } debug(`Constructor - providerConfig.endpoints[0]: ${endpoint}, this.url: ${this.url}`); this.privateKey = process.env.MOON_PRIV_KEY || ALITH_PRIVATE_KEY; } url; privateKey; create() { switch (this.providerConfig.type) { case "polkadotJs": return this.createPolkadotJs(); case "web3": return this.createWeb3(); case "ethers": return this.createEthers(); case "viem": return this.createViem(); case "papi": return this.createPapi(); default: return this.createDefault(); } } createPolkadotJs() { debug( `\u{1F7E2} PolkadotJs provider ${this.providerConfig.name} details prepared to connect to ${this.url}` ); const cacheEnabled = this.providerConfig.cacheMetadata !== false; return { name: this.providerConfig.name, type: this.providerConfig.type, connect: async () => { const cachedMetadata = cacheEnabled ? loadCachedMetadata() : void 0; const startTime = Date.now(); const options = { provider: new WsProvider(this.url), initWasm: false, noInitWarn: true, isPedantic: false, rpc: this.providerConfig.rpc ? this.providerConfig.rpc : void 0, typesBundle: this.providerConfig.additionalTypes ? this.providerConfig.additionalTypes : void 0, metadata: cachedMetadata }; const api = await ApiPromise.create(options); await api.isReady; if (cacheEnabled && !cachedMetadata) { const genesisHash = api.genesisHash.toHex(); const metadataHex = api.runtimeMetadata.toHex(); saveCachedMetadata(genesisHash, metadataHex); debug(`PolkadotJs connected in ${Date.now() - startTime}ms (metadata fetched & cached)`); } else if (cachedMetadata) { debug(`PolkadotJs connected in ${Date.now() - startTime}ms (using cached metadata)`); } else { debug(`PolkadotJs connected in ${Date.now() - startTime}ms (caching disabled)`); } return api; }, ws: () => new WsProvider(this.url) }; } createWeb3() { debug(`\u{1F7E2} Web3 provider ${this.providerConfig.name} details prepared`); return { name: this.providerConfig.name, type: this.providerConfig.type, connect: () => { const provider = new WebSocketProvider( this.url, {}, { delay: 50, autoReconnect: false, maxAttempts: 10 } ); return new Web3(provider); } }; } createEthers() { debug(`\u{1F7E2} Ethers provider ${this.providerConfig.name} details prepared`); return { name: this.providerConfig.name, type: this.providerConfig.type, connect: () => { const provider = this.url.startsWith("ws") ? new ethers.WebSocketProvider(this.url) : new ethers.JsonRpcProvider(this.url); return new Wallet(this.privateKey, provider); } }; } createViem() { debug(`\u{1F7E2} Viem omni provider ${this.providerConfig.name} details prepared`); return { name: this.providerConfig.name, type: this.providerConfig.type, connect: async () => { try { debug(`Original URL (this.url): ${this.url}`); const httpUrl = normalizeUrlToHttps(this.url); debug(`Converted HTTP URL: ${httpUrl} for provider ${this.providerConfig.name}`); debug( `\u{1F50C} Attempting to derive chain for viem provider ${this.providerConfig.name} from ${httpUrl}` ); const chain = await deriveViemChain(httpUrl); const client = createWalletClient({ chain, account: privateKeyToAccount(this.privateKey), transport: http(httpUrl) }).extend(publicActions); return client; } catch (error) { console.error( `\u274C Failed to create viem provider ${this.providerConfig.name} at ${this.url}: ${error.message}` ); throw new Error( `Viem provider initialization failed for ${this.providerConfig.name} at ${this.url}: ${error.message}` ); } } }; } createPapi() { debug(`\u{1F7E2} Papi provider ${this.providerConfig.name} details prepared`); return { name: this.providerConfig.name, type: this.providerConfig.type, connect: () => { const provider = withPolkadotSdkCompat(getWsProvider(this.url, {})); return createClient(provider); } }; } createDefault() { debug(`\u{1F7E2} Default provider ${this.providerConfig.name} details prepared`); return { name: this.providerConfig.name, type: this.providerConfig.type, connect: () => { console.log(`\u{1F6A7} provider ${this.providerConfig.name} not yet implemented`); return null; } }; } static prepare(providerConfigs) { return providerConfigs.map((providerConfig) => new _ProviderFactory(providerConfig).create()); } static prepareDefaultDev() { return _ProviderFactory.prepare([ { name: "dev", type: "polkadotJs", endpoints: [vitestAutoUrl()] }, { name: "w3", type: "web3", endpoints: [vitestAutoUrl()] }, { name: "eth", type: "ethers", endpoints: [vitestAutoUrl()] }, { name: "public", type: "viem", endpoints: [vitestAutoUrl()] } ]); } static prepareDefaultZombie() { const MOON_PARA_WSS = process.env.MOON_PARA_WSS || "error"; const MOON_RELAY_WSS = process.env.MOON_RELAY_WSS || "error"; const providers = [ { name: "w3", type: "web3", endpoints: [MOON_PARA_WSS] }, { name: "eth", type: "ethers", endpoints: [MOON_PARA_WSS] }, { name: "viem", type: "viem", endpoints: [MOON_PARA_WSS] }, { name: "relaychain", type: "polkadotJs", endpoints: [MOON_RELAY_WSS] } ]; if (MOON_PARA_WSS !== "error") { providers.push({ name: "parachain", type: "polkadotJs", endpoints: [MOON_PARA_WSS] }); } return _ProviderFactory.prepare(providers); } static prepareNoEthDefaultZombie() { const MOON_PARA_WSS = process.env.MOON_PARA_WSS || "error"; const MOON_RELAY_WSS = process.env.MOON_RELAY_WSS || "error"; const providers = [ { name: "relaychain", type: "polkadotJs", endpoints: [MOON_RELAY_WSS] } ]; if (MOON_PARA_WSS !== "error") { providers.push({ name: "parachain", type: "polkadotJs", endpoints: [MOON_PARA_WSS] }); } return _ProviderFactory.prepare(providers); } }; var ProviderInterfaceFactory = class _ProviderInterfaceFactory { constructor(name, type, connect) { this.name = name; this.type = type; this.connect = connect; } async create() { switch (this.type) { case "polkadotJs": return this.createPolkadotJs(); case "web3": return this.createWeb3(); case "ethers": return this.createEthers(); case "viem": return this.createViem(); case "papi": return this.createPapi(); default: throw new Error("UNKNOWN TYPE"); } } async createPolkadotJs() { debug(`\u{1F50C} Connecting PolkadotJs provider: ${this.name}`); const api = await this.connect(); debug(`\u2705 PolkadotJs provider ${this.name} connected`); 1; return { name: this.name, api, type: "polkadotJs", greet: async () => { debug( `\u{1F44B} Provider ${this.name} is connected to chain ${api.consts.system.version.specName.toString()} RT${api.consts.system.version.specVersion.toNumber()}` ); return { rtVersion: api.consts.system.version.specVersion.toNumber(), rtName: api.consts.system.version.specName.toString() }; }, disconnect: async () => api.disconnect() }; } async createWeb3() { const api = await this.connect(); return { name: this.name, api, type: "web3", greet: async () => console.log(`\u{1F44B} Provider ${this.name} is connected to chain ${await api.eth.getChainId()}`), disconnect: async () => { if (!api.eth.net.currentProvider) { throw new Error("No connected web3 provider to disconnect from"); } api.eth.net.currentProvider.disconnect(); } }; } async createEthers() { const api = await this.connect(); return { name: this.name, api, type: "ethers", greet: async () => { if (!api.provider) { throw new Error("No connected ethers provider to greet with"); } debug( `\u{1F44B} Provider ${this.name} is connected to chain ${(await api.provider.getNetwork()).chainId}` ); }, disconnect: () => { if (!api.provider) { throw new Error("No connected ethers provider to disconnect from"); } api.provider.destroy(); } }; } async createViem() { const api = await this.connect(); return { name: this.name, api, type: "viem", greet: async () => console.log(`\u{1F44B} Provider ${this.name} is connected to chain ${await api.getChainId()}`), disconnect: async () => { } }; } async createPapi() { const api = await this.connect(); return { name: this.name, api, type: "papi", greet: async () => { const unsafeApi = await api.getUnsafeApi(); const { spec_version, spec_name } = await unsafeApi.constants.System.Version(); return { rtVersion: spec_version, rtName: spec_name }; }, async disconnect() { api.destroy(); } }; } static async populate(name, type, connect) { debug(`\u{1F504} Populating provider: ${name} of type: ${type}`); try { const providerInterface = await new _ProviderInterfaceFactory(name, type, connect).create(); debug(`\u2705 Successfully populated provider: ${name}`); return providerInterface; } catch (error) { if (error instanceof Error) { console.error(`\u274C Failed to populate provider: ${name} - ${error.message}`); } else { console.error(`\u274C Failed to populate provider: ${name} - Unknown error`); } throw error; } } }; var vitestAutoUrl = () => { const url = `ws://127.0.0.1:${process.env.MOONWALL_RPC_PORT}`; debug( `vitestAutoUrl - MOONWALL_RPC_PORT=${process.env.MOONWALL_RPC_PORT}, Generated URL: ${url}` ); return url; }; export { ProviderFactory, ProviderInterfaceFactory, vitestAutoUrl };