diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 03f62424c..6da058786 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -593,6 +593,9 @@
"noTransactions": {
"message": "No Transactions"
},
+ "notFound": {
+ "message": "Not Found"
+ },
"notStarted": {
"message": "Not Started"
},
@@ -972,6 +975,9 @@
"unknown": {
"message": "Unknown"
},
+ "unknownFunction": {
+ "message": "Unknown Function"
+ },
"unknownNetwork": {
"message": "Unknown Private Network"
},
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index caa49d450..ee1aa0ff1 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -513,7 +513,7 @@ describe('MetaMask', function () {
it('displays the contract creation data', async () => {
const dataTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Data')]`))
dataTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
await findElement(driver, By.xpath(`//div[contains(text(), '127.0.0.1')]`))
@@ -523,7 +523,7 @@ describe('MetaMask', function () {
const detailsTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Details')]`))
detailsTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
})
it('confirms a deploy contract transaction', async () => {
@@ -548,7 +548,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
await driver.switchTo().window(extension)
- await delay(regularDelayMs)
+ await delay(largeDelayMs)
await findElements(driver, By.css('.tx-list-pending-item-container'))
const [txListValue] = await findElements(driver, By.css('.tx-list-value'))
@@ -741,7 +741,7 @@ describe('MetaMask', function () {
it('displays the token transfer data', async () => {
const dataTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Data')]`))
dataTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
const functionType = await findElement(driver, By.css('.confirm-page-container-content__function-type'))
const functionTypeText = await functionType.getText()
@@ -753,7 +753,7 @@ describe('MetaMask', function () {
const detailsTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Details')]`))
detailsTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
})
it('submits the transaction', async function () {
@@ -901,7 +901,7 @@ describe('MetaMask', function () {
it('displays the token approval data', async () => {
const dataTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Data')]`))
dataTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
const functionType = await findElement(driver, By.css('.confirm-page-container-content__function-type'))
const functionTypeText = await functionType.getText()
@@ -913,12 +913,12 @@ describe('MetaMask', function () {
const detailsTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Details')]`))
detailsTab.click()
- await (regularDelayMs)
+ await delay(regularDelayMs)
const approvalWarning = await findElement(driver, By.css('.confirm-page-container-warning__warning'))
const approvalWarningText = await approvalWarning.getText()
assert(approvalWarningText.match(/By approving this/))
- await (regularDelayMs)
+ await delay(regularDelayMs)
})
it('opens the gas edit modal', async () => {
diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js
deleted file mode 100644
index c68e013ac..000000000
--- a/test/unit/components/pending-tx-test.js
+++ /dev/null
@@ -1,67 +0,0 @@
-const assert = require('assert')
-const h = require('react-hyperscript')
-const PendingTx = require('../../../ui/app/components/pending-tx')
-const ethUtil = require('ethereumjs-util')
-
-const { createMockStore } = require('redux-test-utils')
-const { shallowWithStore } = require('../../lib/shallow-with-store')
-
-const identities = { abc: {}, def: {} }
-const mockState = {
- metamask: {
- accounts: { abc: {} },
- identities,
- conversionRate: 10,
- selectedAddress: 'abc',
- },
-}
-
-describe('PendingTx', function () {
- const gasPrice = '0x4A817C800' // 20 Gwei
- const txData = {
- 'id': 5021615666270214,
- 'time': 1494458763011,
- 'status': 'unapproved',
- 'metamaskNetworkId': '1494442339676',
- 'txParams': {
- 'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826',
- 'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
- 'value': '0xde0b6b3a7640000',
- gasPrice,
- 'gas': '0x7b0c',
- },
- 'gasLimitSpecified': false,
- 'estimatedGas': '0x5208',
- }
- const newGasPrice = '0x77359400'
-
- const computedBalances = {}
- computedBalances[Object.keys(identities)[0]] = {
- ethBalance: '0x00000000000000056bc75e2d63100000',
- }
- const props = {
- txData,
- computedBalances,
- sendTransaction: (txMeta, event) => {
- // Assert changes:
- const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice)
- assert.notEqual(result, gasPrice, 'gas price should change')
- assert.equal(result, newGasPrice, 'gas price assigned.')
- },
- }
-
- let pendingTxComponent
- let store
- let component
- beforeEach(function () {
- store = createMockStore(mockState)
- component = shallowWithStore(h(PendingTx, props), store)
- pendingTxComponent = component
- })
-
- it('should render correctly', function (done) {
- assert.equal(pendingTxComponent.props().identities, identities)
- done()
- })
-})
-
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
index 631cf5803..f0703dde2 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
@@ -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 => {
- { fiatFee }
+ { fiatText }
- { `\u2666 ${ethFee}` }
+ { ethText }
@@ -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,
diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss
index e82d0e70c..2085cff67 100644
--- a/ui/app/components/network-display/index.scss
+++ b/ui/app/components/network-display/index.scss
@@ -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%);
}
}
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.component.js b/ui/app/components/pages/confirm-approve/confirm-approve.component.js
index d775b0362..b71eaa1d4 100644
--- a/ui/app/components/pages/confirm-approve/confirm-approve.component.js
+++ b/ui/app/components/pages/confirm-approve/confirm-approve.component.js
@@ -1,29 +1,20 @@
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,
+ tokenAmount: PropTypes.number,
tokenSymbol: PropTypes.string,
}
render () {
- const { toAddress, tokenAddress, tokenAmount, tokenSymbol } = this.props
+ const { tokenAmount, tokenSymbol } = this.props
return (
-
)
}
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.container.js b/ui/app/components/pages/confirm-approve/confirm-approve.container.js
index 040e499ae..4ef9f4ced 100644
--- a/ui/app/components/pages/confirm-approve/confirm-approve.container.js
+++ b/ui/app/components/pages/confirm-approve/confirm-approve.container.js
@@ -1,25 +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 { confirmTransaction: { tokenProps: { tokenSymbol } = {} } } = state
+ const { tokenAmount } = approveTokenAmountAndToAddressSelector(state)
return {
- toAddress,
- tokenAddress,
tokenAmount,
tokenSymbol,
}
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
index 46ad9ccab..cb39e3d7b 100644
--- a/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
+++ b/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
@@ -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 (
- this.handleEdit(confirmTransactionData)}
- hideSubtitle
+ tokenAmount={tokenAmount}
/>
)
}
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
index 2d7efeed6..d60911e59 100644
--- a/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
+++ b/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
@@ -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,
}
}
diff --git a/ui/app/components/pages/confirm-send-token/index.scss b/ui/app/components/pages/confirm-send-token/index.scss
deleted file mode 100644
index 0476749f6..000000000
--- a/ui/app/components/pages/confirm-send-token/index.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-.confirm-send-token {
- &__title {
- padding: 4px 0;
- display: flex;
- align-items: center;
- }
-
- &__identicon {
- flex: 0 0 auto;
- }
-
- &__title-text {
- font-size: 2.25rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- padding-left: 8px;
- }
-}
diff --git a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
new file mode 100644
index 000000000..365ae216e
--- /dev/null
+++ b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
@@ -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('noConversionRateAvailable')
+ } 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 (
+
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js
new file mode 100644
index 000000000..be38acdb0
--- /dev/null
+++ b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js
@@ -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)
diff --git a/ui/app/components/pages/confirm-token-transaction-base/index.js b/ui/app/components/pages/confirm-token-transaction-base/index.js
new file mode 100644
index 000000000..e15c5d56b
--- /dev/null
+++ b/ui/app/components/pages/confirm-token-transaction-base/index.js
@@ -0,0 +1,2 @@
+export { default } from './confirm-token-transaction-base.container'
+export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component'
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 5327b116f..e1bf2210f 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -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 || (
this.handleEditGas()}
@@ -169,11 +175,11 @@ export default class ConfirmTransactionBase extends Component {
@@ -206,17 +212,21 @@ export default class ConfirmTransactionBase extends Component {
{`${t('functionType')}:`}
- { name }
+ { name || t('notFound') }
-
-
- { `${t('parameters')}:` }
-
-
-
{ JSON.stringify(params, null, 2) }
-
-
+ {
+ params && (
+
+
+ { `${t('parameters')}:` }
+
+
+
{ JSON.stringify(params, null, 2) }
+
+
+ )
+ }
{`${t('hexData')}:`}
@@ -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}
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
index 25259b98c..0280f73c6 100644
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
@@ -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
}
- 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
@@ -51,6 +55,10 @@ export default class ConfirmTransactionSwitch extends Component {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`
return
}
+ case TOKEN_METHOD_TRANSFER_FROM: {
+ const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`
+ return
+ }
default: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`
return
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
index 622d2a37a..9db4a2f96 100644
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
@@ -1,2 +1,3 @@
export const TOKEN_METHOD_TRANSFER = 'transfer'
export const TOKEN_METHOD_APPROVE = 'approve'
+export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
diff --git a/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
index 874a89fd2..3ac656d73 100644
--- a/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
+++ b/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
@@ -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}
/>
+
backToAccountDetail(selectedAddress),
- }, this.context.t('back')),
- window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay),
- ]),
- h('.page-container__title', this.context.t('confirmContract')),
- h('.page-container__subtitle', this.context.t('pleaseReviewTransaction')),
- ]),
- // Main Send token Card
- h('.page-container__content', [
-
- h(SenderToRecipient, {
- senderName: fromName,
- senderAddress: fromAddress,
- }),
-
- // h('h3.flex-center.confirm-screen-sending-to-message', {
- // style: {
- // textAlign: 'center',
- // fontSize: '16px',
- // },
- // }, [
- // `You're deploying a new contract.`,
- // ]),
-
- this.renderHeroAmount(),
-
- h('div.confirm-screen-rows', [
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('from') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', fromName),
- h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
- ]),
- ]),
-
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('to') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', this.context.t('newContract')),
- ]),
- ]),
-
- this.renderGasFee(),
-
- this.renderTotalPlusGas(),
-
- ]),
- ]),
-
- h('form#pending-tx-form', {
- onSubmit: event => this.onSubmit(event),
- }, [
- h('.page-container__footer', [
- // Cancel Button
- h('button.btn-cancel.page-container__footer-button.allcaps', {
- onClick: event => this.cancel(event, txMeta),
- }, this.context.t('cancel')),
-
- // Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', {
- onClick: event => this.onSubmit(event),
- }, this.context.t('confirm')),
- ]),
- ]),
- ])
- )
- }
-}
-
-ConfirmDeployContract.propTypes = {
- sendTransaction: PropTypes.func,
- cancelTransaction: PropTypes.func,
- backToAccountDetail: PropTypes.func,
- displayWarning: PropTypes.func,
- identities: PropTypes.object,
- conversionRate: PropTypes.number,
- currentCurrency: PropTypes.string,
- selectedAddress: PropTypes.string,
- t: PropTypes.func,
-}
-
-const mapStateToProps = state => {
- const {
- conversionRate,
- identities,
- currentCurrency,
- } = state.metamask
- const accounts = state.metamask.accounts
- const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- return {
- currentCurrency,
- conversionRate,
- identities,
- selectedAddress,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
- cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
- displayWarning: warning => actions.displayWarning(warning),
- }
-}
-
-ConfirmDeployContract.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmDeployContract)
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
deleted file mode 100644
index 67f54aa26..000000000
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ /dev/null
@@ -1,692 +0,0 @@
-const Component = require('react').Component
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const actions = require('../../actions')
-const clone = require('clone')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
-const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
-const classnames = require('classnames')
-const {
- conversionUtil,
- addCurrencies,
- multiplyCurrencies,
-} = require('../../conversion-util')
-const {
- calcGasTotal,
- isBalanceSufficient,
-} = require('../send/send.utils')
-const GasFeeDisplay = require('../send/send-content/send-gas-row/gas-fee-display/').default
-const SenderToRecipient = require('../sender-to-recipient')
-const NetworkDisplay = require('../network-display')
-const currencyFormatter = require('currency-formatter')
-const currencies = require('currency-formatter/currencies')
-
-const { MIN_GAS_PRICE_HEX } = require('../send/send.constants')
-const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
-const {
- ENVIRONMENT_TYPE_POPUP,
- ENVIRONMENT_TYPE_NOTIFICATION,
-} = require('../../../../app/scripts/lib/enums')
-
-import {
- updateSendErrors,
-} from '../../ducks/send.duck'
-
-ConfirmSendEther.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ConfirmSendEther)
-
-
-function mapStateToProps (state) {
- const {
- conversionRate,
- identities,
- currentCurrency,
- send,
- } = state.metamask
- const accounts = state.metamask.accounts
- const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- const { balance } = accounts[selectedAddress]
- return {
- conversionRate,
- identities,
- selectedAddress,
- currentCurrency,
- send,
- balance,
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- clearSend: () => dispatch(actions.clearSend()),
- editTransaction: txMeta => {
- const { id, txParams } = txMeta
- const {
- gas: gasLimit,
- gasPrice,
- to,
- value: amount,
- } = txParams
-
- dispatch(actions.updateSend({
- gasLimit,
- gasPrice,
- gasTotal: null,
- to,
- amount,
- errors: { to: null, amount: null },
- editingTransactionId: id,
- }))
- },
- cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
- showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
- const { id, txParams, lastGasPrice } = txMeta
- const { gas: txGasLimit, gasPrice: txGasPrice } = txParams
-
- let forceGasMin
- if (lastGasPrice) {
- forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
- multiplicandBase: 16,
- multiplierBase: 10,
- toNumericBase: 'hex',
- fromDenomination: 'WEI',
- }))
- }
-
- dispatch(actions.updateSend({
- gasLimit: sendGasLimit || txGasLimit,
- gasPrice: sendGasPrice || txGasPrice,
- editingTransactionId: id,
- gasTotal: sendGasTotal,
- forceGasMin,
- }))
- dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
- },
- updateSendErrors: error => dispatch(updateSendErrors(error)),
- }
-}
-
-inherits(ConfirmSendEther, Component)
-function ConfirmSendEther () {
- Component.call(this)
- this.state = {}
- this.onSubmit = this.onSubmit.bind(this)
-}
-
-ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) {
- const {
- balance: oldBalance,
- conversionRate: oldConversionRate,
- } = prevProps
- const {
- updateSendErrors,
- balance,
- conversionRate,
- send: {
- errors: {
- simulationFails,
- },
- },
- } = this.props
- const txMeta = this.gatherTxMeta()
-
- const shouldUpdateBalanceSendErrors = balance && [
- balance !== oldBalance,
- conversionRate !== oldConversionRate,
- ].some(x => Boolean(x))
-
- if (shouldUpdateBalanceSendErrors) {
- const balanceIsSufficient = this.isBalanceSufficient(txMeta)
- updateSendErrors({
- insufficientFunds: balanceIsSufficient ? false : 'insufficientFunds',
- })
- }
-
- const shouldUpdateSimulationSendError = Boolean(txMeta.simulationFails) !== Boolean(simulationFails)
-
- if (shouldUpdateSimulationSendError) {
- updateSendErrors({
- simulationFails: !txMeta.simulationFails ? false : 'transactionError',
- })
- }
-}
-
-ConfirmSendEther.prototype.componentWillMount = function () {
- this.updateComponentSendErrors({})
-}
-
-ConfirmSendEther.prototype.componentDidUpdate = function (prevProps) {
- this.updateComponentSendErrors(prevProps)
-}
-
-ConfirmSendEther.prototype.getAmount = function () {
- const { conversionRate, currentCurrency } = this.props
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
-
- const FIAT = conversionUtil(txParams.value, {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- fromCurrency: 'ETH',
- toCurrency: currentCurrency,
- numberOfDecimals: 2,
- fromDenomination: 'WEI',
- conversionRate,
- })
- const ETH = conversionUtil(txParams.value, {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- fromCurrency: 'ETH',
- toCurrency: 'ETH',
- fromDenomination: 'WEI',
- conversionRate,
- numberOfDecimals: 6,
- })
-
- return {
- FIAT,
- ETH,
- }
-
-}
-
-ConfirmSendEther.prototype.getGasFee = function () {
- const { conversionRate, currentCurrency } = this.props
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
-
- // Gas
- const gas = txParams.gas
- const gasBn = hexToBn(gas)
-
- // From latest master
-// const gasLimit = new BN(parseInt(blockGasLimit))
-// const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
-// const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
-// const safeGasLimit = safeGasLimitBN.toString(10)
-
- // Gas Price
- const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_HEX
- const gasPriceBn = hexToBn(gasPrice)
-
- const txFeeBn = gasBn.mul(gasPriceBn)
-
- const FIAT = conversionUtil(txFeeBn, {
- fromNumericBase: 'BN',
- toNumericBase: 'dec',
- fromDenomination: 'WEI',
- fromCurrency: 'ETH',
- toCurrency: currentCurrency,
- numberOfDecimals: 2,
- conversionRate,
- })
- const ETH = conversionUtil(txFeeBn, {
- fromNumericBase: 'BN',
- toNumericBase: 'dec',
- fromDenomination: 'WEI',
- fromCurrency: 'ETH',
- toCurrency: 'ETH',
- numberOfDecimals: 6,
- conversionRate,
- })
-
- return {
- FIAT,
- ETH,
- gasFeeInHex: txFeeBn.toString(16),
- }
-}
-
-ConfirmSendEther.prototype.getData = function () {
- const { identities } = this.props
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
- const account = identities ? identities[txParams.from] || {} : {}
- const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee()
- const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount()
-
- const totalInFIAT = addCurrencies(gasFeeInFIAT, amountInFIAT, {
- toNumericBase: 'dec',
- numberOfDecimals: 2,
- })
- const totalInETH = addCurrencies(gasFeeInETH, amountInETH, {
- toNumericBase: 'dec',
- numberOfDecimals: 6,
- })
-
- return {
- from: {
- address: txParams.from,
- name: account.name,
- },
- to: {
- address: txParams.to,
- name: identities[txParams.to] ? identities[txParams.to].name : this.context.t('newRecipient'),
- },
- memo: txParams.memo || '',
- gasFeeInFIAT,
- gasFeeInETH,
- amountInFIAT,
- amountInETH,
- totalInFIAT,
- totalInETH,
- gasFeeInHex,
- }
-}
-
-ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, currencyCode) {
- const upperCaseCurrencyCode = currencyCode.toUpperCase()
-
- return currencies.find(currency => currency.code === upperCaseCurrencyCode)
- ? currencyFormatter.format(Number(value), {
- code: upperCaseCurrencyCode,
- })
- : value
-}
-
-ConfirmSendEther.prototype.editTransaction = function () {
- const { editTransaction, history } = this.props
- const txMeta = this.gatherTxMeta()
- editTransaction(txMeta)
- history.push(SEND_ROUTE)
-}
-
-ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) {
- const windowType = window.METAMASK_UI_TYPE
- const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
- windowType !== ENVIRONMENT_TYPE_POPUP
-
- if (isTxReprice && isFullScreen) {
- return null
- }
-
- return (
- h('.page-container__header-row', [
- h('span.page-container__back-button', {
- onClick: () => this.editTransaction(),
- style: {
- visibility: isTxReprice ? 'hidden' : 'initial',
- },
- }, 'Edit'),
- !isFullScreen && h(NetworkDisplay),
- ])
- )
-}
-
-ConfirmSendEther.prototype.renderHeader = function (isTxReprice) {
- const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
- const subtitle = isTxReprice
- ? this.context.t('speedUpSubtitle')
- : this.context.t('pleaseReviewTransaction')
-
- return (
- h('.page-container__header', [
- this.renderHeaderRow(isTxReprice),
- h('.page-container__title', title),
- h('.page-container__subtitle', subtitle),
- ])
- )
-}
-
-ConfirmSendEther.prototype.render = function () {
- const {
- currentCurrency,
- clearSend,
- conversionRate,
- currentCurrency: convertedCurrency,
- showCustomizeGasModal,
- send: {
- gasTotal,
- gasLimit: sendGasLimit,
- gasPrice: sendGasPrice,
- errors,
- },
- } = this.props
- const txMeta = this.gatherTxMeta()
- const isTxReprice = Boolean(txMeta.lastGasPrice)
- const txParams = txMeta.txParams || {}
-
- const {
- from: {
- address: fromAddress,
- name: fromName,
- },
- to: {
- address: toAddress,
- name: toName,
- },
- memo,
- gasFeeInHex,
- amountInFIAT,
- totalInFIAT,
- totalInETH,
- } = this.getData()
-
- const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency)
- const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
-
- // This is from the latest master
- // It handles some of the errors that we are not currently handling
- // Leaving as comments fo reference
-
- // const balanceBn = hexToBn(balance)
- // const insufficientBalance = balanceBn.lt(maxCost)
- // const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
- // const showRejectAll = props.unconfTxListLength > 1
-// const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
-// const gasLimitSpecified = txMeta.gasLimitSpecified
-
- this.inputs = []
-
- return (
- // Main Send token Card
- h('.page-container', [
- this.renderHeader(isTxReprice),
- h('.page-container__content', [
- h(SenderToRecipient, {
- senderName: fromName,
- senderAddress: fromAddress,
- recipientName: toName,
- recipientAddress: txParams.to,
- }),
-
- // h('h3.flex-center.confirm-screen-sending-to-message', {
- // style: {
- // textAlign: 'center',
- // fontSize: '16px',
- // },
- // }, [
- // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
- // ]),
-
- h('h3.flex-center.confirm-screen-send-amount', [`${convertedAmountInFiat}`]),
- h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]),
- h('div.flex-center.confirm-memo-wrapper', [
- h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
- ]),
-
- h('div.confirm-screen-rows', [
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('from') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', fromName),
- h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
- ]),
- ]),
-
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('to') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', toName),
- h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
- ]),
- ]),
-
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('gasFee') ]),
- h('div.confirm-screen-section-column', [
- h(GasFeeDisplay, {
- gasTotal: gasTotal || gasFeeInHex,
- conversionRate,
- convertedCurrency,
- onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
- }),
- ]),
- ]),
-
- h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
- h('div', {
- className: classnames({
- 'confirm-screen-section-column--with-error': errors['insufficientFunds'],
- 'confirm-screen-section-column': !errors['insufficientFunds'],
- }),
- }, [
- h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]),
- h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]),
- ]),
-
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency.toUpperCase()}`),
- h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
- ]),
-
- this.renderErrorMessage('insufficientFunds'),
- ]),
- ]),
-
-// These are latest errors handling from master
-// Leaving as comments as reference when we start implementing error handling
-// h('style', `
-// .conf-buttons button {
-// margin-left: 10px;
-// text-transform: uppercase;
-// }
-// `),
-
-// txMeta.simulationFails ?
-// h('.error', {
-// style: {
-// marginLeft: 50,
-// fontSize: '0.9em',
-// },
-// }, 'Transaction Error. Exception thrown in contract code.')
-// : null,
-
-// !isValidAddress ?
-// h('.error', {
-// style: {
-// marginLeft: 50,
-// fontSize: '0.9em',
-// },
-// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
-// : null,
-
-// insufficientBalance ?
-// h('span.error', {
-// style: {
-// marginLeft: 50,
-// fontSize: '0.9em',
-// },
-// }, 'Insufficient balance for transaction')
-// : null,
-
-// // send + cancel
-// h('.flex-row.flex-space-around.conf-buttons', {
-// style: {
-// display: 'flex',
-// justifyContent: 'flex-end',
-// margin: '14px 25px',
-// },
-// }, [
-// h('button', {
-// onClick: (event) => {
-// this.resetGasFields()
-// event.preventDefault()
-// },
-// }, 'Reset'),
-
-// // Accept Button or Buy Button
-// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
-// h('input.confirm.btn-green', {
-// type: 'submit',
-// value: 'SUBMIT',
-// style: { marginLeft: '10px' },
-// disabled: buyDisabled,
-// }),
-
-// h('button.cancel.btn-red', {
-// onClick: props.cancelTransaction,
-// }, 'Reject'),
-// ]),
-// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
-// style: {
-// display: 'flex',
-// justifyContent: 'flex-end',
-// margin: '14px 25px',
-// },
-// }, [
-// h('button.cancel.btn-red', {
-// onClick: props.cancelAllTransactions,
-// }, 'Reject All'),
-// ]) : null,
-// ]),
-// ])
-// )
-// }
- ]),
-
- h('form#pending-tx-form', {
- className: 'confirm-screen-form',
- onSubmit: this.onSubmit,
- }, [
- this.renderErrorMessage('simulationFails'),
- h('.page-container__footer', [
- // Cancel Button
- h('button.btn-cancel.page-container__footer-button.allcaps', {
- onClick: (event) => {
- clearSend()
- this.cancel(event, txMeta)
- },
- }, this.context.t('cancel')),
-
- // Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', {
- onClick: event => this.onSubmit(event),
- }, this.context.t('confirm')),
- ]),
- ]),
- ])
- )
-}
-
-ConfirmSendEther.prototype.renderErrorMessage = function (message) {
- const { send: { errors } } = this.props
-
- return errors[message]
- ? h('div.confirm-screen-error', [ errors[message] ])
- : null
-}
-
-ConfirmSendEther.prototype.onSubmit = function (event) {
- event.preventDefault()
- const { updateSendErrors } = this.props
- const txMeta = this.gatherTxMeta()
- const valid = this.checkValidity()
- const balanceIsSufficient = this.isBalanceSufficient(txMeta)
- this.setState({ valid, submitting: true })
-
- if (valid && this.verifyGasParams() && balanceIsSufficient) {
- this.props.sendTransaction(txMeta, event)
- } else if (!balanceIsSufficient) {
- updateSendErrors({ insufficientFunds: 'insufficientFunds' })
- } else {
- updateSendErrors({ invalidGasParams: 'invalidGasParams' })
- this.setState({ submitting: false })
- }
-}
-
-ConfirmSendEther.prototype.cancel = function (event, txMeta) {
- event.preventDefault()
- const { cancelTransaction } = this.props
-
- cancelTransaction(txMeta)
- .then(() => this.props.history.push(DEFAULT_ROUTE))
-}
-
-ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
- const {
- balance,
- conversionRate,
- } = this.props
- const {
- txParams: {
- gas,
- gasPrice,
- value: amount,
- },
- } = txMeta
- const gasTotal = calcGasTotal(gas, gasPrice)
-
- return isBalanceSufficient({
- amount,
- gasTotal,
- balance,
- conversionRate,
- })
-}
-
-ConfirmSendEther.prototype.checkValidity = function () {
- const form = this.getFormEl()
- const valid = form.checkValidity()
- return valid
-}
-
-ConfirmSendEther.prototype.getFormEl = function () {
- const form = document.querySelector('form#pending-tx-form')
- // Stub out form for unit tests:
- if (!form) {
- return { checkValidity () { return true } }
- }
- return form
-}
-
-// After a customizable state value has been updated,
-ConfirmSendEther.prototype.gatherTxMeta = function () {
- const props = this.props
- const state = this.state
- const txData = clone(state.txData) || clone(props.txData)
-
- const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send
- const {
- lastGasPrice,
- txParams: {
- gasPrice: txGasPrice,
- gas: txGasLimit,
- },
- } = txData
-
- let forceGasMin
- if (lastGasPrice) {
- forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
- multiplicandBase: 16,
- multiplierBase: 10,
- toNumericBase: 'hex',
- }))
- }
-
- txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice
- txData.txParams.gas = sendGasLimit || txGasLimit
-
- // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
- return txData
-}
-
-ConfirmSendEther.prototype.verifyGasParams = function () {
- // We call this in case the gas has not been modified at all
- if (!this.state) { return true }
- return (
- this._notZeroOrEmptyString(this.state.gas) &&
- this._notZeroOrEmptyString(this.state.gasPrice)
- )
-}
-
-ConfirmSendEther.prototype._notZeroOrEmptyString = function (obj) {
- return obj !== '' && obj !== '0x0'
-}
-
-ConfirmSendEther.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) {
- const numBN = new BN(numerator)
- const denomBN = new BN(denominator)
- return targetBN.mul(numBN).div(denomBN)
-}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
deleted file mode 100644
index 818853882..000000000
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ /dev/null
@@ -1,696 +0,0 @@
-const Component = require('react').Component
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const tokenAbi = require('human-standard-token-abi')
-const abiDecoder = require('abi-decoder')
-abiDecoder.addABI(tokenAbi)
-const actions = require('../../actions')
-const clone = require('clone')
-const Identicon = require('../identicon')
-const GasFeeDisplay = require('../send/send-content/send-gas-row/gas-fee-display/').default
-const NetworkDisplay = require('../network-display')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
-const {
- conversionUtil,
- multiplyCurrencies,
- addCurrencies,
-} = require('../../conversion-util')
-const {
- calcGasTotal,
- isBalanceSufficient,
-} = require('../send/send.utils')
-const {
- calcTokenAmount,
-} = require('../../token-util')
-const classnames = require('classnames')
-const currencyFormatter = require('currency-formatter')
-const currencies = require('currency-formatter/currencies')
-
-const { MIN_GAS_PRICE_HEX } = require('../send/send.constants')
-
-const {
- getTokenExchangeRate,
- getSelectedAddress,
- getSelectedTokenContract,
-} = require('../../selectors')
-const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
-
-import {
- updateSendErrors,
-} from '../../ducks/send.duck'
-
-const {
- ENVIRONMENT_TYPE_POPUP,
- ENVIRONMENT_TYPE_NOTIFICATION,
-} = require('../../../../app/scripts/lib/enums')
-
-ConfirmSendToken.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ConfirmSendToken)
-
-
-function mapStateToProps (state, ownProps) {
- const { token: { address }, txData } = ownProps
- const { txParams } = txData || {}
- const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data)
-
- const {
- conversionRate,
- identities,
- currentCurrency,
- } = state.metamask
- const accounts = state.metamask.accounts
- const selectedAddress = getSelectedAddress(state)
- const tokenExchangeRate = getTokenExchangeRate(state, address)
- const { balance } = accounts[selectedAddress]
- return {
- conversionRate,
- identities,
- selectedAddress,
- tokenExchangeRate,
- tokenData: tokenData || {},
- currentCurrency: currentCurrency.toUpperCase(),
- send: state.metamask.send,
- tokenContract: getSelectedTokenContract(state),
- balance,
- }
-}
-
-function mapDispatchToProps (dispatch, ownProps) {
- return {
- backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
- cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
- editTransaction: txMeta => {
- const { token: { address } } = ownProps
- const { txParams = {}, id } = txMeta
- const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) || {}
- const { params = [] } = tokenData
- const { value: to } = params[0] || {}
- const { value: tokenAmountInDec } = params[1] || {}
- const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
- fromNumericBase: 'dec',
- toNumericBase: 'hex',
- })
- const {
- gas: gasLimit,
- gasPrice,
- } = txParams
- dispatch(actions.setSelectedToken(address))
- dispatch(actions.updateSend({
- gasLimit,
- gasPrice,
- gasTotal: null,
- to,
- amount: tokenAmountInHex,
- errors: { to: null, amount: null },
- editingTransactionId: id && id.toString(),
- token: ownProps.token,
- }))
- dispatch(actions.showSendTokenPage())
- },
- showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
- const { id, txParams, lastGasPrice } = txMeta
- const { gas: txGasLimit, gasPrice: txGasPrice } = txParams
- const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data)
- const { params = [] } = tokenData
- const { value: to } = params[0] || {}
- const { value: tokenAmountInDec } = params[1] || {}
- const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
- fromNumericBase: 'dec',
- toNumericBase: 'hex',
- })
-
- let forceGasMin
- if (lastGasPrice) {
- forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
- multiplicandBase: 16,
- multiplierBase: 10,
- toNumericBase: 'hex',
- fromDenomination: 'WEI',
- }))
- }
-
- dispatch(actions.updateSend({
- gasLimit: sendGasLimit || txGasLimit,
- gasPrice: sendGasPrice || txGasPrice,
- editingTransactionId: id,
- gasTotal: sendGasTotal,
- to,
- amount: tokenAmountInHex,
- forceGasMin,
- }))
- dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
- },
- updateSendErrors: error => dispatch(updateSendErrors(error)),
- }
-}
-
-inherits(ConfirmSendToken, Component)
-function ConfirmSendToken () {
- Component.call(this)
- this.state = {}
- this.onSubmit = this.onSubmit.bind(this)
-}
-
-ConfirmSendToken.prototype.editTransaction = function (txMeta) {
- const { editTransaction, history } = this.props
- editTransaction(txMeta)
- history.push(SEND_ROUTE)
-}
-
-ConfirmSendToken.prototype.updateComponentSendErrors = function (prevProps) {
- const {
- balance: oldBalance,
- conversionRate: oldConversionRate,
- } = prevProps
- const {
- updateSendErrors,
- balance,
- conversionRate,
- send: {
- errors: {
- simulationFails,
- },
- },
- } = this.props
- const txMeta = this.gatherTxMeta()
-
- const shouldUpdateBalanceSendErrors = balance && [
- balance !== oldBalance,
- conversionRate !== oldConversionRate,
- ].some(x => Boolean(x))
-
- if (shouldUpdateBalanceSendErrors) {
- const balanceIsSufficient = this.isBalanceSufficient(txMeta)
- updateSendErrors({
- insufficientFunds: balanceIsSufficient ? false : this.context.t('insufficientFunds'),
- })
- }
-
- const shouldUpdateSimulationSendError = Boolean(txMeta.simulationFails) !== Boolean(simulationFails)
-
- if (shouldUpdateSimulationSendError) {
- updateSendErrors({
- simulationFails: !txMeta.simulationFails ? false : this.context.t('transactionError'),
- })
- }
-}
-
-ConfirmSendToken.prototype.componentWillMount = function () {
- const { tokenContract, selectedAddress } = this.props
- tokenContract && tokenContract
- .balanceOf(selectedAddress)
- .then(usersToken => {
- })
- this.updateComponentSendErrors({})
-}
-
-ConfirmSendToken.prototype.componentDidUpdate = function (prevProps) {
- this.updateComponentSendErrors(prevProps)
-}
-
-ConfirmSendToken.prototype.getAmount = function () {
- const {
- conversionRate,
- tokenExchangeRate,
- token,
- tokenData,
- send: { amount, editingTransactionId },
- } = this.props
- const { params = [] } = tokenData
- let { value } = params[1] || {}
- const { decimals } = token
-
- if (editingTransactionId) {
- value = conversionUtil(amount, {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- })
- }
-
- const sendTokenAmount = calcTokenAmount(value, decimals)
-
- return {
- fiat: tokenExchangeRate
- ? +(sendTokenAmount * tokenExchangeRate * conversionRate).toFixed(2)
- : null,
- token: typeof value === 'undefined'
- ? this.context.t('unknown')
- : +sendTokenAmount.toFixed(decimals),
- }
-
-}
-
-ConfirmSendToken.prototype.getGasFee = function () {
- const { conversionRate, tokenExchangeRate, token, currentCurrency } = this.props
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
- const { decimals } = token
-
- const gas = txParams.gas
- const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_HEX
- const gasTotal = multiplyCurrencies(gas, gasPrice, {
- multiplicandBase: 16,
- multiplierBase: 16,
- })
-
- const FIAT = conversionUtil(gasTotal, {
- fromNumericBase: 'BN',
- toNumericBase: 'dec',
- fromDenomination: 'WEI',
- fromCurrency: 'ETH',
- toCurrency: currentCurrency,
- numberOfDecimals: 2,
- conversionRate,
- })
- const ETH = conversionUtil(gasTotal, {
- fromNumericBase: 'BN',
- toNumericBase: 'dec',
- fromDenomination: 'WEI',
- fromCurrency: 'ETH',
- toCurrency: 'ETH',
- numberOfDecimals: 6,
- conversionRate,
- })
- const tokenGas = multiplyCurrencies(gas, gasPrice, {
- toNumericBase: 'dec',
- multiplicandBase: 16,
- multiplierBase: 16,
- toCurrency: 'BAT',
- conversionRate: tokenExchangeRate,
- invertConversionRate: true,
- fromDenomination: 'WEI',
- numberOfDecimals: decimals || 4,
- })
-
- return {
- fiat: +Number(FIAT).toFixed(2),
- eth: ETH,
- token: tokenExchangeRate
- ? tokenGas
- : null,
- gasFeeInHex: gasTotal.toString(16),
- }
-}
-
-ConfirmSendToken.prototype.getData = function () {
- const { identities, tokenData } = this.props
- const { params = [] } = tokenData
- const { value } = params[0] || {}
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
-
- return {
- from: {
- address: txParams.from,
- name: identities[txParams.from].name,
- },
- to: {
- address: value,
- name: identities[value] ? identities[value].name : this.context.t('newRecipient'),
- },
- memo: txParams.memo || '',
- }
-}
-
-ConfirmSendToken.prototype.renderHeroAmount = function () {
- const { token: { symbol }, currentCurrency } = this.props
- const { fiat: fiatAmount, token: tokenAmount } = this.getAmount()
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
- const { memo = '' } = txParams
-
- const convertedAmountInFiat = this.convertToRenderableCurrency(fiatAmount, currentCurrency)
-
- return fiatAmount
- ? (
- h('div.confirm-send-token__hero-amount-wrapper', [
- h('h3.flex-center.confirm-screen-send-amount', `${convertedAmountInFiat}`),
- h('h3.flex-center.confirm-screen-send-amount-currency', currentCurrency),
- h('div.flex-center.confirm-memo-wrapper', [
- h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
- ]),
- ])
- )
- : (
- h('div.confirm-send-token__hero-amount-wrapper', [
- h('h3.flex-center.confirm-screen-send-amount', tokenAmount),
- h('h3.flex-center.confirm-screen-send-amount-currency', symbol),
- h('div.flex-center.confirm-memo-wrapper', [
- h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
- ]),
- ])
- )
-}
-
-ConfirmSendToken.prototype.renderGasFee = function () {
- const {
- currentCurrency: convertedCurrency,
- conversionRate,
- send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice },
- showCustomizeGasModal,
- } = this.props
- const txMeta = this.gatherTxMeta()
- const { gasFeeInHex } = this.getGasFee()
-
- return (
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('gasFee') ]),
- h('div.confirm-screen-section-column', [
- h(GasFeeDisplay, {
- gasTotal: gasTotal || gasFeeInHex,
- conversionRate,
- convertedCurrency,
- onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal),
- }),
- ]),
- ])
- )
-}
-
-ConfirmSendToken.prototype.renderTotalPlusGas = function () {
- const { token: { symbol }, currentCurrency, send: { errors } } = this.props
- const { fiat: fiatAmount, token: tokenAmount } = this.getAmount()
- const { fiat: fiatGas, token: tokenGas } = this.getGasFee()
-
- const totalInFIAT = fiatAmount && fiatGas && addCurrencies(fiatAmount, fiatGas)
- const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
-
- return fiatAmount && fiatGas
- ? (
- h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
- h('div.confirm-screen-section-column', [
- h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]),
- h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]),
- ]),
-
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency}`),
- h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`),
- ]),
- ])
- )
- : (
- h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
- h('div', {
- className: classnames({
- 'confirm-screen-section-column--with-error': errors['insufficientFunds'],
- 'confirm-screen-section-column': !errors['insufficientFunds'],
- }),
- }, [
- h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]),
- h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]),
- ]),
-
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`),
- h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} ${this.context.t('gas')}`),
- ]),
-
- this.renderErrorMessage('insufficientFunds'),
- ])
- )
-}
-
-ConfirmSendToken.prototype.renderErrorMessage = function (message) {
- const { send: { errors } } = this.props
-
- return errors[message]
- ? h('div.confirm-screen-error', [ errors[message] ])
- : null
-}
-
-ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, currencyCode) {
- const upperCaseCurrencyCode = currencyCode.toUpperCase()
-
- return currencies.find(currency => currency.code === upperCaseCurrencyCode)
- ? currencyFormatter.format(Number(value), {
- code: upperCaseCurrencyCode,
- })
- : value
-}
-
-ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) {
- const windowType = window.METAMASK_UI_TYPE
- const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
- windowType !== ENVIRONMENT_TYPE_POPUP
-
- if (isTxReprice && isFullScreen) {
- return null
- }
-
- return (
- h('.page-container__header-row', [
- h('span.page-container__back-button', {
- onClick: () => this.editTransaction(),
- style: {
- visibility: isTxReprice ? 'hidden' : 'initial',
- },
- }, 'Edit'),
- !isFullScreen && h(NetworkDisplay),
- ])
- )
-}
-
-ConfirmSendToken.prototype.renderHeader = function (isTxReprice) {
- const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
- const subtitle = isTxReprice
- ? this.context.t('speedUpSubtitle')
- : this.context.t('pleaseReviewTransaction')
-
- return (
- h('.page-container__header', [
- this.renderHeaderRow(isTxReprice),
- h('.page-container__title', title),
- h('.page-container__subtitle', subtitle),
- ])
- )
-}
-
-ConfirmSendToken.prototype.render = function () {
- const txMeta = this.gatherTxMeta()
- const {
- from: {
- address: fromAddress,
- name: fromName,
- },
- to: {
- address: toAddress,
- name: toName,
- },
- } = this.getData()
-
- const isTxReprice = Boolean(txMeta.lastGasPrice)
-
- return (
- h('div.confirm-screen-container.confirm-send-token', [
- // Main Send token Card
- h('div.page-container', [
- this.renderHeader(isTxReprice),
- h('.page-container__content', [
- h('div.flex-row.flex-center.confirm-screen-identicons', [
- h('div.confirm-screen-account-wrapper', [
- h(
- Identicon,
- {
- address: fromAddress,
- diameter: 60,
- },
- ),
- h('span.confirm-screen-account-name', fromName),
- // h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)),
- ]),
- h('i.fa.fa-arrow-right.fa-lg'),
- h('div.confirm-screen-account-wrapper', [
- h(
- Identicon,
- {
- address: toAddress,
- diameter: 60,
- },
- ),
- h('span.confirm-screen-account-name', toName),
- // h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)),
- ]),
- ]),
-
- // h('h3.flex-center.confirm-screen-sending-to-message', {
- // style: {
- // textAlign: 'center',
- // fontSize: '16px',
- // },
- // }, [
- // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
- // ]),
-
- this.renderHeroAmount(),
-
- h('div.confirm-screen-rows', [
- h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('from') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', fromName),
- h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
- ]),
- ]),
-
- toAddress && h('section.flex-row.flex-center.confirm-screen-row', [
- h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('to') ]),
- h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', toName),
- h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
- ]),
- ]),
-
- this.renderGasFee(),
-
- this.renderTotalPlusGas(),
-
- ]),
-
- ]),
-
- h('form#pending-tx-form', {
- className: 'confirm-screen-form',
- onSubmit: this.onSubmit,
- }, [
- this.renderErrorMessage('simulationFails'),
- h('.page-container__footer', [
- // Cancel Button
- h('button.btn-cancel.page-container__footer-button.allcaps', {
- onClick: (event) => this.cancel(event, txMeta),
- }, this.context.t('cancel')),
-
- // Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', {
- onClick: event => this.onSubmit(event),
- }, [this.context.t('confirm')]),
- ]),
- ]),
- ]),
- ])
- )
-}
-
-ConfirmSendToken.prototype.onSubmit = function (event) {
- event.preventDefault()
- const { updateSendErrors } = this.props
- const txMeta = this.gatherTxMeta()
- const valid = this.checkValidity()
- const balanceIsSufficient = this.isBalanceSufficient(txMeta)
- this.setState({ valid, submitting: true })
-
- if (valid && this.verifyGasParams() && balanceIsSufficient) {
- this.props.sendTransaction(txMeta, event)
- } else if (!balanceIsSufficient) {
- updateSendErrors({ insufficientFunds: 'insufficientFunds' })
- } else {
- updateSendErrors({ invalidGasParams: 'invalidGasParams' })
- this.setState({ submitting: false })
- }
-}
-
-ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) {
- const {
- balance,
- conversionRate,
- } = this.props
- const {
- txParams: {
- gas,
- gasPrice,
- },
- } = txMeta
- const gasTotal = calcGasTotal(gas, gasPrice)
-
- return isBalanceSufficient({
- amount: '0',
- gasTotal,
- balance,
- conversionRate,
- })
-}
-
-
-ConfirmSendToken.prototype.cancel = function (event, txMeta) {
- event.preventDefault()
- const { cancelTransaction } = this.props
-
- cancelTransaction(txMeta)
- .then(() => this.props.history.push(DEFAULT_ROUTE))
-}
-
-ConfirmSendToken.prototype.checkValidity = function () {
- const form = this.getFormEl()
- const valid = form.checkValidity()
- return valid
-}
-
-ConfirmSendToken.prototype.getFormEl = function () {
- const form = document.querySelector('form#pending-tx-form')
- // Stub out form for unit tests:
- if (!form) {
- return { checkValidity () { return true } }
- }
- return form
-}
-
-// After a customizable state value has been updated,
-ConfirmSendToken.prototype.gatherTxMeta = function () {
- const props = this.props
- const state = this.state
- const txData = clone(state.txData) || clone(props.txData)
-
- const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send
- const {
- lastGasPrice,
- txParams: {
- gasPrice: txGasPrice,
- gas: txGasLimit,
- },
- } = txData
-
- let forceGasMin
- if (lastGasPrice) {
- forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
- multiplicandBase: 16,
- multiplierBase: 10,
- toNumericBase: 'hex',
- }))
- }
-
- txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice
- txData.txParams.gas = sendGasLimit || txGasLimit
-
- // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
- return txData
-}
-
-ConfirmSendToken.prototype.verifyGasParams = function () {
- // We call this in case the gas has not been modified at all
- if (!this.state) { return true }
- return (
- this._notZeroOrEmptyString(this.state.gas) &&
- this._notZeroOrEmptyString(this.state.gasPrice)
- )
-}
-
-ConfirmSendToken.prototype._notZeroOrEmptyString = function (obj) {
- return obj !== '' && obj !== '0x0'
-}
-
-ConfirmSendToken.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) {
- const numBN = new BN(numerator)
- const denomBN = new BN(denominator)
- return targetBN.mul(numBN).div(denomBN)
-}
diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js
deleted file mode 100644
index 3f8cd8823..000000000
--- a/ui/app/components/pending-tx/index.js
+++ /dev/null
@@ -1,165 +0,0 @@
-const Component = require('react').Component
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const clone = require('clone')
-const abi = require('human-standard-token-abi')
-const abiDecoder = require('abi-decoder')
-abiDecoder.addABI(abi)
-const inherits = require('util').inherits
-const actions = require('../../actions')
-const { getSymbolAndDecimals } = require('../../token-util')
-const ConfirmSendEther = require('./confirm-send-ether')
-const ConfirmSendToken = require('./confirm-send-token')
-const ConfirmDeployContract = require('./confirm-deploy-contract')
-const Loading = require('../loading-screen')
-
-const TX_TYPES = {
- DEPLOY_CONTRACT: 'deploy_contract',
- SEND_ETHER: 'send_ether',
- SEND_TOKEN: 'send_token',
-}
-
-module.exports = connect(mapStateToProps, mapDispatchToProps)(PendingTx)
-
-function mapStateToProps (state) {
- const {
- conversionRate,
- identities,
- tokens: existingTokens,
- } = state.metamask
- const accounts = state.metamask.accounts
- const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- return {
- conversionRate,
- identities,
- selectedAddress,
- existingTokens,
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
- cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
- }
-}
-
-inherits(PendingTx, Component)
-function PendingTx () {
- Component.call(this)
- this.state = {
- isFetching: true,
- transactionType: '',
- tokenAddress: '',
- tokenSymbol: '',
- tokenDecimals: '',
- }
-}
-
-PendingTx.prototype.componentDidMount = function () {
- this.setTokenData()
-}
-
-PendingTx.prototype.componentDidUpdate = function (prevProps, prevState) {
- if (prevState.isFetching) {
- this.setTokenData()
- }
-}
-
-PendingTx.prototype.setTokenData = async function () {
- const { existingTokens } = this.props
- const txMeta = this.gatherTxMeta()
- const txParams = txMeta.txParams || {}
-
- if (txMeta.loadingDefaults) {
- return
- }
-
- if (!txParams.to) {
- return this.setState({
- transactionType: TX_TYPES.DEPLOY_CONTRACT,
- isFetching: false,
- })
- }
-
- // inspect tx data for supported special confirmation screens
- let isTokenTransaction = false
- if (txParams.data) {
- const tokenData = abiDecoder.decodeMethod(txParams.data)
- const { name: tokenMethodName } = tokenData || {}
- isTokenTransaction = (tokenMethodName === 'transfer')
- }
-
- if (isTokenTransaction) {
- const { symbol, decimals } = await getSymbolAndDecimals(txParams.to, existingTokens)
-
- this.setState({
- transactionType: TX_TYPES.SEND_TOKEN,
- tokenAddress: txParams.to,
- tokenSymbol: symbol,
- tokenDecimals: decimals,
- isFetching: false,
- })
- } else {
- this.setState({
- transactionType: TX_TYPES.SEND_ETHER,
- isFetching: false,
- })
- }
-}
-
-PendingTx.prototype.gatherTxMeta = function () {
- const props = this.props
- const state = this.state
- const txData = clone(state.txData) || clone(props.txData)
-
- return txData
-}
-
-PendingTx.prototype.render = function () {
- const {
- isFetching,
- transactionType,
- tokenAddress,
- tokenSymbol,
- tokenDecimals,
- } = this.state
-
- const { sendTransaction } = this.props
-
- if (isFetching) {
- return h(Loading, {
- loadingMessage: this.context.t('generatingTransaction'),
- })
- }
-
- switch (transactionType) {
- case TX_TYPES.SEND_ETHER:
- return h(ConfirmSendEther, {
- txData: this.gatherTxMeta(),
- sendTransaction,
- })
- case TX_TYPES.SEND_TOKEN:
- return h(ConfirmSendToken, {
- txData: this.gatherTxMeta(),
- sendTransaction,
- token: {
- address: tokenAddress,
- symbol: tokenSymbol,
- decimals: tokenDecimals,
- },
- })
- case TX_TYPES.DEPLOY_CONTRACT:
- return h(ConfirmDeployContract, {
- txData: this.gatherTxMeta(),
- sendTransaction,
- })
- default:
- return h(Loading)
- }
-}
-
-PendingTx.contextTypes = {
- t: PropTypes.func,
-}
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 4e8aaa07d..112ea6bca 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -9,11 +9,7 @@ const txHelper = require('../lib/tx-helper')
const log = require('loglevel')
const R = require('ramda')
-const PendingTx = require('./components/pending-tx')
const SignatureRequest = require('./components/signature-request')
-// const PendingMsg = require('./components/pending-msg')
-// const PendingPersonalMsg = require('./components/pending-personal-msg')
-// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading-screen')
const { DEFAULT_ROUTE } = require('./routes')
@@ -151,101 +147,32 @@ ConfirmTxScreen.prototype.render = function () {
currentCurrency,
conversionRate,
blockGasLimit,
- // provider,
- // computedBalances,
} = props
var txData = this.getTxData() || {}
- var txParams = txData.params || {}
-
- // var isNotification = isPopupOrNotification() === 'notification'
- /*
- Client is using the flag above to render the following in conf screen
- // subtitle and nav
- h('.section-title.flex-row.flex-center', [
- !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: this.goHome.bind(this),
- }) : null,
- h('h2.page-subtitle', 'Confirm Transaction'),
- isNotification ? h(NetworkIndicator, {
- network: network,
- provider: provider,
- }) : null,
- ]),
- */
-
- return currentTxView({
- // Properties
- txData: txData,
- key: txData.id,
- selectedAddress: props.selectedAddress,
- accounts: props.accounts,
- identities: props.identities,
- conversionRate,
- currentCurrency,
- blockGasLimit,
- // Actions
- buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
- sendTransaction: this.sendTransaction.bind(this),
- cancelTransaction: this.cancelTransaction.bind(this, txData),
- signMessage: this.signMessage.bind(this, txData),
- signPersonalMessage: this.signPersonalMessage.bind(this, txData),
- signTypedMessage: this.signTypedMessage.bind(this, txData),
- cancelMessage: this.cancelMessage.bind(this, txData),
- cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
- cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
- })
-}
-
-function currentTxView (opts) {
- log.info('rendering current tx view')
- const { txData } = opts
- const { txParams, msgParams } = txData
-
- if (txParams) {
- log.debug('txParams detected, rendering pending tx')
- return h(PendingTx, opts)
- } else if (msgParams) {
- log.debug('msgParams detected, rendering pending msg')
-
- return h(SignatureRequest, opts)
-
- // if (type === 'eth_sign') {
- // log.debug('rendering eth_sign message')
- // return h(PendingMsg, opts)
- // } else if (type === 'personal_sign') {
- // log.debug('rendering personal_sign message')
- // return h(PendingPersonalMsg, opts)
- // } else if (type === 'eth_signTypedData') {
- // log.debug('rendering eth_signTypedData message')
- // return h(PendingTypedMsg, opts)
- // }
- }
-
- return h(Loading)
-}
-
-ConfirmTxScreen.prototype.buyEth = function (address, event) {
- event.preventDefault()
- this.props.dispatch(actions.buyEthView(address))
-}
-
-ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
- this.stopPropagation(event)
- this.props.dispatch(actions.updateAndApproveTx(txData))
- .then(() => this.props.history.push(DEFAULT_ROUTE))
-}
-
-ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
- this.stopPropagation(event)
- event.preventDefault()
- this.props.dispatch(actions.cancelTx(txData))
-}
-
-ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
- this.stopPropagation(event)
- event.preventDefault()
- this.props.dispatch(actions.cancelAllTx(unconfTxList))
+ const { msgParams } = txData
+ log.debug('msgParams detected, rendering pending msg')
+
+ return msgParams
+ ? h(SignatureRequest, {
+ // Properties
+ txData: txData,
+ key: txData.id,
+ selectedAddress: props.selectedAddress,
+ accounts: props.accounts,
+ identities: props.identities,
+ conversionRate,
+ currentCurrency,
+ blockGasLimit,
+ // Actions
+ signMessage: this.signMessage.bind(this, txData),
+ signPersonalMessage: this.signPersonalMessage.bind(this, txData),
+ signTypedMessage: this.signTypedMessage.bind(this, txData),
+ cancelMessage: this.cancelMessage.bind(this, txData),
+ cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
+ cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
+ })
+ : h(Loading)
}
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
@@ -295,20 +222,3 @@ ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
this.stopPropagation(event)
return this.props.dispatch(actions.cancelTypedMsg(msgData))
}
-
-ConfirmTxScreen.prototype.goHome = function (event) {
- this.stopPropagation(event)
- this.props.dispatch(actions.goHome())
-}
-
-// function warningIfExists (warning) {
-// if (warning &&
-// // Do not display user rejections on this screen:
-// warning.indexOf('User denied transaction signature') === -1) {
-// return h('.error', {
-// style: {
-// margin: 'auto',
-// },
-// }, warning)
-// }
-// }
diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js
index ad247a348..1373d28df 100644
--- a/ui/app/helpers/confirm-transaction/util.js
+++ b/ui/app/helpers/confirm-transaction/util.js
@@ -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,
+ })
+}
diff --git a/ui/app/routes.js b/ui/app/routes.js
index 7ac606b1a..9c219115b 100644
--- a/ui/app/routes.js
+++ b/ui/app/routes.js
@@ -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,
}
diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js
index cde83804d..54016a30e 100644
--- a/ui/app/selectors/confirm-transaction.js
+++ b/ui/app/selectors/confirm-transaction.js
@@ -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 ? Number(valueParam.value) : Number(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 = Number(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 = Number(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]
+)