"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HubPoolClient = void 0;
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var lodash_1 = tslib_1.__importDefault(require("lodash"));
var constants_1 = require("../constants");
var lpFeeCalculator = tslib_1.__importStar(require("../lpFeeCalculator"));
var evm_1 = require("../arch/evm");
var utils_1 = require("../utils");
var BaseAbstractClient_1 = require("./BaseAbstractClient");
var HubPoolClient = (function (_super) {
    tslib_1.__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;
        _this.l1TokensToDestinationTokens = {};
        _this.l1Tokens = [];
        _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 evm_1.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 = (0, utils_1.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) {
        var mostRecentSpokePoolUpdateBeforeBlock = this.crossChainContracts[chain].find(function (crossChainContract) {
            return crossChainContract.spokePool.eq(spokePool);
        });
        return mostRecentSpokePoolUpdateBeforeBlock === null || mostRecentSpokePoolUpdateBeforeBlock === void 0 ? void 0 : mostRecentSpokePoolUpdateBeforeBlock.blockNumber;
    };
    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 = (0, utils_1.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));
        }
        var l2Token = (0, utils_1.sortEventsDescending)(this.l1TokensToDestinationTokensWithBlock[l1Token.toEvmAddress()][destinationChainId]).find(function (mapping) { return mapping.blockNumber <= latestHubBlock; });
        if (!l2Token) {
            var chain = (0, utils_1.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;
    };
    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(utils_1.EvmAddress.from(l1Token), destinationChainId); })
            .map(function (l1Token) {
            return _this.l1TokensToDestinationTokensWithBlock[l1Token][destinationChainId].filter(function (dstTokenWithBlock) {
                return dstTokenWithBlock.l2Token.truncateToBytes20() === l2Token.truncateToBytes20() &&
                    dstTokenWithBlock.blockNumber <= latestHubBlock;
            });
        })
            .flat();
        if (l2Tokens.length === 0) {
            var chain = (0, utils_1.getNetworkName)(destinationChainId);
            throw new Error("Could not find HubPool mapping for ".concat(l2Token, " on ").concat(chain, " at or before HubPool block ").concat(latestHubBlock, "!"));
        }
        return (0, utils_1.sortEventsDescending)(l2Tokens)[0].l1Token;
    };
    HubPoolClient.prototype.getL1TokenForDeposit = function (deposit) {
        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;
        var l2Token = (0, utils_1.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);
                });
            });
        });
    };
    HubPoolClient.prototype.getTokenInfoForAddress = function (address, chainId) {
        var _a;
        var tokenInfo = (0, utils_1.getTokenInfo)(address, chainId);
        if (tokenInfo.symbol.toLowerCase() === "usdc" && chainId !== this.chainId) {
            tokenInfo.symbol = (_a = (0, utils_1.getUsdcSymbol)(address, chainId)) !== null && _a !== void 0 ? _a : "UNKNOWN USDC";
        }
        return tokenInfo;
    };
    HubPoolClient.prototype.getBlockNumber = function (timestamp) {
        var hints = { lowBlock: this.deploymentBlock };
        return (0, utils_1.getCachedBlockForTimestamp)(this.chainId, timestamp, this.blockFinder, this.cachingMechanism, hints);
    };
    HubPoolClient.prototype.getBlockNumbers = function (timestamps) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var sortedTimestamps, blockNumbers, _i, sortedTimestamps_1, timestamp, _a, _b;
            return tslib_1.__generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        sortedTimestamps = (0, utils_1.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, 4];
                        timestamp = sortedTimestamps_1[_i];
                        _a = blockNumbers;
                        _b = timestamp;
                        return [4, this.getBlockNumber(timestamp)];
                    case 2:
                        _a[_b] = _c.sent();
                        _c.label = 3;
                    case 3:
                        _i++;
                        return [3, 1];
                    case 4: return [2, blockNumbers];
                }
            });
        });
    };
    HubPoolClient.prototype.getCurrentPoolUtilization = function (l1Token) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var blockNumber, _b;
            return tslib_1.__generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        if (!((_a = this.latestHeightSearched) !== null && _a !== void 0)) return [3, 1];
                        _b = _a;
                        return [3, 3];
                    case 1: return [4, this.hubPool.provider.getBlockNumber()];
                    case 2:
                        _b = (_c.sent());
                        _c.label = 3;
                    case 3:
                        blockNumber = _b;
                        return [4, this.getUtilization(l1Token, blockNumber, utils_1.bnZero, (0, utils_1.getCurrentTime)(), 0)];
                    case 4: return [2, _c.sent()];
                }
            });
        });
    };
    HubPoolClient.prototype.getUtilization = function (hubPoolToken, blockNumber, depositAmount, timestamp, timeToCache) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var resolver, cache, key, result, utilization;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        resolver = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                            var overrides;
                            return tslib_1.__generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        overrides = { blockTag: blockNumber };
                                        if (!depositAmount.eq(0)) return [3, 2];
                                        return [4, this.hubPool.callStatic.liquidityUtilizationCurrent(hubPoolToken.toEvmAddress(), overrides)];
                                    case 1: return [2, _a.sent()];
                                    case 2: return [4, this.hubPool.callStatic.liquidityUtilizationPostRelay(hubPoolToken.toEvmAddress(), depositAmount, overrides)];
                                    case 3: return [2, _a.sent()];
                                }
                            });
                        }); };
                        cache = this.cachingMechanism;
                        if (!cache || !(0, utils_1.shouldCache)((0, utils_1.getCurrentTime)(), timestamp, timeToCache)) {
                            return [2, resolver()];
                        }
                        key = depositAmount.eq(0)
                            ? "utilization_".concat(hubPoolToken.toEvmAddress(), "_").concat(blockNumber)
                            : "utilization_".concat(hubPoolToken.toEvmAddress(), "_").concat(blockNumber, "_").concat(depositAmount.toString(), "_");
                        return [4, cache.get(key)];
                    case 1:
                        result = _a.sent();
                        if ((0, utils_1.isDefined)(result)) {
                            return [2, utils_1.BigNumber.from(result)];
                        }
                        return [4, resolver()];
                    case 2:
                        utilization = _a.sent();
                        if (!(cache && (0, utils_1.shouldCache)((0, utils_1.getCurrentTime)(), timestamp, timeToCache))) return [3, 4];
                        return [4, cache.set(key, "".concat(utilization.toString()), constants_1.DEFAULT_CACHING_TTL)];
                    case 3:
                        _a.sent();
                        _a.label = 4;
                    case 4: return [2, utilization];
                }
            });
        });
    };
    HubPoolClient.prototype.computeRealizedLpFeePct = function (deposit) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var lpFee;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4, this.batchComputeRealizedLpFeePct([deposit])];
                    case 1:
                        lpFee = (_a.sent())[0];
                        return [2, lpFee];
                }
            });
        });
    };
    HubPoolClient.prototype.batchComputeRealizedLpFeePct = function (deposits) {
        var _a;
        return tslib_1.__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 tslib_1.__generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        (0, assert_1.default)(deposits.length > 0, "No deposits supplied to batchComputeRealizedLpFeePct");
                        if (!(0, utils_1.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(tslib_1.__assign(tslib_1.__assign({}, deposit), { quoteBlockNumber: quoteBlockNumber }))));
                            }
                            else
                                return undefined;
                        };
                        getHubPoolTokens = function () { return (0, utils_1.dedupArray)(Object.values(hubPoolTokens).filter(utils_1.isDefined)); };
                        resolveUniqueQuoteTimestamps = function (deposit) {
                            var _a;
                            var _b;
                            var quoteTimestamp = deposit.quoteTimestamp;
                            var quoteBlockNumber = quoteBlocks[quoteTimestamp];
                            var hubPoolToken = getHubPoolToken(deposit, quoteBlockNumber);
                            if (!hubPoolToken) {
                                return;
                            }
                            (_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 tslib_1.__awaiter(_this, void 0, void 0, function () {
                            var _a, _b;
                            var _this = this;
                            return tslib_1.__generator(this, function (_c) {
                                switch (_c.label) {
                                    case 0:
                                        _b = (_a = Object).fromEntries;
                                        return [4, (0, utils_1.mapAsync)(utilizationTimestamps[hubPoolToken.toEvmAddress()], function (quoteTimestamp) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                                                var blockNumber, utilization;
                                                return tslib_1.__generator(this, function (_a) {
                                                    switch (_a.label) {
                                                        case 0:
                                                            blockNumber = quoteBlocks[quoteTimestamp];
                                                            return [4, this.getUtilization(hubPoolToken, blockNumber, utils_1.bnZero, quoteTimestamp, timeToCache)];
                                                        case 1:
                                                            utilization = _a.sent();
                                                            return [2, [blockNumber, utilization]];
                                                    }
                                                });
                                            }); })];
                                    case 1: return [2, _b.apply(_a, [_c.sent()])];
                                }
                            });
                        }); };
                        computeRealizedLpFeePct = function (deposit) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                            var originChainId, paymentChainId, inputAmount, quoteTimestamp, quoteBlock, hubPoolToken, rateModel, preUtilization, postUtilization, realizedLpFeePct;
                            return tslib_1.__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, { quoteBlock: quoteBlock, realizedLpFeePct: utils_1.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, this.getUtilization(hubPoolToken, quoteBlock, inputAmount, quoteTimestamp, timeToCache)];
                                    case 1:
                                        postUtilization = _a.sent();
                                        realizedLpFeePct = lpFeeCalculator.calculateRealizedLpFeePct(rateModel, preUtilization, postUtilization);
                                        return [2, { quoteBlock: quoteBlock, realizedLpFeePct: realizedLpFeePct }];
                                }
                            });
                        }); };
                        timeToCache = (_a = this.configOverride.timeToCache) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_CACHING_SAFE_LAG;
                        quoteTimestamps = (0, utils_1.dedupArray)(deposits.map(function (_a) {
                            var quoteTimestamp = _a.quoteTimestamp;
                            return quoteTimestamp;
                        }));
                        return [4, this.getBlockNumbers(quoteTimestamps)];
                    case 1:
                        quoteBlocks = _d.sent();
                        deposits.forEach(function (deposit) { return resolveUniqueQuoteTimestamps(deposit); });
                        _c = (_b = Object).fromEntries;
                        return [4, (0, utils_1.mapAsync)(getHubPoolTokens(), function (hubPoolToken) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                                var _a;
                                return tslib_1.__generator(this, function (_b) {
                                    switch (_b.label) {
                                        case 0:
                                            _a = [hubPoolToken.toEvmAddress()];
                                            return [4, resolveUtilization(hubPoolToken)];
                                        case 1: return [2, _a.concat([
                                                _b.sent()
                                            ])];
                                    }
                                });
                            }); })];
                    case 2:
                        utilization = _c.apply(_b, [_d.sent()]);
                        return [4, (0, utils_1.mapAsync)(deposits, function (deposit) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                                var quoteBlock;
                                return tslib_1.__generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0:
                                            quoteBlock = quoteBlocks[deposit.quoteTimestamp];
                                            if (!this.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, quoteBlock)) return [3, 2];
                                            return [4, computeRealizedLpFeePct(deposit)];
                                        case 1: return [2, _a.sent()];
                                        case 2: return [2, {
                                                quoteBlock: quoteBlock,
                                                realizedLpFeePct: utils_1.bnZero,
                                            }];
                                    }
                                });
                            }); })];
                    case 3: return [2, _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;
        }
        var l1TokenA = this.getL1TokenForL2TokenAtBlock(tokenA, chainIdA, hubPoolBlock);
        var l1TokenB = this.getL1TokenForL2TokenAtBlock(tokenB, chainIdB, hubPoolBlock);
        if (!l1TokenA.eq(l1TokenB)) {
            return false;
        }
        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;
    };
    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;
    };
    HubPoolClient.prototype.getRootBundleEvalBlockNumberContainingBlock = function (latestMainnetBlock, block, chain, chainIdListOverride) {
        var chainIdList = chainIdListOverride !== null && chainIdListOverride !== void 0 ? chainIdListOverride : this.configStoreClient.getChainIdIndicesForBlock(latestMainnetBlock);
        var endingBlockNumber;
        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;
            }
            var bundleEvalBlockNumber = this.getBundleEndBlockForChain(rootBundle, chain, chainIdList);
            if (bundleEvalBlockNumber <= block) {
                if (bundleEvalBlockNumber === block) {
                    endingBlockNumber = bundleEvalBlockNumber;
                }
                break;
            }
            endingBlockNumber = bundleEvalBlockNumber;
        }
        return endingBlockNumber;
    };
    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 (0, utils_1.sortEventsDescending)(this.canceledRootBundles).filter(function (bundle) { return bundle.blockNumber >= startingBlock && bundle.blockNumber <= endingBlock; });
    };
    HubPoolClient.prototype.getDisputedRootBundlesInBlockRange = function (startingBlock, endingBlock) {
        return (0, utils_1.sortEventsDescending)(this.disputedRootBundles).filter(function (bundle) { return bundle.blockNumber >= startingBlock && bundle.blockNumber <= endingBlock; });
    };
    HubPoolClient.prototype.getTokenMappingsModifiedInBlockRange = function (startingBlock, endingBlock) {
        return (Object.values(this.l1TokensToDestinationTokensWithBlock)
            .flatMap(function (destinationTokens) {
            return Object.entries(destinationTokens).flatMap(function (_a) {
                var destinationChainId = _a[0], tokensWithBlock = _a[1];
                return tokensWithBlock.map(function (token) { return (tslib_1.__assign(tslib_1.__assign({}, token), { l2ChainId: Number(destinationChainId) })); });
            });
        })
            .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 = lodash_1.default.findLastIndex(this.proposedRootBundles, function (bundle) { return bundle.blockNumber === currentRootBundle.blockNumber; });
        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 &&
                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;
        return lodash_1.default.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);
        });
    };
    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 < 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;
                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;
                nextStartBlock = Math.min(bundleBlockNumber + 1, this.latestHeightSearched);
            }
        }
        return bundleToReturn;
    };
    HubPoolClient.prototype.getLatestBundleEndBlockForChain = function (chainIdList, latestMainnetBlock, chainId) {
        var latestFullyExecutedPoolRebalanceRoot = this.getLatestFullyExecutedRootBundle(latestMainnetBlock);
        if (!latestFullyExecutedPoolRebalanceRoot) {
            return 0;
        }
        return this.getBundleEndBlockForChain(latestFullyExecutedPoolRebalanceRoot, chainId, chainIdList);
    };
    HubPoolClient.prototype.getNextBundleStartBlockNumber = function (chainIdList, latestMainnetBlock, chainId) {
        var endBlock = this.getLatestBundleEndBlockForChain(chainIdList, latestMainnetBlock, chainId);
        return endBlock > 0 ? endBlock + 1 : 0;
    };
    HubPoolClient.prototype.getOptimisticBundleStartBlockNumber = function (chainIdList, latestMainnetBlock, chainId) {
        if (!this.hasPendingProposal()) {
            return this.getNextBundleStartBlockNumber(chainIdList, latestMainnetBlock, chainId);
        }
        var latestProposedBundle = this.proposedRootBundles[this.proposedRootBundles.length - 1];
        if (!(0, utils_1.isDefined)(latestProposedBundle)) {
            return 0;
        }
        var optimisticEndBlock = this.getBundleEndBlockForChain(latestProposedBundle, chainId, chainIdList);
        return optimisticEndBlock > 0 ? optimisticEndBlock + 1 : 0;
    };
    HubPoolClient.prototype.getLatestExecutedRootBundleContainingL1Token = function (block, chain, l1Token) {
        return (0, utils_1.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 = (0, utils_1.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 tslib_1.__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 tslib_1.__generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        hubPoolEvents = this.hubPoolEventFilters();
                        return [4, this.updateSearchConfig(this.hubPool.provider)];
                    case 1:
                        searchConfig = _c.sent();
                        if ((0, BaseAbstractClient_1.isUpdateFailureReason)(searchConfig)) {
                            return [2, { success: false, reason: searchConfig }];
                        }
                        supportedEvents = Object.keys(hubPoolEvents);
                        if (eventNames.some(function (eventName) { return !supportedEvents.includes(eventName); })) {
                            return [2, { success: false, reason: BaseAbstractClient_1.UpdateFailureReason.BadRequest }];
                        }
                        eventSearchConfigs = eventNames.map(function (eventName) {
                            var _searchConfig = tslib_1.__assign({}, searchConfig);
                            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, Promise.all(tslib_1.__spreadArray([
                                hubPool.callStatic.multicall(multicallFunctions.map(function (f) { return hubPool.interface.encodeFunctionData(f); }), { blockTag: searchConfig.to })
                            ], eventSearchConfigs.map(function (config) { return (0, utils_1.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, {
                                success: true,
                                currentTime: currentTime,
                                pendingRootBundleProposal: tslib_1.__assign(tslib_1.__assign({}, pendingRootBundleProposal), { proposer: utils_1.EvmAddress.from(pendingRootBundleProposal.proposer) }),
                                searchEndBlock: searchConfig.to,
                                events: _events,
                            }];
                }
            });
        });
    };
    HubPoolClient.prototype.update = function (eventsToQuery) {
        if (eventsToQuery === void 0) { eventsToQuery = Object.keys(this.hubPoolEventFilters()); }
        return tslib_1.__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 tslib_1.__generator(this, function (_l) {
                switch (_l.label) {
                    case 0:
                        if (!this.configStoreClient.isUpdated) {
                            throw new Error("ConfigStoreClient not updated");
                        }
                        return [4, this._update(eventsToQuery)];
                    case 1:
                        update = _l.sent();
                        if (!update.success) {
                            if (update.reason !== BaseAbstractClient_1.UpdateFailureReason.AlreadyUpdated) {
                                throw new Error("Unable to update HubPoolClient: ".concat(update.reason));
                            }
                            return [2];
                        }
                        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 = (0, utils_1.spreadEventWithBlockNumber)(event_1);
                                dataToAdd = {
                                    spokePool: utils_1.EvmAddress.from(args.spokePool),
                                    blockNumber: args.blockNumber,
                                    txnRef: args.txnRef,
                                    logIndex: args.logIndex,
                                    txnIndex: args.txnIndex,
                                    l2ChainId: args.l2ChainId,
                                };
                                if ((0, utils_1.chainIsSvm)(args.l2ChainId)) {
                                    solanaSpokePool = (0, utils_1.getDeployedAddress)("SvmSpoke", args.l2ChainId);
                                    if (!solanaSpokePool) {
                                        throw new Error("SVM spoke pool not found for chain ".concat(args.l2ChainId));
                                    }
                                    svmSpoke = utils_1.SvmAddress.from(solanaSpokePool);
                                    truncatedAddress = svmSpoke.truncateToBytes20();
                                    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;
                                }
                                (0, utils_1.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 = (0, utils_1.spreadEventWithBlockNumber)(event_2);
                                destinationToken = utils_1.EvmAddress.from(args.destinationToken);
                                if ((0, utils_1.chainIsSvm)(args.destinationChainId)) {
                                    usdcTokenSol = constants_1.TOKEN_SYMBOLS_MAP.USDC.addresses[args.destinationChainId];
                                    svmUsdc = utils_1.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 (!destinationToken.isZeroAddress()) {
                                    (0, utils_1.assign)(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], destinationToken);
                                    (0, utils_1.assign)(this.l1TokensToDestinationTokensWithBlock, [args.l1Token, args.destinationChainId], [
                                        {
                                            l1Token: utils_1.EvmAddress.from(args.l1Token),
                                            l2Token: destinationToken,
                                            blockNumber: args.blockNumber,
                                            txnIndex: args.txnIndex,
                                            logIndex: args.logIndex,
                                            txnRef: args.txnRef,
                                        },
                                    ]);
                                }
                            }
                        }
                        if (!eventsToQuery.includes("L1TokenEnabledForLiquidityProvision")) return [3, 3];
                        uniqueL1Tokens = (0, utils_1.dedupArray)(events["L1TokenEnabledForLiquidityProvision"].map(function (event) { return String(event.args["l1Token"]); }));
                        return [4, Promise.all([
                                Promise.all(uniqueL1Tokens.map(function (l1Token) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                                    var tokenInfo;
                                    return tslib_1.__generator(this, function (_a) {
                                        switch (_a.label) {
                                            case 0: return [4, (0, utils_1.fetchTokenInfo)(l1Token, this.hubPool.provider)];
                                            case 1:
                                                tokenInfo = _a.sent();
                                                return [2, tslib_1.__assign(tslib_1.__assign({}, tokenInfo), { address: utils_1.EvmAddress.from(l1Token) })];
                                        }
                                    });
                                }); })),
                                Promise.all(uniqueL1Tokens.map(function (l1Token) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0: return [4, this.hubPool.pooledTokens(l1Token, { blockTag: update.searchEndBlock })];
                                        case 1: return [2, _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 = (0, utils_1.spreadEventWithBlockNumber)(_event);
                                return tslib_1.__assign(tslib_1.__assign({}, args), { proposer: utils_1.EvmAddress.from(args.proposer) });
                            }));
                        }
                        if (eventsToQuery.includes("RootBundleCanceled")) {
                            (_j = this.canceledRootBundles).push.apply(_j, events["RootBundleCanceled"].map(function (event) { return (0, utils_1.spreadEventWithBlockNumber)(event); }));
                        }
                        if (eventsToQuery.includes("RootBundleDisputed")) {
                            (_k = this.disputedRootBundles).push.apply(_k, events["RootBundleDisputed"].map(function (event) { return (0, utils_1.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 = (0, utils_1.spreadEventWithBlockNumber)(event_3);
                                l1Tokens = executedRootBundle.l1Tokens, runningBalances = executedRootBundle.runningBalances;
                                nTokens = l1Tokens.length;
                                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(tslib_1.__assign(tslib_1.__assign({}, executedRootBundle), { l1Tokens: l1Tokens.map(function (l1Token) { return utils_1.EvmAddress.from(l1Token); }) }));
                            }
                        }
                        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) {
                                        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;
                        this.eventSearchConfig.to = undefined;
                        this.isUpdated = true;
                        this.logger.debug({ at: "HubPoolClient::update", message: "HubPool client updated!", searchEndBlock: searchEndBlock });
                        return [2];
                }
            });
        });
    };
    HubPoolClient.prototype.getBundleEndBlockForChain = function (proposeRootBundleEvent, chainId, chainIdList) {
        var bundleEvaluationBlockNumbers = proposeRootBundleEvent.bundleEvaluationBlockNumbers;
        var chainIdIndex = chainIdList.indexOf(chainId);
        if (chainIdIndex === -1) {
            return 0;
        }
        if (chainIdIndex >= bundleEvaluationBlockNumbers.length) {
            return 0;
        }
        return bundleEvaluationBlockNumbers[chainIdIndex].toNumber();
    };
    return HubPoolClient;
}(BaseAbstractClient_1.BaseAbstractClient));
exports.HubPoolClient = HubPoolClient;
//# sourceMappingURL=HubPoolClient.js.map