1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 11:01:41 +01:00
metamask-extension/ui/app/components/tx-list-item.js

356 lines
9.7 KiB
JavaScript
Raw Normal View History

2017-08-30 12:33:00 +02:00
const Component = require('react').Component
const PropTypes = require('prop-types')
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
2017-08-30 12:33:00 +02:00
const h = require('react-hyperscript')
const connect = require('react-redux').connect
2017-08-30 12:33:00 +02:00
const inherits = require('util').inherits
const classnames = require('classnames')
2017-09-14 10:09:57 +02:00
const abi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(abi)
2017-08-30 12:33:00 +02:00
const Identicon = require('./identicon')
const contractMap = require('eth-contract-metadata')
const { checksumAddress } = require('../util')
2017-08-30 12:33:00 +02:00
const actions = require('../actions')
const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
const { calcTokenAmount } = require('../token-util')
const { getCurrentCurrency } = require('../selectors')
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
TxListItem.contextTypes = {
t: PropTypes.func,
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxListItem)
2017-09-14 10:09:57 +02:00
function mapStateToProps (state) {
return {
tokens: state.metamask.tokens,
currentCurrency: getCurrentCurrency(state),
contractExchangeRates: state.metamask.contractExchangeRates,
selectedAddressTxList: state.metamask.selectedAddressTxList,
}
}
function mapDispatchToProps (dispatch) {
return {
2018-03-14 03:14:05 +01:00
setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)),
retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
2017-09-14 10:09:57 +02:00
}
}
2017-08-30 12:33:00 +02:00
inherits(TxListItem, Component)
function TxListItem () {
Component.call(this)
this.state = {
total: null,
fiatTotal: null,
2018-03-14 03:14:05 +01:00
isTokenTx: null,
}
this.unmounted = false
}
TxListItem.prototype.componentDidMount = async function () {
const { txParams = {} } = this.props
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { name: txDataName } = decodedData || {}
2018-03-14 03:14:05 +01:00
const isTokenTx = txDataName === 'transfer'
2018-03-14 03:14:05 +01:00
const { total, fiatTotal } = isTokenTx
? await this.getSendTokenTotal()
: this.getSendEtherTotal()
if (this.unmounted) {
return
}
2018-03-14 03:26:45 +01:00
this.setState({ total, fiatTotal, isTokenTx })
2017-08-30 12:33:00 +02:00
}
TxListItem.prototype.componentWillUnmount = function () {
this.unmounted = true
}
2017-09-14 10:09:57 +02:00
TxListItem.prototype.getAddressText = function () {
const {
address,
txParams = {},
isMsg,
2017-09-14 10:09:57 +02:00
} = this.props
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { name: txDataName, params = [] } = decodedData || {}
const { value } = params[0] || {}
const checksummedAddress = checksumAddress(address)
const checksummedValue = checksumAddress(value)
2017-09-14 10:09:57 +02:00
let addressText
if (txDataName === 'transfer' || address) {
const addressToRender = txDataName === 'transfer' ? checksummedValue : checksummedAddress
addressText = `${addressToRender.slice(0, 10)}...${addressToRender.slice(-4)}`
} else if (isMsg) {
addressText = this.context.t('sigRequest')
} else {
addressText = this.context.t('contractDeployment')
2017-09-14 10:09:57 +02:00
}
return addressText
2017-08-30 12:33:00 +02:00
}
2017-09-14 10:09:57 +02:00
TxListItem.prototype.getSendEtherTotal = function () {
2017-08-30 12:33:00 +02:00
const {
transactionAmount,
conversionRate,
2017-09-14 10:09:57 +02:00
address,
currentCurrency,
2017-08-30 12:33:00 +02:00
} = this.props
2017-09-14 10:09:57 +02:00
if (!address) {
return {}
}
const totalInFiat = conversionUtil(transactionAmount, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromCurrency: 'ETH',
toCurrency: currentCurrency,
fromDenomination: 'WEI',
numberOfDecimals: 2,
conversionRate,
})
const totalInETH = conversionUtil(transactionAmount, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromCurrency: 'ETH',
toCurrency: 'ETH',
fromDenomination: 'WEI',
conversionRate,
numberOfDecimals: 6,
})
2017-09-14 10:09:57 +02:00
return {
total: `${totalInETH} ETH`,
fiatTotal: `${totalInFiat} ${currentCurrency.toUpperCase()}`,
2017-09-14 10:09:57 +02:00
}
}
TxListItem.prototype.getTokenInfo = async function () {
const { txParams = {}, tokenInfoGetter, tokens } = this.props
const toAddress = txParams.to
let decimals
let symbol
({ decimals, symbol } = tokens.filter(({ address }) => address === toAddress)[0] || {})
if (!decimals && !symbol) {
({ decimals, symbol } = contractMap[toAddress] || {})
}
if (!decimals && !symbol) {
({ decimals, symbol } = await tokenInfoGetter(toAddress))
}
return { decimals, symbol, address: toAddress }
}
TxListItem.prototype.getSendTokenTotal = async function () {
2017-09-14 10:09:57 +02:00
const {
txParams = {},
conversionRate,
contractExchangeRates,
currentCurrency,
2017-09-14 10:09:57 +02:00
} = this.props
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { params = [] } = decodedData || {}
const { value } = params[1] || {}
const { decimals, symbol, address } = await this.getTokenInfo()
const total = calcTokenAmount(value, decimals)
2017-09-14 10:09:57 +02:00
let tokenToFiatRate
let totalInFiat
if (contractExchangeRates[address]) {
tokenToFiatRate = multiplyCurrencies(
contractExchangeRates[address],
conversionRate
)
totalInFiat = conversionUtil(total, {
fromNumericBase: 'dec',
toNumericBase: 'dec',
fromCurrency: symbol,
toCurrency: currentCurrency,
numberOfDecimals: 2,
conversionRate: tokenToFiatRate,
})
}
const showFiat = Boolean(totalInFiat) && currentCurrency.toUpperCase() !== symbol
2017-09-14 10:09:57 +02:00
return {
total: `${total} ${symbol}`,
fiatTotal: showFiat && `${totalInFiat} ${currentCurrency.toUpperCase()}`,
2017-09-14 10:09:57 +02:00
}
}
TxListItem.prototype.showRetryButton = function () {
const {
transactionSubmittedTime,
selectedAddressTxList,
transactionId,
txParams,
} = this.props
if (!txParams) {
return false
}
const currentNonce = txParams.nonce
const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
2018-03-14 02:59:47 +01:00
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
const currentSubmittedTxs = selectedAddressTxList.filter(tx => tx.status === 'submitted')
2018-03-14 01:02:22 +01:00
const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1]
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
lastSubmittedTxWithCurrentNonce.id === transactionId
const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => {
if (tx1.id < tx2.id) return tx1
return tx2
})
const currentTxIsLatest = lastTx.id === transactionId
return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 && currentTxIsLatest
}
2018-03-14 03:14:05 +01:00
TxListItem.prototype.setSelectedToken = function (tokenAddress) {
this.props.setSelectedToken(tokenAddress)
}
TxListItem.prototype.resubmit = function () {
const { transactionId } = this.props
this.props.retryTransaction(transactionId)
.then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
}
2017-09-14 10:09:57 +02:00
TxListItem.prototype.render = function () {
const {
transactionStatus,
onClick,
transactionId,
2017-09-14 10:09:57 +02:00
dateString,
address,
className,
2018-03-14 03:14:05 +01:00
txParams,
2017-09-14 10:09:57 +02:00
} = this.props
2018-03-14 03:14:05 +01:00
const { total, fiatTotal, isTokenTx } = this.state
2017-09-14 10:09:57 +02:00
2017-08-30 12:33:00 +02:00
return h(`div${className || ''}`, {
key: transactionId,
onClick: () => onClick && onClick(transactionId),
2017-08-30 12:33:00 +02:00
}, [
h(`div.flex-column.tx-list-item-wrapper`, {}, [
h('div.tx-list-date-wrapper', {
style: {},
}, [
h('span.tx-list-date', {}, [
dateString,
]),
]),
h('div.flex-row.tx-list-content-wrapper', {
style: {},
}, [
h('div.tx-list-identicon-wrapper', {
style: {},
}, [
h(Identicon, {
address,
diameter: 28,
}),
]),
h('div.tx-list-account-and-status-wrapper', {}, [
h('div.tx-list-account-wrapper', {
style: {},
}, [
h('span.tx-list-account', {}, [
this.getAddressText(address),
]),
]),
h('div.tx-list-status-wrapper', {
style: {},
}, [
h('span', {
className: classnames('tx-list-status', {
2017-09-14 10:09:57 +02:00
'tx-list-status--rejected': transactionStatus === 'rejected',
'tx-list-status--failed': transactionStatus === 'failed',
2018-03-14 00:41:50 +01:00
'tx-list-status--dropped': transactionStatus === 'dropped',
2017-09-14 10:09:57 +02:00
}),
},
2018-03-20 20:06:59 +01:00
this.txStatusIndicator(),
),
2017-08-30 12:33:00 +02:00
]),
]),
h('div.flex-column.tx-list-details-wrapper', {
style: {},
}, [
h('span.tx-list-value', total),
2017-09-14 10:09:57 +02:00
fiatTotal && h('span.tx-list-fiat-value', fiatTotal),
2017-08-30 12:33:00 +02:00
]),
]),
this.showRetryButton() && h('.tx-list-item-retry-container', {
onClick: (event) => {
event.stopPropagation()
if (isTokenTx) {
this.setSelectedToken(txParams.to)
}
this.resubmit()
},
}, [
h('span', 'Taking too long? Increase the gas price on your transaction'),
]),
2017-11-02 13:15:59 +01:00
]), // holding on icon from design
2017-08-30 12:33:00 +02:00
])
}
2018-03-20 20:06:59 +01:00
TxListItem.prototype.txStatusIndicator = function () {
const { transactionStatus } = this.props
let name
if (transactionStatus === 'unapproved') {
name = this.context.t('unapproved')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'rejected') {
name = this.context.t('rejected')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'approved') {
name = this.context.t('approved')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'signed') {
name = this.context.t('signed')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'submitted') {
name = this.context.t('submitted')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'confirmed') {
name = this.context.t('confirmed')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'failed') {
name = this.context.t('failed')
2018-03-20 20:06:59 +01:00
} else if (transactionStatus === 'dropped') {
name = this.context.t('dropped')
2018-03-20 20:06:59 +01:00
}
return name
}