From de6ab01e4aa056f8bb336830fb519fdb49bfa71a Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 10 Jun 2020 23:51:12 +0300 Subject: [PATCH 1/2] "Send all" coins option --- CHANGELOG.md | 1 + app/scripts/controllers/transactions/index.js | 2 +- .../controllers/transactions/tx-gas-utils.js | 52 +-- app/scripts/metamask-controller.js | 5 +- .../src/app/first-time/confirm-seed-screen.js | 3 +- .../app/first-time/create-password-screen.js | 3 +- mascara/src/app/first-time/seed-screen.js | 3 +- mascara/src/app/shapeshift-form/index.js | 3 +- old-ui/app/add-suggested-token.js | 2 +- .../add-token/add-token.component.js | 3 +- old-ui/app/components/ens-input.js | 343 +++++++++--------- old-ui/app/components/pending-tx.js | 13 +- .../amount-max-button.component.js | 75 ++++ .../amount-max-button.container.js | 49 +++ .../amount-max-button.utils.js | 32 ++ .../send/amount-max-button/index.js | 1 + .../tests/amount-max-button-component.test.js | 97 +++++ .../tests/amount-max-button-container.test.js | 93 +++++ .../tests/amount-max-button-utils.test.js | 27 ++ old-ui/app/components/send/send-contract.js | 3 +- old-ui/app/components/send/send-token.js | 2 +- old-ui/app/components/send/send.js | 87 ++++- old-ui/app/css/index.css | 41 ++- .../transactions/tx-gas-util-test.js | 2 +- ui/app/actions.js | 3 +- ui/app/conversion-util.js | 1 + ui/app/selectors.js | 94 ++++- ui/app/welcome-screen.js | 3 +- 28 files changed, 789 insertions(+), 254 deletions(-) create mode 100644 old-ui/app/components/send/amount-max-button/amount-max-button.component.js create mode 100644 old-ui/app/components/send/amount-max-button/amount-max-button.container.js create mode 100644 old-ui/app/components/send/amount-max-button/amount-max-button.utils.js create mode 100644 old-ui/app/components/send/amount-max-button/index.js create mode 100644 old-ui/app/components/send/amount-max-button/tests/amount-max-button-component.test.js create mode 100644 old-ui/app/components/send/amount-max-button/tests/amount-max-button-container.test.js create mode 100644 old-ui/app/components/send/amount-max-button/tests/amount-max-button-utils.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed48a909329..188730bb2c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- [#388](https://github.com/poanetwork/nifty-wallet/pull/388) - (Feature) "Send all" option for simple coin transfers - [#385](https://github.com/poanetwork/nifty-wallet/pull/385) - (Feature) Display value of current pending tx's nonce on send tx screen - [#384](https://github.com/poanetwork/nifty-wallet/pull/384) - (Fix) placement of HW Connect button title - [#383](https://github.com/poanetwork/nifty-wallet/pull/383) - (Chore) Replace POA-ETH Binance link to POA-BTC diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 19e7d60c7623..c91e41ea0602 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -10,7 +10,7 @@ import abiDecoder from 'abi-decoder' abiDecoder.addABI(abi) import TransactionStateManager from './tx-state-manager' -const TxGasUtil = require('./tx-gas-utils') +import TxGasUtil from './tx-gas-utils' const PendingTransactionTracker = require('./pending-tx-tracker') import NonceTracker from 'nonce-tracker' import * as txUtils from './lib/util' diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index f02172e0ded3..cdc218d8f4d1 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -1,11 +1,8 @@ -const EthQuery = require('ethjs-query') -const { - hexToBn, - BnMultiplyByFraction, - bnToHex, -} = require('../../lib/util') -const { addHexPrefix } = require('ethereumjs-util') -const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. +import EthQuery from 'ethjs-query' +import { hexToBn, BnMultiplyByFraction, bnToHex } from '../../lib/util' +import { addHexPrefix } from 'ethereumjs-util' +import { MIN_GAS_LIMIT_HEX } from '../../../../ui/app/components/send/send.constants' +import log from 'loglevel' /** tx-gas-utils are gas utility methods for Transaction manager @@ -14,24 +11,31 @@ and used to do things like calculate gas of a tx. @param {Object} provider - A network provider. */ -class TxGasUtil { +export default class TxGasUtil { constructor (provider) { this.query = new EthQuery(provider) } /** - @param txMeta {Object} - the txMeta object - @returns {object} the txMeta object with the gas written to the txParams + @param {Object} txMeta - the txMeta object + @returns {GasAnalysisResult} The result of the gas analysis */ async analyzeGasUsage (txMeta) { const block = await this.query.getBlockByNumber('latest', false) - let estimatedGasHex + + // fallback to block gasLimit + const blockGasLimitBN = hexToBn(block.gasLimit) + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) + let estimatedGasHex = bnToHex(saferGasLimitBN) try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) - } catch (err) { + } catch (error) { + log.warn(error) txMeta.simulationFails = { - reason: err.message, + reason: error.message, + errorKey: error.errorKey, + debug: { blockNumber: block.number, blockGasLimit: block.gasLimit }, } return txMeta } @@ -63,9 +67,9 @@ class TxGasUtil { if (recipient) code = await this.query.getCode(recipient) if (hasRecipient && (!code || code === '0x' || code === '0x0')) { - txParams.gas = SIMPLE_GAS_COST + txParams.gas = MIN_GAS_LIMIT_HEX txMeta.simpleSend = true // Prevents buffer addition - return SIMPLE_GAS_COST + return MIN_GAS_LIMIT_HEX } // if not, fall back to block gasLimit @@ -103,9 +107,9 @@ class TxGasUtil { /** Adds a gas buffer with out exceeding the block gas limit - @param initialGasLimitHex {string} - the initial gas limit to add the buffer too - @param blockGasLimitHex {string} - the block gas limit - @returns {string} the buffered gas limit as a hex string + @param {string} initialGasLimitHex - the initial gas limit to add the buffer too + @param {string} blockGasLimitHex - the block gas limit + @returns {string} - the buffered gas limit as a hex string */ addGasBuffer (initialGasLimitHex, blockGasLimitHex) { const initialGasLimitBn = hexToBn(initialGasLimitHex) @@ -114,12 +118,14 @@ class TxGasUtil { const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) + if (initialGasLimitBn.gt(upperGasLimitBn)) { + return bnToHex(initialGasLimitBn) + } // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) + if (bufferedGasLimitBn.lt(upperGasLimitBn)) { + return bnToHex(bufferedGasLimitBn) + } // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) } } - -module.exports = TxGasUtil diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 18e3e275ca83..3a610c20e9b9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -443,7 +443,7 @@ module.exports = class MetamaskController extends EventEmitter { setDProvider: this.setDProvider.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), - getGasPrice: (cb) => cb(null, this.getGasPrice()), + getGasPrice: nodeify(this.getGasPrice, this), getPendingNonce: nodeify(this.getPendingNonce, this), // shapeshift @@ -1870,10 +1870,9 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {string} A hex representation of the suggested wei gas price. */ async getGasPrice () { - return new Promise(async (resolve, reject) => { + return new Promise(async (resolve) => { const { networkController } = this - const networkIdStr = networkController.store.getState().network const networkId = parseInt(networkIdStr) const isETHC = networkId === CLASSIC_CODE || networkId === MAINNET_CODE diff --git a/mascara/src/app/first-time/confirm-seed-screen.js b/mascara/src/app/first-time/confirm-seed-screen.js index a827d82e2c86..ee51e20ac9f9 100644 --- a/mascara/src/app/first-time/confirm-seed-screen.js +++ b/mascara/src/app/first-time/confirm-seed-screen.js @@ -34,7 +34,8 @@ class ConfirmSeedScreen extends Component { } } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { const { seedWords, history } = this.props if (!seedWords) { diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js index 24de839a7288..e505f6a3cdb5 100644 --- a/mascara/src/app/first-time/create-password-screen.js +++ b/mascara/src/app/first-time/create-password-screen.js @@ -40,7 +40,8 @@ class CreatePasswordScreen extends Component { this.animationEventEmitter = new EventEmitter() } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { const { isInitialized, history } = this.props if (isInitialized) { diff --git a/mascara/src/app/first-time/seed-screen.js b/mascara/src/app/first-time/seed-screen.js index 4579e045f848..1083b9e14b28 100644 --- a/mascara/src/app/first-time/seed-screen.js +++ b/mascara/src/app/first-time/seed-screen.js @@ -58,7 +58,8 @@ class BackupPhraseScreen extends Component { } } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { const { seedWords, history } = this.props if (!seedWords) { diff --git a/mascara/src/app/shapeshift-form/index.js b/mascara/src/app/shapeshift-form/index.js index 91c2b36d07ab..4a89a97df733 100644 --- a/mascara/src/app/shapeshift-form/index.js +++ b/mascara/src/app/shapeshift-form/index.js @@ -26,7 +26,8 @@ export class ShapeShiftForm extends Component { isLoading: false, }; - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { this.props.shapeShiftSubview() } diff --git a/old-ui/app/add-suggested-token.js b/old-ui/app/add-suggested-token.js index 5840c327a1f9..c6f4105d5479 100644 --- a/old-ui/app/add-suggested-token.js +++ b/old-ui/app/add-suggested-token.js @@ -159,7 +159,7 @@ AddSuggestedTokenScreen.prototype.render = function () { ) } -AddSuggestedTokenScreen.prototype.componentWillMount = function () { +AddSuggestedTokenScreen.prototype.UNSAFE_componentWillMount = function () { if (typeof global.ethereumProvider === 'undefined') return } diff --git a/old-ui/app/components/add-token/add-token.component.js b/old-ui/app/components/add-token/add-token.component.js index 4bbb9258314c..b04a8fa02683 100644 --- a/old-ui/app/components/add-token/add-token.component.js +++ b/old-ui/app/components/add-token/add-token.component.js @@ -316,7 +316,8 @@ export default class AddTokenScreen extends Component { ]) } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { if (typeof global.ethereumProvider === 'undefined') return this.eth = new Eth(global.ethereumProvider) diff --git a/old-ui/app/components/ens-input.js b/old-ui/app/components/ens-input.js index 42b575076e77..6dee72c6e9fc 100644 --- a/old-ui/app/components/ens-input.js +++ b/old-ui/app/components/ens-input.js @@ -1,200 +1,209 @@ -const Component = require('react').Component +import { Component } from 'react' +import PropTypes from 'prop-types' +import debounce from 'debounce' +import copyToClipboard from 'copy-to-clipboard' +import ENS from 'ethjs-ens' +import log from 'loglevel' + const h = require('react-hyperscript') -const inherits = require('util').inherits -const debounce = require('debounce') -const copyToClipboard = require('copy-to-clipboard') -const ENS = require('ethjs-ens') const networkMap = require('ethjs-ens/lib/network-map.json') const ensRE = /.+\..+$/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -const log = require('loglevel') const { isValidENSAddress } = require('../util') -module.exports = EnsInput - -inherits(EnsInput, Component) -function EnsInput () { - Component.call(this) -} - -EnsInput.prototype.render = function () { - const props = this.props +class EnsInput extends Component { + static propTypes = { + network: PropTypes.string, + onChange: PropTypes.func, + placeholder: PropTypes.string, + name: PropTypes.string, + identities: PropTypes.object, + addressBook: PropTypes.array, + updateSendTo: PropTypes.func, + } - function onInputChange () { - const network = this.props.network - const networkHasEnsSupport = getNetworkEnsSupport(network) - if (!networkHasEnsSupport) return + render () { + const props = this.props + + function onInputChange () { + const recipient = document.querySelector('input[name="address"]').value + this.props.updateSendTo(recipient, ' ') + const network = this.props.network + const networkHasEnsSupport = getNetworkEnsSupport(network) + if (!networkHasEnsSupport) return + + if (recipient.match(ensRE) === null) { + return this.setState({ + loadingEns: false, + ensResolution: null, + ensFailure: null, + toError: null, + }) + } - const recipient = document.querySelector('input[name="address"]').value - if (recipient.match(ensRE) === null) { - return this.setState({ - loadingEns: false, - ensResolution: null, - ensFailure: null, - toError: null, + this.setState({ + loadingEns: true, }) + this.checkName() } - this.setState({ - loadingEns: true, - }) - this.checkName() + return ( + h('div', { + style: { width: '100%' }, + }, [ + h('input.large-input', { + name: props.name, + placeholder: props.placeholder, + list: 'addresses', + onChange: onInputChange.bind(this), + }), + // The address book functionality. + h('datalist#addresses', + [ + // Corresponds to the addresses owned. + Object.keys(props.identities).map((key) => { + const identity = props.identities[key] + return h('option', { + value: identity.address, + label: identity.name, + key: identity.address, + }) + }), + // Corresponds to previously sent-to addresses. + props.addressBook.map((identity) => { + return h('option', { + value: identity.address, + label: identity.name, + key: identity.address, + }) + }), + ]), + this.ensIcon(), + ]) + ) } - return ( - h('div', { - style: { width: '100%' }, - }, [ - h('input.large-input', { - name: props.name, - placeholder: props.placeholder, - list: 'addresses', - onChange: onInputChange.bind(this), - }), - // The address book functionality. - h('datalist#addresses', - [ - // Corresponds to the addresses owned. - Object.keys(props.identities).map((key) => { - const identity = props.identities[key] - return h('option', { - value: identity.address, - label: identity.name, - key: identity.address, - }) - }), - // Corresponds to previously sent-to addresses. - props.addressBook.map((identity) => { - return h('option', { - value: identity.address, - label: identity.name, - key: identity.address, - }) - }), - ]), - this.ensIcon(), - ]) - ) -} - -EnsInput.prototype.componentDidMount = function () { - const network = this.props.network - const networkHasEnsSupport = getNetworkEnsSupport(network) - this.setState({ ensResolution: ZERO_ADDRESS }) + componentDidMount () { + const network = this.props.network + const networkHasEnsSupport = getNetworkEnsSupport(network) + this.setState({ ensResolution: ZERO_ADDRESS }) - if (networkHasEnsSupport) { - const provider = global.ethereumProvider - this.ens = new ENS({ provider, network }) - this.checkName = debounce(this.lookupEnsName.bind(this), 200) + if (networkHasEnsSupport) { + const provider = global.ethereumProvider + this.ens = new ENS({ provider, network }) + this.checkName = debounce(this.lookupEnsName.bind(this), 200) + } } -} -EnsInput.prototype.lookupEnsName = function () { - const recipient = document.querySelector('input[name="address"]').value - const { ensResolution } = this.state - - log.info(`ENS attempting to resolve name: ${recipient}`) - this.ens.lookup(recipient.trim()) - .then((address) => { - if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.') - if (address !== ensResolution) { - this.setState({ + lookupEnsName () { + const recipient = document.querySelector('input[name="address"]').value + const { ensResolution } = this.state + + log.info(`ENS attempting to resolve name: ${recipient}`) + this.ens.lookup(recipient.trim()) + .then((address) => { + if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.') + if (address !== ensResolution) { + this.setState({ + loadingEns: false, + ensResolution: address, + nickname: recipient.trim(), + hoverText: address + '\nClick to Copy', + ensFailure: false, + toError: null, + }) + } + }) + .catch((reason) => { + const setStateObj = { loadingEns: false, - ensResolution: address, - nickname: recipient.trim(), - hoverText: address + '\nClick to Copy', - ensFailure: false, + ensResolution: recipient, + ensFailure: true, toError: null, - }) - } - }) - .catch((reason) => { - const setStateObj = { - loadingEns: false, - ensResolution: recipient, - ensFailure: true, - toError: null, - } - if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { - setStateObj.hoverText = 'ENS name not found' - setStateObj.toError = 'ensNameNotFound' - setStateObj.ensFailure = false - } else { - log.error(reason) - setStateObj.hoverText = reason.message - } - - return this.setState(setStateObj) - }) -} - -EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { - const state = this.state || {} - const ensResolution = state.ensResolution - // If an address is sent without a nickname, meaning not from ENS or from - // the user's own accounts, a default of a one-space string is used. - const nickname = state.nickname || ' ' - if (prevState && ensResolution && this.props.onChange && - ensResolution !== prevState.ensResolution) { - this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning }) + } + if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { + setStateObj.hoverText = 'ENS name not found' + setStateObj.toError = 'ensNameNotFound' + setStateObj.ensFailure = false + } else { + log.error(reason) + setStateObj.hoverText = reason.message + } + + return this.setState(setStateObj) + }) } -} - -EnsInput.prototype.ensIcon = function (recipient) { - const { hoverText } = this.state || {} - return h('span', { - title: hoverText, - style: { - position: 'absolute', - padding: '6px 0px', - right: '0px', - transform: 'translatex(-40px)', - }, - }, this.ensIconContents(recipient)) -} - -EnsInput.prototype.ensIconContents = function (recipient) { - const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS} - - if (toError) return - if (loadingEns) { - return h('img', { - src: 'images/loading.svg', - style: { - width: '30px', - height: '30px', - transform: 'translateY(-6px)', - marginRight: '-5px', - }, - }) + componentDidUpdate (prevProps, prevState) { + const state = this.state || {} + const ensResolution = state.ensResolution + // If an address is sent without a nickname, meaning not from ENS or from + // the user's own accounts, a default of a one-space string is used. + const nickname = state.nickname || ' ' + if (prevState && ensResolution && this.props.onChange && + ensResolution !== prevState.ensResolution) { + this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning }) + } } - if (ensFailure) { - return h('i.fa.fa-warning.fa-lg.warning', { + ensIcon () { + const { hoverText } = this.state || {} + return h('span', { + title: hoverText, style: { - color: '#df2265', - background: 'white', + position: 'absolute', + padding: '6px 0px', + right: '0px', + transform: 'translatex(-40px)', }, - }) + }, this.ensIconContents()) } - if (ensResolution && (ensResolution !== ZERO_ADDRESS)) { - return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', { - style: { - color: '#60db97', - background: 'white', - }, - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - copyToClipboard(ensResolution) - }, - }) + ensIconContents () { + const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS} + + if (toError) return + + if (loadingEns) { + return h('img', { + src: 'images/loading.svg', + style: { + width: '30px', + height: '30px', + transform: 'translateY(-6px)', + marginRight: '-5px', + }, + }) + } + + if (ensFailure) { + return h('i.fa.fa-warning.fa-lg.warning', { + style: { + color: '#df2265', + background: 'white', + }, + }) + } + + if (ensResolution && (ensResolution !== ZERO_ADDRESS)) { + return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', { + style: { + color: '#60db97', + background: 'white', + }, + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(ensResolution) + }, + }) + } } } function getNetworkEnsSupport (network) { return Boolean(networkMap[network]) } + +module.exports = EnsInput diff --git a/old-ui/app/components/pending-tx.js b/old-ui/app/components/pending-tx.js index 4b0a8ce72de9..f019c46621fd 100644 --- a/old-ui/app/components/pending-tx.js +++ b/old-ui/app/components/pending-tx.js @@ -25,10 +25,11 @@ const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util import BigNumber from 'bignumber.js' import ethNetProps from 'eth-net-props' import { getMetaMaskAccounts } from '../../../ui/app/selectors' +import { MIN_GAS_LIMIT_DEC } from '../../../ui/app/components/send/send.constants' import * as Toast from './toast' const MIN_GAS_PRICE_BN = new BN('0') -const MIN_GAS_LIMIT_BN = new BN('21000') +const MIN_GAS_LIMIT_BN = new BN(MIN_GAS_LIMIT_DEC) const emptyAddress = '0x0000000000000000000000000000000000000000' class PendingTx extends Component { @@ -472,12 +473,7 @@ class PendingTx extends Component { }, }, [ h('.cell.label'), - h('.cell.value', { - style: { - fontFamily: 'Nunito Regular', - fontSize: '14px', - }, - }, `Data included: ${dataLength} bytes`), + h('.cell.value', `Data included: ${dataLength} bytes`), ]), ]), // End of Table @@ -591,7 +587,8 @@ class PendingTx extends Component { } } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} if (this.props.isToken || this.state.isToken) { diff --git a/old-ui/app/components/send/amount-max-button/amount-max-button.component.js b/old-ui/app/components/send/amount-max-button/amount-max-button.component.js new file mode 100644 index 000000000000..bab4c6ea8c3d --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/amount-max-button.component.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class AmountMaxButton extends Component { + + static propTypes = { + balance: PropTypes.string, + buttonDataLoading: PropTypes.bool, + clearMaxAmount: PropTypes.func, + inError: PropTypes.bool, + gasTotal: PropTypes.string, + maxModeOn: PropTypes.bool, + sendToken: PropTypes.object, + setAmountToMax: PropTypes.func, + setMaxModeTo: PropTypes.func, + updateGasData: PropTypes.func, + tokenBalance: PropTypes.string, + address: PropTypes.string, + amount: PropTypes.string, + to: PropTypes.string, + blockGasLimit: PropTypes.string, + data: PropTypes.string, + } + + async setMaxAmount () { + const { + updateGasData, + address, + sendToken, + amount: value, + to, + data, + blockGasLimit, + setAmountToMax, + } = this.props + const params = { address, sendToken, blockGasLimit, to, value, data } + await updateGasData(params) + + const { + balance, + gasTotal, + tokenBalance, + } = this.props + + setAmountToMax({ + balance, + gasTotal, + sendToken, + tokenBalance, + }) + } + + onMaxClick = () => { + const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props + + if (!maxModeOn) { + setMaxModeTo(true) + this.setMaxAmount() + } else { + setMaxModeTo(false) + clearMaxAmount() + } + } + + render () { + const { maxModeOn, buttonDataLoading, inError } = this.props + + return ( +
+ + {'send max amount'} +
+ ) + } +} diff --git a/old-ui/app/components/send/amount-max-button/amount-max-button.container.js b/old-ui/app/components/send/amount-max-button/amount-max-button.container.js new file mode 100644 index 000000000000..4330052692da --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/amount-max-button.container.js @@ -0,0 +1,49 @@ +import { connect } from 'react-redux' +import { + getGasTotal, + getSendToken, + getSendFromBalance, + getTokenBalance, + getSendMaxModeState, + getSendTo, + getSendHexData, +} from '../../../../../ui/app/selectors' +import { calcMaxAmount } from './amount-max-button.utils.js' +import { + updateSendAmount, + setMaxModeTo, + updateGasData, +} from '../../../../../ui/app/actions' +import AmountMaxButton from './amount-max-button.component' + +export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) + +function mapStateToProps (state) { + + return { + balance: getSendFromBalance(state), + gasTotal: getGasTotal(state), + maxModeOn: getSendMaxModeState(state), + sendToken: getSendToken(state), + tokenBalance: getTokenBalance(state), + send: state.metamask.send, + amount: state.metamask.send.amount, + blockGasLimit: state.metamask.currentBlockGasLimit, + address: state.metamask.selectedAddress, + to: getSendTo(state), + data: getSendHexData(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + setAmountToMax: (maxAmountDataObject) => { + dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject))) + }, + clearMaxAmount: () => { + dispatch(updateSendAmount('0')) + }, + setMaxModeTo: (bool) => dispatch(setMaxModeTo(bool)), + updateGasData: (params) => dispatch(updateGasData(params)), + } +} diff --git a/old-ui/app/components/send/amount-max-button/amount-max-button.utils.js b/old-ui/app/components/send/amount-max-button/amount-max-button.utils.js new file mode 100644 index 000000000000..0f1572d825e2 --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/amount-max-button.utils.js @@ -0,0 +1,32 @@ +import { multiplyCurrencies, subtractCurrencies, BIG_NUMBER_WEI_MULTIPLIER } from '../../../../../ui/app/conversion-util' +import ethUtil from 'ethereumjs-util' +import BigNumber from 'bignumber.js' + +export function calcMaxAmount ({ balance, gasTotal, sendToken, tokenBalance }) { + const { decimals } = sendToken || {} + const multiplier = Math.pow(10, Number(decimals || 0)) + + + let maxBalance + if (sendToken) { + maxBalance = multiplyCurrencies( + tokenBalance, + multiplier, + { + toNumericBase: 'hex', + multiplicandBase: 16, + }, + ) + } else { + const maxBalanceInWei = + subtractCurrencies( + ethUtil.addHexPrefix(balance), + ethUtil.addHexPrefix(gasTotal), + { toNumericBase: 'dec' }, + ) + const maxBalanceInWeiBN = new BigNumber(maxBalanceInWei.toString()) + maxBalance = maxBalanceInWeiBN.div(BIG_NUMBER_WEI_MULTIPLIER).toString() + } + + return maxBalance +} diff --git a/old-ui/app/components/send/amount-max-button/index.js b/old-ui/app/components/send/amount-max-button/index.js new file mode 100644 index 000000000000..ee8271494d53 --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/index.js @@ -0,0 +1 @@ +export { default } from './amount-max-button.container' diff --git a/old-ui/app/components/send/amount-max-button/tests/amount-max-button-component.test.js b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-component.test.js new file mode 100644 index 000000000000..322d21288c59 --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-component.test.js @@ -0,0 +1,97 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import AmountMaxButton from '../amount-max-button.component.js' + +describe('AmountMaxButton Component', function () { + let wrapper + let instance + + const propsMethodSpies = { + setAmountToMax: sinon.spy(), + setMaxModeTo: sinon.spy(), + } + + const MOCK_EVENT = { preventDefault: () => {} } + + before(function () { + sinon.spy(AmountMaxButton.prototype, 'setMaxAmount') + }) + + beforeEach(function () { + wrapper = shallow(( + + ), { + context: { + t: (str) => str + '_t', + metricsEvent: () => {}, + }, + }) + instance = wrapper.instance() + }) + + afterEach(function () { + propsMethodSpies.setAmountToMax.resetHistory() + propsMethodSpies.setMaxModeTo.resetHistory() + AmountMaxButton.prototype.setMaxAmount.resetHistory() + }) + + after(function () { + sinon.restore() + }) + + describe('setMaxAmount', function () { + + it('should call setAmountToMax with the correct params', function () { + assert.equal(propsMethodSpies.setAmountToMax.callCount, 0) + instance.setMaxAmount() + assert.equal(propsMethodSpies.setAmountToMax.callCount, 1) + assert.deepEqual( + propsMethodSpies.setAmountToMax.getCall(0).args, + [{ + balance: 'mockBalance', + gasTotal: 'mockGasTotal', + sendToken: { address: 'mockTokenAddress' }, + tokenBalance: 'mockTokenBalance', + }], + ) + }) + + }) + + describe('render', function () { + it('should render an element with a send-v2__amount-max class', function () { + assert(wrapper.exists('.send-v2__amount-max')) + }) + + it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', function () { + const { + onClick, + } = wrapper.find('.send-v2__amount-max').props() + + assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 0) + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0) + onClick(MOCK_EVENT) + assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 1) + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1) + assert.deepEqual( + propsMethodSpies.setMaxModeTo.getCall(0).args, + [true], + ) + }) + + it('should render the expected text when maxModeOn is false', function () { + wrapper.setProps({ maxModeOn: false }) + assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t') + }) + }) +}) diff --git a/old-ui/app/components/send/amount-max-button/tests/amount-max-button-container.test.js b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-container.test.js new file mode 100644 index 000000000000..dff5405bb55a --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-container.test.js @@ -0,0 +1,93 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + setMaxModeTo: sinon.spy(), + updateSendAmount: sinon.spy(), +} +const duckActionSpies = { + updateSendErrors: sinon.spy(), +} + +proxyquire('../amount-max-button.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../../../../selectors': { + getGasTotal: (s) => `mockGasTotal:${s}`, + getSendToken: (s) => `mockSendToken:${s}`, + getSendFromBalance: (s) => `mockBalance:${s}`, + getTokenBalance: (s) => `mockTokenBalance:${s}`, + getSendMaxModeState: (s) => `mockMaxModeOn:${s}`, + getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`, + }, + './amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 }, + '../../../../../store/actions': actionSpies, + '../../../../../ducks/send/send.duck': duckActionSpies, +}) + +describe('amount-max-button container', function () { + + describe('mapStateToProps()', function () { + + it('should map the correct properties to props', function () { + assert.deepEqual(mapStateToProps('mockState'), { + balance: 'mockBalance:mockState', + buttonDataLoading: 'mockButtonDataLoading:mockState', + gasTotal: 'mockGasTotal:mockState', + maxModeOn: 'mockMaxModeOn:mockState', + sendToken: 'mockSendToken:mockState', + tokenBalance: 'mockTokenBalance:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', function () { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(function () { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('setAmountToMax()', function () { + it('should dispatch an action', function () { + mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' }) + assert(dispatchSpy.calledTwice) + assert(duckActionSpies.updateSendErrors.calledOnce) + assert.deepEqual( + duckActionSpies.updateSendErrors.getCall(0).args[0], + { amount: null }, + ) + assert(actionSpies.updateSendAmount.calledOnce) + assert.equal( + actionSpies.updateSendAmount.getCall(0).args[0], + 12, + ) + }) + }) + + describe('setMaxModeTo()', function () { + it('should dispatch an action', function () { + mapDispatchToPropsObject.setMaxModeTo('mockVal') + assert(dispatchSpy.calledOnce) + assert.equal( + actionSpies.setMaxModeTo.getCall(0).args[0], + 'mockVal', + ) + }) + }) + + }) + +}) diff --git a/old-ui/app/components/send/amount-max-button/tests/amount-max-button-utils.test.js b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-utils.test.js new file mode 100644 index 000000000000..08fc28b737e6 --- /dev/null +++ b/old-ui/app/components/send/amount-max-button/tests/amount-max-button-utils.test.js @@ -0,0 +1,27 @@ +import assert from 'assert' +import { + calcMaxAmount, +} from '../amount-max-button.utils.js' + +describe('amount-max-button utils', function () { + + describe('calcMaxAmount()', function () { + it('should calculate the correct amount when no sendToken defined', function () { + assert.deepEqual(calcMaxAmount({ + balance: 'ffffff', + gasTotal: 'ff', + sendToken: false, + }), 'ffff00') + }) + + it('should calculate the correct amount when a sendToken is defined', function () { + assert.deepEqual(calcMaxAmount({ + sendToken: { + decimals: 10, + }, + tokenBalance: '64', + }), 'e8d4a51000') + }) + }) + +}) diff --git a/old-ui/app/components/send/send-contract.js b/old-ui/app/components/send/send-contract.js index 2dc16fdd1937..4eaf16445687 100644 --- a/old-ui/app/components/send/send-contract.js +++ b/old-ui/app/components/send/send-contract.js @@ -139,7 +139,8 @@ class SendTransactionScreen extends PersistentForm { PersistentForm.call(this) } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { this.getContractMethods() } diff --git a/old-ui/app/components/send/send-token.js b/old-ui/app/components/send/send-token.js index 4eb0bb80d358..347434896a9f 100644 --- a/old-ui/app/components/send/send-token.js +++ b/old-ui/app/components/send/send-token.js @@ -63,7 +63,7 @@ class SendTransactionScreen extends PersistentForm { this.recipientDidChange.bind(this)} + onChange={this.recipientDidChange.bind(this)} network={network} identities={identities} addressBook={addressBook} diff --git a/old-ui/app/components/send/send.js b/old-ui/app/components/send/send.js index f358c4d104e8..00a0c5bc82ef 100644 --- a/old-ui/app/components/send/send.js +++ b/old-ui/app/components/send/send.js @@ -14,14 +14,14 @@ import ethUtil from 'ethereumjs-util' import SendProfile from './send-profile' import SendHeader from './send-header' import ErrorComponent from '../error' -import { getMetaMaskAccounts } from '../../../../ui/app/selectors' +import { getMetaMaskAccounts, getGasTotal, getTokenBalance, getCurrentEthBalance, getSendToken, getSendTo } from '../../../../ui/app/selectors' import * as Toast from '../toast' +import AmountMaxButton from './amount-max-button' const optionalDataLabelStyle = { background: '#ffffff', color: '#333333', marginTop: '16px', - marginBottom: '16px', } const optionalDataValueStyle = { width: '100%', @@ -33,11 +33,12 @@ class SendTransactionScreen extends PersistentForm { super(props) this.state = { pendingNonce: null, + recipient: null, } } - async getPendingNonce () { - const pendingNonce = await this.props.dispatch(actions.getPendingNonce(this.props.address)) + async fetchPendingNonce () { + const pendingNonce = await this.props.getPendingNonce(this.props.address) this.setState({pendingNonce: pendingNonce}) } @@ -50,6 +51,7 @@ class SendTransactionScreen extends PersistentForm { identities, addressBook, error, + updateSendTo, } = props return ( @@ -73,6 +75,8 @@ class SendTransactionScreen extends PersistentForm { network={network} identities={identities} addressBook={addressBook} + value={this.state.recipient || ''} + updateSendTo={updateSendTo} /> @@ -88,14 +92,21 @@ class SendTransactionScreen extends PersistentForm { dataset={{ persistentFormid: 'tx-amount', }} + disabled={!!this.props.maxModeOn} + value={this.props.amount || ''} + onChange={(e) => { + const newAmount = e.target.value + this.props.updateSendAmount(newAmount) + }} /> + +

{ + const newTxData = e.target.value + this.props.updateSendHexData(newTxData) + }} /> @@ -137,16 +152,23 @@ class SendTransactionScreen extends PersistentForm { } componentDidMount () { - this.getPendingNonce() + this._isMounted = true + if (this._isMounted) { + this.fetchPendingNonce() + } } componentWillUnmount () { - this.props.dispatch(actions.displayWarning('')) + this.props.displayWarning('') + this.props.updateSendAmount(null) + this.props.setMaxModeTo(false) + this.props.updateSendTo('') + this._isMounted = false } navigateToAccounts (event) { event.stopPropagation() - this.props.dispatch(actions.showAccountsPage()) + this.props.showAccountsPage() } recipientDidChange (recipient, nickname) { @@ -154,6 +176,7 @@ class SendTransactionScreen extends PersistentForm { recipient: recipient, nickname: nickname, }) + this.props.updateSendTo(recipient, nickname) } onSubmit () { @@ -175,14 +198,14 @@ class SendTransactionScreen extends PersistentForm { if (isNaN(input) || input === '') { message = 'Invalid ether value.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } if (parts[1]) { const decimal = parts[1] if (decimal.length > 18) { message = 'Ether amount is too precise.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } } @@ -193,32 +216,32 @@ class SendTransactionScreen extends PersistentForm { if (value.gt(balance)) { message = 'Insufficient funds.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } if (input < 0) { message = 'Can not send negative amounts of ETH.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } if ((isInvalidChecksumAddress(recipient, this.props.network))) { message = 'Recipient address checksum is invalid.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) { message = 'Recipient address is invalid.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) { message = 'Transaction data must be hex string.' - return this.props.dispatch(actions.displayWarning(message)) + return this.props.displayWarning(message) } - this.props.dispatch(actions.hideWarning()) + this.props.hideWarning() - this.props.dispatch(actions.addToAddressBook(recipient, nickname)) + this.props.addToAddressBook(recipient, nickname) const txParams = { from: this.props.address, @@ -229,19 +252,30 @@ class SendTransactionScreen extends PersistentForm { if (txData) txParams.data = txData if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16) - this.props.dispatch(actions.signTx(txParams)) + this.props.signTx(txParams) } } function mapStateToProps (state) { const accounts = getMetaMaskAccounts(state) + const balance = getCurrentEthBalance(state) + const gasTotal = getGasTotal(state) const result = { + send: state.metamask.send, address: state.metamask.selectedAddress, accounts, identities: state.metamask.identities, warning: state.appState.warning, network: state.metamask.network, addressBook: state.metamask.addressBook, + balance, + gasTotal, + to: getSendTo(state), + sendToken: getSendToken(state), + tokenBalance: getTokenBalance(state), + amount: state.metamask.send.amount, + maxModeOn: state.metamask.send.maxModeOn, + blockGasLimit: state.metamask.currentBlockGasLimit, } result.error = result.warning && result.warning.split('.')[0] @@ -251,4 +285,19 @@ function mapStateToProps (state) { return result } -module.exports = connect(mapStateToProps)(SendTransactionScreen) +function mapDispatchToProps (dispatch) { + return { + addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)), + showAccountsPage: () => dispatch(actions.showAccountsPage()), + displayWarning: (msg) => dispatch(actions.displayWarning(msg)), + hideWarning: () => dispatch(actions.hideWarning()), + getPendingNonce: (address) => dispatch(actions.getPendingNonce(address)), + signTx: (txParams) => dispatch(actions.signTx(txParams)), + updateSendAmount: (amount) => dispatch(actions.updateSendAmount(amount)), + setMaxModeTo: (maxMode) => dispatch(actions.setMaxModeTo(maxMode)), + updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)), + updateSendHexData: (txData) => dispatch(actions.updateSendHexData(txData)), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTransactionScreen) diff --git a/old-ui/app/css/index.css b/old-ui/app/css/index.css index c023873a4cda..5a66a699f1e8 100644 --- a/old-ui/app/css/index.css +++ b/old-ui/app/css/index.css @@ -630,10 +630,6 @@ input.large-input { /* accounts screen */ -.identity-section { - -} - .identity-section .identity-panel { background: #E9E9E9; border-bottom: 1px solid #B1B1B1; @@ -670,10 +666,6 @@ input.large-input { flex-grow: 10; } -.name-label{ - -} - .unapproved-tx-icon { height: 16px; width: 16px; @@ -735,14 +727,14 @@ input.large-input { /* Send Screen */ -.send-screen { - -} - .send-screen section { margin: 10px 30px; } +.send-screen section.amount-max-container { + margin-top: 0; +} + .send-screen input { width: 100%; font-size: 14px; @@ -750,17 +742,34 @@ input.large-input { border-radius: 3px; } +.send__amount-max { + display: flex; + width: 100%; + align-items: center; +} + +.send__amount-max input { + width: 10px; + height: 10px; + margin-left: 0; +} + +.secondary-description { + font-family: 'Nunito Regular'; + font-size: 14px +} + /* Ether Balance Widget */ .ether-balance-label { color: #ABA9AA; } -.icon-size{ +.icon-size { width: 20px; } -.info{ +.info { font-family: 'Nunito Regular'; padding-bottom: 10px; display: inline-block; @@ -829,10 +838,6 @@ input.large-input { font-family: Nunito Semibold; } -.buy-radio { - -} - .eth-warning{ transition: opacity 400ms ease-in, transform 400ms ease-in; } diff --git a/test/unit/app/controllers/transactions/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js index 31defd6ed0c4..36b8dfd0534e 100644 --- a/test/unit/app/controllers/transactions/tx-gas-util-test.js +++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js @@ -3,7 +3,7 @@ const Transaction = require('ethereumjs-tx') const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util') -const TxUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils') +import TxUtils from '../../../../../app/scripts/controllers/transactions/tx-gas-utils' describe('txUtils', function () { diff --git a/ui/app/actions.js b/ui/app/actions.js index 679f2aaf4816..23f5e92bb782 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1061,7 +1061,6 @@ function setGasTotal (gasTotal) { function updateGasData ({ blockGasLimit, - recentBlocks, selectedAddress, selectedToken, to, @@ -1100,11 +1099,13 @@ function updateGasData ({ dispatch(actions.setGasTotal(gasEstimate)) dispatch(updateSendErrors({ gasLoadingError: null })) dispatch(actions.gasLoadingFinished()) + return Promise.resolve() }) .catch(err => { log.error(err) dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' })) dispatch(actions.gasLoadingFinished()) + return Promise.reject() }) } } diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index 9062d8efe8c0..047d3afbbc4e 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -248,4 +248,5 @@ module.exports = { conversionMax, toNegative, subtractCurrencies, + BIG_NUMBER_WEI_MULTIPLIER, } diff --git a/ui/app/selectors.js b/ui/app/selectors.js index fb185b8bfa1a..1a3436a955a7 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -1,10 +1,16 @@ -const abi = require('human-standard-token-abi') +import abi from 'human-standard-token-abi' +import { pipe } from 'ramda' import { transactionsSelector, } from './selectors/transactions' -const { +import { addHexPrefix } from 'ethereumjs-util' +import { + conversionUtil, multiplyCurrencies, -} = require('./conversion-util') +} from './conversion-util' +import { + calcGasTotal, +} from './components/send/send.utils' const selectors = { getSelectedAddress, @@ -33,6 +39,19 @@ const selectors = { preferencesSelector, getMetaMaskAccounts, getUsePhishDetect, + getGasLimit, + getGasPrice, + getGasTotal, + getGasPriceInHexWei, + priceEstimateToWei, + getCurrentEthBalance, + getSendToken, + getTokenBalance, + getSendFromBalance, + getSendFromObject, + getSendTo, + getSendHexData, + getTargetAccount, } module.exports = selectors @@ -151,12 +170,20 @@ function getSendFrom (state) { return state.metamask.send.from } +function getSendTo (state) { + return state.metamask.send.to +} + function getSendAmount (state) { return state.metamask.send.amount } function getSendMaxModeState (state) { - return state.metamask.send.maxModeOn + return state.metamask.send.maxModeOn || false +} + +function getSendHexData (state) { + return state.metamask.send.data } function getCurrentCurrency (state) { @@ -203,3 +230,62 @@ function getTotalUnapprovedCount ({ metamask }) { function preferencesSelector ({ metamask }) { return metamask.preferences } + +function getGasLimit (state) { + return state.metamask.send.gasLimit || '0' +} + +function getGasPrice (state) { + return state.metamask.send.gasPrice +} + +function getGasTotal (state) { + return calcGasTotal(getGasLimit(state), getGasPrice(state)) +} + +function priceEstimateToWei (priceEstimate) { + return conversionUtil(priceEstimate, { + fromNumericBase: 'hex', + toNumericBase: 'hex', + fromDenomination: 'GWEI', + toDenomination: 'WEI', + numberOfDecimals: 9, + }) +} + +function getGasPriceInHexWei (price) { + return pipe( + (x) => conversionUtil(x, { fromNumericBase: 'dec', toNumericBase: 'hex' }), + priceEstimateToWei, + addHexPrefix, + )(price) +} + +function getCurrentEthBalance (state) { + return getCurrentAccountWithSendEtherInfo(state).balance +} + +function getSendToken (state) { + return state.metamask.send.token +} + +function getTokenBalance (state) { + return state.metamask.send.tokenBalance +} + +function getSendFromBalance (state) { + const fromAccount = getSendFromObject(state) + return fromAccount.balance +} + +function getSendFromObject (state) { + const fromAddress = getSendFrom(state) + return fromAddress + ? getTargetAccount(state, fromAddress) + : getSelectedAccount(state) +} + +function getTargetAccount (state, targetAddress) { + const accounts = getMetaMaskAccounts(state) + return accounts[targetAddress] +} diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index c02d5a8b7ea5..6400bb9bca05 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -25,7 +25,8 @@ class WelcomeScreen extends Component { this.animationEventEmitter = new EventEmitter() } - componentWillMount () { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount () { const { history, welcomeScreenSeen } = this.props if (welcomeScreenSeen) { From 266241ddfb2f52523d2fd41726a9131d719aa863 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 12 Jun 2020 10:35:06 +0300 Subject: [PATCH 2/2] Fix manual merging conflicts --- old-ui/app/components/ens-input.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/old-ui/app/components/ens-input.js b/old-ui/app/components/ens-input.js index 024d2db580b5..327d201cad05 100644 --- a/old-ui/app/components/ens-input.js +++ b/old-ui/app/components/ens-input.js @@ -101,7 +101,7 @@ class EnsInput extends Component { this.ens = new ENS({ provider, network }) this.checkName = debounce(this.lookupEnsName.bind(this, 'ENS'), 200) } else if (networkHasRnsSupport) { - const registryAddress = getRnsRegistryAddress(network); + const registryAddress = getRnsRegistryAddress(network) const provider = global.ethereumProvider this.ens = new ENS({ provider, network, registryAddress }) this.checkName = debounce(this.lookupEnsName.bind(this, 'RNS'), 200) @@ -135,9 +135,9 @@ class EnsInput extends Component { toError: null, } if ( - (isValidENSAddress(recipient) || isValidRNSAddress(recipient)) - && reason.message === 'ENS name not defined.' - ) + (isValidENSAddress(recipient) || isValidRNSAddress(recipient)) && + reason.message === 'ENS name not defined.' + ) { setStateObj.hoverText = '${nameService} name not found' setStateObj.toError = `${nameService.toLowerCase()}NameNotFound` setStateObj.ensFailure = false @@ -236,5 +236,5 @@ function getRnsRegistryAddress (network) { return } - + module.exports = EnsInput