From 0367e3eee7c3852bcf8a4557741575d783ce05e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Sant=C3=A1ngelo?= Date: Thu, 8 Feb 2018 14:12:59 +0100 Subject: [PATCH] feat: New txUtils object for handling transactions --- src/ethereum/eth.js | 6 +- src/ethereum/index.js | 2 +- src/ethereum/tx.js | 76 ------------------------- src/ethereum/txUtils.js | 120 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 78 deletions(-) delete mode 100644 src/ethereum/tx.js create mode 100644 src/ethereum/txUtils.js diff --git a/src/ethereum/eth.js b/src/ethereum/eth.js index 316512d..e2d8587 100644 --- a/src/ethereum/eth.js +++ b/src/ethereum/eth.js @@ -30,7 +30,7 @@ export const eth = { * @return {boolean} - True if the connection was successful */ async connect(options = {}) { - if (this.wallet && this.wallet.isConnected()) { + if (this.isConnected()) { this.disconnect() } @@ -63,6 +63,10 @@ export const eth = { return wallet }, + isConnected() { + return (this.wallet && this.wallet.isConnected()) || !!web3 + }, + disconnect() { if (this.wallet) { this.wallet.disconnect() diff --git a/src/ethereum/index.js b/src/ethereum/index.js index a839e8c..52188eb 100644 --- a/src/ethereum/index.js +++ b/src/ethereum/index.js @@ -1,4 +1,4 @@ export { eth } from './eth' export { Contract } from './Contract' export { SignedMessage } from './SignedMessage' -export { tx } from './tx' +export { txUtils } from './txUtils' diff --git a/src/ethereum/tx.js b/src/ethereum/tx.js deleted file mode 100644 index 1dff34f..0000000 --- a/src/ethereum/tx.js +++ /dev/null @@ -1,76 +0,0 @@ -import { Log } from '../log' -import { sleep } from '../utils' -import { eth } from './eth' - -const log = new Log('tx') - -/** - * Some utility functions to work with Ethereum transactions. - * @namespace - */ -export const tx = { - DUMMY_TX_ID: '0xdeadbeef', - - /** - * Wait until a transaction finishes by either being mined or failing - * @param {string} txId - Transaction id to watch - * @return {object} data - Current transaction data - * @return {object.tx} transaction - Transaction status - * @return {object.recepeit} transaction - Transaction recepeit - */ - async waitUntilComplete(txId) { - const retry = () => { - log.info(`Transaction ${txId} pending, retrying later`) - return sleep(1000 * 60).then(() => this.whenComplete(txId)) - } - - const { tx, recepeit } = await this.getFull(txId) - - if (this.isPending(tx) || !recepeit) return retry() - - log.info(`Transaction ${txId} completed`) - return { tx, recepeit } - }, - - /** - * Get the transaction status and recepeit - * @param {string} txId - Transaction id - * @return {object} data - Current transaction data - * @return {object.tx} transaction - Transaction status - * @return {object.recepeit} transaction - Transaction recepeit - */ - async getFull(txId) { - const [tx, recepeit] = await Promise.all([ - eth.fetchTxStatus(txId), - eth.fetchTxReceipt(txId) - ]) - - return { tx, recepeit } - }, - - /** - * Expects the result of getTransaction's geth command - * and returns true if the transaction is still pending - * @param {object} tx - The transaction object - * @return boolean - */ - isPending(tx) { - if (!tx) return true - return tx.blockNumber === null || tx.status === 'pending' // `status` is added by us - }, - - /** - * Returns true if a transaction contains an event - * @param {Array} logs - The result of decoding the logs of the getTransaction's geth command - * @param {Array|string} names - A string or array of strings with event names you want to search for - * @return boolean - */ - hasLogEvents(logs, names) { - if (!names || names.length === 0) return false - if (!Array.isArray(names)) names = [names] - - logs = logs.filter(log => log && log.name) - - return logs.every(log => names.includes(log.name)) - } -} diff --git a/src/ethereum/txUtils.js b/src/ethereum/txUtils.js new file mode 100644 index 0000000..6b5aa7b --- /dev/null +++ b/src/ethereum/txUtils.js @@ -0,0 +1,120 @@ +import { Log } from '../log' +import { sleep } from '../utils' +import { eth } from './eth' + +const log = new Log('txUtils') + +/** + * Some utility functions to work with Ethereum transactions. + * @namespace + */ +export const txUtils = { + DUMMY_TX_ID: '0xdeadbeef', + + TRANSACTION_FETCH_DELAY: 10 * 1000, + + TRANSACTION_STATUS: Object.freeze({ + pending: 'pending', + confirmed: 'confirmed', + failed: 'failed' + }), + + /** + * Waits until the transaction finishes. Returns if it was successfull. + * Throws if the transaction fails or if it lacks any of the supplied events + * @param {string} txId - Transaction id to watch + * @param {Array|string} events - Events to watch. See {@link txUtils#getLogEvents} + * @return {object} data - Current transaction data. See {@link txUtils#getTransaction} + */ + async getConfirmedTransaction(txId, events) { + const tx = await txUtils.waitForCompletion(txId) + + if (this.isFailure(tx)) { + throw new Error(`Transaction "${txId}" failed`) + } + + if (!this.hasLogEvents(tx, events)) { + throw new Error(`Missing events for transaction "${txId}": ${events}`) + } + + return tx + }, + + /** + * Wait until a transaction finishes by either being mined or failing + * @param {string} txId - Transaction id to watch + * @return {object} data - Current transaction data. See {@link txUtils#getTransaction} + */ + async waitForCompletion(txId) { + const tx = await this.getTransaction(txId) + + if (this.isPending(tx) || !tx.recepeit) { + log.debug(`"${txId}" pending, wait ${this.TRANSACTION_FETCH_DELAY}ms`) + await sleep(this.TRANSACTION_FETCH_DELAY) + return this.waitForCompletion(txId) + } + + log.debug(`"${txId}" completed`) + return tx + }, + + /** + * Get the transaction status and recepeit + * @param {string} txId - Transaction id + * @return {object} data - Current transaction data. See {@link https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgettransaction} + * @return {object.recepeit} transaction - Transaction recepeit + */ + async getTransaction(txId) { + const [tx, recepeit] = await Promise.all([ + eth.fetchTxStatus(txId), + eth.fetchTxReceipt(txId) + ]) + + return { ...tx, recepeit } + }, + + /** + * Expects the result of getTransaction's geth command and returns true if the transaction is still pending. + * It'll also check for a pending status prop against {@link txUtils#TRANSACTION_STATUS} + * @param {object} tx - The transaction object + * @return boolean + */ + isPending(tx) { + return ( + tx && + (tx.blockNumber === null || tx.status === this.TRANSACTION_STATUS.pending) + ) + }, + + /** + * Expects the result of getTransactionRecepeit's geth command and returns true if the transaction failed. + * It'll also check for a failed status prop against {@link txUtils#TRANSACTION_STATUS} + * @param {object} tx - The transaction object + * @return boolean + */ + isFailure(tx) { + return ( + tx && + (!tx.recepeit || + tx.recepeit.status === '0x0' || + tx.status === this.TRANSACTION_STATUS.failed) + ) + }, + + /** + * Returns true if a transaction contains an event + * @param {Array} tx - Transaction with a decoded recepeit + * @param {Array|string} eventNames - A string or array of strings with event names you want to search for + * @return boolean + */ + hasLogEvents(tx, eventNames) { + if (!eventNames || eventNames.length === 0) return true + if (!tx.recepit) return false + + if (!Array.isArray(eventNames)) eventNames = [eventNames] + + return tx.recepeit + .filter(log => log && log.name) + .every(log => eventNames.includes(log.name)) + } +}