aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--test/integration/index.js3
-rw-r--r--ui/app/actions.js70
-rw-r--r--ui/app/app.js4
-rw-r--r--ui/app/components/customize-gas-modal/index.js8
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js6
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js6
-rw-r--r--ui/app/components/send/send-v2-container.js9
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.container.js2
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js6
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js5
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js14
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js21
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js61
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.component.js6
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.container.js6
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js15
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js1
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.container.js6
-rw-r--r--ui/app/components/send_/send.component.js143
-rw-r--r--ui/app/components/send_/send.container.js88
-rw-r--r--ui/app/components/send_/send.selectors.js18
-rw-r--r--ui/app/components/send_/send.utils.js115
-rw-r--r--ui/app/ducks/send.js15
-rw-r--r--ui/app/reducers/metamask.js11
-rw-r--r--ui/app/send-v2.js6
25 files changed, 489 insertions, 156 deletions
diff --git a/test/integration/index.js b/test/integration/index.js
index b266ddf37..7789eb283 100644
--- a/test/integration/index.js
+++ b/test/integration/index.js
@@ -2,7 +2,8 @@ const fs = require('fs')
const path = require('path')
const pump = require('pump')
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 b = browserify()
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 674669eed..7b838940a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1,6 +1,11 @@
const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util')
+const {
+ calcGasTotal,
+ getParamsForGasEstimate,
+ calcTokenBalance,
+} = require('./components/send_/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper')
const log = require('loglevel')
@@ -173,14 +178,16 @@ var actions = {
updateGasLimit,
updateGasPrice,
updateGasTotal,
+ setGasTotal,
+ setSendTokenBalance,
updateSendTokenBalance,
updateSendFrom,
updateSendTo,
updateSendAmount,
updateSendMemo,
- updateSendErrors,
setMaxModeTo,
updateSend,
+ updateSendErrors,
clearSend,
setSelectedAddress,
// app messages
@@ -716,14 +723,64 @@ function updateGasPrice (gasPrice) {
}
}
-function updateGasTotal (gasTotal) {
+function setGasTotal (gasTotal) {
return {
type: actions.UPDATE_GAS_TOTAL,
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 {
type: actions.UPDATE_SEND_TOKEN_BALANCE,
value: tokenBalance,
@@ -758,13 +815,6 @@ function updateSendMemo (memo) {
}
}
-function updateSendErrors (error) {
- return {
- type: actions.UPDATE_SEND_ERRORS,
- value: error,
- }
-}
-
function setMaxModeTo (bool) {
return {
type: actions.UPDATE_MAX_MODE,
diff --git a/ui/app/app.js b/ui/app/app.js
index 0b38b1326..6511979b2 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -11,7 +11,7 @@ const log = require('loglevel')
// init
const InitializeScreen = require('../../mascara/src/app/first-time').default
// accounts
-const SendTransactionScreen2 = require('./components/send/send-v2-container')
+const SendTransactionScreen = require('./components/send_/send.container')
const ConfirmTxScreen = require('./conf-tx')
// slideout menu
@@ -84,7 +84,7 @@ class App extends Component {
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
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: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 4c693d1c3..0761faa99 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -8,6 +8,10 @@ const GasModalCard = require('./gas-modal-card')
const ethUtil = require('ethereumjs-util')
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
@@ -63,9 +67,9 @@ function mapDispatchToProps (dispatch) {
hideModal: () => dispatch(actions.hideModal()),
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
- updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
+ updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 936db3bcb..ac1b15a4f 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -29,6 +29,10 @@ const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
ConfirmSendEther.contextTypes = {
t: PropTypes.func,
}
@@ -105,7 +109,7 @@ function mapDispatchToProps (dispatch) {
}))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
},
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index 281c42824..e1abc6d3d 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -39,6 +39,10 @@ const {
} = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
ConfirmSendToken.contextTypes = {
t: PropTypes.func,
}
@@ -141,7 +145,7 @@ function mapDispatchToProps (dispatch, ownProps) {
}))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
},
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index adfc91240..6464439f2 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -18,6 +18,10 @@ const {
getSelectedTokenContract,
} = require('../../selectors')
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
@@ -74,7 +78,8 @@ function mapDispatchToProps (dispatch) {
updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
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)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)),
@@ -82,7 +87,7 @@ function mapDispatchToProps (dispatch) {
updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
- updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
+ updateSendErrors: newError => dispatch(updateSendErrors(newError)),
clearSend: () => dispatch(actions.clearSend()),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}
diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js
index e1bc1dd79..3151b1f1d 100644
--- a/ui/app/components/send_/account-list-item/account-list-item.container.js
+++ b/ui/app/components/send_/account-list-item/account-list-item.container.js
@@ -12,4 +12,4 @@ function mapStateToProps (state) {
conversionRate: getConversionRate(state),
currentCurrency: getConvertedCurrency(state),
}
-} \ No newline at end of file
+}
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index 5bdb67995..4211f16ab 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -9,10 +9,12 @@ import { getMaxModeOn } from '../send-amount-row.selectors.js'
import { calcMaxAmount } from './amount-max-button.utils.js'
import {
updateSendAmount,
- updateSendErrors,
setMaxModeTo,
-} from '../../../actions'
+} from '../../../../../actions'
import AmountMaxButton from './amount-max-button.component'
+import {
+ updateSendErrors,
+} from '../../../../../ducks/send'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
index 5c3084365..dbebc0bdf 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
@@ -8,7 +8,10 @@ export default class SendAmountRow extends Component {
static propTypes = {
amount: PropTypes.string,
- amountConversionRate: PropTypes.number,
+ amountConversionRate: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
balance: PropTypes.string,
conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string,
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
index 16e88bede..13888ec53 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
@@ -1,24 +1,26 @@
import { connect } from 'react-redux'
import {
+ getAmountConversionRate,
getConversionRate,
getConvertedCurrency,
getGasTotal,
+ getPrimaryCurrency,
getSelectedToken,
getSendAmount,
getSendFromBalance,
getTokenBalance,
-} from '../../send.selectors.js'
+} from '../../send.selectors'
import {
- getAmountConversionRate,
- getPrimaryCurrency,
sendAmountIsInError,
-} from './send-amount-row.selectors.js'
-import { getAmountErrorObject } from './send-amount-row.utils.js'
+} from './send-amount-row.selectors'
+import { getAmountErrorObject } from '../../send.utils'
import {
setMaxModeTo,
updateSendAmount,
- updateSendErrors,
} from '../../../../actions'
+import {
+ updateSendErrors,
+} from '../../../../ducks/send'
import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
index 88dee0dcb..62e4f6b32 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
@@ -1,13 +1,5 @@
-import {
- getSelectedToken,
- getSelectedTokenToFiatRate,
- getConversionRate,
-} from '../../send.selectors.js'
-
const selectors = {
- getAmountConversionRate,
getMaxModeOn,
- getPrimaryCurrency,
sendAmountIsInError,
}
@@ -18,16 +10,5 @@ function getMaxModeOn (state) {
}
function sendAmountIsInError (state) {
- return Boolean(state.metamask.send.errors.amount)
-}
-
-function getPrimaryCurrency (state) {
- const selectedToken = getSelectedToken(state)
- return selectedToken && selectedToken.symbol
-}
-
-function getAmountConversionRate (state) {
- return getSelectedToken(state)
- ? getSelectedTokenToFiatRate(state)
- : getConversionRate(state)
+ return Boolean(state.send.errors.amount)
}
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js
index 6ec5463d3..e69de29bb 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js
@@ -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,
-}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
index 193262fa3..17e7f8e46 100644
--- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
@@ -14,19 +14,19 @@ export default class SendFromRow extends Component {
openFromDropdown: PropTypes.func,
tokenContract: PropTypes.object,
updateSendFrom: PropTypes.func,
- updateSendTokenBalance: PropTypes.func,
+ setSendTokenBalance: PropTypes.func,
};
async handleFromChange (newFrom) {
const {
updateSendFrom,
tokenContract,
- updateSendTokenBalance,
+ setSendTokenBalance,
} = this.props
if (tokenContract) {
const usersToken = await tokenContract.balanceOf(newFrom.address)
- updateSendTokenBalance(usersToken)
+ setSendTokenBalance(usersToken)
}
updateSendFrom(newFrom)
}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
index d62782e9f..9e366445d 100644
--- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
@@ -11,7 +11,7 @@ import {
import { calcTokenUpdateAmount } from './send-from-row.utils.js'
import {
updateSendFrom,
- updateSendTokenBalance,
+ setSendTokenBalance,
} from '../../../../actions'
import {
closeFromDropdown,
@@ -36,11 +36,11 @@ function mapDispatchToProps (dispatch) {
closeFromDropdown: () => dispatch(closeFromDropdown()),
openFromDropdown: () => dispatch(openFromDropdown()),
updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
- updateSendTokenBalance: (usersToken, selectedToken) => {
+ setSendTokenBalance: (usersToken, selectedToken) => {
if (!usersToken) return
const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken)
- dispatch(updateSendTokenBalance(tokenBalance))
+ dispatch(setSendTokenBalance(tokenBalance))
},
}
}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
index a8441c92f..c62c110e0 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
@@ -18,23 +18,8 @@ export default class SendGasRow extends Component {
showCustomizeGasModal: PropTypes.func,
tokenContract: PropTypes.object,
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 () {
const {
conversionRate,
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index c031248c7..0d314208b 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -10,6 +10,7 @@ export default class SendRowErrorMessage extends Component {
render () {
const { errors, errorType } = this.props
+
const errorMessage = errors[errorType]
return (
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
index b55455f8b..bffdda49c 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
@@ -10,12 +10,12 @@ import {
} from './send-to-row.selectors.js'
import { getToErrorObject } from './send-to-row.utils.js'
import {
- updateSendErrors,
updateSendTo,
} from '../../../../actions'
import {
- openToDropdown,
- closeToDropdown,
+ updateSendErrors,
+ openToDropdown,
+ closeToDropdown,
} from '../../../../ducks/send'
import SendToRow from './send-to-row.component'
diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js
index e69de29bb..e014e5bce 100644
--- a/ui/app/components/send_/send.component.js
+++ b/ui/app/components/send_/send.component.js
@@ -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,
+}
diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js
index e69de29bb..df605469a 100644
--- a/ui/app/components/send_/send.container.js
+++ b/ui/app/components/send_/send.container.js
@@ -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)),
+ }
+}
diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js
index acce4afb7..761b15182 100644
--- a/ui/app/components/send_/send.selectors.js
+++ b/ui/app/components/send_/send.selectors.js
@@ -8,6 +8,7 @@ const selectors = {
accountsWithSendEtherInfoSelector,
autoAddToBetaUI,
getAddressBook,
+ getAmountConversionRate,
getConversionRate,
getConvertedCurrency,
getCurrentAccountWithSendEtherInfo,
@@ -18,6 +19,7 @@ const selectors = {
getGasLimit,
getGasPrice,
getGasTotal,
+ getPrimaryCurrency,
getSelectedAccount,
getSelectedAddress,
getSelectedIdentity,
@@ -77,6 +79,12 @@ function getAddressBook (state) {
return state.metamask.addressBook
}
+function getAmountConversionRate (state) {
+ return getSelectedToken(state)
+ ? getSelectedTokenToFiatRate(state)
+ : getConversionRate(state)
+}
+
function getConversionRate (state) {
return state.metamask.conversionRate
}
@@ -121,6 +129,11 @@ function getGasTotal (state) {
return state.metamask.send.gasTotal
}
+function getPrimaryCurrency (state) {
+ const selectedToken = getSelectedToken(state)
+ return selectedToken && selectedToken.symbol
+}
+
function getSelectedAccount (state) {
const accounts = state.metamask.accounts
const selectedAddress = getSelectedAddress(state)
@@ -161,9 +174,8 @@ function getSelectedTokenExchangeRate (state) {
const tokenExchangeRates = state.metamask.tokenExchangeRates
const selectedToken = getSelectedToken(state) || {}
const { symbol = '' } = selectedToken
-
const pair = `${symbol.toLowerCase()}_eth`
- const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates && tokenExchangeRates[pair] || {}
return tokenExchangeRate
}
@@ -190,7 +202,7 @@ function getSendEditingTransactionId (state) {
}
function getSendErrors (state) {
- return state.metamask.send.errors
+ return state.send.errors
}
function getSendFrom (state) {
diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js
index f289ea989..2e17b04a3 100644
--- a/ui/app/components/send_/send.utils.js
+++ b/ui/app/components/send_/send.utils.js
@@ -7,8 +7,22 @@ const {
const {
calcTokenAmount,
} = require('../../token-util')
+const {
+ conversionGreaterThan,
+} = require('../../conversion-util')
+
+module.exports = {
+ calcGasTotal,
+ doesAmountErrorRequireUpdate,
+ generateTokenTransferData,
+ getAmountErrorObject,
+ getParamsForGasEstimate,
+ calcTokenBalance,
+ isBalanceSufficient,
+ isTokenBalanceSufficient,
+}
-function getGasTotal (gasLimit, gasPrice) {
+function calcGasTotal (gasLimit, gasPrice) {
return multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
@@ -71,8 +85,99 @@ function isTokenBalanceSufficient ({
return tokenBalanceIsSufficient
}
-module.exports = {
- getGasTotal,
- isBalanceSufficient,
- isTokenBalanceSufficient,
+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 }
+}
+
+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('')
}
diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js
index e0feb9f06..aef493ea0 100644
--- a/ui/app/ducks/send.js
+++ b/ui/app/ducks/send.js
@@ -5,6 +5,7 @@ const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'
const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_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
const initState = {
@@ -32,6 +33,13 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
return extend(sendState, {
toDropdownOpen: false,
})
+ case UPDATE_SEND_ERRORS:
+ return extend(sendState, {
+ errors: {
+ ...sendState.errors,
+ ...action.value,
+ },
+ })
default:
return sendState
}
@@ -53,3 +61,10 @@ export function openToDropdown () {
export function closeToDropdown () {
return { type: CLOSE_TO_DROPDOWN }
}
+
+export function updateSendErrors (errorObject) {
+ return {
+ type: UPDATE_SEND_ERRORS,
+ value: errorObject,
+ }
+}
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index bb35cf990..5564c8080 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -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:
return extend(metamaskState, {
send: {
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index ef67ebf5e..16964b45d 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -84,7 +84,7 @@ SendTransactionScreen.prototype.updateGas = function () {
estimateGas,
selectedAddress,
data,
- updateGasTotal,
+ setGasTotal,
from,
tokenContract,
editingTransactionId,
@@ -110,7 +110,7 @@ SendTransactionScreen.prototype.updateGas = function () {
])
.then(([gasPrice, gas]) => {
const newGasTotal = getGasTotal(gas, gasPrice)
- updateGasTotal(newGasTotal)
+ setGasTotal(newGasTotal)
this.setState({ gasLoadingError: false })
})
.catch(err => {
@@ -118,7 +118,7 @@ SendTransactionScreen.prototype.updateGas = function () {
})
} else {
const newGasTotal = getGasTotal(gasLimit, gasPrice)
- updateGasTotal(newGasTotal)
+ setGasTotal(newGasTotal)
}
}