mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Client side error handling for from, to and amount fields in send.js
This commit is contained in:
parent
83cda2b82e
commit
14bdc5a78c
@ -123,7 +123,20 @@ const addCurrencies = (a, b, { toNumericBase, numberOfDecimals }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const conversionGreaterThan = (
|
||||||
|
{ value, fromNumericBase },
|
||||||
|
{ value: compareToValue, fromNumericBase: compareToBase },
|
||||||
|
) => {
|
||||||
|
const firstValue = converter({ value, fromNumericBase })
|
||||||
|
const secondValue = converter({
|
||||||
|
value: compareToValue,
|
||||||
|
fromNumericBase: compareToBase,
|
||||||
|
})
|
||||||
|
return firstValue.gt(secondValue)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
conversionUtil,
|
conversionUtil,
|
||||||
addCurrencies,
|
addCurrencies,
|
||||||
|
conversionGreaterThan,
|
||||||
}
|
}
|
@ -104,6 +104,16 @@
|
|||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.send-screen-input-wrapper__error-message {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
left: 8px;
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-screen-input {
|
.send-screen-input {
|
||||||
@ -295,6 +305,11 @@
|
|||||||
width: 163px;
|
width: 163px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__send-button__disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-token {
|
.send-token {
|
||||||
|
205
ui/app/send.js
205
ui/app/send.js
@ -18,8 +18,8 @@ const {
|
|||||||
signTx,
|
signTx,
|
||||||
} = require('./actions')
|
} = require('./actions')
|
||||||
const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
|
const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
|
||||||
const { isHex, numericBalance, isValidAddress } = require('./util')
|
const { isHex, numericBalance, isValidAddress, allNull } = require('./util')
|
||||||
const { conversionUtil } = require('./conversion-util')
|
const { conversionUtil, conversionGreaterThan } = require('./conversion-util')
|
||||||
const BigNumber = require('bignumber.js')
|
const BigNumber = require('bignumber.js')
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||||
@ -51,7 +51,7 @@ function mapStateToProps (state) {
|
|||||||
error: warning && warning.split('.')[0],
|
error: warning && warning.split('.')[0],
|
||||||
account,
|
account,
|
||||||
identity: identities[address],
|
identity: identities[address],
|
||||||
balance: account ? numericBalance(account.balance) : null,
|
balance: account ? account.balance : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,8 +65,8 @@ function SendTransactionScreen () {
|
|||||||
newTx: {
|
newTx: {
|
||||||
from: '',
|
from: '',
|
||||||
to: '',
|
to: '',
|
||||||
// these values are hardcoded, so "Next" can be clicked
|
amount: 0,
|
||||||
amount: '0x0', // see L544
|
amountToSend: '0x0',
|
||||||
gasPrice: '0x5d21dba00',
|
gasPrice: '0x5d21dba00',
|
||||||
gas: '0x7b0d',
|
gas: '0x7b0d',
|
||||||
txData: null,
|
txData: null,
|
||||||
@ -74,6 +74,8 @@ function SendTransactionScreen () {
|
|||||||
},
|
},
|
||||||
activeCurrency: 'USD',
|
activeCurrency: 'USD',
|
||||||
tooltipIsOpen: false,
|
tooltipIsOpen: false,
|
||||||
|
errors: {},
|
||||||
|
isValid: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.back = this.back.bind(this)
|
this.back = this.back.bind(this)
|
||||||
@ -81,12 +83,26 @@ function SendTransactionScreen () {
|
|||||||
this.onSubmit = this.onSubmit.bind(this)
|
this.onSubmit = this.onSubmit.bind(this)
|
||||||
this.setActiveCurrency = this.setActiveCurrency.bind(this)
|
this.setActiveCurrency = this.setActiveCurrency.bind(this)
|
||||||
this.toggleTooltip = this.toggleTooltip.bind(this)
|
this.toggleTooltip = this.toggleTooltip.bind(this)
|
||||||
|
this.validate = this.validate.bind(this)
|
||||||
|
this.getAmountToSend = this.getAmountToSend.bind(this)
|
||||||
|
this.setErrorsFor = this.setErrorsFor.bind(this)
|
||||||
|
this.clearErrorsFor = this.clearErrorsFor.bind(this)
|
||||||
|
|
||||||
this.renderFromInput = this.renderFromInput.bind(this)
|
this.renderFromInput = this.renderFromInput.bind(this)
|
||||||
this.renderToInput = this.renderToInput.bind(this)
|
this.renderToInput = this.renderToInput.bind(this)
|
||||||
this.renderAmountInput = this.renderAmountInput.bind(this)
|
this.renderAmountInput = this.renderAmountInput.bind(this)
|
||||||
this.renderGasInput = this.renderGasInput.bind(this)
|
this.renderGasInput = this.renderGasInput.bind(this)
|
||||||
this.renderMemoInput = this.renderMemoInput.bind(this)
|
this.renderMemoInput = this.renderMemoInput.bind(this)
|
||||||
|
this.renderErrorMessage = this.renderErrorMessage.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderErrorMessage = function(errorType, warning) {
|
||||||
|
const { errors } = this.state
|
||||||
|
const errorMessage = errors[errorType];
|
||||||
|
|
||||||
|
return errorMessage || warning
|
||||||
|
? h('div.send-screen-input-wrapper__error-message', [ errorMessage || warning ])
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
|
SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
|
||||||
@ -106,6 +122,8 @@ SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onBlur: () => this.setErrorsFor('from'),
|
||||||
|
onFocus: () => this.clearErrorsFor('from'),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
h('datalist#accounts', [
|
h('datalist#accounts', [
|
||||||
@ -118,6 +136,8 @@ SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
|
|||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
this.renderErrorMessage('from'),
|
||||||
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +159,8 @@ SendTransactionScreen.prototype.renderToInput = function (to, identities, addres
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onBlur: () => this.setErrorsFor('to'),
|
||||||
|
onFocus: () => this.clearErrorsFor('to'),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
h('datalist#addresses', [
|
h('datalist#addresses', [
|
||||||
@ -160,6 +182,8 @@ SendTransactionScreen.prototype.renderToInput = function (to, identities, addres
|
|||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
this.renderErrorMessage('to'),
|
||||||
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +207,17 @@ SendTransactionScreen.prototype.renderAmountInput = function (activeCurrency) {
|
|||||||
this.state.newTx,
|
this.state.newTx,
|
||||||
{
|
{
|
||||||
amount: event.target.value,
|
amount: event.target.value,
|
||||||
|
amountToSend: this.getAmountToSend(event.target.value),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onBlur: () => this.setErrorsFor('amount'),
|
||||||
|
onFocus: () => this.clearErrorsFor('amount'),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
this.renderErrorMessage('amount'),
|
||||||
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,14 +289,13 @@ SendTransactionScreen.prototype.render = function () {
|
|||||||
|
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const {
|
const {
|
||||||
// selectedIdentity,
|
warning,
|
||||||
// network,
|
|
||||||
identities,
|
identities,
|
||||||
addressBook,
|
addressBook,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { blockGasLimit, newTx, activeCurrency } = this.state
|
const { blockGasLimit, newTx, activeCurrency, isValid } = this.state
|
||||||
const { gas, gasPrice } = newTx
|
const { gas, gasPrice } = newTx
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -292,12 +320,15 @@ SendTransactionScreen.prototype.render = function () {
|
|||||||
|
|
||||||
this.renderMemoInput(),
|
this.renderMemoInput(),
|
||||||
|
|
||||||
|
this.renderErrorMessage(null, warning),
|
||||||
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Buttons underneath card
|
// Buttons underneath card
|
||||||
h('section.flex-column.flex-center', [
|
h('section.flex-column.flex-center', [
|
||||||
h('button.btn-secondary.send-screen__send-button', {
|
h('button.btn-secondary.send-screen__send-button', {
|
||||||
onClick: (event) => this.onSubmit(event),
|
className: !isValid && 'send-screen__send-button__disabled',
|
||||||
|
onClick: (event) => isValid && this.onSubmit(event),
|
||||||
}, 'Next'),
|
}, 'Next'),
|
||||||
h('button.btn-tertiary.send-screen__cancel-button', {
|
h('button.btn-tertiary.send-screen__cancel-button', {
|
||||||
onClick: this.back,
|
onClick: this.back,
|
||||||
@ -325,62 +356,140 @@ SendTransactionScreen.prototype.back = function () {
|
|||||||
this.props.dispatch(backToAccountDetail(address))
|
this.props.dispatch(backToAccountDetail(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.validate = function (balance, amountToSend, { to, from }) {
|
||||||
|
const sufficientBalance = conversionGreaterThan(
|
||||||
|
{
|
||||||
|
value: balance,
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: amountToSend,
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const amountLessThanZero = conversionGreaterThan(
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
fromNumericBase: 'dec',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: amountToSend,
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const errors = {}
|
||||||
|
|
||||||
|
if (!sufficientBalance) {
|
||||||
|
errors.amount = 'Insufficient funds.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountLessThanZero) {
|
||||||
|
errors.amount = 'Can not send negative amounts of ETH.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!from) {
|
||||||
|
errors.from = 'Required'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from && !isValidAddress(from)) {
|
||||||
|
errors.from = 'Sender address is invalid.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to) {
|
||||||
|
errors.to = 'Required'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to && !isValidAddress(to)) {
|
||||||
|
errors.to = 'Recipient address is invalid.'
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (txData && !isHex(stripHexPrefix(txData))) {
|
||||||
|
// message = 'Transaction data must be hex string.'
|
||||||
|
// return this.props.dispatch(displayWarning(message))
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: allNull(errors),
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.getAmountToSend = function (amount) {
|
||||||
|
const { activeCurrency } = this.state
|
||||||
|
const { conversionRate } = this.props
|
||||||
|
|
||||||
|
// TODO: need a clean way to integrate this into conversionUtil
|
||||||
|
const sendConversionRate = activeCurrency === 'ETH'
|
||||||
|
? conversionRate
|
||||||
|
: new BigNumber(1.0).div(conversionRate)
|
||||||
|
|
||||||
|
return conversionUtil(amount, {
|
||||||
|
fromNumericBase: 'dec',
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
fromCurrency: activeCurrency,
|
||||||
|
toCurrency: 'ETH',
|
||||||
|
toDenomination: 'WEI',
|
||||||
|
conversionRate: sendConversionRate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.setErrorsFor = function (field) {
|
||||||
|
const { balance } = this.props
|
||||||
|
const { newTx, errors: previousErrors } = this.state
|
||||||
|
const { amountToSend } = newTx
|
||||||
|
|
||||||
|
const {
|
||||||
|
isValid,
|
||||||
|
errors: newErrors
|
||||||
|
} = this.validate(balance, amountToSend, newTx)
|
||||||
|
|
||||||
|
const nextErrors = Object.assign({}, previousErrors, {
|
||||||
|
[field]: newErrors[field] || null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
this.setState({
|
||||||
|
errors: nextErrors,
|
||||||
|
isValid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.clearErrorsFor = function (field) {
|
||||||
|
const { errors: previousErrors } = this.state
|
||||||
|
const nextErrors = Object.assign({}, previousErrors, {
|
||||||
|
[field]: null
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
errors: nextErrors,
|
||||||
|
isValid: allNull(nextErrors),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.onSubmit = function (event) {
|
SendTransactionScreen.prototype.onSubmit = function (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const { warning } = this.props
|
const { warning, balance, amountToSend } = this.props
|
||||||
const state = this.state || {}
|
const state = this.state || {}
|
||||||
|
|
||||||
const recipient = state.newTx.to
|
const recipient = state.newTx.to
|
||||||
|
const sender = state.newTx.from
|
||||||
const nickname = state.nickname || ' '
|
const nickname = state.nickname || ' '
|
||||||
|
|
||||||
// TODO: convert this to hex when created and include it in send
|
// TODO: convert this to hex when created and include it in send
|
||||||
const txData = state.newTx.memo
|
const txData = state.newTx.memo
|
||||||
|
|
||||||
let message
|
|
||||||
|
|
||||||
// if (value.gt(balance)) {
|
|
||||||
// message = 'Insufficient funds.'
|
|
||||||
// return this.props.dispatch(actions.displayWarning(message))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (input < 0) {
|
|
||||||
// message = 'Can not send negative amounts of ETH.'
|
|
||||||
// return this.props.dispatch(actions.displayWarning(message))
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!isValidAddress(recipient) && !recipient) {
|
|
||||||
message = 'Recipient address is invalid.'
|
|
||||||
return this.props.dispatch(displayWarning(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (txData && !isHex(stripHexPrefix(txData))) {
|
|
||||||
message = 'Transaction data must be hex string.'
|
|
||||||
return this.props.dispatch(displayWarning(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.dispatch(hideWarning())
|
this.props.dispatch(hideWarning())
|
||||||
|
|
||||||
this.props.dispatch(addToAddressBook(recipient, nickname))
|
this.props.dispatch(addToAddressBook(recipient, nickname))
|
||||||
|
|
||||||
// TODO: need a clean way to integrate this into conversionUtil
|
|
||||||
const sendConversionRate = state.activeCurrency === 'ETH'
|
|
||||||
? this.props.conversionRate
|
|
||||||
: new BigNumber(1.0).div(this.props.conversionRate)
|
|
||||||
|
|
||||||
const sendAmount = conversionUtil(this.state.newTx.amount, {
|
|
||||||
fromNumericBase: 'dec',
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
fromCurrency: state.activeCurrency,
|
|
||||||
toCurrency: 'ETH',
|
|
||||||
toDenomination: 'WEI',
|
|
||||||
conversionRate: sendConversionRate,
|
|
||||||
})
|
|
||||||
|
|
||||||
var txParams = {
|
var txParams = {
|
||||||
from: this.state.newTx.from,
|
from: this.state.newTx.from,
|
||||||
to: this.state.newTx.to,
|
to: this.state.newTx.to,
|
||||||
|
|
||||||
value: sendAmount,
|
value: amountToSend,
|
||||||
|
|
||||||
gas: this.state.newTx.gas,
|
gas: this.state.newTx.gas,
|
||||||
gasPrice: this.state.newTx.gasPrice,
|
gasPrice: this.state.newTx.gasPrice,
|
||||||
@ -389,7 +498,5 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
|
|||||||
if (recipient) txParams.to = addHexPrefix(recipient)
|
if (recipient) txParams.to = addHexPrefix(recipient)
|
||||||
if (txData) txParams.data = txData
|
if (txData) txParams.data = txData
|
||||||
|
|
||||||
if (!warning) {
|
|
||||||
this.props.dispatch(signTx(txParams))
|
this.props.dispatch(signTx(txParams))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -55,6 +55,7 @@ module.exports = {
|
|||||||
getContractAtAddress,
|
getContractAtAddress,
|
||||||
exportAsFile: exportAsFile,
|
exportAsFile: exportAsFile,
|
||||||
isInvalidChecksumAddress,
|
isInvalidChecksumAddress,
|
||||||
|
allNull,
|
||||||
}
|
}
|
||||||
|
|
||||||
function valuesFor (obj) {
|
function valuesFor (obj) {
|
||||||
@ -273,3 +274,7 @@ function exportAsFile (filename, data) {
|
|||||||
document.body.removeChild(elem)
|
document.body.removeChild(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function allNull (obj) {
|
||||||
|
return Object.entries(obj).every(([key, value]) => value === null)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user