import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR } from "@solana/kit";
import { getThrowSolanaErrorResponseTransformer } from "@solana/rpc-transformers";
import { isSolanaError } from "../../arch/svm";
import { delay } from "../../utils";
import { SolanaClusterRpcFactory } from "./baseRpcFactories";
import { RateLimitedSolanaRpcFactory } from "./rateLimitedRpcFactory";
import { shouldFailImmediate } from "./utils";
// This factory adds retry logic on top of the RateLimitedSolanaRpcFactory.
// It follows the same composition pattern as other factories in this module.
export class RetrySolanaRpcFactory extends SolanaClusterRpcFactory {
    retries;
    retryDelaySeconds;
    // Holds the underlying transport that the retry wrapper wraps.
    rateLimitedTransport;
    logger;
    constructor(retries, retryDelaySeconds, ...rateLimitedConstructorParams) {
        // SolanaClusterRpcFactory shares the last two constructor parameters with RateLimitedSolanaRpcFactory.
        const superParams = rateLimitedConstructorParams.slice(-2);
        super(...superParams);
        this.retries = retries;
        this.retryDelaySeconds = retryDelaySeconds;
        // Validate retry configuration
        if (this.retries < 0 || !Number.isInteger(this.retries)) {
            throw new Error(`retries cannot be < 0 and must be an integer. Currently set to ${this.retries}`);
        }
        if (this.retryDelaySeconds < 0) {
            throw new Error(`retryDelaySeconds cannot be < 0. Currently set to ${this.retryDelaySeconds}`);
        }
        // Create the rate limited transport.
        const rateLimitedRpcFactory = new RateLimitedSolanaRpcFactory(...rateLimitedConstructorParams);
        this.rateLimitedTransport = rateLimitedRpcFactory.createTransport();
        this.logger = rateLimitedRpcFactory.logger;
    }
    createTransport() {
        return (...args) => {
            return this._tryCall(() => this.rateLimitedTransport(...args), args);
        };
    }
    /**
     * Retry wrapper for transport calls with Solana-specific error handling.
     * @param transportCall Function that makes the transport call
     * @param args Original transport arguments for logging
     * @returns Promise that resolves to the transport response
     */
    async _tryCall(transportCall, args) {
        const { method, params } = args[0].payload;
        let retryAttempt = 0;
        // eslint-disable-next-line no-constant-condition
        while (true) {
            try {
                const response = await transportCall();
                // Make sure SolanaErrors get thrown
                getThrowSolanaErrorResponseTransformer()(response, { methodName: method, params });
                return response;
            }
            catch (error) {
                if (retryAttempt++ >= this.retries || shouldFailImmediate(method, error)) {
                    throw error;
                }
                // Implement a slightly aggressive exponential backoff to account for fierce parallelism.
                const { retryDelaySeconds } = this;
                const exponentialBackoff = retryDelaySeconds * Math.pow(2, retryAttempt - 1);
                const jitter = 1 + 2 * Math.random(); // Range jitter from [1, 3]s to offset problem where there are many
                // concurrent retry requests sent at the same time.
                const delayS = this.isRateLimitResponse(error) ? exponentialBackoff + jitter : retryDelaySeconds;
                await delay(delayS);
            }
        }
    }
    /**
     * Identify whether an error thrown was the result of an RPC provider 429 response.
     * @param error Error object from the RPC query.
     * @returns True if the response was a 429 rate-limit response.
     */
    isRateLimitResponse(error) {
        if (!isSolanaError(error)) {
            return false;
        }
        const { __code: code } = error.context;
        return code === SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR && error.context.statusCode === 429;
    }
}
//# sourceMappingURL=retryRpcFactory.js.map