UNPKG

@holographxyz/cli

Version:
818 lines 54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const dotenv_1 = tslib_1.__importDefault(require("dotenv")); dotenv_1.default.config(); const core_1 = require("@oclif/core"); const color_1 = tslib_1.__importDefault(require("@oclif/color")); const bignumber_1 = require("@ethersproject/bignumber"); const bytes_1 = require("@ethersproject/bytes"); const environment_1 = require("@holographxyz/environment"); const networks_1 = require("@holographxyz/networks"); const config_1 = require("../../utils/config"); const contract_deployment_1 = require("../../utils/contract-deployment"); const utils_1 = require("../../utils/utils"); const bridge_1 = require("../../utils/bridge"); const network_monitor_1 = require("../../utils/network-monitor"); const healthcheck_1 = require("../../base-commands/healthcheck"); class Indexer extends healthcheck_1.HealthCheck { static hidden = true; static LAST_BLOCKS_FILE_NAME = 'indexer-blocks.json'; static description = 'Listen for EVM events and update database network status'; static examples = [ '$ <%= config.bin %> <%= command.id %> --networks ethereumTestnetGoerli polygonTestnet avalancheTestnet', ]; static flags = { host: core_1.Flags.string({ description: 'The host to send data to', char: 'h', default: 'http://localhost:9001', }), ...network_monitor_1.networksFlag, ...network_monitor_1.warpFlag, ...healthcheck_1.HealthCheck.flags, }; // API Params BASE_URL; JWT; DELAY = 20000; apiColor = color_1.default.keyword('orange'); errorColor = color_1.default.keyword('red'); networkMonitor; dbJobMap = {}; environment; numericSort(a, b) { return a - b; } numberfy(arr) { const numbers = []; for (const a of arr) { numbers.push(Number.parseInt(a, 10)); } return numbers; } /** * Command Entry Point */ async run() { this.log(`Operator command has begun!!!`); const { flags } = await this.parse(Indexer); this.BASE_URL = flags.host; const enableHealthCheckServer = flags.healthCheck; const healthCheckPort = flags.healthCheckPort; this.log('Loading user configurations...'); const { environment, configFile } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, false); this.log('User configurations loaded.'); this.environment = environment; if (this.environment === environment_1.Environment.localhost || this.environment === environment_1.Environment.experimental) { this.log(`Skiping API authentication for ${environment_1.Environment[this.environment]} environment`); } else { this.log(this.apiColor(`API: Authenticating with ${this.BASE_URL}`)); let res; try { res = await axios_1.default.post(`${this.BASE_URL}/v1/auth/operator`, { hash: process.env.OPERATOR_API_KEY, }); this.debug(JSON.stringify(res.data)); } catch (error) { this.error(error.message); } this.JWT = res.data.accessToken; if (typeof this.JWT === 'undefined') { this.error('Failed to authorize as an operator'); } this.debug(`process.env.OPERATOR_API_KEY = ${process.env.OPERATOR_API_KEY}`); this.debug(`this.JWT = ${this.JWT}`); } this.networkMonitor = new network_monitor_1.NetworkMonitor({ parent: this, configFile, networks: flags.networks, debug: this.debug, processTransactions: this.processTransactions, lastBlockFilename: 'indexer-blocks.json', warp: flags.warp, }); // Indexer always synchronizes missed blocks this.networkMonitor.latestBlockHeight = await this.networkMonitor.loadLastBlocks(this.config.configDir); core_1.CliUx.ux.action.start(`Starting indexer`); await this.networkMonitor.run(!(flags.warp > 0), undefined, this.filterBuilder); core_1.CliUx.ux.action.stop('🚀'); // Start health check server on port 6000 or healthCheckPort // Can be used to monitor that the operator is online and running if (enableHealthCheckServer) { await this.config.runHook('healthCheck', { networkMonitor: this.networkMonitor, healthCheckPort }); } this.processDBJobs(); } async filterBuilder() { this.networkMonitor.filters = [ { type: network_monitor_1.FilterType.from, match: this.networkMonitor.LAYERZERO_RECEIVERS, networkDependant: true, }, { type: network_monitor_1.FilterType.to, match: this.networkMonitor.bridgeAddress, networkDependant: false, }, { type: network_monitor_1.FilterType.to, match: this.networkMonitor.factoryAddress, networkDependant: false, }, { type: network_monitor_1.FilterType.to, match: this.networkMonitor.operatorAddress, networkDependant: false, }, { type: network_monitor_1.FilterType.functionSig, match: (0, utils_1.functionSignature)('cxipMint(uint224,uint8,string)'), networkDependant: false, }, ]; return Promise.resolve(); } async processDBJob(timestamp, job) { this.networkMonitor.structuredLog(job.network, job.message); let res; if (this.environment === environment_1.Environment.localhost || this.environment === environment_1.Environment.experimental) { this.networkMonitor.structuredLog(job.network, `Should make an API GET call with query ${job.query}`, job.tags); await job.callback.bind(this)('', ...job.arguments); this.processDBJobs(); } else { try { res = await axios_1.default.get(job.query, { maxRedirects: 0, headers: { Authorization: `Bearer ${this.JWT}`, 'Content-Type': 'application/json', }, }); this.networkMonitor.structuredLog(job.network, `GET response ${JSON.stringify(res.data)}`, job.tags); await job.callback.bind(this)(res.data, ...job.arguments); this.processDBJobs(); } catch (error) { this.networkMonitor.structuredLogError(job.network, error.response.data, [ ...job.tags, this.errorColor(`Failed to GET ${job.query}`), ]); // one second interval await (0, utils_1.sleep)(1000); this.processDBJobs(timestamp, job); } } } processDBJobs(timestamp, job) { if (timestamp !== undefined && job !== undefined) { if (!(timestamp in this.dbJobMap)) { this.dbJobMap[timestamp] = []; } job.attempts += 1; this.networkMonitor.structuredLog(job.network, `JOB ${job.query} is being executed with attempt ${job.attempts}`, job.tags); if (job.attempts >= 10) { // we have exhausted attempts, need to drop it entirely this.networkMonitor.structuredLog(job.network, `Failed to execute API query ${job.query}. Arguments were ${JSON.stringify(job.arguments, undefined, 2)}`, job.tags); } else if (job.attempts >= 9) { // push to end of array as a final attempt this.dbJobMap[timestamp].push(job); } else { this.dbJobMap[timestamp].unshift(job); } } const timestamps = this.numberfy(Object.keys(this.dbJobMap)); if (timestamps.length > 0) { timestamps.sort(this.numericSort); const timestamp = timestamps[0]; if (this.dbJobMap[timestamp].length > 0) { const job = this.dbJobMap[timestamp].shift(); this.processDBJob(timestamp, job); } else { delete this.dbJobMap[timestamp]; setTimeout(this.processDBJobs.bind(this), 1000); } } else { setTimeout(this.processDBJobs.bind(this), 1000); } } async processTransactions(job, transactions) { if (transactions.length > 0) { for (const transaction of transactions) { const tags = []; tags.push(transaction.blockNumber, this.networkMonitor.randomTag()); this.networkMonitor.structuredLog(job.network, `Processing transaction ${transaction.hash} at block ${transaction.blockNumber}`, tags); const to = transaction.to?.toLowerCase(); const from = transaction.from?.toLowerCase(); const functionSig = transaction.data?.slice(0, 10); switch (to) { case this.networkMonitor.factoryAddress: { this.networkMonitor.structuredLog(job.network, `handleContractDeployedEvent ${networks_1.networks[job.network].explorer}/tx/${transaction.hash}`, tags); await this.handleContractDeployedEvent(transaction, job.network, tags); break; } case this.networkMonitor.bridgeAddress: { this.networkMonitor.structuredLog(job.network, `handleBridgeOutEvent ${networks_1.networks[job.network].explorer}/tx/${transaction.hash}`, tags); await this.handleBridgeOutEvent(transaction, job.network, tags); break; } case this.networkMonitor.operatorAddress: { this.networkMonitor.structuredLog(job.network, `handleBridgeInEvent ${networks_1.networks[job.network].explorer}/tx/${transaction.hash}`, tags); await this.handleBridgeInEvent(transaction, job.network, tags); break; } default: if (from === this.networkMonitor.LAYERZERO_RECEIVERS[job.network]) { this.networkMonitor.structuredLog(job.network, `handleAvailableOperatorJobEvent ${networks_1.networks[job.network].explorer}/tx/${transaction.hash}`, tags); await this.handleAvailableOperatorJobEvent(transaction, job.network, tags); } else if (functionSig === (0, utils_1.functionSignature)('cxipMint(uint224,uint8,string)')) { this.networkMonitor.structuredLog(job.network, `handleMintEvent ${networks_1.networks[job.network].explorer}/tx/${transaction.hash}`, tags); await this.handleMintEvent(transaction, job.network, tags); } else { this.networkMonitor.structuredLog(job.network, `irrelevant transaction ${transaction.hash}`, tags); } } } } } async handleContractDeployedEvent(transaction, network, tags) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 10, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking for deployment details`, tags); const deploymentEvent = this.networkMonitor.decodeBridgeableContractDeployedEvent(receipt, this.networkMonitor.factoryAddress); if (deploymentEvent === undefined) { this.networkMonitor.structuredLog(network, `No BridgeableContractDeployed event found`, tags); } else { this.networkMonitor.structuredLog(network, `Decoding DeploymentConfig`, tags); const deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfigInput)(transaction.data); const deploymentHash = (0, contract_deployment_1.deploymentConfigHash)(deploymentConfig); const contractAddress = (0, contract_deployment_1.create2address)(deploymentConfig, this.networkMonitor.factoryAddress); if (deploymentHash !== deploymentEvent[1]) { throw new Error(`DeploymentConfig hashes ${deploymentHash} and ${deploymentEvent[1]} do not match!`); } if (contractAddress !== deploymentEvent[0]) { throw new Error(`Deployment addresses ${contractAddress} and ${deploymentEvent[0]} do not match!`); } this.networkMonitor.structuredLog(network, `updateDeployedContract`, tags); await this.updateDeployedContract(transaction, network, contractAddress, deploymentEvent, deploymentConfig, tags); } } else { this.networkMonitor.structuredLog(network, `Transaction failed, ignoring it`, tags); } } async handleMintEvent(transaction, network, tags) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 10, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking for mint details`, tags); const holographableContractAddress = transaction.to; const erc721TransferEvent = this.networkMonitor.decodeErc721TransferEvent(receipt, holographableContractAddress); if (erc721TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `No Transfer event found`, tags); } else { this.networkMonitor.structuredLog(network, `Decoding contractType`, tags); const slot = await this.networkMonitor.providers[network].getStorageAt(holographableContractAddress, (0, utils_1.storageSlot)('eip1967.Holograph.contractType')); const contractType = (0, utils_1.toAscii)(slot); this.networkMonitor.structuredLog(network, `updateMintedERC721`, tags); await this.updateMintedERC721(transaction, network, contractType, holographableContractAddress, erc721TransferEvent, tags); } } else { this.networkMonitor.structuredLog(network, `Transaction failed, ignoring it`, tags); } } async handleBridgeInEvent(transaction, network, tags) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 10, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking for executeJob function`, tags); const parsedTransaction = this.networkMonitor.operatorContract.interface.parseTransaction(transaction); if (parsedTransaction.name === 'executeJob') { this.networkMonitor.structuredLog(network, `Extracting bridgeInRequest from transaction`, tags); const args = Object.values(parsedTransaction.args); const operatorJobPayload = args === undefined ? undefined : args[0]; const operatorJobHash = operatorJobPayload === undefined ? undefined : (0, utils_1.sha3)(operatorJobPayload); if (operatorJobHash === undefined) { this.networkMonitor.structuredLog(network, `Could not find bridgeInRequest in ${transaction.hash}`, tags); } else { this.networkMonitor.structuredLog(network, `Decoding bridgeInRequest`, tags); const bridgeTransaction = this.networkMonitor.bridgeContract.interface.parseTransaction({ data: operatorJobPayload }); if (bridgeTransaction === null) { this.networkMonitor.structuredLog(network, `Could not decode bridgeInRequest in ${transaction.hash}`, tags); } else { this.networkMonitor.structuredLog(network, `Parsing bridgeInRequest data`, tags); const bridgeIn = bridgeTransaction.args; const fromNetwork = (0, networks_1.getNetworkByHolographId)(bridgeIn.fromChain).key; const toNetwork = network; const bridgeInPayload = bridgeIn.bridgeInPayload; const holographableContractAddress = bridgeIn.holographableContract.toLowerCase(); if (holographableContractAddress === this.networkMonitor.factoryAddress) { this.networkMonitor.structuredLog(network, `BridgeInRequest identified as contract deployment`, tags); this.networkMonitor.structuredLog(network, `Extracting deployment details`, tags); const deploymentEvent = this.networkMonitor.decodeBridgeableContractDeployedEvent(receipt, this.networkMonitor.factoryAddress); if (deploymentEvent === undefined) { this.networkMonitor.structuredLog(network, `Failed extracting deployment details from BridgeableContractDeployed event`, tags); } else { this.networkMonitor.structuredLog(network, `Decoding DeploymentConfig`, tags); const deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfig)(bridgeInPayload); const deploymentHash = (0, contract_deployment_1.deploymentConfigHash)(deploymentConfig); const contractAddress = (0, contract_deployment_1.create2address)(deploymentConfig, this.networkMonitor.factoryAddress); if (deploymentHash !== deploymentEvent[1]) { throw new Error(`DeploymentConfig hashes ${deploymentHash} and ${deploymentEvent[1]} do not match!`); } if (contractAddress !== deploymentEvent[0]) { throw new Error(`Deployment addresses ${contractAddress} and ${deploymentEvent[0]} do not match!`); } this.networkMonitor.structuredLog(network, `updateBridgedContract`, tags); await this.updateBridgedContract('in', transaction, network, fromNetwork, contractAddress, deploymentEvent, deploymentConfig, operatorJobHash, tags); } } else { this.networkMonitor.structuredLog(network, `Decoding contractType`, tags); const slot = await this.networkMonitor.providers[network].getStorageAt(holographableContractAddress, (0, utils_1.storageSlot)('eip1967.Holograph.contractType')); const contractType = (0, utils_1.toAscii)(slot); if (contractType === 'HolographERC20') { this.networkMonitor.structuredLog(network, `BridgeInRequest identified as ERC20 transfer`, tags); // BRIDGE IN ERC20 TOKENS const erc20BeamInfo = (0, bridge_1.decodeBridgeInErc20Args)(bridgeInPayload); const erc20TransferEvent = this.networkMonitor.decodeErc20TransferEvent(receipt, holographableContractAddress); if (erc20TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `Could not find a valid ERC20 Transfer event`, tags); } else { this.networkMonitor.structuredLog(network, `updateBridgedERC20`, tags); await this.updateBridgedERC20('in', transaction, network, fromNetwork, holographableContractAddress, erc20TransferEvent, erc20BeamInfo, operatorJobHash, tags); } } else if (contractType === 'HolographERC721') { this.networkMonitor.structuredLog(network, `BridgeInRequest identified as ERC721 transfer`, tags); // BRIDGE IN ERC721 NFT const erc721BeamInfo = (0, bridge_1.decodeBridgeInErc721Args)(bridgeInPayload); const erc721TransferEvent = this.networkMonitor.decodeErc721TransferEvent(receipt, holographableContractAddress); if (erc721TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `Could not find a valid ERC721 Transfer event`, tags); } else { this.networkMonitor.structuredLog(network, `updateBridgedERC721`, tags); await this.updateBridgedERC721('in', transaction, network, fromNetwork, toNetwork, contractType, holographableContractAddress, erc721TransferEvent, erc721BeamInfo, operatorJobHash, tags); } } else { this.networkMonitor.structuredLog(network, `unknown BridgeInRequest contractType`, tags); } } } } } else { this.networkMonitor.structuredLog(network, `Function call was ${parsedTransaction.name} and not executeJob`, tags); } } else { this.networkMonitor.structuredLog(network, `Transaction failed, ignoring it`, tags); } } async handleBridgeOutEvent(transaction, network, tags) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 10, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking for job hash`, tags); let operatorJobHash; let operatorJobPayload; let args; switch (this.environment) { case environment_1.Environment.localhost: operatorJobHash = this.networkMonitor.decodeCrossChainMessageSentEvent(receipt, this.networkMonitor.operatorAddress); if (operatorJobHash !== undefined) { args = this.networkMonitor.decodeLzEvent(receipt, this.networkMonitor.lzEndpointAddress[network]); if (args !== undefined) { operatorJobPayload = args[2]; } } break; default: operatorJobHash = this.networkMonitor.decodeCrossChainMessageSentEvent(receipt, this.networkMonitor.operatorAddress); if (operatorJobHash !== undefined) { operatorJobPayload = this.networkMonitor.decodeLzPacketEvent(receipt); } break; } if (operatorJobHash === undefined) { this.networkMonitor.structuredLog(network, `No CrossChainMessageSent event found`, tags); } else { // check that operatorJobPayload and operatorJobHash are the same if ((0, utils_1.sha3)(operatorJobPayload) !== operatorJobHash) { throw new Error('The hashed operatorJobPayload does not equal operatorJobHash!'); } const bridgeTransaction = this.networkMonitor.bridgeContract.interface.parseTransaction(transaction); if (bridgeTransaction.name === 'bridgeOutRequest') { const bridgeOut = bridgeTransaction.args; const fromNetwork = network; const toNetwork = (0, networks_1.getNetworkByHolographId)(bridgeOut.toChain).key; const bridgeOutPayload = bridgeOut.bridgeOutPayload; const holographableContractAddress = bridgeOut.holographableContract.toLowerCase(); if (holographableContractAddress === this.networkMonitor.factoryAddress) { // BRIDGE OUT CONTRACT DEPLOYMENT const deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfig)(bridgeOutPayload); const deploymentHash = (0, contract_deployment_1.deploymentConfigHash)(deploymentConfig); const contractAddress = (0, contract_deployment_1.create2address)(deploymentConfig, this.networkMonitor.factoryAddress); const deploymentEvent = [contractAddress, deploymentHash]; await this.updateBridgedContract('out', transaction, network, toNetwork, contractAddress, deploymentEvent, deploymentConfig, operatorJobHash, tags); } else { const slot = await this.networkMonitor.providers[network].getStorageAt(holographableContractAddress, (0, utils_1.storageSlot)('eip1967.Holograph.contractType')); const contractType = (0, utils_1.toAscii)(slot); if (contractType === 'HolographERC20') { // BRIDGE OUT ERC20 TOKENS const erc20BeamInfo = (0, bridge_1.decodeBridgeOutErc20Args)(bridgeOutPayload); const erc20TransferEvent = this.networkMonitor.decodeErc20TransferEvent(receipt, holographableContractAddress); if (erc20TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `Bridge erc20 transfer event not found for ${transaction.hash}`, tags); } else { // we do not currently capture await this.updateBridgedERC20('out', transaction, network, toNetwork, holographableContractAddress, erc20TransferEvent, erc20BeamInfo, operatorJobHash, tags); } } else if (contractType === 'HolographERC721') { // BRIDGE IN ERC721 NFT const erc721BeamInfo = (0, bridge_1.decodeBridgeOutErc721Args)(bridgeOutPayload); const erc721TransferEvent = this.networkMonitor.decodeErc721TransferEvent(receipt, holographableContractAddress); if (erc721TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `Bridge erc721 transfer event not found for ${transaction.hash}`, tags); } else { await this.updateBridgedERC721('out', transaction, network, fromNetwork, toNetwork, contractType, holographableContractAddress, erc721TransferEvent, erc721BeamInfo, operatorJobHash, tags); } } else { this.networkMonitor.structuredLog(network, `unknown bridgeOutRequest contractType`, tags); } } } else { this.networkMonitor.structuredLog(network, `Function call was ${bridgeTransaction.name} and not bridgeOutRequest`, tags); } } } else { this.networkMonitor.structuredLog(network, `Transaction failed, ignoring it`, tags); } } async handleAvailableOperatorJobEvent(transaction, network, tags) { const receipt = await this.networkMonitor.getTransactionReceipt({ network, transactionHash: transaction.hash, attempts: 10, canFail: true, }); if (receipt === null) { throw new Error(`Could not get receipt for ${transaction.hash}`); } if (receipt.status === 1) { this.networkMonitor.structuredLog(network, `Checking for job hash`, tags); const operatorJobPayloadData = this.networkMonitor.decodeAvailableOperatorJobEvent(receipt, this.networkMonitor.operatorAddress); const operatorJobHash = operatorJobPayloadData === undefined ? undefined : operatorJobPayloadData[0]; const operatorJobPayload = operatorJobPayloadData === undefined ? undefined : operatorJobPayloadData[1]; if (operatorJobHash === undefined) { this.networkMonitor.structuredLog(network, `No AvailableOperatorJob event found`, tags); } else { // check that operatorJobPayload and operatorJobHash are the same if ((0, utils_1.sha3)(operatorJobPayload) !== operatorJobHash) { throw new Error('The hashed operatorJobPayload does not equal operatorJobHash!'); } this.networkMonitor.structuredLog(network, `Decoding bridgeInRequest`, tags); const bridgeTransaction = this.networkMonitor.bridgeContract.interface.parseTransaction({ data: operatorJobPayload }); if (bridgeTransaction.name === 'bridgeInRequest') { const bridgeIn = bridgeTransaction.args; const fromNetwork = (0, networks_1.getNetworkByHolographId)(bridgeIn.fromChain).key; const toNetwork = network; const bridgeInPayload = bridgeIn.bridgeInPayload; const holographableContractAddress = bridgeIn.holographableContract.toLowerCase(); // BRIDGE OUT CONTRACT DEPLOYMENT if (holographableContractAddress === this.networkMonitor.factoryAddress) { const deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfig)(bridgeInPayload); const deploymentHash = (0, contract_deployment_1.deploymentConfigHash)(deploymentConfig); const contractAddress = (0, contract_deployment_1.create2address)(deploymentConfig, this.networkMonitor.factoryAddress); const deploymentEvent = [contractAddress, deploymentHash]; await this.updateBridgedContract('msg', transaction, network, toNetwork, contractAddress, deploymentEvent, deploymentConfig, operatorJobHash, tags); } else { const slot = await this.networkMonitor.providers[network].getStorageAt(holographableContractAddress, (0, utils_1.storageSlot)('eip1967.Holograph.contractType')); const contractType = (0, utils_1.toAscii)(slot); if (contractType === 'HolographERC20') { // BRIDGE OUT ERC20 TOKENS const erc20BeamInfo = (0, bridge_1.decodeBridgeOutErc20Args)(bridgeInPayload); const erc20TransferEvent = this.networkMonitor.decodeErc20TransferEvent(receipt, holographableContractAddress); if (erc20TransferEvent === undefined) { this.networkMonitor.structuredLog(network, `Bridge erc20 transfer event not found for ${transaction.hash}`, tags); } else { // we do not currently capture await this.updateBridgedERC20('msg', transaction, network, toNetwork, holographableContractAddress, erc20TransferEvent, erc20BeamInfo, operatorJobHash, tags); } } else if (contractType === 'HolographERC721') { // BRIDGE IN ERC721 NFT const erc721BeamInfo = (0, bridge_1.decodeBridgeInErc721Args)(bridgeInPayload); await this.updateBridgedERC721('msg', transaction, network, fromNetwork, toNetwork, contractType, holographableContractAddress, [erc721BeamInfo.from, erc721BeamInfo.to, bignumber_1.BigNumber.from(erc721BeamInfo.tokenId)], erc721BeamInfo, operatorJobHash, tags); } } this.networkMonitor.structuredLog(network, `Found a valid bridgeInRequest for ${transaction.hash}`, tags); } else { this.networkMonitor.structuredLog(network, `Unknown bridgeIn function executed for ${transaction.hash}`, tags); } this.networkMonitor.structuredLog(network, `Bridge-In transaction type: ${bridgeTransaction.name} -->> ${bridgeTransaction.args}`); } } else { this.networkMonitor.structuredLog(network, `Transaction failed, ignoring it`, tags); } } async updateContractCallback(responseData, transaction, network, contractAddress, deploymentConfig, tags) { const data = JSON.stringify({ contractAddress, // TODO: decide if this should be included in API call // contractCreator: deploymentConfig.signer, chainId: transaction.chainId, status: 'DEPLOYED', salt: deploymentConfig.config.salt, tx: transaction.hash, blockNumber: transaction.blockNumber, // TODO: decide if this should be included in API call // blockTimestamp: transaction.timestamp, }); this.networkMonitor.structuredLog(network, `Successfully found Collection with address ${contractAddress}`, tags); this.networkMonitor.structuredLog(network, `API: Requesting to update Collection ${contractAddress} with id ${responseData.id}`, tags); await this.sendPatchRequest({ responseData, network, query: `${this.BASE_URL}/v1/collections/${responseData.id}`, data, messages: [ `PATCH response for collection ${contractAddress}`, `Successfully updated collection ${contractAddress} chainId to ${transaction.chainId}`, `Failed to update the Holograph database ${contractAddress}`, contractAddress, ], }, tags); } async updateDeployedContract(transaction, network, contractAddress, deploymentEvent, deploymentConfig, tags) { // here we need to extract origin chain from config // to know if this is the main deployment chain for the contract or not // this would allow us to update the db contract deployment tx and to set chain column this.networkMonitor.structuredLog(network, `HolographFactory deployed a new collection on ${(0, utils_1.capitalize)(network)} at address ${contractAddress}. Wallet that deployed the collection is ${transaction.from}. The config used for deployHolographableContract was ${JSON.stringify(deploymentConfig, undefined, 2)}. The transaction hash is: ${transaction.hash}`); this.networkMonitor.structuredLog(network, `Sending deployed collection job to DBJobManager ${contractAddress}`, tags); const job = { attempts: 0, network, timestamp: await this.getBlockTimestamp(network, transaction.blockNumber), message: `API: Requesting to get Collection with address ${contractAddress}`, query: `${this.BASE_URL}/v1/collections/contract/${contractAddress}`, callback: this.updateContractCallback, arguments: [transaction, network, contractAddress, deploymentConfig, tags], tags, }; if (!(job.timestamp in this.dbJobMap)) { this.dbJobMap[job.timestamp] = []; } this.dbJobMap[job.timestamp].push(job); } async updateBridgedContract(direction, transaction, network, fromNetwork, contractAddress, deploymentEvent, deploymentConfig, operatorJobHash, tags) { // not updating DB for any initial call outs since there is no beam status table for this yet if (direction === 'in') { // here we need to extract origin chain from config // to know if this is the main deployment chain for the contract or not // this would allow us to update the db contract deployment tx and to set chain column this.networkMonitor.structuredLog(network, `HolographOperator executed a job which bridged a collection. HolographFactory deployed a new collection on ${(0, utils_1.capitalize)(network)} at address ${contractAddress}. Operator that deployed the collection is ${transaction.from}. The config used for deployHolographableContract function was ${JSON.stringify(deploymentConfig, undefined, 2)}`, tags); this.networkMonitor.structuredLog(network, `Sending bridged collection job to DBJobManager ${contractAddress}`, tags); const job = { attempts: 0, network, timestamp: await this.getBlockTimestamp(network, transaction.blockNumber), query: `${this.BASE_URL}/v1/collections/contract/${contractAddress}`, message: `API: Requesting to get Collection with address ${contractAddress}`, callback: this.updateContractCallback, arguments: [transaction, network, contractAddress, deploymentConfig, tags], tags, }; if (!(job.timestamp in this.dbJobMap)) { this.dbJobMap[job.timestamp] = []; } this.dbJobMap[job.timestamp].push(job); } } async updateBridgedERC20(direction, transaction, network, fromNetwork, contractAddress, erc20TransferEvent, erc20BeamInfo, operatorJobHash, tags) { this.networkMonitor.structuredLog(network, `${transaction.hash} for ERC20 not yet managed ${JSON.stringify(erc20BeamInfo)}`, tags); } async updateERC721Callback(responseData, transaction, network, contractAddress, tokenId, tags) { const data = JSON.stringify({ chainId: transaction.chainId, status: 'MINTED', tx: transaction.hash, }); this.networkMonitor.structuredLog(network, `Successfully found NFT with tokenId ${tokenId} from ${contractAddress}`, tags); this.networkMonitor.structuredLog(network, `API: Requesting to update NFT with collection ${contractAddress} and tokeId ${tokenId} and id ${responseData.id}`, tags); await this.sendPatchRequest({ responseData, network, query: `${this.BASE_URL}/v1/nfts/${responseData.id}`, data, messages: [ `PATCH collection ${contractAddress} tokeId ${tokenId}`, `Successfully updated NFT collection ${contractAddress} and tokeId ${tokenId}`, `Failed to update the database for collection ${contractAddress} and tokeId ${tokenId}`, `collection ${contractAddress} and tokeId ${tokenId}`, ], }, tags); Promise.resolve(); } async updateBridgedERC721(direction, transaction, network, fromNetwork, toNetwork, contractType, contractAddress, erc721TransferEvent, erc721BeamInfo, operatorJobHash, tags) { const tokenId = (0, bytes_1.hexZeroPad)(erc721TransferEvent[2].toHexString(), 32); this.networkMonitor.structuredLog(network, `HolographOperator executed a job which minted an ERC721 NFT. Holographer minted a new NFT on ${(0, utils_1.capitalize)(network)} at address ${contractAddress}. The ID of the NFT is ${tokenId}. Operator that minted the nft is ${transaction.from}`, tags); this.networkMonitor.structuredLog(network, `Sending bridged nft job to DBJobManager ${contractAddress}`, tags); const job = { attempts: 0, network, timestamp: await this.getBlockTimestamp(network, transaction.blockNumber), query: `${this.BASE_URL}/v1/nfts/${contractAddress}/${tokenId}`, message: `API: Requesting to get NFT with tokenId ${tokenId} from ${contractAddress}`, callback: this.updateERC721Callback, arguments: [transaction, network, contractAddress, tokenId, tags], tags, }; if (!(job.timestamp in this.dbJobMap)) { this.dbJobMap[job.timestamp] = []; } this.dbJobMap[job.timestamp].push(job); const crossChainTxType = direction === 'in' ? 'bridgeIn' : direction === 'out' ? 'bridgeOut' : 'relayMessage'; await this.updateCrossChainTransaction(crossChainTxType, network, transaction, fromNetwork, toNetwork, contractAddress, contractType, tokenId, operatorJobHash, tags); } async updateMintedERC721(transaction, network, contractType, contractAddress, erc721TransferEvent, tags) { const tokenId = (0, bytes_1.hexZeroPad)(erc721TransferEvent[2].toHexString(), 32); this.networkMonitor.structuredLog(network, `Indexer identified a minted an ERC721 NFT. Holographer minted a new NFT on ${(0, utils_1.capitalize)(network)} at address ${contractAddress}. The ID of the NFT is ${tokenId}. Account that minted the nft is ${transaction.from}`, tags); this.networkMonitor.structuredLog(network, `Sending minted nft job to DBJobManager ${contractAddress}`, tags); const job = { attempts: 3, network, timestamp: await this.getBlockTimestamp(network, transaction.blockNumber), query: `${this.BASE_URL}/v1/nfts/${contractAddress}/${tokenId}`, message: `API: Requesting to get NFT with tokenId ${tokenId} from ${contractAddress}`, callback: this.updateERC721Callback, arguments: [transaction, network, contractAddress, tokenId, tags], tags, }; if (!(job.timestamp in this.dbJobMap)) { this.dbJobMap[job.timestamp] = []; } this.dbJobMap[job.timestamp].push(job); } async updateCrossChainTransactionCallback(responseData, transaction, network, fromNetwork, toNetwork, contractAddress, tokenId, crossChainTxType, jobHash, tags) { this.networkMonitor.structuredLog(network, `Successfully found NFT with tokenId ${tokenId} from ${contractAddress}`, tags); // Get and convert the destination chain id from network name to chain id const destinationChainid = networks_1.networks[toNetwork].chain; let data = {}; const params = { headers: { Authorization: `Bearer ${this.JWT}`, 'Content-Type': 'application/json', }, data: data, }; this.networkMonitor.structuredLog(network, `Cross chain transaction type is ${crossChainTxType}`, tags); // Set the columns to update based on the type of cross-chain transaction switch (crossChainTxType) { case 'bridgeOut': data = JSON.stringify({ jobHash, jobType: 'ERC721', sourceTx: transaction.hash, sourceBlockNumber: transaction.blockNumber, sourceChainId: transaction.chainId, sourceStatus: 'COMPLETED', sourceAddress: transaction.from, nftId: responseData.id, collectionId: responseData.collectionId, // Include the destination chain id if the transaction is a bridge out messageChainId: destinationChainid, operatorChainId: destinationChainid, }); this.networkMonitor.structuredLog(network, this.apiColor(`API: Requesting to update CrossChainTransaction with ${jobHash} for brigdeOut`), tags); if (this.environment === environment_1.Environment.localhost || this.environment === environment_1.Environment.experimental) { this.networkMonitor.structuredLog(network, `Should make an API POST call to "${this.BASE_URL}/v1/cross-chain-transactions" with data ${data}`, tags); } else { try { const req = await axios_1.default.post(`${this.BASE_URL}/v1/cross-chain-transactions`, data, params); this.networkMonitor.structuredLog(network, this.apiColor(`API: POST CrossChainTransaction ${jobHash} response ${JSON.stringify(req.data)}`), tags); this.networkMonitor.structuredLog(network, `Successfully updated CrossChainTransaction ${jobHash} ID ${req.data.id}`, tags); } catch (error) { this.networkMonitor.structuredLog(network, `Failed to update the database for CrossChainTransaction ${jobHash}`, tags); this.networkMonitor.structuredLogError(network, error.response.data, [ ...tags, this.errorColor(`CrossChainTransaction ${jobHash}`), ]); } } break; case 'relayMessage': data = JSON.stringify({ jobHash, jobType: 'ERC721', messageTx: transaction.hash, messageBlockNumber: transaction.blockNumber, messageChainId: transaction.chainId, messageStatus: 'COMPLETED', messageAddress: transaction.from, nftId: responseData.id, collectionId: responseData.collectionId, }); this.networkMonitor.structuredLog(network, this.apiColor(`API: Requesting to update CrossChainTransaction with ${jobHash} for relayMessage`), tags); if (this.environment === environment_1.Environment.localhost || this.environment === environment_1.Environment.experimental) { this.networkMonitor.structuredLog(network, `Should make an API POST call to "${this.BASE_URL}/v1/cross-chain-transactions" with data ${data}`, tags); } else { try { const req = await axios_1.default.post(`${this.BASE_URL}/v1/cross-chain-transactions`, data, params); this.networkMonitor.structuredLog(network, this.apiColor(`API: POST CrossChainTransaction ${jobHash} response ${JSON.stringify(req.data)}`), tags); this.networkMonitor.structuredLog(network, `Successfully updated CrossChainTransaction ${jobHash} ID ${req.data.id}`, tags); } catch (error) { this.networkMonitor.structuredLog(network, `Failed to update the database for CrossChainTransaction ${jobHash}`, tags); this.networkMonitor.structuredLogError(network, error.response.data, [ ...tags, this.errorColor(`CrossChainTransaction ${jobHash}`), ]); } } break; case 'bridgeIn': data = JSON.stringify({ jobHash, jobType: 'ERC721', operatorTx: transaction.hash, operatorBlockNumber: transaction.blockNumber, operatorChainId: transaction.chainId, operatorStatus: 'COMPLETED', operatorAddress: transaction.from, nftId: responseData.id, collectionId: responseData.collectionId, }); this.networkMonitor.structuredLog(network, this.apiColor(`API: Requesting to update CrossChainTransaction with ${jobHash} for bridgeIn`), tags); if (this.environment === environment_1.Environment.localhost || this.environment === environment_1.Environment.experimental) { this.networkMonitor.structuredLog(network, `Should make an API POST call to "${this.BASE_URL}/v1/cross-chain-transactions" with data ${data}`, tags); } else { try { const req = await axios_1.default.po