Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer';
import mockState from '../../../../../../../test/data/mock-state.json';
import { renderHookWithProvider } from '../../../../../../../test/lib/render-helpers';
import { getTokenList } from '../../../../../../selectors';
import { selectERC20TokensByChain } from '../../../../../../selectors';
import { useTokenDetails } from './useTokenDetails';

jest.mock('react-redux', () => ({
Expand All @@ -14,15 +14,19 @@ jest.mock('react-redux', () => ({
const ICON_SYMBOL = 'FROG';
const ICON_URL =
'https://static.cx.metamask.io/api/v1/tokenIcons/1/0x0a2c375553e6965b42c135bb8b15a8914b08de0c.png';
const MOCK_TOKEN_LIST = (transactionMeta: TransactionMeta) => ({
[transactionMeta.txParams.to as string]: {
address: transactionMeta.txParams.to,
aggregators: ['CoinGecko', 'Socket', 'Coinmarketcap'],
decimals: 9,
iconUrl: ICON_URL,
name: 'Frog on ETH',
occurrences: 3,
symbol: ICON_SYMBOL,
const MOCK_TOKEN_LIST_BY_CHAIN = (transactionMeta: TransactionMeta) => ({
[transactionMeta.chainId]: {
data: {
[(transactionMeta.txParams.to as string).toLowerCase()]: {
address: transactionMeta.txParams.to,
aggregators: ['CoinGecko', 'Socket', 'Coinmarketcap'],
decimals: 9,
iconUrl: ICON_URL,
name: 'Frog on ETH',
occurrences: 3,
symbol: ICON_SYMBOL,
},
},
},
});

Expand All @@ -33,56 +37,15 @@ describe('useTokenDetails', () => {
jest.resetAllMocks();
});

it('returns token details from selected token if it exists', () => {
const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
symbol: 'symbol',
iconUrl: 'iconUrl',
image: 'image',
};

useSelectorMock.mockImplementation((selector) => {
if (selector === getTokenList) {
return MOCK_TOKEN_LIST(transactionMeta);
} else if (selector?.toString().includes('getWatchedToken')) {
return TEST_SELECTED_TOKEN;
}
return undefined;
});

const { result } = renderHookWithProvider(
() => useTokenDetails(transactionMeta),
mockState,
);

expect(result.current).toEqual({
tokenImage: 'iconUrl',
tokenSymbol: 'symbol',
});
});

it('returns token details from the token list if it exists', () => {
const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
};

useSelectorMock.mockImplementation((selector) => {
if (selector === getTokenList) {
return MOCK_TOKEN_LIST(transactionMeta);
} else if (selector?.toString().includes('getWatchedToken')) {
return TEST_SELECTED_TOKEN;
if (selector === selectERC20TokensByChain) {
return MOCK_TOKEN_LIST_BY_CHAIN(transactionMeta);
}

return undefined;
});

Expand All @@ -97,23 +60,14 @@ describe('useTokenDetails', () => {
});
});

it('returns selected token image if no iconUrl is included', () => {
it('returns undefined for tokenImage and "Unknown" for tokenSymbol if token is not found', () => {
const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
symbol: 'symbol',
image: 'image',
};

useSelectorMock.mockImplementation((selector) => {
if (selector === getTokenList) {
return MOCK_TOKEN_LIST(transactionMeta);
} else if (selector?.toString().includes('getWatchedToken')) {
return TEST_SELECTED_TOKEN;
if (selector === selectERC20TokensByChain) {
return {};
}
return undefined;
});
Expand All @@ -124,27 +78,28 @@ describe('useTokenDetails', () => {
);

expect(result.current).toEqual({
tokenImage: 'image',
tokenSymbol: 'symbol',
tokenImage: undefined,
tokenSymbol: 'Unknown',
});
});

it('returns token list icon url if no image is included in the token', () => {
it('returns undefined for tokenImage and "Unknown" for tokenSymbol if chainId is not in token list', () => {
const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
symbol: 'symbol',
};

useSelectorMock.mockImplementation((selector) => {
if (selector === getTokenList) {
return MOCK_TOKEN_LIST(transactionMeta);
} else if (selector?.toString().includes('getWatchedToken')) {
return TEST_SELECTED_TOKEN;
if (selector === selectERC20TokensByChain) {
return {
'0x999': {
data: {
'0xsomeotheraddress': {
iconUrl: ICON_URL,
symbol: ICON_SYMBOL,
},
},
},
};
}
return undefined;
});
Expand All @@ -155,27 +110,28 @@ describe('useTokenDetails', () => {
);

expect(result.current).toEqual({
tokenImage: ICON_URL,
tokenSymbol: 'symbol',
tokenImage: undefined,
tokenSymbol: 'Unknown',
});
});

it('returns undefined if no image is found', () => {
it('returns undefined for tokenImage and "Unknown" for tokenSymbol if token address is not in chain data', () => {
const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
symbol: 'symbol',
};

useSelectorMock.mockImplementation((selector) => {
if (selector === getTokenList) {
return {};
} else if (selector?.toString().includes('getWatchedToken')) {
return TEST_SELECTED_TOKEN;
if (selector === selectERC20TokensByChain) {
return {
[transactionMeta.chainId]: {
data: {
'0xdifferentaddress': {
iconUrl: ICON_URL,
symbol: ICON_SYMBOL,
},
},
},
};
}
return undefined;
});
Expand All @@ -187,7 +143,7 @@ describe('useTokenDetails', () => {

expect(result.current).toEqual({
tokenImage: undefined,
tokenSymbol: 'symbol',
tokenSymbol: 'Unknown',
});
});
});
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import { TokenListMap } from '@metamask/assets-controllers';
import { TransactionMeta } from '@metamask/transaction-controller';
import { useSelector } from 'react-redux';
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { getTokenList, getWatchedToken } from '../../../../../../selectors';
import { MultichainState } from '../../../../../../selectors/multichain';
import {
getAllTokens,
selectERC20TokensByChain,
} from '../../../../../../selectors';
import { Token } from '../../../../../../components/app/assets/types';

export const useTokenDetails = (transactionMeta: TransactionMeta) => {
const t = useI18nContext();
const selectedToken = useSelector((state: MultichainState) =>
getWatchedToken(transactionMeta)(state),
const {
chainId,
txParams: { to, from },
} = transactionMeta ?? { txParams: {} };
const erc20TokensByChain = useSelector(selectERC20TokensByChain);
const allTokens = useSelector(getAllTokens);

const erc20Token =
erc20TokensByChain[chainId]?.data?.[to?.toLowerCase() as string];
const tokenListToken = allTokens?.[chainId]?.[from as string].find(
(token: Token) =>
token.address?.toLowerCase() === (to?.toLowerCase() as string),
);
const tokenList = useSelector(getTokenList) as TokenListMap;

const tokenImage =
selectedToken?.iconUrl ||
selectedToken?.image ||
tokenList[transactionMeta?.txParams?.to as string]?.iconUrl;

erc20Token?.iconUrl || tokenListToken?.iconUrl || undefined;
const tokenSymbol =
selectedToken?.symbol ||
tokenList[transactionMeta?.txParams?.to as string]?.symbol ||
t('unknown');
erc20Token?.symbol || tokenListToken?.symbol || t('unknown');

return { tokenImage, tokenSymbol };
};
Loading