aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/scripts/controllers/transactions.js52
-rw-r--r--app/scripts/lib/tx-state-manager.js38
-rw-r--r--ui/app/actions.js4
-rw-r--r--ui/app/components/customize-gas-modal/index.js18
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js68
-rw-r--r--ui/app/components/send/gas-fee-display-v2.js4
-rw-r--r--ui/app/components/tx-list-item.js50
-rw-r--r--ui/app/components/tx-list.js13
-rw-r--r--ui/app/conversion-util.js13
-rw-r--r--ui/app/css/itcss/components/send.scss21
-rw-r--r--ui/app/css/itcss/components/transaction-list.scss47
-rw-r--r--ui/app/css/itcss/settings/variables.scss1
-rw-r--r--ui/app/reducers/metamask.js4
-rw-r--r--ui/app/selectors.js5
-rw-r--r--ui/app/send-v2.js5
15 files changed, 289 insertions, 54 deletions
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 9c2ca0dc8..a3f731b6e 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -6,7 +6,6 @@ const EthQuery = require('ethjs-query')
const TransactionStateManager = require('../lib/tx-state-manager')
const TxGasUtil = require('../lib/tx-gas-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
-const createId = require('../lib/random-id')
const NonceTracker = require('../lib/nonce-tracker')
/*
@@ -92,8 +91,8 @@ module.exports = class TransactionController extends EventEmitter {
this.pendingTxTracker.on('tx:warning', (txMeta) => {
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
})
+ this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
- this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber
@@ -186,14 +185,7 @@ module.exports = class TransactionController extends EventEmitter {
// validate
await this.txGasUtil.validateTxParams(txParams)
// construct txMeta
- const txMeta = {
- id: createId(),
- time: (new Date()).getTime(),
- status: 'unapproved',
- metamaskNetworkId: this.getNetwork(),
- txParams: txParams,
- loadingDefaults: true,
- }
+ const txMeta = this.txStateManager.generateTxMeta({txParams})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
@@ -215,7 +207,6 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams
// ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
- txMeta.nonceSpecified = Boolean(txParams.nonce)
let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
@@ -226,11 +217,16 @@ module.exports = class TransactionController extends EventEmitter {
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
- async retryTransaction (txId) {
- this.txStateManager.setTxStatusUnapproved(txId)
- const txMeta = this.txStateManager.getTx(txId)
- txMeta.lastGasPrice = txMeta.txParams.gasPrice
- this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
+ async retryTransaction (originalTxId) {
+ const originalTxMeta = this.txStateManager.getTx(originalTxId)
+ const lastGasPrice = originalTxMeta.txParams.gasPrice
+ const txMeta = this.txStateManager.generateTxMeta({
+ txParams: originalTxMeta.txParams,
+ lastGasPrice,
+ loadingDefaults: false,
+ })
+ this.addTx(txMeta)
+ this.emit('newUnapprovedTx', txMeta)
}
async updateTransaction (txMeta) {
@@ -253,11 +249,9 @@ module.exports = class TransactionController extends EventEmitter {
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
- const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
- if (nonce > nonceLock.nextNonce) {
- const message = `Specified nonce may not be larger than account's next valid nonce.`
- throw new Error(message)
- }
+ // if txMeta has lastGasPrice then it is a retry at same nonce with higher
+ // gas price transaction and their for the nonce should not be calculated
+ const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
@@ -314,6 +308,22 @@ module.exports = class TransactionController extends EventEmitter {
// PRIVATE METHODS
//
+ _markNonceDuplicatesDropped (txId) {
+ this.txStateManager.setTxStatusConfirmed(txId)
+ // get the confirmed transactions nonce and from address
+ const txMeta = this.txStateManager.getTx(txId)
+ const { nonce, from } = txMeta.txParams
+ const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from})
+ if (!sameNonceTxs.length) return
+ // mark all same nonce transactions as dropped and give i a replacedBy hash
+ sameNonceTxs.forEach((otherTxMeta) => {
+ if (otherTxMeta.id === txId) return
+ otherTxMeta.replacedBy = txMeta.hash
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce')
+ this.txStateManager.setTxStatusDropped(otherTxMeta.id)
+ })
+ }
+
_updateMemstore () {
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
index 2eb006380..e75f733aa 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/lib/tx-state-manager.js
@@ -1,9 +1,21 @@
const extend = require('xtend')
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
+const createId = require('./random-id')
const ethUtil = require('ethereumjs-util')
const txStateHistoryHelper = require('./tx-state-history-helper')
+// STATUS METHODS
+ // statuses:
+ // - `'unapproved'` the user has not responded
+ // - `'rejected'` the user has responded no!
+ // - `'approved'` the user has approved the tx
+ // - `'signed'` the tx is signed
+ // - `'submitted'` the tx is sent to a server
+ // - `'confirmed'` the tx has been included in a block.
+ // - `'failed'` the tx failed for some reason, included on tx data.
+ // - `'dropped'` the tx nonce was already used
+
module.exports = class TransactionStateManager extends EventEmitter {
constructor ({ initState, txHistoryLimit, getNetwork }) {
super()
@@ -16,6 +28,16 @@ module.exports = class TransactionStateManager extends EventEmitter {
this.getNetwork = getNetwork
}
+ generateTxMeta (opts) {
+ return extend({
+ id: createId(),
+ time: (new Date()).getTime(),
+ status: 'unapproved',
+ metamaskNetworkId: this.getNetwork(),
+ loadingDefaults: true,
+ }, opts)
+ }
+
// Returns the number of txs for the current network.
getTxCount () {
return this.getTxList().length
@@ -164,16 +186,6 @@ module.exports = class TransactionStateManager extends EventEmitter {
})
}
- // STATUS METHODS
- // statuses:
- // - `'unapproved'` the user has not responded
- // - `'rejected'` the user has responded no!
- // - `'approved'` the user has approved the tx
- // - `'signed'` the tx is signed
- // - `'submitted'` the tx is sent to a server
- // - `'confirmed'` the tx has been included in a block.
- // - `'failed'` the tx failed for some reason, included on tx data.
-
// get::set status
// should return the status of the tx.
@@ -211,6 +223,12 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._setTxStatus(txId, 'confirmed')
}
+ // should update the status dropped
+ setTxStatusDropped (txId) {
+ this._setTxStatus(txId, 'dropped')
+ }
+
+
setTxStatusFailed (txId, err) {
const txMeta = this.getTx(txId)
txMeta.err = {
diff --git a/ui/app/actions.js b/ui/app/actions.js
index b56265edc..200946753 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1270,8 +1270,10 @@ function retryTransaction (txId) {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
+ const { selectedAddressTxList } = newState
+ const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1]
dispatch(actions.updateMetamaskState(newState))
- dispatch(actions.viewPendingTx(txId))
+ dispatch(actions.viewPendingTx(newTxId))
})
}
}
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 826d2cd4b..34a93d6c6 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -21,12 +21,14 @@ const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
+ conversionMax,
subtractCurrencies,
} = require('../../conversion-util')
const {
getGasPrice,
getGasLimit,
+ getForceGasMin,
conversionRateSelector,
getSendAmount,
getSelectedToken,
@@ -44,6 +46,7 @@ function mapStateToProps (state) {
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
+ forceGasMin: getForceGasMin(state),
conversionRate,
amount: getSendAmount(state),
maxModeOn: getSendMaxModeState(state),
@@ -217,7 +220,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, {
@@ -229,6 +232,17 @@ CustomizeGasModal.prototype.render = function () {
convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}`
+ if (forceGasMin) {
+ const convertedMinPrice = conversionUtil(forceGasMin, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ })
+ convertedGasPrice = conversionMax(
+ { value: convertedMinPrice, fromNumericBase: 'dec' },
+ { value: convertedGasPrice, fromNumericBase: 'dec' }
+ )
+ }
+
const convertedGasLimit = conversionUtil(gasLimit, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
@@ -251,7 +265,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),
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 3f8d9c28f..422b1c6c1 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -9,6 +9,7 @@ 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 GasFeeDisplay = require('../send/gas-fee-display-v2')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@@ -36,13 +37,29 @@ function mapDispatchToProps (dispatch) {
return {
clearSend: () => dispatch(actions.clearSend()),
editTransaction: txMeta => {
- const { id, txParams } = txMeta
+ const { id, txParams, lastGasPrice } = txMeta
const {
gas: gasLimit,
gasPrice,
to,
value: amount,
} = txParams
+
+ let forceGasMin
+ let nonce
+ if (lastGasPrice) {
+ const stripped = ethUtil.stripHexPrefix(lastGasPrice)
+ forceGasMin = ethUtil.addHexPrefix(addCurrencies(stripped, MIN_GAS_PRICE_HEX, {
+ aBase: 16,
+ bBase: 16,
+ toNumericBase: 'hex',
+ fromDenomination: 'WEI',
+ toDenomination: 'GWEI',
+ }))
+
+ nonce = txParams.nonce
+ }
+
dispatch(actions.updateSend({
gasLimit,
gasPrice,
@@ -51,10 +68,24 @@ function mapDispatchToProps (dispatch) {
amount,
errors: { to: null, amount: null },
editingTransactionId: id,
+ forceGasMin,
+ nonce,
}))
dispatch(actions.showSendPage())
},
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
+ showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
+ const { id, txParams } = txMeta
+ const { gas: txGasLimit, gasPrice: txGasPrice } = txParams
+
+ dispatch(actions.updateSend({
+ gasLimit: sendGasLimit || txGasLimit,
+ gasPrice: sendGasPrice || txGasPrice,
+ editingTransactionId: id,
+ gasTotal: sendGasTotal,
+ }))
+ dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
+ },
}
}
@@ -139,6 +170,7 @@ ConfirmSendEther.prototype.getGasFee = function () {
return {
FIAT,
ETH,
+ gasFeeInHex: txFeeBn.toString(16),
}
}
@@ -146,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, {
@@ -174,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 || {}
@@ -194,11 +235,17 @@ ConfirmSendEther.prototype.render = function () {
memo,
gasFeeInFIAT,
gasFeeInETH,
+ gasFeeInHex,
amountInFIAT,
totalInFIAT,
totalInETH,
} = this.getData()
+ const title = txMeta.lastGasPrice ? 'Overwrite 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
@@ -217,11 +264,11 @@ ConfirmSendEther.prototype.render = function () {
// 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),
}, 'Edit'),
- h('div.page-container__title', 'Confirm'),
- h('div.page-container__subtitle', `Please review your transaction.`),
+ h('div.page-container__title', title),
+ h('div.page-container__subtitle', subtitle),
]),
h('div.flex-row.flex-center.confirm-screen-identicons', [
h('div.confirm-screen-account-wrapper', [
@@ -284,9 +331,12 @@ ConfirmSendEther.prototype.render = function () {
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`),
-
- h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`),
+ h(GasFeeDisplay, {
+ gasTotal: gasTotal || gasFeeInHex,
+ conversionRate,
+ convertedCurrency,
+ onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
+ }),
]),
]),
diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js
index 0c4c3f7a9..da1bdd05e 100644
--- a/ui/app/components/send/gas-fee-display-v2.js
+++ b/ui/app/components/send/gas-fee-display-v2.js
@@ -32,11 +32,11 @@ GasFeeDisplay.prototype.render = function () {
})
: h('div.currency-display', 'Loading...'),
- h('button.send-v2__sliders-icon-container', {
+ h('button.sliders-icon-container', {
onClick,
disabled: !gasTotal,
}, [
- h('i.fa.fa-sliders.send-v2__sliders-icon'),
+ h('i.fa.fa-sliders.sliders-icon'),
]),
])
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 1a13070c8..123f723be 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -9,18 +9,26 @@ 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')
-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 {
+ retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
}
}
@@ -167,12 +175,32 @@ TxListItem.prototype.getSendTokenTotal = async function () {
}
}
+TxListItem.prototype.showRetryButton = function () {
+ const {
+ transactionStatus,
+ transactionTime,
+ selectedAddressTxList,
+ transactionId,
+ txParams,
+ } = this.props
+ const currentNonce = txParams.nonce
+ const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
+ const isLastWithNonce = currentNonceTxs[currentNonceTxs.length - 1].id === transactionId
+
+ return transactionStatus === 'submitted' && isLastWithNonce && Date.now() - transactionTime > 5000
+}
+
+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,
@@ -181,8 +209,8 @@ TxListItem.prototype.render = function () {
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`, {}, [
@@ -240,6 +268,20 @@ 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()
+ this.resubmit()
+ },
+ }, 'Increase the gas on your transaction'),
+
+ ]),
+
]), // holding on icon from design
])
}
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
index 1729e6a6f..30f816d58 100644
--- a/ui/app/components/tx-list.js
+++ b/ui/app/components/tx-list.js
@@ -74,9 +74,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,
+ transactionTime: transaction.time,
}
const {
@@ -84,29 +85,31 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
transactionStatus,
transactionAmount,
dateString,
- transActionId,
+ transactionId,
transactionHash,
transactionNetworkId,
+ transactionTime,
} = 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,
+ transactionTime,
}
const isUnapproved = transactionStatus === 'unapproved'
if (isUnapproved) {
- opts.onClick = () => showConfTxPage({id: transActionId})
+ opts.onClick = () => showConfTxPage({id: transactionId})
opts.transactionStatus = 'Not Started'
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)
diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js
index ee42ebea1..d484ed16d 100644
--- a/ui/app/conversion-util.js
+++ b/ui/app/conversion-util.js
@@ -187,6 +187,18 @@ const conversionGreaterThan = (
return firstValue.gt(secondValue)
}
+const conversionMax = (
+ { ...firstProps },
+ { ...secondProps },
+) => {
+ const firstIsGreater = conversionGreaterThan(
+ { ...firstProps },
+ { ...secondProps }
+ )
+
+ return firstIsGreater ? firstProps.value : secondProps.value
+}
+
const conversionGTE = (
{ ...firstProps },
{ ...secondProps },
@@ -216,6 +228,7 @@ module.exports = {
conversionGreaterThan,
conversionGTE,
conversionLTE,
+ conversionMax,
toNegative,
subtractCurrencies,
}
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index bb17e53cd..1a058b305 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -660,6 +660,7 @@
&__gas-fee-display {
width: 100%;
+ position: relative;
}
&__sliders-icon-container {
@@ -885,3 +886,23 @@
}
}
}
+
+.sliders-icon-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 24px;
+ width: 24px;
+ border: 1px solid $curious-blue;
+ border-radius: 4px;
+ background-color: $white;
+ position: absolute;
+ right: 15px;
+ top: 14px;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+.sliders-icon {
+ color: $curious-blue;
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss
index c3df493df..4545897e3 100644
--- a/ui/app/css/itcss/components/transaction-list.scss
+++ b/ui/app/css/itcss/components/transaction-list.scss
@@ -126,6 +126,53 @@
}
}
+.tx-list-item-retry-container {
+ background: #d1edff;
+ width: 100%;
+ border-radius: 4px;
+ font-size: 0.8em;
+ display: flex;
+ justify-content: center;
+ margin-left: 44px;
+ width: calc(100% - 44px);
+
+ @media screen and (min-width: 576px) and (max-width: 679px) {
+ flex-flow: column;
+ align-items: center;
+ }
+
+ @media screen and (min-width: 380px) and (max-width: 575px) {
+ flex-flow: row;
+ }
+
+ @media screen and (max-width: 379px) {
+ flex-flow: column;
+ align-items: center;
+ }
+}
+
+.tx-list-item-retry-copy {
+ font-family: Roboto;
+}
+
+.tx-list-item-retry-link {
+ text-decoration: underline;
+ margin-left: 6px;
+ cursor: pointer;
+
+ @media screen and (min-width: 576px) and (max-width: 679px) {
+ margin-left: 0px;
+ }
+
+ @media screen and (min-width: 380px) and (max-width: 575px) {
+ margin-left: 6px;
+ }
+
+ @media screen and (max-width: 379px) {
+ margin-left: 0px;
+ }
+}
+
.tx-list-date {
color: $dusty-gray;
font-size: 12px;
diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss
index d96c1ae43..640fd95b8 100644
--- a/ui/app/css/itcss/settings/variables.scss
+++ b/ui/app/css/itcss/settings/variables.scss
@@ -46,6 +46,7 @@ $manatee: #93949d;
$spindle: #c7ddec;
$mid-gray: #5b5d67;
$cape-cod: #38393a;
+$onahau: #d1edff;
$java: #29b6af;
$wild-strawberry: #ff4a8d;
$cornflower-blue: #7057ff;
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 029d830ec..b27e7140a 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -38,6 +38,8 @@ function reduceMetamask (state, action) {
errors: {},
maxModeOn: false,
editingTransactionId: null,
+ forceGasMin: null,
+ nonce: null,
},
coinOptions: {},
useBlockie: false,
@@ -296,6 +298,8 @@ function reduceMetamask (state, action) {
memo: '',
errors: {},
editingTransactionId: null,
+ forceGasMin: null,
+ nonce: null,
},
})
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index 5d2635775..d37c26f7e 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -18,6 +18,7 @@ const selectors = {
getCurrentAccountWithSendEtherInfo,
getGasPrice,
getGasLimit,
+ getForceGasMin,
getAddressBook,
getSendFrom,
getCurrentCurrency,
@@ -130,6 +131,10 @@ function getGasLimit (state) {
return state.metamask.send.gasLimit
}
+function getForceGasMin (state) {
+ return state.metamask.send.forceGasMin
+}
+
function getSendFrom (state) {
return state.metamask.send.from
}
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index fc1df1f51..697dfc517 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -548,6 +548,7 @@ SendTransactionScreen.prototype.getEditedTx = function () {
selectedToken,
editingTransactionId,
unapprovedTxs,
+ nonce,
} = this.props
const editingTx = {
@@ -559,6 +560,10 @@ SendTransactionScreen.prototype.getEditedTx = function () {
},
}
+ if (nonce) {
+ editingTx.txParams.nonce = ethUtil.addHexPrefix(nonce)
+ }
+
if (selectedToken) {
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),