Skip to content
Open
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
41 changes: 41 additions & 0 deletions ui/helpers/utils/multichain/blockExplorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MultichainNetwork } from '../../../selectors/multichain';
import {
getMultichainBlockExplorerUrl,
getMultichainAccountUrl,
getAssetDetailsAccountUrl,
} from './blockExplorer';

const mockEvmNetwork: MultichainNetwork = {
Expand Down Expand Up @@ -79,4 +80,44 @@ describe('Block Explorer Tests', () => {
expect(result).toBe(expectedUrl);
});
});

describe('getAssetDetailsAccountUrl', () => {
it('returns the correct account URL using configured block explorer for EVM networks', () => {
const address = '0x1234567890abcdef';
// Uses the network's configured block explorer URL
const expectedUrl = `https://etherscan.io/address/${address}`;

const result = getAssetDetailsAccountUrl(address, mockEvmNetwork);

expect(result).toBe(expectedUrl);
});

it('falls back to Etherscan multichain when no block explorer is configured', () => {
const address = '0x1234567890abcdef';
const networkWithoutExplorer: MultichainNetwork = {
...mockEvmNetwork,
network: {
...mockEvmNetwork.network,
rpcPrefs: {
imageUrl: ETH_TOKEN_IMAGE_URL,
// No blockExplorerUrl configured
},
},
};
const expectedUrl = `https://etherscan.io/address/${address}#asset-multichain`;

const result = getAssetDetailsAccountUrl(address, networkWithoutExplorer);

expect(result).toBe(expectedUrl);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test expectation mismatch for missing block explorer fallback

The test at line 107 expects getAssetDetailsAccountUrl to return a URL ending with #asset-multichain when no blockExplorerUrl is configured, but the implementation simply calls getAccountLink from @metamask/etherscan-link which won't append this fragment. Either the test expectation is incorrect, or the implementation needs to detect when no block explorer is configured and add the #asset-multichain suffix to match the expected fallback behavior documented in the test name "falls back to Etherscan multichain".

Additional Locations (1)

Fix in Cursor Fix in Web


it('returns the correct account URL for non-EVM networks', () => {
const address = 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq';
const expectedUrl = `https://mempool.space/address/${address}`;

const result = getAssetDetailsAccountUrl(address, mockNonEvmNetwork);

expect(result).toBe(expectedUrl);
});
});
});
23 changes: 23 additions & 0 deletions ui/helpers/utils/multichain/blockExplorer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { KnownCaipNamespace, parseCaipChainId } from '@metamask/utils';
import { getAccountLink } from '@metamask/etherscan-link';
import { MultichainNetwork } from '../../../selectors/multichain';
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
Expand Down Expand Up @@ -31,3 +32,25 @@ export const getMultichainAccountUrl = (

return '';
};

export const getAssetDetailsAccountUrl = (
address: string,
network: MultichainNetwork,
): string => {
const { namespace } = parseCaipChainId(network.chainId);
if (namespace === KnownCaipNamespace.Eip155) {
return getAccountLink(
normalizeSafeAddress(address),
network.network.chainId,
network.network?.rpcPrefs,
);
}

const { blockExplorerFormatUrls } =
network.network as MultichainProviderConfig;
if (blockExplorerFormatUrls) {
return formatBlockExplorerAddressUrl(blockExplorerFormatUrls, address);
}

return '';
};
4 changes: 2 additions & 2 deletions ui/pages/asset/components/token-asset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { getTokenList, selectERC20TokensByChain } from '../../../selectors';
import { showModal } from '../../../store/actions';
import { getMultichainAccountUrl } from '../../../helpers/utils/multichain/blockExplorer';
import { getAssetDetailsAccountUrl } from '../../../helpers/utils/multichain/blockExplorer';
import { useMultichainSelector } from '../../../hooks/useMultichainSelector';
import { getMultichainNetwork } from '../../../selectors/multichain';
import { getInternalAccountBySelectedAccountGroupAndCaip } from '../../../selectors/multichain-accounts/account-tree';
Expand Down Expand Up @@ -112,7 +112,7 @@ const TokenAsset = ({ token, chainId }: { token: Token; chainId: Hex }) => {

const blockExplorerLink = isEvm
? tokenTrackerLink
: getMultichainAccountUrl(
: getAssetDetailsAccountUrl(
parseCaipAssetType(address as CaipAssetType).assetReference,
multichainNetwork,
);
Expand Down
Loading