mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Handling custom token decimal fetch failure due to network error (#10956)
This commit is contained in:
parent
6a73b7c998
commit
d4cb403d51
@ -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…
Reference in New Issue
Block a user