1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Further refactors; includes refactor of send-v2.js and associated container

This commit is contained in:
Dan 2018-04-27 16:33:00 -02:30
parent 33c16d1bf6
commit 26f965bcce
25 changed files with 489 additions and 156 deletions

View File

@ -2,7 +2,8 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const pump = require('pump') const pump = require('pump')
const browserify = require('browserify') const browserify = require('browserify')
const tests = fs.readdirSync(path.join(__dirname, 'lib')) let tests = fs.readdirSync(path.join(__dirname, 'lib'))
tests = tests.filter(fln => fln.match(/send/))
const bundlePath = path.join(__dirname, 'bundle.js') const bundlePath = path.join(__dirname, 'bundle.js')
const b = browserify() const b = browserify()

View File

@ -1,6 +1,11 @@
const abi = require('human-standard-token-abi') const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util') const { getTokenAddressFromTokenObject } = require('./util')
const {
calcGasTotal,
getParamsForGasEstimate,
calcTokenBalance,
} = require('./components/send_/send.utils')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper') const { fetchLocale } = require('../i18n-helper')
const log = require('loglevel') const log = require('loglevel')
@ -173,14 +178,16 @@ var actions = {
updateGasLimit, updateGasLimit,
updateGasPrice, updateGasPrice,
updateGasTotal, updateGasTotal,
setGasTotal,
setSendTokenBalance,
updateSendTokenBalance, updateSendTokenBalance,
updateSendFrom, updateSendFrom,
updateSendTo, updateSendTo,
updateSendAmount, updateSendAmount,
updateSendMemo, updateSendMemo,
updateSendErrors,
setMaxModeTo, setMaxModeTo,
updateSend, updateSend,
updateSendErrors,
clearSend, clearSend,
setSelectedAddress, setSelectedAddress,
// app messages // app messages
@ -716,14 +723,64 @@ function updateGasPrice (gasPrice) {
} }
} }
function updateGasTotal (gasTotal) { function setGasTotal (gasTotal) {
return { return {
type: actions.UPDATE_GAS_TOTAL, type: actions.UPDATE_GAS_TOTAL,
value: gasTotal, value: gasTotal,
} }
} }
function updateSendTokenBalance (tokenBalance) { function updateGasTotal ({ selectedAddress, selectedToken, data }) {
return (dispatch) => {
const { symbol } = selectedToken || {}
const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data)
return Promise.all([
dispatch(actions.getGasPrice()),
dispatch(actions.estimateGas(estimateGasParams)),
])
.then(([gasPrice, gas]) => {
const newGasTotal = calcGasTotal(gas, gasPrice)
dispatch(actions.setGasTotal(newGasTotal))
dispatch(updateSendErrors({ gasLoadingError: null }))
})
.catch(err => {
log.error(err)
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
})
}
}
function updateSendTokenBalance ({
selectedToken,
tokenContract,
address,
}) {
return (dispatch) => {
const tokenBalancePromise = tokenContract
? tokenContract.balanceOf(address)
: Promise.resolve()
return tokenBalancePromise
.then(usersToken => {
if (usersToken) {
const newTokenBalance = calcTokenBalance({ selectedToken, usersToken })
dispatch(setSendTokenBalance(newTokenBalance))
}
})
.catch(err => {
log.error(err)
updateSendErrors({ tokenBalance: 'tokenBalanceError' })
})
}
}
function updateSendErrors (errorObject) {
return {
type: actions.UPDATE_SEND_ERRORS,
value: errorObject,
}
}
function setSendTokenBalance (tokenBalance) {
return { return {
type: actions.UPDATE_SEND_TOKEN_BALANCE, type: actions.UPDATE_SEND_TOKEN_BALANCE,
value: tokenBalance, value: tokenBalance,
@ -758,13 +815,6 @@ function updateSendMemo (memo) {
} }
} }
function updateSendErrors (error) {
return {
type: actions.UPDATE_SEND_ERRORS,
value: error,
}
}
function setMaxModeTo (bool) { function setMaxModeTo (bool) {
return { return {
type: actions.UPDATE_MAX_MODE, type: actions.UPDATE_MAX_MODE,

View File

@ -11,7 +11,7 @@ const log = require('loglevel')
// init // init
const InitializeScreen = require('../../mascara/src/app/first-time').default const InitializeScreen = require('../../mascara/src/app/first-time').default
// accounts // accounts
const SendTransactionScreen2 = require('./components/send/send-v2-container') const SendTransactionScreen = require('./components/send_/send.container')
const ConfirmTxScreen = require('./conf-tx') const ConfirmTxScreen = require('./conf-tx')
// slideout menu // slideout menu
@ -84,7 +84,7 @@ class App extends Component {
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }), h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }), h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),

View File

@ -8,6 +8,10 @@ const GasModalCard = require('./gas-modal-card')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
import {
updateSendErrors,
} from '../../ducks/send'
const { const {
MIN_GAS_PRICE_DEC, MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC, MIN_GAS_LIMIT_DEC,
@ -63,9 +67,9 @@ function mapDispatchToProps (dispatch) {
hideModal: () => dispatch(actions.hideModal()), hideModal: () => dispatch(actions.hideModal()),
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)), updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendErrors: error => dispatch(actions.updateSendErrors(error)), updateSendErrors: error => dispatch(updateSendErrors(error)),
} }
} }

View File

@ -29,6 +29,10 @@ const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
import {
updateSendErrors,
} from '../../ducks/send'
ConfirmSendEther.contextTypes = { ConfirmSendEther.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
@ -105,7 +109,7 @@ function mapDispatchToProps (dispatch) {
})) }))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
}, },
updateSendErrors: error => dispatch(actions.updateSendErrors(error)), updateSendErrors: error => dispatch(updateSendErrors(error)),
} }
} }

View File

@ -39,6 +39,10 @@ const {
} = require('../../selectors') } = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
import {
updateSendErrors,
} from '../../ducks/send'
ConfirmSendToken.contextTypes = { ConfirmSendToken.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
@ -141,7 +145,7 @@ function mapDispatchToProps (dispatch, ownProps) {
})) }))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
}, },
updateSendErrors: error => dispatch(actions.updateSendErrors(error)), updateSendErrors: error => dispatch(updateSendErrors(error)),
} }
} }

View File

@ -18,6 +18,10 @@ const {
getSelectedTokenContract, getSelectedTokenContract,
} = require('../../selectors') } = require('../../selectors')
import {
updateSendErrors,
} from '../../ducks/send'
module.exports = compose( module.exports = compose(
withRouter, withRouter,
connect(mapStateToProps, mapDispatchToProps) connect(mapStateToProps, mapDispatchToProps)
@ -74,7 +78,8 @@ function mapDispatchToProps (dispatch) {
updateTx: txData => dispatch(actions.updateTransaction(txData)), updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)), addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)),
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), setGasTotal: newTotal => dispatch(actions.setGasTotal(newTotal)),
updateGasTotal: () => dispatch(actions.updateGasTotal()),
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)),
@ -82,7 +87,7 @@ function mapDispatchToProps (dispatch) {
updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)), updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)), updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), updateSendErrors: newError => dispatch(updateSendErrors(newError)),
clearSend: () => dispatch(actions.clearSend()), clearSend: () => dispatch(actions.clearSend()),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
} }

View File

@ -9,10 +9,12 @@ import { getMaxModeOn } from '../send-amount-row.selectors.js'
import { calcMaxAmount } from './amount-max-button.utils.js' import { calcMaxAmount } from './amount-max-button.utils.js'
import { import {
updateSendAmount, updateSendAmount,
updateSendErrors,
setMaxModeTo, setMaxModeTo,
} from '../../../actions' } from '../../../../../actions'
import AmountMaxButton from './amount-max-button.component' import AmountMaxButton from './amount-max-button.component'
import {
updateSendErrors,
} from '../../../../../ducks/send'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)

View File

@ -8,7 +8,10 @@ export default class SendAmountRow extends Component {
static propTypes = { static propTypes = {
amount: PropTypes.string, amount: PropTypes.string,
amountConversionRate: PropTypes.number, amountConversionRate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
balance: PropTypes.string, balance: PropTypes.string,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string, convertedCurrency: PropTypes.string,

View File

@ -1,24 +1,26 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { import {
getAmountConversionRate,
getConversionRate, getConversionRate,
getConvertedCurrency, getConvertedCurrency,
getGasTotal, getGasTotal,
getPrimaryCurrency,
getSelectedToken, getSelectedToken,
getSendAmount, getSendAmount,
getSendFromBalance, getSendFromBalance,
getTokenBalance, getTokenBalance,
} from '../../send.selectors.js' } from '../../send.selectors'
import { import {
getAmountConversionRate,
getPrimaryCurrency,
sendAmountIsInError, sendAmountIsInError,
} from './send-amount-row.selectors.js' } from './send-amount-row.selectors'
import { getAmountErrorObject } from './send-amount-row.utils.js' import { getAmountErrorObject } from '../../send.utils'
import { import {
setMaxModeTo, setMaxModeTo,
updateSendAmount, updateSendAmount,
updateSendErrors,
} from '../../../../actions' } from '../../../../actions'
import {
updateSendErrors,
} from '../../../../ducks/send'
import SendAmountRow from './send-amount-row.component' import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)

View File

@ -1,13 +1,5 @@
import {
getSelectedToken,
getSelectedTokenToFiatRate,
getConversionRate,
} from '../../send.selectors.js'
const selectors = { const selectors = {
getAmountConversionRate,
getMaxModeOn, getMaxModeOn,
getPrimaryCurrency,
sendAmountIsInError, sendAmountIsInError,
} }
@ -18,16 +10,5 @@ function getMaxModeOn (state) {
} }
function sendAmountIsInError (state) { function sendAmountIsInError (state) {
return Boolean(state.metamask.send.errors.amount) return Boolean(state.send.errors.amount)
}
function getPrimaryCurrency (state) {
const selectedToken = getSelectedToken(state)
return selectedToken && selectedToken.symbol
}
function getAmountConversionRate (state) {
return getSelectedToken(state)
? getSelectedTokenToFiatRate(state)
: getConversionRate(state)
} }

View File

@ -1,61 +0,0 @@
const {
conversionGreaterThan,
} = require('../../../../conversion-util')
const {
isBalanceSufficient,
isTokenBalanceSufficient,
} = require('../../send.utils')
function getAmountErrorObject ({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
}) {
let insufficientFunds = false
if (gasTotal && conversionRate) {
insufficientFunds = !isBalanceSufficient({
amount: selectedToken ? '0x0' : amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
})
}
let inSufficientTokens = false
if (selectedToken && tokenBalance !== null) {
const { decimals } = selectedToken
inSufficientTokens = !isTokenBalanceSufficient({
tokenBalance,
amount,
decimals,
})
}
const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' },
)
let amountError = null
if (insufficientFunds) {
amountError = 'insufficientFunds'
} else if (inSufficientTokens) {
amountError = 'insufficientTokens'
} else if (amountLessThanZero) {
amountError = 'negativeETH'
}
return { amount: amountError }
}
module.exports = {
getAmountErrorObject,
}

View File

@ -14,19 +14,19 @@ export default class SendFromRow extends Component {
openFromDropdown: PropTypes.func, openFromDropdown: PropTypes.func,
tokenContract: PropTypes.object, tokenContract: PropTypes.object,
updateSendFrom: PropTypes.func, updateSendFrom: PropTypes.func,
updateSendTokenBalance: PropTypes.func, setSendTokenBalance: PropTypes.func,
}; };
async handleFromChange (newFrom) { async handleFromChange (newFrom) {
const { const {
updateSendFrom, updateSendFrom,
tokenContract, tokenContract,
updateSendTokenBalance, setSendTokenBalance,
} = this.props } = this.props
if (tokenContract) { if (tokenContract) {
const usersToken = await tokenContract.balanceOf(newFrom.address) const usersToken = await tokenContract.balanceOf(newFrom.address)
updateSendTokenBalance(usersToken) setSendTokenBalance(usersToken)
} }
updateSendFrom(newFrom) updateSendFrom(newFrom)
} }

View File

@ -11,7 +11,7 @@ import {
import { calcTokenUpdateAmount } from './send-from-row.utils.js' import { calcTokenUpdateAmount } from './send-from-row.utils.js'
import { import {
updateSendFrom, updateSendFrom,
updateSendTokenBalance, setSendTokenBalance,
} from '../../../../actions' } from '../../../../actions'
import { import {
closeFromDropdown, closeFromDropdown,
@ -36,11 +36,11 @@ function mapDispatchToProps (dispatch) {
closeFromDropdown: () => dispatch(closeFromDropdown()), closeFromDropdown: () => dispatch(closeFromDropdown()),
openFromDropdown: () => dispatch(openFromDropdown()), openFromDropdown: () => dispatch(openFromDropdown()),
updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)), updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
updateSendTokenBalance: (usersToken, selectedToken) => { setSendTokenBalance: (usersToken, selectedToken) => {
if (!usersToken) return if (!usersToken) return
const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken) const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken)
dispatch(updateSendTokenBalance(tokenBalance)) dispatch(setSendTokenBalance(tokenBalance))
}, },
} }
} }

View File

@ -18,23 +18,8 @@ export default class SendGasRow extends Component {
showCustomizeGasModal: PropTypes.func, showCustomizeGasModal: PropTypes.func,
tokenContract: PropTypes.object, tokenContract: PropTypes.object,
updateSendFrom: PropTypes.func, updateSendFrom: PropTypes.func,
updateSendTokenBalance: PropTypes.func,
}; };
async handleFromChange (newFrom) {
const {
tokenContract,
updateSendFrom,
updateSendTokenBalance,
} = this.props
if (tokenContract) {
const usersToken = await tokenContract.balanceOf(newFrom.address)
updateSendTokenBalance(usersToken)
}
updateSendFrom(newFrom)
}
render () { render () {
const { const {
conversionRate, conversionRate,

View File

@ -10,6 +10,7 @@ export default class SendRowErrorMessage extends Component {
render () { render () {
const { errors, errorType } = this.props const { errors, errorType } = this.props
const errorMessage = errors[errorType] const errorMessage = errors[errorType]
return ( return (

View File

@ -10,10 +10,10 @@ import {
} from './send-to-row.selectors.js' } from './send-to-row.selectors.js'
import { getToErrorObject } from './send-to-row.utils.js' import { getToErrorObject } from './send-to-row.utils.js'
import { import {
updateSendErrors,
updateSendTo, updateSendTo,
} from '../../../../actions' } from '../../../../actions'
import { import {
updateSendErrors,
openToDropdown, openToDropdown,
closeToDropdown, closeToDropdown,
} from '../../../../ducks/send' } from '../../../../ducks/send'

View File

@ -0,0 +1,143 @@
import React from 'react'
import PropTypes from 'prop-types'
import PersistentForm from '../../../lib/persistent-form'
import {
getAmountErrorObject,
doesAmountErrorRequireUpdate,
} from './send.utils'
import PageContainer from '..//page-container/page-container.component'
import SendHeader from './send-header/send-header.container'
import SendContent from './send-content/send-content.component'
import SendFooter from './send-footer/send-footer.container'
export default class SendTransactionScreen extends PersistentForm {
static propTypes = {
amount: PropTypes.string,
amountConversionRate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
conversionRate: PropTypes.number,
data: PropTypes.string,
editingTransactionId: PropTypes.string,
from: PropTypes.object,
gasLimit: PropTypes.string,
gasPrice: PropTypes.string,
gasTotal: PropTypes.string,
history: PropTypes.object,
network: PropTypes.string,
primaryCurrency: PropTypes.string,
selectedAddress: PropTypes.string,
selectedToken: PropTypes.object,
tokenBalance: PropTypes.string,
tokenContract: PropTypes.object,
updateAndSetGasTotal: PropTypes.func,
updateSendErrors: PropTypes.func,
updateSendTokenBalance: PropTypes.func,
};
updateGas () {
const {
data,
editingTransactionId,
gasLimit,
gasPrice,
selectedAddress,
selectedToken = {},
updateAndSetGasTotal,
} = this.props
updateAndSetGasTotal({
data,
editingTransactionId,
gasLimit,
gasPrice,
selectedAddress,
selectedToken,
})
}
componentDidUpdate (prevProps) {
const {
amount,
amountConversionRate,
conversionRate,
from: { address, balance },
gasTotal,
network,
primaryCurrency,
selectedToken,
tokenBalance,
updateSendErrors,
updateSendTokenBalance,
tokenContract,
} = this.props
const {
from: { balance: prevBalance },
gasTotal: prevGasTotal,
tokenBalance: prevTokenBalance,
network: prevNetwork,
} = prevProps
const uninitialized = [prevBalance, prevGasTotal].every(n => n === null)
if (!uninitialized) {
const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({
balance,
gasTotal,
prevBalance,
prevGasTotal,
prevTokenBalance,
selectedToken,
tokenBalance,
})
if (amountErrorRequiresUpdate) {
const amountErrorObject = getAmountErrorObject({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
})
updateSendErrors(amountErrorObject)
}
if (network !== prevNetwork && network !== 'loading') {
updateSendTokenBalance({
selectedToken,
tokenContract,
address,
})
this.updateGas()
}
}
}
componentWillMount () {
this.updateGas()
}
render () {
const { history } = this.props
return (
<PageContainer>
<SendHeader/>
<SendContent/>
<SendFooter history={history}/>
</PageContainer>
)
}
}
SendTransactionScreen.contextTypes = {
t: PropTypes.func,
}

View File

@ -0,0 +1,88 @@
import { connect } from 'react-redux'
import abi from 'ethereumjs-abi'
import SendEther from './send.component'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import {
getAmountConversionRate,
getConversionRate,
getCurrentNetwork,
getGasLimit,
getGasPrice,
getGasTotal,
getPrimaryCurrency,
getSelectedAddress,
getSelectedToken,
getSelectedTokenContract,
getSelectedTokenToFiatRate,
getSendAmount,
getSendEditingTransactionId,
getSendFromObject,
getTokenBalance,
} from './send.selectors'
import {
updateSendTokenBalance,
updateGasTotal,
setGasTotal,
} from '../../actions'
import {
updateSendErrors,
} from '../../ducks/send'
import {
calcGasTotal,
generateTokenTransferData,
} from './send.utils.js'
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SendEther)
function mapStateToProps (state) {
const selectedAddress = getSelectedAddress(state)
const selectedToken = getSelectedToken(state)
return {
amount: getSendAmount(state),
amountConversionRate: getAmountConversionRate(state),
conversionRate: getConversionRate(state),
data: generateTokenTransferData(abi, selectedAddress, selectedToken),
editingTransactionId: getSendEditingTransactionId(state),
from: getSendFromObject(state),
gasLimit: getGasLimit(state),
gasPrice: getGasPrice(state),
gasTotal: getGasTotal(state),
network: getCurrentNetwork(state),
primaryCurrency: getPrimaryCurrency(state),
selectedAddress: getSelectedAddress(state),
selectedToken: getSelectedToken(state),
tokenBalance: getTokenBalance(state),
tokenContract: getSelectedTokenContract(state),
tokenToFiatRate: getSelectedTokenToFiatRate(state),
}
}
function mapDispatchToProps (dispatch) {
return {
updateAndSetGasTotal: ({
data,
editingTransactionId,
gasLimit,
gasPrice,
selectedAddress,
selectedToken,
}) => {
!editingTransactionId
? dispatch(updateGasTotal({ selectedAddress, selectedToken, data }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
},
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
dispatch(updateSendTokenBalance({
selectedToken,
tokenContract,
address,
}))
},
updateSendErrors: newError => dispatch(updateSendErrors(newError)),
}
}

View File

@ -8,6 +8,7 @@ const selectors = {
accountsWithSendEtherInfoSelector, accountsWithSendEtherInfoSelector,
autoAddToBetaUI, autoAddToBetaUI,
getAddressBook, getAddressBook,
getAmountConversionRate,
getConversionRate, getConversionRate,
getConvertedCurrency, getConvertedCurrency,
getCurrentAccountWithSendEtherInfo, getCurrentAccountWithSendEtherInfo,
@ -18,6 +19,7 @@ const selectors = {
getGasLimit, getGasLimit,
getGasPrice, getGasPrice,
getGasTotal, getGasTotal,
getPrimaryCurrency,
getSelectedAccount, getSelectedAccount,
getSelectedAddress, getSelectedAddress,
getSelectedIdentity, getSelectedIdentity,
@ -77,6 +79,12 @@ function getAddressBook (state) {
return state.metamask.addressBook return state.metamask.addressBook
} }
function getAmountConversionRate (state) {
return getSelectedToken(state)
? getSelectedTokenToFiatRate(state)
: getConversionRate(state)
}
function getConversionRate (state) { function getConversionRate (state) {
return state.metamask.conversionRate return state.metamask.conversionRate
} }
@ -121,6 +129,11 @@ function getGasTotal (state) {
return state.metamask.send.gasTotal return state.metamask.send.gasTotal
} }
function getPrimaryCurrency (state) {
const selectedToken = getSelectedToken(state)
return selectedToken && selectedToken.symbol
}
function getSelectedAccount (state) { function getSelectedAccount (state) {
const accounts = state.metamask.accounts const accounts = state.metamask.accounts
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)
@ -161,9 +174,8 @@ function getSelectedTokenExchangeRate (state) {
const tokenExchangeRates = state.metamask.tokenExchangeRates const tokenExchangeRates = state.metamask.tokenExchangeRates
const selectedToken = getSelectedToken(state) || {} const selectedToken = getSelectedToken(state) || {}
const { symbol = '' } = selectedToken const { symbol = '' } = selectedToken
const pair = `${symbol.toLowerCase()}_eth` const pair = `${symbol.toLowerCase()}_eth`
const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} const { rate: tokenExchangeRate = 0 } = tokenExchangeRates && tokenExchangeRates[pair] || {}
return tokenExchangeRate return tokenExchangeRate
} }
@ -190,7 +202,7 @@ function getSendEditingTransactionId (state) {
} }
function getSendErrors (state) { function getSendErrors (state) {
return state.metamask.send.errors return state.send.errors
} }
function getSendFrom (state) { function getSendFrom (state) {

View File

@ -7,8 +7,22 @@ const {
const { const {
calcTokenAmount, calcTokenAmount,
} = require('../../token-util') } = require('../../token-util')
const {
conversionGreaterThan,
} = require('../../conversion-util')
function getGasTotal (gasLimit, gasPrice) { module.exports = {
calcGasTotal,
doesAmountErrorRequireUpdate,
generateTokenTransferData,
getAmountErrorObject,
getParamsForGasEstimate,
calcTokenBalance,
isBalanceSufficient,
isTokenBalanceSufficient,
}
function calcGasTotal (gasLimit, gasPrice) {
return multiplyCurrencies(gasLimit, gasPrice, { return multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex', toNumericBase: 'hex',
multiplicandBase: 16, multiplicandBase: 16,
@ -71,8 +85,99 @@ function isTokenBalanceSufficient ({
return tokenBalanceIsSufficient return tokenBalanceIsSufficient
} }
module.exports = { function getAmountErrorObject ({
getGasTotal, amount,
isBalanceSufficient, amountConversionRate,
isTokenBalanceSufficient, balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
}) {
let insufficientFunds = false
if (gasTotal && conversionRate) {
insufficientFunds = !isBalanceSufficient({
amount: selectedToken ? '0x0' : amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
})
}
let inSufficientTokens = false
if (selectedToken && tokenBalance !== null) {
const { decimals } = selectedToken
inSufficientTokens = !isTokenBalanceSufficient({
tokenBalance,
amount,
decimals,
})
}
const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' },
)
let amountError = null
if (insufficientFunds) {
amountError = 'insufficientFunds'
} else if (inSufficientTokens) {
amountError = 'insufficientTokens'
} else if (amountLessThanZero) {
amountError = 'negativeETH'
}
return { amount: amountError }
}
function getParamsForGasEstimate (selectedAddress, symbol, data) {
const estimatedGasParams = {
from: selectedAddress,
gas: '746a528800',
}
if (symbol) {
Object.assign(estimatedGasParams, { value: '0x0' })
}
if (data) {
Object.assign(estimatedGasParams, { data })
}
return estimatedGasParams
}
function calcTokenBalance ({ selectedToken, usersToken }) {
const { decimals } = selectedToken || {}
return calcTokenAmount(usersToken.balance.toString(), decimals)
}
function doesAmountErrorRequireUpdate ({
balance,
gasTotal,
prevBalance,
prevGasTotal,
prevTokenBalance,
selectedToken,
tokenBalance,
}) {
const balanceHasChanged = balance !== prevBalance
const gasTotalHasChange = gasTotal !== prevGasTotal
const tokenBalanceHasChanged = selectedToken && tokenBalance !== prevTokenBalance
const amountErrorRequiresUpdate = balanceHasChanged || gasTotalHasChange || tokenBalanceHasChanged
return amountErrorRequiresUpdate
}
function generateTokenTransferData (abi, selectedAddress, selectedToken) {
if (!selectedToken) return
return Array.prototype.map.call(
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
x => ('00' + x.toString(16)).slice(-2)
).join('')
} }

View File

@ -5,6 +5,7 @@ const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'
const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern // TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = { const initState = {
@ -32,6 +33,13 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
return extend(sendState, { return extend(sendState, {
toDropdownOpen: false, toDropdownOpen: false,
}) })
case UPDATE_SEND_ERRORS:
return extend(sendState, {
errors: {
...sendState.errors,
...action.value,
},
})
default: default:
return sendState return sendState
} }
@ -53,3 +61,10 @@ export function openToDropdown () {
export function closeToDropdown () { export function closeToDropdown () {
return { type: CLOSE_TO_DROPDOWN } return { type: CLOSE_TO_DROPDOWN }
} }
export function updateSendErrors (errorObject) {
return {
type: UPDATE_SEND_ERRORS,
value: errorObject,
}
}

View File

@ -254,17 +254,6 @@ function reduceMetamask (state, action) {
}, },
}) })
case actions.UPDATE_SEND_ERRORS:
return extend(metamaskState, {
send: {
...metamaskState.send,
errors: {
...metamaskState.send.errors,
...action.value,
},
},
})
case actions.UPDATE_MAX_MODE: case actions.UPDATE_MAX_MODE:
return extend(metamaskState, { return extend(metamaskState, {
send: { send: {

View File

@ -84,7 +84,7 @@ SendTransactionScreen.prototype.updateGas = function () {
estimateGas, estimateGas,
selectedAddress, selectedAddress,
data, data,
updateGasTotal, setGasTotal,
from, from,
tokenContract, tokenContract,
editingTransactionId, editingTransactionId,
@ -110,7 +110,7 @@ SendTransactionScreen.prototype.updateGas = function () {
]) ])
.then(([gasPrice, gas]) => { .then(([gasPrice, gas]) => {
const newGasTotal = getGasTotal(gas, gasPrice) const newGasTotal = getGasTotal(gas, gasPrice)
updateGasTotal(newGasTotal) setGasTotal(newGasTotal)
this.setState({ gasLoadingError: false }) this.setState({ gasLoadingError: false })
}) })
.catch(err => { .catch(err => {
@ -118,7 +118,7 @@ SendTransactionScreen.prototype.updateGas = function () {
}) })
} else { } else {
const newGasTotal = getGasTotal(gasLimit, gasPrice) const newGasTotal = getGasTotal(gasLimit, gasPrice)
updateGasTotal(newGasTotal) setGasTotal(newGasTotal)
} }
} }