// StaticJsonRpcProvider is used in place of JsonRpcProvider to avoid redundant eth_chainId queries prior to each
// request. This is safe to use when the back-end provider is guaranteed not to change.
// See https://docs.ethers.io/v5/api/providers/jsonrpc-provider/#StaticJsonRpcProvider
import { __assign, __awaiter, __extends, __generator } from "tslib";
import { queue } from "async";
import { ethers } from "ethers";
import { getOriginFromURL } from "../utils/NetworkUtils";
import winston from "winston";
// This provider is a very small addition to the StaticJsonRpcProvider that ensures that no more than `maxConcurrency`
// requests are ever in flight. It uses the async/queue library to manage this.
var RateLimitedProvider = /** @class */ (function (_super) {
    __extends(RateLimitedProvider, _super);
    // Takes the same arguments as the JsonRpcProvider, but it has an additional maxConcurrency value at the beginning
    // of the list.
    function RateLimitedProvider(maxConcurrency, pctRpcCallsLogged, logger) {
        if (logger === void 0) { logger = winston.createLogger({
            transports: [new winston.transports.Console()],
        }); }
        var cacheConstructorParams = [];
        for (var _i = 3; _i < arguments.length; _i++) {
            cacheConstructorParams[_i - 3] = arguments[_i];
        }
        var _this = _super.apply(this, cacheConstructorParams) || this;
        _this.pctRpcCallsLogged = pctRpcCallsLogged;
        _this.logger = logger;
        // This sets up the queue. Each task is executed by calling the superclass's send method, which fires off the
        // request. This queue sends out requests concurrently, but stops once the concurrency limit is reached. The
        // maxConcurrency is configured here.
        _this.queue = queue(function (_a, callback) {
            var sendArgs = _a.sendArgs, resolve = _a.resolve, reject = _a.reject;
            return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0: return [4 /*yield*/, this.wrapSendWithLog.apply(this, sendArgs).then(resolve)
                                .catch(reject)];
                        case 1:
                            _b.sent();
                            // we need this for the queue to know that the task is done
                            // @see: https://caolan.github.io/async/v3/global.html
                            callback();
                            return [2 /*return*/];
                    }
                });
            });
        }, maxConcurrency);
        return _this;
    }
    RateLimitedProvider.prototype.wrapSendWithLog = function (method, params) {
        return __awaiter(this, void 0, void 0, function () {
            var loggerArgs, startTime, result, elapsedTimeS, error_1, elapsedTimeS;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!(this.pctRpcCallsLogged <= 0 || Math.random() > this.pctRpcCallsLogged / 100)) return [3 /*break*/, 1];
                        // Non sample path: no logging or timing, just issue the request.
                        return [2 /*return*/, _super.prototype.send.call(this, method, params)];
                    case 1:
                        loggerArgs = {
                            at: "ProviderUtils",
                            message: "Provider response sample",
                            provider: getOriginFromURL(this.connection.url),
                            method: method,
                            params: params,
                            chainId: this.network.chainId,
                            datadog: true,
                        };
                        startTime = performance.now();
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        return [4 /*yield*/, _super.prototype.send.call(this, method, params)];
                    case 3:
                        result = _a.sent();
                        elapsedTimeS = (performance.now() - startTime) / 1000;
                        this.logger.debug(__assign(__assign({}, loggerArgs), { success: true, timeElapsed: elapsedTimeS }));
                        return [2 /*return*/, result];
                    case 4:
                        error_1 = _a.sent();
                        elapsedTimeS = (performance.now() - startTime) / 1000;
                        this.logger.debug(__assign(__assign({}, loggerArgs), { success: false, timeElapsed: elapsedTimeS }));
                        throw error_1;
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    RateLimitedProvider.prototype.send = function (method, params) {
        var _this = this;
        // This simply creates a promise and adds the arguments and resolve and reject handlers to the task.
        return new Promise(function (resolve, reject) {
            var task = {
                sendArgs: [method, params],
                resolve: resolve,
                reject: reject,
            };
            // We didn't previously wait for this push so we can emulate
            // the same behavior with the `void` keyword.
            void _this.queue.push(task);
        });
    };
    return RateLimitedProvider;
}(ethers.providers.StaticJsonRpcProvider));
export { RateLimitedProvider };
//# sourceMappingURL=rateLimitedProvider.js.map