mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #3489 from MetaMask/retry-tx-refractor
Retry tx refractor
This commit is contained in:
commit
1079d57f8c
@ -2,6 +2,7 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- MetaMask will no longer allow nonces to be specified by the dapp
|
||||
- Add ability for internationalization.
|
||||
- Will now throw an error if the `to` field in txParams is not valid.
|
||||
- Will strip null values from the `to` field.
|
||||
|
@ -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,17 @@ 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)
|
||||
return txMeta
|
||||
}
|
||||
|
||||
async updateTransaction (txMeta) {
|
||||
@ -253,11 +250,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 +309,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({
|
||||
|
@ -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.
|
||||
@ -202,7 +214,11 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'submitted'.
|
||||
// and add a time stamp for when it was called
|
||||
setTxStatusSubmitted (txId) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.submittedTime = (new Date()).getTime()
|
||||
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
||||
this._setTxStatus(txId, 'submitted')
|
||||
}
|
||||
|
||||
@ -211,6 +227,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 = {
|
||||
|
@ -116,7 +116,7 @@
|
||||
"send": {
|
||||
"gasLimit": "0xea60",
|
||||
"gasPrice": "0xba43b7400",
|
||||
"gasTotal": "0xb451dc41b578",
|
||||
"gasTotal": "0xaa87bee538000",
|
||||
"tokenBalance": null,
|
||||
"from": {
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||
|
@ -116,7 +116,7 @@
|
||||
"send": {
|
||||
"gasLimit": "0xea60",
|
||||
"gasPrice": "0xba43b7400",
|
||||
"gasTotal": "0xb451dc41b578",
|
||||
"gasTotal": "0xaa87bee538000",
|
||||
"tokenBalance": null,
|
||||
"from": {
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||
|
@ -29,9 +29,16 @@ function TransactionListItem () {
|
||||
}
|
||||
|
||||
TransactionListItem.prototype.showRetryButton = function () {
|
||||
const { transaction = {} } = this.props
|
||||
const { status, time } = transaction
|
||||
return status === 'submitted' && Date.now() - time > 30000
|
||||
const { transaction = {}, transactions } = this.props
|
||||
const { status, submittedTime, txParams } = transaction
|
||||
const currentNonce = txParams.nonce
|
||||
const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce)
|
||||
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
||||
const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0]
|
||||
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce
|
||||
&& lastSubmittedTxWithCurrentNonce.id === transaction.id
|
||||
|
||||
return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000
|
||||
}
|
||||
|
||||
TransactionListItem.prototype.render = function () {
|
||||
@ -201,6 +208,11 @@ function formatDate (date) {
|
||||
function renderErrorOrWarning (transaction) {
|
||||
const { status, err, warning } = transaction
|
||||
|
||||
// show dropped
|
||||
if (status === 'dropped') {
|
||||
return h('span.dropped', ' (Dropped)')
|
||||
}
|
||||
|
||||
// show rejected
|
||||
if (status === 'rejected') {
|
||||
return h('span.error', ' (Rejected)')
|
||||
|
@ -62,7 +62,7 @@ TransactionList.prototype.render = function () {
|
||||
}
|
||||
return h(TransactionListItem, {
|
||||
transaction, i, network, key,
|
||||
conversionRate,
|
||||
conversionRate, transactions,
|
||||
showTx: (txId) => {
|
||||
this.props.viewPendingTx(txId)
|
||||
},
|
||||
|
@ -247,6 +247,10 @@ app sections
|
||||
color: #FFAE00;
|
||||
}
|
||||
|
||||
.dropped {
|
||||
color: #6195ED;
|
||||
}
|
||||
|
||||
.lock {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -93,7 +93,7 @@ async function runSendFlowTest(assert, done) {
|
||||
'send gas field should show estimated gas total converted to USD'
|
||||
)
|
||||
|
||||
const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container')
|
||||
const sendGasOpenCustomizeModalButton = await queryAsync($, '.sliders-icon-container')
|
||||
sendGasOpenCustomizeModalButton[0].click()
|
||||
|
||||
const customizeGasModal = await queryAsync($, '.send-v2__customize-gas')
|
||||
@ -135,9 +135,9 @@ async function runSendFlowTest(assert, done) {
|
||||
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
||||
|
||||
const confirmScreenRows = await queryAsync($, '.confirm-screen-rows')
|
||||
const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2]
|
||||
assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas')
|
||||
const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3]
|
||||
const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0]
|
||||
assert.equal(confirmScreenGas.textContent, '3.60 USD', 'confirm screen should show correct gas')
|
||||
const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2]
|
||||
assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total')
|
||||
|
||||
const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button')
|
||||
|
@ -392,6 +392,49 @@ describe('Transaction Controller', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#retryTransaction', function () {
|
||||
it('should create a new txMeta with the same txParams as the original one', function (done) {
|
||||
let txParams = {
|
||||
nonce: '0x00',
|
||||
from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
||||
to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
||||
data: '0x0',
|
||||
}
|
||||
txController.txStateManager._saveTxList([
|
||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams },
|
||||
])
|
||||
txController.retryTransaction(1)
|
||||
.then((txMeta) => {
|
||||
assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same')
|
||||
assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same')
|
||||
assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same')
|
||||
assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same')
|
||||
assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`')
|
||||
assert.equal(txController.txStateManager.getTxList().length, 2)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_markNonceDuplicatesDropped', function () {
|
||||
it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () {
|
||||
txController.txStateManager._saveTxList([
|
||||
{ id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
{ id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } },
|
||||
])
|
||||
txController._markNonceDuplicatesDropped(1)
|
||||
const confirmedTx = txController.txStateManager.getTx(1)
|
||||
const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' })
|
||||
assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed')
|
||||
assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs')
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPendingTransactions', function () {
|
||||
beforeEach(function () {
|
||||
@ -401,7 +444,7 @@ describe('Transaction Controller', function () {
|
||||
{ id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
{ id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
{ id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
{ id: 6, status: 'confimed', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
{ id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
{ id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} },
|
||||
])
|
||||
})
|
||||
|
@ -1278,8 +1278,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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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')]),
|
||||
]),
|
||||
|
||||
|
@ -8,7 +8,12 @@ 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 { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
|
||||
@ -44,6 +49,7 @@ function mapDispatchToProps (dispatch) {
|
||||
to,
|
||||
value: amount,
|
||||
} = txParams
|
||||
|
||||
dispatch(actions.updateSend({
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
@ -56,6 +62,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 +169,7 @@ ConfirmSendEther.prototype.getGasFee = function () {
|
||||
return {
|
||||
FIAT,
|
||||
ETH,
|
||||
gasFeeInHex: txFeeBn.toString(16),
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +177,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 +205,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 +232,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
|
||||
@ -218,11 +261,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('.page-container__content', [
|
||||
h('div.flex-row.flex-center.confirm-screen-identicons', [
|
||||
@ -286,13 +329,15 @@ ConfirmSendEther.prototype.render = function () {
|
||||
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(GasFeeDisplay, {
|
||||
gasTotal: gasTotal || gasFeeInHex,
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
|
||||
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') + ' ' ]),
|
||||
@ -450,6 +495,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
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -89,6 +90,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' }))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +222,7 @@ ConfirmSendToken.prototype.getGasFee = function () {
|
||||
token: tokenExchangeRate
|
||||
? tokenGas
|
||||
: null,
|
||||
gasFeeInHex: gasTotal.toString(16),
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,19 +275,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),
|
||||
}),
|
||||
]),
|
||||
])
|
||||
)
|
||||
@ -308,16 +349,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', [
|
||||
@ -441,6 +487,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
|
||||
}
|
||||
|
@ -36,11 +36,11 @@ GasFeeDisplay.prototype.render = function () {
|
||||
? 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 && !gasLoadingError,
|
||||
}, [
|
||||
h('i.fa.fa-sliders.send-v2__sliders-icon'),
|
||||
h('i.fa.fa-sliders.sliders-icon'),
|
||||
]),
|
||||
|
||||
])
|
||||
|
@ -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,6 +262,7 @@ 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,
|
||||
@ -241,6 +280,23 @@ 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
|
||||
])
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -660,6 +660,7 @@
|
||||
|
||||
&__gas-fee-display {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.currency-display--message {
|
||||
padding: 8px 38px 8px 10px;
|
||||
@ -891,3 +892,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;
|
||||
}
|
@ -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;
|
||||
@ -190,6 +237,10 @@
|
||||
.tx-list-status--failed {
|
||||
color: $monzo;
|
||||
}
|
||||
|
||||
.tx-list-status--dropped {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-list-item {
|
||||
|
@ -46,6 +46,7 @@ $manatee: #93949d;
|
||||
$spindle: #c7ddec;
|
||||
$mid-gray: #5b5d67;
|
||||
$cape-cod: #38393a;
|
||||
$onahau: #d1edff;
|
||||
$java: #29b6af;
|
||||
$wild-strawberry: #ff4a8d;
|
||||
$cornflower-blue: #7057ff;
|
||||
|
@ -38,6 +38,7 @@ function reduceMetamask (state, action) {
|
||||
errors: {},
|
||||
maxModeOn: false,
|
||||
editingTransactionId: null,
|
||||
forceGasMin: null,
|
||||
},
|
||||
coinOptions: {},
|
||||
useBlockie: false,
|
||||
@ -297,6 +298,7 @@ function reduceMetamask (state, action) {
|
||||
memo: '',
|
||||
errors: {},
|
||||
editingTransactionId: null,
|
||||
forceGasMin: null,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user