@moonwall/cli
Version:
Testing framework for the Moon family of projects
424 lines (423 loc) • 13.3 kB
JavaScript
// 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
};