Skip to content

Commit 913f44a

Browse files
committed
fix: show account group name in SIWE
1 parent 6a46f53 commit 913f44a

File tree

2 files changed

+371
-1
lines changed

2 files changed

+371
-1
lines changed
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
import React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
4+
import { useRowContext, useFallbackDisplayName } from './hook';
5+
import { ConfirmInfoRowContext, ConfirmInfoRowVariant } from './row';
6+
7+
jest.mock('react-redux', () => ({
8+
useSelector: jest.fn(),
9+
useContext: jest.requireActual('react').useContext,
10+
}));
11+
12+
jest.mock('../../../../../../shared/modules/hexstring-utils', () => ({
13+
toChecksumHexAddress: jest.fn(),
14+
stripHexPrefix: jest.fn((value) => value?.replace?.(/^0x/i, '') || ''),
15+
}));
16+
17+
jest.mock('../../../../../helpers/utils/util', () => ({
18+
shortenAddress: jest.fn(),
19+
}));
20+
21+
import { useSelector } from 'react-redux';
22+
import { toChecksumHexAddress } from '../../../../../../shared/modules/hexstring-utils';
23+
import { shortenAddress } from '../../../../../helpers/utils/util';
24+
25+
const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;
26+
const mockToChecksumHexAddress = toChecksumHexAddress as jest.MockedFunction<
27+
typeof toChecksumHexAddress
28+
>;
29+
const mockShortenAddress = shortenAddress as jest.MockedFunction<
30+
typeof shortenAddress
31+
>;
32+
33+
describe('hook.ts', () => {
34+
beforeEach(() => {
35+
jest.clearAllMocks();
36+
});
37+
38+
describe('useFallbackDisplayName', () => {
39+
const mockAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
40+
const mockChecksumAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
41+
const mockShortenedAddress = '0xd8dA...6045';
42+
43+
const setupMockSelector = (overrides: Record<string, any> = {}) => {
44+
const defaultSelectors = {
45+
getAccountGroupsByAddress: [],
46+
getInternalAccounts: [],
47+
};
48+
49+
const allSelectors = { ...defaultSelectors, ...overrides };
50+
51+
mockUseSelector.mockImplementation((selector: any) => {
52+
const selectorStr = selector.toString();
53+
54+
const match = Object.entries(allSelectors).find(([key]) =>
55+
selectorStr.includes(key),
56+
);
57+
58+
return match ? match[1] : null;
59+
});
60+
};
61+
62+
const renderHookWithMocks = (
63+
selectorOverrides: Record<string, any> = {},
64+
) => {
65+
setupMockSelector(selectorOverrides);
66+
return renderHook(() => useFallbackDisplayName(mockAddress));
67+
};
68+
69+
beforeEach(() => {
70+
mockToChecksumHexAddress.mockReturnValue(mockChecksumAddress);
71+
mockShortenAddress.mockReturnValue(mockShortenedAddress);
72+
setupMockSelector();
73+
});
74+
75+
it('returns account name when available (legacy accounts)', () => {
76+
const mockAccountName = 'My Account';
77+
78+
const { result } = renderHookWithMocks({
79+
getAccountName: mockAccountName,
80+
accountName: mockAccountName,
81+
});
82+
83+
expect(result.current).toEqual({
84+
displayName: mockAccountName,
85+
hexAddress: mockChecksumAddress,
86+
});
87+
expect(mockToChecksumHexAddress).toHaveBeenCalledWith(mockAddress);
88+
});
89+
90+
it('returns account name when available (multichain accounts)', () => {
91+
const mockAccountName = 'My Multichain Account';
92+
93+
const { result } = renderHookWithMocks({
94+
getIsMultichainAccountsState2Enabled: true,
95+
getAccountGroupsByAddress: [{ metadata: { name: mockAccountName } }],
96+
});
97+
98+
expect(result.current).toEqual({
99+
displayName: mockAccountName,
100+
hexAddress: mockChecksumAddress,
101+
});
102+
});
103+
104+
it('should return address book contact name when account name is not available', () => {
105+
const mockContactName = 'John Doe';
106+
107+
mockUseSelector.mockImplementation((selector: any) => {
108+
const selectorStr = selector.toString();
109+
if (selectorStr.includes('getAccountGroupsByAddress')) {
110+
return [];
111+
}
112+
if (selectorStr.includes('getInternalAccounts')) {
113+
return [];
114+
}
115+
if (selectorStr.includes('getAddressBookEntry')) {
116+
return { name: mockContactName };
117+
}
118+
return null;
119+
});
120+
121+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
122+
123+
expect(result.current).toEqual({
124+
displayName: mockContactName,
125+
hexAddress: mockChecksumAddress,
126+
});
127+
});
128+
129+
it('should return metadata contract name when account name and address book contact are not available', () => {
130+
const mockMetadataName = 'USDC Token';
131+
132+
mockUseSelector.mockImplementation((selector: any) => {
133+
const selectorStr = selector.toString();
134+
if (selectorStr.includes('getAccountGroupsByAddress')) {
135+
return [];
136+
}
137+
if (selectorStr.includes('getInternalAccounts')) {
138+
return [];
139+
}
140+
if (selectorStr.includes('getMetadataContractName')) {
141+
return mockMetadataName;
142+
}
143+
return null;
144+
});
145+
146+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
147+
148+
expect(result.current).toEqual({
149+
displayName: mockMetadataName,
150+
hexAddress: mockChecksumAddress,
151+
});
152+
});
153+
154+
it('should return ENS name when other names are not available', () => {
155+
const mockEnsName = 'johndoe.eth';
156+
157+
mockUseSelector.mockImplementation((selector: any) => {
158+
const selectorStr = selector.toString();
159+
if (selectorStr.includes('getAccountGroupsByAddress')) {
160+
return [];
161+
}
162+
if (selectorStr.includes('getInternalAccounts')) {
163+
return [];
164+
}
165+
if (selectorStr.includes('getEnsResolutionByAddress')) {
166+
return mockEnsName;
167+
}
168+
return null;
169+
});
170+
171+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
172+
173+
expect(result.current).toEqual({
174+
displayName: mockEnsName,
175+
hexAddress: mockChecksumAddress,
176+
});
177+
});
178+
179+
it('should return shortened address when no other names are available', () => {
180+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
181+
182+
expect(result.current).toEqual({
183+
displayName: mockShortenedAddress,
184+
hexAddress: mockChecksumAddress,
185+
});
186+
expect(mockShortenAddress).toHaveBeenCalledWith(mockChecksumAddress);
187+
});
188+
189+
it('should prioritize account name over other names', () => {
190+
const mockAccountName = 'My Account';
191+
192+
mockUseSelector.mockImplementation((selector: any) => {
193+
const selectorStr = selector.toString();
194+
if (selectorStr.includes('getAccountGroupsByAddress')) {
195+
return [];
196+
}
197+
if (selectorStr.includes('getInternalAccounts')) {
198+
return [];
199+
}
200+
if (
201+
selectorStr.includes('getAccountName') ||
202+
selectorStr.includes('accountName')
203+
) {
204+
return mockAccountName;
205+
}
206+
if (selectorStr.includes('getAddressBookEntry')) {
207+
return { name: 'John Doe' };
208+
}
209+
if (selectorStr.includes('getMetadataContractName')) {
210+
return 'USDC Token';
211+
}
212+
if (selectorStr.includes('getEnsResolutionByAddress')) {
213+
return 'johndoe.eth';
214+
}
215+
return null;
216+
});
217+
218+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
219+
220+
expect(result.current).toEqual({
221+
displayName: mockAccountName,
222+
hexAddress: mockChecksumAddress,
223+
});
224+
});
225+
226+
it('should prioritize address book contact name over metadata and ENS names', () => {
227+
const mockContactName = 'John Doe';
228+
229+
mockUseSelector.mockImplementation((selector: any) => {
230+
const selectorStr = selector.toString();
231+
if (selectorStr.includes('getAccountGroupsByAddress')) {
232+
return [];
233+
}
234+
if (selectorStr.includes('getInternalAccounts')) {
235+
return [];
236+
}
237+
if (selectorStr.includes('getAddressBookEntry')) {
238+
return { name: mockContactName };
239+
}
240+
if (selectorStr.includes('getMetadataContractName')) {
241+
return 'USDC Token';
242+
}
243+
if (selectorStr.includes('getEnsResolutionByAddress')) {
244+
return 'johndoe.eth';
245+
}
246+
return null;
247+
});
248+
249+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
250+
251+
expect(result.current).toEqual({
252+
displayName: mockContactName,
253+
hexAddress: mockChecksumAddress,
254+
});
255+
});
256+
257+
it('should prioritize metadata name over ENS name', () => {
258+
const mockMetadataName = 'USDC Token';
259+
260+
mockUseSelector.mockImplementation((selector: any) => {
261+
const selectorStr = selector.toString();
262+
if (selectorStr.includes('getAccountGroupsByAddress')) {
263+
return [];
264+
}
265+
if (selectorStr.includes('getInternalAccounts')) {
266+
return [];
267+
}
268+
if (selectorStr.includes('getMetadataContractName')) {
269+
return mockMetadataName;
270+
}
271+
if (selectorStr.includes('getEnsResolutionByAddress')) {
272+
return 'johndoe.eth';
273+
}
274+
return null;
275+
});
276+
277+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
278+
279+
expect(result.current).toEqual({
280+
displayName: mockMetadataName,
281+
hexAddress: mockChecksumAddress,
282+
});
283+
});
284+
285+
it('should handle multichain accounts state correctly', () => {
286+
mockUseSelector.mockImplementation((selector: any) => {
287+
const selectorStr = selector.toString();
288+
if (selectorStr.includes('getIsMultichainAccountsState2Enabled')) {
289+
return true;
290+
}
291+
if (selectorStr.includes('getAccountGroupsByAddress')) {
292+
return [{ metadata: { name: 'Multichain Account' } }];
293+
}
294+
if (selectorStr.includes('getInternalAccounts')) {
295+
return [];
296+
}
297+
return null;
298+
});
299+
300+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
301+
302+
expect(result.current.displayName).toBe('Multichain Account');
303+
});
304+
305+
it('should handle empty account group in multichain state', () => {
306+
mockUseSelector.mockImplementation((selector: any) => {
307+
const selectorStr = selector.toString();
308+
if (selectorStr.includes('getIsMultichainAccountsState2Enabled')) {
309+
return true;
310+
}
311+
if (selectorStr.includes('getAccountGroupsByAddress')) {
312+
return [];
313+
}
314+
if (selectorStr.includes('getInternalAccounts')) {
315+
return [];
316+
}
317+
return null;
318+
});
319+
320+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
321+
322+
expect(result.current).toEqual({
323+
displayName: mockShortenedAddress,
324+
hexAddress: mockChecksumAddress,
325+
});
326+
});
327+
328+
it('should handle undefined account group metadata', () => {
329+
mockUseSelector.mockImplementation((selector: any) => {
330+
const selectorStr = selector.toString();
331+
if (selectorStr.includes('getIsMultichainAccountsState2Enabled')) {
332+
return true;
333+
}
334+
if (selectorStr.includes('getAccountGroupsByAddress')) {
335+
return [{ metadata: undefined }];
336+
}
337+
if (selectorStr.includes('getInternalAccounts')) {
338+
return [];
339+
}
340+
return null;
341+
});
342+
343+
const { result } = renderHook(() => useFallbackDisplayName(mockAddress));
344+
345+
expect(result.current).toEqual({
346+
displayName: mockShortenedAddress,
347+
hexAddress: mockChecksumAddress,
348+
});
349+
});
350+
351+
it('should call utility functions with correct parameters', () => {
352+
renderHook(() => useFallbackDisplayName(mockAddress));
353+
354+
expect(mockToChecksumHexAddress).toHaveBeenCalledWith(mockAddress);
355+
expect(mockShortenAddress).toHaveBeenCalledWith(mockChecksumAddress);
356+
});
357+
});
358+
});

ui/components/app/confirm/info/row/hook.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import {
77
getAddressBookEntry,
88
getEnsResolutionByAddress,
99
getInternalAccounts,
10+
getIsMultichainAccountsState2Enabled,
1011
getMetadataContractName,
1112
} from '../../../../../selectors';
13+
import { getAccountGroupsByAddress } from '../../../../../selectors/multichain-accounts/account-tree';
14+
import { MultichainAccountsState } from '../../../../../selectors/multichain-accounts/account-tree.types';
1215
import { ConfirmInfoRowContext } from './row';
1316

1417
export const useRowContext = () => useContext(ConfirmInfoRowContext);
@@ -18,8 +21,17 @@ export const useFallbackDisplayName = function (address: string): {
1821
hexAddress: string;
1922
} {
2023
const hexAddress = toChecksumHexAddress(address);
24+
const isMultichainAccountsState2Enabled = useSelector(
25+
getIsMultichainAccountsState2Enabled,
26+
);
27+
const [accountGroup] = useSelector((state) =>
28+
getAccountGroupsByAddress(state as MultichainAccountsState, [hexAddress]),
29+
);
30+
2131
const internalAccounts = useSelector(getInternalAccounts);
22-
const accountName = getAccountName(internalAccounts, hexAddress);
32+
const accountName = isMultichainAccountsState2Enabled
33+
? accountGroup?.metadata.name
34+
: getAccountName(internalAccounts, hexAddress);
2335
const addressBookContact = useSelector((state) =>
2436
getAddressBookEntry(state, hexAddress),
2537
);

0 commit comments

Comments
 (0)