Skip to content

Commit

Permalink
fix: token decimals fetched from the chain (#7540)
Browse files Browse the repository at this point in the history
Co-authored-by: Cal Leung <cleun007@gmail.com>
  • Loading branch information
tommasini and Cal-L authored Oct 20, 2023
1 parent 865efd8 commit 8fd310c
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ exports[`AddCustomToken should render correctly 1`] = `
</Text>
<TextInput
keyboardAppearance="light"
onBlur={[Function]}
onChangeText={[Function]}
onSubmitEditing={[Function]}
placeholder="0x..."
Expand Down Expand Up @@ -127,6 +126,7 @@ exports[`AddCustomToken should render correctly 1`] = `
Token Symbol
</Text>
<TextInput
editable={true}
keyboardAppearance="light"
onBlur={[Function]}
onChangeText={[Function]}
Expand Down Expand Up @@ -178,6 +178,7 @@ exports[`AddCustomToken should render correctly 1`] = `
Token Decimal
</Text>
<TextInput
editable={true}
keyboardAppearance="light"
keyboardType="numeric"
maxLength={2}
Expand Down
90 changes: 63 additions & 27 deletions app/components/UI/AddCustomToken/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const createStyles = (colors) =>
...fontStyles.normal,
color: colors.text.default,
},
textInputDisabled: {
color: colors.text.muted,
fontWeight: 'bold',
},
inputLabel: {
...fontStyles.normal,
color: colors.text.default,
Expand Down Expand Up @@ -82,6 +86,7 @@ export default class AddCustomToken extends PureComponent {
warningAddress: '',
warningSymbol: '',
warningDecimals: '',
isSymbolAndDecimalEditable: true,
};

static propTypes = {
Expand Down Expand Up @@ -155,8 +160,42 @@ export default class AddCustomToken extends PureComponent {
this.props.navigation.goBack();
};

onAddressChange = (address) => {
onAddressChange = async (address) => {
this.setState({ address });
if (address.length === 42) {
try {
this.setState({ isSymbolAndDecimalEditable: false });
const validated = await this.validateCustomTokenAddress(address);
if (validated) {
const { AssetsContractController } = Engine.context;
const [decimals, symbol, name] = await Promise.all([
AssetsContractController.getERC20TokenDecimals(address),
AssetsContractController.getERC721AssetSymbol(address),
AssetsContractController.getERC20TokenName(address),
]);

this.setState({
decimals: String(decimals),
symbol,
name,
});
} else {
this.setState({ isSymbolAndDecimalEditable: true });
}
} catch (e) {
this.setState({ isSymbolAndDecimalEditable: true });
}
} else {
// We are cleaning other fields when changing the token address
this.setState({
decimals: '',
symbol: '',
name: '',
warningAddress: '',
warningSymbol: '',
warningDecimals: '',
});
}
};

onSymbolChange = (symbol) => {
Expand All @@ -167,36 +206,25 @@ export default class AddCustomToken extends PureComponent {
this.setState({ decimals });
};

onAddressBlur = async () => {
const validated = await this.validateCustomTokenAddress();
if (validated) {
const address = this.state.address;
const { AssetsContractController } = Engine.context;
const decimals = await AssetsContractController.getERC20TokenDecimals(
address,
);
const symbol = await AssetsContractController.getERC721AssetSymbol(
address,
);
const name = await AssetsContractController.getERC20TokenName(address);

this.setState({ decimals: String(decimals), symbol, name });
}
};

validateCustomTokenAddress = async () => {
validateCustomTokenAddress = async (address) => {
let validated = true;
const address = this.state.address;
const isValidTokenAddress = isValidAddress(address);

const { chainId } = this.props;
const toSmartContract =
isValidTokenAddress && (await isSmartContractAddress(address, chainId));

const addressWithoutSpaces = address.replace(regex.addressWithSpaces, '');

if (addressWithoutSpaces.length === 0) {
this.setState({ warningAddress: strings('token.address_cant_be_empty') });
this.setState({
warningAddress: strings('token.address_cant_be_empty'),
});
validated = false;
} else if (!isValidTokenAddress) {
this.setState({ warningAddress: strings('token.address_must_be_valid') });
this.setState({
warningAddress: strings('token.address_must_be_valid'),
});
validated = false;
} else if (!toSmartContract) {
this.setState({
Expand Down Expand Up @@ -238,7 +266,9 @@ export default class AddCustomToken extends PureComponent {
};

validateCustomToken = async () => {
const validatedAddress = await this.validateCustomTokenAddress();
const validatedAddress = await this.validateCustomTokenAddress(
this.state.address,
);
const validatedSymbol = this.validateCustomTokenSymbol();
const validatedDecimals = this.validateCustomTokenDecimals();
return validatedAddress && validatedSymbol && validatedDecimals;
Expand Down Expand Up @@ -340,11 +370,16 @@ export default class AddCustomToken extends PureComponent {
: this.renderInfoBanner();

render = () => {
const { address, symbol, decimals } = this.state;
const { address, symbol, decimals, isSymbolAndDecimalEditable } =
this.state;
const colors = this.context.colors || mockTheme.colors;
const themeAppearance = this.context.themeAppearance || 'light';
const styles = createStyles(colors);

const textInputSymbolAndDecimalsStyle = !isSymbolAndDecimalEditable
? { ...styles.textInput, ...styles.textInputDisabled }
: styles.textInput;

return (
<View
style={styles.wrapper}
Expand Down Expand Up @@ -372,7 +407,6 @@ export default class AddCustomToken extends PureComponent {
placeholderTextColor={colors.text.muted}
value={this.state.address}
onChangeText={this.onAddressChange}
onBlur={this.onAddressBlur}
{...generateTestId(Platform, TOKEN_ADDRESS_INPUT_BOX_ID)}
onSubmitEditing={this.jumpToAssetSymbol}
returnKeyType={'next'}
Expand All @@ -390,7 +424,7 @@ export default class AddCustomToken extends PureComponent {
{strings('token.token_symbol')}
</Text>
<TextInput
style={styles.textInput}
style={textInputSymbolAndDecimalsStyle}
placeholder={'GNO'}
placeholderTextColor={colors.text.muted}
value={this.state.symbol}
Expand All @@ -401,6 +435,7 @@ export default class AddCustomToken extends PureComponent {
onSubmitEditing={this.jumpToAssetPrecision}
returnKeyType={'next'}
keyboardAppearance={themeAppearance}
editable={isSymbolAndDecimalEditable}
/>
<Text style={styles.warningText}>{this.state.warningSymbol}</Text>
</View>
Expand All @@ -409,7 +444,7 @@ export default class AddCustomToken extends PureComponent {
{strings('token.token_decimal')}
</Text>
<TextInput
style={styles.textInput}
style={textInputSymbolAndDecimalsStyle}
value={this.state.decimals}
keyboardType="numeric"
maxLength={2}
Expand All @@ -422,6 +457,7 @@ export default class AddCustomToken extends PureComponent {
onSubmitEditing={this.addToken}
returnKeyType={'done'}
keyboardAppearance={themeAppearance}
editable={isSymbolAndDecimalEditable}
/>
<Text
style={styles.warningText}
Expand Down
1 change: 1 addition & 0 deletions app/constants/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export const VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP =
// RPCMethodMiddleware
export const TOKEN_NOT_SUPPORTED_FOR_NETWORK =
'This token is not supported on this network';
export const TOKEN_NOT_VALID = 'This token address os mpt valid';
35 changes: 2 additions & 33 deletions app/core/RPCMethods/RPCMethodMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ import setOnboardingWizardStep from '../../actions/wizard';
import { v1 as random } from 'uuid';
import { getPermittedAccounts } from '../Permissions';
import AppConstants from '../AppConstants';
import { isSmartContractAddress } from '../../util/transactions';
import { TOKEN_NOT_SUPPORTED_FOR_NETWORK } from '../../constants/error';
import PPOMUtil from '../../lib/ppom/ppom-util';
import {
selectChainId,
selectProviderConfig,
selectProviderType,
} from '../../selectors/networkController';
Expand Down Expand Up @@ -733,36 +730,8 @@ export const getRpcMethodMiddleware = ({
});
}),

wallet_watchAsset: async () => {
const {
params: {
options: { address, decimals, image, symbol },
type,
},
} = req;
const { TokensController } = Engine.context;
const chainId = selectChainId(store.getState());

checkTabActive();

// Check if token exists on wallet's active network.
const isTokenOnNetwork = await isSmartContractAddress(address, chainId);
if (!isTokenOnNetwork) {
throw new Error(TOKEN_NOT_SUPPORTED_FOR_NETWORK);
}
const permittedAccounts = await getPermittedAccounts(hostname);
// This should return the current active account on the Dapp.
const selectedAddress =
Engine.context.PreferencesController.state.selectedAddress;
// Fallback to wallet address if there is no connected account to Dapp.
const interactingAddress = permittedAccounts?.[0] || selectedAddress;
await TokensController.watchAsset(
{ address, symbol, decimals, image },
type,
safeToChecksumAddress(interactingAddress),
);
res.result = true;
},
wallet_watchAsset: async () =>
RPCMethods.wallet_watchAsset({ req, res, hostname, checkTabActive }),

metamask_removeFavorite: async () => {
checkTabActive();
Expand Down
2 changes: 2 additions & 0 deletions app/core/RPCMethods/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import eth_sendTransaction from './eth_sendTransaction';
import wallet_addEthereumChain from './wallet_addEthereumChain.js';
import wallet_switchEthereumChain from './wallet_switchEthereumChain.js';
import wallet_watchAsset from './wallet_watchAsset.ts';

const RPCMethods = {
eth_sendTransaction,
wallet_addEthereumChain,
wallet_switchEthereumChain,
wallet_watchAsset,
};

export default RPCMethods;
Loading

0 comments on commit 8fd310c

Please sign in to comment.