@omni-kit/omni-deployer
Version:
Deploy smart contracts across multiple chains using SuperchainFactory
231 lines (230 loc) • 12 kB
JavaScript
#!/usr/bin/env node
"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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const ethers_1 = require("ethers");
const config_1 = require("./config");
const compile_1 = require("./compile");
const artifact_1 = require("./artifact");
const utils_1 = require("./utils");
// ABI definition for interacting with the factory contract
const factoryAbi = [
'function deployContract(uint256[] calldata chainIds, bytes memory bytecode, bytes32 salt) external returns (address)',
'function deployHubAndSpokes(bytes memory hubBytecode, bytes memory spokeBytecode, bytes32 salt, uint256[] calldata spokeChainIds) external returns (address)',
'event ContractDeployed(address indexed contractAddress, uint256 indexed chainId)',
'event CrossChainMessageSent(uint256 indexed chainId, address indexed targetFactory)',
];
/**
* Checks if a contract exists at a given address.
* @param provider - Ethers provider.
* @param address - Address to check.
* @returns True if contract code exists at the address, false otherwise.
*/
function contractExists(provider, address) {
return __awaiter(this, void 0, void 0, function* () {
const code = yield provider.getCode(address);
return code !== '0x';
});
}
commander_1.program
.command('deploy [configPath]')
.alias('d')
.description('Deploy a contract across multiple chains using DeploymentFactory')
.action((configPath) => __awaiter(void 0, void 0, void 0, function* () {
try {
// Load deployment configuration
const config = yield (0, config_1.loadConfig)(configPath);
// Retrieve the private key from environment variables
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable is not set. Set it with: export PRIVATE_KEY=0xYourPrivateKey');
}
// Compile the contract and load its artifact
(0, compile_1.compileContract)();
const artifact = (0, artifact_1.loadArtifact)(config.contractName);
// Prepare bytecode and constructor arguments
const iface = new ethers_1.ethers.utils.Interface(artifact.abi);
const bytecode = artifact.bytecode.object;
const encodedArgs = config.constructorArgs && config.constructorArgs.length > 0
? iface.encodeDeploy(config.constructorArgs)
: '0x';
const deployBytecode = ethers_1.ethers.utils.hexConcat([bytecode, encodedArgs]);
const formattedSalt = (0, utils_1.formatSalt)(config.salt);
// Set up provider, wallet, and factory contract
const provider = new ethers_1.ethers.providers.JsonRpcProvider(config.rpcUrl);
const wallet = new ethers_1.ethers.Wallet(privateKey, provider);
const factory = new ethers_1.Contract(config.factoryContract, factoryAbi, wallet);
// Compute CREATE2 address
const computedAddress = (0, utils_1.computeCreate2Address)(config.factoryContract, formattedSalt, deployBytecode);
// Check if contract already exists at the computed address
if (yield contractExists(provider, computedAddress)) {
console.log(`Contract already deployed at ${computedAddress}. Use a different salt to deploy a new instance.`);
return;
}
// Estimate gas for the deployment transaction
let gasEstimate;
try {
gasEstimate = yield provider.estimateGas({
from: wallet.address,
to: config.factoryContract,
data: factory.interface.encodeFunctionData('deployContract', [
config.chains,
deployBytecode,
formattedSalt,
]),
});
console.log('Estimated gas:', gasEstimate.toString());
}
catch (error) {
console.warn('Gas estimation failed, using manual gas limit of 5,000,000');
gasEstimate = 5000000; // Fallback gas limit
}
// Deploy the contract
const tx = yield factory.deployContract(config.chains, deployBytecode, formattedSalt, {
gasLimit: gasEstimate,
});
const receipt = yield tx.wait();
// Log transaction details
console.log('Transaction hash:', receipt.transactionHash);
console.log('Gas used:', receipt.gasUsed.toString());
// Extract deployed contract address from logs
const factoryInterface = new ethers_1.ethers.utils.Interface(factoryAbi);
const event = receipt.logs.find((log) => {
try {
const parsed = factoryInterface.parseLog(log);
return parsed.name === 'ContractDeployed';
}
catch (_a) {
return false;
}
});
if (!event) {
throw new Error('FactoryContract not found. Check configuration parameters like Factory Contract address and RPC url');
}
const parsedEvent = factoryInterface.parseLog(event);
const deployedAddress = ethers_1.ethers.utils.getAddress(parsedEvent.args.contractAddress);
console.log('Deployed contract address on local chain:', deployedAddress);
// Verify CREATE2 address
if (deployedAddress.toLowerCase() !== computedAddress.toLowerCase()) {
console.warn('Warning: Local deployed address does not match computed CREATE2 address');
}
console.log('Contract deployment initiated across specified chains.');
}
catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}));
commander_1.program
.command('deploy-hs [configPath]')
.alias('hs')
.description('Deploy hub and spoke contracts across multiple chains using the same address')
.action((configPath) => __awaiter(void 0, void 0, void 0, function* () {
try {
// Load hub-spoke deployment configuration
const config = yield (0, config_1.loadHubSpokeConfig)(configPath);
// Retrieve the private key from environment variables
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable is not set. Set it with: export PRIVATE_KEY=0xYourPrivateKey');
}
// Compile the contracts and load artifacts
(0, compile_1.compileContract)();
const hubArtifact = (0, artifact_1.loadArtifact)(config.hubContract);
const spokeArtifact = (0, artifact_1.loadArtifact)(config.spokeContract);
// Prepare hub bytecode and constructor arguments
const hubIface = new ethers_1.ethers.utils.Interface(hubArtifact.abi);
const hubBytecode = hubArtifact.bytecode.object;
const hubEncodedArgs = config.hubConstructorArgs && config.hubConstructorArgs.length > 0
? hubIface.encodeDeploy(config.hubConstructorArgs)
: '0x';
const hubDeployBytecode = ethers_1.ethers.utils.hexConcat([hubBytecode, hubEncodedArgs]);
// Prepare spoke bytecode and constructor arguments
const spokeIface = new ethers_1.ethers.utils.Interface(spokeArtifact.abi);
const spokeBytecode = spokeArtifact.bytecode.object;
const spokeEncodedArgs = config.spokeConstructorArgs && config.spokeConstructorArgs.length > 0
? spokeIface.encodeDeploy(config.spokeConstructorArgs)
: '0x';
const spokeDeployBytecode = ethers_1.ethers.utils.hexConcat([spokeBytecode, spokeEncodedArgs]);
// Format the salt
const formattedSalt = (0, utils_1.formatSalt)(config.salt);
// Set up provider, wallet, and factory contract
const provider = new ethers_1.ethers.providers.JsonRpcProvider(config.rpcUrl);
const wallet = new ethers_1.ethers.Wallet(privateKey, provider);
const factory = new ethers_1.Contract(config.factoryContract, factoryAbi, wallet);
// Compute CREATE3 address
const computedAddress = (0, utils_1.computeCreate3Address)(config.factoryContract, formattedSalt);
// Check if contract already exists at the computed address
if (yield contractExists(provider, computedAddress)) {
console.log(`Contract already deployed at ${computedAddress}. Use a different salt to deploy a new instance.`);
return;
}
// Estimate gas for the hub-spoke deployment
let gasEstimate;
try {
gasEstimate = yield provider.estimateGas({
from: wallet.address,
to: config.factoryContract,
data: factory.interface.encodeFunctionData('deployHubAndSpokes', [
hubDeployBytecode,
spokeDeployBytecode,
formattedSalt,
config.chains, // Changed from config.spokeChains to config.chains
]),
});
console.log('Estimated gas:', gasEstimate.toString());
}
catch (error) {
console.warn('Gas estimation failed, using manual gas limit of 6,000,000');
gasEstimate = 6000000; // Fallback gas limit for hub-spoke deployment
}
// Deploy the hub and spoke contracts
const tx = yield factory.deployHubAndSpokes(hubDeployBytecode, spokeDeployBytecode, formattedSalt, config.chains, // Changed from config.spokeChains to config.chains
{
gasLimit: gasEstimate,
});
const receipt = yield tx.wait();
// Log transaction details
console.log('Transaction hash:', receipt.transactionHash);
console.log('Gas used:', receipt.gasUsed.toString());
// Extract deployed hub contract address from logs
const factoryInterface = new ethers_1.ethers.utils.Interface(factoryAbi);
const event = receipt.logs.find((log) => {
try {
const parsed = factoryInterface.parseLog(log);
return parsed.name === 'ContractDeployed';
}
catch (_a) {
return false;
}
});
if (!event) {
throw new Error('FactoryContract not found. Check configuration parameters like Factory Contract address and RPC url.');
}
const parsedEvent = factoryInterface.parseLog(event);
const hubAddress = ethers_1.ethers.utils.getAddress(parsedEvent.args.contractAddress);
console.log('Deployed hub contract address on local chain:', hubAddress);
// Verify CREATE3 address
if (hubAddress.toLowerCase() !== computedAddress.toLowerCase()) {
console.warn('Warning: Local deployed address does not match computed CREATE3 address');
}
// Log deployment information
console.log(`Hub contract (${config.hubContract}) deployed on chain ID ${yield provider.getNetwork().then(n => n.chainId)}`);
console.log(`Spoke contract (${config.spokeContract}) deployment initiated on chains:`, config.chains); // Changed from config.spokeChains to config.chains
console.log('All contracts will be deployed to the same address:', computedAddress);
}
catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}));
commander_1.program.parse(process.argv);