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