import { __assign, __awaiter, __extends, __generator } from "tslib";
import { utils as ethersUtils } from "ethers";
import { bnZero, EvmAddress, MAX_BIG_INT, assign, getRelayEventKey, isDefined, getMessageHash, isSlowFill, validateFillForDeposit, chainIsEvm, chainIsProd, toAddressType, } from "../../utils";
import { duplicateEvent, sortEventsAscendingInPlace } from "../../utils/EventUtils";
import { CHAIN_IDs, ZERO_ADDRESS } from "../../constants";
import { BaseAbstractClient } from "../BaseAbstractClient";
import { getRefundInformationFromFill } from "../BundleDataClient";
export var knownEventNames = [
    "EnabledDepositRoute",
    "TokensBridged",
    "RelayedRootBundle",
    "ExecutedRelayerRefundRoot",
    "V3FundsDeposited",
    "FundsDeposited",
    "RequestedSpeedUpV3Deposit",
    "RequestedSpeedUpDeposit",
    "RequestedV3SlowFill",
    "RequestedSlowFill",
    "FilledV3Relay",
    "FilledRelay",
    "BridgedToHubPool",
    "ClaimedRelayerRefund",
];
/**
 * SpokePoolClient is a client for the SpokePool contract. It is responsible for querying the SpokePool contract
 * for events and storing them in memory. It also provides some convenience methods for querying the stored events.
 */
var SpokePoolClient = /** @class */ (function (_super) {
    __extends(SpokePoolClient, _super);
    /**
     * Creates a new SpokePoolClient.
     * @param logger A logger instance.
     * @param hubPoolClient An optional HubPoolClient instance. This is used to fetch spoke data that is not stored on the SpokePool contract but is stored on the HubPool contract.
     * @param chainId The chain ID of the chain that this client is querying.
     * @param deploymentBlock The block number that the SpokePool contract was deployed at.
     * @param eventSearchConfig An optional EventSearchConfig object that controls how far back in history the client will search for events. If not provided, the client will only search for events from the deployment block.
     */
    function SpokePoolClient(logger, 
    // Can be excluded. This disables some deposit validation.
    hubPoolClient, chainId, deploymentBlock, eventSearchConfig) {
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        var _this = _super.call(this, eventSearchConfig) || this;
        _this.logger = logger;
        _this.hubPoolClient = hubPoolClient;
        _this.chainId = chainId;
        _this.deploymentBlock = deploymentBlock;
        _this.currentTime = 0;
        _this.duplicateDepositHashes = {};
        _this.depositHashesToFills = {};
        _this.speedUps = {};
        _this.slowFillRequests = {};
        _this.depositRoutes = {};
        _this.tokensBridged = [];
        _this.rootBundleRelays = [];
        _this.relayerRefundExecutions = [];
        _this.claimedRelayerRefunds = [];
        _this.bridgedToHubPool = [];
        _this.invalidFills = new Set();
        _this.depositHashes = {};
        _this.fills = {};
        _this.firstHeightToSearch = eventSearchConfig.from;
        _this.latestHeightSearched = 0;
        _this.configStoreClient = hubPoolClient === null || hubPoolClient === void 0 ? void 0 : hubPoolClient.configStoreClient;
        return _this;
    }
    /**
     * Retrieves a list of unique deposits from the SpokePool contract destined for the given destination chain ID.
     * @param destinationChainId The destination chain ID.
     * @returns A list of deposits.
     */
    SpokePoolClient.prototype.getDepositsForDestinationChain = function (destinationChainId) {
        return Object.values(this.depositHashes).filter(function (deposit) { return deposit.destinationChainId === destinationChainId; });
    };
    /**
     * Retrieves a list of duplicate deposits matching the given deposit's deposit hash.
     * @notice A duplicate is considered any deposit sent after the original deposit with the same deposit hash.
     * @param deposit The deposit to find duplicates for.
     * @returns A list of duplicate deposits. Does NOT include the original deposit
     * unless the original deposit is a duplicate.
     */
    SpokePoolClient.prototype._getDuplicateDeposits = function (deposit) {
        var _a;
        var depositHash = getRelayEventKey(deposit);
        return (_a = this.duplicateDepositHashes[depositHash]) !== null && _a !== void 0 ? _a : [];
    };
    /**
     * Returns a list of all deposits including any duplicate ones. Designed only to be used in use cases where
     * all deposits are required, regardless of duplicates. For example, the Dataworker can use this to refund
     * expired deposits including for duplicates.
     * @param destinationChainId
     * @returns A list of deposits
     */
    SpokePoolClient.prototype.getDepositsForDestinationChainWithDuplicates = function (destinationChainId) {
        var _this = this;
        var deposits = this.getDepositsForDestinationChain(destinationChainId);
        var duplicateDeposits = deposits.reduce(function (acc, deposit) {
            var duplicates = _this._getDuplicateDeposits(deposit);
            return acc.concat(duplicates);
        }, []);
        return sortEventsAscendingInPlace(deposits.concat(duplicateDeposits.flat()));
    };
    /**
     * Retrieves a list of deposits from the SpokePool contract that are associated with this spoke pool.
     * @returns A list of deposits.
     * @note This method returns all deposits, regardless of destination chain ID in sorted order.
     */
    SpokePoolClient.prototype.getDeposits = function (filter) {
        var deposits = Object.values(this.depositHashes);
        if (isDefined(filter)) {
            var fromBlock_1 = filter.fromBlock, toBlock_1 = filter.toBlock;
            deposits = deposits.filter(function (_a) {
                var blockNumber = _a.blockNumber;
                return blockNumber >= fromBlock_1 && toBlock_1 >= blockNumber;
            });
        }
        return sortEventsAscendingInPlace(deposits);
    };
    /**
     * Retrieves a list of the tokens that have been bridged.
     * @returns A list of tokens.
     */
    SpokePoolClient.prototype.getTokensBridged = function () {
        return this.tokensBridged;
    };
    /**
     * Retrieves a mapping of tokens and their associated destination chain IDs that can be bridged.
     * @returns A mapping of tokens and their associated destination chain IDs in a nested mapping.
     */
    SpokePoolClient.prototype.getDepositRoutes = function () {
        return this.depositRoutes;
    };
    /**
     * Retrieves a list of fills from the SpokePool contract.
     * @returns A list of fills.
     */
    SpokePoolClient.prototype.getFills = function () {
        return sortEventsAscendingInPlace(Object.values(this.fills).flat());
    };
    /**
     * Retrieves a list of fills from a specific origin chain ID.
     * @param originChainId The origin chain ID.
     * @returns A list of fills.
     */
    SpokePoolClient.prototype.getFillsForOriginChain = function (originChainId) {
        return this.fills[originChainId] || [];
    };
    /**
     * Retrieves a list of fills from a specific relayer address.
     * @param relayer The relayer address.
     * @returns A list of fills.
     */
    SpokePoolClient.prototype.getFillsForRelayer = function (relayer) {
        return this.getFills().filter(function (fill) { return fill.relayer.eq(relayer); });
    };
    /**
     * Retrieves a list of fills from a given block range.
     * @param startingBlock The starting block number.
     * @param endingBlock The ending block number.
     * @returns A list of fills.
     */
    SpokePoolClient.prototype.getFillsWithBlockInRange = function (startingBlock, endingBlock) {
        return this.getFills().filter(function (fill) { return fill.blockNumber >= startingBlock && fill.blockNumber <= endingBlock; });
    };
    /**
     * Retrieves a list of root bundle relays from the SpokePool contract.
     * @returns A list of root bundle relays.
     */
    SpokePoolClient.prototype.getRootBundleRelays = function () {
        return this.rootBundleRelays;
    };
    /**
     * Retrieves the ID of the latest root bundle.
     * @returns The ID of the latest root bundle. This will be 0 if no root bundles have been relayed.
     */
    SpokePoolClient.prototype.getLatestRootBundleId = function () {
        var _a;
        return this.rootBundleRelays.length > 0
            ? ((_a = this.rootBundleRelays[this.rootBundleRelays.length - 1]) === null || _a === void 0 ? void 0 : _a.rootBundleId) + 1
            : 0;
    };
    /**
     * Retrieves a list of relayer refund executions from the SpokePool contract.
     * @returns A list of relayer refund executions.
     */
    SpokePoolClient.prototype.getRelayerRefundExecutions = function () {
        return this.relayerRefundExecutions;
    };
    /**
     * Retrieves a list of claimed relayer refunds from the SpokePool contract.
     * @returns A list of claimed relayer refunds.
     */
    SpokePoolClient.prototype.getClaimedRelayerRefunds = function () {
        return this.claimedRelayerRefunds;
    };
    /**
     * Retrieves a list of bridged to hub pool events from the SpokePool contract.
     * @returns A list of bridged to hub pool events.
     */
    SpokePoolClient.prototype.getBridgedToHubPoolEvents = function () {
        return this.bridgedToHubPool;
    };
    /**
     * Appends a speed up signature to a specific deposit.
     * @param deposit The deposit to append the speed up signature to.
     * @returns A new deposit instance with the speed up signature appended to the deposit.
     */
    SpokePoolClient.prototype.appendMaxSpeedUpSignatureToDeposit = function (deposit) {
        var _a;
        var depositId = deposit.depositId, depositor = deposit.depositor;
        // Note: we know depositor cannot be more than 20 bytes since this is guaranteed by contracts.
        // Additionally, speed ups can only be done on EVM networks.
        var speedups = (_a = this.speedUps[depositor.toEvmAddress()]) === null || _a === void 0 ? void 0 : _a[depositId.toString()];
        if (!isDefined(speedups) || speedups.length === 0) {
            return deposit;
        }
        var maxSpeedUp = speedups.reduce(function (prev, current) {
            return prev.updatedOutputAmount.lt(current.updatedOutputAmount) ? prev : current;
        });
        // We assume that the depositor authorises SpeedUps in isolation of each other, which keeps the relayer
        // logic simple: find the SpeedUp with the lowest updatedOutputAmount, and use all of its fields.
        if (maxSpeedUp.updatedOutputAmount.gte(deposit.outputAmount)) {
            return deposit;
        }
        // Return deposit with updated params from the speedup with the lowest updated output amount.
        var updatedDeposit = __assign(__assign({}, deposit), { speedUpSignature: maxSpeedUp.depositorSignature, updatedOutputAmount: maxSpeedUp.updatedOutputAmount, updatedRecipient: maxSpeedUp.updatedRecipient, updatedMessage: maxSpeedUp.updatedMessage });
        return updatedDeposit;
    };
    /**
     * Find a deposit based on its deposit ID.
     * @notice If evaluating a fill, be sure to verify it against the resulting deposit.
     * @param depositId The unique ID of the deposit being queried.
     * @returns The corresponding deposit if found, undefined otherwise.
     */
    SpokePoolClient.prototype.getDeposit = function (depositId) {
        return Object.values(this.depositHashes).find(function (_a) {
            var _depositId = _a.depositId;
            return _depositId.eq(depositId);
        });
    };
    /**
     * Retrieves a list of slow fill requests from the SpokePool contract.
     * @returns A list of slow fill requests.
     */
    SpokePoolClient.prototype.getSlowFillRequests = function () {
        return sortEventsAscendingInPlace(Object.values(this.slowFillRequests));
    };
    /**
     * Find a SlowFillRequested event based on its deposit RelayData.
     * @param relayData RelayData field for the SlowFill request.
     * @returns The corresponding SlowFillRequest event if found, otherwise undefined.
     */
    SpokePoolClient.prototype.getSlowFillRequest = function (relayData) {
        var hash = getRelayEventKey(__assign(__assign({}, relayData), { destinationChainId: this.chainId }));
        return this.slowFillRequests[hash];
    };
    /**
     * Retrieves a list of slow fill requests for deposits from a specific origin chain ID.
     * @param originChainId The origin chain ID.
     * @returns A list of slow fill requests.
     */
    SpokePoolClient.prototype.getSlowFillRequestsForOriginChain = function (originChainId) {
        return Object.values(this.slowFillRequests).filter(function (e) { return e.originChainId === originChainId; });
    };
    /**
     * Retrieves speed up requests grouped by depositor and depositId.
     * @returns A mapping of depositor addresses to deposit ids with their corresponding speed up requests.
     */
    SpokePoolClient.prototype.getSpeedUps = function () {
        return this.speedUps;
    };
    /**
     * Find a corresponding deposit for a given fill.
     * @param fill The fill to find a corresponding deposit for.
     * @returns The corresponding deposit if found, undefined otherwise.
     */
    SpokePoolClient.prototype.getDepositForFill = function (fill) {
        var deposit = this.depositHashes[getRelayEventKey(fill)];
        var match = validateFillForDeposit(fill, deposit);
        return match.valid ? deposit : undefined;
    };
    SpokePoolClient.prototype.getFillsForDeposit = function (deposit) {
        var _a;
        return (_a = this.depositHashesToFills[this.getDepositHash(deposit)]) !== null && _a !== void 0 ? _a : [];
    };
    SpokePoolClient.prototype.isDepositFilled = function (deposit) {
        var _a;
        var depositHash = this.getDepositHash(deposit);
        var fills = (_a = this.depositHashesToFills[depositHash]) !== null && _a !== void 0 ? _a : [];
        return fills.some(function (fill) { return validateFillForDeposit(fill, deposit).valid; });
    };
    // @TODO: Remove this method after refactoring relayer repo.
    /**
     * Find the unfilled amount for a given deposit. This is the full deposit amount minus the total filled amount.
     * @param deposit The deposit to find the unfilled amount for.
     * @param fillCount The number of fills that have been applied to this deposit.
     * @param invalidFills The invalid fills that have been applied to this deposit.
     * @returns The unfilled amount.
     */
    SpokePoolClient.prototype.getValidUnfilledAmountForDeposit = function (deposit) {
        var _this = this;
        var outputAmount = deposit.outputAmount, originChainId = deposit.originChainId;
        var fillsForDeposit = this.depositHashesToFills[this.getDepositHash(deposit)];
        // If no fills then the full amount is remaining.
        if (fillsForDeposit === undefined || fillsForDeposit.length === 0) {
            return { unfilledAmount: outputAmount, fillCount: 0, invalidFills: [] };
        }
        var _a = fillsForDeposit.reduce(function (groupedFills, fill) {
            if (validateFillForDeposit(fill, deposit).valid) {
                var fillRepaymentData = __assign(__assign({}, fill), { fromLiteChain: deposit.fromLiteChain });
                var repaymentChainId = getRefundInformationFromFill(fillRepaymentData, _this.hubPoolClient, _this.hubPoolClient.latestHeightSearched).chainToSendRefundTo;
                // In order to keep this function sync, we can't call verifyFillRepayment so we'll log any fills where
                // the filler-specified repayment chain and repayment address is not a valid repayment upon
                // first glance. In other words, the repayment address is not a valid EVM address or the repayment chain
                // is not a valid EVM chain. In the case where the repayment address is not a valid EVM address, the dataworker
                // might be able to overwrite the repayment address to the msg.sender on the fill txn, but to keep this
                // functioon synchronous, we can't make that decision now. So this function might log some false positives.
                if (_this.hubPoolClient && !isSlowFill(fill) && !fill.relayer.isValidOn(repaymentChainId)) {
                    groupedFills.unrepayableFills.push(fill);
                }
                // This fill is still valid and means that the deposit cannot be filled on-chain anymore, but it
                // also can be unrepayable which we should want to log.
                groupedFills.validFills.push(fill);
            }
            else {
                groupedFills.invalidFills.push(fill);
            }
            return groupedFills;
        }, { validFills: [], invalidFills: [], unrepayableFills: [] }), validFills = _a.validFills, invalidFills = _a.invalidFills, unrepayableFills = _a.unrepayableFills;
        var logLevel = chainIsProd(originChainId) ? "warn" : "debug";
        var unrepayableFillsForDeposit = unrepayableFills.filter(function (x) { return x.depositId.eq(deposit.depositId); });
        if (unrepayableFillsForDeposit.length > 0) {
            this.logger[logLevel]({
                at: "SpokePoolClient",
                chainId: this.chainId,
                message: "Unrepayable fills found where we need to switch repayment address and or chain",
                deposit: deposit,
                unrepayableFills: Object.fromEntries(unrepayableFillsForDeposit.map(function (x) { return [x.relayer.toNative(), x]; })),
                notificationPath: "across-unrepayable-fills",
            });
        }
        // If all fills are invalid we can consider this unfilled.
        if (validFills.length === 0) {
            return { unfilledAmount: outputAmount, fillCount: 0, invalidFills: invalidFills };
        }
        return {
            unfilledAmount: bnZero,
            fillCount: validFills.length,
            invalidFills: invalidFills,
        };
    };
    /**
     * Formulate a hash for a given deposit or fill
     * @param event The deposit or fill to formulate a hash for.
     * @returns The hash.
     * @note This hash is used to match deposits and fills together.
     * @note This hash takes the form of: `${depositId}-${originChainId}`.
     */
    SpokePoolClient.prototype.getDepositHash = function (event) {
        return "".concat(event.depositId.toString(), "-").concat(event.originChainId);
    };
    SpokePoolClient.prototype.canResolveZeroAddressOutputToken = function (deposit) {
        var _a, _b;
        if (!((_a = this.hubPoolClient) === null || _a === void 0 ? void 0 : _a.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, deposit.quoteBlockNumber))) {
            return false;
        }
        else {
            var l1Token = (_b = this.hubPoolClient) === null || _b === void 0 ? void 0 : _b.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, deposit.quoteBlockNumber);
            return this.hubPoolClient.l2TokenEnabledForL1TokenAtBlock(l1Token, deposit.destinationChainId, deposit.quoteBlockNumber);
        }
    };
    /**
     * A wrapper over the `_update` method that handles errors and logs. This method additionally calls into the
     * HubPoolClient to update the state of this client with data from the HubPool contract.
     * @param eventsToQuery An optional list of events to query. If not provided, all events will be queried.
     * @returns A Promise that resolves to a SpokePoolUpdate object.
     * @note This method is the primary method for updating the state of this client externally.
     * @see _update
     */
    SpokePoolClient.prototype.update = function (eventsToQuery) {
        var _a, _b;
        if (eventsToQuery === void 0) { eventsToQuery = this._queryableEventNames(); }
        return __awaiter(this, void 0, void 0, function () {
            var duplicateEvents, update, queryResults, currentTime, searchEndBlock, _i, _c, _event, event_1, queryDepositEvents, _d, _e, event_2, querySpeedUpDepositEvents, queryRequestedSlowFillEvents, queryFilledRelayEvents, enableDepositsEvents, _f, enableDepositsEvents_1, event_3, relayedRootBundleEvents, _g, relayedRootBundleEvents_1, event_4, refundEvents, _h, refundEvents_1, _event, event_5, claimedRelayerRefundEvents, _j, claimedRelayerRefundEvents_1, event_6, bridgedToHubPoolEvents, _k, bridgedToHubPoolEvents_1, event_7;
            var _this = this;
            return __generator(this, function (_l) {
                switch (_l.label) {
                    case 0:
                        duplicateEvents = [];
                        if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) {
                            throw new Error("HubPoolClient not updated");
                        }
                        return [4 /*yield*/, this._update(eventsToQuery)];
                    case 1:
                        update = _l.sent();
                        if (!update.success) {
                            return [2 /*return*/];
                        }
                        queryResults = update.events, currentTime = update.currentTime, searchEndBlock = update.searchEndBlock;
                        if (eventsToQuery.includes("TokensBridged")) {
                            for (_i = 0, _c = queryResults[eventsToQuery.indexOf("TokensBridged")]; _i < _c.length; _i++) {
                                _event = _c[_i];
                                event_1 = _event;
                                this.tokensBridged.push(__assign(__assign({}, event_1), { l2TokenAddress: toAddressType(event_1.l2TokenAddress, this.chainId) }));
                            }
                        }
                        queryDepositEvents = function (eventName) { return __awaiter(_this, void 0, void 0, function () {
                            var depositEvents, quoteBlockNumbers, _loop_1, this_1, _i, depositEvents_1, event_8;
                            var _this = this;
                            var _a;
                            return __generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0:
                                        depositEvents = ((_a = queryResults[eventsToQuery.indexOf(eventName)]) !== null && _a !== void 0 ? _a : []).map(function (_event) {
                                            var event = _event;
                                            return __assign(__assign({}, event), { depositor: toAddressType(event.depositor, _this.chainId), recipient: toAddressType(event.recipient, event.destinationChainId), inputToken: toAddressType(event.inputToken, _this.chainId), outputToken: toAddressType(event.outputToken, event.destinationChainId), exclusiveRelayer: toAddressType(event.exclusiveRelayer, event.destinationChainId) });
                                        });
                                        if (depositEvents.length > 0) {
                                            this.log("debug", "Using ".concat(depositEvents.length, " newly queried ").concat(eventName, " deposit events for chain ").concat(this.chainId), {
                                                earliestEvent: depositEvents[0].blockNumber,
                                            });
                                        }
                                        return [4 /*yield*/, this.getBlockNumbers(depositEvents.map(function (e) { return e.quoteTimestamp; }))];
                                    case 1:
                                        quoteBlockNumbers = _b.sent();
                                        _loop_1 = function (event_8) {
                                            var quoteBlockNumber = quoteBlockNumbers[Number(event_8.quoteTimestamp)];
                                            // Derive and append the common properties that are not part of the onchain event.
                                            var deposit = __assign(__assign({}, event_8), { messageHash: getMessageHash(event_8.message), quoteBlockNumber: quoteBlockNumber, originChainId: this_1.chainId, 
                                                // The following properties are placeholders to be updated immediately.
                                                fromLiteChain: true, toLiteChain: true });
                                            deposit.fromLiteChain = this_1.isOriginLiteChain(deposit);
                                            deposit.toLiteChain = this_1.isDestinationLiteChain(deposit);
                                            if (deposit.outputToken.isZeroAddress()) {
                                                deposit.outputToken = this_1.getDestinationTokenForDeposit(deposit);
                                            }
                                            if (this_1.depositHashes[getRelayEventKey(deposit)] !== undefined) {
                                                // Sanity check that this event is not a duplicate, even though the relay data hash is a duplicate.
                                                var allDeposits = this_1._getDuplicateDeposits(deposit).concat(this_1.depositHashes[getRelayEventKey(deposit)]);
                                                if (allDeposits.some(function (e) { return duplicateEvent(deposit, e); })) {
                                                    duplicateEvents.push(event_8);
                                                    return "continue";
                                                }
                                                assign(this_1.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]);
                                                return "continue";
                                            }
                                            assign(this_1.depositHashes, [getRelayEventKey(deposit)], deposit);
                                        };
                                        this_1 = this;
                                        for (_i = 0, depositEvents_1 = depositEvents; _i < depositEvents_1.length; _i++) {
                                            event_8 = depositEvents_1[_i];
                                            _loop_1(event_8);
                                        }
                                        return [2 /*return*/];
                                }
                            });
                        }); };
                        _d = 0, _e = ["FundsDeposited"];
                        _l.label = 2;
                    case 2:
                        if (!(_d < _e.length)) return [3 /*break*/, 5];
                        event_2 = _e[_d];
                        if (!eventsToQuery.includes(event_2)) return [3 /*break*/, 4];
                        return [4 /*yield*/, queryDepositEvents(event_2)];
                    case 3:
                        _l.sent();
                        _l.label = 4;
                    case 4:
                        _d++;
                        return [3 /*break*/, 2];
                    case 5:
                        querySpeedUpDepositEvents = function (eventName) {
                            var _a;
                            var speedUpEvents = ((_a = queryResults[eventsToQuery.indexOf(eventName)]) !== null && _a !== void 0 ? _a : [])
                                .map(function (_event) {
                                var event = _event;
                                var invalid = [event.depositor, event.updatedRecipient].some(function (addr) { return !EvmAddress.validate(ethersUtils.arrayify(addr)); });
                                if (invalid) {
                                    return;
                                }
                                return __assign(__assign({}, event), { depositor: EvmAddress.from(event.depositor), updatedRecipient: EvmAddress.from(event.updatedRecipient) });
                            })
                                .filter(isDefined);
                            for (var _i = 0, speedUpEvents_1 = speedUpEvents; _i < speedUpEvents_1.length; _i++) {
                                var event_9 = speedUpEvents_1[_i];
                                var speedUp = __assign(__assign({}, event_9), { originChainId: _this.chainId });
                                assign(_this.speedUps, [speedUp.depositor.toEvmAddress(), speedUp.depositId.toString()], [speedUp]);
                                // Find deposit hash matching this speed up event and update the deposit data associated with the hash,
                                // if the hash+data exists. nb. Relying on depositId alone can produce collisions on deterministic deposit IDs.
                                var deposit = _this.getDeposit(speedUp.depositId);
                                // SpeedUp requests are only supported EVM -> EVM.
                                if (isDefined(deposit) && chainIsEvm(deposit.destinationChainId) && deposit.depositor.eq(speedUp.depositor)) {
                                    // We can assume all deposits in this lookback window are loaded in-memory already so if the depositHash
                                    // is not mapped to a deposit, then we can throw away the speedup as it can't be applied to anything.
                                    var eventKey = getRelayEventKey(deposit);
                                    _this.depositHashes[eventKey] = _this.appendMaxSpeedUpSignatureToDeposit(deposit);
                                }
                            }
                        };
                        // Update deposits with speed up requests from depositor.
                        ["RequestedSpeedUpDeposit"].forEach(function (event) {
                            if (eventsToQuery.includes(event)) {
                                querySpeedUpDepositEvents(event);
                            }
                        });
                        queryRequestedSlowFillEvents = function (eventName) {
                            var _a, _b;
                            var _c;
                            var slowFillRequests = ((_a = queryResults[eventsToQuery.indexOf(eventName)]) !== null && _a !== void 0 ? _a : []).map(function (_event) {
                                var event = _event;
                                return __assign(__assign({}, event), { depositor: toAddressType(event.depositor, event.originChainId), recipient: toAddressType(event.recipient, _this.chainId), inputToken: toAddressType(event.inputToken, event.originChainId), outputToken: toAddressType(event.outputToken, _this.chainId), exclusiveRelayer: toAddressType(event.exclusiveRelayer, _this.chainId) });
                            });
                            for (var _i = 0, slowFillRequests_1 = slowFillRequests; _i < slowFillRequests_1.length; _i++) {
                                var event_10 = slowFillRequests_1[_i];
                                var slowFillRequest = __assign(__assign({}, event_10), { destinationChainId: _this.chainId });
                                var depositHash = getRelayEventKey(__assign(__assign({}, slowFillRequest), { destinationChainId: _this.chainId }));
                                // Sanity check that this event is not a duplicate.
                                if (_this.slowFillRequests[depositHash] !== undefined) {
                                    duplicateEvents.push(event_10);
                                    continue;
                                }
                                (_b = (_c = _this.slowFillRequests)[depositHash]) !== null && _b !== void 0 ? _b : (_c[depositHash] = slowFillRequest);
                            }
                        };
                        ["RequestedSlowFill"].forEach(function (event) {
                            if (eventsToQuery.includes(event)) {
                                queryRequestedSlowFillEvents(event);
                            }
                        });
                        queryFilledRelayEvents = function (eventName) {
                            var _a, _b;
                            var fillEvents = ((_a = queryResults[eventsToQuery.indexOf(eventName)]) !== null && _a !== void 0 ? _a : []).map(function (_event) {
                                var event = _event;
                                return __assign(__assign({}, event), { depositor: toAddressType(event.depositor, event.originChainId), recipient: toAddressType(event.recipient, _this.chainId), inputToken: toAddressType(event.inputToken, event.originChainId), outputToken: toAddressType(event.outputToken, _this.chainId), exclusiveRelayer: toAddressType(event.exclusiveRelayer, _this.chainId), relayer: toAddressType(event.relayer, _this.chainId), relayExecutionInfo: __assign(__assign({}, event.relayExecutionInfo), { updatedRecipient: toAddressType(event.relayExecutionInfo.updatedRecipient, _this.chainId) }) });
                            });
                            if (fillEvents.length > 0) {
                                _this.log("debug", "Using ".concat(fillEvents.length, " newly queried ").concat(eventName, " events for chain ").concat(_this.chainId), {
                                    earliestEvent: fillEvents[0].blockNumber,
                                });
                            }
                            var _loop_2 = function (event_11) {
                                var fill = __assign(__assign({}, event_11), { destinationChainId: _this.chainId });
                                // Sanity check that this event is not a duplicate.
                                var duplicateFill = (_b = _this.fills[fill.originChainId]) === null || _b === void 0 ? void 0 : _b.find(function (f) { return duplicateEvent(fill, f); });
                                if (duplicateFill) {
                                    duplicateEvents.push(event_11);
                                    return "continue";
                                }
                                assign(_this.fills, [fill.originChainId], [fill]);
                                assign(_this.depositHashesToFills, [_this.getDepositHash(fill)], [fill]);
                            };
                            // @note The type assertions here suppress errors that might arise due to incomplete types. For now, verify via
                            // test that the types are complete. A broader change in strategy for safely unpacking events will be introduced.
                            for (var _i = 0, fillEvents_1 = fillEvents; _i < fillEvents_1.length; _i++) {
                                var event_11 = fillEvents_1[_i];
                                _loop_2(event_11);
                            }
                        };
                        // Update observed fills with ingested event data.
                        ["FilledRelay"].forEach(function (event) {
                            if (eventsToQuery.includes(event)) {
                                queryFilledRelayEvents(event);
                            }
                        });
                        if (eventsToQuery.includes("EnabledDepositRoute")) {
                            enableDepositsEvents = queryResults[eventsToQuery.indexOf("EnabledDepositRoute")].map(function (_event) {
                                var event = _event;
                                return __assign(__assign({}, event), { originToken: toAddressType(event.originToken, CHAIN_IDs.MAINNET) });
                            });
                            for (_f = 0, enableDepositsEvents_1 = enableDepositsEvents; _f < enableDepositsEvents_1.length; _f++) {
                                event_3 = enableDepositsEvents_1[_f];
                                assign(this.depositRoutes, [event_3.originToken.toBytes32(), event_3.destinationChainId], event_3.enabled);
                            }
                        }
                        if (eventsToQuery.includes("RelayedRootBundle")) {
                            relayedRootBundleEvents = queryResults[eventsToQuery.indexOf("RelayedRootBundle")];
                            for (_g = 0, relayedRootBundleEvents_1 = relayedRootBundleEvents; _g < relayedRootBundleEvents_1.length; _g++) {
                                event_4 = relayedRootBundleEvents_1[_g];
                                this.rootBundleRelays.push(event_4);
                            }
                        }
                        if (eventsToQuery.includes("ExecutedRelayerRefundRoot")) {
                            refundEvents = queryResults[eventsToQuery.indexOf("ExecutedRelayerRefundRoot")];
                            for (_h = 0, refundEvents_1 = refundEvents; _h < refundEvents_1.length; _h++) {
                                _event = refundEvents_1[_h];
                                event_5 = _event;
                                this.relayerRefundExecutions.push(__assign(__assign({}, event_5), { l2TokenAddress: toAddressType(event_5.l2TokenAddress, this.chainId), refundAddresses: event_5.refundAddresses.map(function (addr) { return toAddressType(addr, _this.chainId); }) }));
                            }
                        }
                        if (eventsToQuery.includes("ClaimedRelayerRefund")) {
                            claimedRelayerRefundEvents = ((_a = queryResults[eventsToQuery.indexOf("ClaimedRelayerRefund")]) !== null && _a !== void 0 ? _a : []);
                            for (_j = 0, claimedRelayerRefundEvents_1 = claimedRelayerRefundEvents; _j < claimedRelayerRefundEvents_1.length; _j++) {
                                event_6 = claimedRelayerRefundEvents_1[_j];
                                this.claimedRelayerRefunds.push(__assign(__assign({}, event_6), { amount: event_6.amount || event_6.claimAmount }));
                            }
                        }
                        if (eventsToQuery.includes("BridgedToHubPool")) {
                            bridgedToHubPoolEvents = ((_b = queryResults[eventsToQuery.indexOf("BridgedToHubPool")]) !== null && _b !== void 0 ? _b : []);
                            for (_k = 0, bridgedToHubPoolEvents_1 = bridgedToHubPoolEvents; _k < bridgedToHubPoolEvents_1.length; _k++) {
                                event_7 = bridgedToHubPoolEvents_1[_k];
                                this.bridgedToHubPool.push(event_7);
                            }
                        }
                        if (duplicateEvents.length > 0) {
                            this.log("debug", "Duplicate events listed", {
                                duplicateEvents: duplicateEvents,
                            });
                            this.log("error", "Duplicate events detected, check debug logs");
                        }
                        // Next iteration should start off from where this one ended.
                        this.currentTime = currentTime;
                        this.latestHeightSearched = searchEndBlock;
                        this.firstHeightToSearch = searchEndBlock + 1;
                        this.eventSearchConfig.to = undefined; // Caller can re-set on subsequent updates if necessary
                        this.isUpdated = true;
                        this.log("debug", "SpokePool client for chain ".concat(this.chainId, " updated!"), {
                            nextFirstHeightToSearch: this.firstHeightToSearch,
                        });
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Resolve a given timestamp to a block number on the HubPool chain via the HubPoolClient.
     * @param timestamp A single timestamp to be resolved via the HubPoolClient.
     * @returns The block number on the HubPool chain corresponding to the supplied timestamp.
     */
    SpokePoolClient.prototype.getBlockNumber = function (timestamp) {
        var _a, _b;
        return (_b = (_a = this.hubPoolClient) === null || _a === void 0 ? void 0 : _a.getBlockNumber(timestamp)) !== null && _b !== void 0 ? _b : Promise.resolve(MAX_BIG_INT.toNumber());
    };
    /**
     * For an array of timestamps, resolve each timestamp to a block number on the HubPool chain via the HubPoolClient.
     * @param timestamps Array of timestamps to be resolved to a block number via the HubPoolClient.
     * @returns A mapping of quoteTimestamp -> HubPool block number.
     */
    SpokePoolClient.prototype.getBlockNumbers = function (timestamps) {
        var _a, _b;
        return ((_b = (_a = this.hubPoolClient) === null || _a === void 0 ? void 0 : _a.getBlockNumbers(timestamps)) !== null && _b !== void 0 ? _b : Promise.resolve(Object.fromEntries(timestamps.map(function (timestamp) { return [timestamp, MAX_BIG_INT.toNumber()]; }))));
    };
    /**
     * Retrieves the destination token for a given deposit.
     * @param deposit The deposit to retrieve the destination token for.
     * @returns The destination token.
     */
    SpokePoolClient.prototype.getDestinationTokenForDeposit = function (deposit) {
        if (!this.canResolveZeroAddressOutputToken(deposit)) {
            return toAddressType(ZERO_ADDRESS, CHAIN_IDs.MAINNET);
        }
        // L1 token should be resolved if we get here:
        var l1Token = this.hubPoolClient.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, deposit.quoteBlockNumber);
        var counterpartToken = this.hubPoolClient.getL2TokenForL1TokenAtBlock(l1Token, deposit.destinationChainId, deposit.quoteBlockNumber);
        return counterpartToken !== null && counterpartToken !== void 0 ? counterpartToken : toAddressType(ZERO_ADDRESS, CHAIN_IDs.MAINNET);
    };
    /**
     * Performs a log for a specific level, message and data.
     * @param level The log level.
     * @param message The log message.
     * @param data Optional data to log.
     */
    SpokePoolClient.prototype.log = function (level, message, data) {
        this.logger[level](__assign({ at: "SpokePoolClient", chainId: this.chainId, message: message }, data));
    };
    /**
     * Retrieves the current time from the SpokePool contract.
     * @returns The current time, which will be 0 if there has been no update() yet.
     */
    SpokePoolClient.prototype.getCurrentTime = function () {
        return this.currentTime;
    };
    /**
     * Determines whether a deposit originates from a lite chain.
     * @param deposit The deposit to evaluate.
     * @returns True if the deposit originates from a lite chain, false otherwise. If the hub pool client is not defined,
     *          this method will return false.
     */
    SpokePoolClient.prototype.isOriginLiteChain = function (deposit) {
        var _a, _b;
        return (_b = (_a = this.configStoreClient) === null || _a === void 0 ? void 0 : _a.isChainLiteChainAtTimestamp(deposit.originChainId, deposit.quoteTimestamp)) !== null && _b !== void 0 ? _b : false;
    };
    /**
     * Determines whether the deposit destination chain is a lite chain.
     * @param deposit The deposit to evaluate.
     * @returns True if the deposit is destined to a lite chain, false otherwise. If the hub pool client is not defined,
     *          this method will return false.
     */
    SpokePoolClient.prototype.isDestinationLiteChain = function (deposit) {
        var _a, _b;
        return ((_b = (_a = this.configStoreClient) === null || _a === void 0 ? void 0 : _a.isChainLiteChainAtTimestamp(deposit.destinationChainId, deposit.quoteTimestamp)) !== null && _b !== void 0 ? _b : false);
    };
    return SpokePoolClient;
}(BaseAbstractClient));
export { SpokePoolClient };
//# sourceMappingURL=SpokePoolClient.js.map