diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1ec6823..113d2f7cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed. - Fix memory leak in RPC Cache - Override RPC commands eth_syncing and web3_clientVersion - Remove certain non-essential permissions from certain builds. diff --git a/app/scripts/background.js b/app/scripts/background.js index 6b7926526..3f15488ee 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -27,7 +27,6 @@ function triggerUi () { if (!popupIsOpen) notification.show() } // On first install, open a window to MetaMask website to how-it-works. - extension.runtime.onInstalled.addListener(function (details) { if ((details.reason === 'install') && (!METAMASK_DEBUG)) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index d4c0d863e..2b4515e79 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -95,7 +95,6 @@ module.exports = class KeyringController extends EventEmitter { isInitialized: (!!wallet || !!vault), isUnlocked: Boolean(this.password), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), - transactions: this.configManager.getTxList(), unconfMsgs: messageManager.unconfirmedMsgs(), messages: messageManager.getMsgList(), selectedAccount: address, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1fc97e81d..c0b9b753c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -65,6 +65,7 @@ module.exports = class MetamaskController extends EventEmitter { this.ethStore.on('update', this.sendUpdate.bind(this)) this.keyringController.on('update', this.sendUpdate.bind(this)) + this.txManager.on('update', this.sendUpdate.bind(this)) } getState () { diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 6becfa6d1..f5b57f3c2 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -83,6 +83,7 @@ module.exports = class TransactionManager extends EventEmitter { var index = txList.findIndex(txData => txData.id === txId) txList[index] = txMeta this._saveTxList(txList) + this.emit('update') } get unapprovedTxCount () { @@ -113,10 +114,26 @@ module.exports = class TransactionManager extends EventEmitter { txDidComplete (txMeta, onTxDoneCb, cb, err) { if (err) return cb(err) + var {maxCost, txFee} = this.getMaxTxCostAndFee(txMeta) + txMeta.maxCost = maxCost + txMeta.txFee = txFee this.addTx(txMeta, onTxDoneCb) cb(null, txMeta) } + getMaxTxCostAndFee (txMeta) { + var txParams = txMeta.txParams + + var gasMultiplier = txMeta.gasMultiplier + var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) + var txFee = gasCost.mul(gasPrice) + var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + var maxCost = txValue.add(txFee) + return {maxCost, txFee} + } + getUnapprovedTxList () { var txList = this.getTxList() return txList.filter((txMeta) => txMeta.status === 'unapproved') @@ -167,7 +184,6 @@ module.exports = class TransactionManager extends EventEmitter { this.updateTx(metaTx) var rawTx = ethUtil.bufferToHex(tx.serialize()) return Promise.resolve(rawTx) - } /* @@ -240,19 +256,31 @@ module.exports = class TransactionManager extends EventEmitter { // checks if a signed tx is in a block and // if included sets the tx status as 'confirmed' checkForTxInBlock () { - var signedTxList = this.getFilteredTxList({status: 'signed', err: undefined}) + var signedTxList = this.getFilteredTxList({status: 'signed'}) if (!signedTxList.length) return - signedTxList.forEach((tx) => { - var txHash = tx.hash - var txId = tx.id - if (!txHash) return - this.txProviderUtils.query.getTransactionByHash(txHash, (err, txMeta) => { - if (err || !txMeta) { - tx.err = err || 'Tx could possibly have not been submitted' - this.updateTx(tx) - return txMeta ? console.error(err) : console.debug(`txMeta is ${txMeta} for:`, tx) + signedTxList.forEach((txMeta) => { + var txHash = txMeta.hash + var txId = txMeta.id + if (!txHash) { + txMeta.err = { + errCode: 'No hash was provided', + message: 'We had an error while submitting this transaction, please try again.', } - if (txMeta.blockNumber) { + this.updateTx(txMeta) + return this._setTxStatus(txId, 'failed') + } + this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => { + if (err || !txParams) { + if (!txParams) return + txMeta.err = { + isWarning: true, + errorCode: err, + message: 'There was a problem loading this transaction.', + } + this.updateTx(txMeta) + return console.error(err) + } + if (txParams.blockNumber) { this.setTxStatusConfirmed(txId) } }) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index c41ba61fd..7a0c599ba 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -26,11 +26,10 @@ function mapStateToProps (state) { accounts: state.metamask.accounts, address: state.metamask.selectedAccount, accountDetail: state.appState.accountDetail, - transactions: state.metamask.transactions, network: state.metamask.network, - unconfTxs: valuesFor(state.metamask.unconfTxs), unconfMsgs: valuesFor(state.metamask.unconfMsgs), shapeShiftTxList: state.metamask.shapeShiftTxList, + transactions: state.metamask.selectedAccountTxList || [], } } @@ -248,20 +247,10 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.transactionList = function () { - const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props - - var txsToRender = transactions.concat(unconfTxs) - // only transactions that are from the current address - .filter(tx => tx.txParams.from === address) - // only transactions that are on the current network - .filter(tx => tx.txParams.metamaskNetworkId === network) - // sort by recency - .sort((a, b) => b.time - a.time) - + const {transactions, unconfMsgs, address, network, shapeShiftTxList } = this.props return h(TransactionList, { - txsToRender, + transactions: transactions.sort((a, b) => b.time - a.time), network, - unconfTxs, unconfMsgs, address, shapeShiftTxList, diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 89472b221..286931f6f 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -7,8 +7,6 @@ const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN module.exports = PendingTxDetails @@ -29,15 +27,9 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' - var gasMultiplier = txData.gasMultiplier - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) + var txFee = txData.txFee || '' + var maxCost = txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons return ( diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 8b118b1d4..eca0d693a 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -13,13 +13,40 @@ function TransactionIcon () { TransactionIcon.prototype.render = function () { const { transaction, txParams, isMsg } = this.props + switch (transaction.status) { + case 'unapproved': + return h('.unapproved-tx', { + style: { + width: '24px', + height: '24px', + background: '#4dffff', + border: 'solid', + borderColor: '#AEAEAE', + borderWidth: '0.5px', + borderRadius: '13px', + }, + }) - if (transaction.status === 'rejected') { - return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { - style: { - width: '24px', - }, - }) + case 'rejected': + return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { + style: { + width: '24px', + }, + }) + + case 'failed': + return h('i.fa.fa-exclamation-triangle.fa-lg.error', { + style: { + width: '24px', + }, + }) + + case 'signed': + return h('i.fa.fa-ellipsis-h', { + style: { + fontSize: '27px', + }, + }) } if (isMsg) { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index bb685abda..bcd50c333 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -8,6 +8,7 @@ const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) const extension = require('../../../app/scripts/lib/extension') +const Tooltip = require('./tooltip') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -59,11 +60,7 @@ TransactionListItem.prototype.render = function () { }, [ h('.identicon-wrapper.flex-column.flex-center.select-none', [ - transaction.status === 'unapproved' ? h('i.fa.fa-ellipsis-h', { - style: { - fontSize: '27px', - }, - }) : h('.pop-hover', { + h('.pop-hover', { onClick: (event) => { event.stopPropagation() if (!isTx || isPending) return @@ -139,7 +136,14 @@ function failIfFailed (transaction) { if (transaction.status === 'rejected') { return h('span.error', ' (Rejected)') } - if (transaction.status === 'failed') { - return h('span.error', ' (Failed)') + if (transaction.err) { + + return h(Tooltip, { + title: transaction.err.message, + position: 'bottom', + }, [ + h('span.error', ' (Failed)'), + ]) } + } diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 7e1bedb05..b055ca9d5 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -13,12 +13,13 @@ function TransactionList () { } TransactionList.prototype.render = function () { - const { txsToRender, network, unconfMsgs } = this.props + const { transactions, network, unconfMsgs } = this.props + var shapeShiftTxList if (network === '1') { shapeShiftTxList = this.props.shapeShiftTxList } - const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) + const txsToRender = !shapeShiftTxList ? transactions.concat(unconfMsgs) : transactions.concat(unconfMsgs, shapeShiftTxList) .sort((a, b) => b.time - a.time) return ( @@ -55,8 +56,8 @@ TransactionList.prototype.render = function () { }, }, [ - transactions.length - ? transactions.map((transaction, i) => { + txsToRender.length + ? txsToRender.map((transaction, i) => { let key switch (transaction.key) { case 'shapeshift': diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 5a645022a..8d8285f3d 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -43,10 +43,9 @@ ConfirmTxScreen.prototype.render = function () { var unconfMsgs = state.unconfMsgs var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var index = state.index !== undefined ? state.index : 0 - var txData = unconfTxList[index] || unconfTxList[0] || {} + var txData = unconfTxList[index] || {txParams: {}} var txParams = txData.txParams || {} var isNotification = isPopupOrNotification() === 'notification' - return ( h('.flex-column.flex-grow', [ @@ -125,17 +124,10 @@ function currentTxView (opts) { } ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { var state = this.props - - var txParams = txData.txParams || {} - var address = txParams.from || state.selectedAccount + var address = txData.txParams.from || state.selectedAccount var account = state.accounts[address] var balance = account ? account.balance : '0x0' - - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) + var maxCost = new BN(txData.maxCost) var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) return maxCost.gt(balanceBn)