1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-11 20:27:12 +01:00
metamask-extension/ui/app/pages/add-token/add-token.component.js

342 lines
9.0 KiB
JavaScript
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ethUtil from 'ethereumjs-util';
import { checkExistingAddresses } from '../../helpers/utils/util';
import { tokenInfoGetter } from '../../helpers/utils/token-util';
import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes';
import TextField from '../../components/ui/text-field';
import PageContainer from '../../components/ui/page-container';
import { Tabs, Tab } from '../../components/ui/tabs';
import { addHexPrefix } from '../../../../app/scripts/lib/util';
import TokenList from './token-list';
import TokenSearch from './token-search';
const emptyAddr = '0x0000000000000000000000000000000000000000';
2018-05-20 08:04:19 +02:00
class AddToken extends Component {
static contextTypes = {
t: PropTypes.func,
};
2018-05-20 08:04:19 +02:00
static propTypes = {
history: PropTypes.object,
setPendingTokens: PropTypes.func,
pendingTokens: PropTypes.object,
clearPendingTokens: PropTypes.func,
tokens: PropTypes.array,
identities: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
};
2018-05-20 08:04:19 +02:00
state = {
customAddress: '',
customSymbol: '',
customDecimals: 0,
searchResults: [],
selectedTokens: {},
tokenSelectorError: null,
customAddressError: null,
customSymbolError: null,
customDecimalsError: null,
autoFilled: false,
forceEditSymbol: false,
};
2018-05-20 08:04:19 +02:00
2020-11-03 00:41:28 +01:00
componentDidMount() {
this.tokenInfoGetter = tokenInfoGetter();
const { pendingTokens = {} } = this.props;
const pendingTokenKeys = Object.keys(pendingTokens);
2018-05-20 08:04:19 +02:00
if (pendingTokenKeys.length > 0) {
let selectedTokens = {};
let customToken = {};
2018-05-20 08:04:19 +02:00
2020-02-15 21:34:12 +01:00
pendingTokenKeys.forEach((tokenAddress) => {
const token = pendingTokens[tokenAddress];
const { isCustom } = token;
2018-05-20 08:04:19 +02:00
if (isCustom) {
customToken = { ...token };
2018-05-20 08:04:19 +02:00
} else {
selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } };
2018-05-20 08:04:19 +02:00
}
});
2018-05-20 08:04:19 +02:00
const {
address: customAddress = '',
symbol: customSymbol = '',
decimals: customDecimals = 0,
} = customToken;
2018-05-20 08:04:19 +02:00
2020-11-03 00:41:28 +01:00
this.setState({
selectedTokens,
customAddress,
customSymbol,
customDecimals,
});
2018-05-20 08:04:19 +02:00
}
}
2020-11-03 00:41:28 +01:00
handleToggleToken(token) {
const { address } = token;
const { selectedTokens = {} } = this.state;
const selectedTokensCopy = { ...selectedTokens };
2018-05-20 08:04:19 +02:00
if (address in selectedTokensCopy) {
delete selectedTokensCopy[address];
2018-05-20 08:04:19 +02:00
} else {
selectedTokensCopy[address] = token;
2018-05-20 08:04:19 +02:00
}
this.setState({
selectedTokens: selectedTokensCopy,
tokenSelectorError: null,
});
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
hasError() {
2018-05-20 08:04:19 +02:00
const {
tokenSelectorError,
customAddressError,
customSymbolError,
customDecimalsError,
} = this.state;
2018-05-20 08:04:19 +02:00
2020-11-03 00:41:28 +01:00
return (
tokenSelectorError ||
customAddressError ||
customSymbolError ||
customDecimalsError
);
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
hasSelected() {
const { customAddress = '', selectedTokens = {} } = this.state;
return customAddress || Object.keys(selectedTokens).length > 0;
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
handleNext() {
2018-05-20 08:04:19 +02:00
if (this.hasError()) {
return;
2018-05-20 08:04:19 +02:00
}
if (!this.hasSelected()) {
this.setState({ tokenSelectorError: this.context.t('mustSelectOne') });
return;
2018-05-20 08:04:19 +02:00
}
const { setPendingTokens, history } = this.props;
2018-05-20 08:04:19 +02:00
const {
customAddress: address,
customSymbol: symbol,
customDecimals: decimals,
selectedTokens,
} = this.state;
2018-05-20 08:04:19 +02:00
const customToken = {
address,
symbol,
decimals,
};
2018-05-20 08:04:19 +02:00
setPendingTokens({ customToken, selectedTokens });
history.push(CONFIRM_ADD_TOKEN_ROUTE);
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
async attemptToAutoFillTokenParams(address) {
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address);
2018-05-20 23:08:45 +02:00
const autoFilled = Boolean(symbol && decimals);
this.setState({ autoFilled });
this.handleCustomSymbolChange(symbol || '');
this.handleCustomDecimalsChange(decimals);
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
handleCustomAddressChange(value) {
const customAddress = value.trim();
2018-05-20 08:04:19 +02:00
this.setState({
customAddress,
customAddressError: null,
tokenSelectorError: null,
autoFilled: false,
});
2018-05-20 08:04:19 +02:00
const isValidAddress = ethUtil.isValidAddress(customAddress);
const standardAddress = addHexPrefix(customAddress).toLowerCase();
2018-05-20 08:04:19 +02:00
switch (true) {
case !isValidAddress:
this.setState({
customAddressError: this.context.t('invalidAddress'),
customSymbol: '',
customDecimals: 0,
customSymbolError: null,
customDecimalsError: null,
});
2018-05-20 08:04:19 +02:00
break;
2018-05-20 08:04:19 +02:00
case Boolean(this.props.identities[standardAddress]):
this.setState({
customAddressError: this.context.t('personalAddressDetected'),
});
2018-05-20 08:04:19 +02:00
break;
2018-05-20 08:04:19 +02:00
case checkExistingAddresses(customAddress, this.props.tokens):
this.setState({
customAddressError: this.context.t('tokenAlreadyAdded'),
});
2018-05-20 08:04:19 +02:00
break;
2018-05-20 08:04:19 +02:00
default:
if (customAddress !== emptyAddr) {
this.attemptToAutoFillTokenParams(customAddress);
2018-05-20 08:04:19 +02:00
}
}
}
2020-11-03 00:41:28 +01:00
handleCustomSymbolChange(value) {
const customSymbol = value.trim();
const symbolLength = customSymbol.length;
let customSymbolError = null;
2018-05-20 08:04:19 +02:00
2018-11-22 19:39:59 +01:00
if (symbolLength <= 0 || symbolLength >= 12) {
customSymbolError = this.context.t('symbolBetweenZeroTwelve');
2018-05-20 08:04:19 +02:00
}
this.setState({ customSymbol, customSymbolError });
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
handleCustomDecimalsChange(value) {
const customDecimals = value.trim();
2020-11-03 00:41:28 +01:00
const validDecimals =
customDecimals !== null &&
2018-05-20 08:04:19 +02:00
customDecimals !== '' &&
customDecimals >= 0 &&
customDecimals <= 36;
let customDecimalsError = null;
2018-05-20 08:04:19 +02:00
if (!validDecimals) {
customDecimalsError = this.context.t('decimalsMustZerotoTen');
2018-05-20 08:04:19 +02:00
}
this.setState({ customDecimals, customDecimalsError });
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
renderCustomTokenForm() {
2018-05-20 08:04:19 +02:00
const {
customAddress,
customSymbol,
customDecimals,
customAddressError,
customSymbolError,
customDecimalsError,
autoFilled,
forceEditSymbol,
} = this.state;
2018-05-20 08:04:19 +02:00
return (
<div className="add-token__custom-token-form">
<TextField
id="custom-address"
label={this.context.t('tokenContractAddress')}
2018-05-20 08:04:19 +02:00
type="text"
value={customAddress}
2020-02-15 21:34:12 +01:00
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
2018-05-20 08:04:19 +02:00
error={customAddressError}
fullWidth
autoFocus
2018-05-20 08:04:19 +02:00
margin="normal"
/>
<TextField
id="custom-symbol"
2020-11-03 00:41:28 +01:00
label={
<div className="add-token__custom-symbol__label-wrapper">
<span className="add-token__custom-symbol__label">
{this.context.t('tokenSymbol')}
</span>
2020-11-03 00:41:28 +01:00
{autoFilled && !forceEditSymbol && (
<div
className="add-token__custom-symbol__edit"
onClick={() => this.setState({ forceEditSymbol: true })}
>
{this.context.t('edit')}
</div>
)}
</div>
2020-11-03 00:41:28 +01:00
}
2018-05-20 08:04:19 +02:00
type="text"
value={customSymbol}
2020-02-15 21:34:12 +01:00
onChange={(e) => this.handleCustomSymbolChange(e.target.value)}
2018-05-20 08:04:19 +02:00
error={customSymbolError}
fullWidth
margin="normal"
disabled={autoFilled && !forceEditSymbol}
2018-05-20 08:04:19 +02:00
/>
<TextField
id="custom-decimals"
2018-06-06 20:38:57 +02:00
label={this.context.t('decimal')}
2018-05-20 08:04:19 +02:00
type="number"
value={customDecimals}
2020-02-15 21:34:12 +01:00
onChange={(e) => this.handleCustomDecimalsChange(e.target.value)}
2018-05-20 08:04:19 +02:00
error={customDecimalsError}
fullWidth
margin="normal"
disabled={autoFilled}
/>
</div>
);
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
renderSearchToken() {
const { tokenSelectorError, selectedTokens, searchResults } = this.state;
2018-05-20 08:04:19 +02:00
return (
<div className="add-token__search-token">
<TokenSearch
2020-11-03 00:41:28 +01:00
onSearch={({ results = [] }) =>
this.setState({ searchResults: results })
}
2018-05-20 08:04:19 +02:00
error={tokenSelectorError}
/>
<div className="add-token__token-list">
<TokenList
results={searchResults}
selectedTokens={selectedTokens}
2020-02-15 21:34:12 +01:00
onToggleToken={(token) => this.handleToggleToken(token)}
2018-05-20 08:04:19 +02:00
/>
</div>
</div>
);
2018-05-20 08:04:19 +02:00
}
2020-11-03 00:41:28 +01:00
renderTabs() {
2018-07-19 02:47:01 +02:00
return (
<Tabs>
2020-11-03 00:41:28 +01:00
<Tab name={this.context.t('search')}>{this.renderSearchToken()}</Tab>
2018-07-19 02:47:01 +02:00
<Tab name={this.context.t('customToken')}>
2020-11-03 00:41:28 +01:00
{this.renderCustomTokenForm()}
2018-07-19 02:47:01 +02:00
</Tab>
</Tabs>
);
2018-07-19 02:47:01 +02:00
}
2020-11-03 00:41:28 +01:00
render() {
const { history, clearPendingTokens, mostRecentOverviewPage } = this.props;
2018-05-20 08:04:19 +02:00
return (
2018-07-19 02:47:01 +02:00
<PageContainer
title={this.context.t('addTokens')}
tabsComponent={this.renderTabs()}
onSubmit={() => this.handleNext()}
disabled={Boolean(this.hasError()) || !this.hasSelected()}
2018-07-19 02:47:01 +02:00
onCancel={() => {
clearPendingTokens();
history.push(mostRecentOverviewPage);
2018-07-19 02:47:01 +02:00
}}
/>
);
2018-05-20 08:04:19 +02:00
}
}
export default AddToken;