1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Add fallback when no function found, fix network colors, add fiat values for tokens with contract exchange rates

This commit is contained in:
Alexander Tseung 2018-07-14 13:47:07 -07:00
parent e9a8c24cc4
commit d19c42fcae
17 changed files with 322 additions and 104 deletions

View File

@ -581,6 +581,9 @@
"noAddressForName": {
"message": "No address has been set for this name."
},
"noConversionRate": {
"message": "No Conversion Rate"
},
"noDeposits": {
"message": "No deposits received"
},
@ -593,6 +596,9 @@
"noTransactions": {
"message": "No Transactions"
},
"notFound": {
"message": "Not Found"
},
"notStarted": {
"message": "Not Started"
},
@ -972,6 +978,9 @@
"unknown": {
"message": "Unknown"
},
"unknownFunction": {
"message": "Unknown Function"
},
"unknownNetwork": {
"message": "Unknown Private Network"
},

View File

@ -5,10 +5,10 @@ import classnames from 'classnames'
const ConfirmDetailRow = props => {
const {
label,
fiatFee,
ethFee,
fiatText,
ethText,
onHeaderClick,
fiatFeeColor,
fiatTextColor,
headerText,
headerTextClassName,
} = props
@ -27,12 +27,12 @@ const ConfirmDetailRow = props => {
</div>
<div
className="confirm-detail-row__fiat"
style={{ color: fiatFeeColor }}
style={{ color: fiatTextColor }}
>
{ fiatFee }
{ fiatText }
</div>
<div className="confirm-detail-row__eth">
{ `\u2666 ${ethFee}` }
{ ethText }
</div>
</div>
</div>
@ -41,9 +41,9 @@ const ConfirmDetailRow = props => {
ConfirmDetailRow.propTypes = {
label: PropTypes.string,
fiatFee: PropTypes.string,
ethFee: PropTypes.string,
fiatFeeColor: PropTypes.string,
fiatText: PropTypes.string,
ethText: PropTypes.string,
fiatTextColor: PropTypes.string,
onHeaderClick: PropTypes.func,
headerText: PropTypes.string,
headerTextClassName: PropTypes.string,

View File

@ -9,7 +9,7 @@
height: 25px;
&--mainnet {
background-color: lighten($blue-lagoon, 45%);
background-color: lighten($blue-lagoon, 68%);
}
&--ropsten {
@ -17,11 +17,11 @@
}
&--kovan {
background-color: lighten($purple, 45%);
background-color: lighten($purple, 65%);
}
&--rinkeby {
background-color: lighten($tulip-tree, 45%);
background-color: lighten($tulip-tree, 35%);
}
}

View File

@ -1,29 +1,18 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base'
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
export default class ConfirmApprove extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
tokenAddress: PropTypes.string,
toAddress: PropTypes.string,
tokenAmount: PropTypes.string,
tokenSymbol: PropTypes.string,
tokenAmount: PropTypes.number,
}
render () {
const { toAddress, tokenAddress, tokenAmount, tokenSymbol } = this.props
const { tokenAmount } = this.props
return (
<ConfirmTransactionBase
toAddress={toAddress}
identiconAddress={tokenAddress}
title={`${tokenAmount} ${tokenSymbol}`}
warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`}
hideSubtitle
<ConfirmTokenTransactionBase
tokenAmount={tokenAmount}
/>
)
}

View File

@ -1,27 +1,12 @@
import { connect } from 'react-redux'
import ConfirmApprove from './confirm-approve.component'
import { approveTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
const mapStateToProps = state => {
const { confirmTransaction } = state
const {
tokenData = {},
txData: { txParams: { to: tokenAddress } = {} } = {},
tokenProps: { tokenSymbol } = {},
} = confirmTransaction
const { params = [] } = tokenData
let toAddress = ''
let tokenAmount = ''
if (params && params.length === 2) {
[{ value: toAddress }, { value: tokenAmount }] = params
}
const { tokenAmount } = approveTokenAmountAndToAddressSelector(state)
return {
toAddress,
tokenAddress,
tokenAmount,
tokenSymbol,
}
}

View File

@ -1,20 +1,13 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base'
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
import { SEND_ROUTE } from '../../../routes'
export default class ConfirmSendToken extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
history: PropTypes.object,
tokenAddress: PropTypes.string,
toAddress: PropTypes.string,
numberOfTokens: PropTypes.number,
tokenSymbol: PropTypes.string,
editTransaction: PropTypes.func,
tokenAmount: PropTypes.number,
}
handleEdit (confirmTransactionData) {
@ -24,15 +17,12 @@ export default class ConfirmSendToken extends Component {
}
render () {
const { toAddress, tokenAddress, tokenSymbol, numberOfTokens } = this.props
const { tokenAmount } = this.props
return (
<ConfirmTransactionBase
toAddress={toAddress}
identiconAddress={tokenAddress}
title={`${numberOfTokens} ${tokenSymbol}`}
<ConfirmTokenTransactionBase
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
hideSubtitle
tokenAmount={tokenAmount}
/>
)
}

View File

@ -2,36 +2,16 @@ import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import ConfirmSendToken from './confirm-send-token.component'
import { calcTokenAmount } from '../../../token-util'
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
import { setSelectedToken, updateSend, showSendTokenPage } from '../../../actions'
import { conversionUtil } from '../../../conversion-util'
import { sendTokenTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
const mapStateToProps = state => {
const { confirmTransaction } = state
const {
tokenData = {},
tokenProps: { tokenSymbol, tokenDecimals } = {},
txData: { txParams: { to: tokenAddress } = {} } = {},
} = confirmTransaction
const { params = [] } = tokenData
let toAddress = ''
let tokenAmount = ''
if (params && params.length === 2) {
[{ value: toAddress }, { value: tokenAmount }] = params
}
const numberOfTokens = tokenAmount && tokenDecimals
? calcTokenAmount(tokenAmount, tokenDecimals)
: 0
const { tokenAmount } = sendTokenTokenAmountAndToAddressSelector(state)
return {
toAddress,
tokenAddress,
tokenSymbol,
numberOfTokens,
tokenAmount,
}
}

View File

@ -0,0 +1,85 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base'
import {
formatCurrency,
convertTokenToFiat,
addFiat,
} from '../../../helpers/confirm-transaction/util'
export default class ConfirmTokenTransactionBase extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
tokenAddress: PropTypes.string,
toAddress: PropTypes.string,
tokenAmount: PropTypes.number,
tokenSymbol: PropTypes.string,
fiatTransactionTotal: PropTypes.string,
ethTransactionTotal: PropTypes.string,
contractExchangeRate: PropTypes.number,
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
}
getFiatTransactionAmount () {
const { tokenAmount, currentCurrency, conversionRate, contractExchangeRate } = this.props
return convertTokenToFiat({
value: tokenAmount,
toCurrency: currentCurrency,
conversionRate,
contractExchangeRate,
})
}
getSubtitle () {
const { currentCurrency, contractExchangeRate } = this.props
if (typeof contractExchangeRate === 'undefined') {
return this.context.t('noConversionRate')
} else {
const fiatTransactionAmount = this.getFiatTransactionAmount()
return formatCurrency(fiatTransactionAmount, currentCurrency)
}
}
getFiatTotalTextOverride () {
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
if (typeof contractExchangeRate === 'undefined') {
return formatCurrency(fiatTransactionTotal, currentCurrency)
} else {
const fiatTransactionAmount = this.getFiatTransactionAmount()
const fiatTotal = addFiat(fiatTransactionAmount, fiatTransactionTotal)
return formatCurrency(fiatTotal, currentCurrency)
}
}
render () {
const {
toAddress,
tokenAddress,
tokenSymbol,
tokenAmount,
ethTransactionTotal,
...restProps
} = this.props
const tokensText = `${tokenAmount} ${tokenSymbol}`
return (
<ConfirmTransactionBase
toAddress={toAddress}
identiconAddress={tokenAddress}
title={tokensText}
subtitle={this.getSubtitle()}
ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`}
fiatTotalTextOverride={this.getFiatTotalTextOverride()}
{...restProps}
/>
)
}
}

View File

@ -0,0 +1,34 @@
import { connect } from 'react-redux'
import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'
import {
tokenAmountAndToAddressSelector,
contractExchangeRateSelector,
} from '../../../selectors/confirm-transaction'
const mapStateToProps = (state, ownProps) => {
const { tokenAmount: ownTokenAmount } = ownProps
const { confirmTransaction, metamask: { currentCurrency, conversionRate } } = state
const {
txData: { txParams: { to: tokenAddress } = {} } = {},
tokenProps: { tokenSymbol } = {},
fiatTransactionTotal,
ethTransactionTotal,
} = confirmTransaction
const { tokenAmount, toAddress } = tokenAmountAndToAddressSelector(state)
const contractExchangeRate = contractExchangeRateSelector(state)
return {
toAddress,
tokenAddress,
tokenAmount: typeof ownTokenAmount !== 'undefined' ? ownTokenAmount : tokenAmount,
tokenSymbol,
currentCurrency,
conversionRate,
contractExchangeRate,
fiatTransactionTotal,
ethTransactionTotal,
}
}
export default connect(mapStateToProps)(ConfirmTokenTransactionBase)

View File

@ -0,0 +1,2 @@
export { default } from './confirm-token-transaction-base.container'
export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component'

View File

@ -54,6 +54,8 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
ethTotalTextOverride: PropTypes.string,
fiatTotalTextOverride: PropTypes.string,
hideData: PropTypes.bool,
hideDetails: PropTypes.bool,
hideSubtitle: PropTypes.bool,
@ -146,6 +148,8 @@ export default class ConfirmTransactionBase extends Component {
currentCurrency,
fiatTransactionTotal,
ethTransactionTotal,
fiatTotalTextOverride,
ethTotalTextOverride,
hideDetails,
} = this.props
@ -153,14 +157,16 @@ export default class ConfirmTransactionBase extends Component {
return null
}
const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
return (
detailsComponent || (
<div className="confirm-page-container-content__details">
<div className="confirm-page-container-content__gas-fee">
<ConfirmDetailRow
label="Gas Fee"
fiatFee={formatCurrency(fiatTransactionFee, currentCurrency)}
ethFee={ethTransactionFee}
fiatText={formatCurrency(fiatTransactionFee, currentCurrency)}
ethText={`\u2666 ${ethTransactionFee}`}
headerText="Edit"
headerTextClassName="confirm-detail-row__header-text--edit"
onHeaderClick={() => this.handleEditGas()}
@ -169,11 +175,11 @@ export default class ConfirmTransactionBase extends Component {
<div>
<ConfirmDetailRow
label="Total"
fiatFee={formatCurrency(fiatTransactionTotal, currentCurrency)}
ethFee={ethTransactionTotal}
fiatText={fiatTotalTextOverride || formattedCurrency}
ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`}
headerText="Amount + Gas Fee"
headerTextClassName="confirm-detail-row__header-text--total"
fiatFeeColor="#2f9ae0"
fiatTextColor="#2f9ae0"
/>
</div>
</div>
@ -206,17 +212,21 @@ export default class ConfirmTransactionBase extends Component {
<div className="confirm-page-container-content__data-box-label">
{`${t('functionType')}:`}
<span className="confirm-page-container-content__function-type">
{ name }
{ name || t('notFound') }
</span>
</div>
<div className="confirm-page-container-content__data-box">
<div className="confirm-page-container-content__data-field-label">
{ `${t('parameters')}:` }
</div>
<div>
<pre>{ JSON.stringify(params, null, 2) }</pre>
</div>
</div>
{
params && (
<div className="confirm-page-container-content__data-box">
<div className="confirm-page-container-content__data-field-label">
{ `${t('parameters')}:` }
</div>
<div>
<pre>{ JSON.stringify(params, null, 2) }</pre>
</div>
</div>
)
}
<div className="confirm-page-container-content__data-box-label">
{`${t('hexData')}:`}
</div>
@ -297,7 +307,7 @@ export default class ConfirmTransactionBase extends Component {
toName={toName}
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
action={action || name}
action={action || name || this.context.t('unknownFunction')}
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
hideSubtitle={hideSubtitle}

View File

@ -8,11 +8,16 @@ import {
CONFIRM_SEND_ETHER_PATH,
CONFIRM_SEND_TOKEN_PATH,
CONFIRM_APPROVE_PATH,
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
} from '../../../routes'
import { isConfirmDeployContract } from './confirm-transaction-switch.util'
import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE } from './confirm-transaction-switch.constants'
import {
TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER_FROM,
} from './confirm-transaction-switch.constants'
export default class ConfirmTransactionSwitch extends Component {
static propTypes = {
@ -27,8 +32,7 @@ export default class ConfirmTransactionSwitch extends Component {
methodData: { name },
fetchingMethodData,
} = this.props
const { id } = txData
const { id, txParams: { data } = {} } = txData
if (isConfirmDeployContract(txData)) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
@ -39,10 +43,10 @@ export default class ConfirmTransactionSwitch extends Component {
return <Loading />
}
if (name) {
const methodName = name.toLowerCase()
if (data) {
const methodName = name && name.toLowerCase()
switch (methodName.toLowerCase()) {
switch (methodName) {
case TOKEN_METHOD_TRANSFER: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`
return <Redirect to={{ pathname }} />
@ -51,6 +55,10 @@ export default class ConfirmTransactionSwitch extends Component {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`
return <Redirect to={{ pathname }} />
}
case TOKEN_METHOD_TRANSFER_FROM: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`
return <Redirect to={{ pathname }} />
}
default: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`
return <Redirect to={{ pathname }} />

View File

@ -1,2 +1,3 @@
export const TOKEN_METHOD_TRANSFER = 'transfer'
export const TOKEN_METHOD_APPROVE = 'approve'
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'

View File

@ -8,6 +8,7 @@ import ConfirmSendEther from '../confirm-send-ether'
import ConfirmSendToken from '../confirm-send-token'
import ConfirmDeployContract from '../confirm-deploy-contract'
import ConfirmApprove from '../confirm-approve'
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
import ConfTx from '../../../conf-tx'
import {
DEFAULT_ROUTE,
@ -16,6 +17,7 @@ import {
CONFIRM_SEND_ETHER_PATH,
CONFIRM_SEND_TOKEN_PATH,
CONFIRM_APPROVE_PATH,
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
} from '../../../routes'
@ -137,6 +139,11 @@ export default class ConfirmTransaction extends Component {
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`}
component={ConfirmApprove}
/>
<Route
exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`}
component={ConfirmTokenTransactionBase}
/>
<Route
exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`}

View File

@ -114,3 +114,20 @@ export function formatCurrency (value, currencyCode) {
? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode })
: value
}
export function convertTokenToFiat ({
value,
toCurrency,
conversionRate,
contractExchangeRate,
}) {
const totalExchangeRate = conversionRate * contractExchangeRate
return conversionUtil(value, {
fromNumericBase: 'dec',
toNumericBase: 'dec',
toCurrency,
numberOfDecimals: 2,
conversionRate: totalExchangeRate,
})
}

View File

@ -26,6 +26,7 @@ const CONFIRM_SEND_ETHER_PATH = '/send-ether'
const CONFIRM_SEND_TOKEN_PATH = '/send-token'
const CONFIRM_DEPLOY_CONTRACT_PATH = '/deploy-contract'
const CONFIRM_APPROVE_PATH = '/approve'
const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from'
const CONFIRM_TOKEN_METHOD_PATH = '/token-method'
const SIGNATURE_REQUEST_PATH = '/signature-request'
@ -57,6 +58,7 @@ module.exports = {
CONFIRM_SEND_TOKEN_PATH,
CONFIRM_DEPLOY_CONTRACT_PATH,
CONFIRM_APPROVE_PATH,
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
}

View File

@ -1,5 +1,6 @@
import { createSelector } from 'reselect'
import txHelper from '../../lib/tx-helper'
import { calcTokenAmount } from '../token-util'
const unapprovedTxsSelector = state => state.metamask.unapprovedTxs
const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
@ -63,3 +64,101 @@ export const unconfirmedTransactionsHashSelector = createSelector(
export const currentCurrencySelector = state => state.metamask.currentCurrency
export const conversionRateSelector = state => state.metamask.conversionRate
const txDataSelector = state => state.confirmTransaction.txData
const tokenDataSelector = state => state.confirmTransaction.tokenData
const tokenPropsSelector = state => state.confirmTransaction.tokenProps
const contractExchangeRatesSelector = state => state.metamask.contractExchangeRates
const tokenDecimalsSelector = createSelector(
tokenPropsSelector,
tokenProps => tokenProps && tokenProps.tokenDecimals
)
const tokenDataParamsSelector = createSelector(
tokenDataSelector,
tokenData => tokenData && tokenData.params || []
)
const txParamsSelector = createSelector(
txDataSelector,
txData => txData && txData.txParams || {}
)
export const tokenAddressSelector = createSelector(
txParamsSelector,
txParams => txParams && txParams.to
)
const TOKEN_PARAM_SPENDER = '_spender'
const TOKEN_PARAM_TO = '_to'
const TOKEN_PARAM_VALUE = '_value'
export const tokenAmountAndToAddressSelector = createSelector(
tokenDataParamsSelector,
params => {
let toAddress = ''
let tokenAmount = 0
if (params && params.length) {
const toParam = params.find(param => param.name === TOKEN_PARAM_TO)
const valueParam = params.find(param => param.name === TOKEN_PARAM_VALUE)
toAddress = toParam ? toParam.value : params[0].value
tokenAmount = valueParam ? +valueParam.value : +params[1].value
}
return {
toAddress,
tokenAmount,
}
}
)
export const approveTokenAmountAndToAddressSelector = createSelector(
tokenDataParamsSelector,
params => {
let toAddress = ''
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_SPENDER).value
tokenAmount = +params.find(param => param.name === TOKEN_PARAM_VALUE).value
}
return {
toAddress,
tokenAmount,
}
}
)
export const sendTokenTokenAmountAndToAddressSelector = createSelector(
tokenDataParamsSelector,
tokenDecimalsSelector,
(params, tokenDecimals) => {
let toAddress = ''
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_TO).value
tokenAmount = +params.find(param => param.name === TOKEN_PARAM_VALUE).value
if (tokenDecimals) {
tokenAmount = calcTokenAmount(tokenAmount, tokenDecimals)
}
}
return {
toAddress,
tokenAmount,
}
}
)
export const contractExchangeRateSelector = createSelector(
contractExchangeRatesSelector,
tokenAddressSelector,
(contractExchangeRates, tokenAddress) => contractExchangeRates[tokenAddress]
)