@0x/web3-wrapper
Version:
Wraps around web3 and gives a nicer interface
812 lines • 37.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Web3Wrapper = void 0;
const assert_1 = require("@0x/assert");
const json_schemas_1 = require("@0x/json-schemas");
const utils_1 = require("@0x/utils");
const ethereum_types_1 = require("ethereum-types");
const _ = require("lodash");
const marshaller_1 = require("./marshaller");
const types_1 = require("./types");
const utils_2 = require("./utils");
const BASE_TEN = 10;
// These are unique identifiers contained in the response of the
// web3_clientVersion call.
const uniqueVersionIds = {
geth: 'Geth',
ganache: 'EthereumJS TestRPC',
};
/**
* An alternative to the Web3.js library that provides a consistent, clean, promise-based interface.
*/
class Web3Wrapper {
/**
* Instantiates a new Web3Wrapper.
* @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with
* the backing Ethereum node.
* @param callAndTxnDefaults Override Call and Txn Data defaults sent with RPC requests to the backing Ethereum node.
* @return An instance of the Web3Wrapper class.
*/
constructor(supportedProvider, callAndTxnDefaults = {}) {
/**
* Flag to check if this instance is of type Web3Wrapper
*/
this.isZeroExWeb3Wrapper = true;
this.abiDecoder = new utils_1.AbiDecoder([]);
this._supportedProvider = supportedProvider;
this._provider = utils_1.providerUtils.standardizeOrThrow(supportedProvider);
this._callAndTxnDefaults = callAndTxnDefaults;
this._jsonRpcRequestId = 1;
}
/**
* Check if an address is a valid Ethereum address
* @param address Address to check
* @returns Whether the address is a valid Ethereum address
*/
static isAddress(address) {
return utils_1.addressUtils.isAddress(address);
}
/**
* A unit amount is defined as the amount of a token above the specified decimal places (integer part).
* E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent
* to 1 unit.
* @param amount The amount in baseUnits that you would like converted to units.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in units.
*/
static toUnitAmount(amount, decimals) {
assert_1.assert.isValidBaseUnitAmount('amount', amount);
assert_1.assert.isNumber('decimals', decimals);
const aUnit = new utils_1.BigNumber(BASE_TEN).pow(decimals);
const unit = amount.div(aUnit);
return unit;
}
/**
* A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits
* is the amount expressed in the smallest denomination.
* E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000
* @param amount The amount of units that you would like converted to baseUnits.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in baseUnits.
*/
static toBaseUnitAmount(amount, decimals) {
assert_1.assert.isNumber('decimals', decimals);
const unit = new utils_1.BigNumber(BASE_TEN).pow(decimals);
const baseUnitAmount = unit.times(amount);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
if (hasDecimals) {
throw new Error(`Invalid unit amount: ${amount.toString(BASE_TEN)} - Too many decimal places`);
}
return baseUnitAmount;
}
/**
* Convert an Ether amount from ETH to Wei
* @param ethAmount Amount of Ether to convert to wei
* @returns Amount in wei
*/
static toWei(ethAmount) {
assert_1.assert.isBigNumber('ethAmount', ethAmount);
const ETH_DECIMALS = 18;
const balanceWei = Web3Wrapper.toBaseUnitAmount(ethAmount, ETH_DECIMALS);
return balanceWei;
}
static _assertBlockParam(blockParam) {
if (_.isNumber(blockParam)) {
return;
}
else if (_.isString(blockParam)) {
assert_1.assert.doesBelongToStringEnum('blockParam', blockParam, ethereum_types_1.BlockParamLiteral);
}
}
static _assertBlockParamOrString(blockParam) {
try {
Web3Wrapper._assertBlockParam(blockParam);
}
catch (err) {
try {
assert_1.assert.isHexString('blockParam', blockParam);
return;
}
catch (err) {
throw new Error(`Expected blockParam to be of type "string | BlockParam", encountered ${blockParam}`);
}
}
}
static _normalizeTxReceiptStatus(status) {
// Transaction status might have four values
// undefined - Testrpc and other old clients
// null - New clients on old transactions
// number - Parity
// hex - Geth
if (_.isString(status)) {
return utils_2.utils.convertHexToNumber(status);
}
else if (status === undefined) {
return null;
}
else {
return status;
}
}
/**
* Get the contract defaults set to the Web3Wrapper instance
* @return CallAndTxnData defaults (e.g gas, gasPrice, nonce, etc...)
*/
getContractDefaults() {
return this._callAndTxnDefaults;
}
/**
* Retrieve the Web3 provider
* @return Web3 provider instance
*/
getProvider() {
return this._supportedProvider;
}
/**
* Update the used Web3 provider
* @param provider The new Web3 provider to be set
*/
setProvider(supportedProvider) {
const provider = utils_1.providerUtils.standardizeOrThrow(supportedProvider);
this._provider = provider;
}
/**
* Check whether an address is available through the backing provider. This can be
* useful if you want to know whether a user can sign messages or transactions from
* a given Ethereum address.
* @param senderAddress Address to check availability for
* @returns Whether the address is available through the provider.
*/
isSenderAddressAvailableAsync(senderAddress) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('senderAddress', senderAddress);
const addresses = yield this.getAvailableAddressesAsync();
const normalizedAddress = senderAddress.toLowerCase();
return _.includes(addresses, normalizedAddress);
});
}
/**
* Fetch the backing Ethereum node's version string (e.g `MetaMask/v4.2.0`)
* @returns Ethereum node's version string
*/
getNodeVersionAsync() {
return __awaiter(this, void 0, void 0, function* () {
const nodeVersion = yield this.sendRawPayloadAsync({ method: 'web3_clientVersion' });
return nodeVersion;
});
}
/**
* Fetches the networkId of the backing Ethereum node
* @returns The network id
*/
getNetworkIdAsync() {
return __awaiter(this, void 0, void 0, function* () {
const networkIdStr = yield this.sendRawPayloadAsync({ method: 'net_version' });
const networkId = _.parseInt(networkIdStr);
return networkId;
});
}
/**
* Fetches the chainId of the backing Ethereum node
* @returns The chain id
*/
getChainIdAsync() {
return __awaiter(this, void 0, void 0, function* () {
const chainIdStr = yield this.sendRawPayloadAsync({ method: 'eth_chainId' });
const chainId = _.parseInt(chainIdStr);
return chainId;
});
}
/**
* Fetch the current gas price.
* For post-London hardfork chains, this will be baseFeePerGas + maxPriorityFeePerGas
*/
getGasPriceAsync() {
return __awaiter(this, void 0, void 0, function* () {
const gasPriceStr = yield this.sendRawPayloadAsync({ method: 'eth_gasPrice' });
return new utils_1.BigNumber(gasPriceStr);
});
}
/**
* Fetch the base fee per gas for the pending block.
*/
getBaseFeePerGasAsync() {
return __awaiter(this, void 0, void 0, function* () {
const rawBlock = yield this.sendRawPayloadAsync({
method: 'eth_getBlockByNumber',
params: ['pending', false],
});
const { baseFeePerGas } = rawBlock;
return new utils_1.BigNumber(baseFeePerGas || 0);
});
}
/**
* Fetch the current max piority fee per gas. This is the suggested miner tip
* to get mined in the current block.
*/
getMaxPriorityFeePerGasAsync() {
return __awaiter(this, void 0, void 0, function* () {
const feeStr = yield this.sendRawPayloadAsync({ method: 'eth_maxPriorityFeePerGas' });
return new utils_1.BigNumber(feeStr);
});
}
/**
* Retrieves the transaction receipt for a given transaction hash if found
* @param txHash Transaction hash
* @returns The transaction receipt, including it's status (0: failed, 1: succeeded). Returns undefined if transaction not found.
*/
getTransactionReceiptIfExistsAsync(txHash) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isHexString('txHash', txHash);
const transactionReceiptRpc = yield this.sendRawPayloadAsync({
method: 'eth_getTransactionReceipt',
params: [txHash],
});
// HACK Parity can return a pending transaction receipt. We check for a non null
// block number before continuing with returning a fully realised receipt.
// ref: https://github.com/paritytech/parity-ethereum/issues/1180
if (transactionReceiptRpc !== null && transactionReceiptRpc.blockNumber !== null) {
transactionReceiptRpc.status = Web3Wrapper._normalizeTxReceiptStatus(transactionReceiptRpc.status);
const transactionReceipt = marshaller_1.marshaller.unmarshalTransactionReceipt(transactionReceiptRpc);
return transactionReceipt;
}
else {
return undefined;
}
});
}
/**
* Retrieves the transaction data for a given transaction
* @param txHash Transaction hash
* @returns The raw transaction data
*/
getTransactionByHashAsync(txHash) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isHexString('txHash', txHash);
const transactionRpc = yield this.sendRawPayloadAsync({
method: 'eth_getTransactionByHash',
params: [txHash],
});
const transaction = marshaller_1.marshaller.unmarshalTransaction(transactionRpc);
return transaction;
});
}
/**
* Retrieves an accounts Ether balance in wei
* @param owner Account whose balance you wish to check
* @param defaultBlock The block depth at which to fetch the balance (default=latest)
* @returns Balance in wei
*/
getBalanceInWeiAsync(owner, defaultBlock) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('owner', owner);
if (defaultBlock !== undefined) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller_1.marshaller.marshalBlockParam(defaultBlock);
const encodedOwner = marshaller_1.marshaller.marshalAddress(owner);
const balanceInWei = yield this.sendRawPayloadAsync({
method: 'eth_getBalance',
params: [encodedOwner, marshalledDefaultBlock],
});
// Rewrap in a new BigNumber
return new utils_1.BigNumber(balanceInWei);
});
}
/**
* Check if a contract exists at a given address
* @param address Address to which to check
* @returns Whether or not contract code was found at the supplied address
*/
doesContractExistAtAddressAsync(address) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('address', address);
const code = yield this.getContractCodeAsync(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const isCodeEmpty = /^0x0{0,40}$/i.test(code);
return !isCodeEmpty;
});
}
/**
* Gets the contract code by address
* @param address Address of the contract
* @param defaultBlock Block height at which to make the call. Defaults to `latest`
* @return Code of the contract
*/
getContractCodeAsync(address, defaultBlock) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('address', address);
if (defaultBlock !== undefined) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller_1.marshaller.marshalBlockParam(defaultBlock);
const encodedAddress = marshaller_1.marshaller.marshalAddress(address);
const code = yield this.sendRawPayloadAsync({
method: 'eth_getCode',
params: [encodedAddress, marshalledDefaultBlock],
});
return code;
});
}
/**
* Gets the debug trace of a transaction
* @param txHash Hash of the transactuon to get a trace for
* @param traceParams Config object allowing you to specify if you need memory/storage/stack traces.
* @return Transaction trace
*/
getTransactionTraceAsync(txHash, traceParams) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isHexString('txHash', txHash);
const trace = yield this.sendRawPayloadAsync({
method: 'debug_traceTransaction',
params: [txHash, traceParams],
});
return trace;
});
}
/**
* Sign a message with a specific address's private key (`eth_sign`)
* @param address Address of signer
* @param message Message to sign
* @returns Signature string (might be VRS or RSV depending on the Signer)
*/
signMessageAsync(address, message) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('address', address);
assert_1.assert.isString('message', message); // TODO: Should this be stricter? Hex string?
const signData = yield this.sendRawPayloadAsync({
method: 'eth_sign',
params: [address, message],
});
return signData;
});
}
/**
* Sign an EIP712 typed data message with a specific address's private key (`eth_signTypedData`)
* @param address Address of signer
* @param typedData Typed data message to sign
* @returns Signature string (as RSV)
*/
signTypedDataAsync(address, typedData) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('address', address);
assert_1.assert.doesConformToSchema('typedData', typedData, json_schemas_1.schemas.eip712TypedDataSchema);
// Try decreasing versions of `eth_signTypedData` until it works.
const methodsToTry = ['eth_signTypedData_v4', 'eth_signTypedData_v3', 'eth_signTypedData'];
let lastErr;
for (const method of methodsToTry) {
try {
return yield this.sendRawPayloadAsync({
method,
params: [address, typedData],
});
}
catch (err) {
lastErr = err;
// If there are no more methods to try or the error says something other
// than the method not existing, throw.
if (!/(not handled|does not exist|not supported)/.test(err.message)) {
throw err;
}
}
}
throw lastErr;
});
}
/**
* Fetches the latest block number
* @returns Block number
*/
getBlockNumberAsync() {
return __awaiter(this, void 0, void 0, function* () {
const blockNumberHex = yield this.sendRawPayloadAsync({
method: 'eth_blockNumber',
params: [],
});
const blockNumber = utils_2.utils.convertHexToNumberOrNull(blockNumberHex);
return blockNumber;
});
}
/**
* Fetches the nonce for an account (transaction count for EOAs).
* @param address Address of account.
* @param defaultBlock Block height at which to make the call. Defaults to `latest`
* @returns Account nonce.
*/
getAccountNonceAsync(address, defaultBlock) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isETHAddressHex('address', address);
if (defaultBlock !== undefined) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller_1.marshaller.marshalBlockParam(defaultBlock);
const encodedAddress = marshaller_1.marshaller.marshalAddress(address);
const nonceHex = yield this.sendRawPayloadAsync({
method: 'eth_getTransactionCount',
params: [encodedAddress, marshalledDefaultBlock],
});
assert_1.assert.isHexString('nonce', nonceHex);
// tslint:disable-next-line:custom-no-magic-numbers
return parseInt(nonceHex.substr(2), 16);
});
}
/**
* Fetch a specific Ethereum block without transaction data
* @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral)
* @returns The requested block without transaction data, or undefined if block was not found
* (e.g the node isn't fully synced, there was a block re-org and the requested block was uncles, etc...)
*/
getBlockIfExistsAsync(blockParam) {
return __awaiter(this, void 0, void 0, function* () {
Web3Wrapper._assertBlockParamOrString(blockParam);
const encodedBlockParam = marshaller_1.marshaller.marshalBlockParam(blockParam);
const method = utils_2.utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = false;
const blockWithoutTransactionDataWithHexValuesOrNull = yield this.sendRawPayloadAsync({
method,
params: [encodedBlockParam, shouldIncludeTransactionData],
});
let blockWithoutTransactionDataIfExists;
if (blockWithoutTransactionDataWithHexValuesOrNull !== null) {
blockWithoutTransactionDataIfExists = marshaller_1.marshaller.unmarshalIntoBlockWithoutTransactionData(blockWithoutTransactionDataWithHexValuesOrNull);
}
return blockWithoutTransactionDataIfExists;
});
}
/**
* Fetch a specific Ethereum block with transaction data
* @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral)
* @returns The requested block with transaction data
*/
getBlockWithTransactionDataAsync(blockParam) {
return __awaiter(this, void 0, void 0, function* () {
Web3Wrapper._assertBlockParamOrString(blockParam);
let encodedBlockParam = blockParam;
if (_.isNumber(blockParam)) {
encodedBlockParam = utils_2.utils.numberToHex(blockParam);
}
const method = utils_2.utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = true;
const blockWithTransactionDataWithHexValues = yield this.sendRawPayloadAsync({
method,
params: [encodedBlockParam, shouldIncludeTransactionData],
});
const blockWithoutTransactionData = marshaller_1.marshaller.unmarshalIntoBlockWithTransactionData(blockWithTransactionDataWithHexValues);
return blockWithoutTransactionData;
});
}
/**
* Fetch a block's timestamp
* @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral)
* @returns The block's timestamp
*/
getBlockTimestampAsync(blockParam) {
return __awaiter(this, void 0, void 0, function* () {
Web3Wrapper._assertBlockParamOrString(blockParam);
const blockIfExists = yield this.getBlockIfExistsAsync(blockParam);
if (blockIfExists === undefined) {
throw new Error(`Failed to fetch block with blockParam: ${JSON.stringify(blockParam)}`);
}
return blockIfExists.timestamp;
});
}
/**
* Retrieve the user addresses available through the backing provider
* @returns Available user addresses
*/
getAvailableAddressesAsync() {
return __awaiter(this, void 0, void 0, function* () {
const addresses = yield this.sendRawPayloadAsync({
method: 'eth_accounts',
params: [],
});
const normalizedAddresses = _.map(addresses, address => address.toLowerCase());
return normalizedAddresses;
});
}
/**
* Take a snapshot of the blockchain state on a TestRPC/Ganache local node
* @returns The snapshot id. This can be used to revert to this snapshot
*/
takeSnapshotAsync() {
return __awaiter(this, void 0, void 0, function* () {
const snapshotId = Number(yield this.sendRawPayloadAsync({ method: 'evm_snapshot', params: [] }));
return snapshotId;
});
}
/**
* Revert the blockchain state to a previous snapshot state on TestRPC/Ganache local node
* @param snapshotId snapshot id to revert to
* @returns Whether the revert was successful
*/
revertSnapshotAsync(snapshotId) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isNumber('snapshotId', snapshotId);
const didRevert = yield this.sendRawPayloadAsync({ method: 'evm_revert', params: [snapshotId] });
return didRevert;
});
}
/**
* Mine a block on a TestRPC/Ganache local node
*/
mineBlockAsync() {
return __awaiter(this, void 0, void 0, function* () {
yield this.sendRawPayloadAsync({ method: 'evm_mine', params: [] });
});
}
/**
* Increase the next blocks timestamp on TestRPC/Ganache or Geth local node.
* Will throw if provider is neither TestRPC/Ganache or Geth.
* @param timeDelta Amount of time to add in seconds
*/
increaseTimeAsync(timeDelta) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isNumber('timeDelta', timeDelta);
// Detect Geth vs. Ganache and use appropriate endpoint.
const version = yield this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) {
return this.sendRawPayloadAsync({ method: 'debug_increaseTime', params: [timeDelta] });
}
else if (_.includes(version, uniqueVersionIds.ganache)) {
return this.sendRawPayloadAsync({ method: 'evm_increaseTime', params: [timeDelta] });
}
else {
throw new Error(`Unknown client version: ${version}`);
}
});
}
/**
* Retrieve smart contract logs for a given filter
* @param filter Parameters by which to filter which logs to retrieve
* @returns The corresponding log entries
*/
getLogsAsync(filter) {
return __awaiter(this, void 0, void 0, function* () {
if (filter.blockHash !== undefined && (filter.fromBlock !== undefined || filter.toBlock !== undefined)) {
throw new Error(`Cannot specify 'blockHash' as well as 'fromBlock'/'toBlock' in the filter supplied to 'getLogsAsync'`);
}
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = utils_2.utils.numberToHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = utils_2.utils.numberToHex(toBlock);
}
const serializedFilter = Object.assign(Object.assign({}, filter), { fromBlock,
toBlock });
const payload = {
method: 'eth_getLogs',
params: [serializedFilter],
};
const rawLogs = yield this.sendRawPayloadAsync(payload);
const formattedLogs = _.map(rawLogs, marshaller_1.marshaller.unmarshalLog.bind(marshaller_1.marshaller));
return formattedLogs;
});
}
/**
* Calculate the estimated gas cost for a given transaction
* @param txData Transaction data
* @returns Estimated gas cost
*/
estimateGasAsync(txData) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.doesConformToSchema('txData', txData, json_schemas_1.schemas.txDataSchema);
const txDataHex = marshaller_1.marshaller.marshalTxData(txData);
const gasHex = yield this.sendRawPayloadAsync({ method: 'eth_estimateGas', params: [txDataHex] });
const gas = utils_2.utils.convertHexToNumber(gasHex);
return gas;
});
}
/**
* Generate an access list for an ethereum call and also compute the gas used.
* @param callData Call data
* @param defaultBlock Block height at which to make the call. Defaults to 'latest'.
* @returns The access list and gas used.
*/
createAccessListAsync(callData, defaultBlock) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.doesConformToSchema('callData', callData, json_schemas_1.schemas.callDataSchema, [
json_schemas_1.schemas.addressSchema,
json_schemas_1.schemas.numberSchema,
json_schemas_1.schemas.jsNumber,
]);
const rawResult = yield this.sendRawPayloadAsync({
method: 'eth_createAccessList',
params: [marshaller_1.marshaller.marshalCallData(callData), marshaller_1.marshaller.marshalBlockParam(defaultBlock)],
});
if (rawResult.error) {
throw new Error(rawResult.error);
}
return {
accessList: rawResult.accessList.reduce((o, v) => {
o[v.address] = o[v.address] || [];
o[v.address].push(...(v.storageKeys || []));
return o;
},
// tslint:disable-next-line: no-object-literal-type-assertion
{}),
// tslint:disable-next-line: custom-no-magic-numbers
gasUsed: parseInt(rawResult.gasUsed.slice(2), 16),
};
});
}
/**
* Call a smart contract method at a given block height
* @param callData Call data
* @param defaultBlock Block height at which to make the call. Defaults to `latest`
* @returns The raw call result
*/
callAsync(callData, defaultBlock) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.doesConformToSchema('callData', callData, json_schemas_1.schemas.callDataSchema);
if (defaultBlock !== undefined) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller_1.marshaller.marshalBlockParam(defaultBlock);
const callDataHex = marshaller_1.marshaller.marshalCallData(callData);
const overrides = marshaller_1.marshaller.marshalCallOverrides(callData.overrides || {});
const rawCallResult = yield this.sendRawPayloadAsync({
method: 'eth_call',
params: [callDataHex, marshalledDefaultBlock, ...(Object.keys(overrides).length === 0 ? [] : [overrides])],
});
return rawCallResult;
});
}
/**
* Send a transaction
* @param txData Transaction data
* @returns Transaction hash
*/
sendTransactionAsync(txData) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.doesConformToSchema('txData', txData, json_schemas_1.schemas.txDataSchema);
const txDataHex = marshaller_1.marshaller.marshalTxData(txData);
const txHash = yield this.sendRawPayloadAsync({ method: 'eth_sendTransaction', params: [txDataHex] });
return txHash;
});
}
/**
* Waits for a transaction to be mined and returns the transaction receipt.
* Note that just because a transaction was mined does not mean it was
* successful. You need to check the status code of the transaction receipt
* to find out if it was successful, or use the helper method
* awaitTransactionSuccessAsync.
* @param txHash Transaction hash
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
* @return Transaction receipt with decoded log args.
*/
awaitTransactionMinedAsync(txHash, pollingIntervalMs = 1000, timeoutMs) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isHexString('txHash', txHash);
assert_1.assert.isNumber('pollingIntervalMs', pollingIntervalMs);
if (timeoutMs !== undefined) {
assert_1.assert.isNumber('timeoutMs', timeoutMs);
}
// Immediately check if the transaction has already been mined.
let transactionReceipt = yield this.getTransactionReceiptIfExistsAsync(txHash);
if (transactionReceipt !== undefined) {
const logsWithDecodedArgs = _.map(transactionReceipt.logs, this.abiDecoder.tryToDecodeLogOrNoop.bind(this.abiDecoder));
const transactionReceiptWithDecodedLogArgs = Object.assign(Object.assign({}, transactionReceipt), { logs: logsWithDecodedArgs });
return transactionReceiptWithDecodedLogArgs;
}
// Otherwise, check again every pollingIntervalMs.
let wasTimeoutExceeded = false;
if (timeoutMs) {
setTimeout(() => (wasTimeoutExceeded = true), timeoutMs);
}
const txReceiptPromise = new Promise((resolve, reject) => {
const intervalId = utils_1.intervalUtils.setAsyncExcludingInterval(() => __awaiter(this, void 0, void 0, function* () {
if (wasTimeoutExceeded) {
utils_1.intervalUtils.clearAsyncExcludingInterval(intervalId);
return reject(types_1.Web3WrapperErrors.TransactionMiningTimeout);
}
transactionReceipt = yield this.getTransactionReceiptIfExistsAsync(txHash);
if (transactionReceipt !== undefined) {
utils_1.intervalUtils.clearAsyncExcludingInterval(intervalId);
const logsWithDecodedArgs = _.map(transactionReceipt.logs, this.abiDecoder.tryToDecodeLogOrNoop.bind(this.abiDecoder));
const transactionReceiptWithDecodedLogArgs = Object.assign(Object.assign({}, transactionReceipt), { logs: logsWithDecodedArgs });
resolve(transactionReceiptWithDecodedLogArgs);
}
}), pollingIntervalMs, (err) => {
utils_1.intervalUtils.clearAsyncExcludingInterval(intervalId);
reject(err);
});
});
const txReceipt = yield txReceiptPromise;
return txReceipt;
});
}
/**
* Waits for a transaction to be mined and returns the transaction receipt.
* Unlike awaitTransactionMinedAsync, it will throw if the receipt has a
* status that is not equal to 1. A status of 0 or null indicates that the
* transaction was mined, but failed. See:
* https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgettransactionreceipt
* @param txHash Transaction hash
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
* @return Transaction receipt with decoded log args.
*/
awaitTransactionSuccessAsync(txHash, pollingIntervalMs = 1000, timeoutMs) {
return __awaiter(this, void 0, void 0, function* () {
const receipt = yield this.awaitTransactionMinedAsync(txHash, pollingIntervalMs, timeoutMs);
if (receipt.status !== 1) {
throw new Error(`Transaction failed: ${txHash}`);
}
return receipt;
});
}
/**
* Calls the 'debug_setHead' JSON RPC method, which sets the current head of
* the local chain by block number. Note, this is a destructive action and
* may severely damage your chain. Use with extreme caution. As of now, this
* is only supported by Geth. It sill throw if the 'debug_setHead' method is
* not supported.
* @param blockNumber The block number to reset to.
*/
setHeadAsync(blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.assert.isNumber('blockNumber', blockNumber);
yield this.sendRawPayloadAsync({ method: 'debug_setHead', params: [utils_2.utils.numberToHex(blockNumber)] });
});
}
/**
* Sends a raw Ethereum JSON RPC payload and returns the response's `result` key
* @param payload A partial JSON RPC payload. No need to include version, id, params (if none needed)
* @return The contents nested under the result key of the response body
*/
sendRawPayloadAsync(payload) {
return __awaiter(this, void 0, void 0, function* () {
if (!payload.method) {
throw new Error(`Must supply method in JSONRPCRequestPayload, tried: [${payload}]`);
}
// tslint:disable:no-object-literal-type-assertion
const payloadWithDefaults = Object.assign({ id: this._jsonRpcRequestId++, params: [], jsonrpc: '2.0' }, payload);
// tslint:enable:no-object-literal-type-assertion
const sendAsync = utils_1.promisify(this._provider.sendAsync.bind(this._provider));
const response = yield sendAsync(payloadWithDefaults); // will throw if it fails
if (!response) {
throw new Error(`No response`);
}
const errorMessage = response.error ? response.error.message || response.error : undefined;
if (errorMessage) {
throw new Error(errorMessage);
}
if (response.result === undefined) {
throw new Error(`JSON RPC response has no result`);
}
return response.result;
});
}
/**
* Returns either NodeType.Geth or NodeType.Ganache depending on the type of
* the backing Ethereum node. Throws for any other type of node.
*/
getNodeTypeAsync() {
return __awaiter(this, void 0, void 0, function* () {
const version = yield this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) {
return types_1.NodeType.Geth;
}
else if (_.includes(version, uniqueVersionIds.ganache)) {
return types_1.NodeType.Ganache;
}
else {
throw new Error(`Unknown client version: ${version}`);
}
});
}
} // tslint:disable-line:max-file-line-count
exports.Web3Wrapper = Web3Wrapper;
//# sourceMappingURL=web3_wrapper.js.map