@holographxyz/cli
Version:
Holograph operator CLI
186 lines (185 loc) • 9.32 kB
JavaScript
;
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;