Beta 7 with some repo names changed.
This commit is contained in:
1 parent
0f7b40cca7
commit
963c19493a
3408 files changed
+462217
-15516
No files matched your search
+879
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
return method.accounts.signTransaction(txOptions, wallet.privateKey)
|
||||
.then(sendSignedTx)
|
||||
.catch(function (err) {
|
||||
if (_.isFunction(defer.eventEmitter.listeners) && defer.eventEmitter.listeners('error').length) {
|
||||
defer.eventEmitter.emit('error', err);
|
||||
defer.eventEmitter.removeAllListeners();
|
||||
defer.eventEmitter.catch(function () {
|
||||
});
|
||||
}
|
||||
defer.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
Reference in new issue
Block a user