Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions ui/ducks/metamask/metamask.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ export const getTokens = (state) => {
return allTokens?.[chainId]?.[selectedAddress] || [];
};

export const getTokensByChainId = (state, chainId) => {
const { allTokens } = state.metamask;
const { address: selectedAddress } = getSelectedInternalAccount(state);
return allTokens?.[chainId]?.[selectedAddress] || [];
};

export function getNftsDropdownState(state) {
return state.metamask.nftsDropdownState;
}
Expand Down
69 changes: 69 additions & 0 deletions ui/ducks/metamask/metamask.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import reduceMetamask, {
isNotEIP1559Network,
getCurrentCurrency,
getAllNfts,
getTokensByChainId,
} from './metamask';

jest.mock('@metamask/transaction-controller', () => ({
Expand Down Expand Up @@ -740,4 +741,72 @@ describe('MetaMask Reducers', () => {
});
});
});

describe('getTokensByChainId', () => {
const state = {
metamask: {
allTokens: {
'0x1': {
'0x123': [
{ symbol: 'ETH', address: '0xabc' },
{ symbol: 'DAI', address: '0xdef' },
],
},
'0x2': {
'0x456': [{ symbol: 'USDC', address: '0xghi' }],
},
},
internalAccounts: {
selectedAccount: 'account1',
accounts: {
account1: {
address: '0x123',
},
account2: {
address: '0x456',
},
},
},
},
};

it('returns tokens for the selected account and chain ID', () => {
const tokens = getTokensByChainId(state, '0x1');
expect(tokens).toStrictEqual([
{ symbol: 'ETH', address: '0xabc' },
{ symbol: 'DAI', address: '0xdef' },
]);
});

it('returns an empty array if no tokens exist for the chain ID', () => {
const tokens = getTokensByChainId(state, '0x3');
expect(tokens).toStrictEqual([]);
});

it('returns an empty array if no tokens exist for the selected account', () => {
const stateWithNoTokens = {
...state,
metamask: {
...state.metamask,
allTokens: {
'0x1': {},
},
},
};
const tokens = getTokensByChainId(stateWithNoTokens, '0x1');
expect(tokens).toStrictEqual([]);
});

it('returns an empty array if allTokens is undefined', () => {
const stateWithNoTokens = {
...state,
metamask: {
...state.metamask,
allTokens: undefined,
},
};
const tokens = getTokensByChainId(stateWithNoTokens, '0x1');
expect(tokens).toStrictEqual([]);
});
});
});
23 changes: 17 additions & 6 deletions ui/helpers/utils/token-util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import log from 'loglevel';
import { getTokenStandardAndDetails } from '../../store/actions';
import {
getTokenStandardAndDetails,
getTokenStandardAndDetailsByChain,
} from '../../store/actions';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
import { TokenStandard } from '../../../shared/constants/transaction';
Expand Down Expand Up @@ -261,6 +264,7 @@ export async function getAssetDetails(
currentUserAddress,
transactionData,
existingNfts,
chainId,
) {
const tokenData = parseStandardTokenTransactionData(transactionData);
if (!tokenData) {
Expand Down Expand Up @@ -296,11 +300,18 @@ export async function getAssetDetails(
}

try {
tokenDetails = await getTokenStandardAndDetails(
tokenAddress,
currentUserAddress,
tokenId,
);
tokenDetails = chainId
? await getTokenStandardAndDetailsByChain(
tokenAddress,
currentUserAddress,
tokenId,
chainId,
)
: await getTokenStandardAndDetails(
tokenAddress,
currentUserAddress,
tokenId,
);
} catch (error) {
log.warn(error);
// if we can't determine any token standard or details return the data we can extract purely from the parsed transaction data
Expand Down
173 changes: 95 additions & 78 deletions ui/helpers/utils/token-util.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { TokenStandard } from '../../../shared/constants/transaction';
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
import { getTokenStandardAndDetails } from '../../store/actions';
import {
getTokenStandardAndDetails,
getTokenStandardAndDetailsByChain,
} from '../../store/actions';
import { getAssetDetails } from './token-util';

jest.mock('../../../shared/modules/transaction.utils', () => ({
Expand All @@ -9,6 +12,7 @@ jest.mock('../../../shared/modules/transaction.utils', () => ({

jest.mock('../../store/actions', () => ({
getTokenStandardAndDetails: jest.fn(),
getTokenStandardAndDetailsByChain: jest.fn(),
}));

describe('getAssetDetails', () => {
Expand All @@ -20,53 +24,106 @@ describe('getAssetDetails', () => {
jest.clearAllMocks();
});

it('should return asset details for an erc721 token transaction', async () => {
const erc721Params = {
it('calls getTokenStandardAndDetailsByChain when chainId is passed', async () => {
const paramsWithChainId = {
tokenAddress: '0xAddrEssToken',
currentUserAddress: '0xAddrEss',
currentUserAddress: '0xAccouNtAddress',
transactionData: '0xTransactionData',
existingNfts: [
{
address: '0xAddrEss',
name: null,
standard: 'ERC721',
tokenId: '1',
tokenURI: 'tokenURI',
},
],
chainId: '0x1',
};

parseStandardTokenTransactionData.mockReturnValue({
args: { id: 1, to: '0xtoAddRess' },
args: { to: '0xtoAddRess' },
});
getTokenStandardAndDetails.mockReturnValue({
name: 'myToken',

getTokenStandardAndDetailsByChain.mockResolvedValue({
name: 'myERC20Token',
symbol: 'MTK',
standard: TokenStandard.ERC721,
standard: TokenStandard.ERC20,
});

const result = await getAssetDetails(
erc721Params.currentUserAddress,
erc721Params.tokenAddress,
erc721Params.transactionData,
erc721Params.existingNfts,
paramsWithChainId.tokenAddress,
paramsWithChainId.currentUserAddress,
paramsWithChainId.transactionData,
[],
paramsWithChainId.chainId,
);

// should be called if name is null
expect(getTokenStandardAndDetails).toHaveBeenCalled();
expect(result.name).toStrictEqual('myToken');
// Verify that getTokenStandardAndDetailsByChain is called with the correct arguments
expect(getTokenStandardAndDetailsByChain).toHaveBeenCalledWith(
paramsWithChainId.tokenAddress,
paramsWithChainId.currentUserAddress,
undefined, // tokenId is undefined in this case
paramsWithChainId.chainId,
);

// Verify the result
expect(result.name).toStrictEqual('myERC20Token');
expect(result.symbol).toStrictEqual('MTK');
expect(result.standard).toStrictEqual(TokenStandard.ERC721);
expect(result.standard).toStrictEqual(TokenStandard.ERC20);
});

it('should return asset details for an erc721 token transaction without calling api if name is not null', async () => {
const erc721ParamsWithName = {
it('calls getTokenStandardAndDetails when chainId is not passed', async () => {
const paramsWithoutChainId = {
tokenAddress: '0xAddrEssToken',
currentUserAddress: '0xAccouNtAddress',
transactionData: '0xTransactionData',
};

parseStandardTokenTransactionData.mockReturnValue({
args: { to: '0xtoAddRess' },
});

getTokenStandardAndDetails.mockResolvedValue({
name: 'myERC20Token',
symbol: 'MTK',
standard: TokenStandard.ERC20,
});

const result = await getAssetDetails(
paramsWithoutChainId.tokenAddress,
paramsWithoutChainId.currentUserAddress,
paramsWithoutChainId.transactionData,
[],
);

// Verify that getTokenStandardAndDetails is called with the correct arguments
expect(getTokenStandardAndDetails).toHaveBeenCalledWith(
paramsWithoutChainId.tokenAddress,
paramsWithoutChainId.currentUserAddress,
undefined, // tokenId is undefined in this case
);

// Verify the result
expect(result.name).toStrictEqual('myERC20Token');
expect(result.symbol).toStrictEqual('MTK');
expect(result.standard).toStrictEqual(TokenStandard.ERC20);
});

it('throws an error with the token address if token data cannot be parsed', async () => {
const tokenAddress = '0xAddrEssToken';
const currentUserAddress = '0xAccouNtAddress';
const transactionData = '0xTransactionData';

parseStandardTokenTransactionData.mockReturnValue(undefined);

await expect(
getAssetDetails(tokenAddress, currentUserAddress, transactionData, []),
).rejects.toThrow(
`Unable to detect valid token data for token: 0xAddrEssToken`,
);
});

it('returns asset details for an erc721 token transaction', async () => {
const erc721Params = {
tokenAddress: '0xAddrEssToken',
currentUserAddress: '0xAddrEss',
transactionData: '0xTransactionData',
existingNfts: [
{
address: '0xAddrEss',
name: 'myToken',
symbol: 'MTK',
name: null,
standard: 'ERC721',
tokenId: '1',
tokenURI: 'tokenURI',
Expand All @@ -82,28 +139,28 @@ describe('getAssetDetails', () => {
standard: TokenStandard.ERC721,
});
const result = await getAssetDetails(
erc721ParamsWithName.currentUserAddress,
erc721ParamsWithName.tokenAddress,
erc721ParamsWithName.transactionData,
erc721ParamsWithName.existingNfts,
erc721Params.currentUserAddress,
erc721Params.tokenAddress,
erc721Params.transactionData,
erc721Params.existingNfts,
);

// should not be called if name is not null
expect(getTokenStandardAndDetails).not.toHaveBeenCalled();
// should be called if name is null
expect(getTokenStandardAndDetails).toHaveBeenCalled();
expect(result.name).toStrictEqual('myToken');
expect(result.symbol).toStrictEqual('MTK');
expect(result.standard).toStrictEqual(TokenStandard.ERC721);
});

it('should return the correct asset details for an erc721 token transaction without calling api if symbol is not null', async () => {
it('returns asset details for an erc721 token transaction without calling api if name is not null', async () => {
const erc721ParamsWithName = {
tokenAddress: '0xAddrEssToken',
currentUserAddress: '0xAddrEss',
transactionData: '0xTransactionData',
existingNfts: [
{
address: '0xAddrEss',
name: null,
name: 'myToken',
symbol: 'MTK',
standard: 'ERC721',
tokenId: '1',
Expand All @@ -128,48 +185,8 @@ describe('getAssetDetails', () => {

// should not be called if name is not null
expect(getTokenStandardAndDetails).not.toHaveBeenCalled();
expect(result.name).toStrictEqual(null);
expect(result.name).toStrictEqual('myToken');
expect(result.symbol).toStrictEqual('MTK');
expect(result.standard).toStrictEqual(TokenStandard.ERC721);
});

it('should return the correct asset details for an erc20 token transaction', async () => {
const erc20Params = {
tokenAddress: '0xAddrEssToken',
currentUserAddress: '0xAccouNtAddress',
transactionData: '0xTransactionData',
};
parseStandardTokenTransactionData.mockReturnValue({
args: { to: '0xtoAddRess' },
});
getTokenStandardAndDetails.mockReturnValue({
name: 'myERC20Token',
symbol: 'MTK',
standard: TokenStandard.ERC20,
});
const result = await getAssetDetails(
erc20Params.currentUserAddress,
erc20Params.tokenAddress,
erc20Params.transactionData,
erc20Params.existingNfts,
);

expect(getTokenStandardAndDetails).toHaveBeenCalled();
expect(result.name).toStrictEqual('myERC20Token');
expect(result.symbol).toStrictEqual('MTK');
});

it('throws an error with the token address if token data cannot be parsed', async () => {
const tokenAddress = '0xAddrEssToken';
const currentUserAddress = '0xAccouNtAddress';
const transactionData = '0xTransactionData';

parseStandardTokenTransactionData.mockReturnValue(undefined);

await expect(
getAssetDetails(tokenAddress, currentUserAddress, transactionData, []),
).rejects.toThrow(
`Unable to detect valid token data for token: 0xAddrEssToken`,
);
});
});
Loading
Loading