UNPKG

@omni-kit/omni-deployer

Version:

Deploy smart contracts across multiple chains using SuperchainFactory

331 lines (330 loc) 13.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadConfig = loadConfig; exports.loadHubSpokeConfig = loadHubSpokeConfig; const fs_1 = __importDefault(require("fs")); const inquirer_1 = __importDefault(require("inquirer")); /** * Parses and validates chain IDs from a comma-separated string * @returns An array of valid chain IDs or an error message */ const validateAndParseChainIds = (input) => { if (!input || !input.trim()) { return 'At least one chain ID is required.'; } const ids = input.split(','); const result = []; for (const idStr of ids) { const trimmed = idStr.trim(); if (!trimmed) continue; // Check if the input contains only digits if (!/^\d+$/.test(trimmed)) { return `"${trimmed}" is not a valid chain ID. All chain IDs must be positive integers.`; } const id = parseInt(trimmed, 10); if (id <= 0) { return `"${id}" is not valid. Chain IDs must be positive integers.`; } result.push(id); } if (result.length === 0) { return 'At least one valid chain ID is required.'; } return result; }; const validateEVMAddress = (input) => { if (!input || !input.trim()) { return 'Address is required.'; } // EVM addresses should be 42 characters long (0x + 40 hex chars) if (!/^0x[0-9a-fA-F]{40}$/.test(input)) { return 'Invalid EVM address format. Address should be in format: 0x followed by 40 hexadecimal characters.'; } return true; }; const validateUrl = (input) => { if (!input || !input.trim()) { return 'URL is required.'; } try { new URL(input); return true; } catch (e) { return 'Invalid URL format. Please enter a valid URL (e.g., https://rpc.example.com).'; } }; const parseConstructorArgs = (input) => { if (!input || !input.trim()) return []; try { const parsed = JSON.parse(input); if (Array.isArray(parsed)) return parsed; } catch (_a) { // If not valid JSON, try comma-separated list return input.split(',').map((item) => item.trim()); } return input.split(',').map((item) => item.trim()); }; const validateConstructorArgs = (input) => { if (!input || !input.trim()) return true; try { const parsed = JSON.parse(input); return Array.isArray(parsed) ? true : 'Input must be a JSON array (e.g., ["arg1", 42]) or comma-separated list (e.g., arg1, 42)'; } catch (_a) { return input.includes(',') || !input.includes(' ') ? true : 'Input must be a comma-separated list (e.g., arg1, 42) or JSON array (e.g., ["arg1", 42])'; } }; function loadConfig(configPath) { return __awaiter(this, void 0, void 0, function* () { let config = {}; if (configPath) { try { config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8')); } catch (error) { console.error(`Failed to read or parse config file at ${configPath}: ${error.message}`); } } const questions = [ { type: 'input', name: 'chainsInput', // Use a different name to store raw input message: 'Enter chain IDs (comma-separated, e.g., 1,137,42161):', when: () => !config.chains || !Array.isArray(config.chains) || config.chains.length === 0, validate: (input) => { const result = validateAndParseChainIds(input); return typeof result === 'string' ? result : true; } }, { type: 'input', name: 'factoryContract', message: 'Enter factory contract address (e.g., 0x538DB2dF0f1CCF9fBA392A0248D41292f01D3966):', when: () => !config.factoryContract, validate: validateEVMAddress }, { type: 'input', name: 'contractName', message: 'Enter contract name (e.g., MyContract):', when: () => !config.contractName, validate: (input) => !!input ? true : 'Contract name is required.' }, { type: 'input', name: 'constructorArgsInput', // Use a different name to store raw input message: 'Enter constructor arguments as a JSON array or comma-separated list (e.g., ["arg1", 42] or arg1, 42, or press Enter for none):', when: () => !config.constructorArgs || !Array.isArray(config.constructorArgs), default: '', validate: validateConstructorArgs }, { type: 'input', name: 'salt', message: 'Enter salt (e.g., mysalt):', when: () => !config.salt, validate: (input) => !!input ? true : 'Salt is required.' }, { type: 'input', name: 'rpcUrl', message: 'Enter RPC URL (e.g., https://rpc.ethereum.example.com):', when: () => !config.rpcUrl, validate: validateUrl } ]; const answers = yield inquirer_1.default.prompt(questions); // Process the raw inputs after validation if (answers.chainsInput) { const parsedChains = validateAndParseChainIds(answers.chainsInput); if (typeof parsedChains === 'string') { throw new Error(`Chain ID validation failed: ${parsedChains}`); } answers.chains = parsedChains; delete answers.chainsInput; // Remove the temporary input property } if (answers.constructorArgsInput !== undefined) { answers.constructorArgs = parseConstructorArgs(answers.constructorArgsInput); delete answers.constructorArgsInput; // Remove the temporary input property } config = Object.assign(Object.assign({}, config), answers); // Final validation of all fields const errors = []; if (!config.chains || !Array.isArray(config.chains) || config.chains.length === 0) { errors.push('Invalid or missing "chains"'); } if (!config.factoryContract || !/^0x[0-9a-fA-F]{40}$/.test(config.factoryContract)) { errors.push('Invalid or missing "factoryContract". Must be a valid EVM address'); } if (!config.contractName) { errors.push('Missing "contractName"'); } if (!config.salt) { errors.push('Missing "salt"'); } if (!config.rpcUrl) { errors.push('Missing "rpcUrl"'); } else { try { new URL(config.rpcUrl); } catch (e) { errors.push('Invalid "rpcUrl". Must be a valid URL'); } } if (errors.length > 0) { throw new Error(`Configuration validation failed:\n${errors.join('\n')}`); } return config; }); } function loadHubSpokeConfig(configPath) { return __awaiter(this, void 0, void 0, function* () { let config = {}; if (configPath) { try { config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8')); } catch (error) { console.error(`Failed to read or parse config file at ${configPath}: ${error.message}`); } } const questions = [ { type: 'input', name: 'chainsInput', // Use a different name to store raw input message: 'Enter chain IDs for spoke deployments (comma-separated, e.g., 1,137,42161):', when: () => !config.chains || !Array.isArray(config.chains) || config.chains.length === 0, validate: (input) => { const result = validateAndParseChainIds(input); return typeof result === 'string' ? result : true; } }, { type: 'input', name: 'factoryContract', message: 'Enter factory contract address (e.g., 0x538DB2dF0f1CCF9fBA392A0248D41292f01D3966):', when: () => !config.factoryContract, validate: validateEVMAddress }, { type: 'input', name: 'hubContract', message: 'Enter hub contract name (e.g., HubContract):', when: () => !config.hubContract, validate: (input) => !!input ? true : 'Hub contract name is required.' }, { type: 'input', name: 'spokeContract', message: 'Enter spoke contract name (e.g., SpokeContract):', when: () => !config.spokeContract, validate: (input) => !!input ? true : 'Spoke contract name is required.' }, { type: 'input', name: 'hubConstructorArgsInput', // Use a different name to store raw input message: 'Enter hub constructor arguments as a JSON array or comma-separated list (e.g., ["arg1", 42] or arg1, 42, or press Enter for none):', when: () => !config.hubConstructorArgs || !Array.isArray(config.hubConstructorArgs), default: '', validate: validateConstructorArgs }, { type: 'input', name: 'spokeConstructorArgsInput', // Use a different name to store raw input message: 'Enter spoke constructor arguments as a JSON array or comma-separated list (e.g., ["arg1", 42] or arg1, 42, or press Enter for none):', when: () => !config.spokeConstructorArgs || !Array.isArray(config.spokeConstructorArgs), default: '', validate: validateConstructorArgs }, { type: 'input', name: 'salt', message: 'Enter salt (e.g., mysalt):', when: () => !config.salt, validate: (input) => !!input ? true : 'Salt is required.' }, { type: 'input', name: 'rpcUrl', message: 'Enter RPC URL (e.g., https://rpc.ethereum.example.com):', when: () => !config.rpcUrl, validate: validateUrl } ]; const answers = yield inquirer_1.default.prompt(questions); // Process the raw inputs after validation if (answers.chainsInput) { const parsedChains = validateAndParseChainIds(answers.chainsInput); if (typeof parsedChains === 'string') { throw new Error(`Chain ID validation failed: ${parsedChains}`); } answers.chains = parsedChains; delete answers.chainsInput; // Remove the temporary input property } if (answers.hubConstructorArgsInput !== undefined) { answers.hubConstructorArgs = parseConstructorArgs(answers.hubConstructorArgsInput); delete answers.hubConstructorArgsInput; // Remove the temporary input property } if (answers.spokeConstructorArgsInput !== undefined) { answers.spokeConstructorArgs = parseConstructorArgs(answers.spokeConstructorArgsInput); delete answers.spokeConstructorArgsInput; // Remove the temporary input property } config = Object.assign(Object.assign({}, config), answers); // Final validation const errors = []; if (!config.chains || !Array.isArray(config.chains) || config.chains.length === 0) { errors.push('Invalid or missing "chains"'); } if (!config.factoryContract || !/^0x[0-9a-fA-F]{40}$/.test(config.factoryContract)) { errors.push('Invalid or missing "factoryContract". Must be a valid EVM address'); } if (!config.hubContract) { errors.push('Missing "hubContract"'); } if (!config.spokeContract) { errors.push('Missing "spokeContract"'); } if (!config.salt) { errors.push('Missing "salt"'); } if (!config.rpcUrl) { errors.push('Missing "rpcUrl"'); } else { try { new URL(config.rpcUrl); } catch (e) { errors.push('Invalid "rpcUrl". Must be a valid URL'); } } if (errors.length > 0) { throw new Error(`Configuration validation failed:\n${errors.join('\n')}`); } return config; }); }