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
7 changes: 3 additions & 4 deletions ui/ducks/domains.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export async function fetchResolutions({ domain, chainId, state }) {
return filteredResults;
}

export function lookupDomainName(domainName) {
export function lookupDomainName(domainName, chainId) {
return async (dispatch, getState) => {
const trimmedDomainName = domainName.trim();
let state = getState();
Expand All @@ -186,9 +186,8 @@ export function lookupDomainName(domainName) {
await dispatch(lookupStart(trimmedDomainName));
state = getState();
log.info(`Resolvers attempting to resolve name: ${trimmedDomainName}`);

const chainId = getCurrentChainId(state);
const chainIdInt = parseInt(chainId, 16);
const finalChainId = chainId || getCurrentChainId(state);
const chainIdInt = parseInt(finalChainId, 16);
Copy link

Choose a reason for hiding this comment

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

Bug: Bug

The lookupEnd action dispatches with the initial chainId parameter instead of the finalChainId used for resolution. If the chainId parameter was omitted and getCurrentChainId was used as a fallback, the state will record an incorrect (undefined) chainId for the completed resolution.

Fix in Cursor Fix in Web

const resolutions = await fetchResolutions({
domain: trimmedDomainName,
chainId: `eip155:${chainIdInt}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

We are avoiding use of redux in redesigned confirmations, we should try to avoid it here also.

Copy link
Member Author

@OGPoyraz OGPoyraz Oct 22, 2025

Choose a reason for hiding this comment

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

This is already in use unfortunately and it's not redesigned confirmation but Name component.

There is no other fix in order to show reverse lookup ENS domain in the Name component unless we always want Name component to resolve name resolutions by default but that is not the case.

Expand Down
60 changes: 60 additions & 0 deletions ui/pages/confirmations/hooks/send/useNameValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,46 @@ import { AddressResolution } from '@metamask/snaps-sdk';

import mockState from '../../../../../test/data/mock-state.json';
import { renderHookWithProvider } from '../../../../../test/lib/render-helpers';
import { lookupDomainName } from '../../../../ducks/domains';
// eslint-disable-next-line import/no-namespace
import * as SnapNameResolution from '../../../../hooks/snaps/useSnapNameResolution';
// eslint-disable-next-line import/no-namespace
import * as SendValidationUtils from '../../utils/sendValidations';
import { useNameValidation } from './useNameValidation';
import { useSendType } from './useSendType';

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
useDispatch: jest.fn().mockReturnValue((callback: any) => callback?.()),
}));

jest.mock('@metamask/bridge-controller', () => ({
...jest.requireActual('@metamask/bridge-controller'),
formatChainIdToCaip: jest.fn(),
}));

jest.mock('../../../../ducks/domains', () => ({
lookupDomainName: jest.fn(),
}));

jest.mock('./useSendType', () => ({
useSendType: jest.fn().mockReturnValue({
isEvmSendType: false,
}),
}));

describe('useNameValidation', () => {
const lookupDomainNameMock = jest.mocked(lookupDomainName);
const useSendTypeMock = jest.mocked(useSendType);

beforeEach(() => {
jest.clearAllMocks();
useSendTypeMock.mockReturnValue({
isEvmSendType: false,
} as unknown as ReturnType<typeof useSendType>);
});

it('return function to validate name', () => {
const { result } = renderHookWithProvider(
() => useNameValidation(),
Expand Down Expand Up @@ -47,6 +75,38 @@ describe('useNameValidation', () => {
});
});

it('dispatch lookupDomainName when name is resolved', async () => {
useSendTypeMock.mockReturnValue({
isEvmSendType: true,
} as unknown as ReturnType<typeof useSendType>);
jest.spyOn(SnapNameResolution, 'useSnapNameResolution').mockReturnValue({
fetchResolutions: () =>
Promise.resolve([
{
resolvedAddress: 'dummy_address',
protocol: 'dummy_protocol',
} as unknown as AddressResolution,
]),
});
const { result } = renderHookWithProvider(
() => useNameValidation(),
mockState,
);
expect(
await result.current.validateName(
'5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
'test.eth',
),
).toStrictEqual({
protocol: 'dummy_protocol',
resolvedLookup: 'dummy_address',
});
expect(lookupDomainNameMock).toHaveBeenCalledWith(
'test.eth',
'5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
);
});

it('return confusable error and warning as name is resolved', async () => {
jest
.spyOn(SendValidationUtils, 'findConfusablesInRecipient')
Expand Down
13 changes: 12 additions & 1 deletion ui/pages/confirmations/hooks/send/useNameValidation.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { formatChainIdToCaip } from '@metamask/bridge-controller';
import { useDispatch } from 'react-redux';
import { useCallback } from 'react';

import { isValidDomainName } from '../../../../helpers/utils/util';
import { useSnapNameResolution } from '../../../../hooks/snaps/useSnapNameResolution';
import { findConfusablesInRecipient } from '../../utils/sendValidations';
import { lookupDomainName } from '../../../../ducks/domains';
import { useSendType } from './useSendType';

export const useNameValidation = () => {
const { fetchResolutions } = useSnapNameResolution();
const dispatch = useDispatch();
const { isEvmSendType } = useSendType();

const validateName = useCallback(
async (chainId: string, to: string) => {
Expand All @@ -25,6 +30,12 @@ export const useNameValidation = () => {
const resolvedLookup = resolutions[0]?.resolvedAddress;
const protocol = resolutions[0]?.protocol;

// In order to display the ENS name component we need to initiate reverse lookup by calling lookupDomainName
// so the domain resolution will be available in the store then Name component will use it in the confirmation
if (isEvmSendType) {
dispatch(lookupDomainName(to, chainId));
}

return {
resolvedLookup,
protocol,
Expand All @@ -36,7 +47,7 @@ export const useNameValidation = () => {
error: 'nameResolutionFailedError',
};
},
[fetchResolutions],
[dispatch, fetchResolutions],
Copy link

Choose a reason for hiding this comment

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

Bug: Missing Dependency Causes Stale Value Usage

The validateName useCallback is missing isEvmSendType from its dependency array. This can cause the callback to use a stale value, leading to incorrect conditional dispatching of lookupDomainName and affecting the ENS name component display.

Fix in Cursor Fix in Web

);

return {
Expand Down
Loading