"use strict";
// This file is a modified version of the following library:
// https://github.com/authereum/eth-revert-reason
// implemented changes:
// - add _tx parameter so we can call a tx without a txHash
// - handle Ganache errors
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 (g && (g = 0, op[0] && (_ = 0)), _) 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.getRevertReason = void 0;
var ethers_1 = require("ethers");
var toUtf8String = ethers_1.utils.toUtf8String;
var AddressZero = ethers_1.constants.AddressZero;
/**
 * Get the revert reason from just a transaction hash
 * @param {string} txHash - Hash of an Ethereum transaction
 * @param {object} tx - Ethereum transaction object
 * @param {string} network - Ethereum network name
 * @param {number || string} blockNumber - A block number or 'latest' to make the call from
 * @param {*} customProvider - Custom provider (Only ethers and web3 providers are supported at thsitime)
 */
function getRevertReason(txHash, _tx, // custom addition
network, blockNumber, customProvider) {
    if (network === void 0) { network = 'mainnet'; }
    if (blockNumber === void 0) { blockNumber = undefined; }
    if (customProvider === void 0) { customProvider = undefined; }
    return __awaiter(this, void 0, void 0, function () {
        var provider, tx, _a, code, err_1;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    // Normalize the input params
                    network = network.toLowerCase();
                    blockNumber = blockNumber || 'latest';
                    if (!txHash) return [3 /*break*/, 2];
                    return [4 /*yield*/, validateInputPreProvider(txHash, network)];
                case 1:
                    _b.sent();
                    _b.label = 2;
                case 2:
                    // If a web3 provider is passed in, wrap it in an ethers provider
                    // A standard web3 provider will have `.version`, while an ethers will not
                    if (customProvider && customProvider.version) {
                        customProvider = new ethers_1.providers.Web3Provider(customProvider.currentProvider);
                    }
                    provider = customProvider || (0, ethers_1.getDefaultProvider)(network);
                    // Perform validation after defining a provider
                    return [4 /*yield*/, validateInputPostProvider(txHash, network, blockNumber, provider)];
                case 3:
                    // Perform validation after defining a provider
                    _b.sent();
                    _b.label = 4;
                case 4:
                    _b.trys.push([4, 8, , 9]);
                    _a = _tx;
                    if (_a) return [3 /*break*/, 6];
                    return [4 /*yield*/, provider.getTransaction(txHash)];
                case 5:
                    _a = (_b.sent());
                    _b.label = 6;
                case 6:
                    tx = _a;
                    return [4 /*yield*/, getCode(tx, network, blockNumber, provider)];
                case 7:
                    code = _b.sent();
                    return [2 /*return*/, decodeMessage(code, network)];
                case 8:
                    err_1 = _b.sent();
                    throw new Error('Unable to decode revert reason');
                case 9: return [2 /*return*/];
            }
        });
    });
}
exports.getRevertReason = getRevertReason;
function validateInputPreProvider(txHash, network) {
    return __awaiter(this, void 0, void 0, function () {
        var networks;
        return __generator(this, function (_a) {
            // Only accept a valid txHash
            if (!/^0x([A-Fa-f0-9]{64})$/.test(txHash) ||
                txHash.substring(0, 2) !== '0x') {
                throw new Error('Invalid transaction hash');
            }
            networks = ['mainnet', 'kovan', 'goerli', 'ropsten', 'rinkeby'];
            if (!networks.includes(network)) {
                throw new Error('Not a valid network');
            }
            return [2 /*return*/];
        });
    });
}
function validateInputPostProvider(txHash, network, blockNumber, provider) {
    return __awaiter(this, void 0, void 0, function () {
        var tx, err_2, currentBlockNumber, err_3, errCode, infuraErrCode;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (!(network === 'kovan')) return [3 /*break*/, 4];
                    _a.label = 1;
                case 1:
                    _a.trys.push([1, 3, , 4]);
                    return [4 /*yield*/, provider.getTransaction(txHash)];
                case 2:
                    tx = _a.sent();
                    getCode(tx, network, blockNumber, provider);
                    return [3 /*break*/, 4];
                case 3:
                    err_2 = _a.sent();
                    throw new Error('Please use a provider that exposes the Parity trace methods to decode the revert reason');
                case 4:
                    if (!(blockNumber !== 'latest')) return [3 /*break*/, 9];
                    return [4 /*yield*/, provider.getBlockNumber()];
                case 5:
                    currentBlockNumber = _a.sent();
                    blockNumber = Number(blockNumber);
                    // The block cannot be in the future
                    if (blockNumber >= currentBlockNumber) {
                        throw new Error('You cannot use a blocknumber that has not yet happened');
                    }
                    if (!(blockNumber < currentBlockNumber - 128)) return [3 /*break*/, 9];
                    _a.label = 6;
                case 6:
                    _a.trys.push([6, 8, , 9]);
                    // Check to see if a provider has access to an archive node
                    return [4 /*yield*/, provider.getBalance(AddressZero, blockNumber)];
                case 7:
                    // Check to see if a provider has access to an archive node
                    _a.sent();
                    return [3 /*break*/, 9];
                case 8:
                    err_3 = _a.sent();
                    errCode = JSON.parse(err_3.responseText).error.code;
                    infuraErrCode = -32002;
                    if (errCode === infuraErrCode) {
                        throw new Error('You cannot use a blocknumber that is older than 128 blocks. Please use a provider that uses a full archival node.');
                    }
                    return [3 /*break*/, 9];
                case 9: return [2 /*return*/];
            }
        });
    });
}
function decodeMessage(code, network) {
    // NOTE: `code` may end with 0's which will return a text string with empty whitespace characters
    // This will truncate all 0s and set up the hex string as expected
    // NOTE: Parity (Kovan) returns in a different format than other clients
    var codeString;
    var fnSelectorByteLength = 4;
    var dataOffsetByteLength = 32;
    var strLengthByteLength = 32;
    var strLengthStartPos = 2 + (fnSelectorByteLength + dataOffsetByteLength) * 2;
    var strDataStartPos = 2 + (fnSelectorByteLength + dataOffsetByteLength + strLengthByteLength) * 2;
    if (network === 'kovan') {
        var strLengthHex = code
            .slice(strLengthStartPos)
            .slice(0, strLengthByteLength * 2);
        var strLengthInt = parseInt("0x".concat(strLengthHex), 16);
        var strDataEndPos = strDataStartPos + strLengthInt * 2;
        if (codeString === '0x')
            return '';
        codeString = "0x".concat(code.slice(strDataStartPos, strDataEndPos));
    }
    else {
        codeString = "0x".concat(code.substr(138)).replace(/0+$/, '');
    }
    // If the codeString is an odd number of characters, add a trailing 0
    if (codeString.length % 2 === 1) {
        codeString += '0';
    }
    return toUtf8String(codeString);
}
function getCode(tx, network, blockNumber, provider) {
    return __awaiter(this, void 0, void 0, function () {
        var err_4, data, err_5, err_6;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    console.log('network', network);
                    if (!(network === 'unknown' || network === 'local')) return [3 /*break*/, 4];
                    _a.label = 1;
                case 1:
                    _a.trys.push([1, 3, , 4]);
                    return [4 /*yield*/, provider.call(tx, blockNumber)];
                case 2: 
                // NOTE: The await is intentional in order for the catch to work
                return [2 /*return*/, _a.sent()];
                case 3:
                    err_4 = _a.sent();
                    data = err_4.data[Object.keys(err_4.data)[0]];
                    return [2 /*return*/, data.return];
                case 4:
                    if (!(network === 'kovan')) return [3 /*break*/, 8];
                    _a.label = 5;
                case 5:
                    _a.trys.push([5, 7, , 8]);
                    return [4 /*yield*/, provider.call(tx, blockNumber)];
                case 6: 
                // NOTE: The await is intentional in order for the catch to work
                return [2 /*return*/, _a.sent()];
                case 7:
                    err_5 = _a.sent();
                    return [2 /*return*/, JSON.parse(err_5.responseText).error.data.substr(9)];
                case 8:
                    if (!(network === 'polygon')) return [3 /*break*/, 13];
                    _a.label = 9;
                case 9:
                    _a.trys.push([9, 11, , 12]);
                    return [4 /*yield*/, provider.call(tx, blockNumber)];
                case 10: 
                // NOTE: The await is intentional in order for the catch to work
                return [2 /*return*/, _a.sent()];
                case 11:
                    err_6 = _a.sent();
                    // console.log('code err', err.error.message);
                    console.log('err', err_6.message);
                    return [2 /*return*/, err_6.error
                            ? err_6.error.message.replace('code err execution reverted: ', '')
                            : err_6.message.replace('code err execution reverted: ', '')];
                case 12: return [3 /*break*/, 14];
                case 13: return [2 /*return*/, provider.call(tx, blockNumber)];
                case 14: return [2 /*return*/];
            }
        });
    });
}
//# sourceMappingURL=eth-revert-reason.js.map