aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/currency-input.js2
-rw-r--r--ui/app/components/customize-gas-modal/index.js31
-rw-r--r--ui/app/components/input-number.js1
-rw-r--r--ui/app/components/network-display.js51
-rw-r--r--ui/app/components/pending-tx/confirm-deploy-contract.js10
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js408
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js89
-rw-r--r--ui/app/components/send/gas-fee-display-v2.js11
-rw-r--r--ui/app/components/send/send-v2-container.js1
-rw-r--r--ui/app/components/sender-to-recipient.js29
-rw-r--r--ui/app/components/tx-list-item.js97
-rw-r--r--ui/app/components/tx-list.js15
12 files changed, 525 insertions, 220 deletions
diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js
index 940238fa5..ece3eb43d 100644
--- a/ui/app/components/currency-input.js
+++ b/ui/app/components/currency-input.js
@@ -91,6 +91,7 @@ CurrencyInput.prototype.render = function () {
placeholder,
readOnly,
inputRef,
+ type,
} = this.props
const { emptyState, focused } = this.state
@@ -99,6 +100,7 @@ CurrencyInput.prototype.render = function () {
const valueToRender = this.getValueToRender()
return h('input', {
className,
+ type,
value: emptyState ? '' : valueToRender,
placeholder: focused ? '' : placeholder,
size: valueToRender.length * inputSizeMultiplier,
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 920dfeab6..d8384c19d 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -22,12 +22,14 @@ const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
+ conversionMax,
subtractCurrencies,
} = require('../../conversion-util')
const {
getGasPrice,
getGasLimit,
+ getForceGasMin,
conversionRateSelector,
getSendAmount,
getSelectedToken,
@@ -45,6 +47,7 @@ function mapStateToProps (state) {
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
+ forceGasMin: getForceGasMin(state),
conversionRate,
amount: getSendAmount(state),
maxModeOn: getSendMaxModeState(state),
@@ -115,9 +118,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateSendAmount(maxAmount)
}
- updateGasPrice(gasPrice)
- updateGasLimit(gasLimit)
- updateGasTotal(gasTotal)
+ updateGasPrice(ethUtil.addHexPrefix(gasPrice))
+ updateGasLimit(ethUtil.addHexPrefix(gasLimit))
+ updateGasTotal(ethUtil.addHexPrefix(gasTotal))
hideModal()
}
@@ -218,7 +221,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
}
CustomizeGasModal.prototype.render = function () {
- const { hideModal } = this.props
+ const { hideModal, forceGasMin } = this.props
const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state
let convertedGasPrice = conversionUtil(gasPrice, {
@@ -230,6 +233,22 @@ CustomizeGasModal.prototype.render = function () {
convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}`
+ let newGasPrice = gasPrice
+ if (forceGasMin) {
+ const convertedMinPrice = conversionUtil(forceGasMin, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ })
+ convertedGasPrice = conversionMax(
+ { value: convertedMinPrice, fromNumericBase: 'dec' },
+ { value: convertedGasPrice, fromNumericBase: 'dec' }
+ )
+ newGasPrice = conversionMax(
+ { value: gasPrice, fromNumericBase: 'hex' },
+ { value: forceGasMin, fromNumericBase: 'hex' }
+ )
+ }
+
const convertedGasLimit = conversionUtil(gasLimit, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
@@ -252,7 +271,7 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, {
value: convertedGasPrice,
- min: MIN_GAS_PRICE_GWEI,
+ min: forceGasMin || MIN_GAS_PRICE_GWEI,
// max: 1000,
step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10),
onChange: value => this.convertAndSetGasPrice(value),
@@ -288,7 +307,7 @@ CustomizeGasModal.prototype.render = function () {
}, [t('cancel')]),
h(`div.send-v2__customize-gas__save${error ? '__error' : ''}.allcaps`, {
- onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal),
+ onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
}, [t('save')]),
]),
diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js
index fd8c5c309..5600e35ee 100644
--- a/ui/app/components/input-number.js
+++ b/ui/app/components/input-number.js
@@ -55,6 +55,7 @@ InputNumber.prototype.render = function () {
className: 'customize-gas-input',
value,
placeholder,
+ type: 'number',
onInputChange: newValue => {
this.setValue(newValue)
},
diff --git a/ui/app/components/network-display.js b/ui/app/components/network-display.js
new file mode 100644
index 000000000..9dc31b5c7
--- /dev/null
+++ b/ui/app/components/network-display.js
@@ -0,0 +1,51 @@
+const { Component } = require('react')
+const h = require('react-hyperscript')
+const PropTypes = require('prop-types')
+const { connect } = require('react-redux')
+const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon')
+const t = require('../../i18n')
+
+const networkToColorHash = {
+ 1: '#038789',
+ 3: '#e91550',
+ 42: '#690496',
+ 4: '#ebb33f',
+}
+
+class NetworkDisplay extends Component {
+ renderNetworkIcon () {
+ const { network } = this.props
+ const networkColor = networkToColorHash[network]
+
+ return networkColor
+ ? h(NetworkDropdownIcon, { backgroundColor: networkColor })
+ : h('i.fa.fa-question-circle.fa-med', {
+ style: {
+ margin: '0 4px',
+ color: 'rgb(125, 128, 130)',
+ },
+ })
+ }
+
+ render () {
+ const { provider: { type } } = this.props
+ return h('.network-display__container', [
+ this.renderNetworkIcon(),
+ h('.network-name', t(type)),
+ ])
+ }
+}
+
+NetworkDisplay.propTypes = {
+ network: PropTypes.string,
+ provider: PropTypes.object,
+}
+
+const mapStateToProps = ({ metamask: { network, provider } }) => {
+ return {
+ network,
+ provider,
+ }
+}
+
+module.exports = connect(mapStateToProps)(NetworkDisplay)
diff --git a/ui/app/components/pending-tx/confirm-deploy-contract.js b/ui/app/components/pending-tx/confirm-deploy-contract.js
index 4810bb515..b75f3a964 100644
--- a/ui/app/components/pending-tx/confirm-deploy-contract.js
+++ b/ui/app/components/pending-tx/confirm-deploy-contract.js
@@ -10,6 +10,7 @@ const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
const { conversionUtil } = require('../../conversion-util')
const t = require('../../../i18n')
const SenderToRecipient = require('../sender-to-recipient')
+const NetworkDisplay = require('../network-display')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@@ -244,9 +245,12 @@ class ConfirmDeployContract extends Component {
return (
h('.page-container', [
h('.page-container__header', [
- h('.page-container__back-button', {
- onClick: () => backToAccountDetail(selectedAddress),
- }, t('back')),
+ h('.page-container__header-row', [
+ h('span.page-container__back-button', {
+ onClick: () => backToAccountDetail(selectedAddress),
+ }, t('back')),
+ window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay),
+ ]),
h('.page-container__title', t('confirmContract')),
h('.page-container__subtitle', t('pleaseReviewTransaction')),
]),
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 908df3671..d1ce25cbf 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -4,12 +4,18 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const actions = require('../../actions')
const clone = require('clone')
-const Identicon = require('../identicon')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
-const { conversionUtil, addCurrencies } = require('../../conversion-util')
+const {
+ conversionUtil,
+ addCurrencies,
+ multiplyCurrencies,
+} = require('../../conversion-util')
+const GasFeeDisplay = require('../send/gas-fee-display-v2')
const t = require('../../../i18n')
+const SenderToRecipient = require('../sender-to-recipient')
+const NetworkDisplay = require('../network-display')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@@ -44,6 +50,7 @@ function mapDispatchToProps (dispatch) {
to,
value: amount,
} = txParams
+
dispatch(actions.updateSend({
gasLimit,
gasPrice,
@@ -56,6 +63,29 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.showSendPage())
},
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
+ showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
+ const { id, txParams, lastGasPrice } = txMeta
+ const { gas: txGasLimit, gasPrice: txGasPrice } = txParams
+
+ let forceGasMin
+ if (lastGasPrice) {
+ forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
+ multiplicandBase: 16,
+ multiplierBase: 10,
+ toNumericBase: 'hex',
+ fromDenomination: 'WEI',
+ }))
+ }
+
+ dispatch(actions.updateSend({
+ gasLimit: sendGasLimit || txGasLimit,
+ gasPrice: sendGasPrice || txGasPrice,
+ editingTransactionId: id,
+ gasTotal: sendGasTotal,
+ forceGasMin,
+ }))
+ dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
+ },
}
}
@@ -140,6 +170,7 @@ ConfirmSendEther.prototype.getGasFee = function () {
return {
FIAT,
ETH,
+ gasFeeInHex: txFeeBn.toString(16),
}
}
@@ -147,7 +178,7 @@ ConfirmSendEther.prototype.getData = function () {
const { identities } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
- const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH } = this.getGasFee()
+ const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee()
const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount()
const totalInFIAT = addCurrencies(gasFeeInFIAT, amountInFIAT, {
@@ -175,11 +206,20 @@ ConfirmSendEther.prototype.getData = function () {
amountInETH,
totalInFIAT,
totalInETH,
+ gasFeeInHex,
}
}
ConfirmSendEther.prototype.render = function () {
- const { editTransaction, currentCurrency, clearSend } = this.props
+ const {
+ editTransaction,
+ currentCurrency,
+ clearSend,
+ conversionRate,
+ currentCurrency: convertedCurrency,
+ showCustomizeGasModal,
+ send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice },
+ } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
@@ -193,13 +233,17 @@ ConfirmSendEther.prototype.render = function () {
name: toName,
},
memo,
- gasFeeInFIAT,
- gasFeeInETH,
+ gasFeeInHex,
amountInFIAT,
totalInFIAT,
totalInETH,
} = this.getData()
+ const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm'
+ const subtitle = txMeta.lastGasPrice
+ ? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
+ : 'Please review your transaction.'
+
// This is from the latest master
// It handles some of the errors that we are not currently handling
// Leaving as comments fo reference
@@ -214,194 +258,181 @@ ConfirmSendEther.prototype.render = function () {
this.inputs = []
return (
- h('div.confirm-screen-container.confirm-send-ether', [
- // Main Send token Card
- h('div.page-container', [
- h('div.page-container__header', [
- h('button.confirm-screen-back-button', {
+ // Main Send token Card
+ h('.page-container', [
+ h('.page-container__header', [
+ h('.page-container__header-row', [
+ h('span.page-container__back-button', {
onClick: () => editTransaction(txMeta),
+ style: {
+ visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
+ },
}, 'Edit'),
- h('div.page-container__title', 'Confirm'),
- h('div.page-container__subtitle', `Please review your transaction.`),
+ window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay),
+ ]),
+ h('.page-container__title', title),
+ h('.page-container__subtitle', subtitle),
+ ]),
+ h('.page-container__content', [
+ h(SenderToRecipient, {
+ senderName: fromName,
+ senderAddress: fromAddress,
+ recipientName: toName,
+ recipientAddress: txParams.to,
+ }),
+
+ // h('h3.flex-center.confirm-screen-sending-to-message', {
+ // style: {
+ // textAlign: 'center',
+ // fontSize: '16px',
+ // },
+ // }, [
+ // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
+ // ]),
+
+ h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]),
+ h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]),
+ h('div.flex-center.confirm-memo-wrapper', [
+ h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
]),
- h('.page-container__content', [
- h('div.flex-row.flex-center.confirm-screen-identicons', [
- h('div.confirm-screen-account-wrapper', [
- h(
- Identicon,
- {
- address: fromAddress,
- diameter: 60,
- },
- ),
- h('span.confirm-screen-account-name', fromName),
- // h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)),
- ]),
- h('i.fa.fa-arrow-right.fa-lg'),
- h('div.confirm-screen-account-wrapper', [
- h(
- Identicon,
- {
- address: txParams.to,
- diameter: 60,
- },
- ),
- h('span.confirm-screen-account-name', toName),
- // h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)),
- ]),
- ]),
- // h('h3.flex-center.confirm-screen-sending-to-message', {
- // style: {
- // textAlign: 'center',
- // fontSize: '16px',
- // },
- // }, [
- // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
- // ]),
-
- h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]),
- h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]),
- h('div.flex-center.confirm-memo-wrapper', [
- h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
+ h('div.confirm-screen-rows', [
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', fromName),
+ h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
+ ]),
]),
- h('div.confirm-screen-rows', [
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', fromName),
- h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
- ]),
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', toName),
+ h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
]),
+ ]),
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', toName),
- h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
- ]),
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
+ h('div.confirm-screen-section-column', [
+ h(GasFeeDisplay, {
+ gasTotal: gasTotal || gasFeeInHex,
+ conversionRate,
+ convertedCurrency,
+ onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
+ }),
]),
+ ]),
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`),
-
- h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`),
- ]),
+ h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
+ h('div.confirm-screen-section-column', [
+ h('span.confirm-screen-label', [ t('total') + ' ' ]),
+ h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
]),
-
- h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
- h('div.confirm-screen-section-column', [
- h('span.confirm-screen-label', [ t('total') + ' ' ]),
- h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
- ]),
-
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`),
- h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
- ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`),
+ h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]),
]),
-
- // These are latest errors handling from master
- // Leaving as comments as reference when we start implementing error handling
- // h('style', `
- // .conf-buttons button {
- // margin-left: 10px;
- // text-transform: uppercase;
- // }
- // `),
-
- // txMeta.simulationFails ?
- // h('.error', {
- // style: {
- // marginLeft: 50,
- // fontSize: '0.9em',
- // },
- // }, 'Transaction Error. Exception thrown in contract code.')
- // : null,
-
- // !isValidAddress ?
- // h('.error', {
- // style: {
- // marginLeft: 50,
- // fontSize: '0.9em',
- // },
- // }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
- // : null,
-
- // insufficientBalance ?
- // h('span.error', {
- // style: {
- // marginLeft: 50,
- // fontSize: '0.9em',
- // },
- // }, 'Insufficient balance for transaction')
- // : null,
-
- // // send + cancel
- // h('.flex-row.flex-space-around.conf-buttons', {
- // style: {
- // display: 'flex',
- // justifyContent: 'flex-end',
- // margin: '14px 25px',
- // },
- // }, [
- // h('button', {
- // onClick: (event) => {
- // this.resetGasFields()
- // event.preventDefault()
- // },
- // }, 'Reset'),
-
- // // Accept Button or Buy Button
- // insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
- // h('input.confirm.btn-green', {
- // type: 'submit',
- // value: 'SUBMIT',
- // style: { marginLeft: '10px' },
- // disabled: buyDisabled,
- // }),
-
- // h('button.cancel.btn-red', {
- // onClick: props.cancelTransaction,
- // }, 'Reject'),
- // ]),
- // showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
- // style: {
- // display: 'flex',
- // justifyContent: 'flex-end',
- // margin: '14px 25px',
- // },
- // }, [
- // h('button.cancel.btn-red', {
- // onClick: props.cancelAllTransactions,
- // }, 'Reject All'),
- // ]) : null,
- // ]),
- // ])
- // )
- // }
]),
- h('form#pending-tx-form', {
- onSubmit: this.onSubmit,
- }, [
- h('.page-container__footer', [
- // Cancel Button
- h('button.btn-cancel.page-container__footer-button.allcaps', {
- onClick: (event) => {
- clearSend()
- this.cancel(event, txMeta)
- },
- }, t('cancel')),
-
- // Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', [t('confirm')]),
- ]),
+// These are latest errors handling from master
+// Leaving as comments as reference when we start implementing error handling
+// h('style', `
+// .conf-buttons button {
+// margin-left: 10px;
+// text-transform: uppercase;
+// }
+// `),
+
+// txMeta.simulationFails ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Transaction Error. Exception thrown in contract code.')
+// : null,
+
+// !isValidAddress ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
+// : null,
+
+// insufficientBalance ?
+// h('span.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Insufficient balance for transaction')
+// : null,
+
+// // send + cancel
+// h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button', {
+// onClick: (event) => {
+// this.resetGasFields()
+// event.preventDefault()
+// },
+// }, 'Reset'),
+
+// // Accept Button or Buy Button
+// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
+// h('input.confirm.btn-green', {
+// type: 'submit',
+// value: 'SUBMIT',
+// style: { marginLeft: '10px' },
+// disabled: buyDisabled,
+// }),
+
+// h('button.cancel.btn-red', {
+// onClick: props.cancelTransaction,
+// }, 'Reject'),
+// ]),
+// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button.cancel.btn-red', {
+// onClick: props.cancelAllTransactions,
+// }, 'Reject All'),
+// ]) : null,
+// ]),
+// ])
+// )
+// }
+ ]),
+
+ h('form#pending-tx-form', {
+ onSubmit: this.onSubmit,
+ }, [
+ h('.page-container__footer', [
+ // Cancel Button
+ h('button.btn-cancel.page-container__footer-button.allcaps', {
+ onClick: (event) => {
+ clearSend()
+ this.cancel(event, txMeta)
+ },
+ }, t('cancel')),
+
+ // Accept Button
+ h('button.btn-confirm.page-container__footer-button.allcaps', [t('confirm')]),
]),
]),
])
@@ -450,6 +481,27 @@ ConfirmSendEther.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
+ const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send
+ const {
+ lastGasPrice,
+ txParams: {
+ gasPrice: txGasPrice,
+ gas: txGasLimit,
+ },
+ } = txData
+
+ let forceGasMin
+ if (lastGasPrice) {
+ forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
+ multiplicandBase: 16,
+ multiplierBase: 10,
+ toNumericBase: 'hex',
+ }))
+ }
+
+ txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice
+ txData.txParams.gas = sendGasLimit || txGasLimit
+
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index c347dd21e..f9276e8a5 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -9,6 +9,7 @@ const actions = require('../../actions')
const t = require('../../../i18n')
const clone = require('clone')
const Identicon = require('../identicon')
+const GasFeeDisplay = require('../send/gas-fee-display-v2.js')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const {
@@ -90,6 +91,39 @@ function mapDispatchToProps (dispatch, ownProps) {
}))
dispatch(actions.showSendTokenPage())
},
+ showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
+ const { id, txParams, lastGasPrice } = txMeta
+ const { gas: txGasLimit, gasPrice: txGasPrice } = txParams
+ const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { params = [] } = tokenData
+ const { value: to } = params[0] || {}
+ const { value: tokenAmountInDec } = params[1] || {}
+ const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ })
+
+ let forceGasMin
+ if (lastGasPrice) {
+ forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
+ multiplicandBase: 16,
+ multiplierBase: 10,
+ toNumericBase: 'hex',
+ fromDenomination: 'WEI',
+ }))
+ }
+
+ dispatch(actions.updateSend({
+ gasLimit: sendGasLimit || txGasLimit,
+ gasPrice: sendGasPrice || txGasPrice,
+ editingTransactionId: id,
+ gasTotal: sendGasTotal,
+ to,
+ amount: tokenAmountInHex,
+ forceGasMin,
+ }))
+ dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
+ },
}
}
@@ -189,6 +223,7 @@ ConfirmSendToken.prototype.getGasFee = function () {
token: tokenExchangeRate
? tokenGas
: null,
+ gasFeeInHex: gasTotal.toString(16),
}
}
@@ -241,19 +276,25 @@ ConfirmSendToken.prototype.renderHeroAmount = function () {
}
ConfirmSendToken.prototype.renderGasFee = function () {
- const { token: { symbol }, currentCurrency } = this.props
- const { fiat: fiatGas, token: tokenGas, eth: ethGas } = this.getGasFee()
+ const {
+ currentCurrency: convertedCurrency,
+ conversionRate,
+ send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice },
+ showCustomizeGasModal,
+ } = this.props
+ const txMeta = this.gatherTxMeta()
+ const { gasFeeInHex } = this.getGasFee()
return (
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency}`),
-
- h(
- 'div.confirm-screen-row-detail',
- tokenGas ? `${tokenGas} ${symbol}` : `${ethGas} ETH`
- ),
+ h(GasFeeDisplay, {
+ gasTotal: gasTotal || gasFeeInHex,
+ conversionRate,
+ convertedCurrency,
+ onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
+ }),
]),
])
)
@@ -309,16 +350,21 @@ ConfirmSendToken.prototype.render = function () {
this.inputs = []
+ const title = txMeta.lastGasPrice ? 'Reprice Transaction' : t('confirm')
+ const subtitle = txMeta.lastGasPrice
+ ? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
+ : t('pleaseReviewTransaction')
+
return (
h('div.confirm-screen-container.confirm-send-token', [
// Main Send token Card
h('div.page-container', [
h('div.page-container__header', [
- h('button.confirm-screen-back-button', {
+ !txMeta.lastGasPrice && h('button.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
}, t('edit')),
- h('div.page-container__title', t('confirm')),
- h('div.page-container__subtitle', t('pleaseReviewTransaction')),
+ h('div.page-container__title', title),
+ h('div.page-container__subtitle', subtitle),
]),
h('.page-container__content', [
h('div.flex-row.flex-center.confirm-screen-identicons', [
@@ -442,6 +488,27 @@ ConfirmSendToken.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
+ const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send
+ const {
+ lastGasPrice,
+ txParams: {
+ gasPrice: txGasPrice,
+ gas: txGasLimit,
+ },
+ } = txData
+
+ let forceGasMin
+ if (lastGasPrice) {
+ forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
+ multiplicandBase: 16,
+ multiplierBase: 10,
+ toNumericBase: 'hex',
+ }))
+ }
+
+ txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice
+ txData.txParams.gas = sendGasLimit || txGasLimit
+
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}
diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js
index 0c6f76303..f6af13454 100644
--- a/ui/app/components/send/gas-fee-display-v2.js
+++ b/ui/app/components/send/gas-fee-display-v2.js
@@ -18,6 +18,7 @@ GasFeeDisplay.prototype.render = function () {
onClick,
primaryCurrency = 'ETH',
convertedCurrency,
+ gasLoadingError,
} = this.props
return h('div.send-v2__gas-fee-display', [
@@ -31,13 +32,15 @@ GasFeeDisplay.prototype.render = function () {
convertedPrefix: '$',
readOnly: true,
})
- : h('div.currency-display', t('loading')),
+ : gasLoadingError
+ ? h('div..currency-display.currency-display--message', 'Set with the gas price customizer.')
+ : h('div.currency-display', t('loading')),
- h('button.send-v2__sliders-icon-container', {
+ h('button.sliders-icon-container', {
onClick,
- disabled: !gasTotal,
+ disabled: !gasTotal && !gasLoadingError,
}, [
- h('i.fa.fa-sliders.send-v2__sliders-icon'),
+ h('i.fa.fa-sliders.sliders-icon'),
]),
])
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index 1106902b7..d1319b6dc 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -48,6 +48,7 @@ function mapStateToProps (state) {
primaryCurrency,
convertedCurrency: getCurrentCurrency(state),
data,
+ selectedAddress,
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate,
tokenContract: getSelectedTokenContract(state),
unapprovedTxs: state.metamask.unapprovedTxs,
diff --git a/ui/app/components/sender-to-recipient.js b/ui/app/components/sender-to-recipient.js
index 25b9c7d6b..f35c353ad 100644
--- a/ui/app/components/sender-to-recipient.js
+++ b/ui/app/components/sender-to-recipient.js
@@ -5,6 +5,28 @@ const t = require('../../i18n')
const Identicon = require('./identicon')
class SenderToRecipient extends Component {
+ renderRecipientIcon () {
+ const { recipientAddress } = this.props
+ return (
+ recipientAddress
+ ? h(Identicon, { address: recipientAddress, diameter: 20 })
+ : h('i.fa.fa-file-text-o')
+ )
+ }
+
+ renderRecipient () {
+ const { recipientName } = this.props
+ return (
+ h('.sender-to-recipient__recipient', [
+ this.renderRecipientIcon(),
+ h(
+ '.sender-to-recipient__name.sender-to-recipient__recipient-name',
+ recipientName || t('newContract')
+ ),
+ ])
+ )
+ }
+
render () {
const { senderName, senderAddress } = this.props
@@ -28,10 +50,7 @@ class SenderToRecipient extends Component {
}),
]),
]),
- h('.sender-to-recipient__recipient', [
- h('i.fa.fa-file-text-o'),
- h('.sender-to-recipient__name.sender-to-recipient__recipient-name', t('newContract')),
- ]),
+ this.renderRecipient(),
])
)
}
@@ -40,6 +59,8 @@ class SenderToRecipient extends Component {
SenderToRecipient.propTypes = {
senderName: PropTypes.string,
senderAddress: PropTypes.string,
+ recipientName: PropTypes.string,
+ recipientAddress: PropTypes.string,
}
module.exports = SenderToRecipient
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 849d70489..d104eda88 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -9,19 +9,28 @@ abiDecoder.addABI(abi)
const Identicon = require('./identicon')
const contractMap = require('eth-contract-metadata')
+const actions = require('../actions')
const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
const { calcTokenAmount } = require('../token-util')
const { getCurrentCurrency } = require('../selectors')
const t = require('../../i18n')
-module.exports = connect(mapStateToProps)(TxListItem)
+module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem)
function mapStateToProps (state) {
return {
tokens: state.metamask.tokens,
currentCurrency: getCurrentCurrency(state),
tokenExchangeRates: state.metamask.tokenExchangeRates,
+ selectedAddressTxList: state.metamask.selectedAddressTxList,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)),
+ retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
}
}
@@ -32,6 +41,7 @@ function TxListItem () {
this.state = {
total: null,
fiatTotal: null,
+ isTokenTx: null,
}
}
@@ -40,12 +50,13 @@ TxListItem.prototype.componentDidMount = async function () {
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { name: txDataName } = decodedData || {}
+ const isTokenTx = txDataName === 'transfer'
- const { total, fiatTotal } = txDataName === 'transfer'
+ const { total, fiatTotal } = isTokenTx
? await this.getSendTokenTotal()
: this.getSendEtherTotal()
- this.setState({ total, fiatTotal })
+ this.setState({ total, fiatTotal, isTokenTx })
}
TxListItem.prototype.getAddressText = function () {
@@ -168,22 +179,49 @@ TxListItem.prototype.getSendTokenTotal = async function () {
}
}
+TxListItem.prototype.showRetryButton = function () {
+ const {
+ transactionSubmittedTime,
+ selectedAddressTxList,
+ transactionId,
+ txParams,
+ } = this.props
+ const currentNonce = txParams.nonce
+ const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
+ const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
+ const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1]
+ const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce
+ && lastSubmittedTxWithCurrentNonce.id === transactionId
+
+ return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000
+}
+
+TxListItem.prototype.setSelectedToken = function (tokenAddress) {
+ this.props.setSelectedToken(tokenAddress)
+}
+
+TxListItem.prototype.resubmit = function () {
+ const { transactionId } = this.props
+ this.props.retryTransaction(transactionId)
+}
+
TxListItem.prototype.render = function () {
const {
transactionStatus,
transactionAmount,
onClick,
- transActionId,
+ transactionId,
dateString,
address,
className,
+ txParams,
} = this.props
- const { total, fiatTotal } = this.state
+ const { total, fiatTotal, isTokenTx } = this.state
const showFiatTotal = transactionAmount !== '0x0' && fiatTotal
return h(`div${className || ''}`, {
- key: transActionId,
- onClick: () => onClick && onClick(transActionId),
+ key: transactionId,
+ onClick: () => onClick && onClick(transactionId),
}, [
h(`div.flex-column.tx-list-item-wrapper`, {}, [
@@ -224,9 +262,10 @@ TxListItem.prototype.render = function () {
className: classnames('tx-list-status', {
'tx-list-status--rejected': transactionStatus === 'rejected',
'tx-list-status--failed': transactionStatus === 'failed',
+ 'tx-list-status--dropped': transactionStatus === 'dropped',
}),
},
- transactionStatus,
+ this.txStatusIndicator(),
),
]),
]),
@@ -241,6 +280,48 @@ TxListItem.prototype.render = function () {
]),
]),
+
+ this.showRetryButton() && h('div.tx-list-item-retry-container', [
+
+ h('span.tx-list-item-retry-copy', 'Taking too long?'),
+
+ h('span.tx-list-item-retry-link', {
+ onClick: (event) => {
+ event.stopPropagation()
+ if (isTokenTx) {
+ this.setSelectedToken(txParams.to)
+ }
+ this.resubmit()
+ },
+ }, 'Increase the gas price on your transaction'),
+
+ ]),
+
]), // holding on icon from design
])
}
+
+TxListItem.prototype.txStatusIndicator = function () {
+ const { transactionStatus } = this.props
+
+ let name
+
+ if (transactionStatus === 'unapproved') {
+ name = t('unapproved')
+ } else if (transactionStatus === 'rejected') {
+ name = t('rejected')
+ } else if (transactionStatus === 'approved') {
+ name = t('approved')
+ } else if (transactionStatus === 'signed') {
+ name = t('signed')
+ } else if (transactionStatus === 'submitted') {
+ name = t('submitted')
+ } else if (transactionStatus === 'confirmed') {
+ name = t('confirmed')
+ } else if (transactionStatus === 'failed') {
+ name = t('failed')
+ } else if (transactionStatus === 'dropped') {
+ name = t('dropped')
+ }
+ return name
+}
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
index 34dc837ae..037c7de8c 100644
--- a/ui/app/components/tx-list.js
+++ b/ui/app/components/tx-list.js
@@ -40,7 +40,7 @@ TxList.prototype.render = function () {
return h('div.flex-column', [
h('div.flex-row.tx-list-header-wrapper', [
h('div.flex-row.tx-list-header', [
- h('div', 'transactions'),
+ h('div', t('transactions')),
]),
]),
h('div.flex-column.tx-list-container', {}, [
@@ -75,9 +75,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
address: transaction.txParams.to,
transactionStatus: transaction.status,
transactionAmount: transaction.txParams.value,
- transActionId: transaction.id,
+ transactionId: transaction.id,
transactionHash: transaction.hash,
transactionNetworkId: transaction.metamaskNetworkId,
+ transactionSubmittedTime: transaction.submittedTime,
}
const {
@@ -85,29 +86,31 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
transactionStatus,
transactionAmount,
dateString,
- transActionId,
+ transactionId,
transactionHash,
transactionNetworkId,
+ transactionSubmittedTime,
} = props
const { showConfTxPage } = this.props
const opts = {
- key: transActionId || transactionHash,
+ key: transactionId || transactionHash,
txParams: transaction.txParams,
transactionStatus,
- transActionId,
+ transactionId,
dateString,
address,
transactionAmount,
transactionHash,
conversionRate,
tokenInfoGetter: this.tokenInfoGetter,
+ transactionSubmittedTime,
}
const isUnapproved = transactionStatus === 'unapproved'
if (isUnapproved) {
- opts.onClick = () => showConfTxPage({id: transActionId})
+ opts.onClick = () => showConfTxPage({id: transactionId})
opts.transactionStatus = t('Not Started')
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)