From 711a4def86d9e8c626ee554a976d9eab6abdcbee Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 15 Jun 2017 15:38:23 -0700 Subject: [PATCH] Make add token screen a fully working form Entering the address of a valid HumanStandardToken even auto-fills the other fields! --- ui/app/add-token.js | 152 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 11 deletions(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index cd47709ab..fdbb5fe53 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -4,21 +4,37 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const ethUtil = require('ethereumjs-util') +const abi = require('human-standard-token-abi') +const Eth = require('ethjs-query') +const EthContract = require('ethjs-contract') + +const emptyAddr = '0x0000000000000000000000000000000000000000' + module.exports = connect(mapStateToProps)(AddTokenScreen) function mapStateToProps (state) { - return {} + return { + } } inherits(AddTokenScreen, Component) function AddTokenScreen () { - this.state = { warning: null } + this.state = { + warning: null, + address: null, + symbol: 'TOKEN', + decimals: 18, + } Component.call(this) } AddTokenScreen.prototype.render = function () { + const props = this.props + const state = this.state - const { warning } = state + const { warning, address, symbol, decimals } = state + return ( h('.flex-column.flex-grow', [ @@ -48,6 +64,26 @@ AddTokenScreen.prototype.render = function () { }, }, [ + h('div', [ + h('span', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + }, 'Token Address'), + ]), + + h('section.flex-row.flex-center', [ + h('input#token-address', { + name: 'address', + placeholder: 'Token Address', + onChange: this.tokenAddressDidChange.bind(this), + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + }), + ]), + h('div', [ h('span', { style: { fontWeight: 'bold', paddingRight: '10px'}, @@ -57,17 +93,43 @@ AddTokenScreen.prototype.render = function () { h('div', { style: {display: 'flex'} }, [ h('input#token_symbol', { placeholder: `Like "ETH"`, + value: symbol, style: { width: 'inherit', flex: '1 0 auto', height: '30px', margin: '8px', }, - onKeyPress (event) { - if (event.key === 'Enter') { - var element = event.target - var newRpc = element.value - } + onChange: (event) => { + var element = event.target + var symbol = element.value + this.setState({ symbol }) + }, + }), + ]), + + h('div', [ + h('span', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + }, 'Decimals of Precision'), + ]), + + h('div', { style: {display: 'flex'} }, [ + h('input#token_decimals', { + value: decimals, + type: 'number', + min: 0, + max: 36, + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onChange: (event) => { + var element = event.target + var decimals = element.value.trim() + this.setState({ decimals }) }, }), ]), @@ -77,9 +139,11 @@ AddTokenScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - event.preventDefault() - var tokenSymbolEl = document.querySelector('input#token_symbol') - var tokenSymbol = tokenSymbolEl.value + const valid = this.validateInputs() + if (!valid) return + + const { address, symbol, decimals } = state + this.props.dispatch(addToken(address.trim(), symbol.trim(), decimals)) }, }, 'Add'), ]), @@ -88,3 +152,69 @@ AddTokenScreen.prototype.render = function () { ) } +AddTokenScreen.prototype.componentWillMount = function () { + if (typeof global.ethereumProvider === 'undefined') return + + this.eth = new Eth(global.ethereumProvider) + this.contract = new EthContract(this.eth) + this.TokenContract = this.contract(abi) +} + +AddTokenScreen.prototype.tokenAddressDidChange = function (event) { + const el = event.target + const address = el.value.trim() + if (ethUtil.isValidAddress(address) && address !== emptyAddr) { + this.setState({ address }) + this.attemptToAutoFillTokenParams(address) + } +} + +AddTokenScreen.prototype.validateInputs = function () { + let msg = '' + const state = this.state + const { address, symbol, decimals } = state + + const validAddress = ethUtil.isValidAddress(address) + if (!validAddress) { + msg += 'Address is invalid. ' + } + + const validDecimals = decimals >= 0 && decimals < 36 + if (!validDecimals) { + msg += 'Decimals must be at least 0, and not over 36. ' + } + + const symbolLen = symbol.trim().length + const validSymbol = symbolLen > 0 && symbolLen < 10 + if (!validSymbol) { + msg += 'Symbol must be between 0 and 10 characters.' + } + + const isValid = validAddress && validDecimals + + if (!isValid) { + this.setState({ + warning: msg, + }) + } else { + this.setState({ warning: null }) + } + + return isValid +} + +AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { + const contract = this.TokenContract.at(address) + + const results = await Promise.all([ + contract.symbol(), + contract.decimals(), + ]) + + const [ symbol, decimals ] = results + if (symbol && decimals) { + console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals }) + this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) + } +} +