text: /*
      This file is part of web3.js.
  
      web3.js is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      web3.js is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.
  
      You should have received a copy of the GNU Lesser General Public License
      along with web3.js.  If not, see <http://www.gnu.org/licenses/>.
  */
  /**
   * @file index.js
   * @author Fabian Vogelsteller <fabian@ethereum.org>
   * @author Marek Kotewicz <marek@parity.io>
   * @date 2017
   */
  
  'use strict';
  
  var _ = require('underscore');
  var errors = require('web3-core-helpers').errors;
  var formatters = require('web3-core-helpers').formatters;
  var utils = require('web3-utils');
  var promiEvent = require('web3-core-promievent');
  var Subscriptions = require('web3-core-subscriptions').subscriptions;
  
  var EthersTransactionUtils = require('@ethersproject/transactions');
  
  var Method = function Method(options) {
  
      if (!options.call || !options.name) {
          throw new Error('When creating a method you need to provide at least the "name" and "call" property.');
      }
  
      this.name = options.name;
      this.call = options.call;
      this.params = options.params || 0;
      this.inputFormatter = options.inputFormatter;
      this.outputFormatter = options.outputFormatter;
      this.transformPayload = options.transformPayload;
      this.extraFormatters = options.extraFormatters;
      this.abiCoder = options.abiCoder; // Will be used to encode the revert reason string
  
      this.requestManager = options.requestManager;
  
      // reference to eth.accounts
      this.accounts = options.accounts;
  
      this.defaultBlock = options.defaultBlock || 'latest';
      this.defaultAccount = options.defaultAccount || null;
      this.transactionBlockTimeout = options.transactionBlockTimeout || 50;
      this.transactionConfirmationBlocks = options.transactionConfirmationBlocks || 24;
      this.transactionPollingTimeout = options.transactionPollingTimeout || 750;
      this.defaultCommon = options.defaultCommon;
      this.defaultChain = options.defaultChain;
      this.defaultHardfork = options.defaultHardfork;
      this.handleRevert = options.handleRevert;
  };
  
  Method.prototype.setRequestManager = function (requestManager, accounts) {
      this.requestManager = requestManager;
  
      // reference to eth.accounts
      if (accounts) {
          this.accounts = accounts;
      }
  
  };
  
  Method.prototype.createFunction = function (requestManager, accounts) {
      var func = this.buildCall();
      func.call = this.call;
  
      this.setRequestManager(requestManager || this.requestManager, accounts || this.accounts);
  
      return func;
  };
  
  Method.prototype.attachToObject = function (obj) {
      var func = this.buildCall();
      func.call = this.call;
      var name = this.name.split('.');
      if (name.length > 1) {
          obj[name[0]] = obj[name[0]] || {};
          obj[name[0]][name[1]] = func;
      } else {
          obj[name[0]] = func;
      }
  };
  
  /**
   * Should be used to determine name of the jsonrpc method based on arguments
   *
   * @method getCall
   * @param {Array} arguments
   * @return {String} name of jsonrpc method
   */
  Method.prototype.getCall = function (args) {
      return _.isFunction(this.call) ? this.call(args) : this.call;
  };
  
  /**
   * Should be used to extract callback from array of arguments. Modifies input param
   *
   * @method extractCallback
   * @param {Array} arguments
   * @return {Function|Null} callback, if exists
   */
  Method.prototype.extractCallback = function (args) {
      if (_.isFunction(args[args.length - 1])) {
          return args.pop(); // modify the args array!
      }
  };
  
  /**
   * Should be called to check if the number of arguments is correct
   *
   * @method validateArgs
   * @param {Array} arguments
   * @throws {Error} if it is not
   */
  Method.prototype.validateArgs = function (args) {
      if (args.length !== this.params) {
          throw errors.InvalidNumberOfParams(args.length, this.params, this.name);
      }
  };
  
  /**
   * Should be called to format input args of method
   *
   * @method formatInput
   * @param {Array}
   * @return {Array}
   */
  Method.prototype.formatInput = function (args) {
      var _this = this;
  
      if (!this.inputFormatter) {
          return args;
      }
  
      return this.inputFormatter.map(function (formatter, index) {
          // bind this for defaultBlock, and defaultAccount
          return formatter ? formatter.call(_this, args[index]) : args[index];
      });
  };
  
  /**
   * Should be called to format output(result) of method
   *
   * @method formatOutput
   * @param {Object}
   * @return {Object}
   */
  Method.prototype.formatOutput = function (result) {
      var _this = this;
  
      if (_.isArray(result)) {
          return result.map(function (res) {
              return _this.outputFormatter && res ? _this.outputFormatter(res) : res;
          });
      } else {
          return this.outputFormatter && result ? this.outputFormatter(result) : result;
      }
  };
  
  /**
   * Should create payload from given input args
   *
   * @method toPayload
   * @param {Array} args
   * @return {Object}
   */
  Method.prototype.toPayload = function (args) {
      var call = this.getCall(args);
      var callback = this.extractCallback(args);
      var params = this.formatInput(args);
      this.validateArgs(params);
  
      var payload = {
          method: call,
          params: params,
          callback: callback
      };
  
      if (this.transformPayload) {
          payload = this.transformPayload(payload);
      }
  
      return payload;
  };
  
  
  Method.prototype._confirmTransaction = function (defer, result, payload) {
      var method = this,
          promiseResolved = false,
          canUnsubscribe = true,
          timeoutCount = 0,
          confirmationCount = 0,
          intervalId = null,
          lastBlock = null,
          receiptJSON = '',
          gasProvided = (_.isObject(payload.params[0]) && payload.params[0].gas) ? payload.params[0].gas : null,
          isContractDeployment = _.isObject(payload.params[0]) &&
              payload.params[0].data &&
              payload.params[0].from &&
              !payload.params[0].to,
          hasBytecode = isContractDeployment && payload.params[0].data.length > 2;
  
      // add custom send Methods
      var _ethereumCalls = [
          new Method({
              name: 'getBlockByNumber',
              call: 'eth_getBlockByNumber',
              params: 2,
              inputFormatter: [formatters.inputBlockNumberFormatter, function (val) {
                  return !!val;
              }],
              outputFormatter: formatters.outputBlockFormatter
          }),
          new Method({
              name: 'getTransactionReceipt',
              call: 'eth_getTransactionReceipt',
              params: 1,
              inputFormatter: [null],
              outputFormatter: formatters.outputTransactionReceiptFormatter
          }),
          new Method({
              name: 'getCode',
              call: 'eth_getCode',
              params: 2,
              inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter]
          }),
          new Method({
              name: 'getTransactionByHash',
              call: 'eth_getTransactionByHash',
              params: 1,
              inputFormatter: [null],
              outputFormatter: formatters.outputTransactionFormatter
          }),
          new Subscriptions({
              name: 'subscribe',
              type: 'eth',
              subscriptions: {
                  'newBlockHeaders': {
                      subscriptionName: 'newHeads', // replace subscription with this name
                      params: 0,
                      outputFormatter: formatters.outputBlockFormatter
                  }
              }
          })
      ];
      // attach methods to this._ethereumCall
      var _ethereumCall = {};
      _.each(_ethereumCalls, function (mthd) {
          mthd.attachToObject(_ethereumCall);
          mthd.requestManager = method.requestManager; // assign rather than call setRequestManager()
      });
  
  
      // fire "receipt" and confirmation events and resolve after
      var checkConfirmation = function (existingReceipt, isPolling, err, blockHeader, sub) {
          if (!err) {
              // create fake unsubscribe
              if (!sub) {
                  sub = {
                      unsubscribe: function () {
                          clearInterval(intervalId);
                      }
                  };
              }
              // if we have a valid receipt we don't need to send a request
              return (existingReceipt ? promiEvent.resolve(existingReceipt) : _ethereumCall.getTransactionReceipt(result))
              // catch error from requesting receipt
                  .catch(function (err) {
                      sub.unsubscribe();
                      promiseResolved = true;
                      utils._fireError(
                          {
                              message: 'Failed to check for transaction receipt:',
                              data: err
                          },
                          defer.eventEmitter,
                          defer.reject
                      );
                  })
                  // if CONFIRMATION listener exists check for confirmations, by setting canUnsubscribe = false
                  .then(async function (receipt) {
                      if (!receipt || !receipt.blockHash) {
                          throw new Error('Receipt missing or blockHash null');
                      }
  
                      // apply extra formatters
                      if (method.extraFormatters && method.extraFormatters.receiptFormatter) {
                          receipt = method.extraFormatters.receiptFormatter(receipt);
                      }
  
                      // check if confirmation listener exists
                      if (defer.eventEmitter.listeners('confirmation').length > 0) {
                          var block;
  
                          // If there was an immediately retrieved receipt, it's already
                          // been confirmed by the direct call to checkConfirmation needed
                          // for parity instant-seal
                          if (existingReceipt === undefined || confirmationCount !== 0) {
                              // Get latest block to emit with confirmation
                              var latestBlock = await _ethereumCall.getBlockByNumber('latest');
                              var latestBlockHash = latestBlock ? latestBlock.hash : null;
  
                              if (isPolling) { // Check if actually a new block is existing on polling
                                  if (lastBlock) {
                                      block = await _ethereumCall.getBlockByNumber(lastBlock.number + 1);
                                      if (block) {
                                          lastBlock = block;
                                          defer.eventEmitter.emit('confirmation', confirmationCount, receipt, latestBlockHash);
                                      }
                                  } else {
                                      block = await _ethereumCall.getBlockByNumber(receipt.blockNumber);
                                      lastBlock = block;
                                      defer.eventEmitter.emit('confirmation', confirmationCount, receipt, latestBlockHash);
                                  }
                              } else {
                                  defer.eventEmitter.emit('confirmation', confirmationCount, receipt, latestBlockHash);
                              }
                          }
  
                          if ((isPolling && block) || !isPolling) {
                              confirmationCount++;
                          }
                          canUnsubscribe = false;
  
                          if (confirmationCount === method.transactionConfirmationBlocks + 1) { // add 1 so we account for conf 0
                              sub.unsubscribe();
                              defer.eventEmitter.removeAllListeners();
                          }
                      }
  
                      return receipt;
                  })
                  // CHECK for CONTRACT DEPLOYMENT
                  .then(async function (receipt) {
  
                      if (isContractDeployment && !promiseResolved) {
  
                          if (!receipt.contractAddress) {
  
                              if (canUnsubscribe) {
                                  sub.unsubscribe();
                                  promiseResolved = true;
                              }
  
                              utils._fireError(
                                  errors.NoContractAddressFoundError(receipt),
                                  defer.eventEmitter,
                                  defer.reject,
                                  null,
                                  receipt
                              );
                              return;
                          }
  
                          var code;
                          try {
                              code = await _ethereumCall.getCode(receipt.contractAddress);
                          } catch(err){
                              // ignore;
                          }
  
                          if (!code) {
                              return;
                          }
  
                          // If deployment is status.true and there was a real
                          // bytecode string, assume it was successful.
                          var deploymentSuccess = receipt.status === true && hasBytecode;
  
                          if (deploymentSuccess || code.length > 2) {
                              defer.eventEmitter.emit('receipt', receipt);
  
                              // if contract, return instance instead of receipt
                              if (method.extraFormatters && method.extraFormatters.contractDeployFormatter) {
                                  defer.resolve(method.extraFormatters.contractDeployFormatter(receipt));
                              } else {
                                  defer.resolve(receipt);
                              }
  
                              // need to remove listeners, as they aren't removed automatically when succesfull
                              if (canUnsubscribe) {
                                  defer.eventEmitter.removeAllListeners();
                              }
  
                          } else {
                              utils._fireError(
                                  errors.ContractCodeNotStoredError(receipt),
                                  defer.eventEmitter,
                                  defer.reject,
                                  null,
                                  receipt
                              );
                          }
  
                          if (canUnsubscribe) {
                              sub.unsubscribe();
                          }
                          promiseResolved = true;
                      }
  
                      return receipt;
                  })
                  // CHECK for normal tx check for receipt only
                  .then(async function (receipt) {
                      if (!isContractDeployment && !promiseResolved) {
                          if (!receipt.outOfGas &&
                              (!gasProvided || gasProvided !== receipt.gasUsed) &&
                              (receipt.status === true || receipt.status === '0x1' || typeof receipt.status === 'undefined')) {
                              defer.eventEmitter.emit('receipt', receipt);
                              defer.resolve(receipt);
  
                              // need to remove listeners, as they aren't removed automatically when succesfull
                              if (canUnsubscribe) {
                                  defer.eventEmitter.removeAllListeners();
                              }
  
                          } else {
                              receiptJSON = JSON.stringify(receipt, null, 2);
  
                              if (receipt.status === false || receipt.status === '0x0') {
                                  try {
                                      var revertMessage = null;
  
                                      if ( method.handleRevert &&
                                          (method.call === 'eth_sendTransaction' || method.call === 'eth_sendRawTransaction'))
                                      {
                                          var txReplayOptions = payload.params[0];
  
                                          // If send was raw, fetch the transaction and reconstitute the
                                          // original params so they can be replayed with `eth_call`
                                          if (method.call === 'eth_sendRawTransaction'){
                                              var rawTransactionHex = payload.params[0];
  
                                              var parsedTx = EthersTransactionUtils.parse(rawTransactionHex);
  
                                              txReplayOptions = formatters.inputTransactionFormatter({
                                                  data: parsedTx.data,
                                                  to: parsedTx.to,
                                                  from: parsedTx.from,
                                                  gas: parsedTx.gasLimit.toHexString(),
                                                  gasPrice: parsedTx.gasPrice.toHexString(),
                                                  value: parsedTx.value.toHexString()
                                              });
                                          }
  
                                          // Get revert reason string with eth_call
                                          revertMessage = await method.getRevertReason(
                                              txReplayOptions,
                                              receipt.blockNumber
                                          );
  
                                          if (revertMessage) { // Only throw a revert error if a revert reason is existing
                                              utils._fireError(
                                                  errors.TransactionRevertInstructionError(revertMessage.reason, revertMessage.signature, receipt),
                                                  defer.eventEmitter,
                                                  defer.reject,
                                                  null,
                                                  receipt
                                              );
                                          } else {
                                              throw false; // Throw false and let the try/catch statement handle the error correctly after
                                          }
                                      } else {
                                          throw false; // Throw false and let the try/catch statement handle the error correctly after
                                      }
                                  } catch (error) {
                                      // Throw an normal revert error if no revert reason is given or the detection of it is disabled
                                      utils._fireError(
                                          errors.TransactionRevertedWithoutReasonError(receipt),
                                          defer.eventEmitter,
                                          defer.reject,
                                          null,
                                          receipt
                                      );
                                  }
                              } else {
                                  // Throw OOG if status is not existing and provided gas and used gas are equal
                                  utils._fireError(
                                      errors.TransactionOutOfGasError(receipt),
                                      defer.eventEmitter,
                                      defer.reject,
                                      null,
                                      receipt
                                  );
                              }
                          }
  
                          if (canUnsubscribe) {
                              sub.unsubscribe();
                          }
                          promiseResolved = true;
                      }
  
                  })
                  // time out the transaction if not mined after 50 blocks
                  .catch(function () {
                      timeoutCount++;
  
                      // check to see if we are http polling
                      if (!!isPolling) {
                          // polling timeout is different than transactionBlockTimeout blocks since we are triggering every second
                          if (timeoutCount - 1 >= method.transactionPollingTimeout) {
                              sub.unsubscribe();
                              promiseResolved = true;
                              utils._fireError(
                                  errors.TransactionError('Transaction was not mined within ' + method.transactionPollingTimeout + ' seconds, please make sure your transaction was properly sent. Be aware that it might still be mined!'),
                                  defer.eventEmitter,
                                  defer.reject
                              );
                          }
                      } else {
                          if (timeoutCount - 1 >= method.transactionBlockTimeout) {
                              sub.unsubscribe();
                              promiseResolved = true;
                              utils._fireError(
                                  errors.TransactionError('Transaction was not mined within ' + method.transactionBlockTimeout + ' blocks, please make sure your transaction was properly sent. Be aware that it might still be mined!'),
                                  defer.eventEmitter,
                                  defer.reject
                              );
                          }
                      }
                  });
  
  
          } else {
              sub.unsubscribe();
              promiseResolved = true;
              utils._fireError({
                  message: 'Failed to subscribe to new newBlockHeaders to confirm the transaction receipts.',
                  data: err
              }, defer.eventEmitter, defer.reject);
          }
      };
  
      // start watching for confirmation depending on the support features of the provider
      var startWatching = function (existingReceipt) {
          const startInterval = () => {
              intervalId = setInterval(checkConfirmation.bind(null, existingReceipt, true), 1000);
          };
  
          if (!this.requestManager.provider.on) {
              startInterval();
          } else {
              _ethereumCall.subscribe('newBlockHeaders', function (err, blockHeader, sub) {
                  if (err || !blockHeader) {
                      // fall back to polling
                      startInterval();
                  } else {
                      checkConfirmation(existingReceipt, false, err, blockHeader, sub);
                  }
              });
          }
      }.bind(this);
  
  
      // first check if we already have a confirmed transaction
      _ethereumCall.getTransactionReceipt(result)
          .then(function (receipt) {
              if (receipt && receipt.blockHash) {
                  if (defer.eventEmitter.listeners('confirmation').length > 0) {
                      // We must keep on watching for new Blocks, if a confirmation listener is present
                      startWatching(receipt);
                  }
                  checkConfirmation(receipt, false);
  
              } else if (!promiseResolved) {
                  startWatching();
              }
          })
          .catch(function () {
              if (!promiseResolved) startWatching();
          });
  
  };
  
  
  var getWallet = function (from, accounts) {
      var wallet = null;
  
      // is index given
      if (_.isNumber(from)) {
          wallet = accounts.wallet[from];
  
          // is account given
      } else if (_.isObject(from) && from.address && from.privateKey) {
          wallet = from;
  
          // search in wallet for address
      } else {
          wallet = accounts.wallet[from.toLowerCase()];
      }
  
      return wallet;
  };
  
  Method.prototype.buildCall = function () {
      var method = this,
          isSendTx = (method.call === 'eth_sendTransaction' || method.call === 'eth_sendRawTransaction'), // || method.call === 'personal_sendTransaction'
          isCall = (method.call === 'eth_call');
  
      // actual send function
      var send = function () {
          var defer = promiEvent(!isSendTx),
              payload = method.toPayload(Array.prototype.slice.call(arguments));
  
          // CALLBACK function
          var sendTxCallback = function (err, result) {
              if (method.handleRevert && isCall && method.abiCoder) {
                  var reasonData;
  
                  // Ganache / Geth <= 1.9.13 return the reason data as a successful eth_call response
                  // Geth >= 1.9.15 attaches the reason data to an error object.
                  // Geth 1.9.14 is missing revert reason (https://github.com/ethereum/web3.js/issues/3520)
                  if (!err && method.isRevertReasonString(result)){
                      reasonData = result.substring(10);
                  } else if (err && err.data){
                      reasonData = err.data.substring(10);
                  }
  
                  if (reasonData){
                      var reason = method.abiCoder.decodeParameter('string', '0x' + reasonData);
                      var signature = 'Error(String)';
  
                      utils._fireError(
                          errors.RevertInstructionError(reason, signature),
                          defer.eventEmitter,
                          defer.reject,
                          payload.callback,
                          {
                              reason: reason,
                              signature: signature
                          }
                      );
  
                      return;
                  }
              }
  
              try {
                  result = method.formatOutput(result);
              } catch (e) {
                  err = e;
              }
  
              if (result instanceof Error) {
                  err = result;
              }
  
              if (!err) {
                  if (payload.callback) {
                      payload.callback(null, result);
                  }
              } else {
                  if (err.error) {
                      err = err.error;
                  }
  
                  return utils._fireError(err, defer.eventEmitter, defer.reject, payload.callback);
              }
  
              // return PROMISE
              if (!isSendTx) {
                  if (!err) {
                      defer.resolve(result);
                  }
  
                  // return PROMIEVENT
              } else {
                  defer.eventEmitter.emit('transactionHash', result);
  
                  method._confirmTransaction(defer, result, payload);
              }
  
          };
  
          // SENDS the SIGNED SIGNATURE
          var sendSignedTx = function (sign) {
  
              var signedPayload = _.extend({}, payload, {
                  method: 'eth_sendRawTransaction',
                  params: [sign.rawTransaction]
              });
  
              method.requestManager.send(signedPayload, sendTxCallback);
          };
  
  
          var sendRequest = function (payload, method) {
  
              if (method && method.accounts && method.accounts.wallet && method.accounts.wallet.length) {
                  var wallet;
  
                  // ETH_SENDTRANSACTION
                  if (payload.method === 'eth_sendTransaction') {
                      var tx = payload.params[0];
                      wallet = getWallet((_.isObject(tx)) ? tx.from : null, method.accounts);
  
  
                      // If wallet was found, sign tx, and send using sendRawTransaction
                      if (wallet && wallet.privateKey) {
                          var txOptions = _.omit(tx, 'from');
  
                          if (method.defaultChain && !txOptions.chain) {
                              txOptions.chain = method.defaultChain;
                          }
  
                          if (method.defaultHardfork && !txOptions.hardfork) {
                              txOptions.hardfork = method.defaultHardfork;
                          }
  
                          if (method.defaultCommon && !txOptions.common) {
                              txOptions.common = method.defaultCommon;
                          }
  
                          method.accounts.signTransaction(txOptions, wallet.privateKey)
                              .then(sendSignedTx)
                              .catch(function (err) {
                                  if (_.isFunction(defer.eventEmitter.listeners) && defer.eventEmitter.listeners('error').length) {
                                      try {
                                          defer.eventEmitter.emit('error', err);
                                      } catch (err) {
                                          // Ignore userland error prevent it to bubble up within web3.
                                      }
                                      defer.eventEmitter.removeAllListeners();
                                      defer.eventEmitter.catch(function () {
                                      });
                                  }
                                  defer.reject(err);
                              });
                          return;
                      }
  
                      // ETH_SIGN
                  } else if (payload.method === 'eth_sign') {
                      var data = payload.params[1];
                      wallet = getWallet(payload.params[0], method.accounts);
  
                      // If wallet was found, sign tx, and send using sendRawTransaction
                      if (wallet && wallet.privateKey) {
                          var sign = method.accounts.sign(data, wallet.privateKey);
  
                          if (payload.callback) {
                              payload.callback(null, sign.signature);
                          }
  
                          defer.resolve(sign.signature);
                          return;
                      }
  
  
                  }
              }
  
  
              return method.requestManager.send(payload, sendTxCallback);
          };
  
          // Send the actual transaction
          if (isSendTx && _.isObject(payload.params[0]) && typeof payload.params[0].gasPrice === 'undefined') {
  
              var getGasPrice = (new Method({
                  name: 'getGasPrice',
                  call: 'eth_gasPrice',
                  params: 0
              })).createFunction(method.requestManager);
  
              getGasPrice(function (err, gasPrice) {
  
                  if (gasPrice) {
                      payload.params[0].gasPrice = gasPrice;
                  }
  
                  if (isSendTx) {
                      setTimeout(() => {
                          defer.eventEmitter.emit('sending', payload);
                      }, 0);
                  }
  
                  sendRequest(payload, method);
              });
  
          } else {
              if (isSendTx) {
                  setTimeout(() => {
                      defer.eventEmitter.emit('sending', payload);
                  }, 0);
              }
  
              sendRequest(payload, method);
          }
  
          if (isSendTx) {
              setTimeout(() => {
                  defer.eventEmitter.emit('sent', payload);
              }, 0);
          }
  
          return defer.eventEmitter;
      };
  
      // necessary to attach things to the method
      send.method = method;
      // necessary for batch requests
      send.request = this.request.bind(this);
      return send;
  };
  
  /**
   * Returns the revert reason string if existing or otherwise false.
   *
   * @method getRevertReason
   *
   * @param {Object} txOptions
   * @param {Number} blockNumber
   *
   * @returns {Promise<Boolean|String>}
   */
  Method.prototype.getRevertReason = function (txOptions, blockNumber) {
      var self = this;
  
      return new Promise(function (resolve, reject) {
          (new Method({
              name: 'call',
              call: 'eth_call',
              params: 2,
              abiCoder: self.abiCoder,
              handleRevert: true
          }))
              .createFunction(self.requestManager)(txOptions, utils.numberToHex(blockNumber))
              .then(function () {
                  resolve(false);
              })
              .catch(function (error) {
                  if (error.reason) {
                      resolve({
                          reason: error.reason,
                          signature: error.signature
                      });
                  } else {
                      reject(error);
                  }
              });
      });
  };
  
  /**
   * Checks if the given hex string is a revert message from the EVM
   *
   * @method isRevertReasonString
   *
   * @param {String} data - Hex string prefixed with 0x
   *
   * @returns {Boolean}
   */
  Method.prototype.isRevertReasonString = function (data) {
      return _.isString(data) && ((data.length - 2) / 2) % 32 === 4 && data.substring(0, 10) === '0x08c379a0';
  };
  
  /**
   * Should be called to create the pure JSONRPC request which can be used in a batch request
   *
   * @method request
   * @return {Object} jsonrpc request
   */
  Method.prototype.request = function () {
      var payload = this.toPayload(Array.prototype.slice.call(arguments));
      payload.format = this.formatOutput.bind(this);
      return payload;
  };
  
  module.exports = Method;
  