UNPKG

caver-js

Version:

caver-js is a JavaScript API library that allows developers to interact with a Klaytn node

865 lines (755 loc) 33.3 kB
/* Modifications copyright 2018 The caver-js Authors 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/>. This file is derived from web3.js/packages/web3-core-method/src/index.js (2019/06/12). Modified and improved for the caver-js development. */ /** * @file index.js * @author Fabian Vogelsteller <fabian@ethereum.org> * @author Marek Kotewicz <marek@parity.io> * @date 2017 */ const _ = require('lodash') const errors = require('../../caver-core-helpers').errors const formatters = require('../../caver-core-helpers').formatters const utils = require('../../caver-utils') const Subscriptions = require('../../caver-core-subscriptions').subscriptions const validateParams = require('../../caver-core-helpers').validateFunction.validateParams const TIMEOUTBLOCK = 50 const AVERAGE_BLOCK_TIME = 1 // 1s const POLLINGTIMEOUT = AVERAGE_BLOCK_TIME * TIMEOUTBLOCK // ~average block time (seconds) * TIMEOUTBLOCK const TransactionDecoder = require('../../caver-transaction/src/transactionDecoder/transactionDecoder') const { TX_TYPE_STRING } = require('../../caver-transaction/src/transactionHelper/transactionHelper') const { resolveRawKeyToAccountKey } = require('../../caver-klay/caver-klay-accounts/src/transactionType/account') function Method(options) { // call, name should be existed to create a method. if (!options.call || !options.name) throw errors.needNameCallPropertyToCreateMethod this.name = options.name this.call = options.call this.hexCall = options.hexCall this.params = options.params || 0 this.inputFormatter = options.inputFormatter || [] this.outputFormatter = options.outputFormatter this.transformPayload = options.transformPayload this.extraFormatters = options.extraFormatters this.requestManager = options.requestManager // reference to klay.accounts this.accounts = options.accounts this.defaultBlock = options.defaultBlock || 'latest' this.defaultAccount = options.defaultAccount || null this.outputFormatterDisable = options.outputFormatterDisable } Method.prototype.setRequestManager = setRequestManager Method.prototype.createFunction = createFunction Method.prototype.attachToObject = attachToObject Method.prototype.getCall = getCall Method.prototype.extractCallback = extractCallback Method.prototype.validateArgs = validateArgs Method.prototype.formatInput = formatInput Method.prototype.formatOutput = formatOutput Method.prototype.toPayload = toPayload Method.prototype.buildCall = buildCall Method.prototype._confirmTransaction = _confirmTransaction Method.prototype.request = request /** * Set requestManager for rpc calling. * If it has accounts parameter also, set it. * @method setRequestManager * @param {Object} requestManager * @param {Object} accounts */ function setRequestManager(requestManager, accounts) { this.requestManager = requestManager // reference to klay.accounts if (accounts) this.accounts = accounts } /** * createFunction through 'this' context (= instance by created through new Method(...)) * @method createFunction * @param {Object} requestManager * @param {Object} accounts * @return {Function} it will be used for sending RPC call. */ function createFunction(requestManager, accounts) { // set requestManager for method individulally. this.setRequestManager(requestManager || this.requestManager, accounts || this.accounts) // this.buildCall() returns function `send = function() { ... }` const func = this.buildCall() // call is directly used for rpc calling, // ex) 'klay_sendTransaction' func.call = this.call return func } /** * attach buildCalled method to 'obj' object, * by adding a property name through this.name * @method attachToObject * @param {Object} obj */ function attachToObject(obj) { const func = this.buildCall() func.call = this.call const [callName, optionalName] = this.name.split('.') if (optionalName) { obj[callName] = obj[callName] || {} obj[callName][optionalName] = func obj[callName][optionalName].getMethod = () => this } else { obj[callName] = func obj[callName].getMethod = () => this } } /** * Should be used to determine name of the jsonrpc method based on arguments * * @method getCall * @param {Array} arguments * @return {String} name of jsonrpc method */ function getCall(args) { // If hexCall is defined, args[0] type is truly hexParameter, return this.hexCall // If not, return this.call // 'this.call', 'this.hexCall' are defined in rpc.json return this.hexCall && utils.isHexParameter(args[0]) ? this.hexCall : this.call } /** * Should be used to extract callback from array of arguments. * (caution) It modifies input param. * * @method extractCallback * @param {Array} arguments * @return {Function|Null} callback, if exists */ function extractCallback(args) { if (_.isFunction(args[args.length - 1])) { return args.pop() // 'pop' method modifies the original 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 */ function validateArgs(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} */ function formatInput(args) { const _this = this // If inputFormatter is not defined, or empty just return original args. if (!this.inputFormatter || _.isEmpty(this.inputFormatter)) { return args } // If inputFormatter is defined, map original args by calling formatter. return this.inputFormatter.map((formatter, index) => { // bind this for defaultBlock, and defaultAccount let formattedInput = args[index] if (formatter) { formattedInput = formatter.call(_this, args[index]) } return formattedInput }) } /** * Should be called to format output(result) of method * * @method formatOutput * @param {Object} * @return {Object} */ function formatOutput(result) { const _this = this // If outputFormatter is defined, calling outputFormatter, // If not, just return original res. const _formatOutput = res => (typeof _this.outputFormatter === 'function' ? _this.outputFormatter(res) : res) // If result is array, map it through calling _formatOuput // If result is single, just calling _formatOutput. return _.isArray(result) ? result.map(_formatOutput) : _formatOutput(result) } /** * Should create payload from given input args * * @method toPayload * @param {Array} args * @return {Object} */ function toPayload(args) { const call = this.getCall(args) const callback = this.extractCallback(args) const inputParams = this.formatInput(args) this.validateArgs(inputParams) const payload = { method: call, params: inputParams, callback, } // If payload transform option is existing, apply it. // If not, just return payload. return (this.transformPayload && this.transformPayload(payload)) || payload } const buildSendTxCallbackFunc = (defer, method, payload, isSendTx) => (err, result) => { try { result = method.formatOutput(result) } catch (e) { if (!err) err = e } err = (result instanceof Error && result) || err // If err exists, fireError if (err) { return utils._fireError( err.error || err, // sometimes, err.error property exists, in case, fire it instead 'err' defer.eventEmitter, defer.reject, payload.callback ) } // fire callback if (payload.callback) payload.callback(null, result) // return PROMISE if (!isSendTx) { defer.resolve(result) } else { defer.eventEmitter.emit('transactionHash', result) method._confirmTransaction(defer, result, payload) } } const buildSendSignedTxFunc = (method, payload, sendTxCallback) => signed => { const rawTransaction = signed.rawTransaction ? signed.rawTransaction : signed const signedPayload = _.extend({}, payload, { method: 'klay_sendRawTransaction', params: [rawTransaction], }) method.requestManager.send(signedPayload, sendTxCallback) } const buildSendRequestFunc = (defer, sendSignedTx, sendTxCallback) => (payload, method) => { const methodName = payload.method // Logic for handling multiple cases of parameters in sendSignedTransaction. // 1. Object containing rawTransaction // : call 'klay_sendRawTransaction' with RLP encoded transaction(rawTransaction) in object // 2. A transaction object containing signatures or feePayerSignatures // : call 'getRawTransactionWithSignatures', then call 'klay_sendRawTransaction' with result of getRawTransactionWithSignatures if (method && methodName === 'klay_sendRawTransaction') { // The existence of accounts in the method means the implementation before the common architecture. if (method.accounts) { const transaction = payload.params[0] if (typeof transaction !== 'string' && _.isObject(transaction)) { if (transaction.rawTransaction) { return sendSignedTx(transaction) } return method.accounts .getRawTransactionWithSignatures(transaction) .then(sendSignedTx) .catch(e => { sendTxCallback(e) }) } } else { const transaction = payload.params[0] if (!_.isString(transaction) && _.isObject(transaction) && _.isFunction(transaction.getRLPEncoding)) { return sendSignedTx(transaction.getRLPEncoding()) } } } // In the previous implementation of common architecture, // if there was an account in the in-memory wallet before requesting to send or sign a transaction to the node, // it was handled by using it. if (method && method.accounts && method.accounts.wallet && method.accounts.wallet.length) { switch (methodName) { case 'klay_sendTransaction': { const tx = payload.params[0] let error if (!_.isObject(tx)) { sendTxCallback(new Error('The transaction must be defined as an object.')) return } let addressToUse = tx.from if (tx.senderRawTransaction && tx.feePayer) { addressToUse = tx.feePayer if (tx.from) { console.log('"from" is ignored for a fee-delegated transaction.') delete tx.from } } let wallet try { wallet = method.accounts.wallet.getAccount(addressToUse) } catch (e) { sendTxCallback(e) return } if (wallet && wallet.privateKey) { const privateKey = method.accounts._getRoleKey(tx, wallet) // If wallet was found, sign tx, and send using sendRawTransaction return method.accounts .signTransaction(tx, privateKey) .then(sendSignedTx) .catch(e => { sendTxCallback(e) }) } if (tx.signatures) { // If signatures is defined inside of the transaction object, // get rawTransaction string from signed transaction object and send to network return method.accounts .getRawTransactionWithSignatures(tx) .then(sendSignedTx) .catch(e => { sendTxCallback(e) }) } // If wallet was not found in caver-js wallet, then it has to use wallet in Node. // Signing to transaction using wallet in Node supports only LEGACY transaction, so if transaction is not LEGACY, return error. if (tx.feePayer !== undefined || (tx.type !== undefined && tx.type !== 'LEGACY')) { error = new Error( `No private key found in the caver-js wallet. Trying to use the Klaytn node's wallet, but it only supports legacy transactions. Please add private key of ${addressToUse} to the caver-js wallet.` ) sendTxCallback(error) return } error = validateParams(tx) if (error) { sendTxCallback(error) return } break } case 'klay_sign': { const data = payload.params[1] const wallet = method.accounts.wallet.getAccount(payload.params[0]) if (wallet && wallet.privateKey) { // If wallet was found, sign tx, and send using sendRawTransaction const sign = method.accounts.sign(data, wallet.privateKey) if (payload.callback) payload.callback(null, sign.signature) defer.resolve(sign.signature) return } break } } } // When sending a request to send or sign a transaction using a key stored in a Klaytn node, // the variable names inside the transaction must be properly formatted. // { _from: '0x..', _signatures: ['0x..', '0x..', '0x..'] } -> { from: '0x..', signatures: { V: '0x..', R: '0x..', S: '0x..'} } if (methodName.includes('sendTransaction') || methodName.includes('signTransaction')) { const tx = {} Object.keys(payload.params[0]).map(k => { let key = k if (key.startsWith('_')) key = key.slice(1) if (key === 'signatures' || key === 'feePayerSignatures') { if (!utils.isEmptySig(payload.params[0][key])) { tx[key] = utils.transformSignaturesToObject(payload.params[0][key]) if (key === 'signatures' && (methodName === 'klay_signTransaction' || methodName === 'klay_sendTransaction')) { console.warn(`When sign/send a transaction using the Node API, existing 'signatures' can be initialized.`) } if ( key === 'feePayerSignatures' && (methodName === 'klay_signTransactionAsFeePayer' || methodName === 'klay_sendTransactionAsFeePayer') ) { console.warn(`When sign/send a transaction using the Node API, existing 'feePayerSignatures' can be initialized.`) } } } else if (key === 'codeFormat') { tx[key] = utils.hexToNumber(payload.params[0][key]) } else if (key === 'key' && _.isObject(payload.params[0][key])) { // If key field is `AccountForUpdate`, resolve this to raw encoded account key string. tx.key = resolveRawKeyToAccountKey(payload.params[0]) } else if (key === 'account') { tx.key = payload.params[0][key].getRLPEncodingAccountKey() } else if (key === 'chainId') { if (payload.params[0].type !== undefined && payload.params[0].type.includes('Ethereum')) { tx[key] = payload.params[0][key] } } else if (key === 'accessList') { tx[key] = payload.params[0][key].toObject() } else if (payload.params[0][key] !== '0x') { tx[key] = payload.params[0][key] } }) payload.params[0] = tx } return method.requestManager.send(payload, sendTxCallback) } const buildSendFunc = (method, isSendTx) => (...args) => { const defer = utils.promiEvent(!isSendTx) const payload = method.toPayload(args) const sendTxCallback = buildSendTxCallbackFunc(defer, method, payload, isSendTx) const sendSignedTx = buildSendSignedTxFunc(method, payload, sendTxCallback) const sendRequest = buildSendRequestFunc(defer, sendSignedTx, sendTxCallback) // isSendTx can determine only for "send"Transaction request. // For sign transaction request, we also need to fill up the optional values. const isSignTx = method.name.includes('signTransaction') let isGasPriceInputMissing = false if ((isSendTx || isSignTx) && _.isObject(payload.params[0]) && payload.params[0].gasPrice === undefined) { isGasPriceInputMissing = true } // The TxTypeEthereumDynamicFee transaction does not use the gasPrice field, // so we need to check `maxPriorityFeePerGas` and `maxFeePerGas` field instead of `gasPrice`. const isDynamicFeeTx = (isSendTx || isSignTx) && payload.params[0].type === TX_TYPE_STRING.TxTypeEthereumDynamicFee const filledDynamicGasFeeTx = isDynamicFeeTx && payload.params[0].maxPriorityFeePerGas !== undefined && payload.params[0].maxFeePerGas !== undefined // gasPrice is already set so it is ok to send transaction. if (!isGasPriceInputMissing || filledDynamicGasFeeTx) { sendRequest(payload, method) return defer.eventEmitter } // gasPrice is missing, have to fill gasPrice field before sending tx const getGasPrice = new Method({ name: 'getGasPrice', call: 'klay_gasPrice', params: 0, }).createFunction(method.requestManager) const getMaxPriorityFeePerGas = new Method({ name: 'getMaxPriorityFeePerGas', call: 'klay_maxPriorityFeePerGas', params: 0, }).createFunction(method.requestManager) // `klay_gasPrice` returns a suggestion of gas price. // So using this value in gasPrice field (or maxFeePerGas). getGasPrice((err, gp) => { // The TxTypeEthereumDynamicFee transaction does not use the gasPrice field, // so the gas price default is not set for TxTypeEthereumDynamicFee. if (!isDynamicFeeTx) { payload.params[0].gasPrice = payload.params[0].gasPrice || gp } else { payload.params[0].maxFeePerGas = payload.params[0].maxFeePerGas || gp // If maxPriorityFeePerGas is undefined, call `klay_maxPriorityFeePerGas`. if (payload.params[0].maxPriorityFeePerGas === undefined) { return getMaxPriorityFeePerGas((e, maxPriorityFeePerGas) => { payload.params[0].maxPriorityFeePerGas = maxPriorityFeePerGas // Format gas price parameters(gasPrice, maxPriorityFeePerGas, maxFeePerGas) formatGasParametersToHex(payload.params[0]) sendRequest(payload, method) }) } } // Format gas price parameters(gasPrice, maxPriorityFeePerGas, maxFeePerGas) formatGasParametersToHex(payload.params[0]) sendRequest(payload, method) }) /** * attaching `.on('receipt')` is possible by returning defer.eventEmitter */ return defer.eventEmitter } // A function to change the format to a hex string after randomly filling the default gasPrice value // with the API (personal_sendValueTransfer, personal_sendAccountUpdate) that does not use a transaction object. function formatGasParametersToHex(txObject) { if (txObject.gasPrice !== undefined && !utils.isHexStrict(txObject.gasPrice)) { txObject.gasPrice = utils.toHex(txObject.gasPrice) } if (txObject.maxPriorityFeePerGas !== undefined && !utils.isHexStrict(txObject.maxPriorityFeePerGas)) { txObject.maxPriorityFeePerGas = utils.toHex(txObject.maxPriorityFeePerGas) } if (txObject.maxFeePerGas !== undefined && !utils.isHexStrict(txObject.maxFeePerGas)) { txObject.maxFeePerGas = utils.toHex(txObject.maxFeePerGas) } } function buildCall() { const method = this const isSendTx = method.call === 'klay_sendTransaction' || method.call === 'klay_sendTransactionAsFeePayer' || method.call === 'klay_sendRawTransaction' || method.call === 'personal_sendTransaction' || method.call === 'personal_sendValueTransfer' || method.call === 'personal_sendAccountUpdate' const send = buildSendFunc(method, isSendTx) // necessary to attach things to the method send.method = method // necessary for batch requests send.request = this.request.bind(this) return send } function _confirmTransaction(defer, result, payload) { let payloadTxObject = (payload.params && _.isObject(payload.params[0]) && payload.params[0]) || {} // If payload.params[0] is RLP-encoded string, decode RLP-encoded string to Transaction instance. if (_.isString(payload.params[0])) payloadTxObject = TransactionDecoder.decode(payload.params[0]) // mutableConfirmationPack will be used in // 1) checkConfirmation, // 2) startWatching functions // It is * mutable *, both functions can affect properties mutably. const mutableConfirmationPack = { method: this, promiseResolved: false, canUnsubscribe: true, timeoutCount: 0, intervalId: null, gasProvided: payloadTxObject.gas || null, isContractDeployment: utils.isContractDeployment(payloadTxObject), defer, result, _klaytnCall: {}, } addCustomSendMethod(mutableConfirmationPack) kickoffConfirmation(mutableConfirmationPack) } const addCustomSendMethod = mutableConfirmationPack => { const customSendMethods = [ new Method({ name: 'getTransactionReceipt', call: 'klay_getTransactionReceipt', params: 1, outputFormatter: !mutableConfirmationPack.method.outputFormatterDisable ? formatters.outputTransactionReceiptFormatter : undefined, }), new Method({ name: 'getCode', call: 'klay_getCode', params: 2, inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter], }), new Subscriptions({ name: 'subscribe', type: 'klay', subscriptions: { newBlockHeaders: { subscriptionName: 'newHeads', // replace subscription with this name params: 0, outputFormatter: formatters.outputBlockFormatter, }, }, }), ] // add custom send Methods _.each(customSendMethods, mthd => { // attach methods to _klaytnCall mthd.attachToObject(mutableConfirmationPack._klaytnCall) // assign rather than call setRequestManager() mthd.requestManager = mutableConfirmationPack.method.requestManager }) } const kickoffConfirmation = mutableConfirmationPack => { // eslint-disable-next-line no-unused-vars const { defer, promiseResolved, result, _klaytnCall } = mutableConfirmationPack // first check if we already have a confirmed transaction _klaytnCall .getTransactionReceipt(result) .then(receipt => { if (receipt && receipt.blockHash) { // `isPolling` is false in default. checkConfirmation(mutableConfirmationPack, receipt, false) } else if (!promiseResolved) startWatching(mutableConfirmationPack, receipt) }) .catch(() => { if (!promiseResolved) startWatching(mutableConfirmationPack) }) } // start watching for confirmation depending on the support features of the provider const startWatching = function(mutableConfirmationPack, existingReceipt) { // eslint-disable-next-line no-unused-vars const { _klaytnCall, intervalId, method } = mutableConfirmationPack // if provider allows PUB/SUB if (method.requestManager.provider.supportsSubscriptions()) { _klaytnCall.subscribe('newBlockHeaders', checkConfirmation.bind(null, mutableConfirmationPack, existingReceipt, false)) } else { mutableConfirmationPack.intervalId = setInterval(checkConfirmation.bind(null, mutableConfirmationPack, existingReceipt, true), 1000) } } // fire "receipt" and confirmation events and resolve after const checkConfirmation = function(mutableConfirmationPack, existingReceipt, isPolling, err, blockHeader, sub) { const { // L1 intervalId, // eslint-disable-line no-unused-vars defer, method, canUnsubscribe, // eslint-disable-line no-unused-vars _klaytnCall, // L2 isContractDeployment, promiseResolved, // eslint-disable-line no-unused-vars timeoutCount, // eslint-disable-line no-unused-vars result, } = mutableConfirmationPack if (err) { sub.unsubscribe() mutableConfirmationPack.promiseResolved = true utils._fireError( { message: 'Failed to subscribe to new newBlockHeaders to confirm the transaction receipts.', data: err, }, defer.eventEmitter, defer.reject ) return } // create fake unsubscribe sub = sub || { unsubscribe: () => clearInterval(mutableConfirmationPack.intervalId), } // if we have a valid receipt we don't need to send a request return ( ((existingReceipt && utils.promiEvent.resolve(existingReceipt)) || _klaytnCall.getTransactionReceipt(result)) // if CONFIRMATION listener exists check for confirmations, by setting canUnsubscribe = false .then(receipt => { checkIsReceiptInBlock(receipt) const formattedReceipt = formatReceipt(receipt, method) if (mutableConfirmationPack.promiseResolved) return return isContractDeployment ? checkForContractDeployment(mutableConfirmationPack, formattedReceipt, sub) : checkForNormalTx(mutableConfirmationPack, formattedReceipt, sub) }) .catch(countTimeout) ) } const checkIsReceiptInBlock = receipt => { if (receipt && !receipt.blockHash) throw errors.blockHashNull } const formatReceipt = (receipt, method) => { if (method.extraFormatters && method.extraFormatters.receiptFormatter) { receipt = method.extraFormatters.receiptFormatter(receipt) } return receipt } const countTimeout = (mutableConfirmationPack, isPolling, sub) => { // eslint-disable-next-line no-unused-vars const { defer, timeoutCount, promiseResolved } = mutableConfirmationPack // time out the transaction if not mined after 50 blocks mutableConfirmationPack.timeoutCount++ // check to see if we are http polling if (isPolling) { // polling timeout is different than TIMEOUTBLOCK blocks since we are triggering every second if (mutableConfirmationPack.timeoutCount - 1 >= POLLINGTIMEOUT) { sub.unsubscribe() mutableConfirmationPack.promiseResolved = true utils._fireError( new Error( `Transaction was not mined within${POLLINGTIMEOUT} seconds, please make sure your transaction was properly sent. Be aware that it might still be mined!` ), defer.eventEmitter, defer.reject ) } } else if (mutableConfirmationPack.timeoutCount - 1 >= TIMEOUTBLOCK) { sub.unsubscribe() mutableConfirmationPack.promiseResolved = true utils._fireError( new Error( 'Transaction was not mined within 50 blocks, please make sure your transaction was properly sent. Be aware that it might still be mined!' ), defer.eventEmitter, defer.reject ) } } const checkForContractDeployment = (mutableConfirmationPack, receipt, sub) => { // eslint-disable-next-line no-unused-vars const { defer, method, canUnsubscribe, _klaytnCall, promiseResolved } = mutableConfirmationPack // If contract address doesn't exist, fire error. if (!receipt.contractAddress) { if (canUnsubscribe) { sub.unsubscribe() mutableConfirmationPack.promiseResolved = true } utils._fireError(errors.receiptDidntContainContractAddress, defer.eventEmitter, defer.reject) return } if (!receipt.status && receipt.txError) { const receiptJSON = JSON.stringify(receipt, null, 2) utils._fireError(new Error(`${errors.txErrorTable[receipt.txError]}\n ${receiptJSON}`), defer.eventEmitter, defer.reject) } _klaytnCall.getCode(receipt.contractAddress, (e, code) => { if (!code) return defer.eventEmitter.emit('receipt', receipt) // if contract, return instance instead of receipt defer.resolve( (method.extraFormatters && method.extraFormatters.contractDeployFormatter && method.extraFormatters.contractDeployFormatter(receipt)) || receipt ) // need to remove listeners, as they aren't removed automatically when succesfull if (canUnsubscribe) defer.eventEmitter.removeAllListeners() if (canUnsubscribe) sub.unsubscribe() mutableConfirmationPack.promiseResolved = true }) return receipt } const checkForNormalTx = (mutableConfirmationPack, receipt, sub) => { // eslint-disable-next-line no-unused-vars const { defer, canUnsubscribe, promiseResolved, gasProvided } = mutableConfirmationPack if ( receipt && !receipt.outOfGas && (!gasProvided || utils.toBN(gasProvided).cmp(utils.toBN(receipt.gasUsed)) >= 0) && (receipt.status === true || receipt.status === '0x1' || typeof receipt.status === 'undefined') ) { // Happy case: transaction is processed well. A.K.A 'well-done receipt'. try { mutableConfirmationPack.defer.eventEmitter.emit('receipt', receipt) mutableConfirmationPack.defer.resolve(receipt) } catch (e) { console.log('receipt error', e) } // need to remove listeners, as they aren't removed automatically when succesfull if (canUnsubscribe) { mutableConfirmationPack.defer.eventEmitter.removeAllListeners() } } else { // Unhappy case: trasaction has error. A.K.A 'bad receipt'. if (!receipt) return const receiptJSON = JSON.stringify(receipt, null, 2) const { txError } = receipt if (txError && errors.txErrorTable[txError]) { utils._fireError( new Error(`${errors.txErrorTable[txError]}\n ${receiptJSON}`), mutableConfirmationPack.defer.eventEmitter, mutableConfirmationPack.defer.reject ) } else if (receipt.status === false || receipt.status === '0x0') { utils._fireError( errors.transactionReverted(receiptJSON), mutableConfirmationPack.defer.eventEmitter, mutableConfirmationPack.defer.reject ) } else if (receipt.gasUsed >= gasProvided) { utils._fireError( errors.transactionRanOutOfGas(receiptJSON), mutableConfirmationPack.defer.eventEmitter, mutableConfirmationPack.defer.reject ) } else { utils._fireError( errors.transactionRanOutOfGas(receiptJSON), mutableConfirmationPack.defer.eventEmitter, mutableConfirmationPack.defer.reject ) } } if (canUnsubscribe) sub.unsubscribe() mutableConfirmationPack.promiseResolved = true } /** * Should be called to create the pure JSONRPC request which can be used in a batch request * * @method request * @return {Object} jsonrpc request */ function request(...args) { const payload = this.toPayload(args) payload.format = this.formatOutput.bind(this) return payload } module.exports = Method