UNPKG

@holographxyz/cli

Version:
185 lines (184 loc) 10.8 kB
"use strict"; 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 node_path_1 = tslib_1.__importDefault(require("node:path")); const core_1 = require("@oclif/core"); const units_1 = require("@ethersproject/units"); const contracts_1 = require("@ethersproject/contracts"); const bignumber_1 = require("@ethersproject/bignumber"); const networks_1 = require("@holographxyz/networks"); const config_1 = require("../../utils/config"); const network_monitor_1 = require("../../utils/network-monitor"); const validation_1 = require("../../utils/validation"); const utils_1 = require("../../utils/utils"); class BridgeNFT extends core_1.Command { static description = 'Bridge a Holographable NFT from one network to another.'; static examples = [ '$ <%= config.bin %> <%= command.id %> --sourceNetwork="goerli" --destinationNetwork="fuji" --collectionAddress="0x1318d3420b0169522eB8F3EF0830aceE700A2eda" --tokenId="0x01"', ]; static flags = { collectionAddress: core_1.Flags.string({ description: 'The address of the collection smart contract', parse: validation_1.validateContractAddress, multiple: false, required: false, }), tokenId: core_1.Flags.string({ description: 'The token ID of the NFT to beam', parse: validation_1.validateTokenIdInput, multiple: false, required: false, }), sourceNetwork: core_1.Flags.string({ description: 'The source network from which to beam', parse: validation_1.validateNetwork, options: networks_1.supportedShortNetworks, multiple: false, required: false, }), destinationNetwork: core_1.Flags.string({ description: 'The destination network which to beam to', parse: validation_1.validateNetwork, options: networks_1.supportedShortNetworks, multiple: false, required: false, }), }; async checkIfContractExists(network, provider, contractAddress, throwError = true) { const code = await provider.getCode(contractAddress); if (code === '0x' || code === '') { if (throwError) { this.error(`Contract at ${contractAddress} does not exist on ${networks_1.networks[network].shortKey} network`); } else { this.log(`Contract at ${contractAddress} does not exist on ${networks_1.networks[network].shortKey} network`); } return false; } return true; } /** * BridgeNFT class variables */ networkMonitor; async run() { this.log('Loading user configurations...'); const { environment, userWallet, configFile, supportedNetworksOptions } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, true); const { flags } = await this.parse(BridgeNFT); this.log('User configurations loaded'); const sourceNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.sourceNetwork, 'Select the source network from which to beam'); const destinationNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.destinationNetwork, 'Select the destination network which to beam to', sourceNetwork); const collectionAddress = await (0, validation_1.checkContractAddressFlag)(flags.collectionAddress, 'Enter the address of the collection smart contract'); const tokenId = await (0, validation_1.checkTokenIdFlag)(flags.tokenId, 'Enter the token ID of the NFT to beam'); this.networkMonitor = new network_monitor_1.NetworkMonitor({ parent: this, configFile, networks: [sourceNetwork, destinationNetwork], 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(); // Check if the contract is deployed on the source chain and not on the destination chain core_1.CliUx.ux.action.start('Checking if the collection is deployed on both source and destination networks'); const deployedOnSourceChain = await this.checkIfContractExists(sourceNetwork, this.networkMonitor.providers[sourceNetwork], collectionAddress, false); const deployedOnDestinationChain = await this.checkIfContractExists(destinationNetwork, this.networkMonitor.providers[destinationNetwork], collectionAddress, false); core_1.CliUx.ux.action.stop(); if (!deployedOnSourceChain || !deployedOnDestinationChain) { if (!deployedOnSourceChain) { this.networkMonitor.structuredLog(sourceNetwork, `Collection does not exist on ${networks_1.networks[sourceNetwork].shortKey} network.`); } if (!deployedOnDestinationChain) { this.networkMonitor.structuredLog(destinationNetwork, `Collection does not exist on ${networks_1.networks[destinationNetwork].shortKey} network.`); } this.log('You can deploy the missing contracts with the "create:contract" or "bridge:contract" command.'); this.exit(); } core_1.CliUx.ux.action.start('Checking if collection is holographed'); const holographedContract = await this.networkMonitor.registryContract.isHolographedContract(collectionAddress); core_1.CliUx.ux.action.stop(); if (!holographedContract) { this.log('Collection is not an official holographed contract. You cannot bridge it through this command. Alternatively, check if you are using the correct environment.'); this.exit(); } core_1.CliUx.ux.action.start('Retrieving collection smart contract'); const collectionABI = await fs.readJson(node_path_1.default.join(__dirname, `../../abi/${environment}/HolographERC721.json`)); const collection = new contracts_1.Contract(collectionAddress, collectionABI, this.networkMonitor.providers[sourceNetwork]); core_1.CliUx.ux.action.stop(); this.log(`tokenId is ${tokenId}`); core_1.CliUx.ux.action.start('Checking if token ID exists on ' + sourceNetwork + ' network.'); const tokenExists = await collection.exists(tokenId); core_1.CliUx.ux.action.stop(); if (!tokenExists) { this.log('Token does not exist.'); this.exit(); } const data = (0, utils_1.generateInitCode)(['address', 'address', 'uint256'], [userWallet.address, userWallet.address, tokenId]); const TESTGASLIMIT = bignumber_1.BigNumber.from('10000000'); let payload = await this.networkMonitor.bridgeContract .connect(this.networkMonitor.providers[sourceNetwork]) .callStatic.getBridgeOutRequestPayload(networks_1.networks[destinationNetwork].holographId, collectionAddress, '0x' + 'ff'.repeat(32), '0x' + 'ff'.repeat(32), data); let estimatedGas = TESTGASLIMIT.sub(await this.networkMonitor.operatorContract .connect(this.networkMonitor.providers[destinationNetwork]) .callStatic.jobEstimator(payload, { gasLimit: TESTGASLIMIT })); const gasPricing = this.networkMonitor.gasPrices[destinationNetwork]; let gasPrice = gasPricing.isEip1559 ? gasPricing.maxFeePerGas : gasPricing.gasPrice; gasPrice = gasPrice.add(gasPrice.div(bignumber_1.BigNumber.from('4'))); payload = await this.networkMonitor.bridgeContract .connect(this.networkMonitor.providers[sourceNetwork]) .callStatic.getBridgeOutRequestPayload(networks_1.networks[destinationNetwork].holographId, collectionAddress, estimatedGas, // allow LZ module to set gas price // '0x' + '00'.repeat(32), gasPrice, data); const fees = await this.networkMonitor.bridgeContract .connect(this.networkMonitor.providers[sourceNetwork]) .callStatic.getMessageFee(networks_1.networks[destinationNetwork].holographId, estimatedGas, gasPrice, payload); const total = fees[0].add(fees[1]); estimatedGas = TESTGASLIMIT.sub(await this.networkMonitor.operatorContract .connect(this.networkMonitor.providers[destinationNetwork]) .callStatic.jobEstimator(payload, { value: total, gasLimit: TESTGASLIMIT })); this.log('hlg fee', (0, units_1.formatUnits)(fees[0], 'ether'), 'ether'); this.log('lz fee', (0, units_1.formatUnits)(fees[1], 'ether'), 'ether'); this.log('lz gasPrice', (0, units_1.formatUnits)(fees[2], 'gwei'), 'GWEI'); this.log('our estimated gasPrice', (0, units_1.formatUnits)(gasPrice, 'gwei'), 'GWEI'); this.log('estimated gas usage', estimatedGas.toNumber()); 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) { this.log('Dropping command, no blockchain transactions executed'); this.exit(); } core_1.CliUx.ux.action.start('Making beam request...'); const receipt = await this.networkMonitor.executeTransaction({ network: sourceNetwork, contract: this.networkMonitor.bridgeContract.connect(this.networkMonitor.providers[destinationNetwork]), methodName: 'bridgeOutRequest', args: [networks_1.networks[destinationNetwork].holographId, collectionAddress, estimatedGas, gasPrice, data], waitForReceipt: true, value: total.add(total.div(bignumber_1.BigNumber.from('4'))), }); core_1.CliUx.ux.action.stop(); if (receipt === null) { throw new Error('failed to confirm that the transaction was mined'); } else { const jobHash = this.networkMonitor.decodeCrossChainMessageSentEvent(receipt, this.networkMonitor.operatorAddress); if (jobHash === undefined) { this.log('Failed to extract cross-chain job hash transaction receipt'); } this.log(`Cross-chain beaming from ${networks_1.networks[sourceNetwork].shortKey} network, to ${networks_1.networks[destinationNetwork].shortKey} network has started under job hash ${jobHash}`); } this.exit(); } } exports.default = BridgeNFT;