'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
var minterjsUtil = require('minterjs-util');
var postSignedTx = require('./post-signed-tx.js');
var getNonce = require('./get-nonce.js');
var replaceCoin = require('./replace-coin.js');
var tx = require('../tx.js');
var utils = require('../utils.js');

var _excluded = ["gasRetryLimit", "nonceRetryLimit", "mempoolRetryLimit"],
  _excluded2 = ["gasRetryLimit", "nonceRetryLimit", "mempoolRetryLimit"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

/**
 * @typedef {TxOptions & PostTxOptionsExtra} PostTxOptions
 * @typedef {object} PostTxOptionsExtra
 * @property {number} [gasRetryLimit=2] - max number of autofix retries after gas error
 * @property {number} [nonceRetryLimit=0] - max number of autofix retries after nonce error
 * @preserve {number} [mempoolRetryLimit=0] - max number of retries after "already exists in mempool" error
 */

/**
 * @param {MinterApiInstance} apiInstance
 * @param {import('axios').AxiosRequestConfig} [factoryAxiosOptions]
 * @param {import('axios').AxiosRequestConfig} [factoryExtraAxiosOptions]
 * @return {PostTxInstance}
 */
function PostTx(apiInstance, factoryAxiosOptions, factoryExtraAxiosOptions) {
  var ensureNonce = new EnsureNonce(apiInstance, factoryExtraAxiosOptions);
  var replaceCoinSymbol = new replaceCoin.ReplaceCoinSymbol(apiInstance, factoryExtraAxiosOptions);
  /**
   * @typedef {Function} PostTxInstance
   * @param {TxParams} txParams
   * @param {PostTxOptions} options
   * @param {import('axios').AxiosRequestConfig} [axiosOptions]
   * @param {import('axios').AxiosRequestConfig} [extraAxiosOptions] - applied to secondary requests
   * @return {Promise<PostTxResponse>}
   */
  return function postTx(txParams) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
      _ref$gasRetryLimit = _ref.gasRetryLimit,
      gasRetryLimit = _ref$gasRetryLimit === void 0 ? 2 : _ref$gasRetryLimit,
      _ref$nonceRetryLimit = _ref.nonceRetryLimit,
      nonceRetryLimit = _ref$nonceRetryLimit === void 0 ? 0 : _ref$nonceRetryLimit,
      _ref$mempoolRetryLimi = _ref.mempoolRetryLimit,
      mempoolRetryLimit = _ref$mempoolRetryLimi === void 0 ? 0 : _ref$mempoolRetryLimi,
      txOptions = _objectWithoutProperties(_ref, _excluded);
    var axiosOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
    var extraAxiosOptions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
    axiosOptions = _objectSpread(_objectSpread({}, factoryAxiosOptions), axiosOptions);
    if (!txOptions.privateKey && txParams.privateKey) {
      txOptions.privateKey = txParams.privateKey;
      // eslint-disable-next-line no-console
      console.warn('privateKey field in tx params is deprecated, pass it to the second parameter');
    }
    // @TODO asserts

    var privateKeyPromise;
    if (txOptions.privateKey) {
      privateKeyPromise = Promise.resolve(txOptions.privateKey);
    } else if (txOptions.seedPhrase) {
      privateKeyPromise = utils.getPrivateKeyFromSeedPhraseAsync(txOptions.seedPhrase);
    } else {
      privateKeyPromise = Promise.resolve(undefined);
    }
    return privateKeyPromise.then(function (privateKey) {
      return Promise.all([ensureNonce(txParams, _objectSpread(_objectSpread({}, txOptions), {}, {
        privateKey: privateKey
      }), extraAxiosOptions), replaceCoinSymbol(txParams, extraAxiosOptions), Promise.resolve(privateKey)]);
    }).then(function (_ref2) {
      var _ref3 = _slicedToArray(_ref2, 3),
        newNonce = _ref3[0],
        newTxParams = _ref3[1],
        privateKey = _ref3[2];
      return _postTxHandleErrors(apiInstance, _objectSpread(_objectSpread({}, newTxParams), {}, {
        nonce: newNonce
      }), _objectSpread(_objectSpread({
        gasRetryLimit: gasRetryLimit,
        nonceRetryLimit: nonceRetryLimit,
        mempoolRetryLimit: mempoolRetryLimit
      }, txOptions), {}, {
        privateKey: privateKey,
        axiosOptions: axiosOptions
      }));
    });
  };
}

/**
 * @param {MinterApiInstance} apiInstance
 * @param {TxParams} txParams
 * @param {TxOptions} [options]
 * @param {import('axios').AxiosRequestConfig} [axiosOptions]
 * @return {Promise<PostTxResponse>}
 */
function _postTx(apiInstance, txParams, options, axiosOptions) {
  if (!txParams.chainId && apiInstance.defaults.chainId) {
    txParams.chainId = apiInstance.defaults.chainId;
  }
  var tx$1;
  tx$1 = !txParams.signatureData && utils.toInteger(txParams.signatureType) !== '2' ? tx.default(txParams, options) : tx.prepareTx(txParams, options);
  return new postSignedTx(apiInstance)(tx$1.serializeToString(), axiosOptions);
}

/**
 * Send `_postTx()` request and if it fails because of too low gas or already exists in mempool - make retries
 * On gas retry `txParams.gasPrice` will be updated with required min gas value from error response.
 * On mempool retry request will be sent after 5 seconds (average time of a block) to try put transaction into next block
 * @param {MinterApiInstance} apiInstance
 * @param {TxParams} txParams
 * @param {PostTxOptions} options
 * @param {import('axios').AxiosRequestConfig} [axiosOptions]
 * @return {Promise<PostTxResponse>}
 */
function _postTxHandleErrors(apiInstance, txParams, options, axiosOptions) {
  var gasRetryLimit = options.gasRetryLimit,
    nonceRetryLimit = options.nonceRetryLimit,
    mempoolRetryLimit = options.mempoolRetryLimit,
    txOptions = _objectWithoutProperties(options, _excluded2);
  return _postTx(apiInstance, txParams, txOptions, axiosOptions)["catch"](function (error) {
    // @TODO limit max gas_price to prevent sending tx with to high fees
    if (utils.toInteger(txParams.signatureType) !== '2' && gasRetryLimit > 0 && isGasError(error)) {
      var minGas = getMinGasFromError(error);
      // eslint-disable-next-line no-console
      console.log("make postTx retry, old gasPrice ".concat(txParams.gasPrice, ", new gasPrice ").concat(minGas));
      return _postTxHandleErrors(apiInstance, _objectSpread(_objectSpread({}, txParams), {}, {
        gasPrice: minGas
      }), _objectSpread(_objectSpread({}, options), {}, {
        gasRetryLimit: gasRetryLimit - 1
      }), axiosOptions);
    } else if (utils.toInteger(txParams.signatureType) !== '2' && nonceRetryLimit > 0 && isNonceError(error)) {
      var newNonce = getNonceFromError(error);
      // eslint-disable-next-line no-console
      console.log("make postTx retry, old nonce ".concat(txParams.nonce, ", new nonce ").concat(newNonce));
      return _postTxHandleErrors(apiInstance, _objectSpread(_objectSpread({}, txParams), {}, {
        nonce: newNonce
      }), _objectSpread(_objectSpread({}, options), {}, {
        nonceRetryLimit: nonceRetryLimit - 1
      }), axiosOptions);
    } else if (mempoolRetryLimit > 0 && isMempoolError(error)) {
      // eslint-disable-next-line no-console
      console.log('make postTx retry: tx exists in mempool');
      return utils.wait(5000).then(function () {
        return _postTxHandleErrors(apiInstance, txParams, _objectSpread(_objectSpread({}, options), {}, {
          mempoolRetryLimit: mempoolRetryLimit - 1
        }), axiosOptions);
      });
    } else {
      throw error;
    }
  });
}

/**
 * @param {MinterApiInstance} apiInstance
 * @param {import('axios').AxiosRequestConfig} [factoryAxiosOptions]
 * @return {EnsureNonceInstance}
 */
function EnsureNonce(apiInstance, factoryAxiosOptions) {
  var getNonce$1 = new getNonce(apiInstance, factoryAxiosOptions);
  /**
   * @typedef {Function} EnsureNonceInstance
   * @param {TxParams} txParams
   * @param {object} [txOptions]
   * @param {ByteArray} [txOptions.privateKey]
   * @param {string} [txOptions.address]
   * @param {string} [txOptions.seedPhrase]
   * @param {import('axios').AxiosRequestConfig} [axiosOptions]
   * @return {Promise<number>}
   */
  return function ensureNonce(txParams) {
    var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
      privateKey = _ref4.privateKey,
      address = _ref4.address;
      _ref4.seedPhrase;
    var axiosOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
    var nonce = txParams.nonce;
    if (!nonce && !address && !privateKey) {
      throw new Error('No nonce is given and no address or privateKey to retrieve it from API');
    }
    if (nonce) {
      return Promise.resolve(nonce);
    }
    // @TODO seedPhrase not used
    if (privateKey) {
      var privateKeyBuffer = utils.bufferFromBytes(privateKey);
      address = minterjsUtil.privateToAddressString(privateKeyBuffer);
    }
    return getNonce$1(address, axiosOptions);
  };
}

/**
 * Get tx_result data from error
 * @param error
 * @return {object|undefined}
 */
// function getTxResult(error) {
//     error = error.response?.data?.error;
//     // gate moves tx_result into root error, so check it too
//     return error && (error.tx_result || error);
// }

/**
 * Check if error caused by too low gas
 * @param {AxiosError} error
 * @return {boolean}
 */
function isGasError(error) {
  var _error$response;
  return ((_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.data.error.code) === '114';
}

/**
 * Check if error caused by: "Tx from address already exists in mempool"
 * @param {AxiosError} error
 * @return {boolean}
 */
function isMempoolError(error) {
  var _error$response2;
  return ((_error$response2 = error.response) === null || _error$response2 === void 0 ? void 0 : _error$response2.data.error.code) === '113';
}

/**
 * Check if error caused by nonce
 * @param {AxiosError} error
 * @return {boolean}
 */
function isNonceError(error) {
  var _error$response3;
  return ((_error$response3 = error.response) === null || _error$response3 === void 0 ? void 0 : _error$response3.data.error.code) === '101';
}

/**
 * Retrieve required min gas value from error message
 * @param {AxiosError} error
 * @return {number}
 */
function getMinGasFromError(error) {
  var _error$response4;
  return Number((_error$response4 = error.response) === null || _error$response4 === void 0 ? void 0 : _error$response4.data.error.data.min_gas_price);
}

/**
 * Retrieve required min gas value from error message
 * @param {AxiosError} error
 * @return {number}
 */
function getNonceFromError(error) {
  var _error$response5;
  return Number((_error$response5 = error.response) === null || _error$response5 === void 0 ? void 0 : _error$response5.data.error.data.expected_nonce);
}

exports.EnsureNonce = EnsureNonce;
exports.default = PostTx;
