@holographxyz/cli
Version:
Holograph operator CLI
308 lines (307 loc) • 17.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const inquirer = tslib_1.__importStar(require("inquirer"));
const fs = tslib_1.__importStar(require("fs-extra"));
const core_1 = require("@oclif/core");
const networks_1 = require("@holographxyz/networks");
const bytecodes_1 = require("../../utils/bytecodes");
const config_1 = require("../../utils/config");
const utils_1 = require("../../utils/utils");
const network_monitor_1 = require("../../utils/network-monitor");
const contract_deployment_1 = require("../../utils/contract-deployment");
const signature_1 = require("../../utils/signature");
const holograph_contract_events_1 = require("../../utils/holograph-contract-events");
const validation_1 = require("../../utils/validation");
async function getCodeFromFile(prompt) {
const codeFile = await (0, validation_1.checkStringFlag)(undefined, prompt);
if (await fs.pathExists(codeFile)) {
return (0, validation_1.validateBytes)(await fs.readFile(codeFile, 'utf8'));
}
throw new Error('The file "' + codeFile + '" does not exist.');
}
class Contract extends core_1.Command {
static hidden = false;
static description = 'Deploy a Holographable contract.';
static examples = [
'$ <%= config.bin %> <%= command.id %> --deploymentType="deployedTx" --tx="0xdb8b393dd18a71b386c8de75b87310c0c8ded0c57cf6b4c5bab52873d54d1e8a" --txNetwork="goerli"',
];
static flags = {
...contract_deployment_1.deploymentFlags,
};
/**
* Contract class variables
*/
networkMonitor;
// eslint-disable-next-line complexity
async run() {
this.log('Loading user configurations...');
const { userWallet, configFile, supportedNetworksOptions } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, true);
const { flags } = await this.parse(Contract);
this.log('User configurations loaded.');
let configHash;
let tx;
let txNetwork;
let deploymentConfig = {
config: {
contractType: '',
chainType: '',
salt: '',
byteCode: '',
initCode: '',
},
signature: {
r: '',
s: '',
v: 0,
},
signer: userWallet.address,
};
let deploymentConfigFile;
this.networkMonitor = new network_monitor_1.NetworkMonitor({
parent: this,
configFile,
debug: this.debug,
userWallet,
verbose: false,
});
core_1.CliUx.ux.action.start('Loading network RPC providers');
await this.networkMonitor.run(true);
core_1.CliUx.ux.action.stop();
let chainType;
let chainId;
let salt;
let bytecodeType;
const contractTypes = ['HolographERC20', 'HolographERC721'];
let contractType;
let contractTypeHash;
let byteCode;
let eventConfig = (0, holograph_contract_events_1.allEventsEnabled)();
let sourceInitCode = (0, utils_1.generateInitCode)(['bytes'], ['0x00']);
let initCode = (0, utils_1.generateInitCode)(['bytes'], [sourceInitCode]);
let tokenName;
let tokenSymbol;
let domainSeperator;
const domainVersion = '1';
let decimals;
let collectionName;
let collectionSymbol;
let royaltyBps;
let configHashBytes;
let sig;
let signature;
let needToSign = false;
const deploymentType = await (0, validation_1.checkDeploymentTypeFlag)(flags.deploymentType, 'Select the type of deployment to use');
switch (deploymentType) {
case contract_deployment_1.DeploymentType.deployedTx:
txNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.txNetwork, 'Select the network on which the transaction was executed');
tx = await (0, validation_1.checkTransactionHashFlag)(flags.tx, 'Enter the hash of transaction that deployed the original contract');
break;
case contract_deployment_1.DeploymentType.deploymentConfig:
deploymentConfigFile = await (0, validation_1.checkStringFlag)(flags.deploymentConfig, 'Enter the config file to use');
if (await fs.pathExists(deploymentConfigFile)) {
deploymentConfig = (await fs.readJson(deploymentConfigFile));
}
else {
throw new Error('The file "' + deploymentConfigFile + '" does not exist.');
}
break;
case contract_deployment_1.DeploymentType.createConfig:
chainType = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, undefined, 'Select the primary network of the contract (does not prepend chainId to tokenIds)');
chainId = '0x' + networks_1.networks[chainType].holographId.toString(16).padStart(8, '0');
deploymentConfig.config.chainType = chainId;
salt =
'0x' +
(0, utils_1.remove0x)(await (0, validation_1.checkTokenIdFlag)(undefined, 'Enter a bytes32 hash or number to use for salt hash')).padStart(64, '0');
deploymentConfig.config.salt = salt;
bytecodeType = await (0, validation_1.checkBytecodeTypeFlag)(undefined, 'Select the bytecode type to deploy');
contractType =
bytecodeType === bytecodes_1.BytecodeType.Custom
? await (0, validation_1.checkOptionFlag)(contractTypes, undefined, 'Select the contract type to create')
: bytecodeType === bytecodes_1.BytecodeType.SampleERC20
? 'HolographERC20'
: 'HolographERC721';
contractTypeHash = '0x' + utils_1.web3.utils.asciiToHex(contractType).slice(2).padStart(64, '0');
deploymentConfig.config.contractType = contractTypeHash;
byteCode =
bytecodeType === bytecodes_1.BytecodeType.Custom
? await getCodeFromFile('Provide the filename containing the hex encoded string of the bytecode you want to use')
: bytecodes_1.bytecodes[bytecodeType];
switch (contractType) {
case 'HolographERC20':
tokenName = await (0, validation_1.checkStringFlag)(undefined, 'Enter the token name to use');
tokenSymbol = await (0, validation_1.checkStringFlag)(undefined, 'Enter the token symbol to use');
domainSeperator = tokenName;
decimals = await (0, validation_1.checkNumberFlag)(undefined, 'Enter the number of decimals [0-18] to use. The recommended number is 18.');
if (decimals > 18 || decimals < 0) {
throw new Error('Invalid decimals was provided: ' + decimals.toString());
}
switch (bytecodeType) {
case bytecodes_1.BytecodeType.SampleERC20:
eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2]); // [HolographERC20Event.bridgeIn, HolographERC20Event.bridgeOut]
sourceInitCode = (0, utils_1.generateInitCode)(['address'], [userWallet.address]);
break;
case bytecodes_1.BytecodeType.Custom:
eventConfig = (0, holograph_contract_events_1.configureEvents)((await inquirer.prompt([
{
type: 'checkbox',
message: 'Select the events to enable',
name: 'erc20events',
choices: holograph_contract_events_1.HolographERC20Event,
},
])).erc20events);
sourceInitCode = await getCodeFromFile('Provide the filename containing the hex encoded string of the initCode you want to use');
break;
}
initCode = (0, utils_1.generateInitCode)(['string', 'string', 'uint8', 'uint256', 'string', 'string', 'bool', 'bytes'], [
tokenName,
tokenSymbol,
decimals,
eventConfig,
domainSeperator,
domainVersion,
false,
sourceInitCode,
]);
break;
case 'HolographERC721':
collectionName = await (0, validation_1.checkStringFlag)(undefined, 'Enter the collection name to use');
collectionSymbol = await (0, validation_1.checkStringFlag)(undefined, 'Enter the collection symbol to use');
royaltyBps = await (0, validation_1.checkNumberFlag)(undefined, 'Enter the percentage of royalty to collect in basepoints. (1 = 0.01%, 10000 = 100%)');
if (royaltyBps > 10000 || royaltyBps < 0) {
throw new Error('Invalid royalty basepoints was provided: ' + royaltyBps.toString());
}
switch (bytecodeType) {
case bytecodes_1.BytecodeType.CxipERC721:
eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2, 7]); // [HolographERC721Event.bridgeIn, HolographERC721Event.bridgeOut, HolographERC721Event.afterBurn]
sourceInitCode = (0, utils_1.generateInitCode)(['bytes32', 'address', 'bytes'], [
'0x' + utils_1.web3.utils.asciiToHex('CxipERC721').slice(2).padStart(64, '0'),
await this.networkMonitor.registryContract.address,
(0, utils_1.generateInitCode)(['address'], [userWallet.address]),
]);
break;
case bytecodes_1.BytecodeType.SampleERC721:
eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2, 7]); // [HolographERC721Event.bridgeIn, HolographERC721Event.bridgeOut, HolographERC721Event.afterBurn]
sourceInitCode = (0, utils_1.generateInitCode)(['address'], [userWallet.address]);
break;
case bytecodes_1.BytecodeType.Custom:
eventConfig = (0, holograph_contract_events_1.configureEvents)((await inquirer.prompt([
{
type: 'checkbox',
message: 'Select the events to enable',
name: 'erc721events',
choices: holograph_contract_events_1.HolographERC721Event,
},
])).erc721events);
sourceInitCode = await getCodeFromFile('Provide the filename containing the hex encoded string of the initCode you want to use');
break;
}
initCode = (0, utils_1.generateInitCode)(['string', 'string', 'uint16', 'uint256', 'bool', 'bytes'], [
collectionName,
collectionSymbol,
royaltyBps,
eventConfig,
false,
sourceInitCode,
]);
break;
}
deploymentConfig.config.byteCode = byteCode;
deploymentConfig.config.initCode = initCode;
configHash = (0, utils_1.sha3)('0x' +
deploymentConfig.config.contractType.slice(2) +
deploymentConfig.config.chainType.slice(2) +
deploymentConfig.config.salt.slice(2) +
(0, utils_1.sha3)(deploymentConfig.config.byteCode).slice(2) +
(0, utils_1.sha3)(deploymentConfig.config.initCode).slice(2) +
deploymentConfig.signer.slice(2));
configHashBytes = utils_1.web3.utils.hexToBytes(configHash);
needToSign = true;
break;
}
const targetNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.targetNetwork, 'Select the network on which the contract will be executed', txNetwork);
if (needToSign) {
sig = await this.networkMonitor.wallets[targetNetwork].signMessage(configHashBytes);
signature = (0, signature_1.strictECDSA)({
r: '0x' + sig.slice(2, 66),
s: '0x' + sig.slice(66, 130),
v: '0x' + sig.slice(130, 132),
});
deploymentConfig.signature.r = signature.r;
deploymentConfig.signature.s = signature.s;
deploymentConfig.signature.v = Number.parseInt(signature.v, 16);
}
if (deploymentType === contract_deployment_1.DeploymentType.deployedTx) {
core_1.CliUx.ux.action.start('Retrieving transaction details from "' + txNetwork + '" network');
const deploymentTransaction = await this.networkMonitor.providers[txNetwork].getTransaction(tx);
deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfigInput)(deploymentTransaction.data);
core_1.CliUx.ux.action.stop();
}
configHash = (0, utils_1.sha3)('0x' +
deploymentConfig.config.contractType.slice(2) +
deploymentConfig.config.chainType.slice(2) +
deploymentConfig.config.salt.slice(2) +
(0, utils_1.sha3)(deploymentConfig.config.byteCode).slice(2) +
(0, utils_1.sha3)(deploymentConfig.config.initCode).slice(2) +
deploymentConfig.signer.slice(2));
if (deploymentType !== contract_deployment_1.DeploymentType.deploymentConfig) {
const configFilePrompt = await inquirer.prompt([
{
name: 'shouldSave',
message: 'Would you like to export/save the deployment config file?',
type: 'confirm',
default: true,
},
]);
if (configFilePrompt.shouldSave) {
deploymentConfigFile = await (0, validation_1.checkStringFlag)(undefined, 'Enter the path and file where to save (ie ./deploymentConfig.json)');
await fs.ensureFile(deploymentConfigFile);
await fs.writeFile(deploymentConfigFile, JSON.stringify(deploymentConfig, undefined, 2), 'utf8');
this.log('File successfully saved to "' + deploymentConfigFile + '"');
}
}
core_1.CliUx.ux.action.start('Checking that contract is not already deployed on "' + targetNetwork + '" network');
const contractAddress = await this.networkMonitor.registryContract
.connect(this.networkMonitor.providers[targetNetwork])
.getContractTypeAddress(configHash);
core_1.CliUx.ux.action.stop();
if (contractAddress !== utils_1.zeroAddress) {
throw new Error('Contract already deployed at ' + contractAddress + ' on "' + targetNetwork + '" network');
}
const blockchainPrompt = await inquirer.prompt([
{
name: 'shouldContinue',
message: 'Next steps submit the transaction, would you like to proceed?',
type: 'confirm',
default: true,
},
]);
if (!blockchainPrompt.shouldContinue) {
throw new Error('Dropping command, no blockchain transactions executed');
}
core_1.CliUx.ux.action.start('Deploying contract');
const receipt = await this.networkMonitor.executeTransaction({
network: targetNetwork,
contract: this.networkMonitor.factoryContract.connect(this.networkMonitor.providers[targetNetwork]),
methodName: 'deployHolographableContract',
args: [deploymentConfig.config, deploymentConfig.signature, deploymentConfig.signer],
waitForReceipt: true,
});
core_1.CliUx.ux.action.stop();
if (receipt === null) {
throw new Error('failed to confirm that the transaction was mined');
}
else {
const logs = this.networkMonitor.decodeBridgeableContractDeployedEvent(receipt, this.networkMonitor.factoryAddress);
if (logs === undefined) {
throw new Error('failed to extract transfer event from transaction receipt');
}
else {
const deploymentAddress = logs[0];
this.log(`Contract has been deployed to address ${deploymentAddress} on ${targetNetwork} network`);
this.exit();
}
}
}
}
exports.default = Contract;