Handling custom token decimal fetch failure due to network error (#10956)

feature/default_network_editable
Niranjana Binoy 4 years ago committed by Dan Miller
parent 6a73b7c998
commit d4cb403d51
  1. 9
      app/_locales/en/messages.json
  2. 2
      package.json
  3. 22
      ui/helpers/utils/token-util.js
  4. 95
      ui/pages/add-token/add-token.component.js
  5. 14
      ui/pages/add-token/add-token.container.js
  6. 2
      ui/pages/add-token/add-token.test.js
  7. 13
      ui/pages/add-token/index.scss
  8. 15
      ui/store/actions.js
  9. 8
      yarn.lock

@ -486,7 +486,7 @@
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts, and tokens. Would you like to restore this data now?"
},
"decimal": {
"message": "Decimals of Precision"
"message": "Token Decimal"
},
"decimalsMustZerotoTen": {
"message": "Decimals must be at least 0, and not over 36."
@ -2221,6 +2221,9 @@
"tokenContractAddress": {
"message": "Token Contract Address"
},
"tokenDecimalFetchFailed": {
"message": "Token decimal required."
},
"tokenSymbol": {
"message": "Token Symbol"
},
@ -2352,6 +2355,10 @@
"userName": {
"message": "Username"
},
"verifyThisTokenDecimalOn": {
"message": "Token decimal can be found on $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisTokenOn": {
"message": "Verify this token on $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""

@ -100,7 +100,7 @@
"@metamask/controllers": "^8.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.5.0",
"@metamask/eth-token-tracker": "^3.0.1",
"@metamask/etherscan-link": "^2.0.0",
"@metamask/etherscan-link": "^2.1.0",
"@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^2.5.0",
"@metamask/obs-store": "^5.0.0",

@ -13,7 +13,6 @@ const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
}, {});
const DEFAULT_SYMBOL = '';
const DEFAULT_DECIMALS = '0';
async function getSymbolFromContract(tokenAddress) {
const token = util.getContractAtAddress(tokenAddress);
@ -78,25 +77,6 @@ async function getDecimals(tokenAddress) {
return decimals;
}
export async function fetchSymbolAndDecimals(tokenAddress) {
let symbol, decimals;
try {
symbol = await getSymbol(tokenAddress);
decimals = await getDecimals(tokenAddress);
} catch (error) {
log.warn(
`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`,
error,
);
}
return {
symbol: symbol || DEFAULT_SYMBOL,
decimals: decimals || DEFAULT_DECIMALS,
};
}
export async function getSymbolAndDecimals(tokenAddress, existingTokens = []) {
const existingToken = existingTokens.find(
({ address }) => tokenAddress === address,
@ -123,7 +103,7 @@ export async function getSymbolAndDecimals(tokenAddress, existingTokens = []) {
return {
symbol: symbol || DEFAULT_SYMBOL,
decimals: decimals || DEFAULT_DECIMALS,
decimals,
};
}

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { getTokenTrackerLink } from '@metamask/etherscan-link';
import { checkExistingAddresses } from '../../helpers/utils/util';
import { tokenInfoGetter } from '../../helpers/utils/token-util';
import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes';
@ -8,6 +9,10 @@ import PageContainer from '../../components/ui/page-container';
import { Tabs, Tab } from '../../components/ui/tabs';
import { isValidHexAddress } from '../../../shared/modules/hexstring-utils';
import { addHexPrefix } from '../../../app/scripts/lib/util';
import ActionableMessage from '../swaps/actionable-message';
import Typography from '../../components/ui/typography';
import { TYPOGRAPHY, FONT_WEIGHT } from '../../helpers/constants/design-system';
import Button from '../../components/ui/button';
import TokenList from './token-list';
import TokenSearch from './token-search';
@ -30,6 +35,8 @@ class AddToken extends Component {
identities: PropTypes.object,
showSearchTab: PropTypes.bool.isRequired,
mostRecentOverviewPage: PropTypes.string.isRequired,
chainId: PropTypes.string,
rpcPrefs: PropTypes.object,
};
state = {
@ -42,8 +49,9 @@ class AddToken extends Component {
customAddressError: null,
customSymbolError: null,
customDecimalsError: null,
autoFilled: false,
forceEditSymbol: false,
symbolAutoFilled: false,
decimalAutoFilled: false,
};
componentDidMount() {
@ -148,10 +156,11 @@ class AddToken extends Component {
}
async attemptToAutoFillTokenParams(address) {
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address);
const { symbol = '', decimals } = await this.tokenInfoGetter(address);
const autoFilled = Boolean(symbol && decimals);
this.setState({ autoFilled });
const symbolAutoFilled = Boolean(symbol);
const decimalAutoFilled = Boolean(decimals);
this.setState({ symbolAutoFilled, decimalAutoFilled });
this.handleCustomSymbolChange(symbol || '');
this.handleCustomDecimalsChange(decimals);
}
@ -162,7 +171,8 @@ class AddToken extends Component {
customAddress,
customAddressError: null,
tokenSelectorError: null,
autoFilled: false,
symbolAutoFilled: false,
decimalAutoFilled: false,
});
const addressIsValid = isValidHexAddress(customAddress, {
@ -213,16 +223,18 @@ class AddToken extends Component {
}
handleCustomDecimalsChange(value) {
const customDecimals = value.trim();
const validDecimals =
customDecimals !== null &&
customDecimals !== '' &&
customDecimals >= MIN_DECIMAL_VALUE &&
customDecimals <= MAX_DECIMAL_VALUE;
let customDecimals;
let customDecimalsError = null;
if (!validDecimals) {
customDecimalsError = this.context.t('decimalsMustZerotoTen');
if (value) {
customDecimals = Number(value.trim());
customDecimalsError =
value < MIN_DECIMAL_VALUE || value > MAX_DECIMAL_VALUE
? this.context.t('decimalsMustZerotoTen')
: null;
} else {
customDecimals = '';
customDecimalsError = this.context.t('tokenDecimalFetchFailed');
}
this.setState({ customDecimals, customDecimalsError });
@ -236,10 +248,23 @@ class AddToken extends Component {
customAddressError,
customSymbolError,
customDecimalsError,
autoFilled,
forceEditSymbol,
symbolAutoFilled,
decimalAutoFilled,
} = this.state;
const { chainId, rpcPrefs } = this.props;
const blockExplorerTokenLink = getTokenTrackerLink(
customAddress,
chainId,
null,
null,
{ blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null },
);
const blockExplorerLabel = rpcPrefs?.blockExplorerUrl
? new URL(blockExplorerTokenLink).hostname
: this.context.t('etherscan');
return (
<div className="add-token__custom-token-form">
<TextField
@ -260,7 +285,7 @@ class AddToken extends Component {
<span className="add-token__custom-symbol__label">
{this.context.t('tokenSymbol')}
</span>
{autoFilled && !forceEditSymbol && (
{symbolAutoFilled && !forceEditSymbol && (
<div
className="add-token__custom-symbol__edit"
onClick={() => this.setState({ forceEditSymbol: true })}
@ -276,7 +301,7 @@ class AddToken extends Component {
error={customSymbolError}
fullWidth
margin="normal"
disabled={autoFilled && !forceEditSymbol}
disabled={symbolAutoFilled && !forceEditSymbol}
/>
<TextField
id="custom-decimals"
@ -284,13 +309,47 @@ class AddToken extends Component {
type="number"
value={customDecimals}
onChange={(e) => this.handleCustomDecimalsChange(e.target.value)}
error={customDecimalsError}
error={customDecimals ? customDecimalsError : null}
fullWidth
margin="normal"
disabled={autoFilled}
disabled={decimalAutoFilled}
min={MIN_DECIMAL_VALUE}
max={MAX_DECIMAL_VALUE}
/>
{customDecimals === '' && (
<ActionableMessage
message={
<>
<Typography
variant={TYPOGRAPHY.H7}
fontWeight={FONT_WEIGHT.BOLD}
>
{this.context.t('tokenDecimalFetchFailed')}
</Typography>
<Typography
variant={TYPOGRAPHY.H7}
fontWeight={FONT_WEIGHT.NORMAL}
>
{this.context.t('verifyThisTokenDecimalOn', [
<Button
type="link"
key="add-token-verify-token-decimal"
className="add-token__link"
rel="noopener noreferrer"
target="_blank"
href={blockExplorerTokenLink}
>
{blockExplorerLabel}
</Button>,
])}
</Typography>
</>
}
type="warning"
withRightButton
className="add-token__decimal-warning"
/>
)}
</div>
);
}

@ -2,12 +2,20 @@ import { connect } from 'react-redux';
import { setPendingTokens, clearPendingTokens } from '../../store/actions';
import { getMostRecentOverviewPage } from '../../ducks/history/history';
import { getIsMainnet } from '../../selectors/selectors';
import {
getIsMainnet,
getRpcPrefsForCurrentProvider,
} from '../../selectors/selectors';
import AddToken from './add-token.component';
const mapStateToProps = (state) => {
const {
metamask: { identities, tokens, pendingTokens },
metamask: {
identities,
tokens,
pendingTokens,
provider: { chainId },
},
} = state;
return {
identities,
@ -15,6 +23,8 @@ const mapStateToProps = (state) => {
tokens,
pendingTokens,
showSearchTab: getIsMainnet(state) || process.env.IN_TEST === 'true',
chainId,
rpcPrefs: getRpcPrefsForCurrentProvider(state),
};
};

@ -82,7 +82,7 @@ describe('Add Token', () => {
expect(
wrapper.find('AddToken').instance().state.customDecimals,
).toStrictEqual(tokenPrecision);
).toStrictEqual(Number(tokenPrecision));
});
it('next', () => {

@ -1,6 +1,8 @@
@import 'token-list/index';
.add-token {
$self: &;
&__custom-token-form {
padding: 8px 16px 16px;
@ -13,6 +15,9 @@
-webkit-appearance: none;
display: none;
}
& #{$self}__decimal-warning {
margin-top: 5px;
}
}
&__search-token {
@ -41,4 +46,12 @@
cursor: pointer;
}
}
&__link {
@include H7;
display: inline;
color: $primary-blue;
padding-left: 0;
}
}

@ -9,7 +9,7 @@ import {
loadRelativeTimeFormatLocaleData,
} from '../helpers/utils/i18n-helper';
import { getMethodDataAsync } from '../helpers/utils/transactions.util';
import { fetchSymbolAndDecimals } from '../helpers/utils/token-util';
import { getSymbolAndDecimals } from '../helpers/utils/token-util';
import switchDirection from '../helpers/utils/switch-direction';
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../shared/constants/app';
import { hasUnconfirmedTransactions } from '../helpers/utils/confirm-tx.util';
@ -24,7 +24,6 @@ import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-accoun
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { LISTED_CONTRACT_ADDRESSES } from '../../shared/constants/tokens';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import * as actionConstants from './actionConstants';
let background = null;
@ -2252,7 +2251,7 @@ export function setPendingTokens(pendingTokens) {
const { customToken = {}, selectedTokens = {} } = pendingTokens;
const { address, symbol, decimals } = customToken;
const tokens =
address && symbol && decimals
address && symbol && decimals >= 0 <= 36
? {
...selectedTokens,
[address]: {
@ -2654,12 +2653,10 @@ export function getTokenParams(tokenAddress) {
dispatch(loadingTokenParamsStarted());
log.debug(`loadingTokenParams`);
return fetchSymbolAndDecimals(tokenAddress, existingTokens).then(
({ symbol, decimals }) => {
dispatch(addToken(tokenAddress, symbol, Number(decimals)));
dispatch(loadingTokenParamsFinished());
},
);
return getSymbolAndDecimals(tokenAddress).then(({ symbol, decimals }) => {
dispatch(addToken(tokenAddress, symbol, Number(decimals)));
dispatch(loadingTokenParamsFinished());
});
};
}

@ -2739,10 +2739,10 @@
human-standard-token-abi "^1.0.2"
safe-event-emitter "^1.0.1"
"@metamask/etherscan-link@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.0.0.tgz#89035736515a39532ba1142d87b9a8c2b4f920f1"
integrity sha512-/YS32hS2UTTxs0KyUmAgaDj1w4dzAvOrT+p4TJtpICeH3E/k51r2FO0Or7WJJI/mpzTqNKgcH5yyS2oCtupGiA==
"@metamask/etherscan-link@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973"
integrity sha512-ADuWlTUkFfN2vXlz81Bg/0BA+XRor+CdK1055p6k7H6BLIPoDKn9SBOFld9haQFuR9cKh/JYHcnlSIv5R4fUEw==
"@metamask/forwarder@^1.1.0":
version "1.1.0"

Loading…
Cancel
Save