import { __assign, __awaiter, __extends, __generator, __spreadArray } from "tslib";
import assert from "assert";
import _ from "lodash";
import { DEFAULT_CACHING_SAFE_LAG, DEFAULT_CACHING_TTL, TOKEN_SYMBOLS_MAP } from "../constants";
import * as lpFeeCalculator from "../lpFeeCalculator";
import { EVMBlockFinder } from "../arch/evm";
import { BigNumber, bnZero, dedupArray, assign, fetchTokenInfo, getCachedBlockForTimestamp, getCurrentTime, getNetworkName, isDefined, mapAsync, paginatedEventQuery, shouldCache, sortEventsDescending, spreadEventWithBlockNumber, toBN, getTokenInfo, getUsdcSymbol, chainIsSvm, getDeployedAddress, SvmAddress, EvmAddress, } from "../utils";
import { BaseAbstractClient, isUpdateFailureReason, UpdateFailureReason } from "./BaseAbstractClient";
var HubPoolClient = /** @class */ (function (_super) {
    __extends(HubPoolClient, _super);
    function HubPoolClient(logger, hubPool, configStoreClient, deploymentBlock, chainId, eventSearchConfig, configOverride, cachingMechanism) {
        if (deploymentBlock === void 0) { deploymentBlock = 0; }
        if (chainId === void 0) { chainId = 1; }
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        if (configOverride === void 0) { configOverride = {
            ignoredHubExecutedBundles: [],
            ignoredHubProposedBundles: [],
        }; }
        var _this = _super.call(this, eventSearchConfig, cachingMechanism) || this;
        _this.logger = logger;
        _this.hubPool = hubPool;
        _this.configStoreClient = configStoreClient;
        _this.deploymentBlock = deploymentBlock;
        _this.chainId = chainId;
        _this.configOverride = configOverride;
        // L1Token -> destinationChainId -> destinationToken
        _this.l1TokensToDestinationTokens = {};
        _this.l1Tokens = []; // L1Tokens and their associated info.
        // @dev `token` here is a 20-byte hex sting
        _this.lpTokens = {};
        _this.proposedRootBundles = [];
        _this.canceledRootBundles = [];
        _this.disputedRootBundles = [];
        _this.executedRootBundles = [];
        _this.crossChainContracts = {};
        _this.l1TokensToDestinationTokensWithBlock = {};
        _this.latestHeightSearched = Math.min(deploymentBlock - 1, 0);
        _this.firstHeightToSearch = eventSearchConfig.from;
        var provider = _this.hubPool.provider;
        _this.blockFinder = new EVMBlockFinder(provider);
        return _this;
    }
    HubPoolClient.prototype.hubPoolEventFilters = function () {
        return {
            SetPoolRebalanceRoute: this.hubPool.filters.SetPoolRebalanceRoute(),
            L1TokenEnabledForLiquidityProvision: this.hubPool.filters.L1TokenEnabledForLiquidityProvision(),
            ProposeRootBundle: this.hubPool.filters.ProposeRootBundle(),
            RootBundleCanceled: this.hubPool.filters.RootBundleCanceled(),
            RootBundleDisputed: this.hubPool.filters.RootBundleDisputed(),
            RootBundleExecuted: this.hubPool.filters.RootBundleExecuted(),
            CrossChainContractsSet: this.hubPool.filters.CrossChainContractsSet(),
        };
    };
    HubPoolClient.prototype.hasPendingProposal = function () {
        return this.pendingRootBundle !== undefined;
    };
    HubPoolClient.prototype.getPendingRootBundle = function () {
        return this.pendingRootBundle;
    };
    HubPoolClient.prototype.getProposedRootBundles = function () {
        return this.proposedRootBundles;
    };
    HubPoolClient.prototype.getCancelledRootBundles = function () {
        return this.canceledRootBundles;
    };
    HubPoolClient.prototype.getDisputedRootBundles = function () {
        return this.disputedRootBundles;
    };
    HubPoolClient.prototype.getExecutedRootBundles = function () {
        return this.executedRootBundles;
    };
    HubPoolClient.prototype.getSpokePoolForBlock = function (chain, block) {
        if (block === void 0) { block = Number.MAX_SAFE_INTEGER; }
        if (!this.crossChainContracts[chain]) {
            throw new Error("No cross chain contracts set for ".concat(chain));
        }
        var mostRecentSpokePoolUpdateBeforeBlock = sortEventsDescending(this.crossChainContracts[chain]).find(function (crossChainContract) { return crossChainContract.blockNumber <= block; });
        if (!mostRecentSpokePoolUpdateBeforeBlock) {
            throw new Error("No cross chain contract found before block ".concat(block, " for chain ").concat(chain));
        }
        else {
            return mostRecentSpokePoolUpdateBeforeBlock.spokePool;
        }
    };
    HubPoolClient.prototype.getSpokePoolActivationBlock = function (chain, spokePool) {
        // Return first time that this spoke pool was registered in the HubPool as a cross chain contract. We can use
        // this block as the oldest block that we should query for SpokePoolClient purposes.
        var mostRecentSpokePoolUpdateBeforeBlock = this.crossChainContracts[chain].find(function (crossChainContract) {
            return crossChainContract.spokePool.eq(spokePool);
        });
        return mostRecentSpokePoolUpdateBeforeBlock === null || mostRecentSpokePoolUpdateBeforeBlock === void 0 ? void 0 : mostRecentSpokePoolUpdateBeforeBlock.blockNumber;
    };
    // Returns the latest L2 token to use for an L1 token as of the input hub block.
    HubPoolClient.prototype.getL2TokenForL1TokenAtBlock = function (l1Token, destinationChainId, latestHubBlock) {
        var _a, _b, _c, _d;
        if (latestHubBlock === void 0) { latestHubBlock = Number.MAX_SAFE_INTEGER; }
        if (!((_b = (_a = this.l1TokensToDestinationTokensWithBlock) === null || _a === void 0 ? void 0 : _a[l1Token.toEvmAddress()]) === null || _b === void 0 ? void 0 : _b[destinationChainId])) {
            var chain = getNetworkName(destinationChainId);
            var symbol = ((_c = this.l1Tokens.find(function (_a) {
                var address = _a.address;
                return address.eq(l1Token);
            })) !== null && _c !== void 0 ? _c : { symbol: l1Token.toString() }).symbol;
            throw new Error("Could not find SpokePool mapping for ".concat(symbol, " on ").concat(chain, " and L1 token ").concat(l1Token));
        }
        // Find the last mapping published before the target block.
        var l2Token = sortEventsDescending(this.l1TokensToDestinationTokensWithBlock[l1Token.toEvmAddress()][destinationChainId]).find(function (mapping) { return mapping.blockNumber <= latestHubBlock; });
        if (!l2Token) {
            var chain = getNetworkName(destinationChainId);
            var symbol = ((_d = this.l1Tokens.find(function (_a) {
                var address = _a.address;
                return address.eq(l1Token);
            })) !== null && _d !== void 0 ? _d : { symbol: l1Token.toString() }).symbol;
            throw new Error("Could not find SpokePool mapping for ".concat(symbol, " on ").concat(chain, " at or before HubPool block ").concat(latestHubBlock, "!"));
        }
        return l2Token.l2Token;
    };
    // Returns the latest L1 token to use for an L2 token as of the input hub block.
    HubPoolClient.prototype.getL1TokenForL2TokenAtBlock = function (l2Token, destinationChainId, latestHubBlock) {
        var _this = this;
        if (latestHubBlock === void 0) { latestHubBlock = Number.MAX_SAFE_INTEGER; }
        var l2Tokens = Object.keys(this.l1TokensToDestinationTokensWithBlock)
            .filter(function (l1Token) { return _this.l2TokenEnabledForL1Token(EvmAddress.from(l1Token), destinationChainId); })
            .map(function (l1Token) {
            // Return all matching L2 token mappings that are equal to or earlier than the target block.
            // @dev Since tokens on L2s (like Solana) can have 32 byte addresses, filter on the lower 20 bytes of the token only.
            return _this.l1TokensToDestinationTokensWithBlock[l1Token][destinationChainId].filter(function (dstTokenWithBlock) {
                return dstTokenWithBlock.l2Token.truncateToBytes20() === l2Token.truncateToBytes20() &&
                    dstTokenWithBlock.blockNumber <= latestHubBlock;
            });
        })
            .flat();
        if (l2Tokens.length === 0) {
            var chain = getNetworkName(destinationChainId);
            throw new Error("Could not find HubPool mapping for ".concat(l2Token, " on ").concat(chain, " at or before HubPool block ").concat(latestHubBlock, "!"));
        }
        // Find the last mapping published before the target block.
        return sortEventsDescending(l2Tokens)[0].l1Token;
    };
    HubPoolClient.prototype.getL1TokenForDeposit = function (deposit) {
        // L1-->L2 token mappings are set via PoolRebalanceRoutes which occur on mainnet,
        // so we use the latest token mapping. This way if a very old deposit is filled, the relayer can use the
        // latest L2 token mapping to find the L1 token counterpart.
        return this.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, deposit.quoteBlockNumber);
    };
    HubPoolClient.prototype.l2TokenEnabledForL1Token = function (l1Token, destinationChainId) {
        var _a, _b;
        return ((_b = (_a = this.l1TokensToDestinationTokens) === null || _a === void 0 ? void 0 : _a[l1Token.toEvmAddress()]) === null || _b === void 0 ? void 0 : _b[destinationChainId]) != undefined;
    };
    HubPoolClient.prototype.l2TokenEnabledForL1TokenAtBlock = function (l1Token, destinationChainId, hubBlockNumber) {
        var _a, _b, _c;
        // Find the last mapping published before the target block.
        var l2Token = sortEventsDescending((_c = (_b = (_a = this.l1TokensToDestinationTokensWithBlock) === null || _a === void 0 ? void 0 : _a[l1Token.toEvmAddress()]) === null || _b === void 0 ? void 0 : _b[destinationChainId]) !== null && _c !== void 0 ? _c : []).find(function (mapping) { return mapping.blockNumber <= hubBlockNumber; });
        return l2Token !== undefined;
    };
    HubPoolClient.prototype.l2TokenHasPoolRebalanceRoute = function (l2Token, l2ChainId, hubPoolBlock) {
        if (hubPoolBlock === void 0) { hubPoolBlock = this.latestHeightSearched; }
        return Object.values(this.l1TokensToDestinationTokensWithBlock).some(function (destinationTokenMapping) {
            return Object.entries(destinationTokenMapping).some(function (_a) {
                var _l2ChainId = _a[0], setPoolRebalanceRouteEvents = _a[1];
                return setPoolRebalanceRouteEvents.some(function (e) {
                    return (e.blockNumber <= hubPoolBlock &&
                        e.l2Token.truncateToBytes20() === l2Token.truncateToBytes20() &&
                        Number(_l2ChainId) === l2ChainId);
                });
            });
        });
    };
    /**
     * @dev If tokenAddress + chain do not exist in TOKEN_SYMBOLS_MAP then this will throw.
     * @param tokenAddress Token address on `chain`
     * @param chainId Chain where the `tokenAddress` exists in TOKEN_SYMBOLS_MAP.
     * @returns Token info for the given token address on the L2 chain including symbol and decimal.
     */
    HubPoolClient.prototype.getTokenInfoForAddress = function (address, chainId) {
        var _a;
        var tokenInfo = getTokenInfo(address, chainId);
        // @dev Temporarily handle case where an L2 token for chain ID can map to more than one TOKEN_SYMBOLS_MAP
        // entry. For example, L2 Bridged USDC maps to both the USDC and USDC.e/USDbC entries in TOKEN_SYMBOLS_MAP.
        if (tokenInfo.symbol.toLowerCase() === "usdc" && chainId !== this.chainId) {
            tokenInfo.symbol = (_a = getUsdcSymbol(address, chainId)) !== null && _a !== void 0 ? _a : "UNKNOWN USDC";
        }
        return tokenInfo;
    };
    /**
     * Resolve a given timestamp to a block number on the HubPool chain.
     * @param timestamp A single timestamp to be resolved to a block number on the HubPool chain.
     * @returns The block number corresponding to the supplied timestamp.
     */
    HubPoolClient.prototype.getBlockNumber = function (timestamp) {
        var hints = { lowBlock: this.deploymentBlock };
        return getCachedBlockForTimestamp(this.chainId, timestamp, this.blockFinder, this.cachingMechanism, hints);
    };
    /**
     * For an array of timestamps, resolve each unique timestamp to a block number on the HubPool chain.
     * @dev Inputs are filtered for uniqueness and sorted to improve BlockFinder efficiency.
     * @dev Querying block numbers sequentially also improves BlockFinder efficiency.
     * @param timestamps Array of timestamps to be resolved to a block number on the HubPool chain.
     * @returns A mapping of quoteTimestamp -> HubPool block number.
     */
    HubPoolClient.prototype.getBlockNumbers = function (timestamps) {
        return __awaiter(this, void 0, void 0, function () {
            var sortedTimestamps, blockNumbers, _i, sortedTimestamps_1, timestamp, _a, _b;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        sortedTimestamps = dedupArray(timestamps).sort(function (x, y) { return x - y; });
                        blockNumbers = {};
                        _i = 0, sortedTimestamps_1 = sortedTimestamps;
                        _c.label = 1;
                    case 1:
                        if (!(_i < sortedTimestamps_1.length)) return [3 /*break*/, 4];
                        timestamp = sortedTimestamps_1[_i];
                        _a = blockNumbers;
                        _b = timestamp;
                        return [4 /*yield*/, this.getBlockNumber(timestamp)];
                    case 2:
                        _a[_b] = _c.sent();
                        _c.label = 3;
                    case 3:
                        _i++;
                        return [3 /*break*/, 1];
                    case 4: return [2 /*return*/, blockNumbers];
                }
            });
        });
    };
    HubPoolClient.prototype.getCurrentPoolUtilization = function (l1Token) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            var blockNumber, _b;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        if (!((_a = this.latestHeightSearched) !== null && _a !== void 0)) return [3 /*break*/, 1];
                        _b = _a;
                        return [3 /*break*/, 3];
                    case 1: return [4 /*yield*/, this.hubPool.provider.getBlockNumber()];
                    case 2:
                        _b = (_c.sent());
                        _c.label = 3;
                    case 3:
                        blockNumber = _b;
                        return [4 /*yield*/, this.getUtilization(l1Token, blockNumber, bnZero, getCurrentTime(), 0)];
                    case 4: return [2 /*return*/, _c.sent()];
                }
            });
        });
    };
    /**
     * For a HubPool token at a specific block number, compute the relevant utilization.
     * @param hubPoolToken HubPool token to query utilization for.
     * @param blocknumber Block number to query utilization at.
     * @param amount Amount to query. If set to 0, the closing utilization at blockNumber is returned.
     * @param amount timestamp Associated quoteTimestamp for query, used for caching evaluation.
     * @param timeToCache Age at which the response is able to be cached.
     * @returns HubPool utilization at `blockNumber` after optional `amount` increase in utilization.
     */
    HubPoolClient.prototype.getUtilization = function (hubPoolToken, blockNumber, depositAmount, timestamp, timeToCache) {
        return __awaiter(this, void 0, void 0, function () {
            var resolver, cache, key, result, utilization;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        resolver = function () { return __awaiter(_this, void 0, void 0, function () {
                            var overrides;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        overrides = { blockTag: blockNumber };
                                        if (!depositAmount.eq(0)) return [3 /*break*/, 2];
                                        return [4 /*yield*/, this.hubPool.callStatic.liquidityUtilizationCurrent(hubPoolToken.toEvmAddress(), overrides)];
                                    case 1: 
                                    // For zero amount, just get the utilisation at `blockNumber`.
                                    return [2 /*return*/, _a.sent()];
                                    case 2: return [4 /*yield*/, this.hubPool.callStatic.liquidityUtilizationPostRelay(hubPoolToken.toEvmAddress(), depositAmount, overrides)];
                                    case 3: return [2 /*return*/, _a.sent()];
                                }
                            });
                        }); };
                        cache = this.cachingMechanism;
                        // If there is no cache or the timestamp is not old enough to be cached, just resolve the function.
                        if (!cache || !shouldCache(getCurrentTime(), timestamp, timeToCache)) {
                            return [2 /*return*/, resolver()];
                        }
                        key = depositAmount.eq(0)
                            ? "utilization_".concat(hubPoolToken.toEvmAddress(), "_").concat(blockNumber)
                            : "utilization_".concat(hubPoolToken.toEvmAddress(), "_").concat(blockNumber, "_").concat(depositAmount.toString(), "_");
                        return [4 /*yield*/, cache.get(key)];
                    case 1:
                        result = _a.sent();
                        if (isDefined(result)) {
                            return [2 /*return*/, BigNumber.from(result)];
                        }
                        return [4 /*yield*/, resolver()];
                    case 2:
                        utilization = _a.sent();
                        if (!(cache && shouldCache(getCurrentTime(), timestamp, timeToCache))) return [3 /*break*/, 4];
                        // If we should cache the result, store it for up to DEFAULT_CACHING_TTL.
                        return [4 /*yield*/, cache.set(key, "".concat(utilization.toString()), DEFAULT_CACHING_TTL)];
                    case 3:
                        // If we should cache the result, store it for up to DEFAULT_CACHING_TTL.
                        _a.sent();
                        _a.label = 4;
                    case 4: return [2 /*return*/, utilization];
                }
            });
        });
    };
    HubPoolClient.prototype.computeRealizedLpFeePct = function (deposit) {
        return __awaiter(this, void 0, void 0, function () {
            var lpFee;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.batchComputeRealizedLpFeePct([deposit])];
                    case 1:
                        lpFee = (_a.sent())[0];
                        return [2 /*return*/, lpFee];
                }
            });
        });
    };
    HubPoolClient.prototype.batchComputeRealizedLpFeePct = function (deposits) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            var utilizationTimestamps, utilization, quoteBlocks, hubPoolTokens, getHubPoolToken, getHubPoolTokens, resolveUniqueQuoteTimestamps, resolveUtilization, computeRealizedLpFeePct, timeToCache, quoteTimestamps, _b, _c;
            var _this = this;
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        assert(deposits.length > 0, "No deposits supplied to batchComputeRealizedLpFeePct");
                        if (!isDefined(this.currentTime)) {
                            throw new Error("HubPoolClient has not set a currentTime");
                        }
                        utilizationTimestamps = {};
                        utilization = {};
                        quoteBlocks = {};
                        hubPoolTokens = {};
                        getHubPoolToken = function (deposit, quoteBlockNumber) {
                            var _a;
                            var tokenKey = "".concat(deposit.originChainId, "-").concat(deposit.inputToken);
                            if (_this.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, quoteBlockNumber)) {
                                return ((_a = hubPoolTokens[tokenKey]) !== null && _a !== void 0 ? _a : (hubPoolTokens[tokenKey] = _this.getL1TokenForDeposit(__assign(__assign({}, deposit), { quoteBlockNumber: quoteBlockNumber }))));
                            }
                            else
                                return undefined;
                        };
                        getHubPoolTokens = function () { return dedupArray(Object.values(hubPoolTokens).filter(isDefined)); };
                        resolveUniqueQuoteTimestamps = function (deposit) {
                            var _a;
                            var _b;
                            var quoteTimestamp = deposit.quoteTimestamp;
                            // Resolve the HubPool token address for this origin chainId/token pair, if it isn't already known.
                            var quoteBlockNumber = quoteBlocks[quoteTimestamp];
                            var hubPoolToken = getHubPoolToken(deposit, quoteBlockNumber);
                            if (!hubPoolToken) {
                                return;
                            }
                            // Append the quoteTimestamp for this HubPool token, if it isn't already enqueued.
                            (_a = utilizationTimestamps[_b = hubPoolToken.toEvmAddress()]) !== null && _a !== void 0 ? _a : (utilizationTimestamps[_b] = []);
                            if (!utilizationTimestamps[hubPoolToken.toEvmAddress()].includes(quoteTimestamp)) {
                                utilizationTimestamps[hubPoolToken.toEvmAddress()].push(quoteTimestamp);
                            }
                        };
                        resolveUtilization = function (hubPoolToken) { return __awaiter(_this, void 0, void 0, function () {
                            var _a, _b;
                            var _this = this;
                            return __generator(this, function (_c) {
                                switch (_c.label) {
                                    case 0:
                                        _b = (_a = Object).fromEntries;
                                        return [4 /*yield*/, mapAsync(utilizationTimestamps[hubPoolToken.toEvmAddress()], function (quoteTimestamp) { return __awaiter(_this, void 0, void 0, function () {
                                                var blockNumber, utilization;
                                                return __generator(this, function (_a) {
                                                    switch (_a.label) {
                                                        case 0:
                                                            blockNumber = quoteBlocks[quoteTimestamp];
                                                            return [4 /*yield*/, this.getUtilization(hubPoolToken, blockNumber, bnZero, // amount
                                                                quoteTimestamp, timeToCache)];
                                                        case 1:
                                                            utilization = _a.sent();
                                                            return [2 /*return*/, [blockNumber, utilization]];
                                                    }
                                                });
                                            }); })];
                                    case 1: return [2 /*return*/, _b.apply(_a, [_c.sent()])];
                                }
                            });
                        }); };
                        computeRealizedLpFeePct = function (deposit) { return __awaiter(_this, void 0, void 0, function () {
                            var originChainId, paymentChainId, inputAmount, quoteTimestamp, quoteBlock, hubPoolToken, rateModel, preUtilization, postUtilization, realizedLpFeePct;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        originChainId = deposit.originChainId, paymentChainId = deposit.paymentChainId, inputAmount = deposit.inputAmount, quoteTimestamp = deposit.quoteTimestamp;
                                        quoteBlock = quoteBlocks[quoteTimestamp];
                                        if (paymentChainId === undefined || paymentChainId === originChainId) {
                                            return [2 /*return*/, { quoteBlock: quoteBlock, realizedLpFeePct: bnZero }];
                                        }
                                        hubPoolToken = getHubPoolToken(deposit, quoteBlock);
                                        if (hubPoolToken === undefined) {
                                            throw new Error("Cannot computeRealizedLpFeePct for deposit with no pool rebalance route for input token ".concat(deposit.inputToken, " on ").concat(originChainId));
                                        }
                                        rateModel = this.configStoreClient.getRateModelForBlockNumber(hubPoolToken, originChainId, paymentChainId, quoteBlock);
                                        preUtilization = utilization[hubPoolToken.toEvmAddress()][quoteBlock];
                                        return [4 /*yield*/, this.getUtilization(hubPoolToken, quoteBlock, inputAmount, quoteTimestamp, timeToCache)];
                                    case 1:
                                        postUtilization = _a.sent();
                                        realizedLpFeePct = lpFeeCalculator.calculateRealizedLpFeePct(rateModel, preUtilization, postUtilization);
                                        return [2 /*return*/, { quoteBlock: quoteBlock, realizedLpFeePct: realizedLpFeePct }];
                                }
                            });
                        }); };
                        timeToCache = (_a = this.configOverride.timeToCache) !== null && _a !== void 0 ? _a : DEFAULT_CACHING_SAFE_LAG;
                        quoteTimestamps = dedupArray(deposits.map(function (_a) {
                            var quoteTimestamp = _a.quoteTimestamp;
                            return quoteTimestamp;
                        }));
                        return [4 /*yield*/, this.getBlockNumbers(quoteTimestamps)];
                    case 1:
                        quoteBlocks = _d.sent();
                        // Identify the unique hubPoolToken & quoteTimestamp mappings. This is used to optimise subsequent HubPool queries.
                        deposits.forEach(function (deposit) { return resolveUniqueQuoteTimestamps(deposit); });
                        _c = (_b = Object).fromEntries;
                        return [4 /*yield*/, mapAsync(getHubPoolTokens(), function (hubPoolToken) { return __awaiter(_this, void 0, void 0, function () {
                                var _a;
                                return __generator(this, function (_b) {
                                    switch (_b.label) {
                                        case 0:
                                            _a = [hubPoolToken.toEvmAddress()];
                                            return [4 /*yield*/, resolveUtilization(hubPoolToken)];
                                        case 1: return [2 /*return*/, _a.concat([
                                                _b.sent()
                                            ])];
                                    }
                                });
                            }); })];
                    case 2:
                        // For each token / quoteBlock pair, resolve the utilisation for each quoted block.
                        // This can be reused for each deposit with the same HubPool token and quoteTimestamp pair.
                        utilization = _c.apply(_b, [_d.sent()]);
                        return [4 /*yield*/, mapAsync(deposits, function (deposit) { return __awaiter(_this, void 0, void 0, function () {
                                var quoteBlock;
                                return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0:
                                            quoteBlock = quoteBlocks[deposit.quoteTimestamp];
                                            if (!this.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, quoteBlock)) return [3 /*break*/, 2];
                                            return [4 /*yield*/, computeRealizedLpFeePct(deposit)];
                                        case 1: return [2 /*return*/, _a.sent()];
                                        case 2: return [2 /*return*/, {
                                                quoteBlock: quoteBlock,
                                                realizedLpFeePct: bnZero,
                                            }];
                                    }
                                });
                            }); })];
                    case 3: 
                    // For each deposit, compute the post-relay HubPool utilisation independently.
                    // @dev The caller expects to receive an array in the same length and ordering as the input `deposits`.
                    return [2 /*return*/, _d.sent()];
                }
            });
        });
    };
    HubPoolClient.prototype.getL1Tokens = function () {
        return this.l1Tokens;
    };
    HubPoolClient.prototype.getTokenInfoForL1Token = function (l1Token) {
        return this.l1Tokens.find(function (token) { return token.address.eq(l1Token); });
    };
    HubPoolClient.prototype.getLpTokenInfoForL1Token = function (l1Token) {
        return this.lpTokens[l1Token.toEvmAddress()];
    };
    HubPoolClient.prototype.areTokensEquivalent = function (tokenA, chainIdA, tokenB, chainIdB, hubPoolBlock) {
        if (hubPoolBlock === void 0) { hubPoolBlock = this.latestHeightSearched; }
        if (!this.l2TokenHasPoolRebalanceRoute(tokenA, chainIdA, hubPoolBlock) ||
            !this.l2TokenHasPoolRebalanceRoute(tokenB, chainIdB, hubPoolBlock)) {
            return false;
        }
        // Resolve both SpokePool tokens back to their respective HubPool tokens and verify that they match.
        var l1TokenA = this.getL1TokenForL2TokenAtBlock(tokenA, chainIdA, hubPoolBlock);
        var l1TokenB = this.getL1TokenForL2TokenAtBlock(tokenB, chainIdB, hubPoolBlock);
        if (!l1TokenA.eq(l1TokenB)) {
            return false;
        }
        // Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
        var _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock);
        var _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock);
        return tokenA.eq(_tokenA) && tokenB.eq(_tokenB);
    };
    HubPoolClient.prototype.getSpokeActivationBlockForChain = function (chainId) {
        var _a;
        return (_a = this.getSpokePoolActivationBlock(chainId, this.getSpokePoolForBlock(chainId))) !== null && _a !== void 0 ? _a : 0;
    };
    // Root bundles are valid if all of their pool rebalance leaves have been executed before the next bundle, or the
    // latest mainnet block to search. Whichever comes first.
    HubPoolClient.prototype.isRootBundleValid = function (rootBundle, latestMainnetBlock) {
        var nextRootBundle = this.getFollowingRootBundle(rootBundle);
        var executedLeafCount = this.getExecutedLeavesForRootBundle(rootBundle, nextRootBundle ? Math.min(nextRootBundle.blockNumber, latestMainnetBlock) : latestMainnetBlock);
        return executedLeafCount.length === rootBundle.poolRebalanceLeafCount;
    };
    // This should find the ProposeRootBundle event whose bundle block number for `chain` is closest to the `block`
    // without being smaller. It returns the bundle block number for the chain or undefined if not matched.
    HubPoolClient.prototype.getRootBundleEvalBlockNumberContainingBlock = function (latestMainnetBlock, block, chain, chainIdListOverride) {
        var chainIdList = chainIdListOverride !== null && chainIdListOverride !== void 0 ? chainIdListOverride : this.configStoreClient.getChainIdIndicesForBlock(latestMainnetBlock);
        var endingBlockNumber;
        // Search proposed root bundles in reverse chronological order.
        for (var i = this.proposedRootBundles.length - 1; i >= 0; i--) {
            var rootBundle = this.proposedRootBundles[i];
            var nextRootBundle = this.getFollowingRootBundle(rootBundle);
            if (!this.isRootBundleValid(rootBundle, nextRootBundle ? nextRootBundle.blockNumber : latestMainnetBlock)) {
                continue;
            }
            // 0 is the default value bundleEvalBlockNumber.
            var bundleEvalBlockNumber = this.getBundleEndBlockForChain(rootBundle, chain, chainIdList);
            // Since we're iterating from newest to oldest, bundleEvalBlockNumber is only decreasing, and if the
            // bundleEvalBlockNumber is smaller than the target block, then we should return the last set `endingBlockNumber`.
            if (bundleEvalBlockNumber <= block) {
                if (bundleEvalBlockNumber === block) {
                    endingBlockNumber = bundleEvalBlockNumber;
                }
                break;
            }
            endingBlockNumber = bundleEvalBlockNumber;
        }
        return endingBlockNumber;
    };
    // TODO: This might not be necessary since the cumulative root bundle count doesn't grow fast enough, but consider
    // using _.findLast/_.find instead of resorting the arrays if these functions begin to take a lot time.
    HubPoolClient.prototype.getProposedRootBundlesInBlockRange = function (startingBlock, endingBlock) {
        return this.proposedRootBundles.filter(function (bundle) { return bundle.blockNumber >= startingBlock && bundle.blockNumber <= endingBlock; });
    };
    HubPoolClient.prototype.getCancelledRootBundlesInBlockRange = function (startingBlock, endingBlock) {
        return sortEventsDescending(this.canceledRootBundles).filter(function (bundle) { return bundle.blockNumber >= startingBlock && bundle.blockNumber <= endingBlock; });
    };
    HubPoolClient.prototype.getDisputedRootBundlesInBlockRange = function (startingBlock, endingBlock) {
        return sortEventsDescending(this.disputedRootBundles).filter(function (bundle) { return bundle.blockNumber >= startingBlock && bundle.blockNumber <= endingBlock; });
    };
    /**
     * Retrieves token mappings that were modified within a specified block range.
     * @param startingBlock - The starting block of the range (inclusive).
     * @param endingBlock - The ending block of the range (inclusive).
     * @returns An array of destination tokens, each containing the `l2ChainId`, that
     *          were modified within the given block range.
     */
    HubPoolClient.prototype.getTokenMappingsModifiedInBlockRange = function (startingBlock, endingBlock) {
        // This function iterates over `l1TokensToDestinationTokensWithBlock`, a nested
        // structure of L1 tokens mapped to destination chain IDs, each containing lists
        // of destination tokens with associated block numbers.
        return (Object.values(this.l1TokensToDestinationTokensWithBlock)
            .flatMap(function (destinationTokens) {
            // Map through destination chain IDs and their associated tokens
            return Object.entries(destinationTokens).flatMap(function (_a) {
                var destinationChainId = _a[0], tokensWithBlock = _a[1];
                // Map the tokens to add the l2ChainId field for each token
                return tokensWithBlock.map(function (token) { return (__assign(__assign({}, token), { l2ChainId: Number(destinationChainId) })); });
            });
        })
            // Filter out tokens whose blockNumber is outside the block range
            .filter(function (token) { return token.blockNumber >= startingBlock && token.blockNumber <= endingBlock; }));
    };
    HubPoolClient.prototype.getLatestProposedRootBundle = function () {
        return this.proposedRootBundles[this.proposedRootBundles.length - 1];
    };
    HubPoolClient.prototype.getFollowingRootBundle = function (currentRootBundle) {
        var index = _.findLastIndex(this.proposedRootBundles, function (bundle) { return bundle.blockNumber === currentRootBundle.blockNumber; });
        // If index of current root bundle is not found or is the last bundle, return undefined.
        if (index === -1 || index === this.proposedRootBundles.length - 1) {
            return undefined;
        }
        return this.proposedRootBundles[index + 1];
    };
    HubPoolClient.prototype.getExecutedLeavesForRootBundle = function (rootBundle, latestMainnetBlockToSearch) {
        return this.executedRootBundles.filter(function (executedLeaf) {
            return executedLeaf.blockNumber <= latestMainnetBlockToSearch &&
                // Note: We can use > instead of >= here because a leaf can never be executed in same block as its root
                // proposal due to bundle liveness enforced by HubPool. This importantly avoids the edge case
                // where the execution all leaves occurs in the same block as the next proposal, leading us to think
                // that the next proposal is fully executed when its not.
                executedLeaf.blockNumber > rootBundle.blockNumber;
        });
    };
    HubPoolClient.prototype.getValidatedRootBundles = function (latestMainnetBlock) {
        var _this = this;
        if (latestMainnetBlock === void 0) { latestMainnetBlock = Number.MAX_SAFE_INTEGER; }
        return this.proposedRootBundles.filter(function (rootBundle) {
            if (rootBundle.blockNumber > latestMainnetBlock) {
                return false;
            }
            return _this.isRootBundleValid(rootBundle, latestMainnetBlock);
        });
    };
    HubPoolClient.prototype.getLatestFullyExecutedRootBundle = function (latestMainnetBlock) {
        var _this = this;
        // Search for latest ProposeRootBundleExecuted event followed by all of its RootBundleExecuted event suggesting
        // that all pool rebalance leaves were executed. This ignores any proposed bundles that were partially executed.
        return _.findLast(this.proposedRootBundles, function (rootBundle) {
            if (rootBundle.blockNumber > latestMainnetBlock) {
                return false;
            }
            return _this.isRootBundleValid(rootBundle, latestMainnetBlock);
        });
    };
    HubPoolClient.prototype.getEarliestFullyExecutedRootBundle = function (latestMainnetBlock, startBlock) {
        var _this = this;
        if (startBlock === void 0) { startBlock = 0; }
        return this.proposedRootBundles.find(function (rootBundle) {
            if (rootBundle.blockNumber > latestMainnetBlock) {
                return false;
            }
            if (rootBundle.blockNumber < startBlock) {
                return false;
            }
            return _this.isRootBundleValid(rootBundle, latestMainnetBlock);
        });
    };
    // If n is negative, then return the Nth latest executed bundle, otherwise return the Nth earliest
    // executed bundle. Latest means most recent, earliest means oldest. N cannot be 0.
    // `startBlock` can be used to set the starting point from which we look forwards or backwards, depending
    // on whether n is positive or negative.
    HubPoolClient.prototype.getNthFullyExecutedRootBundle = function (n, startBlock) {
        if (n === 0) {
            throw new Error("n cannot be 0");
        }
        if (!this.latestHeightSearched) {
            throw new Error("HubPoolClient::getNthFullyExecutedRootBundle client not updated");
        }
        var bundleToReturn;
        // If n is negative, then return the Nth latest executed bundle, otherwise return the Nth earliest
        // executed bundle.
        if (n < 0) {
            var nextLatestMainnetBlock = startBlock !== null && startBlock !== void 0 ? startBlock : this.latestHeightSearched;
            for (var i = 0; i < Math.abs(n); i++) {
                bundleToReturn = this.getLatestFullyExecutedRootBundle(nextLatestMainnetBlock);
                var bundleBlockNumber = bundleToReturn ? bundleToReturn.blockNumber : 0;
                // Subtract 1 so that next `getLatestFullyExecutedRootBundle` call filters out the root bundle we just found
                // because its block number is > nextLatestMainnetBlock.
                nextLatestMainnetBlock = Math.max(0, bundleBlockNumber - 1);
            }
        }
        else {
            var nextStartBlock = startBlock !== null && startBlock !== void 0 ? startBlock : 0;
            for (var i = 0; i < n; i++) {
                bundleToReturn = this.getEarliestFullyExecutedRootBundle(this.latestHeightSearched, nextStartBlock);
                var bundleBlockNumber = bundleToReturn ? bundleToReturn.blockNumber : 0;
                // Add 1 so that next `getEarliestFullyExecutedRootBundle` call filters out the root bundle we just found
                // because its block number is < nextStartBlock.
                nextStartBlock = Math.min(bundleBlockNumber + 1, this.latestHeightSearched);
            }
        }
        return bundleToReturn;
    };
    HubPoolClient.prototype.getLatestBundleEndBlockForChain = function (chainIdList, latestMainnetBlock, chainId) {
        var latestFullyExecutedPoolRebalanceRoot = this.getLatestFullyExecutedRootBundle(latestMainnetBlock);
        // If no event, then we can return a conservative default starting block like 0,
        // or we could throw an Error.
        if (!latestFullyExecutedPoolRebalanceRoot) {
            return 0;
        }
        // Once this proposal event is found, determine its mapping of indices to chainId in its
        // bundleEvaluationBlockNumbers array using CHAIN_ID_LIST. For each chainId, their starting block number is that
        // chain's bundleEvaluationBlockNumber + 1 in this past proposal event.
        return this.getBundleEndBlockForChain(latestFullyExecutedPoolRebalanceRoot, chainId, chainIdList);
    };
    HubPoolClient.prototype.getNextBundleStartBlockNumber = function (chainIdList, latestMainnetBlock, chainId) {
        var endBlock = this.getLatestBundleEndBlockForChain(chainIdList, latestMainnetBlock, chainId);
        // This assumes that chain ID's are only added to the chain ID list over time, and that chains are never
        // deleted.
        return endBlock > 0 ? endBlock + 1 : 0;
    };
    // @dev Returns the start block of the next bundle assuming that if there is a currently outstanding root bundle proposal, it will pass liveness.
    HubPoolClient.prototype.getOptimisticBundleStartBlockNumber = function (chainIdList, latestMainnetBlock, chainId) {
        // If there is no pending root bundle, then return block ranges based on the latest fully executed root bundle.
        if (!this.hasPendingProposal()) {
            return this.getNextBundleStartBlockNumber(chainIdList, latestMainnetBlock, chainId);
        }
        // We cannot normally index `this.proposedRootBundles` since a bundle there may have been previously disputed, so only index `this.proposedRootBundles`
        // if we have a pending proposal, since this must mean that the pending root bundle is the most recent proposed root bundle.
        var latestProposedBundle = this.proposedRootBundles[this.proposedRootBundles.length - 1];
        // If there is no previous root bundle, then return 0.
        if (!isDefined(latestProposedBundle)) {
            return 0;
        }
        // Otherwise, get the bundle end block for the optimistic bundle.
        var optimisticEndBlock = this.getBundleEndBlockForChain(latestProposedBundle, chainId, chainIdList);
        // As above, this assumes that chain ID's are only added to the chain ID list over time, and that chains are never
        // deleted.
        return optimisticEndBlock > 0 ? optimisticEndBlock + 1 : 0;
    };
    HubPoolClient.prototype.getLatestExecutedRootBundleContainingL1Token = function (block, chain, l1Token) {
        // Search ExecutedRootBundles in descending block order to find the most recent event before the target block.
        return sortEventsDescending(this.executedRootBundles).find(function (executedLeaf) {
            return (executedLeaf.blockNumber <= block &&
                executedLeaf.chainId === chain &&
                executedLeaf.l1Tokens.some(function (token) { return token.eq(l1Token); }));
        });
    };
    HubPoolClient.prototype.getRunningBalanceBeforeBlockForChain = function (block, chain, l1Token) {
        var executedRootBundle = this.getLatestExecutedRootBundleContainingL1Token(block, chain, l1Token);
        return this.getRunningBalanceForToken(l1Token, executedRootBundle);
    };
    HubPoolClient.prototype.getRunningBalanceForToken = function (l1Token, executedRootBundle) {
        var runningBalance = toBN(0);
        if (executedRootBundle) {
            var indexOfL1Token = executedRootBundle.l1Tokens.findIndex(function (tokenInBundle) { return tokenInBundle.eq(l1Token); });
            if (indexOfL1Token !== -1) {
                runningBalance = executedRootBundle.runningBalances[indexOfL1Token];
            }
        }
        return { runningBalance: runningBalance };
    };
    HubPoolClient.prototype._update = function (eventNames) {
        return __awaiter(this, void 0, void 0, function () {
            var hubPoolEvents, searchConfig, supportedEvents, eventSearchConfigs, timerStart, hubPool, multicallFunctions, _a, multicallOutput, events, _b, currentTime, pendingRootBundleProposal, _events;
            var _this = this;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        hubPoolEvents = this.hubPoolEventFilters();
                        return [4 /*yield*/, this.updateSearchConfig(this.hubPool.provider)];
                    case 1:
                        searchConfig = _c.sent();
                        if (isUpdateFailureReason(searchConfig)) {
                            return [2 /*return*/, { success: false, reason: searchConfig }];
                        }
                        supportedEvents = Object.keys(hubPoolEvents);
                        if (eventNames.some(function (eventName) { return !supportedEvents.includes(eventName); })) {
                            return [2 /*return*/, { success: false, reason: UpdateFailureReason.BadRequest }];
                        }
                        eventSearchConfigs = eventNames.map(function (eventName) {
                            var _searchConfig = __assign({}, searchConfig); // shallow copy
                            // By default, an event's query range is controlled by the `searchConfig` passed in during
                            // instantiation. However, certain events generally must be queried back to HubPool genesis.
                            var overrideEvents = ["CrossChainContractsSet", "L1TokenEnabledForLiquidityProvision", "SetPoolRebalanceRoute"];
                            if (overrideEvents.includes(eventName) && !_this.isUpdated) {
                                _searchConfig.from = _this.deploymentBlock;
                            }
                            return {
                                eventName: eventName,
                                filter: hubPoolEvents[eventName],
                                searchConfig: _searchConfig,
                            };
                        });
                        this.logger.debug({
                            at: "HubPoolClient",
                            message: "Updating HubPool client",
                            searchConfig: eventSearchConfigs.map(function (_a) {
                                var eventName = _a.eventName, searchConfig = _a.searchConfig;
                                return ({ eventName: eventName, searchConfig: searchConfig });
                            }),
                        });
                        timerStart = Date.now();
                        hubPool = this.hubPool;
                        multicallFunctions = ["getCurrentTime", "rootBundleProposal"];
                        return [4 /*yield*/, Promise.all(__spreadArray([
                                hubPool.callStatic.multicall(multicallFunctions.map(function (f) { return hubPool.interface.encodeFunctionData(f); }), { blockTag: searchConfig.to })
                            ], eventSearchConfigs.map(function (config) { return paginatedEventQuery(hubPool, config.filter, config.searchConfig); }), true))];
                    case 2:
                        _a = _c.sent(), multicallOutput = _a[0], events = _a.slice(1);
                        _b = multicallFunctions.map(function (fn, idx) {
                            var output = hubPool.interface.decodeFunctionResult(fn, multicallOutput[idx]);
                            return output.length > 1 ? output : output[0];
                        }), currentTime = _b[0], pendingRootBundleProposal = _b[1];
                        this.logger.debug({
                            at: "HubPoolClient#_update",
                            message: "Time to query new events from RPC for ".concat(this.chainId, ": ").concat(Date.now() - timerStart, " ms"),
                        });
                        _events = Object.fromEntries(eventNames.map(function (eventName, idx) { return [eventName, events[idx]]; }));
                        return [2 /*return*/, {
                                success: true,
                                currentTime: currentTime,
                                pendingRootBundleProposal: __assign(__assign({}, pendingRootBundleProposal), { proposer: EvmAddress.from(pendingRootBundleProposal.proposer) }),
                                searchEndBlock: searchConfig.to,
                                events: _events,
                            }];
                }
            });
        });
    };
    HubPoolClient.prototype.update = function (eventsToQuery) {
        if (eventsToQuery === void 0) { eventsToQuery = Object.keys(this.hubPoolEventFilters()); }
        return __awaiter(this, void 0, void 0, function () {
            var update, events, currentTime, pendingRootBundleProposal, searchEndBlock, _i, _a, event_1, args, dataToAdd, solanaSpokePool, svmSpoke, truncatedAddress, _b, _c, event_2, args, destinationToken, usdcTokenSol, svmUsdc, uniqueL1Tokens, _d, tokenInfo, lpTokenInfo_1, _loop_1, this_1, _e, tokenInfo_1, info, _f, _g, event_3, executedRootBundle, l1Tokens, runningBalances, nTokens, mostRecentProposedRootBundle;
            var _h, _j, _k;
            var _this = this;
            return __generator(this, function (_l) {
                switch (_l.label) {
                    case 0:
                        if (!this.configStoreClient.isUpdated) {
                            throw new Error("ConfigStoreClient not updated");
                        }
                        return [4 /*yield*/, this._update(eventsToQuery)];
                    case 1:
                        update = _l.sent();
                        if (!update.success) {
                            if (update.reason !== UpdateFailureReason.AlreadyUpdated) {
                                throw new Error("Unable to update HubPoolClient: ".concat(update.reason));
                            }
                            // No need to touch `this.isUpdated` because it should already be set from a previous update.
                            return [2 /*return*/];
                        }
                        events = update.events, currentTime = update.currentTime, pendingRootBundleProposal = update.pendingRootBundleProposal, searchEndBlock = update.searchEndBlock;
                        if (eventsToQuery.includes("CrossChainContractsSet")) {
                            for (_i = 0, _a = events["CrossChainContractsSet"]; _i < _a.length; _i++) {
                                event_1 = _a[_i];
                                args = spreadEventWithBlockNumber(event_1);
                                dataToAdd = {
                                    spokePool: EvmAddress.from(args.spokePool),
                                    blockNumber: args.blockNumber,
                                    txnRef: args.txnRef,
                                    logIndex: args.logIndex,
                                    txnIndex: args.txnIndex,
                                    l2ChainId: args.l2ChainId,
                                };
                                // If the chain is SVM then the resulting SpokePool address will be inferred based on the lower 20
                                // bytes of the address. Matching addresses will be promoted to a full 32-byte SvmAddress type.
                                if (chainIsSvm(args.l2ChainId)) {
                                    solanaSpokePool = getDeployedAddress("SvmSpoke", args.l2ChainId);
                                    if (!solanaSpokePool) {
                                        throw new Error("SVM spoke pool not found for chain ".concat(args.l2ChainId));
                                    }
                                    svmSpoke = SvmAddress.from(solanaSpokePool);
                                    truncatedAddress = svmSpoke.truncateToBytes20();
                                    // Verify the event address matches our expected truncated address
                                    if (args.spokePool.toLowerCase() !== truncatedAddress.toLowerCase()) {
                                        throw new Error("SVM spoke pool address mismatch for chain ".concat(args.l2ChainId, ". ") +
                                            "Expected ".concat(truncatedAddress, ", got ").concat(args.spokePool));
                                    }
                                    dataToAdd.spokePool = svmSpoke;
                                }
                                assign(this.crossChainContracts, [args.l2ChainId], [dataToAdd]);
                            }
                        }
                        if (eventsToQuery.includes("SetPoolRebalanceRoute")) {
                            for (_b = 0, _c = events["SetPoolRebalanceRoute"]; _b < _c.length; _b++) {
                                event_2 = _c[_b];
                                args = spreadEventWithBlockNumber(event_2);
                                destinationToken = EvmAddress.from(args.destinationToken);
                                if (chainIsSvm(args.destinationChainId)) {
                                    usdcTokenSol = TOKEN_SYMBOLS_MAP.USDC.addresses[args.destinationChainId];
                                    svmUsdc = SvmAddress.from(usdcTokenSol);
                                    if (destinationToken.truncateToBytes20() !== svmUsdc.truncateToBytes20()) {
                                        throw new Error("SVM USDC address mismatch for chain ".concat(args.destinationChainId, ". ") +
                                            "Expected ".concat(svmUsdc.truncateToBytes20(), ", got ").concat(destinationToken));
                                    }
                                    destinationToken = svmUsdc;
                                }
                                // If the destination token is set to the zero address in an event, then this means Across should no longer
                                // rebalance to this chain.
                                if (!destinationToken.isZeroAddress()) {
                                    assign(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], destinationToken);
                                    assign(this.l1TokensToDestinationTokensWithBlock, [args.l1Token, args.destinationChainId], [
                                        {
                                            l1Token: EvmAddress.from(args.l1Token),
                                            l2Token: destinationToken,
                                            blockNumber: args.blockNumber,
                                            txnIndex: args.txnIndex,
                                            logIndex: args.logIndex,
                                            txnRef: args.txnRef,
                                        },
                                    ]);
                                }
                            }
                        }
                        if (!eventsToQuery.includes("L1TokenEnabledForLiquidityProvision")) return [3 /*break*/, 3];
                        uniqueL1Tokens = dedupArray(events["L1TokenEnabledForLiquidityProvision"].map(function (event) { return String(event.args["l1Token"]); }));
                        return [4 /*yield*/, Promise.all([
                                Promise.all(uniqueL1Tokens.map(function (l1Token) { return __awaiter(_this, void 0, void 0, function () {
                                    var tokenInfo;
                                    return __generator(this, function (_a) {
                                        switch (_a.label) {
                                            case 0: return [4 /*yield*/, fetchTokenInfo(l1Token, this.hubPool.provider)];
                                            case 1:
                                                tokenInfo = _a.sent();
                                                return [2 /*return*/, __assign(__assign({}, tokenInfo), { address: EvmAddress.from(l1Token) })];
                                        }
                                    });
                                }); })),
                                Promise.all(uniqueL1Tokens.map(function (l1Token) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0: return [4 /*yield*/, this.hubPool.pooledTokens(l1Token, { blockTag: update.searchEndBlock })];
                                        case 1: return [2 /*return*/, _a.sent()];
                                    }
                                }); }); })),
                            ])];
                    case 2:
                        _d = _l.sent(), tokenInfo = _d[0], lpTokenInfo_1 = _d[1];
                        _loop_1 = function (info) {
                            if (!this_1.l1Tokens.find(function (token) { return token.address.eq(info.address); })) {
                                if (info.decimals > 0 && info.decimals <= 18) {
                                    this_1.l1Tokens.push(info);
                                }
                                else {
                                    throw new Error("Unsupported HubPool token: ".concat(JSON.stringify(info)));
                                }
                            }
                        };
                        this_1 = this;
                        for (_e = 0, tokenInfo_1 = tokenInfo; _e < tokenInfo_1.length; _e++) {
                            info = tokenInfo_1[_e];
                            _loop_1(info);
                        }
                        uniqueL1Tokens.forEach(function (token, i) {
                            _this.lpTokens[token] = {
                                lastLpFeeUpdate: lpTokenInfo_1[i].lastLpFeeUpdate,
                                liquidReserves: lpTokenInfo_1[i].liquidReserves,
                            };
                        });
                        _l.label = 3;
                    case 3:
                        if (eventsToQuery.includes("ProposeRootBundle")) {
                            (_h = this.proposedRootBundles).push.apply(_h, events["ProposeRootBundle"]
                                .filter(function (event) { return !_this.configOverride.ignoredHubProposedBundles.includes(event.blockNumber); })
                                .map(function (_event) {
                                var args = spreadEventWithBlockNumber(_event);
                                return __assign(__assign({}, args), { proposer: EvmAddress.from(args.proposer) });
                            }));
                        }
                        if (eventsToQuery.includes("RootBundleCanceled")) {
                            (_j = this.canceledRootBundles).push.apply(_j, events["RootBundleCanceled"].map(function (event) { return spreadEventWithBlockNumber(event); }));
                        }
                        if (eventsToQuery.includes("RootBundleDisputed")) {
                            (_k = this.disputedRootBundles).push.apply(_k, events["RootBundleDisputed"].map(function (event) { return spreadEventWithBlockNumber(event); }));
                        }
                        if (eventsToQuery.includes("RootBundleExecuted")) {
                            for (_f = 0, _g = events["RootBundleExecuted"]; _f < _g.length; _f++) {
                                event_3 = _g[_f];
                                if (this.configOverride.ignoredHubExecutedBundles.includes(event_3.blockNumber)) {
                                    continue;
                                }
                                executedRootBundle = spreadEventWithBlockNumber(event_3);
                                l1Tokens = executedRootBundle.l1Tokens, runningBalances = executedRootBundle.runningBalances;
                                nTokens = l1Tokens.length;
                                // Safeguard
                                if (![nTokens, nTokens * 2].includes(runningBalances.length)) {
                                    throw new Error("Invalid runningBalances length: ".concat(runningBalances.length, ".") +
                                        " Expected ".concat(nTokens, " or ").concat(nTokens * 2, " for chain ").concat(this.chainId, " transaction ").concat(event_3.transactionHash));
                                }
                                executedRootBundle.runningBalances = runningBalances.slice(0, nTokens);
                                this.executedRootBundles.push(__assign(__assign({}, executedRootBundle), { l1Tokens: l1Tokens.map(function (l1Token) { return EvmAddress.from(l1Token); }) }));
                            }
                        }
                        // If the contract's current rootBundleProposal() value has an unclaimedPoolRebalanceLeafCount > 0, then
                        // it means that either the root bundle proposal is in the challenge period and can be disputed, or it has
                        // passed the challenge period and pool rebalance leaves can be executed. Once all leaves are executed, the
                        // unclaimed count will drop to 0 and at that point there is nothing more that we can do with this root bundle
                        // besides proposing another one.
                        if (eventsToQuery.includes("ProposeRootBundle")) {
                            if (pendingRootBundleProposal.unclaimedPoolRebalanceLeafCount > 0) {
                                mostRecentProposedRootBundle = this.proposedRootBundles[this.proposedRootBundles.length - 1];
                                this.pendingRootBundle = {
                                    poolRebalanceRoot: pendingRootBundleProposal.poolRebalanceRoot,
                                    relayerRefundRoot: pendingRootBundleProposal.relayerRefundRoot,
                                    slowRelayRoot: pendingRootBundleProposal.slowRelayRoot,
                                    proposer: pendingRootBundleProposal.proposer,
                                    unclaimedPoolRebalanceLeafCount: pendingRootBundleProposal.unclaimedPoolRebalanceLeafCount,
                                    challengePeriodEndTimestamp: pendingRootBundleProposal.challengePeriodEndTimestamp,
                                    bundleEvaluationBlockNumbers: mostRecentProposedRootBundle.bundleEvaluationBlockNumbers.map(function (block) {
                                        // Ideally, the HubPool.sol contract should limit the size of the elements within the
                                        // bundleEvaluationBlockNumbers array. But because it doesn't, we wrap the cast of BN --> Number
                                        // in a try/catch statement and return some value that would always be disputable.
                                        // This catches the denial of service attack vector where a malicious proposer proposes with bundle block
                                        // evaluation block numbers larger than what BigNumber::toNumber() can handle.
                                        try {
                                            return block.toNumber();
                                        }
                                        catch (_a) {
                                            return 0;
                                        }
                                    }),
                                    proposalBlockNumber: mostRecentProposedRootBundle.blockNumber,
                                };
                            }
                            else {
                                this.pendingRootBundle = undefined;
                            }
                        }
                        this.currentTime = currentTime;
                        this.latestHeightSearched = searchEndBlock;
                        this.firstHeightToSearch = update.searchEndBlock + 1; // Next iteration should start off from where this one ended.
                        this.eventSearchConfig.to = undefined; // Caller can re-set on subsequent updates if necessary.
                        this.isUpdated = true;
                        this.logger.debug({ at: "HubPoolClient::update", message: "HubPool client updated!", searchEndBlock: searchEndBlock });
                        return [2 /*return*/];
                }
            });
        });
    };
    // Returns end block for `chainId` in ProposedRootBundle.bundleBlockEvalNumbers. Looks up chainId
    // in chainId list, gets the index where its located, and returns the value of the index in
    // bundleBlockEvalNumbers. Returns 0 if `chainId` can't be found in `chainIdList` and if index doesn't
    // exist in bundleBlockEvalNumbers.
    HubPoolClient.prototype.getBundleEndBlockForChain = function (proposeRootBundleEvent, chainId, chainIdList) {
        var bundleEvaluationBlockNumbers = proposeRootBundleEvent.bundleEvaluationBlockNumbers;
        var chainIdIndex = chainIdList.indexOf(chainId);
        if (chainIdIndex === -1) {
            return 0;
        }
        // Sometimes, the root bundle event's chain ID list will update from bundle to bundle, so we need to check that
        // the bundle evaluation block number list is long enough to contain this index. We assume that chain ID's
        // are only added to the bundle block list, never deleted.
        if (chainIdIndex >= bundleEvaluationBlockNumbers.length) {
            return 0;
        }
        return bundleEvaluationBlockNumbers[chainIdIndex].toNumber();
    };
    return HubPoolClient;
}(BaseAbstractClient));
export { HubPoolClient };
//# sourceMappingURL=HubPoolClient.js.map