| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808 |
- /*
- 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 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.transactionPollingInterval = options.transactionPollingInterval || 1000;
- this.blockHeaderTimeout = options.blockHeaderTimeout || 10; // 10 seconds
- 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();
- Object.defineProperty(func, 'call', { configurable: true, writable: true, value: this.call });
- this.setRequestManager(requestManager || this.requestManager, accounts || this.accounts);
- return func;
- };
- Method.prototype.attachToObject = function (obj) {
- var func = this.buildCall();
- Object.defineProperty(func, 'call', { configurable: true, writable: true, value: 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 typeof this.call === 'function' ? 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 (typeof (args[args.length - 1]) === 'function') {
- 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 (Array.isArray(result)) {
- return result.map(function (res) {
- return _this.outputFormatter && res ? _this.outputFormatter(res, this?.hexFormat) : res;
- });
- }
- else {
- return this.outputFormatter && result ? this.outputFormatter(result, this?.hexFormat) : 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, blockHeaderTimeoutId = null, lastBlock = null, receiptJSON = '', gasProvided = ((!!payload.params[0] && typeof payload.params[0] === 'object') && payload.params[0].gas) ? payload.params[0].gas : null, isContractDeployment = (!!payload.params[0] && typeof payload.params[0] === 'object') &&
- 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',
- params: 0,
- outputFormatter: formatters.outputBlockFormatter
- }
- }
- })
- ];
- // attach methods to this._ethereumCall
- var _ethereumCall = {};
- _ethereumCalls.forEach(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);
- clearTimeout(blockHeaderTimeoutId);
- }
- };
- }
- // 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 ? parsedTx.gasPrice.toHexString() : undefined,
- 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) {
- let blockHeaderArrived = false;
- const startInterval = () => {
- intervalId = setInterval(checkConfirmation.bind(null, existingReceipt, true), method.transactionPollingInterval);
- };
- // If provider do not support event subscription use polling
- if (!this.requestManager.provider.on) {
- return startInterval();
- }
- // Subscribe to new block headers to look for tx receipt
- _ethereumCall.subscribe('newBlockHeaders', function (err, blockHeader, sub) {
- blockHeaderArrived = true;
- if (err || !blockHeader) {
- // fall back to polling
- return startInterval();
- }
- checkConfirmation(existingReceipt, false, err, blockHeader, sub);
- });
- // Fallback to polling if tx receipt didn't arrived in "blockHeaderTimeout" [10 seconds]
- blockHeaderTimeoutId = setTimeout(() => {
- if (!blockHeaderArrived) {
- startInterval();
- }
- }, this.blockHeaderTimeout * 1000);
- }.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 (typeof from === 'number') {
- wallet = accounts.wallet[from];
- // is account given
- }
- else if (!!from && typeof from === 'object' && 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 () {
- let args = Array.prototype.slice.call(arguments);
- var defer = promiEvent(!isSendTx), payload = method.toPayload(args);
- method.hexFormat = false;
- if (method.call === 'eth_getTransactionReceipt'
- || method.call === 'eth_getTransactionByHash'
- || method.name === 'getBlock') {
- method.hexFormat = (payload.params.length < args.length && args[args.length - 1] === 'hex');
- }
- // 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) {
- // workaround embedded error details got from some providers like MetaMask
- if (typeof err.data === 'object') {
- // Ganache has no `originalError` sub-object unlike others
- var originalError = err.data.originalError ?? err.data;
- reasonData = originalError.data.substring(10);
- }
- else {
- 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 = { ...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((!!tx && typeof tx === 'object') ? tx.from : null, method.accounts);
- // If wallet was found, sign tx, and send using sendRawTransaction
- if (wallet && wallet.privateKey) {
- var tx = JSON.parse(JSON.stringify(tx));
- delete tx.from;
- if (method.defaultChain && !tx.chain) {
- tx.chain = method.defaultChain;
- }
- if (method.defaultHardfork && !tx.hardfork) {
- tx.hardfork = method.defaultHardfork;
- }
- if (method.defaultCommon && !tx.common) {
- tx.common = method.defaultCommon;
- }
- method.accounts.signTransaction(tx, wallet.privateKey)
- .then(sendSignedTx)
- .catch(function (err) {
- if (typeof defer.eventEmitter.listeners === 'function' && 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);
- };
- const hasSendTxObject = isSendTx
- && !!payload.params[0]
- && typeof payload.params[0] === 'object';
- if (hasSendTxObject &&
- payload.params[0].type === '0x1'
- && typeof payload.params[0].accessList === 'undefined') {
- payload.params[0].accessList = [];
- }
- // Send the actual transaction
- if (hasSendTxObject
- && (typeof payload.params[0].gasPrice === 'undefined'
- && (typeof payload.params[0].maxPriorityFeePerGas === 'undefined'
- || typeof payload.params[0].maxFeePerGas === 'undefined'))) {
- _handleTxPricing(method, payload.params[0]).then(txPricing => {
- if (txPricing.gasPrice !== undefined) {
- payload.params[0].gasPrice = txPricing.gasPrice;
- }
- else if (txPricing.maxPriorityFeePerGas !== undefined
- && txPricing.maxFeePerGas !== undefined) {
- payload.params[0].maxPriorityFeePerGas = txPricing.maxPriorityFeePerGas;
- payload.params[0].maxFeePerGas = txPricing.maxFeePerGas;
- }
- 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;
- };
- function _handleTxPricing(method, tx) {
- return new Promise((resolve, reject) => {
- try {
- var getBlockByNumber = (new Method({
- name: 'getBlockByNumber',
- call: 'eth_getBlockByNumber',
- params: 2,
- inputFormatter: [function (blockNumber) {
- return blockNumber ? utils.toHex(blockNumber) : 'latest';
- }, function () {
- return false;
- }]
- })).createFunction(method.requestManager);
- var getGasPrice = (new Method({
- name: 'getGasPrice',
- call: 'eth_gasPrice',
- params: 0
- })).createFunction(method.requestManager);
- Promise.all([
- getBlockByNumber(),
- getGasPrice()
- ]).then(responses => {
- const [block, gasPrice] = responses;
- if ((tx.type === '0x2' || tx.type === undefined) &&
- (block && block.baseFeePerGas)) {
- // The network supports EIP-1559
- // Taken from https://github.com/ethers-io/ethers.js/blob/ba6854bdd5a912fe873d5da494cb5c62c190adde/packages/abstract-provider/src.ts/index.ts#L230
- let maxPriorityFeePerGas, maxFeePerGas;
- if (tx.gasPrice) {
- // Using legacy gasPrice property on an eip-1559 network,
- // so use gasPrice as both fee properties
- maxPriorityFeePerGas = tx.gasPrice;
- maxFeePerGas = tx.gasPrice;
- delete tx.gasPrice;
- }
- else {
- maxPriorityFeePerGas = tx.maxPriorityFeePerGas || '0x9502F900'; // 2.5 Gwei
- maxFeePerGas = tx.maxFeePerGas ||
- utils.toHex(utils.toBN(block.baseFeePerGas)
- .mul(utils.toBN(2))
- .add(utils.toBN(maxPriorityFeePerGas)));
- }
- resolve({ maxFeePerGas, maxPriorityFeePerGas });
- }
- else {
- if (tx.maxPriorityFeePerGas || tx.maxFeePerGas)
- throw Error("Network doesn't support eip-1559");
- resolve({ gasPrice });
- }
- });
- }
- catch (error) {
- reject(error);
- }
- });
- }
- /**
- * 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 typeof data === 'string' && ((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;
|