aboutsummaryrefslogblamecommitdiffstats
path: root/ui/app/components/send-token/index.js
blob: dd8ca6b9dde73cd7e4336e0a6de57e854e8d121e (plain) (tree)
1
2
3
4
5
6
7
8


                                              
                                        


                                            
                                                







                                                          




                                                                              
                                    



                                                                  
                                          
                                                                  
                                                                                    



                                                                        
 
          
                    



                         
                      
                         
                  
            




                                        
                                                                                   







                                                                            
                                                                                       







                                    
               



                            



               








                                                            


                                                  
                         





                                                         
                                     
 

                                
                                        



                                                      
                                  



                                                                                
          

                                  



                                                













                         
                  


                                                 



                                             
   










                                




                                                             









                                                              
                                 

                




                                                        





                                              



                                    

















                                                                    
                                                                        





                                                           

                                     


                
                      


                            




                                                        


                                        

                                                                      





                                                                           




                                    
       
                                                                        








                                                        



                                     



                   
                      


                         




                                                                                       





                                                                
                                                         









                                                  
                          
                 
                                         








                                                                                  


                                                       



                                                         
                                             







                                                                




                                                             
                                   






                                                          



                                                
            


                              















                                                                                           
                                                          



                                                           
       
                         

    
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const classnames = require('classnames')
const inherits = require('util').inherits
const actions = require('../../actions')
const selectors = require('../../selectors')
const { isValidAddress } = require('../../util')

// 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
  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
  const selectedTokenAddress = state.metamask.selectedTokenAddress
  const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
  const selectedToken = selectors.getSelectedToken(state)
  const tokenExchangeRates = state.metamask.tokenExchangeRates
  const pair = `${selectedToken.symbol.toLowerCase()}_eth`
  const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}

  return {
    selectedAddress,
    selectedTokenAddress,
    identities,
    addressBook,
    conversionRate,
    tokenExchangeRate,
    currentBlockGasLimit,
    selectedToken,
    warning,
  }
}

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))
    ),
    updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
  }
}

inherits(SendTokenScreen, Component)
function SendTokenScreen () {
  Component.call(this)
  this.state = {
    to: '',
    amount: '',
    selectedCurrency: 'USD',
    isGasTooltipOpen: false,
    gasPrice: '0x5d21dba00',
    gasLimit: '0x7b0d',
    errors: {},
  }
}

SendTokenScreen.prototype.componentWillMount = function () {
  const {
    updateTokenExchangeRate,
    selectedToken: { symbol },
  } = this.props

  updateTokenExchangeRate(symbol)
}

SendTokenScreen.prototype.validate = function () {
  const {
    to,
    amount: stringAmount,
    gasPrice: hexGasPrice,
    gasLimit: hexGasLimit,
  } = this.state

  const gasPrice = parseInt(hexGasPrice, 16)
  const gasLimit = parseInt(hexGasLimit, 16) / 1000000000
  const amount = Number(stringAmount)

  const errors = {
    to: !to ? 'Required' : null,
    amount: !amount ? 'Required' : null,
    gasPrice: !gasPrice ? 'Gas Price Required' : null,
    gasLimit: !gasLimit ? 'Gas Limit Required' : null,
  }

  if (to && !isValidAddress(to)) {
    errors.to = 'Invalid address'
  }

  const isValid = Object.entries(errors).every(([key, value]) => value === null)
  return {
    isValid,
    errors: isValid ? {} : errors,
  }
}

SendTokenScreen.prototype.submit = function () {
  const {
    to,
    amount,
    gasPrice,
    gasLimit,
  } = this.state

  const {
    identities,
    selectedAddress,
    selectedTokenAddress,
    hideWarning,
    addToAddressBook,
    signTokenTx,
    selectedToken,
  } = this.props

  const { nickname = ' ' } = identities[to] || {}
  const { isValid, errors } = this.validate()

  if (!isValid) {
    return this.setState({ errors })
  }

  hideWarning()
  addToAddressBook(to, nickname)

  const txParams = {
    from: selectedAddress,
    value: '0',
    gas: gasLimit,
    gasPrice: gasPrice,
  }

  const { decimals } = selectedToken || {}
  const multiplier = Math.pow(10, Number(decimals || 0))
  const sendAmount = Number(amount * multiplier).toString(16)

  signTokenTx(selectedTokenAddress, to, sendAmount, txParams)
}

SendTokenScreen.prototype.renderToAddressInput = function () {
  const {
    identities,
    addressBook,
  } = this.props

  const {
    to,
    errors: { to: errorMessage },
  } = this.state

  return h('div', {
    className: classnames('send-screen-input-wrapper', {
      'send-screen-input-wrapper--error': errorMessage,
    }),
  }, [
    h('div', ['To:']),
    h('input.large-input.send-screen-input', {
      name: 'address',
      list: 'addresses',
      placeholder: 'Address',
      value: to,
      onChange: e => this.setState({
        to: e.target.value,
        errors: {},
      }),
    }),
    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,
        })
      }),
    ]),
    h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
  ])
}

SendTokenScreen.prototype.renderAmountInput = function () {
  const {
    selectedCurrency,
    amount,
    errors: { amount: errorMessage },
  } = this.state

  const {
    tokenExchangeRate,
    selectedToken: {symbol},
  } = this.props

  return h('div.send-screen-input-wrapper', {
    className: classnames('send-screen-input-wrapper', {
      'send-screen-input-wrapper--error': errorMessage,
    }),
  }, [
    h('div.send-screen-amount-labels', [
      h('span', ['Amount']),
      h(CurrencyToggle, {
        currentCurrency: tokenExchangeRate ? selectedCurrency : 'USD',
        currencies: tokenExchangeRate ? [ symbol, 'USD' ] : [],
        onClick: currency => this.setState({ selectedCurrency: currency }),
      }),
    ]),
    h('input.large-input.send-screen-input', {
      placeholder: `0 ${symbol}`,
      type: 'number',
      value: amount,
      onChange: e => this.setState({
        amount: e.target.value,
        errors: {},
      }),
    }),
    h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
  ])
}

SendTokenScreen.prototype.renderGasInput = function () {
  const {
    isGasTooltipOpen,
    gasPrice,
    gasLimit,
    selectedCurrency,
    errors: {
      gasPrice: gasPriceErrorMessage,
      gasLimit: gasLimitErrorMessage,
    },
  } = this.state

  const {
    conversionRate,
    tokenExchangeRate,
    currentBlockGasLimit,
  } = this.props

  return h('div.send-screen-input-wrapper', {
    className: classnames('send-screen-input-wrapper', {
      'send-screen-input-wrapper--error': gasPriceErrorMessage || gasLimitErrorMessage,
    }),
  }, [
    isGasTooltipOpen && h(GasTooltip, {
      className: 'send-tooltip',
      gasPrice,
      gasLimit,
      onClose: () => this.setState({ isGasTooltipOpen: false }),
      onFeeChange: ({ gasLimit, gasPrice }) => {
        this.setState({ gasLimit, gasPrice, errors: {} })
      },
    }),

    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,
        tokenExchangeRate,
        gasPrice,
        activeCurrency: selectedCurrency,
        gas: gasLimit,
        blockGasLimit: currentBlockGasLimit,
      }),
      h(
        'div.send-screen-gas-input-customize',
        { onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) },
        ['Customize']
      ),
    ]),
    h('div.send-screen-input-wrapper__error-message', [
      gasPriceErrorMessage || gasLimitErrorMessage,
    ]),
  ])
}

SendTokenScreen.prototype.renderMemoInput = function () {
  return h('div.send-screen-input-wrapper', [
    h('div', {}, ['Transaction memo (optional)']),
    h(
      'input.large-input.send-screen-input',
      { onChange: e => this.setState({ memo: e.target.value }) }
    ),
  ])
}

SendTokenScreen.prototype.renderButtons = function () {
  const { selectedAddress, backToAccountDetail } = this.props

  return h('div.send-token__button-group', [
    h('button.send-token__button-next.btn-secondary', {
      onClick: () => this.submit(),
    }, ['Next']),
    h('button.send-token__button-cancel.btn-tertiary', {
      onClick: () => backToAccountDetail(selectedAddress),
    }, ['Cancel']),
  ])
}

SendTokenScreen.prototype.render = function () {
  const {
    selectedTokenAddress,
    selectedToken,
    warning,
  } = this.props

  return h('div.send-token', [
    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(),
      warning && h('div.send-screen-input-wrapper--error',
        h('div.send-screen-input-wrapper__error-message', [
          warning,
        ])
      ),
    ]),
    this.renderButtons(),
  ])
}