"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EtherscanProvider = void 0;
var bytes_1 = require("@ethersproject/bytes");
var properties_1 = require("@ethersproject/properties");
var transactions_1 = require("@ethersproject/transactions");
var web_1 = require("@ethersproject/web");
var formatter_1 = require("./formatter");
var logger_1 = require("@ethersproject/logger");
var _version_1 = require("./_version");
var logger = new logger_1.Logger(_version_1.version);
var base_provider_1 = require("./base-provider");
// The transaction has already been sanitized by the calls in Provider
function getTransactionPostData(transaction) {
    var result = {};
    for (var key in transaction) {
        if (transaction[key] == null) {
            continue;
        }
        var value = transaction[key];
        if (key === "type" && value === 0) {
            continue;
        }
        // Quantity-types require no leading zero, unless 0
        if ({ type: true, gasLimit: true, gasPrice: true, maxFeePerGs: true, maxPriorityFeePerGas: true, nonce: true, value: true }[key]) {
            value = (0, bytes_1.hexValue)((0, bytes_1.hexlify)(value));
        }
        else if (key === "accessList") {
            value = "[" + (0, transactions_1.accessListify)(value).map(function (set) {
                return "{address:\"" + set.address + "\",storageKeys:[\"" + set.storageKeys.join('","') + "\"]}";
            }).join(",") + "]";
        }
        else {
            value = (0, bytes_1.hexlify)(value);
        }
        result[key] = value;
    }
    return result;
}
function getResult(result) {
    // getLogs, getHistory have weird success responses
    if (result.status == 0 && (result.message === "No records found" || result.message === "No transactions found")) {
        return result.result;
    }
    if (result.status != 1 || result.message != "OK") {
        var error = new Error("invalid response");
        error.result = JSON.stringify(result);
        if ((result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
            error.throttleRetry = true;
        }
        throw error;
    }
    return result.result;
}
function getJsonResult(result) {
    // This response indicates we are being throttled
    if (result && result.status == 0 && result.message == "NOTOK" && (result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
        var error = new Error("throttled response");
        error.result = JSON.stringify(result);
        error.throttleRetry = true;
        throw error;
    }
    if (result.jsonrpc != "2.0") {
        // @TODO: not any
        var error = new Error("invalid response");
        error.result = JSON.stringify(result);
        throw error;
    }
    if (result.error) {
        // @TODO: not any
        var error = new Error(result.error.message || "unknown error");
        if (result.error.code) {
            error.code = result.error.code;
        }
        if (result.error.data) {
            error.data = result.error.data;
        }
        throw error;
    }
    return result.result;
}
// The blockTag was normalized as a string by the Provider pre-perform operations
function checkLogTag(blockTag) {
    if (blockTag === "pending") {
        throw new Error("pending not supported");
    }
    if (blockTag === "latest") {
        return blockTag;
    }
    return parseInt(blockTag.substring(2), 16);
}
var defaultApiKey = "9D13ZE7XSBTJ94N9BNJ2MA33VMAY2YPIRB";
function checkError(method, error, transaction) {
    // Undo the "convenience" some nodes are attempting to prevent backwards
    // incompatibility; maybe for v6 consider forwarding reverts as errors
    if (method === "call" && error.code === logger_1.Logger.errors.SERVER_ERROR) {
        var e = error.error;
        // Etherscan keeps changing their string
        if (e && (e.message.match(/reverted/i) || e.message.match(/VM execution error/i))) {
            // Etherscan prefixes the data like "Reverted 0x1234"
            var data = e.data;
            if (data) {
                data = "0x" + data.replace(/^.*0x/i, "");
            }
            if ((0, bytes_1.isHexString)(data)) {
                return data;
            }
            logger.throwError("missing revert data in call exception", logger_1.Logger.errors.CALL_EXCEPTION, {
                error: error,
                data: "0x"
            });
        }
    }
    // Get the message from any nested error structure
    var message = error.message;
    if (error.code === logger_1.Logger.errors.SERVER_ERROR) {
        if (error.error && typeof (error.error.message) === "string") {
            message = error.error.message;
        }
        else if (typeof (error.body) === "string") {
            message = error.body;
        }
        else if (typeof (error.responseText) === "string") {
            message = error.responseText;
        }
    }
    message = (message || "").toLowerCase();
    // "Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 21464000000000 and got: 0"
    if (message.match(/insufficient funds/)) {
        logger.throwError("insufficient funds for intrinsic transaction cost", logger_1.Logger.errors.INSUFFICIENT_FUNDS, {
            error: error,
            method: method,
            transaction: transaction
        });
    }
    // "Transaction with the same hash was already imported."
    if (message.match(/same hash was already imported|transaction nonce is too low|nonce too low/)) {
        logger.throwError("nonce has already been used", logger_1.Logger.errors.NONCE_EXPIRED, {
            error: error,
            method: method,
            transaction: transaction
        });
    }
    // "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce."
    if (message.match(/another transaction with same nonce/)) {
        logger.throwError("replacement fee too low", logger_1.Logger.errors.REPLACEMENT_UNDERPRICED, {
            error: error,
            method: method,
            transaction: transaction
        });
    }
    if (message.match(/execution failed due to an exception|execution reverted/)) {
        logger.throwError("cannot estimate gas; transaction may fail or may require manual gas limit", logger_1.Logger.errors.UNPREDICTABLE_GAS_LIMIT, {
            error: error,
            method: method,
            transaction: transaction
        });
    }
    throw error;
}
var EtherscanProvider = /** @class */ (function (_super) {
    __extends(EtherscanProvider, _super);
    function EtherscanProvider(network, apiKey) {
        var _newTarget = this.constructor;
        var _this = this;
        logger.checkNew(_newTarget, EtherscanProvider);
        _this = _super.call(this, network) || this;
        (0, properties_1.defineReadOnly)(_this, "baseUrl", _this.getBaseUrl());
        (0, properties_1.defineReadOnly)(_this, "apiKey", apiKey || defaultApiKey);
        return _this;
    }
    EtherscanProvider.prototype.getBaseUrl = function () {
        switch (this.network ? this.network.name : "invalid") {
            case "homestead":
                return "https:/\/api.etherscan.io";
            case "ropsten":
                return "https:/\/api-ropsten.etherscan.io";
            case "rinkeby":
                return "https:/\/api-rinkeby.etherscan.io";
            case "kovan":
                return "https:/\/api-kovan.etherscan.io";
            case "goerli":
                return "https:/\/api-goerli.etherscan.io";
            default:
        }
        return logger.throwArgumentError("unsupported network", "network", name);
    };
    EtherscanProvider.prototype.getUrl = function (module, params) {
        var query = Object.keys(params).reduce(function (accum, key) {
            var value = params[key];
            if (value != null) {
                accum += "&" + key + "=" + value;
            }
            return accum;
        }, "");
        var apiKey = ((this.apiKey) ? "&apikey=" + this.apiKey : "");
        return this.baseUrl + "/api?module=" + module + query + apiKey;
    };
    EtherscanProvider.prototype.getPostUrl = function () {
        return this.baseUrl + "/api";
    };
    EtherscanProvider.prototype.getPostData = function (module, params) {
        params.module = module;
        params.apikey = this.apiKey;
        return params;
    };
    EtherscanProvider.prototype.fetch = function (module, params, post) {
        return __awaiter(this, void 0, void 0, function () {
            var url, payload, procFunc, connection, payloadStr, result;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        url = (post ? this.getPostUrl() : this.getUrl(module, params));
                        payload = (post ? this.getPostData(module, params) : null);
                        procFunc = (module === "proxy") ? getJsonResult : getResult;
                        this.emit("debug", {
                            action: "request",
                            request: url,
                            provider: this
                        });
                        connection = {
                            url: url,
                            throttleSlotInterval: 1000,
                            throttleCallback: function (attempt, url) {
                                if (_this.isCommunityResource()) {
                                    (0, formatter_1.showThrottleMessage)();
                                }
                                return Promise.resolve(true);
                            }
                        };
                        payloadStr = null;
                        if (payload) {
                            connection.headers = { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" };
                            payloadStr = Object.keys(payload).map(function (key) {
                                return key + "=" + payload[key];
                            }).join("&");
                        }
                        return [4 /*yield*/, (0, web_1.fetchJson)(connection, payloadStr, procFunc || getJsonResult)];
                    case 1:
                        result = _a.sent();
                        this.emit("debug", {
                            action: "response",
                            request: url,
                            response: (0, properties_1.deepCopy)(result),
                            provider: this
                        });
                        return [2 /*return*/, result];
                }
            });
        });
    };
    EtherscanProvider.prototype.detectNetwork = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                return [2 /*return*/, this.network];
            });
        });
    };
    EtherscanProvider.prototype.perform = function (method, params) {
        return __awaiter(this, void 0, void 0, function () {
            var _a, postData, error_1, postData, error_2, args, topic0, logs, blocks, i, log, block, _b;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _a = method;
                        switch (_a) {
                            case "getBlockNumber": return [3 /*break*/, 1];
                            case "getGasPrice": return [3 /*break*/, 2];
                            case "getBalance": return [3 /*break*/, 3];
                            case "getTransactionCount": return [3 /*break*/, 4];
                            case "getCode": return [3 /*break*/, 5];
                            case "getStorageAt": return [3 /*break*/, 6];
                            case "sendTransaction": return [3 /*break*/, 7];
                            case "getBlock": return [3 /*break*/, 8];
                            case "getTransaction": return [3 /*break*/, 9];
                            case "getTransactionReceipt": return [3 /*break*/, 10];
                            case "call": return [3 /*break*/, 11];
                            case "estimateGas": return [3 /*break*/, 15];
                            case "getLogs": return [3 /*break*/, 19];
                            case "getEtherPrice": return [3 /*break*/, 26];
                        }
                        return [3 /*break*/, 28];
                    case 1: return [2 /*return*/, this.fetch("proxy", { action: "eth_blockNumber" })];
                    case 2: return [2 /*return*/, this.fetch("proxy", { action: "eth_gasPrice" })];
                    case 3: 
                    // Returns base-10 result
                    return [2 /*return*/, this.fetch("account", {
                            action: "balance",
                            address: params.address,
                            tag: params.blockTag
                        })];
                    case 4: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_getTransactionCount",
                            address: params.address,
                            tag: params.blockTag
                        })];
                    case 5: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_getCode",
                            address: params.address,
                            tag: params.blockTag
                        })];
                    case 6: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_getStorageAt",
                            address: params.address,
                            position: params.position,
                            tag: params.blockTag
                        })];
                    case 7: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_sendRawTransaction",
                            hex: params.signedTransaction
                        }, true).catch(function (error) {
                            return checkError("sendTransaction", error, params.signedTransaction);
                        })];
                    case 8:
                        if (params.blockTag) {
                            return [2 /*return*/, this.fetch("proxy", {
                                    action: "eth_getBlockByNumber",
                                    tag: params.blockTag,
                                    boolean: (params.includeTransactions ? "true" : "false")
                                })];
                        }
                        throw new Error("getBlock by blockHash not implemented");
                    case 9: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_getTransactionByHash",
                            txhash: params.transactionHash
                        })];
                    case 10: return [2 /*return*/, this.fetch("proxy", {
                            action: "eth_getTransactionReceipt",
                            txhash: params.transactionHash
                        })];
                    case 11:
                        if (params.blockTag !== "latest") {
                            throw new Error("EtherscanProvider does not support blockTag for call");
                        }
                        postData = getTransactionPostData(params.transaction);
                        postData.module = "proxy";
                        postData.action = "eth_call";
                        _c.label = 12;
                    case 12:
                        _c.trys.push([12, 14, , 15]);
                        return [4 /*yield*/, this.fetch("proxy", postData, true)];
                    case 13: return [2 /*return*/, _c.sent()];
                    case 14:
                        error_1 = _c.sent();
                        return [2 /*return*/, checkError("call", error_1, params.transaction)];
                    case 15:
                        postData = getTransactionPostData(params.transaction);
                        postData.module = "proxy";
                        postData.action = "eth_estimateGas";
                        _c.label = 16;
                    case 16:
                        _c.trys.push([16, 18, , 19]);
                        return [4 /*yield*/, this.fetch("proxy", postData, true)];
                    case 17: return [2 /*return*/, _c.sent()];
                    case 18:
                        error_2 = _c.sent();
                        return [2 /*return*/, checkError("estimateGas", error_2, params.transaction)];
                    case 19:
                        args = { action: "getLogs" };
                        if (params.filter.fromBlock) {
                            args.fromBlock = checkLogTag(params.filter.fromBlock);
                        }
                        if (params.filter.toBlock) {
                            args.toBlock = checkLogTag(params.filter.toBlock);
                        }
                        if (params.filter.address) {
                            args.address = params.filter.address;
                        }
                        // @TODO: We can handle slightly more complicated logs using the logs API
                        if (params.filter.topics && params.filter.topics.length > 0) {
                            if (params.filter.topics.length > 1) {
                                logger.throwError("unsupported topic count", logger_1.Logger.errors.UNSUPPORTED_OPERATION, { topics: params.filter.topics });
                            }
                            if (params.filter.topics.length === 1) {
                                topic0 = params.filter.topics[0];
                                if (typeof (topic0) !== "string" || topic0.length !== 66) {
                                    logger.throwError("unsupported topic format", logger_1.Logger.errors.UNSUPPORTED_OPERATION, { topic0: topic0 });
                                }
                                args.topic0 = topic0;
                            }
                        }
                        return [4 /*yield*/, this.fetch("logs", args)];
                    case 20:
                        logs = _c.sent();
                        blocks = {};
                        i = 0;
                        _c.label = 21;
                    case 21:
                        if (!(i < logs.length)) return [3 /*break*/, 25];
                        log = logs[i];
                        if (log.blockHash != null) {
                            return [3 /*break*/, 24];
                        }
                        if (!(blocks[log.blockNumber] == null)) return [3 /*break*/, 23];
                        return [4 /*yield*/, this.getBlock(log.blockNumber)];
                    case 22:
                        block = _c.sent();
                        if (block) {
                            blocks[log.blockNumber] = block.hash;
                        }
                        _c.label = 23;
                    case 23:
                        log.blockHash = blocks[log.blockNumber];
                        _c.label = 24;
                    case 24:
                        i++;
                        return [3 /*break*/, 21];
                    case 25: return [2 /*return*/, logs];
                    case 26:
                        if (this.network.name !== "homestead") {
                            return [2 /*return*/, 0.0];
                        }
                        _b = parseFloat;
                        return [4 /*yield*/, this.fetch("stats", { action: "ethprice" })];
                    case 27: return [2 /*return*/, _b.apply(void 0, [(_c.sent()).ethusd])];
                    case 28: return [3 /*break*/, 29];
                    case 29: return [2 /*return*/, _super.prototype.perform.call(this, method, params)];
                }
            });
        });
    };
    // Note: The `page` page parameter only allows pagination within the
    //       10,000 window available without a page and offset parameter
    //       Error: Result window is too large, PageNo x Offset size must
    //              be less than or equal to 10000
    EtherscanProvider.prototype.getHistory = function (addressOrName, startBlock, endBlock) {
        return __awaiter(this, void 0, void 0, function () {
            var params, result;
            var _a;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _a = {
                            action: "txlist"
                        };
                        return [4 /*yield*/, this.resolveName(addressOrName)];
                    case 1:
                        params = (_a.address = (_b.sent()),
                            _a.startblock = ((startBlock == null) ? 0 : startBlock),
                            _a.endblock = ((endBlock == null) ? 99999999 : endBlock),
                            _a.sort = "asc",
                            _a);
                        return [4 /*yield*/, this.fetch("account", params)];
                    case 2:
                        result = _b.sent();
                        return [2 /*return*/, result.map(function (tx) {
                                ["contractAddress", "to"].forEach(function (key) {
                                    if (tx[key] == "") {
                                        delete tx[key];
                                    }
                                });
                                if (tx.creates == null && tx.contractAddress != null) {
                                    tx.creates = tx.contractAddress;
                                }
                                var item = _this.formatter.transactionResponse(tx);
                                if (tx.timeStamp) {
                                    item.timestamp = parseInt(tx.timeStamp);
                                }
                                return item;
                            })];
                }
            });
        });
    };
    EtherscanProvider.prototype.isCommunityResource = function () {
        return (this.apiKey === defaultApiKey);
    };
    return EtherscanProvider;
}(base_provider_1.BaseProvider));
exports.EtherscanProvider = EtherscanProvider;
//# sourceMappingURL=etherscan-provider.js.map