UNPKG

@holographxyz/cli

Version:
186 lines (185 loc) 9.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const inquirer = tslib_1.__importStar(require("inquirer")); const core_1 = require("@oclif/core"); const ethers_1 = require("ethers"); const networks_1 = require("@holographxyz/networks"); const config_1 = require("../../utils/config"); const network_monitor_1 = require("../../utils/network-monitor"); const utils_1 = require("../../utils/utils"); const validation_1 = require("../../utils/validation"); const operator_job_1 = require("../../utils/operator-job"); const healthcheck_1 = require("../../base-commands/healthcheck"); class Recover extends operator_job_1.OperatorJobAwareCommand { static description = 'Attempt to re-run/recover a specific job.'; static examples = ['$ <%= config.bin %> <%= command.id %> --network="ethereumTestnetGoerli" --tx="0x..."']; static flags = { network: core_1.Flags.string({ description: 'The network on which the transaction was executed', options: networks_1.supportedShortNetworks, }), tx: core_1.Flags.string({ description: 'The hash of transaction that we want to attempt to execute', }), ...healthcheck_1.HealthCheck.flags, }; /** * Command Entry Point */ async run() { this.log('Loading user configurations...'); const { userWallet, configFile, supportedNetworksOptions } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, true); this.log('User configurations loaded.'); const { flags } = await this.parse(Recover); const network = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.network, 'Select the network to extract transaction details from'); const tx = await (0, validation_1.checkTransactionHashFlag)(flags.tx, 'Enter the hash of transaction from which to extract recovery data from'); 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(); core_1.CliUx.ux.action.start('Retrieving transaction details from ' + network + ' network'); const transaction = await this.networkMonitor.getTransaction({ transactionHash: tx, network, canFail: true, attempts: 30, interval: 500, }); core_1.CliUx.ux.action.stop(); if (transaction === null) { this.networkMonitor.structuredLog(network, 'Could not retrieve the transaction'); // eslint-disable-next-line no-process-exit, unicorn/no-process-exit process.exit(); } else { await this.processTransaction(network, transaction); } } /** * Process a transaction and attempt to either handle the bridge out or bridge in depending on the event emitted */ async processTransaction(network, transaction) { this.networkMonitor.structuredLog(network, `Processing transaction ${transaction.hash} at block ${transaction.blockNumber}`); const to = transaction.to?.toLowerCase(); const from = transaction.from?.toLowerCase(); switch (to) { case this.networkMonitor.bridgeAddress: { await this.handleBridgeOutEvent(transaction, network); break; } default: if (from === this.networkMonitor.LAYERZERO_RECEIVERS[network]) { await this.handleAvailableOperatorJobEvent(transaction, network); } else { this.networkMonitor.structuredLog(network, `Function processTransaction stumbled on an unknown transaction ${transaction.hash}`); } } } /** * Handles the event emitted by the bridge contract when a token is bridged out */ async handleBridgeOutEvent(transaction, network) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 30, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking if a bridge request was made at tx: ${transaction.hash}`); const operatorJobPayload = this.networkMonitor.decodePacketEvent(receipt) ?? this.networkMonitor.decodeLzPacketEvent(receipt); const operatorJobHash = operatorJobPayload === undefined ? undefined : (0, utils_1.sha3)(operatorJobPayload); if (operatorJobHash === undefined) { this.networkMonitor.structuredLog(network, `Could not extract cross-chain packet for ${transaction.hash}`); } else { const bridgeTransaction = this.networkMonitor.bridgeContract.interface.parseTransaction(transaction); const chainId = (await this.networkMonitor.interfacesContract.getChainId(2, ethers_1.BigNumber.from(bridgeTransaction.args.toChain), 1)).toNumber(); let destinationNetwork; const networkNames = networks_1.supportedNetworks; for (let i = 0, l = networkNames.length; i < l; i++) { const n = networks_1.networks[networkNames[i]]; if (n.chain === chainId) { destinationNetwork = networkNames[i]; break; } } if (destinationNetwork === undefined) { throw new Error('Failed to identify destination network from the bridge-out request'); } this.networkMonitor.structuredLog(network, `Bridge-Out transaction type: ${bridgeTransaction.name} -->> ${bridgeTransaction.args}`); await this.executePayload(destinationNetwork, operatorJobPayload); } } } /** * Handles the event emitted by the operator contract when a job is available and can be executed */ async handleAvailableOperatorJobEvent(transaction, network) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 30, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking if Operator was sent a bridge job via the LayerZero Relayer at tx: ${transaction.hash}`); const operatorJobEvent = this.networkMonitor.decodeAvailableOperatorJobEvent(receipt, this.networkMonitor.operatorAddress); const operatorJobPayload = operatorJobEvent === undefined ? undefined : operatorJobEvent[1]; const operatorJobHash = operatorJobPayload === undefined ? undefined : operatorJobEvent[0]; if (operatorJobHash === undefined) { this.networkMonitor.structuredLog(network, `Could not extract relayer available job for ${transaction.hash}`); } else { this.networkMonitor.structuredLog(network, `HolographOperator received a new bridge job. The job payload hash is ${operatorJobHash}. The job payload is ${operatorJobPayload}`); const bridgeTransaction = this.networkMonitor.bridgeContract.interface.parseTransaction({ data: operatorJobPayload, }); this.networkMonitor.structuredLog(network, `Bridge-In transaction type: ${bridgeTransaction.name} -->> ${bridgeTransaction.args}`); await this.executePayload(network, operatorJobPayload); } } } /** * Execute the payload on the destination network */ async executePayload(network, payload) { // If the operator is in listen mode, payloads will not be executed // If the operator is in manual mode, the payload must be manually executed // If the operator is in auto mode, the payload will be executed automatically const operatorPrompt = await inquirer.prompt([ { name: 'shouldContinue', message: `Transaction on ${network} is ready for execution, would you like to recover it?\n`, type: 'confirm', default: false, }, ]); const operate = operatorPrompt.shouldContinue; if (operate) { await this.networkMonitor.executeTransaction({ network, contract: this.networkMonitor.operatorContract, methodName: 'executeJob', args: [payload], }); } // eslint-disable-next-line no-process-exit, unicorn/no-process-exit process.exit(); } } exports.default = Recover;