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

Prevent send to token warning (#6058)

This commit is contained in:
Esteban Miño 2019-01-23 15:09:56 -03:00 committed by Whymarrh Whitby
parent fba17d77de
commit 2d7c9b3dac
28 changed files with 256 additions and 15 deletions

View File

@ -104,6 +104,7 @@
"amount": "0x0", "amount": "0x0",
"memo": "", "memo": "",
"errors": {}, "errors": {},
"warnings": {},
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },

View File

@ -159,7 +159,8 @@
"send": { "send": {
"fromDropdownOpen": false, "fromDropdownOpen": false,
"toDropdownOpen": false, "toDropdownOpen": false,
"errors": {} "errors": {},
"warnings": {}
}, },
"confirmTransaction": { "confirmTransaction": {
"txData": {}, "txData": {},

View File

@ -148,6 +148,7 @@
"amount": "0x1bc16d674ec80000", "amount": "0x1bc16d674ec80000",
"memo": "", "memo": "",
"errors": {}, "errors": {},
"warnings": {},
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },

View File

@ -106,6 +106,7 @@
"amount": "0x0", "amount": "0x0",
"memo": "", "memo": "",
"errors": {}, "errors": {},
"warnings": {},
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },

View File

@ -280,7 +280,8 @@
"send": { "send": {
"fromDropdownOpen": false, "fromDropdownOpen": false,
"toDropdownOpen": false, "toDropdownOpen": false,
"errors": {} "errors": {},
"warnings": {}
}, },
"confirmTransaction": { "confirmTransaction": {
"txData": { "txData": {

View File

@ -128,6 +128,7 @@
"amount": "0x1bc16d674ec80000", "amount": "0x1bc16d674ec80000",
"memo": "", "memo": "",
"errors": {}, "errors": {},
"warnings": {},
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
@ -161,7 +162,8 @@
"send": { "send": {
"fromDropdownOpen": false, "fromDropdownOpen": false,
"toDropdownOpen": false, "toDropdownOpen": false,
"errors": {} "errors": {},
"warnings": {}
}, },
"confirmTransaction": { "confirmTransaction": {
"txData": {}, "txData": {},

View File

@ -107,6 +107,7 @@
"amount": "0x0", "amount": "0x0",
"memo": "", "memo": "",
"errors": {}, "errors": {},
"warnings": {},
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
@ -141,6 +142,7 @@
"fromDropdownOpen": false, "fromDropdownOpen": false,
"toDropdownOpen": false, "toDropdownOpen": false,
"errors": {}, "errors": {},
"warnings": {},
"gasButtonGroupShown": true "gasButtonGroupShown": true
}, },
"confirmTransaction": { "confirmTransaction": {

View File

@ -133,7 +133,8 @@
"send": { "send": {
"fromDropdownOpen": false, "fromDropdownOpen": false,
"toDropdownOpen": false, "toDropdownOpen": false,
"errors": {} "errors": {},
"warnings": {}
}, },
"gas": { "gas": {
"customData": { "customData": {

View File

@ -191,6 +191,7 @@ var actions = {
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT', UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO', UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS', UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
UPDATE_SEND_WARNINGS: 'UPDATE_SEND_WARNINGS',
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE', UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND', UPDATE_SEND: 'UPDATE_SEND',
CLEAR_SEND: 'CLEAR_SEND', CLEAR_SEND: 'CLEAR_SEND',
@ -211,6 +212,7 @@ var actions = {
setMaxModeTo, setMaxModeTo,
updateSend, updateSend,
updateSendErrors, updateSendErrors,
updateSendWarnings,
clearSend, clearSend,
setSelectedAddress, setSelectedAddress,
gasLoadingStarted, gasLoadingStarted,
@ -1068,6 +1070,13 @@ function updateSendErrors (errorObject) {
} }
} }
function updateSendWarnings (warningObject) {
return {
type: actions.UPDATE_SEND_WARNINGS,
value: warningObject,
}
}
function setSendTokenBalance (tokenBalance) { function setSendTokenBalance (tokenBalance) {
return { return {
type: actions.UPDATE_SEND_TOKEN_BALANCE, type: actions.UPDATE_SEND_TOKEN_BALANCE,

View File

@ -128,7 +128,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
} }
if (prevState && ensResolution && this.props.onChange && if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) { ensResolution !== prevState.ensResolution) {
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError }) this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
} }
} }

View File

@ -0,0 +1 @@
export { default } from './send-row-warning-message.container'

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class SendRowWarningMessage extends Component {
static propTypes = {
warnings: PropTypes.object,
warningType: PropTypes.string,
};
static contextTypes = {
t: PropTypes.func,
};
render () {
const { warnings, warningType } = this.props
const warningMessage = warningType in warnings && warnings[warningType]
return (
warningMessage
? <div className="send-v2__warning">{this.context.t(warningMessage)}</div>
: null
)
}
}

View File

@ -0,0 +1,12 @@
import { connect } from 'react-redux'
import { getSendWarnings } from '../../../send.selectors'
import SendRowWarningMessage from './send-row-warning-message.component'
export default connect(mapStateToProps)(SendRowWarningMessage)
function mapStateToProps (state, ownProps) {
return {
warnings: getSendWarnings(state),
warningType: ownProps.warningType,
}
}

View File

@ -0,0 +1,28 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import SendRowWarningMessage from '../send-row-warning-message.component.js'
describe('SendRowWarningMessage Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(<SendRowWarningMessage
warnings={{ warning1: 'abc', warning2: 'def' }}
warningType={'warning3'}
/>, { context: { t: str => str + '_t' } })
})
describe('render', () => {
it('should render null if the passed warnings do not contain a warning of warningType', () => {
assert.equal(wrapper.find('.send-v2__warning').length, 0)
assert.equal(wrapper.html(), null)
})
it('should render a warning message if the passed warnings contain a warning of warningType', () => {
wrapper.setProps({ warnings: { warning1: 'abc', warning2: 'def', warning3: 'xyz' } })
assert.equal(wrapper.find('.send-v2__warning').length, 1)
assert.equal(wrapper.find('.send-v2__warning').text(), 'xyz_t')
})
})
})

View File

@ -0,0 +1,28 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps
proxyquire('../send-row-warning-message.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
return () => ({})
},
},
'../../../send.selectors': { getSendWarnings: (s) => `mockWarnings:${s}` },
})
describe('send-row-warning-message container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState', { warningType: 'someType' }), {
warnings: 'mockWarnings:mockState',
warningType: 'someType' })
})
})
})

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowErrorMessage from './send-row-error-message/' import SendRowErrorMessage from './send-row-error-message/'
import SendRowWarningMessage from './send-row-warning-message/'
export default class SendRowWrapper extends Component { export default class SendRowWrapper extends Component {
@ -9,6 +10,8 @@ export default class SendRowWrapper extends Component {
errorType: PropTypes.string, errorType: PropTypes.string,
label: PropTypes.string, label: PropTypes.string,
showError: PropTypes.bool, showError: PropTypes.bool,
showWarning: PropTypes.bool,
warningType: PropTypes.string,
}; };
static contextTypes = { static contextTypes = {
@ -21,8 +24,9 @@ export default class SendRowWrapper extends Component {
errorType = '', errorType = '',
label, label,
showError = false, showError = false,
showWarning = false,
warningType = '',
} = this.props } = this.props
const formField = Array.isArray(children) ? children[1] || children[0] : children const formField = Array.isArray(children) ? children[1] || children[0] : children
const customLabelContent = children.length > 1 ? children[0] : null const customLabelContent = children.length > 1 ? children[0] : null
@ -31,6 +35,7 @@ export default class SendRowWrapper extends Component {
<div className="send-v2__form-label"> <div className="send-v2__form-label">
{label} {label}
{showError && <SendRowErrorMessage errorType={errorType}/>} {showError && <SendRowErrorMessage errorType={errorType}/>}
{!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
{customLabelContent} {customLabelContent}
</div> </div>
<div className="send-v2__form-field"> <div className="send-v2__form-field">

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/' import SendRowWrapper from '../send-row-wrapper/'
import EnsInput from '../../../ens-input' import EnsInput from '../../../ens-input'
import { getToErrorObject } from './send-to-row.utils.js' import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js'
export default class SendToRow extends Component { export default class SendToRow extends Component {
@ -10,6 +10,7 @@ export default class SendToRow extends Component {
closeToDropdown: PropTypes.func, closeToDropdown: PropTypes.func,
hasHexData: PropTypes.bool.isRequired, hasHexData: PropTypes.bool.isRequired,
inError: PropTypes.bool, inError: PropTypes.bool,
inWarning: PropTypes.bool,
network: PropTypes.string, network: PropTypes.string,
openToDropdown: PropTypes.func, openToDropdown: PropTypes.func,
selectedToken: PropTypes.object, selectedToken: PropTypes.object,
@ -20,6 +21,7 @@ export default class SendToRow extends Component {
updateGas: PropTypes.func, updateGas: PropTypes.func,
updateSendTo: PropTypes.func, updateSendTo: PropTypes.func,
updateSendToError: PropTypes.func, updateSendToError: PropTypes.func,
updateSendToWarning: PropTypes.func,
scanQrCode: PropTypes.func, scanQrCode: PropTypes.func,
} }
@ -27,11 +29,13 @@ export default class SendToRow extends Component {
t: PropTypes.func, t: PropTypes.func,
} }
handleToChange (to, nickname = '', toError) { handleToChange (to, nickname = '', toError, toWarning) {
const { hasHexData, updateSendTo, updateSendToError, updateGas, tokens, selectedToken } = this.props const { hasHexData, updateSendTo, updateSendToError, updateGas, tokens, selectedToken, updateSendToWarning } = this.props
const toErrorObject = getToErrorObject(to, toError, hasHexData, tokens, selectedToken) const toErrorObject = getToErrorObject(to, toError, hasHexData)
const toWarningObject = getToWarningObject(to, toWarning, tokens, selectedToken)
updateSendTo(to, nickname) updateSendTo(to, nickname)
updateSendToError(toErrorObject) updateSendToError(toErrorObject)
updateSendToWarning(toWarningObject)
if (toErrorObject.to === null) { if (toErrorObject.to === null) {
updateGas({ to }) updateGas({ to })
} }
@ -41,6 +45,7 @@ export default class SendToRow extends Component {
const { const {
closeToDropdown, closeToDropdown,
inError, inError,
inWarning,
network, network,
openToDropdown, openToDropdown,
to, to,
@ -53,6 +58,8 @@ export default class SendToRow extends Component {
errorType={'to'} errorType={'to'}
label={`${this.context.t('to')}: `} label={`${this.context.t('to')}: `}
showError={inError} showError={inError}
showWarning={inWarning}
warningType={'to'}
> >
<EnsInput <EnsInput
scanQrCode={_ => this.props.scanQrCode()} scanQrCode={_ => this.props.scanQrCode()}
@ -62,7 +69,7 @@ export default class SendToRow extends Component {
inError={inError} inError={inError}
name={'address'} name={'address'}
network={network} network={network}
onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)} onChange={({ toAddress, nickname, toError, toWarning }) => this.handleToChange(toAddress, nickname, toError, toWarning)}
openDropdown={() => openToDropdown()} openDropdown={() => openToDropdown()}
placeholder={this.context.t('recipientAddress')} placeholder={this.context.t('recipientAddress')}
to={to} to={to}

View File

@ -10,12 +10,14 @@ import {
getToDropdownOpen, getToDropdownOpen,
getTokens, getTokens,
sendToIsInError, sendToIsInError,
sendToIsInWarning,
} from './send-to-row.selectors.js' } from './send-to-row.selectors.js'
import { import {
updateSendTo, updateSendTo,
} from '../../../../actions' } from '../../../../actions'
import { import {
updateSendErrors, updateSendErrors,
updateSendWarnings,
openToDropdown, openToDropdown,
closeToDropdown, closeToDropdown,
} from '../../../../ducks/send.duck' } from '../../../../ducks/send.duck'
@ -27,6 +29,7 @@ function mapStateToProps (state) {
return { return {
hasHexData: Boolean(getSendHexData(state)), hasHexData: Boolean(getSendHexData(state)),
inError: sendToIsInError(state), inError: sendToIsInError(state),
inWarning: sendToIsInWarning(state),
network: getCurrentNetwork(state), network: getCurrentNetwork(state),
selectedToken: getSelectedToken(state), selectedToken: getSelectedToken(state),
to: getSendTo(state), to: getSendTo(state),
@ -44,5 +47,8 @@ function mapDispatchToProps (dispatch) {
updateSendToError: (toErrorObject) => { updateSendToError: (toErrorObject) => {
dispatch(updateSendErrors(toErrorObject)) dispatch(updateSendErrors(toErrorObject))
}, },
updateSendToWarning: (toWarningObject) => {
dispatch(updateSendWarnings(toWarningObject))
},
} }
} }

View File

@ -2,6 +2,7 @@ const selectors = {
getToDropdownOpen, getToDropdownOpen,
getTokens, getTokens,
sendToIsInError, sendToIsInError,
sendToIsInWarning,
} }
module.exports = selectors module.exports = selectors
@ -14,6 +15,10 @@ function sendToIsInError (state) {
return Boolean(state.send.errors.to) return Boolean(state.send.errors.to)
} }
function sendToIsInWarning (state) {
return Boolean(state.send.warnings.to)
}
function getTokens (state) { function getTokens (state) {
return state.metamask.tokens return state.metamask.tokens
} }

View File

@ -19,10 +19,17 @@ function getToErrorObject (to, toError = null, hasHexData = false, tokens = [],
} else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) { } else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
toError = KNOWN_RECIPIENT_ADDRESS_ERROR toError = KNOWN_RECIPIENT_ADDRESS_ERROR
} }
return { to: toError } return { to: toError }
} }
function getToWarningObject (to, toWarning = null, tokens = [], selectedToken = null) {
if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
toWarning = KNOWN_RECIPIENT_ADDRESS_ERROR
}
return { to: toWarning }
}
module.exports = { module.exports = {
getToErrorObject, getToErrorObject,
getToWarningObject,
} }

View File

@ -9,6 +9,9 @@ const SendToRow = proxyquire('../send-to-row.component.js', {
getToErrorObject: (to, toError) => ({ getToErrorObject: (to, toError) => ({
to: to === false ? null : `mockToErrorObject:${to}${toError}`, to: to === false ? null : `mockToErrorObject:${to}${toError}`,
}), }),
getToWarningObject: (to, toWarning) => ({
to: to === false ? null : `mockToWarningObject:${to}${toWarning}`,
}),
}, },
}).default }).default
@ -21,6 +24,7 @@ const propsMethodSpies = {
updateGas: sinon.spy(), updateGas: sinon.spy(),
updateSendTo: sinon.spy(), updateSendTo: sinon.spy(),
updateSendToError: sinon.spy(), updateSendToError: sinon.spy(),
updateSendToWarning: sinon.spy(),
} }
sinon.spy(SendToRow.prototype, 'handleToChange') sinon.spy(SendToRow.prototype, 'handleToChange')
@ -33,6 +37,7 @@ describe('SendToRow Component', function () {
wrapper = shallow(<SendToRow wrapper = shallow(<SendToRow
closeToDropdown={propsMethodSpies.closeToDropdown} closeToDropdown={propsMethodSpies.closeToDropdown}
inError={false} inError={false}
inWarning={false}
network={'mockNetwork'} network={'mockNetwork'}
openToDropdown={propsMethodSpies.openToDropdown} openToDropdown={propsMethodSpies.openToDropdown}
to={'mockTo'} to={'mockTo'}
@ -41,6 +46,7 @@ describe('SendToRow Component', function () {
updateGas={propsMethodSpies.updateGas} updateGas={propsMethodSpies.updateGas}
updateSendTo={propsMethodSpies.updateSendTo} updateSendTo={propsMethodSpies.updateSendTo}
updateSendToError={propsMethodSpies.updateSendToError} updateSendToError={propsMethodSpies.updateSendToError}
updateSendToWarning={propsMethodSpies.updateSendToWarning}
/>, { context: { t: str => str + '_t' } }) />, { context: { t: str => str + '_t' } })
instance = wrapper.instance() instance = wrapper.instance()
}) })
@ -50,6 +56,7 @@ describe('SendToRow Component', function () {
propsMethodSpies.openToDropdown.resetHistory() propsMethodSpies.openToDropdown.resetHistory()
propsMethodSpies.updateSendTo.resetHistory() propsMethodSpies.updateSendTo.resetHistory()
propsMethodSpies.updateSendToError.resetHistory() propsMethodSpies.updateSendToError.resetHistory()
propsMethodSpies.updateSendToWarning.resetHistory()
SendToRow.prototype.handleToChange.resetHistory() SendToRow.prototype.handleToChange.resetHistory()
}) })
@ -75,6 +82,16 @@ describe('SendToRow Component', function () {
) )
}) })
it('should call updateSendToWarning', () => {
assert.equal(propsMethodSpies.updateSendToWarning.callCount, 0)
instance.handleToChange('mockTo2', '', '', 'mockToWarning')
assert.equal(propsMethodSpies.updateSendToWarning.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateSendToWarning.getCall(0).args,
[{ to: 'mockToWarningObject:mockTo2mockToWarning' }]
)
})
it('should not call updateGas if there is a to error', () => { it('should not call updateGas if there is a to error', () => {
assert.equal(propsMethodSpies.updateGas.callCount, 0) assert.equal(propsMethodSpies.updateGas.callCount, 0)
instance.handleToChange('mockTo2') instance.handleToChange('mockTo2')
@ -138,11 +155,11 @@ describe('SendToRow Component', function () {
openDropdown() openDropdown()
assert.equal(propsMethodSpies.openToDropdown.callCount, 1) assert.equal(propsMethodSpies.openToDropdown.callCount, 1)
assert.equal(SendToRow.prototype.handleToChange.callCount, 0) assert.equal(SendToRow.prototype.handleToChange.callCount, 0)
onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' }) onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError', toWarning: 'mockToWarning' })
assert.equal(SendToRow.prototype.handleToChange.callCount, 1) assert.equal(SendToRow.prototype.handleToChange.callCount, 1)
assert.deepEqual( assert.deepEqual(
SendToRow.prototype.handleToChange.getCall(0).args, SendToRow.prototype.handleToChange.getCall(0).args,
['mockNewTo', 'mockNewNickname', 'mockToError'] ['mockNewTo', 'mockNewNickname', 'mockToError', 'mockToWarning']
) )
}) })
}) })

View File

@ -12,6 +12,7 @@ const duckActionSpies = {
closeToDropdown: sinon.spy(), closeToDropdown: sinon.spy(),
openToDropdown: sinon.spy(), openToDropdown: sinon.spy(),
updateSendErrors: sinon.spy(), updateSendErrors: sinon.spy(),
updateSendWarnings: sinon.spy(),
} }
proxyquire('../send-to-row.container.js', { proxyquire('../send-to-row.container.js', {
@ -32,6 +33,7 @@ proxyquire('../send-to-row.container.js', {
'./send-to-row.selectors.js': { './send-to-row.selectors.js': {
getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`, getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`,
sendToIsInError: (s) => `mockInError:${s}`, sendToIsInError: (s) => `mockInError:${s}`,
sendToIsInWarning: (s) => `mockInWarning:${s}`,
getTokens: (s) => `mockTokens:${s}`, getTokens: (s) => `mockTokens:${s}`,
}, },
'../../../../actions': actionSpies, '../../../../actions': actionSpies,
@ -46,6 +48,7 @@ describe('send-to-row container', () => {
assert.deepEqual(mapStateToProps('mockState'), { assert.deepEqual(mapStateToProps('mockState'), {
hasHexData: true, hasHexData: true,
inError: 'mockInError:mockState', inError: 'mockInError:mockState',
inWarning: 'mockInWarning:mockState',
network: 'mockNetwork:mockState', network: 'mockNetwork:mockState',
selectedToken: 'mockSelectedToken:mockState', selectedToken: 'mockSelectedToken:mockState',
to: 'mockTo:mockState', to: 'mockTo:mockState',
@ -114,6 +117,18 @@ describe('send-to-row container', () => {
}) })
}) })
describe('updateSendToWarning()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendToWarning('mockToWarningObject')
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.updateSendWarnings.calledOnce)
assert.equal(
duckActionSpies.updateSendWarnings.getCall(0).args[0],
'mockToWarningObject'
)
})
})
}) })
}) })

View File

@ -19,6 +19,7 @@ const toRowUtils = proxyquire('../send-to-row.utils.js', {
}) })
const { const {
getToErrorObject, getToErrorObject,
getToWarningObject,
} = toRowUtils } = toRowUtils
describe('send-to-row utils', () => { describe('send-to-row utils', () => {
@ -78,4 +79,29 @@ describe('send-to-row utils', () => {
}) })
}) })
describe('getToWarningObject()', () => {
it('should return a known address recipient if to is truthy but part of state tokens', () => {
assert.deepEqual(getToWarningObject('0xabc123', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
})
})
it('should null if to is truthy part of tokens but selectedToken falsy', () => {
assert.deepEqual(getToWarningObject('0xabc123', undefined, [{'address': '0xabc123'}]), {
to: null,
})
})
it('should return a known address recipient if to is truthy but part of contract metadata', () => {
assert.deepEqual(getToWarningObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
})
})
it('should null if to is truthy part of contract metadata but selectedToken falsy', () => {
assert.deepEqual(getToWarningObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
})
})
})
}) })

View File

@ -51,6 +51,7 @@ const selectors = {
getSendMaxModeState, getSendMaxModeState,
getSendTo, getSendTo,
getSendToAccounts, getSendToAccounts,
getSendWarnings,
getTokenBalance, getTokenBalance,
getTokenExchangeRate, getTokenExchangeRate,
getUnapprovedTxs, getUnapprovedTxs,
@ -268,6 +269,10 @@ function getSendToAccounts (state) {
return Object.entries(allAccounts).map(([key, account]) => account) return Object.entries(allAccounts).map(([key, account]) => account)
} }
function getSendWarnings (state) {
return state.send.warnings
}
function getTokenBalance (state) { function getTokenBalance (state) {
return state.metamask.send.tokenBalance return state.metamask.send.tokenBalance
} }

View File

@ -520,6 +520,13 @@
color: $red; color: $red;
} }
&__warning {
font-size: 12px;
line-height: 12px;
left: 8px;
color: $orange;
}
&__error-border { &__error-border {
color: $red; color: $red;
} }

View File

@ -6,6 +6,7 @@ const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
const UPDATE_SEND_WARNINGS = 'metamask/send/UPDATE_SEND_WARNINGS'
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE' const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP' const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP'
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP' const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP'
@ -16,6 +17,7 @@ const initState = {
toDropdownOpen: false, toDropdownOpen: false,
gasButtonGroupShown: true, gasButtonGroupShown: true,
errors: {}, errors: {},
warnings: {},
} }
// Reducer // Reducer
@ -46,6 +48,13 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
...action.value, ...action.value,
}, },
}) })
case UPDATE_SEND_WARNINGS:
return extend(newState, {
warnings: {
...newState.warnings,
...action.value,
},
})
case SHOW_GAS_BUTTON_GROUP: case SHOW_GAS_BUTTON_GROUP:
return extend(newState, { return extend(newState, {
gasButtonGroupShown: true, gasButtonGroupShown: true,
@ -85,6 +94,13 @@ export function updateSendErrors (errorObject) {
} }
} }
export function updateSendWarnings (warningObject) {
return {
type: UPDATE_SEND_WARNINGS,
value: warningObject,
}
}
export function resetSendState () { export function resetSendState () {
return { type: RESET_SEND_STATE } return { type: RESET_SEND_STATE }
} }

View File

@ -6,6 +6,7 @@ import SendReducer, {
updateSendErrors, updateSendErrors,
showGasButtonGroup, showGasButtonGroup,
hideGasButtonGroup, hideGasButtonGroup,
updateSendWarnings,
} from '../send.duck.js' } from '../send.duck.js'
describe('Send Duck', () => { describe('Send Duck', () => {
@ -19,12 +20,14 @@ describe('Send Duck', () => {
toDropdownOpen: false, toDropdownOpen: false,
errors: {}, errors: {},
gasButtonGroupShown: true, gasButtonGroupShown: true,
warnings: {},
} }
const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'
const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
const UPDATE_SEND_WARNINGS = 'metamask/send/UPDATE_SEND_WARNINGS'
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE' const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP' const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP'
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP' const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP'
@ -173,4 +176,11 @@ describe('Send Duck', () => {
) )
}) })
describe('updateSendWarnings', () => {
assert.deepEqual(
updateSendWarnings('mockWarningObject'),
{ type: UPDATE_SEND_WARNINGS, value: 'mockWarningObject' }
)
})
}) })