Skip to content

Commit

Permalink
fix: fix token autodetection
Browse files Browse the repository at this point in the history
  • Loading branch information
salimtb committed Nov 15, 2024
1 parent 3a4cc62 commit 31326df
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const TokenListFooter = ({
const allDetectedTokens = useSelector(
selectAllDetectedTokensFlat,
) as TokenI[];

const isTokenDetectionEnabled = useSelector(selectUseTokenDetection);
const chainId = useSelector(selectChainId);
// TODO: Can probably create "isAllNetworks" selector for these
Expand Down
14 changes: 13 additions & 1 deletion app/components/UI/Tokens/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,21 @@ const Tokens: React.FC<TokensI> = ({ tokens }) => {
AccountTrackerController,
CurrencyRateController,
TokenRatesController,
NetworkController,
AccountsController,
} = Engine.context;

const networkConfigurations =
NetworkController.state.networkConfigurationsByChainId;
const chainIds = Object.keys(networkConfigurations);
const selectedAddress =
AccountsController.state.internalAccounts.selectedAccount;

const actions = [
TokenDetectionController.detectTokens(),
TokenDetectionController.detectTokens({
chainIds: [chainIds] as Hex[],

Check failure on line 235 in app/components/UI/Tokens/index.tsx

View workflow job for this annotation

GitHub Actions / scripts (lint:tsc)

Cannot find name 'Hex'.
selectedAddress,
}),
AccountTrackerController.refresh(),
CurrencyRateController.updateExchangeRate(nativeCurrencies),
TokenRatesController.updateExchangeRates(),
Expand Down
62 changes: 50 additions & 12 deletions app/components/Views/DetectedTokens/components/Token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ import {
} from '../../../../util/number';
import { useTheme } from '../../../../util/theme';
import {
selectConversionRate,
selectConversionRateFoAllChains,
selectCurrentCurrency,
} from '../../../../selectors/currencyRateController';
import { selectContractExchangeRates } from '../../../../selectors/tokenRatesController';
import { selectContractBalances } from '../../../../selectors/tokenBalancesController';
import { selectTokenMarketData } from '../../../../selectors/tokenRatesController';
import { selectTokensBalances } from '../../../../selectors/tokenBalancesController';
import { Colors } from '../../../../util/theme/models';
import { Hex } from '@metamask/utils';
import BadgeWrapper from '../../../../component-library/components/Badges/BadgeWrapper';
import Badge, {
BadgeVariant,
} from '../../../../component-library/components/Badges/Badge';
import { NetworkBadgeSource } from '../../../UI/AssetOverview/Balance/Balance';
import { CURRENCY_SYMBOL_BY_CHAIN_ID } from '../../../../constants/network';
import { selectSelectedInternalAccountAddress } from '../../../../selectors/accountsController';

// Replace this interface by importing from TokenRatesController when it exports it
interface MarketDataDetails {
Expand Down Expand Up @@ -109,19 +116,29 @@ const createStyles = (colors: Colors) =>
});

interface Props {
token: TokenType;
token: TokenType & { chainId: Hex };
selected: boolean;
toggleSelected: (selected: boolean) => void;
}

const Token = ({ token, selected, toggleSelected }: Props) => {
const { address, symbol, aggregators = [], decimals } = token;
const accountAddress = useSelector(selectSelectedInternalAccountAddress);
const { colors } = useTheme();
const styles = createStyles(colors);
const [expandTokenList, setExpandTokenList] = useState(false);
const tokenExchangeRates = useSelector(selectContractExchangeRates);
const tokenBalances = useSelector(selectContractBalances);
const conversionRate = useSelector(selectConversionRate);
const tokenExchangeRatesAllChains = useSelector(selectTokenMarketData);
const tokenExchangeRates = tokenExchangeRatesAllChains[token.chainId];
const tokenBalancesAllChains = useSelector(selectTokensBalances);
const balanceAllChainsForAccount =
tokenBalancesAllChains[accountAddress as Hex];
const tokenBalances = balanceAllChainsForAccount[token.chainId as Hex];
const conversionRateByChainId = useSelector(selectConversionRateFoAllChains);

const conversionRate =
conversionRateByChainId[CURRENCY_SYMBOL_BY_CHAIN_ID[token.chainId]]
?.conversionRate;

const currentCurrency = useSelector(selectCurrentCurrency);
const tokenMarketData =
(tokenExchangeRates as Record<Hex, MarketDataDetails>)?.[address as Hex] ??
Expand Down Expand Up @@ -168,11 +185,32 @@ const Token = ({ token, selected, toggleSelected }: Props) => {

return (
<View style={styles.tokenContainer}>
<TokenImage
asset={token}
containerStyle={styles.logo}
iconStyle={styles.logo}
/>
{process.env.PORTFOLIO_VIEW === 'true' ? (
<BadgeWrapper
badgeElement={
<Badge
variant={BadgeVariant.Network}
imageSource={NetworkBadgeSource(
token.chainId,
CURRENCY_SYMBOL_BY_CHAIN_ID[token.chainId],
)}
/>
}
>
<TokenImage
asset={token}
containerStyle={styles.logo}
iconStyle={styles.logo}
/>
</BadgeWrapper>
) : (
<TokenImage
asset={token}
containerStyle={styles.logo}
iconStyle={styles.logo}
/>
)}

<View style={styles.tokenInfoContainer}>
<Text style={styles.tokenUnitLabel}>{tokenBalanceWithSymbol}</Text>
{fiatBalance ? (
Expand Down
16 changes: 16 additions & 0 deletions app/components/Views/DetectedTokens/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ import { DetectedTokensSelectorIDs } from '../../../../e2e/selectors/wallet/Dete
import { TokenI } from '../../UI/Tokens/types';
import { selectTokenNetworkFilter } from '../../../selectors/preferencesController';
import { organizeTokensByChainId } from '../../UI/Tokens/util/organizeTokensByChainId';
import Badge, {
BadgeVariant,
} from '../../../component-library/components/Badges/Badge';
import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper';
import { NetworkBadgeSource } from '../../UI/AssetOverview/Balance/Balance';

// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -256,6 +261,16 @@ const DetectedTokens = () => {
const isChecked = !ignoredTokens[address];

return (
// <BadgeWrapper
// // style={styles.badgeWrapper}
// badgeElement={
// <Badge
// variant={BadgeVariant.Network}
// imageSource={NetworkBadgeSource(chainId, item.symbol)}
// // name={networkName}
// />
// }
// >
<Token
token={item}

Check failure on line 275 in app/components/Views/DetectedTokens/index.tsx

View workflow job for this annotation

GitHub Actions / scripts (lint:tsc)

Type 'Token' is not assignable to type 'Token & { chainId: `0x${string}`; }'.
selected={isChecked}
Expand All @@ -269,6 +284,7 @@ const DetectedTokens = () => {
setIgnoredTokens(newIgnoredTokens);
}}
/>
// </BadgeWrapper>
);
};

Expand Down
2 changes: 2 additions & 0 deletions app/components/hooks/AssetPolling/AssetPollingProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import useCurrencyRatePolling from './useCurrencyRatePolling';
import useTokenRatesPolling from './useTokenRatesPolling';
import useTokenBalancesPolling from './useTokenBalancesPolling';
import useTokenListPolling from './useTokenListPolling';
import useTokenDetectionPolling from './useTokenAutoDetectionPolling';

// This provider is a step towards making controller polling fully UI based.
// Eventually, individual UI components will call the use*Polling hooks to
Expand All @@ -12,6 +13,7 @@ export const AssetPollingProvider = ({ children }: { children: ReactNode }) => {
useTokenRatesPolling();
useTokenListPolling();
useTokenBalancesPolling();
useTokenDetectionPolling();

return <>{children}</>;
};
49 changes: 49 additions & 0 deletions app/components/hooks/AssetPolling/useTokenAutoDetectionPolling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useSelector } from 'react-redux';
import { selectNetworkConfigurations } from '../../../selectors/networkController';
import { selectUseTokenDetection } from '../../../selectors/preferencesController';
import { selectAllDetectedTokensForSelectedAddress } from '../../../selectors/tokensController';
import usePolling from '../usePolling';
import Engine from '../../../core/Engine';
import { selectSelectedInternalAccountAddress } from '../../../selectors/accountsController';
import { Hex } from '@metamask/utils';

type TokenDetectionPollingInput = {
chainIds: Hex[];
address: string;
};

const useTokenDetectionPolling = () => {
const networkConfigurations = useSelector(selectNetworkConfigurations);
const selectedAddress = useSelector(selectSelectedInternalAccountAddress);

const useTokenDetection = useSelector(selectUseTokenDetection);
const chainIds = Object.keys(networkConfigurations);

const detectedTokens = useSelector(selectAllDetectedTokensForSelectedAddress);

const { TokenDetectionController } = Engine.context;

const pollingInput: TokenDetectionPollingInput[] = [
{
chainIds: chainIds as Hex[],
address: selectedAddress ?? '',
},
];

usePolling({
startPolling: TokenDetectionController.startPolling.bind(
TokenDetectionController,
),
stopPollingByPollingToken:
TokenDetectionController.stopPollingByPollingToken.bind(
TokenDetectionController,
),
input: useTokenDetection ? pollingInput : [{ chainIds: [], address: '' }],
});

return {
detectedTokens,
};
};

export default useTokenDetectionPolling;
2 changes: 1 addition & 1 deletion app/core/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,7 @@ export class Engine {
const tokenBalances =
contractBalances?.[selectedInternalAccount?.address as Hex]?.[
chainId
];
] ?? {};

tokens.forEach(
(item: { address: string; balance?: string; decimals: number }) => {
Expand Down
6 changes: 6 additions & 0 deletions app/selectors/currencyRateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export const selectConversionRate = createSelector(
},
);

export const selectConversionRateFoAllChains = createSelector(
selectCurrencyRateControllerState,
(currencyRateControllerState: CurrencyRateState) =>
currencyRateControllerState?.currencyRates,
);

export const selectCurrentCurrency = createSelector(
selectCurrencyRateControllerState,
selectTicker,
Expand Down
29 changes: 19 additions & 10 deletions app/selectors/tokensController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ export const selectAllTokens = createSelector(
export const selectAllDetectedTokensForSelectedAddress = createSelector(

Check failure on line 48 in app/selectors/tokensController.ts

View workflow job for this annotation

GitHub Actions / scripts (lint:tsc)

No overload matches this call.
selectTokensControllerState,
selectSelectedInternalAccountAddress,
(tokensControllerState, selectedAddress) => {
(
tokensControllerState: TokensControllerState,
selectedAddress: string | null,
) => {
// Updated return type to specify the structure more clearly
if (!selectedAddress) {
return {};
return {} as { [chainId: string]: Token[] }; // Specify return type
}

return Object.entries(tokensControllerState?.allDetectedTokens).reduce<{
return Object.entries(
tokensControllerState?.allDetectedTokens || {},
).reduce<{
[chainId: string]: Token[];
}>((acc, [chainId, chainTokens]) => {
const tokensForAddress = chainTokens[selectedAddress];
if (tokensForAddress) {
const tokensForAddress = chainTokens[selectedAddress] || [];
if (tokensForAddress.length > 0) {
acc[chainId] = tokensForAddress.map((token: Token) => ({
...token,
chainId,
Expand All @@ -73,7 +79,8 @@ export const selectAllDetectedTokensForSelectedAddress = createSelector(
// detected tokens if the user has chosen it in the past
export const selectAllDetectedTokensFlat = createSelector(
selectAllDetectedTokensForSelectedAddress,
(detectedTokensByChain) => {
(detectedTokensByChain: { [chainId: string]: Token[] }) => {
// Updated type here
if (Object.keys(detectedTokensByChain).length === 0) {
return [];
}
Expand All @@ -90,15 +97,17 @@ export const selectAllDetectedTokensFlat = createSelector(

export const selectAllTokensFlat = createSelector(
selectAllTokens,
(tokensByAccountByChain) => {
(tokensByAccountByChain: {
[account: string]: { [chainId: string]: Token[] };
}): Token[] => {
if (Object.values(tokensByAccountByChain).length === 0) {
return [];
}
const tokensByAccountArray = Object.values(tokensByAccountByChain);

return tokensByAccountArray.reduce((acc, tokensByAccount) => {
const tokensArray = Object.values(tokensByAccount);
return tokensByAccountArray.reduce<Token[]>((acc, tokensByAccount) => {
const tokensArray = Object.values(tokensByAccount).flat();
return acc.concat(...tokensArray);
}, [] as Token[]);
}, []);
},
);
3 changes: 3 additions & 0 deletions app/util/networks/customNetworks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,7 @@ export const CustomNetworkImgMapping: Record<Hex, string> = {
[NETWORK_CHAIN_ID.APE_CHAIN_TESTNET]: require('../../images/ape-network.png'),
[NETWORK_CHAIN_ID.APE_CHAIN_MAINNET]: require('../../images/ape-network.png'),
[NETWORK_CHAIN_ID.GRAVITY_ALPHA_MAINNET]: require('../../images/gravity.png'),
[NETWORK_CHAIN_ID.LINEA_MAINNET]: require('../../images/linea-mainnet-logo.png'),
};


0 comments on commit 31326df

Please sign in to comment.