1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 19:10:22 +01:00
metamask-extension/ui/app/components/send-token/index.js

441 lines
12 KiB
JavaScript
Raw Normal View History

2017-09-07 13:24:03 +02:00
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
2017-09-12 08:18:54 +02:00
const classnames = require('classnames')
2017-09-29 02:39:53 +02:00
const abi = require('ethereumjs-abi')
2017-09-07 13:24:03 +02:00
const inherits = require('util').inherits
const actions = require('../../actions')
const selectors = require('../../selectors')
const { isValidAddress, allNull } = require('../../util')
const t = require('../../../i18n')
2017-09-07 13:24:03 +02:00
// const BalanceComponent = require('./balance-component')
const Identicon = require('../identicon')
const TokenBalance = require('../token-balance')
const CurrencyToggle = require('../send/currency-toggle')
const GasTooltip = require('../send/gas-tooltip')
const GasFeeDisplay = require('../send/gas-fee-display')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTokenScreen)
function mapStateToProps (state) {
// const sidebarOpen = state.appState.sidebarOpen
const { warning } = state.appState
2017-09-07 13:24:03 +02:00
const identities = state.metamask.identities
const addressBook = state.metamask.addressBook
const conversionRate = state.metamask.conversionRate
const currentBlockGasLimit = state.metamask.currentBlockGasLimit
const accounts = state.metamask.accounts
2017-09-07 13:24:03 +02:00
const selectedTokenAddress = state.metamask.selectedTokenAddress
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
2017-09-13 10:25:39 +02:00
const selectedToken = selectors.getSelectedToken(state)
const tokenExchangeRates = state.metamask.tokenExchangeRates
const pair = `${selectedToken.symbol.toLowerCase()}_eth`
const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
2017-09-07 13:24:03 +02:00
return {
selectedAddress,
2017-09-07 13:24:03 +02:00
selectedTokenAddress,
identities,
addressBook,
conversionRate,
2017-09-13 10:25:39 +02:00
tokenExchangeRate,
2017-09-07 13:24:03 +02:00
currentBlockGasLimit,
2017-09-13 10:25:39 +02:00
selectedToken,
warning,
2017-09-07 13:24:03 +02:00
}
}
function mapDispatchToProps (dispatch) {
return {
backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
hideWarning: () => dispatch(actions.hideWarning()),
addToAddressBook: (recipient, nickname) => dispatch(
actions.addToAddressBook(recipient, nickname)
),
signTx: txParams => dispatch(actions.signTx(txParams)),
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
),
2017-09-13 10:25:39 +02:00
updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
2017-09-29 02:39:53 +02:00
estimateGas: params => dispatch(actions.estimateGas(params)),
getGasPrice: () => dispatch(actions.getGasPrice()),
2017-09-07 13:24:03 +02:00
}
}
inherits(SendTokenScreen, Component)
function SendTokenScreen () {
Component.call(this)
this.state = {
to: '',
amount: '0x0',
amountToSend: '0x0',
2017-09-07 13:24:03 +02:00
selectedCurrency: 'USD',
isGasTooltipOpen: false,
gasPrice: null,
gasLimit: null,
2017-09-12 08:18:54 +02:00
errors: {},
}
}
2017-09-13 10:25:39 +02:00
SendTokenScreen.prototype.componentWillMount = function () {
const {
updateTokenExchangeRate,
selectedToken: { symbol },
getGasPrice,
estimateGas,
2017-09-29 02:39:53 +02:00
selectedAddress,
2017-09-13 10:25:39 +02:00
} = this.props
updateTokenExchangeRate(symbol)
2017-09-29 02:39:53 +02:00
const data = Array.prototype.map.call(
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
x => ('00' + x.toString(16)).slice(-2)
).join('')
console.log(data)
Promise.all([
getGasPrice(),
2017-09-29 02:39:53 +02:00
estimateGas({
from: selectedAddress,
value: '0x0',
gas: '746a528800',
data,
}),
])
.then(([blockGasPrice, estimatedGas]) => {
2017-09-29 02:39:53 +02:00
console.log({ blockGasPrice, estimatedGas})
this.setState({
gasPrice: blockGasPrice,
gasLimit: estimatedGas,
})
})
}
2017-09-12 08:18:54 +02:00
SendTokenScreen.prototype.validate = function () {
const {
to,
amount: stringAmount,
2017-09-12 08:18:54 +02:00
gasPrice: hexGasPrice,
gasLimit: hexGasLimit,
} = this.state
const gasPrice = parseInt(hexGasPrice, 16)
const gasLimit = parseInt(hexGasLimit, 16) / 1000000000
const amount = Number(stringAmount)
2017-09-12 08:18:54 +02:00
const errors = {
to: !to ? t('required') : null,
amount: !amount ? t('required') : null,
gasPrice: !gasPrice ? t('gasPriceRequired') : null,
gasLimit: !gasLimit ? t('gasLimitRequired') : null,
2017-09-12 08:18:54 +02:00
}
2017-09-22 23:34:56 +02:00
if (to && !isValidAddress(to)) {
errors.to = t('invalidAddress')
}
const isValid = Object.entries(errors).every(([key, value]) => value === null)
2017-09-12 08:18:54 +02:00
return {
isValid,
errors: isValid ? {} : errors,
2017-09-12 08:18:54 +02:00
}
}
SendTokenScreen.prototype.setErrorsFor = function (field) {
const { errors: previousErrors } = this.state
const {
isValid,
2017-11-02 13:15:59 +01:00
errors: newErrors,
} = this.validate()
const nextErrors = Object.assign({}, previousErrors, {
2017-11-02 13:15:59 +01:00
[field]: newErrors[field] || null,
})
if (!isValid) {
this.setState({
errors: nextErrors,
isValid,
})
}
}
SendTokenScreen.prototype.clearErrorsFor = function (field) {
const { errors: previousErrors } = this.state
const nextErrors = Object.assign({}, previousErrors, {
2017-11-02 13:15:59 +01:00
[field]: null,
})
this.setState({
errors: nextErrors,
isValid: allNull(nextErrors),
})
}
SendTokenScreen.prototype.getAmountToSend = function (amount, selectedToken) {
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
const sendAmount = '0x' + Number(amount * multiplier).toString(16)
return sendAmount
}
2017-09-12 08:18:54 +02:00
SendTokenScreen.prototype.submit = function () {
const {
to,
amount,
gasPrice,
gasLimit,
} = this.state
const {
identities,
selectedAddress,
selectedTokenAddress,
hideWarning,
addToAddressBook,
signTokenTx,
2017-09-14 10:09:57 +02:00
selectedToken,
} = this.props
const { nickname = ' ' } = identities[to] || {}
hideWarning()
addToAddressBook(to, nickname)
const txParams = {
from: selectedAddress,
value: '0',
gas: gasLimit,
gasPrice: gasPrice,
}
const sendAmount = this.getAmountToSend(amount, selectedToken)
2017-09-14 10:09:57 +02:00
signTokenTx(selectedTokenAddress, to, sendAmount, txParams)
2017-09-07 13:24:03 +02:00
}
SendTokenScreen.prototype.renderToAddressInput = function () {
const {
identities,
addressBook,
} = this.props
const {
to,
2017-09-12 08:18:54 +02:00
errors: { to: errorMessage },
2017-09-07 13:24:03 +02:00
} = this.state
2017-09-12 08:18:54 +02:00
return h('div', {
className: classnames('send-screen-input-wrapper', {
'send-screen-input-wrapper--error': errorMessage,
}),
}, [
2018-01-29 21:29:01 +01:00
h('div', [t('to') + ':']),
2017-09-07 13:24:03 +02:00
h('input.large-input.send-screen-input', {
name: 'address',
list: 'addresses',
placeholder: t('address'),
2017-09-07 13:24:03 +02:00
value: to,
2017-09-12 08:18:54 +02:00
onChange: e => this.setState({
to: e.target.value,
errors: {},
}),
onBlur: () => {
this.setErrorsFor('to')
},
onFocus: event => {
if (to) event.target.select()
this.clearErrorsFor('to')
},
2017-09-07 13:24:03 +02:00
}),
h('datalist#addresses', [
// Corresponds to the addresses owned.
Object.entries(identities).map(([key, { address, name }]) => {
return h('option', {
value: address,
label: name,
key: address,
})
}),
addressBook.map(({ address, name }) => {
return h('option', {
value: address,
label: name,
key: address,
})
}),
]),
2017-09-12 08:18:54 +02:00
h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
2017-09-07 13:24:03 +02:00
])
}
SendTokenScreen.prototype.renderAmountInput = function () {
const {
selectedCurrency,
2017-09-12 08:18:54 +02:00
amount,
errors: { amount: errorMessage },
2017-09-07 13:24:03 +02:00
} = this.state
const {
2017-09-13 10:25:39 +02:00
tokenExchangeRate,
2017-09-07 13:24:03 +02:00
selectedToken: {symbol},
} = this.props
2017-09-12 08:18:54 +02:00
return h('div.send-screen-input-wrapper', {
className: classnames('send-screen-input-wrapper', {
'send-screen-input-wrapper--error': errorMessage,
}),
}, [
2017-09-07 13:24:03 +02:00
h('div.send-screen-amount-labels', [
2018-01-29 21:29:01 +01:00
h('span', [t('amount')]),
2017-09-07 13:24:03 +02:00
h(CurrencyToggle, {
2017-09-13 10:25:39 +02:00
currentCurrency: tokenExchangeRate ? selectedCurrency : 'USD',
currencies: tokenExchangeRate ? [ symbol, 'USD' ] : [],
2017-09-07 13:24:03 +02:00
onClick: currency => this.setState({ selectedCurrency: currency }),
}),
]),
h('input.large-input.send-screen-input', {
placeholder: `0 ${symbol}`,
type: 'number',
2017-09-12 08:18:54 +02:00
value: amount,
onChange: e => this.setState({
amount: e.target.value,
}),
onBlur: () => {
this.setErrorsFor('amount')
},
onFocus: () => this.clearErrorsFor('amount'),
2017-09-07 13:24:03 +02:00
}),
2017-09-12 08:18:54 +02:00
h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
2017-09-07 13:24:03 +02:00
])
}
SendTokenScreen.prototype.renderGasInput = function () {
const {
isGasTooltipOpen,
gasPrice,
gasLimit,
selectedCurrency,
2017-09-12 08:18:54 +02:00
errors: {
gasPrice: gasPriceErrorMessage,
gasLimit: gasLimitErrorMessage,
},
2017-09-07 13:24:03 +02:00
} = this.state
const {
conversionRate,
2017-09-13 10:25:39 +02:00
tokenExchangeRate,
2017-09-07 13:24:03 +02:00
currentBlockGasLimit,
} = this.props
2017-09-12 08:18:54 +02:00
return h('div.send-screen-input-wrapper', {
className: classnames('send-screen-input-wrapper', {
'send-screen-input-wrapper--error': gasPriceErrorMessage || gasLimitErrorMessage,
}),
}, [
2017-09-07 13:24:03 +02:00
isGasTooltipOpen && h(GasTooltip, {
className: 'send-tooltip',
gasPrice: gasPrice || '0x0',
gasLimit: gasLimit || '0x0',
2017-09-07 13:24:03 +02:00
onClose: () => this.setState({ isGasTooltipOpen: false }),
onFeeChange: ({ gasLimit, gasPrice }) => {
2017-09-12 08:18:54 +02:00
this.setState({ gasLimit, gasPrice, errors: {} })
2017-09-07 13:24:03 +02:00
},
onBlur: () => {
this.setErrorsFor('gasLimit')
this.setErrorsFor('gasPrice')
},
onFocus: () => {
this.clearErrorsFor('gasLimit')
this.clearErrorsFor('gasPrice')
},
2017-09-07 13:24:03 +02:00
}),
h('div.send-screen-gas-labels', {}, [
2018-01-29 21:29:01 +01:00
h('span', [ h('i.fa.fa-bolt'), t('gasFee') + ':']),
h('span', [t('whatsThis')]),
2017-09-07 13:24:03 +02:00
]),
h('div.large-input.send-screen-gas-input', [
h(GasFeeDisplay, {
conversionRate,
2017-09-13 10:25:39 +02:00
tokenExchangeRate,
gasPrice: gasPrice || '0x0',
activeCurrency: selectedCurrency,
gas: gasLimit || '0x0',
2017-09-07 13:24:03 +02:00
blockGasLimit: currentBlockGasLimit,
}),
h(
'div.send-screen-gas-input-customize',
{ onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) },
[t('customize')]
2017-09-07 13:24:03 +02:00
),
]),
2017-09-12 08:18:54 +02:00
h('div.send-screen-input-wrapper__error-message', [
gasPriceErrorMessage || gasLimitErrorMessage,
]),
2017-09-07 13:24:03 +02:00
])
}
SendTokenScreen.prototype.renderMemoInput = function () {
return h('div.send-screen-input-wrapper', [
h('div', {}, [t('transactionMemo')]),
2017-09-07 13:24:03 +02:00
h(
'input.large-input.send-screen-input',
{ onChange: e => this.setState({ memo: e.target.value }) }
),
])
}
SendTokenScreen.prototype.renderButtons = function () {
const { selectedAddress, backToAccountDetail } = this.props
const { isValid } = this.validate()
return h('div.send-token__button-group', [
h('button.send-token__button-next.btn-secondary', {
className: !isValid && 'send-screen__send-button__disabled',
onClick: () => isValid && this.submit(),
}, [t('next')]),
h('button.send-token__button-cancel.btn-tertiary', {
onClick: () => backToAccountDetail(selectedAddress),
}, [t('cancel')]),
])
}
2017-09-07 13:24:03 +02:00
SendTokenScreen.prototype.render = function () {
const {
selectedTokenAddress,
selectedToken,
warning,
2017-09-07 13:24:03 +02:00
} = this.props
return h('div.send-token', [
h('div.send-token__content', [
h(Identicon, {
diameter: 75,
address: selectedTokenAddress,
}),
h('div.send-token__title', [t('sendTokens')]),
h('div.send-token__description', [t('sendTokensAnywhere')]),
h('div.send-token__balance-text', [t('tokenBalance')]),
h('div.send-token__token-balance', [
h(TokenBalance, { token: selectedToken, balanceOnly: true }),
]),
h('div.send-token__token-symbol', [selectedToken.symbol]),
this.renderToAddressInput(),
this.renderAmountInput(),
this.renderGasInput(),
this.renderMemoInput(),
2017-11-02 13:15:59 +01:00
warning && h('div.send-screen-input-wrapper--error', {},
h('div.send-screen-input-wrapper__error-message', [
warning,
])
),
2017-09-07 13:24:03 +02:00
]),
this.renderButtons(),
2017-09-07 13:24:03 +02:00
])
}