mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
387 lines
11 KiB
JavaScript
387 lines
11 KiB
JavaScript
const inherits = require('util').inherits
|
||
const Component = require('react').Component
|
||
const classnames = require('classnames')
|
||
const h = require('react-hyperscript')
|
||
const connect = require('react-redux').connect
|
||
const Fuse = require('fuse.js')
|
||
const contractMap = require('eth-contract-metadata')
|
||
const contractList = Object.entries(contractMap).map(([ _, tokenData]) => tokenData)
|
||
const fuse = new Fuse(contractList, {
|
||
shouldSort: true,
|
||
threshold: 0.45,
|
||
location: 0,
|
||
distance: 100,
|
||
maxPatternLength: 32,
|
||
minMatchCharLength: 1,
|
||
keys: ['address', 'name', 'symbol'],
|
||
})
|
||
const actions = require('./actions')
|
||
// const Tooltip = require('./components/tooltip.js')
|
||
|
||
|
||
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, mapDispatchToProps)(AddTokenScreen)
|
||
|
||
function mapStateToProps (state) {
|
||
return {
|
||
identities: state.metamask.identities,
|
||
}
|
||
}
|
||
|
||
function mapDispatchToProps (dispatch) {
|
||
return {
|
||
goHome: () => dispatch(actions.goHome()),
|
||
}
|
||
}
|
||
|
||
inherits(AddTokenScreen, Component)
|
||
function AddTokenScreen () {
|
||
this.state = {
|
||
// warning: null,
|
||
// address: null,
|
||
// symbol: 'TOKEN',
|
||
// decimals: 18,
|
||
customAddress: '',
|
||
customSymbol: '',
|
||
customDecimals: 0,
|
||
searchQuery: '',
|
||
isCollapsed: true,
|
||
selectedToken: {},
|
||
}
|
||
this.tokenAddressDidChange = this.tokenAddressDidChange.bind(this)
|
||
Component.call(this)
|
||
}
|
||
|
||
AddTokenScreen.prototype.toggleToken = function (symbol) {
|
||
const { selectedToken } = this.state
|
||
const { [symbol]: isSelected } = selectedToken
|
||
this.setState({
|
||
selectedToken: {
|
||
...selectedToken,
|
||
[symbol]: !isSelected,
|
||
},
|
||
})
|
||
}
|
||
|
||
AddTokenScreen.prototype.renderCustomForm = function () {
|
||
const { customAddress, customSymbol, customDecimals } = this.state
|
||
|
||
return !this.state.isCollapsed && (
|
||
h('div.add-token__add-custom-form', [
|
||
h('div.add-token__add-custom-field', [
|
||
h('div.add-token__add-custom-label', 'Token Address'),
|
||
h('input.add-token__add-custom-input', {
|
||
type: 'text',
|
||
onChange: this.tokenAddressDidChange,
|
||
value: customAddress,
|
||
}),
|
||
]),
|
||
h('div.add-token__add-custom-field', [
|
||
h('div.add-token__add-custom-label', 'Token Symbol'),
|
||
h('input.add-token__add-custom-input', {
|
||
type: 'text',
|
||
value: customSymbol,
|
||
disabled: true,
|
||
}),
|
||
]),
|
||
h('div.add-token__add-custom-field', [
|
||
h('div.add-token__add-custom-label', 'Decimals of Precision'),
|
||
h('input.add-token__add-custom-input', {
|
||
type: 'number',
|
||
value: customDecimals,
|
||
disabled: true,
|
||
}),
|
||
]),
|
||
])
|
||
)
|
||
}
|
||
|
||
AddTokenScreen.prototype.renderTokenList = function () {
|
||
const { searchQuery = '', selectedToken } = this.state
|
||
const results = searchQuery
|
||
? fuse.search(searchQuery) || []
|
||
: contractList
|
||
|
||
return Array(6).fill(undefined)
|
||
.map((_, i) => {
|
||
const { logo, symbol, name } = results[i] || {}
|
||
return Boolean(logo || symbol || name) && (
|
||
h('div.add-token__token-wrapper', {
|
||
className: classnames('add-token__token-wrapper', {
|
||
'add-token__token-wrapper--selected': selectedToken[symbol],
|
||
}),
|
||
onClick: () => this.toggleToken(symbol),
|
||
}, [
|
||
h('div.add-token__token-icon', {
|
||
style: {
|
||
backgroundImage: `url(images/contract/${logo})`,
|
||
},
|
||
}),
|
||
h('div.add-token__token-data', [
|
||
h('div.add-token__token-symbol', symbol),
|
||
h('div.add-token__token-name', name),
|
||
]),
|
||
])
|
||
)
|
||
})
|
||
}
|
||
|
||
AddTokenScreen.prototype.render = function () {
|
||
const { isCollapsed } = this.state
|
||
const { goHome } = this.props
|
||
|
||
return (
|
||
h('div.add-token', [
|
||
h('div.add-token__wrapper', [
|
||
h('div.add-token__title-container', [
|
||
h('div.add-token__title', 'Add Token'),
|
||
h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'),
|
||
h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'),
|
||
]),
|
||
h('div.add-token__content-container', [
|
||
h('div.add-token__input-container', [
|
||
h('input.add-token__input', {
|
||
type: 'text',
|
||
placeholder: 'Search',
|
||
onChange: e => this.setState({ searchQuery: e.target.value }),
|
||
}),
|
||
]),
|
||
h(
|
||
'div.add-token__token-icons-container',
|
||
this.renderTokenList(),
|
||
),
|
||
]),
|
||
h('div.add-token__footers', [
|
||
h('div.add-token__add-custom', {
|
||
onClick: () => this.setState({ isCollapsed: !isCollapsed }),
|
||
}, 'Add custom token'),
|
||
this.renderCustomForm(),
|
||
]),
|
||
]),
|
||
h('div.add-token__buttons', [
|
||
h('button.btn-secondary', 'Next'),
|
||
h('button.btn-tertiary', {
|
||
onClick: goHome,
|
||
}, 'Cancel'),
|
||
]),
|
||
])
|
||
)
|
||
}
|
||
|
||
// AddTokenScreen.prototype.render = function () {
|
||
// const state = this.state
|
||
// const props = this.props
|
||
// const { warning, symbol, decimals } = state
|
||
|
||
// return (
|
||
// h('.flex-column.flex-grow', [
|
||
|
||
// // subtitle and nav
|
||
// h('.section-title.flex-row.flex-center', [
|
||
// h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||
// onClick: (event) => {
|
||
// props.dispatch(actions.goHome())
|
||
// },
|
||
// }),
|
||
// h('h2.page-subtitle', 'Add Token'),
|
||
// ]),
|
||
|
||
// h('.error', {
|
||
// style: {
|
||
// display: warning ? 'block' : 'none',
|
||
// padding: '0 20px',
|
||
// textAlign: 'center',
|
||
// },
|
||
// }, warning),
|
||
|
||
// // conf view
|
||
// h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||
// h('.flex-space-around', {
|
||
// style: {
|
||
// padding: '20px',
|
||
// },
|
||
// }, [
|
||
|
||
// h('div', [
|
||
// h(Tooltip, {
|
||
// position: 'top',
|
||
// title: 'The contract of the actual token contract. Click for more info.',
|
||
// }, [
|
||
// h('a', {
|
||
// style: { fontWeight: 'bold', paddingRight: '10px'},
|
||
// href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
|
||
// target: '_blank',
|
||
// }, [
|
||
// h('span', 'Token Contract Address '),
|
||
// h('i.fa.fa-question-circle'),
|
||
// ]),
|
||
// ]),
|
||
// ]),
|
||
|
||
// h('section.flex-row.flex-center', [
|
||
// h('input#token-address', {
|
||
// name: 'address',
|
||
// placeholder: 'Token Contract 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'},
|
||
// }, 'Token Symbol'),
|
||
// ]),
|
||
|
||
// 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',
|
||
// },
|
||
// 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 })
|
||
// },
|
||
// }),
|
||
// ]),
|
||
|
||
// h('button', {
|
||
// style: {
|
||
// alignSelf: 'center',
|
||
// },
|
||
// onClick: (event) => {
|
||
// const valid = this.validateInputs()
|
||
// if (!valid) return
|
||
|
||
// const { address, symbol, decimals } = this.state
|
||
// this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
|
||
// },
|
||
// }, 'Add'),
|
||
// ]),
|
||
// ]),
|
||
// ])
|
||
// )
|
||
// }
|
||
|
||
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 (e) {
|
||
const customAddress = e.target.value.trim()
|
||
this.setState({ customAddress })
|
||
if (ethUtil.isValidAddress(customAddress) && customAddress !== emptyAddr) {
|
||
this.attemptToAutoFillTokenParams(customAddress)
|
||
} else {
|
||
this.setState({
|
||
customSymbol: '',
|
||
customDecimals: 0,
|
||
})
|
||
}
|
||
}
|
||
|
||
AddTokenScreen.prototype.validateInputs = function () {
|
||
let msg = ''
|
||
const state = this.state
|
||
const identitiesList = Object.keys(this.props.identities)
|
||
const { address, symbol, decimals } = state
|
||
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
|
||
|
||
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 ownAddress = identitiesList.includes(standardAddress)
|
||
if (ownAddress) {
|
||
msg = 'Personal address detected. Input the token contract address.'
|
||
}
|
||
|
||
const isValid = validAddress && validDecimals && !ownAddress
|
||
|
||
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) {
|
||
this.setState({
|
||
customSymbol: symbol[0],
|
||
customDecimals: decimals[0].toString(),
|
||
})
|
||
}
|
||
}
|