"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Executor = void 0;
const tslib_1 = require("tslib");
const ethers_1 = require("ethers");
const experimental_1 = require("@ethersproject/experimental");
const logger_1 = require("../utils/logger");
const config_1 = require("../config/config");
const contract_1 = require("../utils/contract");
const helpers_1 = require("../utils/helpers");
const rpc_service_1 = require("../rpc/rpc.service");
const executor_arbitrage_strategies_1 = require("../config/executor-arbitrage-strategies");
const sieve_1 = require("../transmitter/sieve");
const transmitter_service_1 = require("../transmitter/transmitter.service");
const types_1 = require("../transmitter/types");
const order_service_1 = require("../order/order.service");
const types_2 = require("../types");
const Sentry = tslib_1.__importStar(require("@sentry/node"));
const enums_1 = require("../pricer/enums");
const pricer_api_1 = require("../pricer/pricer-api");
const types_3 = require("../pricer/types");
const helpers_2 = require("../pricer/helpers");
const math_1 = require("../utils/math");
const pendingOrders_1 = require("./processing/pendingOrders");
const wallet_1 = require("../utils/wallet");
const mappings_1 = require("../pricer/mappings");
const pendingExecutions_1 = require("./processing/pendingExecutions");
const keyv_1 = tslib_1.__importDefault(require("keyv"));
const pricer_logic_service_1 = require("../pricer/pricer.logic.service");
class Executor {
    prometheus;
    shutdownInProgress;
    config;
    sieve;
    wallet = {};
    walletBalances = {};
    nonceManager = {};
    rpc = {};
    transmitter = new Map();
    orderService;
    pricerApi;
    escrowGmpContract = {};
    remoteOrderContract = {};
    claimerGMP = {};
    attestersContract = {};
    avpBatchSubmitterContract = {};
    erc20MockContract = {};
    protocolFees = {};
    protocolFeesMin = {};
    protocolFeesMax = {};
    orderBookContract = {};
    escrowOrderBookContract = {};
    singleAttestationBookContract = {};
    singleAttestationBookContractV1 = {};
    singleAttestationBookContractV2 = {};
    singleAttestationBookContractV3 = {};
    attestationBatchMakerContract = {};
    attestationBatchMakerRemoteContract = {};
    biddingBookContract = {};
    confirmationBookContract = {};
    enabledNetworks = [];
    enabledNetworksIds = [];
    insurance = ethers_1.BigNumber.from(0);
    sumOfNative = ethers_1.BigNumber.from(0);
    gasPriceL3 = ethers_1.BigNumber.from(0);
    rewardsOn = false;
    haltProcessingNewOrders = false;
    dbBidsWon = new keyv_1.default();
    networkConfigL3;
    bidQueue = [];
    arbitrageStrategies;
    priceNetworkMap = new Map();
    balancesLocalCache = { ts: 0, balances: {} };
    balancesCacheTTLSec = 30 * 60; // 30 minutes
    processExecutionsIntervalSec = 0;
    processOrdersIntervalSec = 0;
    rejectionReasons = {};
    constructor(_config, prometheus) {
        this.prometheus = prometheus;
        this.shutdownInProgress = false;
        this.config = _config;
        this.prometheus = prometheus;
        this.rpc = new rpc_service_1.Rpc(_config, this.prometheus);
        this.transmitter = new Map();
        this.sieve = sieve_1.Sieve.getInstance();
        this.pricerApi = new pricer_api_1.PricerApi(_config);
        this.orderService = new order_service_1.OrderService(_config);
        this.networkConfigL3 = this.config.networks[this.config.l3.currentActiveNetwork];
        this.arbitrageStrategies = (0, executor_arbitrage_strategies_1.getArbitrageStrategies)(_config.executor.arbitrageStrategy);
        for (const [name, network] of Object.entries(this.config.networks)) {
            if (this.config.executor.enabledNetworks.includes(name) && !config_1.disabledNetworks.includes(name)) {
                logger_1.logger.info(`🔗 Adding ${name} network to executor`);
                this.enabledNetworks.push([name, network]);
                this.enabledNetworksIds.push(network.id);
            }
        }
        // Always enable L3 network if not in the list
        if (!this.config.executor.enabledNetworks.includes(this.networkConfigL3.name)) {
            logger_1.logger.info(`🔗 Adding ${this.networkConfigL3.name} network to executor`);
            this.enabledNetworks.push([this.networkConfigL3.name, this.networkConfigL3]);
            this.enabledNetworksIds.push(this.networkConfigL3.id);
        }
    }
    // Public getters
    getConfig() {
        return this.config;
    }
    getPricerApi() {
        return this.pricerApi;
    }
    getPrometheus() {
        return this.prometheus;
    }
    getTransmitterL3() {
        return this.transmitter.get(this.networkConfigL3.id);
    }
    getGasPriceL3() {
        return this.gasPriceL3;
    }
    getOrderService() {
        return this.orderService;
    }
    getMyAddress() {
        return this.wallet[this.networkConfigL3.id].address;
    }
    getPendingOrdersInterval() {
        return this.processOrdersIntervalSec || this.config.executor.processOrders.api.intervalSec;
    }
    getPendingExecutionsInterval() {
        return this.processExecutionsIntervalSec || this.config.executor.processPendingExecutions.api.intervalSec;
    }
    getBalances() {
        return this.walletBalances;
    }
    // Used by AIxecutor consumers
    /**
     *
     * Fetches the balances of all tokens for the current executor.
     *
     */
    async getBalancesWithTokens() {
        const now = Math.floor(Date.now() / 1000);
        const address = this.getMyAddress();
        // 1. Look in local cache first
        if (this.balancesLocalCache &&
            this.balancesLocalCache.balances &&
            now - this.balancesLocalCache.ts < this.balancesCacheTTLSec) {
            logger_1.logger.debug({ address }, '🏦 Balances found in local cache for executor');
            return this.balancesLocalCache.balances;
        }
        // 2. Otherwise fetch from DB
        try {
            const dbResult = await this.pricerApi.getAccountBalances(address);
            const { balances, timestamp } = dbResult;
            const staleThresholdSec = 24 * 60 * 60; // 24 hours in seconds
            if (!timestamp || now - timestamp > staleThresholdSec) {
                logger_1.logger.debug({ address, dbTimestamp: timestamp }, '🏦 Stale balances in DB for executor, fetching from RPC');
                // Fetch from RPC fallback
                const fallback = await (0, wallet_1.getAddressTokensBalances)(address, this.enabledNetworks, this.rpc, this.config.executor.enabledAssets);
                this.balancesLocalCache = { ts: now, balances: fallback };
                logger_1.logger.debug({ address }, '🏦 Balances fetched from RPC for executor');
                return fallback;
            }
            // DB result is ok, use it
            this.balancesLocalCache = { ts: now, balances };
            logger_1.logger.debug({ address }, '🏦 Balances found in DB for executor');
            return balances;
        }
        catch (err) {
            // 3. On 404 fetch them on the spot
            if (err.response?.status === 404) {
                logger_1.logger.info({ address }, '🏦 Balances not found in DB for executor. Fetching balances from RPC fallback.');
                const fallback = await (0, wallet_1.getAddressTokensBalances)(address, this.enabledNetworks, this.rpc, this.config.executor.enabledAssets);
                this.balancesLocalCache = { ts: now, balances: fallback };
                logger_1.logger.debug({ address }, '🏦 Balances fetched from RPC for executor');
                return fallback;
            }
            throw err;
        }
    }
    getHaltProcessingNewOrders() {
        return this.haltProcessingNewOrders;
    }
    getInsurance() {
        return this.insurance;
    }
    getTransmitter() {
        return this.transmitter;
    }
    // Used in AI Executor
    getArbitrageStrategies() {
        return this.arbitrageStrategies;
    }
    // Used in AI Executor
    setArbitrageStrategies(newStrategies) {
        this.arbitrageStrategies = (0, executor_arbitrage_strategies_1.getArbitrageStrategies)(newStrategies);
    }
    getEnabledNetworksIds() {
        return this.enabledNetworksIds;
    }
    getExecutorEnabledNetworksAndAssets() {
        const executorEnabledNetworkNames = this.config.executor.enabledNetworks;
        const executorEnabledNetworkIds = executorEnabledNetworkNames.map((name) => this.config.networks[name].id);
        const executorEnabledAssets = [...this.config.executor.enabledAssets];
        return {
            networkIds: executorEnabledNetworkIds,
            assets: executorEnabledAssets,
        };
    }
    handleProcessNewOrderApi(...args) {
        return this.processNewOrderApi(...args);
    }
    async rebalanceWallet(address, amount, sourceNetwork, destinationNetwork, fromAsset, toAsset) {
        // Sanity checks
        if (!(0, helpers_2.isValidAddress)(address)) {
            throw new Error('Invalid address');
        }
        if (address !== this.getMyAddress()) {
            throw new Error(`Address mismatch! Provided: ${address}, Expected: ${this.getMyAddress()}`);
        }
        if (!(0, helpers_2.canBeValidBigNumber)(amount)) {
            throw new Error('Invalid amount type');
        }
        if (!(0, types_3.isSupportedNetworkT3rn)(sourceNetwork))
            throw new Error(`Unsupported network ${sourceNetwork}`);
        if (!(0, types_3.isSupportedNetworkT3rn)(destinationNetwork))
            throw new Error(`Unsupported network ${destinationNetwork}`);
        if (!(0, types_3.isSupportedAssetT3rn)(fromAsset))
            throw new Error(`Unsupported asset ${fromAsset}`);
        if (!(0, types_3.isSupportedAssetT3rn)(toAsset))
            throw new Error(`Unsupported asset ${toAsset}`);
        const executorEnabledNetworkNames = this.config.executor.enabledNetworks;
        const executorEnabledNetworks = new Set(executorEnabledNetworkNames.map((name) => this.config.networks[name].id));
        if (!executorEnabledNetworks.has(sourceNetwork))
            throw new Error(`Network ${sourceNetwork} not enabled for executor`);
        if (!executorEnabledNetworks.has(destinationNetwork))
            throw new Error(`Network ${destinationNetwork} not enabled for executor`);
        const executorEnabledAssets = new Set(this.config.executor.enabledAssets);
        if (!executorEnabledAssets.has(fromAsset))
            throw new Error(`Asset ${fromAsset} not enabled for executor`);
        if (!executorEnabledAssets.has(toAsset))
            throw new Error(`Asset ${toAsset} not enabled for executor`);
        const amountBN = ethers_1.BigNumber.from(amount);
        // Estimate received amount with pricer's /estimate
        const estimateResponse = await this.pricerApi.estimate(fromAsset, toAsset, sourceNetwork, destinationNetwork, amountBN);
        if (!estimateResponse) {
            throw new Error('Failed to estimate received amount');
        }
        // Prepare params for remoteOrder call
        const remoteOrder = this.remoteOrderContract[sourceNetwork];
        if (!remoteOrder) {
            throw new Error(`RemoteOrder contract not found for network ${sourceNetwork}`);
        }
        const estimatedReceivedAmountWei = ethers_1.BigNumber.from(estimateResponse.estimatedReceivedAmountWei);
        const sourceTokenAddress = (0, helpers_2.getAssetAddressByName)(sourceNetwork, fromAsset);
        const destinationTokenAddress = (0, helpers_2.getAssetAddressByName)(destinationNetwork, toAsset);
        const destinationTokenNumber = (0, helpers_2.getAssetNumberByAddress)(destinationNetwork, destinationTokenAddress);
        const destinationNetworkEncoded = (0, helpers_1.encodeNetworkId)(destinationNetwork);
        const targetAccount = `0x000000000000000000000000${address.slice(2)}`;
        const insurance = this.config.fastWriter.insurance;
        const txParams = {};
        txParams.value = sourceTokenAddress === ethers_1.ethers.constants.AddressZero ? amountBN : ethers_1.BigNumber.from(0);
        txParams.gasLimit = await remoteOrder.estimateGas.order(destinationNetworkEncoded, destinationTokenNumber, targetAccount, estimatedReceivedAmountWei, sourceTokenAddress, insurance, amountBN, txParams);
        // Call remoteOrder
        const tx = await remoteOrder.order(destinationNetworkEncoded, destinationTokenNumber, targetAccount, estimatedReceivedAmountWei, sourceTokenAddress, insurance, amountBN, txParams);
        if (!tx) {
            throw new Error('Transaction to RemoteOrder failed');
        }
        // Await tx to finish and get the result
        const receipt = await tx.wait();
        if (!receipt || receipt.status !== 1) {
            throw new Error('Transaction to RemoteOrder failed or was reverted');
        }
        const event = receipt.events.find((e) => e.event === 'OrderCreated');
        if (!event) {
            throw new Error('OrderCreated event not found in transaction logs');
        }
        const txHash = receipt.transactionHash;
        const { id, nonce, orderTimestamp, sourceAccount } = event.args;
        return {
            id,
            txHash,
            nonce,
            orderTimestamp: orderTimestamp.toNumber(),
            sender: sourceAccount,
        };
    }
    async init() {
        // Init L3 network instances before anything else
        const { provider, wallet, nonceManager, biddingBookContract, orderBookContract, escrowOrderBookContract, confirmationBookContract, singleAttestationBookContract, singleAttestationBookContractV1, singleAttestationBookContractV2, singleAttestationBookContractV3, segmentBookContract, payloadGetterContract, transmitter, insurance, } = await (0, helpers_1.initL3Network)(this.rpc, this.networkConfigL3, this.config, this.prometheus, undefined, 'executor');
        this.rpc.provider[this.networkConfigL3.id] = provider;
        this.wallet[this.networkConfigL3.id] = wallet;
        this.nonceManager[this.networkConfigL3.id] = nonceManager;
        this.orderBookContract = orderBookContract;
        this.escrowOrderBookContract = escrowOrderBookContract;
        this.singleAttestationBookContract = singleAttestationBookContract;
        this.singleAttestationBookContractV1 = singleAttestationBookContractV1;
        this.singleAttestationBookContractV2 = singleAttestationBookContractV2;
        this.singleAttestationBookContractV3 = singleAttestationBookContractV3;
        this.biddingBookContract = biddingBookContract;
        this.confirmationBookContract = confirmationBookContract;
        this.transmitter.set(this.networkConfigL3.id, transmitter);
        this.insurance = insurance;
        const disableNetworks = [];
        for (const [_, network] of this.enabledNetworks) {
            logger_1.logger.debug({ network: network.id }, `🔗 Init executor for network ${network.name} (${network.id})`);
            if (network.exotic) {
                logger_1.logger.info({ network: network.id }, '🥥 Skipping contracts initialization for exotic network');
                continue;
            }
            if (network.id !== this.networkConfigL3.id) {
                this.rpc.provider[network.id] = this.rpc.init(network.rpcEndpoints, network);
                this.wallet[network.id] = new ethers_1.Wallet(this.config.privateKey, this.rpc.provider[network.id]);
                this.nonceManager[network.id] = new experimental_1.NonceManager(this.wallet[network.id]);
                logger_1.logger.info(`🔗 Connected to network ${network.id}`);
            }
            const walletAddress = this.wallet[network.id].address;
            if (network.contracts.claimerGMPV2) {
                this.claimerGMP[network.id] = (0, contract_1.initContract)(contract_1.ContractName.ClaimerGMPV2, network.contracts.claimerGMPV2, this.nonceManager[network.id]);
            }
            this.remoteOrderContract[network.id] = (0, contract_1.initContract)(contract_1.ContractName.RemoteOrder, network.contracts.remoteOrder, this.nonceManager[network.id]);
            this.attestersContract[network.id] = (0, contract_1.initContract)(contract_1.ContractName.AttestationsVerifierProofs, network.contracts.attesters, this.nonceManager[network.id]);
            this.avpBatchSubmitterContract[network.id] = network.contracts.avpBatchSubmitter
                ? (0, contract_1.initContract)(contract_1.ContractName.avpBatchSubmitter, network.contracts.avpBatchSubmitter, this.nonceManager[network.id])
                : {};
            this.escrowGmpContract[network.id] = (0, contract_1.initContract)(contract_1.ContractName.EscrowGMP, network.contracts.escrowGMP, this.nonceManager[network.id]);
            if (network.tokens.TRN) {
                this.erc20MockContract[network.id] = (0, contract_1.initContract)(contract_1.ContractName.TRN, network.tokens.TRN, this.nonceManager[network.id]);
            }
            try {
                const balance = await this.rpc.provider[network.id]?.getBalance(walletAddress);
                if (balance === undefined) {
                    throw new Error('Cannot fetch the initial balance');
                }
                logger_1.logger.debug({
                    address: walletAddress,
                    sourceNetwork: network.name,
                    balanceEth: ethers_1.ethers.utils.formatEther(balance),
                }, `✅ Wallet loaded`);
            }
            catch (error) {
                logger_1.logger.error({
                    sourceNetwork: network.id,
                    error: `${error.reason} - ${error.code}`,
                }, `🚨 Removing failed network`);
                disableNetworks.push(network.id);
            }
            const transmitterInstance = new transmitter_service_1.Transmitter(this.config, this.nonceManager[network.id], this.prometheus, this.rpc.provider[network.id], network.id, network.name);
            transmitterInstance.init(this.remoteOrderContract, this.attestersContract, {}, this.orderBookContract, this.escrowOrderBookContract, this.biddingBookContract, this.confirmationBookContract, {}, {}, {}, {}, this.avpBatchSubmitterContract);
            this.transmitter.set(network.id, transmitterInstance);
        }
        // Disabling networks must be done outside the init for-loop, because we can't modify the array while iterating
        for (const networkId of disableNetworks) {
            const idx = this.enabledNetworks.findIndex(([_, network]) => network.id === networkId);
            this.enabledNetworks.splice(idx, 1);
            this.enabledNetworksIds.splice(idx, 1);
        }
        if (this.enabledNetworks.length === 0) {
            logger_1.logger.error(`🚨 No networks enabled. Exiting...`);
            process.exit(1);
        }
        this.orderService.init(this.insurance, this.orderBookContract, this.escrowOrderBookContract, this.biddingBookContract, this.singleAttestationBookContract, this.attestationBatchMakerContract, this.singleAttestationBookContractV1, this.singleAttestationBookContractV2, this.singleAttestationBookContractV3, this.attestationBatchMakerRemoteContract, this.confirmationBookContract, {}, {}, payloadGetterContract, segmentBookContract, this.escrowGmpContract, this.attestersContract, this.remoteOrderContract, {}, this.claimerGMP, this.enabledNetworksIds);
    }
    /**
     * Starts all continuous processes and service instances for the Prophet service
     */
    start() {
        void this.sieve.start();
        for (const [_, network] of this.enabledNetworks) {
            if (network.exotic) {
                logger_1.logger.info({ network: network.id }, '🥥 Skipping initialization for exotic network');
                continue;
            }
            this.initTransmitterListenersAndStart(network.id);
            void this.protocolFeeFetcher(network.id);
            if (network.id === this.networkConfigL3.id) {
                void this.fetchLatestInsurance();
            }
            if (this.config.featureFlags.supportTokens) {
                void this.setInfiniteAllowanceForRemoteOrderContract(network);
            }
            if (this.config.executor.claimsListenerEnabled) {
                this.listenClaimedEvents(network);
            }
            // TODO: outbidding functionality
            // this.listenOutbidReceivedEventsOnL3(network)
            this.walletBalances[network.id] = ethers_1.BigNumber.from(0);
            void (0, wallet_1.monitorWalletBalance)('executor', network, this.wallet[network.id].address, ethers_1.ethers.constants.AddressZero, this.config.walletCheckIntervalSec, this.rpc, this.prometheus.balance, this.walletBalances);
        }
        void this.intervalsFetcher();
        void this.gasPriceFetcher();
        void this.startFetchingAssetPrices();
        void this.processBidQueue();
        void this.logRejectionReasons();
        /**
         * API processing
         *
         * Fetch and processes pending orders - orders that have not either:
         * - been bid on at all (no executor picked them up)
         * - been bid on but not executed (only processes orders belonging to the current executor)
         * - been executed and attested, but not yet claimed (only the ones belonging to current executor)
         */
        if (this.config.executor.processOrders.enabled) {
            if (this.config.executor.processOrders.api.enabled) {
                void (0, pendingOrders_1.processPendingOrders)(this);
            }
            else {
                this.listenOrderCreatedEventsOnL3(this.networkConfigL3);
            }
        }
        if (this.config.executor.processPendingExecutions.enabled) {
            void (0, pendingExecutions_1.processPendingExecutions)(this, this.config.executor.processPendingExecutions.batch);
        }
    }
    async startFetchingAssetPrices() {
        logger_1.logger.info('📦 Start fetching asset prices');
        const waitTimeSec = 60;
        while (!this.shutdownInProgress) {
            try {
                const prices = await this.pricerApi.fetchAllPrices();
                this.priceNetworkMap = new Map(Object.entries(prices).map(([networkId, assets]) => [
                    networkId,
                    new Map(Object.entries(assets).map(([assetName, priceData]) => [
                        assetName,
                        priceData,
                    ])),
                ]));
            }
            catch (error) {
                logger_1.logger.warn({ error: error.message, code: error.code }, `🚨 Couldn't fetch asset prices from pricer API`);
            }
            await (0, helpers_1.sleep)(waitTimeSec);
        }
    }
    async gasPriceFetcher() {
        this.gasPriceL3 = ethers_1.BigNumber.from(0);
        while (true) {
            try {
                this.gasPriceL3 = this.getTransmitterL3()?.rpcProvider.gasPrice || this.gasPriceL3;
            }
            catch {
                logger_1.logger.error('🅰️⛽ Failed to fetch latest L3 gas price');
            }
            await (0, helpers_1.sleep)(60);
        }
    }
    async protocolFeeFetcher(networkId) {
        this.protocolFees[networkId] = this.config.orders.defaultProtocolFeeInMicroPercentage;
        this.protocolFeesMin[networkId] = this.config.orders.defaultProtocolFlatFee;
        this.protocolFeesMax[networkId] = this.config.orders.defaultProtocolFeeInMicroPercentage;
        while (true) {
            try {
                this.protocolFees[networkId] =
                    (await this.remoteOrderContract[networkId].currentProtocolFee()) || this.protocolFees[networkId];
                this.protocolFeesMin[networkId] =
                    (await this.remoteOrderContract[networkId].currentProtocolFeeFlatLow()) || this.protocolFeesMin[networkId];
                this.protocolFeesMax[networkId] =
                    (await this.remoteOrderContract[networkId].currentProtocolFeeFlatHigh()) || this.protocolFeesMax[networkId];
            }
            catch (error) {
                logger_1.logger.error({ error: error.message, networkId }, '🅰️⛽ Failed to fetch latest protocol fee');
            }
            await (0, helpers_1.sleep)(600);
        }
    }
    getProtocolFee(networkId) {
        const fee = this.protocolFees[networkId];
        return fee && fee.gt(0) ? fee : this.config.orders.defaultProtocolFeeInMicroPercentage;
    }
    getProtocolFeeFlatMin(networkId) {
        const feeMin = this.protocolFeesMin[networkId];
        return feeMin && feeMin.gt(0) ? feeMin : this.config.orders.defaultProtocolFlatFee;
    }
    getProtocolFeeFlatMax(networkId) {
        return this.protocolFeesMax[networkId] || this.config.orders.defaultProtocolFeeInMicroPercentage;
    }
    async intervalsFetcher() {
        while (true) {
            try {
                const intervals = await this.pricerApi.fetchExecutorProcessingIntervals();
                const processExecutionsIntervalSec = intervals.processExecutionsIntervalSec;
                const processOrdersIntervalSec = intervals.processOrdersIntervalSec;
                logger_1.logger.info({
                    processExecutionsIntervalSec,
                    processOrdersIntervalSec,
                }, `🫑 Retrieved new processing intervals`);
                logger_1.logger.trace({
                    balances: this.getBalances(),
                }, `🫑 Current executor balances`);
                if (processExecutionsIntervalSec != this.processExecutionsIntervalSec && processExecutionsIntervalSec > 0) {
                    this.processExecutionsIntervalSec = processExecutionsIntervalSec;
                }
                if (processOrdersIntervalSec != this.processOrdersIntervalSec && processOrdersIntervalSec > 0) {
                    this.processOrdersIntervalSec = processOrdersIntervalSec;
                }
            }
            catch (error) {
                logger_1.logger.warn({
                    error: error.message,
                    code: error.code,
                }, `🍋 Failed to fetch intervals`);
            }
            await (0, helpers_1.sleep)(600);
        }
    }
    /**
     * Returns the current shutdown state of the service
     */
    isShutdownInProgress() {
        return this.shutdownInProgress;
    }
    /**
     * Triggers a shutdown process of the service, by removing listeners for new incoming orders,
     * and waiting for Transmitter queues to process and dry out.
     */
    async triggerShutdown() {
        this.shutdownInProgress = true;
        this.orderBookContract.removeAllListeners();
        // TODO (0xdavinchee): remove escrowOrderBookContract listeners
        await (0, helpers_1.waitTransmitterFinishProcessing)(this.transmitter, this.config);
    }
    /**
     * Listen for RemoteOrderCreated events on L3
     *
     * @param {NetworkConfig} network
     */
    listenOrderCreatedEventsOnL3(network) {
        const eventName = types_2.EventName.RemoteOrderCreated;
        logger_1.logger.info({
            address: this.networkConfigL3.contracts.L3.orderBook,
            networkId: this.networkConfigL3.id,
        }, `⏳ Listening for ${eventName} events on OrderBook contract`);
        this.orderBookContract.on(eventName, (...args) => {
            const order = this.orderService.parseL3OrderCreatedEvent(args);
            const childLogger = logger_1.logger.child({
                networkId: network.id,
                id: order.id,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
                destinationAsset: order.destinationAsset,
                sourceAsset: order.sourceAsset,
                amount: ethers_1.ethers.utils.formatEther(order.amount),
                maxReward: ethers_1.ethers.utils.formatEther(order.maxReward),
                sourceAccount: order.sourceAccount,
                targetAccount: order.targetAccount,
                orderTimestamp: order.orderTimestamp,
                txHash: order.txHash,
            });
            if (!this.orderService.areNetworksSupported(order.source, order.destination)) {
                return;
            }
            (0, logger_1.batchLogger)({
                id: order.id,
                txHash: order.txHash,
                source: order.source,
                destination: order.destination,
                amount: order.amount.toString(),
            }, `📝 RemoteOrderCreated event received`);
            const gasPriceOnL3 = this.getGasPriceL3();
            if (gasPriceOnL3.gt(ethers_1.ethers.utils.parseUnits(this.config.executor.maxL3GasPrice.toString(), 'gwei'))) {
                (0, logger_1.batchLogger)({ id: order.id }, `🔥⛽️ Gas price on L3 is too high & exceeds ${this.config.executor.maxL3GasPrice} gwei configured. Skipping bidding.`);
                return;
            }
            this.processNewOrder(order, childLogger);
        });
    }
    // TODO (0xdavinchee): add listenEscrowOrderCreatedEventsOnL3
    /**
     * Listen for Claimed events
     *
     * @param {NetworkConfig} network
     */
    listenClaimedEvents(network) {
        const eventName = types_2.EventName.Claimed;
        logger_1.logger.info({ address: network.contracts.remoteOrder, networkId: network.id }, `⏳ Listening for ${eventName} events on RemoteOrder contract`);
        const wallet = this.wallet[network.id].address;
        const filter = this.remoteOrderContract[network.id].filters.Claimed(null, wallet, null, null);
        this.remoteOrderContract[network.id].on(filter, (id, executor, amount, assetAddress, eventData) => {
            const { transactionHash: txHash, blockNumber } = eventData;
            const childLogger = logger_1.logger.child({
                id,
                sourceNetwork: network.id,
                networkId: network.id,
                executor,
                wallet,
                amount: amount.toString(),
                assetAddress,
                txHash,
                blockNumber,
            });
            childLogger.info(`🍝 Claimed event received`);
            if (executor !== wallet) {
                childLogger.warn(`🍝 Order doesn't match executor address. Skip.`);
                return;
            }
            const { isFirstTime } = this.sieve.siftId(id, eventName);
            if (!isFirstTime) {
                return;
            }
            this.onClaimedSuccess(network.id, amount, assetAddress, childLogger);
        });
    }
    /**
     * Listen for OutbidReceived events on L3
     *
     * @param {NetworkConfig} network
     */
    listenOutbidReceivedEventsOnL3(network) {
        const eventName = types_2.EventName.OutbidReceived;
        logger_1.logger.info({
            address: this.networkConfigL3.contracts.L3.biddingBook,
            networkId: this.networkConfigL3.id,
        }, `⏳ Listening for ${eventName} events on OrderBook contract`);
        this.orderBookContract.on(eventName, async (id, source, executor, proposedReward, newProposedReward, refund, eventData) => {
            const sourceNetwork = (0, helpers_1.decodeNetworkId)(source);
            const childLogger = logger_1.logger.child({
                id,
                sourceNetwork,
                networkId: network.id,
                executor,
                proposedReward: proposedReward.toString(),
                newProposedReward: newProposedReward.toString(),
                refund: refund.toString(),
                txHash: eventData.transactionHash,
            });
            childLogger.info(`💸 OutbidReceived event received`);
            if (!this.orderService.isNetworkSupported(sourceNetwork)) {
                return;
            }
            const walletSource = this.wallet[sourceNetwork]
                ? this.wallet[sourceNetwork].address
                : ethers_1.ethers.constants.AddressZero;
            if (walletSource === ethers_1.ethers.constants.AddressZero) {
                childLogger.info(`💸 Wallet not found for source network. Skip.`);
                return;
            }
            if (executor !== this.wallet[sourceNetwork]?.address || executor === ethers_1.ethers.constants.AddressZero) {
                childLogger.info(`💸 Bid not done by current executor. Skip.`);
                return;
            }
            const orderResponseL3 = await this.orderService.getOrderFromOrderBookResponse(id, childLogger);
            const order = orderResponseL3.order;
            if (!order) {
                return;
            }
            this.prometheus.profitabilityExecutor.inc({ result: 'total', destinationNetwork: order.destination });
            // Decrease the reward proposed by the executor who outbid the current executor,
            // so he can bid again wit the lower reward amount. And we work with micro-percentage.
            // F.e. if the factor is 1000000 and the decrease % is 1, and the original bid amount
            // is 40, then after decreasing it will be 39,99996.
            const decreasedBidAmount = newProposedReward
                .mul(ethers_1.BigNumber.from(this.config.executor.bidDecreaseFactor).sub(this.config.executor.bidDecreasePercent))
                .div(this.config.executor.bidDecreaseFactor);
            order.maxReward = decreasedBidAmount;
            if (order.maxReward.eq(order.amount)) {
                logger_1.logger.warn({
                    id: order.id,
                    maxReward: order.maxReward.toString(),
                    amount: order.amount.toString(),
                    context: 'listenOutbidReceivedEventsOnL3',
                }, '🔮 Order maxReward is equal to amount.');
            }
            const assetBalanceOnDestination = this.getBalance(order.destination, order.destinationAssetCode);
            const orderProfitability = this.getOrderProfitability(order, assetBalanceOnDestination);
            if (!orderProfitability.isProfitable) {
                childLogger.warn({
                    isProfitable: orderProfitability.isProfitable,
                    profit: orderProfitability.profit.toString(),
                    loss: orderProfitability.loss.toString(),
                    supportTokens: this.config.featureFlags.supportTokens,
                    assetNative: order.assetNative,
                }, `💸💔 Order is not profitable anymore. Do not try to bid again.`);
                childLogger.info(`💸☹️ Another executor has outbid you on this order. You will be refunded.`);
                return;
            }
            const transmitterInstance = this.transmitter.get(this.networkConfigL3.id);
            if (!transmitterInstance) {
                childLogger.fatal({ networkId: this.networkConfigL3.id }, '🚨 Transmitter instance not found for network');
                Sentry.captureMessage(`Transmitter instance not found for network: ${this.networkConfigL3.id}`);
                return;
            }
            transmitterInstance.transmitBid(order, this.wallet[sourceNetwork].address, this.getInsurance());
        });
    }
    async onBatchBidComplete(queueItem) {
        await Promise.all(queueItem.orders.map((order) => this.onBidOrderComplete(order)));
    }
    async onBidOrderComplete(order) {
        this.prometheus.bidCompleted.inc({ sourceNetwork: order.source });
        const executor = this.getMyAddress();
        try {
            // Allow DB to catch up
            // What DB?
            // await sleep(1)
            let bidWinner = ethers_1.ethers.constants.AddressZero;
            bidWinner = await this.orderBookContract.getBidWinner(order.id);
            const didIWin = ethers_1.ethers.utils.getAddress(bidWinner.toLowerCase()) === ethers_1.ethers.utils.getAddress(executor.toLowerCase());
            if (!didIWin) {
                this.onBidOrderFailed(order, 'Outbid by another executor', executor, bidWinner);
                return;
            }
            logger_1.logger.info({
                id: order.id,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
                destinationAsset: order.destinationAsset,
                amount: (0, math_1.BNtoFloat)(order.amount),
                maxReward: (0, math_1.BNtoFloat)(order.maxReward),
                isUnderbidEnabled: this.config.featureFlags.executorUnderbidMicroPercentage > 0,
                underbidPercentage: this.config.featureFlags.executorUnderbidMicroPercentage,
            }, `✅ Bid successful`);
            if (this.config.executor.processPendingExecutions.adHoc) {
                const transmitterInstance = this.transmitter.get(order.destination);
                if (!transmitterInstance) {
                    logger_1.logger.fatal({ networkId: order.destination }, `🚨 Transmitter instance not found for network`);
                    Sentry.captureMessage(`Transmitter instance not found for network: ${order.destination}`);
                    return;
                }
                transmitterInstance.transmitExecute(order);
            }
        }
        catch (error) {
            logger_1.logger.error({ error: error.message }, `🚨 Error fetching bid winner from OrderBook contract. Skip bid.`);
        }
    }
    /**
     * Proceeds with executing the order, if the bid has been won by the current executor
     *
     * @param {queueItem} queueItem
     */
    async onBidComplete(queueItem) {
        await this.onBidOrderComplete(queueItem.order);
    }
    onBidOrderFailed(order, error, executor, bidWinner, txHash) {
        if (error) {
            (0, logger_1.batchLogger)({ id: order.id, executor: bidWinner }, `${error}`);
            this.prometheus.bidFailed.inc({ sourceNetwork: order.source, reason: `${error}` });
            return;
        }
        const childLogger = logger_1.logger.child({
            txHash,
            id: order.id,
            sourceNetwork: order.source,
            destinationNetwork: order.destination,
            bidWinner,
            executor,
            destinationAsset: order.destinationAsset,
            amount: (0, math_1.BNtoFloat)(order.amount),
            maxReward: (0, math_1.BNtoFloat)(order.maxReward),
            isUnderbidEnabled: this.config.featureFlags.executorUnderbidMicroPercentage > 0,
            underbidPercentage: this.config.featureFlags.executorUnderbidMicroPercentage,
        });
        childLogger.error({ order: order.id, reason: error }, `❌ Bid failed`);
        this.prometheus.bidFailed.inc({ sourceNetwork: order.source, reason: 'failed' });
    }
    onBidFailed(queueItem, executor, bidWinner) {
        this.onBidOrderFailed(queueItem.order, queueItem.error, executor, bidWinner, queueItem.txReceipt.txHash);
    }
    onExecuteComplete(queueItem) {
        logger_1.logger.info({
            id: queueItem.order.id,
            sourceNetwork: queueItem.order.source,
            destinationNetwork: queueItem.order.destination,
            txHash: queueItem.txReceipt.txHash,
            amount: (0, math_1.BNtoFloat)(queueItem.order.amount),
            targetAccount: queueItem.order.targetAccount,
            reward: (0, math_1.BNtoFloat)(queueItem.order.maxReward),
            lifecycle: queueItem.lifecycle,
        }, '✅ Execution successful');
        this.prometheus.executionsCompleted.inc({ destinationNetwork: queueItem.order.destination });
    }
    onExecuteFailed(queueItem) {
        logger_1.logger.error({
            id: queueItem.order.id,
            sourceNetwork: queueItem.order.source,
            destinationNetwork: queueItem.order.destination,
            sourceAsset: queueItem.order.sourceAsset,
            destinationAsset: queueItem.order.destinationAsset,
            sourceAssetCode: queueItem.order.sourceAssetCode,
            destinationAssetCode: queueItem.order.destinationAssetCode,
            txHash: queueItem.txReceipt.txHash,
            amount: (0, math_1.BNtoFloat)(queueItem.order.amount),
            targetAccount: queueItem.order.targetAccount,
            reward: (0, math_1.BNtoFloat)(queueItem.order.maxReward),
            lifecycle: queueItem.lifecycle,
        }, `❌ Execution failed`);
        this.prometheus.executionsFailed.inc({ destinationNetwork: queueItem.order.destination });
    }
    onExecuteBatchComplete(queueItem) {
        for (const order of queueItem.orders) {
            logger_1.logger.info({
                id: order.id,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
                txHash: queueItem.txReceipt.txHash,
                amount: (0, math_1.BNtoFloat)(order.amount),
                targetAccount: order.targetAccount,
                destinationAssetCode: order.destinationAssetCode,
                reward: (0, math_1.BNtoFloat)(order.maxReward),
            }, '✅ Execution successful for order from batch');
        }
        this.prometheus.executionsCompleted.inc({ destinationNetwork: queueItem.networkId }, queueItem.orders.length);
    }
    onExecuteBatchFailed(queueItem) {
        for (const order of queueItem.orders) {
            logger_1.logger.error({
                id: order.id,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
                txHash: queueItem.txReceipt.txHash,
                amount: (0, math_1.BNtoFloat)(order.amount),
                targetAccount: order.targetAccount,
                destinationAssetCode: order.destinationAssetCode,
                reward: (0, math_1.BNtoFloat)(order.maxReward),
            }, `❌ Execution failed for order from batch`);
            this.prometheus.executionsFailed.inc({ destinationNetwork: queueItem.networkId }, queueItem.orders.length);
        }
    }
    /**
     * This function is triggered when an order is successfully claimed after being attested.
     * It updates the metrics.
     *
     * @param sourceNetwork
     * @param amount
     * @param _assetAddress
     * @param {Logger}  childLogger
     */
    onClaimedSuccess(sourceNetwork, amount, _assetAddress, childLogger) {
        this.prometheus.claimsCompleted.inc({ sourceNetwork });
        childLogger.info({
            reward: (0, math_1.BNtoFloat)(amount),
        }, `🍝✅ Claimed reward.`);
    }
    areAssetsSupported(order) {
        if (order.sourceAssetCode === undefined || order.destinationAssetCode === undefined) {
            (0, logger_1.batchLogger)({
                id: order.id,
                source: order.sourceAssetCode?.toString(),
                destination: order.destinationAssetCode?.toString(),
            }, `📝🍋 Order is missing source or destination asset code. Skip order`);
            return false;
        }
        if (!this.config.executor.enabledAssets.includes(enums_1.assetNumberT3rnToAssetNamePriceProvider[order.sourceAssetCode])) {
            (0, logger_1.batchLogger)({ id: order.id, source: order.sourceAssetCode?.toString() }, `📝🍋 Order source asset ${order.sourceAssetCode} is not supported. Skip order`);
            return false;
        }
        if (!this.config.executor.enabledAssets.includes(enums_1.assetNumberT3rnToAssetNamePriceProvider[order.destinationAssetCode])) {
            (0, logger_1.batchLogger)({ id: order.id, destination: order.destinationAssetCode?.toString() }, `📝🍋 Order destination asset ${order.destinationAssetCode} is not supported. Skip order`);
            return false;
        }
        return true;
    }
    areOrderNetworksValid(order) {
        // check if networks are enabled
        if (!this.orderService.networksAreOfSameEnv(order.source, order.destination)) {
            return false;
        }
        // check if order.source and order.dest are enabled
        if (!this.orderService.areNetworksSupported(order.source, order.destination)) {
            return false;
        }
        return true;
    }
    pushRejectionReason(reason) {
        if (!reason) {
            return;
        }
        if (!this.rejectionReasons[reason]) {
            this.rejectionReasons[reason] = 0;
        }
        this.rejectionReasons[reason] += 1;
    }
    verifyUnderbidMargin(order, profit) {
        const currentGasPriceOnDestination = this.rpc.provider[order.destination]?.getEffectiveGasPrice() || ethers_1.BigNumber.from(0);
        // Get the price of destination native asset in USD
        const nativePriceDestinationInUsd = this.priceNetworkMap
            .get(order.destination)
            ?.get(types_3.idToNativeAssetName[order.destination])?.usdPrice;
        // Get the t3ETH price on source and calculate available profit bandwidth from eth in wei to usd
        const ethPriceOnSource = this.priceNetworkMap.get(order.source)?.get(enums_1.AssetName.t3ETH)?.usdPrice;
        if (order.destinationAssetCode === undefined) {
            (0, logger_1.batchLogger)({ id: order.id, destination: order.destination }, `📝🍋 Order is missing destination asset code. Skip order`);
            return {
                proceed: true,
                availableUnderbidMarginInUSD: 0,
                availableUnderbidMarginInAssetWei: ethers_1.BigNumber.from(0),
                proposePercentageDrop: 0,
                proposedUnderbidAmountInAssetWei: ethers_1.BigNumber.from(0),
                proposedUnderbidAmountInUSD: 0,
            };
        }
        // Get asset price on destination
        const assetPriceOnDestination = this.priceNetworkMap
            .get(order.destination)
            ?.get(enums_1.assetNumberT3rnToAssetNamePriceProvider[order.destinationAssetCode])?.usdPrice;
        if (!ethPriceOnSource) {
            (0, logger_1.batchLogger)({ id: order.id, source: order.source }, `📝🍋 ETH price on source network ${order.source} not found. Skip order`);
            return {
                proceed: true,
                availableUnderbidMarginInUSD: 0,
                availableUnderbidMarginInAssetWei: ethers_1.BigNumber.from(0),
                proposePercentageDrop: 0,
                proposedUnderbidAmountInAssetWei: ethers_1.BigNumber.from(0),
                proposedUnderbidAmountInUSD: 0,
            };
        }
        // Nothing to do with underbid margin if the price of destination asset isn't know
        if (!nativePriceDestinationInUsd || !assetPriceOnDestination) {
            return {
                proceed: true,
                availableUnderbidMarginInUSD: 0,
                availableUnderbidMarginInAssetWei: ethers_1.BigNumber.from(0),
                proposePercentageDrop: 0,
                proposedUnderbidAmountInAssetWei: ethers_1.BigNumber.from(0),
                proposedUnderbidAmountInUSD: 0,
            };
        }
        // Calculate in USD cost of delivery to dest with the current gas price
        const deliveryCostInUSD = pricer_logic_service_1.PricerLogic.calculateUSD(currentGasPriceOnDestination.mul(this.config.pricer.erc20TransferGasLimit), nativePriceDestinationInUsd, 18);
        const profitMarginInUsd = pricer_logic_service_1.PricerLogic.calculateUSD(profit, ethPriceOnSource, 18);
        const availableUnderbidMarginInUSD = profitMarginInUsd - deliveryCostInUSD;
        const availableUnderbidMarginInAssetWei = pricer_logic_service_1.PricerLogic.calculateFromUSD(availableUnderbidMarginInUSD, assetPriceOnDestination, 18);
        // If Either is negative - do not proceed
        if (availableUnderbidMarginInUSD < 0 || availableUnderbidMarginInAssetWei.lte(0)) {
            return {
                proceed: false,
                availableUnderbidMarginInUSD,
                availableUnderbidMarginInAssetWei,
                proposePercentageDrop: 0,
                proposedUnderbidAmountInAssetWei: ethers_1.BigNumber.from(0),
                proposedUnderbidAmountInUSD: 0,
            };
        }
        // Draw a random number from the range [0, 100] based on the set aggression rate.
        const proposePercentageDrop = Math.random() * this.config.executor.aggressionRate;
        // If the available underbid margin is less than the proposed percentage drop, we can't proceed
        const proposedUnderbidAmountInAssetWei = availableUnderbidMarginInAssetWei.mul(100 - proposePercentageDrop).div(100);
        const proposedUnderbidAmountInUSD = (availableUnderbidMarginInUSD * (100 - proposePercentageDrop)) / 100;
        // Mark availableUnderbidMarginInUSD to prometheus
        this.prometheus.availableUnderbidMarginUSD.set({
            assetCode: order.destinationAssetCode,
            destinationNetwork: order.destination,
        }, availableUnderbidMarginInUSD);
        return {
            proceed: proposedUnderbidAmountInAssetWei.gt(0) && proposedUnderbidAmountInUSD > 0,
            availableUnderbidMarginInUSD,
            availableUnderbidMarginInAssetWei: proposedUnderbidAmountInAssetWei,
            proposePercentageDrop,
            proposedUnderbidAmountInUSD,
            proposedUnderbidAmountInAssetWei,
        };
    }
    /**
     * Processes a new order received from RemoteOrderCreated event.
     *
     * @param {Order} order
     * @param {Logger}  childLogger
     * @param {boolean} transmitBid
     */
    processNewOrderApi(order) {
        const balanceDestination = this.getBalance(order.destination, order.destinationAssetCode);
        // Validate source/destination networks (short-circuits if invalid)
        if (!this.areOrderNetworksValid(order)) {
            (0, logger_1.batchLogger)({
                id: order.id,
            }, `📝🍋 Network ${order.source} or ${order.destination} not enabled. Skip order`);
            return false;
        }
        const minBalanceThreshold = ethers_1.ethers.utils.parseEther(this.config.executor.minBalanceThresholdEth);
        const nativeBalanceDestination = this.getBalance(order.destination);
        if (nativeBalanceDestination.lt(minBalanceThreshold)) {
            (0, logger_1.batchLogger)({
                amount: ethers_1.ethers.utils.formatEther(nativeBalanceDestination),
                source: order.destination,
            }, `📝🍋 Native wallet balance on destination below the configured EXECUTOR_MIN_BALANCE_THRESHOLD_ETH. Skip order`);
            return false;
        }
        // Compute profitability (depends on isOrderExecutable, so must come after)
        const { profit, isProfitable, reason, reasonDetails } = this.getOrderProfitability(order, balanceDestination);
        const verifyUnderbidMarginResults = this.verifyUnderbidMargin(order, profit);
        if (!isProfitable || !verifyUnderbidMarginResults.proceed) {
            const tooLowUnderBidMarginReason = `Underbid margin not sufficient for order profitability.`;
            this.pushRejectionReason(reason || tooLowUnderBidMarginReason);
            (0, logger_1.batchLogger)({
                id: order.id,
                destination: order.destination,
                source: order.source,
                message: reasonDetails,
                amount: verifyUnderbidMarginResults.availableUnderbidMarginInUSD.toString(),
            }, `📝🥔 ${reason || tooLowUnderBidMarginReason}. Skip order`, 10);
            return false;
        }
        // Check if the order is executable
        const isOrderExecutable = this.orderService.isOrderExecutable(order, balanceDestination, this.arbitrageStrategies);
        if (!isOrderExecutable) {
            logger_1.logger.trace({
                id: order.id,
                source: order.source,
                destination: order.destination,
                amount: ethers_1.ethers.utils.formatEther(order.amount),
                maxReward: ethers_1.ethers.utils.formatEther(order.maxReward),
            }, `📝🍋 Order is not executable. Skip order`);
            return false;
        }
        (0, logger_1.batchLogger)({
            amount: ethers_1.ethers.utils.formatEther(profit),
            source: this.networkConfigL3.id,
        }, `📝🥭 Order ${order.source}:${order.sourceAssetCode} -> ${order.destination}:${order.sourceAssetCode} executable, proceed`, 10);
        return true;
    }
    async logRejectionReasons() {
        const sleepSec = 60 * 10;
        while (true) {
            // Each 10m
            await (0, helpers_1.sleep)(sleepSec);
            // Iterate over rejection reasons and log them
            for (const [reason, count] of Object.entries(this.rejectionReasons)) {
                // Reset the count after logging
                this.rejectionReasons[reason] = 0;
                // If count is 0, skip logging
                if (count === 0) {
                    continue;
                }
                // Log the reason and count
                if (count > 1) {
                    logger_1.logger.warn({ reason, count }, `📝🍋 Rejection reason: ${reason} occurred ${count} times in the last ${sleepSec} minutes`);
                }
            }
        }
    }
    /**
     * Processes a new order received from RemoteOrderCreated event.
     *
     * @param {Order} order
     * @param {Logger}  childLogger
     */
    async processBidQueue() {
        while (true) {
            // Random sleep time 1-4 sec
            await (0, helpers_1.sleepRandom)(1, 4);
            // Take up to 50 orders from the queue
            const orders = this.bidQueue.splice(0, 50);
            if (orders.length === 0) {
                continue;
            }
            logger_1.logger.info({ count: orders.length }, `🍝 Processing orders from Bid Queue`);
            if (!this.config.executor.processOrders.batch) {
                orders.forEach((order) => {
                    this.getTransmitterL3()?.transmitBid(order, this.wallet[order.source].address, this.getInsurance());
                });
            }
            else {
                const MAX_BATCH_SIZE = 30;
                for (let i = 0; i < orders.length; i += MAX_BATCH_SIZE) {
                    this.getTransmitterL3()?.transmitBidBatch(orders.slice(i, i + MAX_BATCH_SIZE), this.wallet[orders[0].source].address, false, this.getInsurance());
                }
            }
        }
    }
    processNewOrder(order, childLogger) {
        if (!this.orderService.networksAreOfSameEnv(order.source, order.destination)) {
            childLogger.error('📝🚨 Order networks are not of the same environment. One is testnet and the other is mainnet. Skip order.');
            return;
        }
        const orderIsNewlyAdded = this.sieve.addOrder(order);
        if (!orderIsNewlyAdded) {
            childLogger.debug('📝🤷 OrderCreated event is already being processed. Skip order');
            return;
        }
        const assetBalanceOnDestination = this.getBalance(order.destination, order.destinationAssetCode);
        const isWalletBalanceEnough = this.isWalletBalanceEnough(order.source, order.destination);
        if (!isWalletBalanceEnough) {
            const balancesSummary = JSON.stringify({
                [order.source]: (0, math_1.BNtoFloat)(this.getBalance(order.source, order.sourceAssetCode)),
                [order.destination]: (0, math_1.BNtoFloat)(assetBalanceOnDestination),
            });
            (0, logger_1.batchLogger)({ executor: this.getMyAddress(), message: balancesSummary }, `📝🍋 Wallet balance is not enough. Skip order`, 5);
            return;
        }
        let isOrderExecutable;
        try {
            isOrderExecutable = this.orderService.isOrderExecutable(order, assetBalanceOnDestination, this.arbitrageStrategies);
        }
        catch (error) {
            childLogger.error({
                error: error.message,
                assetBalanceOnDestination: (0, math_1.BNtoFloat)(assetBalanceOnDestination),
            }, `📝🚨 Could not check if order is executable. Skip order`);
            return;
        }
        if (!isOrderExecutable) {
            return;
        }
        const { profit, loss, isProfitable } = this.getOrderProfitability(order, assetBalanceOnDestination);
        if (!this.isOrderProfitable(order, profit, loss, isProfitable, childLogger)) {
            return;
        }
        const transmitterInstance = this.transmitter.get(this.networkConfigL3.id);
        if (!transmitterInstance) {
            childLogger.fatal({ networkId: this.networkConfigL3.id }, '🚨 Transmitter instance not found for network');
            Sentry.captureMessage(`Transmitter instance not found for network: ${this.networkConfigL3.id}`);
            return;
        }
        let bidAmount = order.maxReward;
        const underbidMicroPercentage = this.config.featureFlags.executorUnderbidMicroPercentage;
        if (underbidMicroPercentage > 0 && underbidMicroPercentage <= 100) {
            const MICRO_PERCENT_FACTOR = ethers_1.BigNumber.from(100_000_000);
            bidAmount = order.maxReward.mul(MICRO_PERCENT_FACTOR.sub(underbidMicroPercentage)).div(MICRO_PERCENT_FACTOR);
            childLogger.info({
                executor: this.wallet[order.source].address,
                underbidMicroPercentage,
                originalMaxReward: ethers_1.ethers.utils.formatEther(order.maxReward),
                underbidAmount: ethers_1.ethers.utils.formatEther(bidAmount),
            }, `Placing an underbid based on feature flag`);
        }
        order.maxReward = bidAmount;
        if (order.maxReward.eq(order.amount)) {
            logger_1.logger.warn({
                id: order.id,
                maxReward: order.maxReward.toString(),
                amount: order.amount.toString(),
                context: 'processNewOrder',
            }, '🔮 Order maxReward is equal to amount.');
        }
        if (this.config.executor.processOrders.api.enabled) {
            this.bidQueue.push(order);
        }
    }
    isOrderProfitable(order, profit, loss, isProfitable, childLogger) {
        if (!isProfitable) {
            childLogger.debug({
                profit: ethers_1.ethers.utils.formatEther(profit),
                loss: ethers_1.ethers.utils.formatEther(loss),
                amount: ethers_1.ethers.utils.formatEther(order.amount),
                maxReward: ethers_1.ethers.utils.formatEther(order.maxReward),
                isProfitable: isProfitable,
                supportTokens: this.config.featureFlags.supportTokens,
                assetNative: order.assetNative,
            }, `📝💔 Order is not profitable. Skip order`);
            return false;
        }
        childLogger.trace({
            profit: ethers_1.ethers.utils.formatEther(profit),
            loss: ethers_1.ethers.utils.formatEther(loss),
            amount: ethers_1.ethers.utils.formatEther(order.amount),
            maxReward: ethers_1.ethers.utils.formatEther(order.maxReward),
            isProfitable: isProfitable,
            supportTokens: this.config.featureFlags.supportTokens,
            assetNative: order.assetNative,
        }, `📝🧡 Processing new profitable Order - debug`);
        (0, logger_1.batchLogger)({ id: order.id }, `📝🧡 Processing new profitable Order`);
        return true;
    }
    /**
     * Calculates the profitability of an order based on the balance, cost on the destination, strategy, and pricing.
     *
     * @param {Order} order - The order object.
     * @param {BigNumber} balance - The balance of the executor's wallet.
     * @returns {Promise<OrderProfitability>} - Returns an object containing the profitability information.
     */
    getOrderProfitability(order, balance) {
        // Asset to spend is the asset that the executor has - to spend as an executor - at the destination network,
        // in exchange for the assetToReceive to receive at source network.
        let assetToReceive;
        let assetToSpend;
        try {
            const assetAddressToSpend = (0, helpers_2.getAssetAddressByNumber)(order.destination, order.destinationAssetCode || 0);
            assetToSpend = (0, helpers_2.getAssetNameByAddress)(order.destination, assetAddressToSpend);
            assetToReceive = (0, helpers_2.getAssetNameByAddress)(order.source, order.sourceAsset);
        }
        catch {
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `One of the assets is not supported`,
                reasonDetails: `${order.source}:${order.sourceAssetCode} or ${order.destination}:${order.destinationAssetCode}`,
            };
        }
        const strategiesForNetwork = this.arbitrageStrategies[order.destination];
        if (!strategiesForNetwork) {
            logger_1.logger.debug({
                assetToSpend,
                assetToReceive,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            }, `🚨 Strategies not found for network. Return order not profitable`);
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Strategies not loaded for network ${order.destination}`,
            };
        }
        const strategy = strategiesForNetwork[assetToSpend];
        if (!strategy) {
            logger_1.logger.trace({
                assetToSpend,
                assetToReceive,
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            }, `🚨 Strategy not found for asset. Return order not profitable`);
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Strategy not loaded for asset ${assetToSpend} to network ${order.destination}`,
            };
        }
        const childLogger = logger_1.logger.child({
            id: order.id,
            sourceNetwork: order.source,
            destinationNetwork: order.destination,
            asset: order.destinationAsset,
            assetToSpend,
            assetToReceive,
            strategy: {
                ...strategy,
                minProfitPerOrder: strategy.minProfitPerOrder.toString(),
                maxAmountPerOrder: strategy.maxAmountPerOrder.toString(),
                minAmountPerOrder: strategy.minAmountPerOrder.toString(),
            },
        });
        if (assetToReceive === enums_1.AssetName.UNKNOWN) {
            childLogger.error({ assetToReceive, isNative: order.assetNative }, `🍑 Asset to receive failed lookup via AssetMapper. Return order not profitable`);
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Asset to receive ${assetToReceive} failed lookup via AssetMapper`,
            };
        }
        const orderProfitability = this.evaluateOrderProfitability(order, balance, strategy);
        const maxRewardInEther = (0, math_1.BNtoFloat)(order.maxReward);
        if (orderProfitability.isProfitable) {
            this.prometheus.profitabilityExecutor.inc({ destinationNetwork: order.destination, result: 'profitable' });
            const profit = (0, math_1.BNtoFloat)(ethers_1.BigNumber.from(orderProfitability.profit));
            childLogger.trace({ profit }, `📈️ Order is profitable. Trade will have a profit denominated in "assetToSpend"`);
            this.prometheus.ordersAtProfit.inc({
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            });
            this.prometheus.ordersProfitTotal.set({
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            }, profit);
            if (profit !== 0 && maxRewardInEther !== 0) {
                const orderAvgProfitPercentage = (profit / maxRewardInEther) * 100;
                if (orderAvgProfitPercentage >= 0 && orderAvgProfitPercentage <= 100) {
                    this.prometheus.orderAvgProfitPercentage.set({
                        sourceNetwork: order.source,
                        destinationNetwork: order.destination,
                    }, orderAvgProfitPercentage);
                }
            }
        }
        else {
            this.prometheus.profitabilityExecutor.inc({ destinationNetwork: order.destination, result: 'unprofitable' });
            const loss = (0, math_1.BNtoFloat)(ethers_1.BigNumber.from(orderProfitability.loss));
            childLogger.trace({ loss }, `📉 Order is NOT profitable. Trade will have a loss denominated in "assetToSpend"`);
            this.prometheus.ordersAtLoss.inc({
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            });
            this.prometheus.ordersLossTotal.set({
                sourceNetwork: order.source,
                destinationNetwork: order.destination,
            }, loss);
            if (loss !== 0 && maxRewardInEther !== 0) {
                const orderAvgLossPercentage = (loss / maxRewardInEther) * 100;
                if (orderAvgLossPercentage >= 0 && orderAvgLossPercentage <= 100) {
                    this.prometheus.orderAvgLossPercentage.set({
                        sourceNetwork: order.source,
                        destinationNetwork: order.destination,
                    }, orderAvgLossPercentage);
                }
            }
        }
        return orderProfitability;
    }
    evaluateOrderProfitability(order, balance, strategy) {
        let assetToSpend;
        let assetToReceive;
        try {
            assetToSpend = (0, mappings_1.mapAssetNumberT3rnToAssetNameProvider)(order.destinationAssetCode);
            assetToReceive = (0, helpers_2.getAssetNameByAddress)(order.source, order.sourceAsset);
        }
        catch {
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(1),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `One of the assets is not supported`,
                reasonDetails: `${order.source}:${order.sourceAssetCode} or ${order.destination}:${order.destinationAssetCode}`,
            };
        }
        const priceAinEth = this.orderService.fetchAssetsRatio(this.priceNetworkMap, assetToReceive, enums_1.AssetName.t3ETH, order.source);
        const priceBinEth = this.orderService.fetchAssetsRatio(this.priceNetworkMap, assetToSpend, enums_1.AssetName.t3ETH, order.destination);
        const priceEthInUSD = this.priceNetworkMap.get(order.source)?.get(enums_1.AssetName.t3ETH);
        if (!priceEthInUSD) {
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(1),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Price for ${assetToReceive} on ${order.source} not found`,
                reasonDetails: `${order.source}:${order.sourceAssetCode} or ${order.destination}:${order.destinationAssetCode}`,
            };
        }
        const princeAinEthBigNumber = (0, math_1.parsePriceStringToBigNumberOn18DecimalsExt)(priceAinEth.toFixed(18));
        const priceBinEthBigNumber = (0, math_1.parsePriceStringToBigNumberOn18DecimalsExt)(priceBinEth.toFixed(18));
        const result = this.evaluateDeal(this.config, balance, strategy, order, princeAinEthBigNumber, priceBinEthBigNumber, priceEthInUSD.usdPrice);
        logger_1.logger.trace({
            source: order.source,
            destination: order.destination,
            sourceAssetCode: order.sourceAssetCode,
            destinationAssetCode: order.destinationAssetCode,
            id: order.id,
            priceAinEth,
            priceBinEth,
            princeAinEthBigNumber: ethers_1.ethers.utils.formatEther(princeAinEthBigNumber),
            priceBinEthBigNumber: ethers_1.ethers.utils.formatEther(priceBinEthBigNumber),
            loss: ethers_1.ethers.utils.formatEther(result.loss),
            profit: ethers_1.ethers.utils.formatEther(result.profit),
        }, 'ℹ️ 🪙 Executor evaluateOrderProfitability');
        return result;
    }
    evaluateDeal(config, balance, strategy, order, priceAinEth, priceBinEth, priceEthInUSD) {
        if (!this.enabledNetworksIds.includes(order.source) || !this.enabledNetworksIds.includes(order.destination)) {
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Source ${order.source} or destination ${order.destination} not included in executor enabled networks`,
            };
        }
        if (!balance || ethers_1.BigNumber.isBigNumber(balance) === false || balance.isZero()) {
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: ethers_1.BigNumber.from(0),
                protocolFeeCut: ethers_1.BigNumber.from(0),
                reason: `Balance of ${order.destination}:${order.destinationAssetCode} loaded as zero`,
            };
        }
        const maxReward = ethers_1.BigNumber.from(order.maxReward);
        const amount = ethers_1.BigNumber.from(order.amount);
        const protocolFeeCut = this.orderService.calculateProtocolFeeCut(order, this.getProtocolFee(order.source), this.getProtocolFeeFlatMin(order.source), this.getProtocolFeeFlatMax(order.source));
        const { rewardInEth, orderAmountInEth, potentialProfit, potentialLoss } = this.orderService.evaluateProfitabilityInWei(maxReward, amount, priceAinEth, priceBinEth, protocolFeeCut, config.pricer.maxDecimals18);
        logger_1.logger.trace({
            id: order.id,
            source: order.source,
            destination: order.destination,
            rewardInEth: ethers_1.ethers.utils.formatEther(rewardInEth),
            orderAmountInEth: ethers_1.ethers.utils.formatEther(orderAmountInEth),
            destinationAsset: order.destinationAsset,
            sourceAsset: order.sourceAsset,
            balanceEth: ethers_1.ethers.utils.formatEther(balance),
            amount: ethers_1.ethers.utils.formatEther(amount),
            maxReward: ethers_1.ethers.utils.formatEther(maxReward),
            protocolFee: ethers_1.ethers.utils.formatEther(protocolFeeCut),
            potentialProfitEth: ethers_1.ethers.utils.formatEther(potentialProfit),
            potentialLossEth: ethers_1.ethers.utils.formatEther(potentialLoss),
            protocolFeeCutEth: ethers_1.ethers.utils.formatEther(protocolFeeCut),
        }, '📈️ Executor evaluateDeal');
        let executor;
        try {
            executor = this.wallet[order.source].address;
        }
        catch (err) {
            logger_1.logger.warn({ id: order.id, error: err.message, network: order.source }, '👁 Network disabled or loaded incorrectly');
        }
        if (potentialProfit.lte(ethers_1.BigNumber.from(0))) {
            logger_1.logger.trace({
                id: order.id,
                source: order.source,
                destination: order.destination,
                amount: ethers_1.ethers.utils.formatEther(potentialProfit),
                maxReward: ethers_1.ethers.utils.formatEther(maxReward),
                protocolFeeCutEth: ethers_1.ethers.utils.formatEther(protocolFeeCut),
            }, `🚨 Order not profitable. Potential profit is lesser than or equal to 0`);
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: potentialLoss,
                protocolFeeCut,
                reason: `Potential profit is negative`,
                reasonDetails: `${order.source}:${order.sourceAssetCode}->${order.destination}:${order.destinationAssetCode}`,
            };
        }
        const potentialProfitUSD = pricer_logic_service_1.PricerLogic.calculateUSD(potentialProfit, priceEthInUSD, config.pricer.maxDecimals18);
        let maxProfitRateBaseline;
        let maxProfitRateBaselineBN;
        try {
            maxProfitRateBaseline =
                (Number.parseFloat(strategy.maxShareOfMyBalancePerOrder.toString()) * Number.parseFloat(balance.toString())) /
                    100;
            maxProfitRateBaseline = Math.ceil(maxProfitRateBaseline);
            maxProfitRateBaselineBN = (0, math_1.floatToBN)(maxProfitRateBaseline);
        }
        catch (error) {
            logger_1.logger.error({ id: order.id, error: error.message }, '🚨 Order not profitable. Strategy crashed');
            return {
                isProfitable: false,
                profit: ethers_1.BigNumber.from(0),
                loss: potentialLoss,
                protocolFeeCut,
                reason: 'Order not profitable. Strategy crashed',
            };
        }
        const maxAmountPerOrder = ethers_1.BigNumber.from(strategy.maxAmountPerOrder);
        const minAmountPerOrder = ethers_1.BigNumber.from(strategy.minAmountPerOrder);
        const minProfitPerOrderInUSD = strategy.minProfitPerOrder;
        const isProfitBelowMaxProfitRate = potentialProfit.lte(maxProfitRateBaselineBN);
        const isAmountBelowMaxAmountPerOrder = amount.lte(maxAmountPerOrder);
        const isAmountAboveMinAmountPerOrder = amount.gte(minAmountPerOrder);
        const isProfitAboveMinProfitPerOrder = potentialProfitUSD >= minProfitPerOrderInUSD;
        // Check if profit satisfies strategy constraints
        const isProfitable = isProfitBelowMaxProfitRate &&
            isProfitAboveMinProfitPerOrder &&
            isAmountBelowMaxAmountPerOrder &&
            isAmountAboveMinAmountPerOrder;
        let reason = null;
        let sourceAssetName;
        let destinationAssetName;
        try {
            sourceAssetName = (0, helpers_2.getAssetNameByAddress)(order.source, order.sourceAsset);
            destinationAssetName = (0, helpers_2.getAssetNameByAddress)(order.destination, order.destinationAsset);
        }
        catch {
            sourceAssetName = enums_1.AssetName.UNKNOWN;
            destinationAssetName = enums_1.AssetName.UNKNOWN;
        }
        // Individual logging for each step
        if (!isProfitable) {
            const childLogger = logger_1.logger.child({
                id: order.id,
                executor,
                source: order.source,
                destination: order.destination,
                balanceEth: ethers_1.ethers.utils.formatEther(balance),
            });
            if (!isProfitAboveMinProfitPerOrder) {
                reason = `Order not profitable: Potential profit is below minimum profit per order $${minProfitPerOrderInUSD} > $${potentialProfitUSD}`;
                childLogger.warn({
                    isProfitAboveMinProfitPerOrder,
                    potentialProfitEther: ethers_1.ethers.utils.formatEther(potentialProfit),
                    minProfitPerOrderInUSD,
                    potentialProfitUSD,
                    sourceAssetName,
                    destinationAssetName,
                }, reason);
            }
            if (!isAmountBelowMaxAmountPerOrder) {
                childLogger.warn({
                    isAmountBelowMaxAmountPerOrder,
                    amountEther: ethers_1.ethers.utils.formatEther(amount),
                    maxAmountPerOrderEther: ethers_1.ethers.utils.formatEther(maxAmountPerOrder),
                    sourceAssetName,
                    destinationAssetName,
                }, 'Order not profitable: Amount is above maximum amount per order');
                reason = 'Order not profitable: Amount is above maximum amount per order';
            }
            if (!isAmountAboveMinAmountPerOrder) {
                childLogger.warn({
                    isAmountAboveMinAmountPerOrder,
                    amountEther: ethers_1.ethers.utils.formatEther(amount),
                    minAmountPerOrderEther: ethers_1.ethers.utils.formatEther(minAmountPerOrder),
                    sourceAssetName,
                    destinationAssetName,
                }, 'Order not profitable: Amount is below minimum amount per order');
                reason = 'Order not profitable: Amount is below minimum amount per order';
            }
        }
        logger_1.logger.trace({
            id: order.id,
            source: order.source,
            destination: order.destination,
            isProfitable,
            isProfitBelowMaxProfitRate,
            isProfitAboveMinProfitPerOrder,
            isAmountBelowMaxAmountPerOrder,
            isAmountAboveMinAmountPerOrder,
            potentialProfitEth: ethers_1.ethers.utils.formatEther(potentialProfit),
            minProfitPerOrderInUSD,
            amount: ethers_1.ethers.utils.formatEther(amount),
            maxReward: ethers_1.ethers.utils.formatEther(maxReward),
            protocolFeeCut: ethers_1.ethers.utils.formatEther(protocolFeeCut),
            strategy: JSON.stringify({
                minProfitPerOrderInUSD,
                minProfitRate: strategy.minProfitRate,
                maxAmountPerOrder: ethers_1.ethers.utils.formatEther(maxAmountPerOrder),
                minAmountPerOrder: ethers_1.ethers.utils.formatEther(minAmountPerOrder),
                maxShareOfMyBalancePerOrder: strategy.maxShareOfMyBalancePerOrder,
            }),
        }, 'Order profitability check');
        return {
            isProfitable,
            profit: isProfitable ? potentialProfit : ethers_1.BigNumber.from(0),
            loss: isProfitable ? ethers_1.BigNumber.from(0) : potentialLoss,
            protocolFeeCut,
            reason,
        };
    }
    async processClaimSegment(networkId, batchPayload, batchHash, orderIds, childLogger) {
        const myAddressOnL3 = this.wallet[this.config.l3.currentActiveNetwork].address;
        const transmitterInstance = this.transmitter.get(networkId);
        const transmitterL3 = this.transmitter.get(this.config.l3.currentActiveNetwork);
        if (!transmitterInstance) {
            childLogger.fatal('🚨 Transmitter instance not found for network');
            Sentry.captureMessage(`Transmitter instance not found for network: ${networkId}`);
            return;
        }
        for (const orderId of orderIds) {
            (0, logger_1.batchLogger)({ id: orderId }, `🌺🏆 Order is claimable. Transmit claim for segment ...`);
        }
        const newEmptyOrdersOfIdsAndSource = orderIds.map((id) => {
            return order_service_1.OrderService.newOnlyIdAndNetworkOrder(id, networkId, 'source');
        });
        transmitterInstance.transmitAVPClaim(newEmptyOrdersOfIdsAndSource, [batchPayload], [batchHash], networkId);
        if (!transmitterL3) {
            childLogger.fatal('🚨 TransmitterL3 instance not found for network during processClaimInsurance');
            return;
        }
        if (this.getInsurance().gt(0) || this.rewardsOn) {
            try {
                await Promise.all(orderIds.map(async (id) => {
                    const alreadyClaimed = await this.biddingBookContract.insuranceClaimed(id);
                    if (!alreadyClaimed) {
                        transmitterL3.transmitClaimInsuranceById(id, myAddressOnL3, networkId);
                    }
                    else {
                        (0, logger_1.batchLogger)({ id }, `🥀 Insurance / Rewards already claimed for order`);
                    }
                }));
            }
            catch (error) {
                childLogger.error({ code: error.code, error: error.message }, `🚨 Failed to get current Insurance / Rewards from BiddingBook.`);
            }
        }
    }
    /**
     * Continuously fetches the current insurance value from the OrderBook contract,
     * and sets it to the insurance class param.
     *
     * @private
     */
    async fetchLatestInsurance() {
        logger_1.logger.info(`🏦 Fetching latest insurance on L3 network`);
        while (true) {
            await (0, helpers_1.sleep)(60);
            try {
                this.insurance = await this.pricerApi.getInsurance();
                logger_1.logger.trace({ insurance: this.insurance.toString() }, `🏦 Fetched latest insurance on L3 network`);
            }
            catch (error) {
                logger_1.logger.error({ code: error.code, error: error.message }, `🚨 Failed to get current insurance from OrderBook contract. Return zero.`);
            }
            try {
                this.rewardsOn = await this.biddingBookContract.isBrnRewardsOn();
                logger_1.logger.trace({ insurance: this.getInsurance().toString() }, `🏦 Fetched latest rewards on L3 network`);
            }
            catch (error) {
                this.rewardsOn = false;
                logger_1.logger.error({ code: error.code, error: error.message }, `🚨 Failed to get current rewards from BiddingBook contract. Set as false.`);
            }
        }
    }
    /**
     * Checks if the wallet balances are enough to execute transactions.
     *
     * @param {Wallet} sourceWallet - The source wallet object.
     * @param {Wallet} destinationWallet - The destination wallet object.
     * @param {any} childLogger - The child logger object.
     * @returns {Promise<boolean>} - Returns TRUE if the wallet balances are enough, FALSE otherwise
     */
    isWalletBalanceEnough(sourceId, destinationId) {
        const minBalanceThreshold = ethers_1.ethers.utils.parseEther(this.config.executor.minBalanceThresholdEth);
        if (this.getBalance(sourceId).lt(minBalanceThreshold) || this.getBalance(destinationId).lt(minBalanceThreshold)) {
            return false;
        }
        return true;
    }
    /**
     * Initializes the listeners on the Transmitter instance for the given network and start the instance.
     *
     * @param {NetworkIdT3rn}  networkId
     */
    initTransmitterListenersAndStart(networkId) {
        const transmitterInstance = this.transmitter.get(networkId);
        if (!transmitterInstance) {
            logger_1.logger.fatal({ networkId }, `🚨 Transmitter instance not found for network`);
            Sentry.captureMessage(`Transmitter instance not found for network: ${networkId}`);
            return;
        }
        transmitterInstance.on(types_1.OrderLifecycleEventSuccess.Bid, (item) => this.onBidComplete(item));
        transmitterInstance.on(types_1.OrderLifecycleEventSuccess.Execute, (item) => this.onExecuteComplete(item));
        transmitterInstance.on(types_1.OrderLifecycleEventFailure.Bid, (item) => this.onBidFailed(item));
        transmitterInstance.on(types_1.OrderLifecycleEventFailure.Execute, (item) => this.onExecuteFailed(item));
        transmitterInstance.on(types_1.BatchOrderLifecycleEventSuccess.Bid, (item) => this.onBatchBidComplete(item));
        transmitterInstance.on(types_1.BatchOrderLifecycleEventSuccess.Execute, (item) => this.onExecuteBatchComplete(item));
        transmitterInstance.on(types_1.BatchOrderLifecycleEventFailure.Execute, (item) => this.onExecuteBatchFailed(item));
        transmitterInstance.start();
    }
    /**
     * For all active Arbitrage strategies, set infinite allowance for traded tokens for RemoteOrder contract
     *
     * @private
     */
    setInfiniteAllowanceForRemoteOrderContract(network) {
        let childLogger = logger_1.logger.child({ networkId: network.id });
        const provider = this.rpc.provider[network.id];
        if (!provider) {
            childLogger.error(`🟡 Allowance:: Provider not found. Skip.`);
            return;
        }
        const strategies = this.arbitrageStrategies[network.id];
        if (!strategies) {
            childLogger.warn(`🟡 Allowance:: Arbitrage strategies not found. Skip.`);
            return;
        }
        for (const [asset, _strategy] of Object.entries(strategies)) {
            childLogger = logger_1.logger.child({ ...childLogger.bindings(), asset });
            // If asset is native, skip setting allowance
            if (types_3.nativeAssetsList.includes(asset)) {
                childLogger.debug(`🥝 Allowance:: Skip allowance for native asset`);
                continue;
            }
            let assetId;
            try {
                assetId = (0, mappings_1.mapAssetNameProviderToAssetNumberT3rn)(asset);
                if (types_3.idToNativeAssetName[network.id] === asset ||
                    types_3.nativeAssetsList.includes(asset)) {
                    logger_1.logger.info({
                        assetId,
                        asset,
                        networkId: network.id,
                    }, `🥝 Skip allowance for native asset`);
                    continue;
                }
            }
            catch (error) {
                childLogger.warn({ error: error.message }, `🟡 Allowance:: AssetID for asset not found. Skip.`);
                continue;
            }
            void this.subscribeTokenBalanceFetch(network.id, assetId, provider, childLogger);
            void (0, helpers_1.setInfiniteAllowanceOnAssetForAddress)(network.id, assetId, this.wallet[network.id], this.remoteOrderContract[network.id].address, provider, childLogger);
        }
    }
    getBalance(networkId, assetId = 0) {
        const assetOnNetworkKey = this.getAssetOnNetworkKey(networkId, assetId);
        logger_1.logger.trace({
            assetOnNetworkKey,
            assetId,
            networkId,
            balance: ethers_1.ethers.utils.formatEther(this.walletBalances[assetOnNetworkKey] || ethers_1.BigNumber.from(0)),
        }, `🥝 Get balance for asset on network`);
        return this.walletBalances[assetOnNetworkKey] || ethers_1.BigNumber.from(0);
    }
    getBalanceByKey(key) {
        const balance = this.walletBalances[key];
        if (!balance) {
            logger_1.logger.debug({ key }, `🥝 Get wallet balance for key not found`);
            return ethers_1.BigNumber.from(0);
        }
        return balance;
    }
    /**
     * Returns the asset key on the network
     *
     * @param networkId - The network ID
     * @param assetId - The asset ID
     * @returns {string} - The asset key on the network
     */
    getAssetOnNetworkKey(networkId, assetId) {
        if (!assetId || types_3.idToNativeAssetName[networkId] === enums_1.assetNumberT3rnToAssetNamePriceProvider[assetId]) {
            return networkId;
        }
        return `${networkId}-${assetId}`;
    }
    /**
     * Subscribes to token balance fetch for the given asset on the given network
     *
     * @param networkId - The network ID
     * @param assetId - The asset ID
     * @param provider - The provider
     * @param childLogger - The child logger
     * @returns {Promise<void>}
     */
    async subscribeTokenBalanceFetch(networkId, assetId, provider, childLogger) {
        logger_1.logger.debug({
            networkId,
            assetId,
        }, `🥝 Subscribe to ERC-20 token balance fetcher`);
        try {
            const tokenAddress = (0, helpers_2.getAssetAddressByNumber)(networkId, assetId);
            if (!tokenAddress) {
                childLogger.error(`🟡🥝 Token Balance:: Token address not found. Skip.`);
                return;
            }
            const assetOnNetworkKey = this.getAssetOnNetworkKey(networkId, assetId);
            this.walletBalances[assetOnNetworkKey] = ethers_1.BigNumber.from(0);
            const tokenContract = (0, contract_1.initContract)(contract_1.ContractName.ERC20Mock, tokenAddress, provider);
            // Subscribe to token balance fetch
            while (true) {
                try {
                    const balance = await tokenContract.balanceOf(this.getMyAddress());
                    this.walletBalances[assetOnNetworkKey] = balance;
                    childLogger.debug({ networkId, assetId, balance: ethers_1.ethers.utils.formatEther(balance) }, `🥝 Token Balance:: Fetched token balance`);
                }
                catch (error) {
                    childLogger.error({ networkId, assetId, error: error.message }, `🟡🥝 Token Balance:: Failed to fetch token balance`);
                }
                await (0, helpers_1.sleep)(this.config.walletCheckIntervalSec * 3);
            }
        }
        catch (error) {
            childLogger.error({ networkId, assetId, error: error.message }, `🟡🥝  Token Balance:: Failed to subscribe to token balance fetch`);
        }
    }
}
exports.Executor = Executor;
//# sourceMappingURL=executor.service.js.map