// The async/queue library has a task-based interface for building a concurrent queue.
import assert from "assert";
import { isEqual, sortBy } from "lodash";
import { getOriginFromURL, isDefined } from "../utils";
import { JsonRpcError, RpcError } from "./types";
import * as alchemy from "./alchemy";
import * as infura from "./infura";
import * as drpc from "./drpc";
import * as quicknode from "./quicknode";
/**
 * Infura DIN is identified separately to allow it to be configured explicitly.
 */
const PROVIDERS = {
    ALCHEMY: alchemy.getURL,
    INFURA: infura.getURL,
    INFURA_DIN: infura.getURL,
    DRPC: drpc.getURL,
    QUICKNODE: quicknode.getURL,
};
/**
 * Type predicate for RPCProvider type.
 * @param provider Provider string (ALCHEMY, INFURA, ...).
 * @returns True if the provider string is a supported provider.
 */
export function isSupportedProvider(provider) {
    return Object.keys(PROVIDERS).includes(provider);
}
/**
 * Produce an RPC for a given RPC provider, chainId API key and transport.
 * @param provider RPC provider identifier (ALCHEMY, INFURA, ...)
 * @param chainId Chain ID to obtain a URL for.
 * @param apiKey API key for provider.
 * @param transport Optional transport specifier (HTTPS or WSS).
 * @returns An RPC URL confirming to the specified inputs.
 */
export function getURL(provider, chainId, apiKey, transport = "https") {
    const getURL = PROVIDERS[provider];
    assert(getURL, `Unsupported RPC provider (${provider})`);
    return getURL(chainId, apiKey, transport);
}
/**
 * Deletes keys from an object and returns new copy of object without ignored keys
 * @param ignoredKeys
 * @param obj
 * @returns Objects with ignored keys removed
 */
function deleteIgnoredKeys(ignoredKeys, obj) {
    if (!isDefined(obj)) {
        return;
    }
    const newObj = { ...obj };
    for (const key of ignoredKeys) {
        delete newObj[key];
    }
    return newObj;
}
export function compareResultsAndFilterIgnoredKeys(ignoredKeys, _objA, _objB) {
    // Remove ignored keys from copied objects.
    const filteredA = deleteIgnoredKeys(ignoredKeys, _objA);
    const filteredB = deleteIgnoredKeys(ignoredKeys, _objB);
    // Compare objects without the ignored keys.
    return isEqual(filteredA, filteredB);
}
export function compareArrayResultsWithIgnoredKeys(ignoredKeys, objA, objB) {
    // Remove ignored keys from each element of copied arrays.
    const filteredA = objA?.map((obj) => deleteIgnoredKeys(ignoredKeys, obj));
    const filteredB = objB?.map((obj) => deleteIgnoredKeys(ignoredKeys, obj));
    // Compare objects without the ignored keys.
    const sortKeys = ["transactionIndex", "logIndex"];
    return (isDefined(filteredA) && isDefined(filteredB) && isEqual(sortBy(filteredA, sortKeys), sortBy(filteredB, sortKeys)));
}
/**
 * A record of error codes that correspond to fields that should be ignored when comparing RPC results.
 * This is used to compare results from different providers that may have different, but still valid, results.
 */
const IGNORED_FIELDS = {
    // We've seen some RPC's like QuickNode add in transactionLogIndex which isn't in the
    // JSON RPC spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterchanges
    // Additional reference: https://github.com/ethers-io/ethers.js/issues/1721
    // 2023-08-31 Added blockHash because of upstream zkSync provider disagreements. Consider removing later.
    // 2024-05-07 Added l1BatchNumber and logType due to Alchemy. Consider removing later.
    // 2024-07-11 Added blockTimestamp after zkSync rolled out a new node release.
    // 2025-07-24 Added additional fields returned by Chainstack on (at least) Polygon.
    eth_getBlockByNumber: [
        "miner", // polygon (sometimes)
        "l1BatchNumber", // zkSync
        "l1BatchTimestamp", // zkSync
        "requestsHash", // Chainstack (Polygon)
        "size", // Alchemy/Arbitrum (temporary)
        "totalDifficulty", // Quicknode/Alchemy (sometimes)
        "logsBloom", // zkSync (third-party providers return 0x0..0)
        "transactions", // Polygon yParity field in transactions[]
        "withdrawals", // Chainstack (Polygon)
        "sendCount", // Arbitrum
        "sendRoot", // Arbitrum
    ],
    eth_getLogs: ["blockTimestamp", "transactionLogIndex", "l1BatchNumber", "logType"],
};
/**
 * A helper function to format an error message for a provider.
 * @param provider The provider that failed.
 * @param rawErrorText The raw error text.
 * @returns The formatted error message.
 */
export function formatProviderError(provider, rawErrorText) {
    return `Provider ${getOriginFromURL(provider.connection.url)} failed with error: ${rawErrorText}`;
}
// `{ cause }` keeps the wrapper an actual Error (was a spread-collapsed plain object) and keeps
// the underlying rejection reason reachable for callers and loggers.
export function createSendErrorWithMessage(message, sendError) {
    return new Error(message, { cause: sendError });
}
/**
 * Validate and parse a possible JSON-RPC error response.
 * @param error An unknown error object received in response to a JSON-RPC request.
 * @returns A JSON-RPC error object, or undefined.
 */
export function parseJsonRpcError(response) {
    if (!RpcError.is(response)) {
        return;
    }
    try {
        const error = JSON.parse(response.body);
        if (JsonRpcError.is(error)) {
            return error.error;
        }
    }
    catch {
        // Suppress error.
    }
    return;
}
/**
 * Compares two RPC results, filtering out fields that are known to differ between providers.
 * Note: this function references `IGNORED_ERROR_CODES` which is a record of error codes that correspond to fields
 *       that should be ignored when comparing RPC results.
 * @param method The method that was called - conditionally filters out fields based on the method.
 * @param rpcResultA The first RPC result.
 * @param rpcResultB The second RPC result.
 * @returns True if the results are equal, false otherwise.
 */
export function compareRpcResults(method, rpcResultA, rpcResultB) {
    if (method === "eth_getBlockByNumber") {
        // We've seen RPC's disagree on the miner field, for example when Polygon nodes updated software that
        // led alchemy and quicknode to disagree on the miner field's value.
        return compareResultsAndFilterIgnoredKeys(IGNORED_FIELDS.eth_getBlockByNumber, rpcResultA, rpcResultB);
    }
    else if (method === "eth_getLogs") {
        return compareArrayResultsWithIgnoredKeys(IGNORED_FIELDS.eth_getLogs, rpcResultA, rpcResultB);
    }
    else {
        return isEqual(rpcResultA, rpcResultB);
    }
}
export function compareSvmRpcResults(_method, rpcResultA, rpcResultB) {
    return isEqual(rpcResultA, rpcResultB);
}
export var CacheType;
(function (CacheType) {
    CacheType[CacheType["NONE"] = 0] = "NONE";
    CacheType[CacheType["WITH_TTL"] = 1] = "WITH_TTL";
    CacheType[CacheType["NO_TTL"] = 2] = "NO_TTL";
    CacheType[CacheType["DECIDE_TTL_POST_SEND"] = 3] = "DECIDE_TTL_POST_SEND";
})(CacheType || (CacheType = {}));
//# sourceMappingURL=utils.js.map