1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/ui/app/send-v2.js

461 lines
9.9 KiB
JavaScript

const { inherits } = require('util')
const PersistentForm = require('../lib/persistent-form')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const classnames = require('classnames')
const Identicon = require('./components/identicon')
const FromDropdown = require('./components/send/from-dropdown')
const ToAutoComplete = require('./components/send/to-autocomplete')
const CurrencyDisplay = require('./components/send/currency-display')
const MemoTextArea = require('./components/send/memo-textarea')
const GasFeeDisplay = require('./components/send/gas-fee-display-v2')
const { MIN_GAS_TOTAL } = require('./components/send/send-constants')
const { showModal } = require('./actions')
const {
multiplyCurrencies,
conversionGreaterThan,
addCurrencies,
} = require('./conversion-util')
const {
isBalanceSufficient,
} = require('./components/send/send-utils.js')
const { isValidAddress } = require('./util')
module.exports = SendTransactionScreen
inherits(SendTransactionScreen, PersistentForm)
function SendTransactionScreen () {
PersistentForm.call(this)
this.state = {
dropdownOpen: false,
errors: {
to: null,
amount: null,
},
}
this.handleToChange = this.handleToChange.bind(this)
this.handleAmountChange = this.handleAmountChange.bind(this)
this.validateAmount = this.validateAmount.bind(this)
}
SendTransactionScreen.prototype.componentWillMount = function () {
const {
updateTokenExchangeRate,
selectedToken = {},
getGasPrice,
estimateGas,
selectedAddress,
data,
updateGasTotal,
} = this.props
const { symbol } = selectedToken || {}
const estimateGasParams = {
from: selectedAddress,
gas: '746a528800',
}
if (symbol) {
updateTokenExchangeRate(symbol)
Object.assign(estimateGasParams, { value: '0x0' })
}
if (data) {
Object.assign(estimateGasParams, { data })
}
Promise
.all([
getGasPrice(),
estimateGas({
from: selectedAddress,
gas: '746a528800',
}),
])
.then(([gasPrice, gas]) => {
const newGasTotal = multiplyCurrencies(gas, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
updateGasTotal(newGasTotal)
})
}
SendTransactionScreen.prototype.renderHeaderIcon = function () {
const { selectedToken } = this.props
return h('div.send-v2__send-header-icon-container', [
selectedToken
? h(Identicon, {
diameter: 40,
address: selectedToken.address,
})
: h('img.send-v2__send-header-icon', { src: '../images/eth_logo.svg' })
])
}
SendTransactionScreen.prototype.renderTitle = function () {
const { selectedToken } = this.props
return h('div.send-v2__title', [selectedToken ? 'Send Tokens' : 'Send Funds'])
}
SendTransactionScreen.prototype.renderCopy = function () {
const { selectedToken } = this.props
const tokenText = selectedToken ? 'tokens' : 'ETH'
return h('div.send-v2__form-header-copy', [
h('div.send-v2__copy', `Only send ${tokenText} to an Ethereum address.`),
h('div.send-v2__copy', 'Sending to a different crytpocurrency that is not Ethereum may result in permanent loss.'),
])
}
SendTransactionScreen.prototype.renderHeader = function () {
return h('div', [
h('div.send-v2__header', {}, [
this.renderHeaderIcon(),
h('div.send-v2__arrow-background', [
h('i.fa.fa-lg.fa-arrow-circle-right.send-v2__send-arrow-icon'),
]),
h('div.send-v2__header-tip'),
]),
])
}
SendTransactionScreen.prototype.renderErrorMessage = function(errorType) {
const { errors } = this.props
const errorMessage = errors[errorType];
return errorMessage
? h('div.send-v2__error', [ errorMessage ] )
: null
}
SendTransactionScreen.prototype.renderFromRow = function () {
const {
from,
fromAccounts,
conversionRate,
setSelectedAddress,
updateSendFrom,
} = this.props
const { dropdownOpen } = this.state
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', 'From:'),
h('div.send-v2__form-field', [
h(FromDropdown, {
dropdownOpen,
accounts: fromAccounts,
selectedAccount: from,
onSelect: updateSendFrom,
openDropdown: () => this.setState({ dropdownOpen: true }),
closeDropdown: () => this.setState({ dropdownOpen: false }),
conversionRate,
}),
]),
])
}
SendTransactionScreen.prototype.handleToChange = function (event) {
const { updateSendTo, updateSendErrors } = this.props
const to = event.target.value
let toError = null
if (!to) {
toError = 'Required'
} else if (!isValidAddress(to)) {
toError = 'Recipient address is invalid.'
}
updateSendTo(to)
updateSendErrors({ to: toError })
}
SendTransactionScreen.prototype.renderToRow = function () {
const { toAccounts, errors } = this.props
const { to } = this.state
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', [
'To:',
this.renderErrorMessage('to'),
]),
h('div.send-v2__form-field', [
h(ToAutoComplete, {
to,
accounts: toAccounts,
onChange: this.handleToChange,
inError: Boolean(errors.to),
}),
]),
])
}
SendTransactionScreen.prototype.handleAmountChange = function (value) {
const amount = value
const { updateSendAmount } = this.props
updateSendAmount(amount)
}
SendTransactionScreen.prototype.validateAmount = function (value) {
const {
from: { balance },
updateSendErrors,
amountConversionRate,
conversionRate,
primaryCurrency,
toCurrency,
selectedToken,
gasTotal,
} = this.props
const amount = value
let amountError = null
const sufficientBalance = isBalanceSufficient({
amount,
gasTotal,
balance,
primaryCurrency,
selectedToken,
amountConversionRate,
conversionRate,
})
const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' },
)
if (!sufficientBalance) {
amountError = 'Insufficient funds.'
} else if (amountLessThanZero) {
amountError = 'Can not send negative amounts of ETH.'
}
updateSendErrors({ amount: amountError })
}
SendTransactionScreen.prototype.renderAmountRow = function () {
const {
selectedToken,
primaryCurrency = 'ETH',
convertedCurrency,
amountConversionRate,
errors,
} = this.props
const { amount } = this.state
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', [
'Amount:',
this.renderErrorMessage('amount'),
]),
h('div.send-v2__form-field', [
h(CurrencyDisplay, {
inError: Boolean(errors.amount),
primaryCurrency,
convertedCurrency,
selectedToken,
value: amount,
conversionRate: amountConversionRate,
handleChange: this.handleAmountChange,
validate: this.validateAmount,
}),
]),
])
}
SendTransactionScreen.prototype.renderGasRow = function () {
const {
conversionRate,
convertedCurrency,
showCustomizeGasModal,
gasTotal = MIN_GAS_TOTAL,
} = this.props
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', 'Gas fee:'),
h('div.send-v2__form-field', [
h(GasFeeDisplay, {
gasTotal,
conversionRate,
convertedCurrency,
onClick: showCustomizeGasModal,
}),
h('div.send-v2__sliders-icon-container', {
onClick: showCustomizeGasModal,
}, [
h('i.fa.fa-sliders.send-v2__sliders-icon'),
]),
]),
])
}
SendTransactionScreen.prototype.renderMemoRow = function () {
const { updateSendMemo } = this.props
const { memo } = this.state
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', 'Transaction Memo:'),
h('div.send-v2__form-field', [
h(MemoTextArea, {
memo,
onChange: (event) => updateSendMemo(event.target.value),
}),
]),
])
}
SendTransactionScreen.prototype.renderForm = function () {
return h('div.send-v2__form', {}, [
h('div.sendV2__form-header', [
this.renderTitle(),
this.renderCopy(),
]),
this.renderFromRow(),
this.renderToRow(),
this.renderAmountRow(),
this.renderGasRow(),
this.renderMemoRow(),
])
}
SendTransactionScreen.prototype.renderFooter = function () {
const {
goHome,
clearSend,
errors: { amount: amountError, to: toError }
} = this.props
const noErrors = amountError === null && toError === null
return h('div.send-v2__footer', [
h('button.send-v2__cancel-btn', {
onClick: () => {
clearSend()
goHome()
},
}, 'Cancel'),
h(`button`, {
className: classnames({
'send-v2__next-btn': noErrors,
'send-v2__next-btn__disabled': !noErrors,
}),
disabled: !noErrors,
onClick: event => this.onSubmit(event),
}, 'Next'),
])
}
SendTransactionScreen.prototype.render = function () {
return (
h('div.send-v2__container', [
this.renderHeader(),
this.renderForm(),
this.renderFooter(),
])
)
}
SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress) {
const { toAccounts, addToAddressBook } = this.props
if (!toAccounts.find(({ address }) => newAddress === address)) {
// TODO: nickname, i.e. addToAddressBook(recipient, nickname)
addToAddressBook(newAddress)
}
}
SendTransactionScreen.prototype.onSubmit = function (event) {
event.preventDefault()
const {
from: {address: from},
to,
amount,
gasLimit: gas,
gasPrice,
signTokenTx,
signTx,
selectedToken,
toAccounts,
clearSend,
} = this.props
this.addToAddressBookIfNew(to)
const txParams = {
from,
value: '0',
gas,
gasPrice,
}
if (!selectedToken) {
txParams.value = amount
txParams.to = to
}
clearSend()
selectedToken
? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams)
}