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-07 13:24:03 +02:00
|
|
|
const inherits = require('util').inherits
|
|
|
|
const actions = require('../../actions')
|
|
|
|
const selectors = require('../../selectors')
|
2017-09-21 01:04:03 +02:00
|
|
|
const { isValidAddress } = require('../../util')
|
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
|
|
|
|
|
2017-09-21 01:04:03 +02:00
|
|
|
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
|
2017-09-12 07:14:09 +02:00
|
|
|
const accounts = state.metamask.accounts
|
2017-09-07 13:24:03 +02:00
|
|
|
const selectedTokenAddress = state.metamask.selectedTokenAddress
|
2017-09-12 07:14:09 +02:00
|
|
|
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-21 01:04:03 +02:00
|
|
|
|
2017-09-07 13:24:03 +02:00
|
|
|
return {
|
2017-09-12 07:14:09 +02:00
|
|
|
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,
|
2017-09-21 01:04:03 +02:00
|
|
|
warning,
|
2017-09-07 13:24:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapDispatchToProps (dispatch) {
|
|
|
|
return {
|
2017-09-12 07:14:09 +02:00
|
|
|
backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
|
2017-09-12 11:22:23 +02:00
|
|
|
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-07 13:24:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inherits(SendTokenScreen, Component)
|
|
|
|
function SendTokenScreen () {
|
|
|
|
Component.call(this)
|
|
|
|
this.state = {
|
|
|
|
to: '',
|
2017-09-13 10:25:39 +02:00
|
|
|
amount: '',
|
2017-09-07 13:24:03 +02:00
|
|
|
selectedCurrency: 'USD',
|
|
|
|
isGasTooltipOpen: false,
|
|
|
|
gasPrice: '0x5d21dba00',
|
|
|
|
gasLimit: '0x7b0d',
|
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 },
|
|
|
|
} = this.props
|
|
|
|
|
|
|
|
updateTokenExchangeRate(symbol)
|
|
|
|
}
|
|
|
|
|
2017-09-12 08:18:54 +02:00
|
|
|
SendTokenScreen.prototype.validate = function () {
|
|
|
|
const {
|
|
|
|
to,
|
2017-09-12 11:22:23 +02:00
|
|
|
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
|
2017-09-12 11:22:23 +02:00
|
|
|
const amount = Number(stringAmount)
|
2017-09-12 08:18:54 +02:00
|
|
|
|
|
|
|
const errors = {
|
|
|
|
to: !to ? 'Required' : null,
|
2017-09-12 11:22:23 +02:00
|
|
|
amount: !amount ? 'Required' : null,
|
2017-09-12 08:18:54 +02:00
|
|
|
gasPrice: !gasPrice ? 'Gas Price Required' : null,
|
|
|
|
gasLimit: !gasLimit ? 'Gas Limit Required' : null,
|
|
|
|
}
|
|
|
|
|
2017-09-22 23:34:56 +02:00
|
|
|
if (to && !isValidAddress(to)) {
|
2017-09-21 01:04:03 +02:00
|
|
|
errors.to = 'Invalid address'
|
|
|
|
}
|
|
|
|
|
|
|
|
const isValid = Object.entries(errors).every(([key, value]) => value === null)
|
2017-09-12 08:18:54 +02:00
|
|
|
return {
|
2017-09-21 01:04:03 +02:00
|
|
|
isValid,
|
|
|
|
errors: isValid ? {} : errors,
|
2017-09-12 08:18:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTokenScreen.prototype.submit = function () {
|
2017-09-12 11:22:23 +02:00
|
|
|
const {
|
|
|
|
to,
|
|
|
|
amount,
|
|
|
|
gasPrice,
|
|
|
|
gasLimit,
|
|
|
|
} = this.state
|
|
|
|
|
|
|
|
const {
|
|
|
|
identities,
|
|
|
|
selectedAddress,
|
|
|
|
selectedTokenAddress,
|
|
|
|
hideWarning,
|
|
|
|
addToAddressBook,
|
|
|
|
signTokenTx,
|
2017-09-14 10:09:57 +02:00
|
|
|
selectedToken,
|
2017-09-12 11:22:23 +02:00
|
|
|
} = this.props
|
|
|
|
|
|
|
|
const { nickname = ' ' } = identities[to] || {}
|
2017-09-12 08:18:54 +02:00
|
|
|
const { isValid, errors } = this.validate()
|
|
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
return this.setState({ errors })
|
2017-09-07 13:24:03 +02:00
|
|
|
}
|
2017-09-12 11:22:23 +02:00
|
|
|
|
|
|
|
hideWarning()
|
|
|
|
addToAddressBook(to, nickname)
|
|
|
|
|
|
|
|
const txParams = {
|
|
|
|
from: selectedAddress,
|
|
|
|
value: '0',
|
|
|
|
gas: gasLimit,
|
|
|
|
gasPrice: gasPrice,
|
|
|
|
}
|
|
|
|
|
2017-09-14 10:09:57 +02:00
|
|
|
const { decimals } = selectedToken || {}
|
|
|
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
|
|
|
const sendAmount = Number(amount * multiplier).toString(16)
|
|
|
|
|
|
|
|
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,
|
|
|
|
}),
|
|
|
|
}, [
|
2017-09-07 13:24:03 +02:00
|
|
|
h('div', ['To:']),
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
name: 'address',
|
|
|
|
list: 'addresses',
|
|
|
|
placeholder: 'Address',
|
|
|
|
value: to,
|
2017-09-12 08:18:54 +02:00
|
|
|
onChange: e => this.setState({
|
|
|
|
to: e.target.value,
|
|
|
|
errors: {},
|
|
|
|
}),
|
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', [
|
|
|
|
h('span', ['Amount']),
|
|
|
|
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,
|
|
|
|
errors: {},
|
|
|
|
}),
|
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,
|
|
|
|
gasLimit,
|
|
|
|
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
|
|
|
},
|
|
|
|
}),
|
|
|
|
|
|
|
|
h('div.send-screen-gas-labels', {}, [
|
|
|
|
h('span', [ h('i.fa.fa-bolt'), 'Gas fee:']),
|
|
|
|
h('span', ['What\'s this?']),
|
|
|
|
]),
|
|
|
|
h('div.large-input.send-screen-gas-input', [
|
|
|
|
h(GasFeeDisplay, {
|
|
|
|
conversionRate,
|
2017-09-13 10:25:39 +02:00
|
|
|
tokenExchangeRate,
|
2017-09-07 13:24:03 +02:00
|
|
|
gasPrice,
|
2017-09-14 04:57:33 +02:00
|
|
|
activeCurrency: selectedCurrency,
|
2017-09-07 13:24:03 +02:00
|
|
|
gas: gasLimit,
|
|
|
|
blockGasLimit: currentBlockGasLimit,
|
|
|
|
}),
|
|
|
|
h(
|
|
|
|
'div.send-screen-gas-input-customize',
|
|
|
|
{ onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) },
|
|
|
|
['Customize']
|
|
|
|
),
|
|
|
|
]),
|
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 () {
|
2017-09-12 07:14:09 +02:00
|
|
|
return h('div.send-screen-input-wrapper', [
|
2017-09-07 13:24:03 +02:00
|
|
|
h('div', {}, ['Transaction memo (optional)']),
|
|
|
|
h(
|
|
|
|
'input.large-input.send-screen-input',
|
|
|
|
{ onChange: e => this.setState({ memo: e.target.value }) }
|
|
|
|
),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
2017-09-12 07:14:09 +02:00
|
|
|
SendTokenScreen.prototype.renderButtons = function () {
|
|
|
|
const { selectedAddress, backToAccountDetail } = this.props
|
|
|
|
|
|
|
|
return h('div.send-token__button-group', [
|
|
|
|
h('button.send-token__button-next.btn-secondary', {
|
2017-09-12 08:18:54 +02:00
|
|
|
onClick: () => this.submit(),
|
2017-09-12 07:14:09 +02:00
|
|
|
}, ['Next']),
|
|
|
|
h('button.send-token__button-cancel.btn-tertiary', {
|
|
|
|
onClick: () => backToAccountDetail(selectedAddress),
|
|
|
|
}, ['Cancel']),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
2017-09-07 13:24:03 +02:00
|
|
|
SendTokenScreen.prototype.render = function () {
|
|
|
|
const {
|
|
|
|
selectedTokenAddress,
|
|
|
|
selectedToken,
|
2017-09-21 01:04:03 +02:00
|
|
|
warning,
|
2017-09-07 13:24:03 +02:00
|
|
|
} = this.props
|
|
|
|
|
|
|
|
return h('div.send-token', [
|
2017-09-12 07:14:09 +02:00
|
|
|
h('div.send-token__content', [
|
|
|
|
h(Identicon, {
|
|
|
|
diameter: 75,
|
|
|
|
address: selectedTokenAddress,
|
|
|
|
}),
|
|
|
|
h('div.send-token__title', ['Send Tokens']),
|
|
|
|
h('div.send-token__description', ['Send Tokens to anyone with an Ethereum account']),
|
|
|
|
h('div.send-token__balance-text', ['Your Token Balance is:']),
|
|
|
|
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-09-22 23:34:56 +02:00
|
|
|
warning && h('div.send-screen-input-wrapper--error',
|
2017-09-21 01:04:03 +02:00
|
|
|
h('div.send-screen-input-wrapper__error-message', [
|
|
|
|
warning,
|
|
|
|
])
|
|
|
|
),
|
2017-09-07 13:24:03 +02:00
|
|
|
]),
|
2017-09-12 07:14:09 +02:00
|
|
|
this.renderButtons(),
|
2017-09-07 13:24:03 +02:00
|
|
|
])
|
|
|
|
}
|