Skip to content

feat(snaps): Add useDisplayName hook #27868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 30, 2024
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
18 changes: 11 additions & 7 deletions ui/components/app/snaps/snap-ui-address/snap-ui-address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { shortenAddress } from '../../../../helpers/utils/util';
import { toChecksumHexAddress } from '../../../../../shared/modules/hexstring-utils';
import { SnapUIAvatar } from '../snap-ui-avatar';
import { useDisplayName } from '../../../../hooks/snaps/useDisplayName';

export type SnapUIAddressProps = {
// The address must be a CAIP-10 string.
Expand Down Expand Up @@ -40,12 +41,15 @@ export const SnapUIAddress: React.FunctionComponent<SnapUIAddressProps> = ({
[caipIdentifier],
);

// For EVM addresses, we make sure they are checksummed.
const transformedAddress =
parsed.chain.namespace === 'eip155'
? toChecksumHexAddress(parsed.address)
: parsed.address;
const shortenedAddress = shortenAddress(transformedAddress);
const displayName = useDisplayName(parsed);

const value =
displayName ??
shortenAddress(
parsed.chain.namespace === 'eip155'
? toChecksumHexAddress(parsed.address)
: parsed.address,
);

return (
<Box
Expand All @@ -55,7 +59,7 @@ export const SnapUIAddress: React.FunctionComponent<SnapUIAddressProps> = ({
gap={2}
>
<SnapUIAvatar address={caipIdentifier} size={avatarSize} />
<Text color={TextColor.inherit}>{shortenedAddress}</Text>
<Text color={TextColor.inherit}>{value}</Text>
</Box>
);
};
54 changes: 54 additions & 0 deletions ui/hooks/snaps/useDisplayName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NamespaceId } from '@metamask/snaps-utils';
import { CaipChainId, KnownCaipNamespace } from '@metamask/utils';
import { useSelector } from 'react-redux';
import {
getMemoizedAccountName,
getAddressBookEntryByNetwork,
AddressBookMetaMaskState,
AccountsMetaMaskState,
} from '../../selectors/snaps';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import { decimalToHex } from '../../../shared/modules/conversion.utils';

export type UseDisplayNameParams = {
chain: {
namespace: NamespaceId;
reference: string;
};
chainId: CaipChainId;
address: string;
};

/**
* Get the display name for an address.
* This will look for an account name in the state, and if not found, it will look for an address book entry.
*
* @param params - The parsed CAIP-10 ID.
* @returns The display name for the address.
*/
export const useDisplayName = (
params: UseDisplayNameParams,
): string | undefined => {
const {
address,
chain: { namespace, reference },
} = params;

const isEip155 = namespace === KnownCaipNamespace.Eip155;

const parsedAddress = isEip155 ? toChecksumHexAddress(address) : address;

const accountName = useSelector((state: AccountsMetaMaskState) =>
getMemoizedAccountName(state, parsedAddress),
);

const addressBookEntry = useSelector((state: AddressBookMetaMaskState) =>
getAddressBookEntryByNetwork(
state,
parsedAddress,
`0x${decimalToHex(isEip155 ? reference : `0`)}`,
),
);

return accountName || (isEip155 && addressBookEntry?.name) || undefined;
};
38 changes: 38 additions & 0 deletions ui/selectors/snaps/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createSelector } from 'reselect';
import { AccountsControllerState } from '@metamask/accounts-controller';
import { getAccountName, getInternalAccounts } from '../selectors';
import { createDeepEqualSelector } from '../util';

/**
* The Metamask state for the accounts controller.
*/
export type AccountsMetaMaskState = {
metamask: AccountsControllerState;
};

/**
* Get the account name for an address.
*
* @param _state - The Metamask state for the accounts controller.
* @param address - The address to get the display name for.
* @returns The account name for the address.
*/
export const getAccountNameFromState = createSelector(
[
getInternalAccounts,
(_state: AccountsMetaMaskState, address: string) => address,
],
getAccountName,
);

/**
* Get the memoized account name for an address.
*
* @param state - The Metamask state for the accounts controller.
* @param address - The address to get the display name for.
* @returns The account name for the address.
*/
export const getMemoizedAccountName = createDeepEqualSelector(
[getAccountNameFromState],
(accountName: string) => accountName,
);
80 changes: 80 additions & 0 deletions ui/selectors/snaps/address-book.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { AddressBookController } from '@metamask/address-book-controller';
import { createDeepEqualSelector } from '../util';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';

/**
* The Metamask state for the address book controller.
*/
export type AddressBookMetaMaskState = {
metamask: {
addressBook: AddressBookController['state']['addressBook'];
};
};

/**
* Get the full address book.
*
* @param state - The Metamask state for the address book controller.
* @returns The full address book.
*/
export const getFullAddressBook = (state: AddressBookMetaMaskState) =>
state.metamask.addressBook;

/**
* Get the memoized full address book.
*
* @param state - The Metamask state for the address book controller.
* @returns The full address book.
*/
export const getMemoizedFullAddressBook = createDeepEqualSelector(
[getFullAddressBook],
(addressBook) => addressBook,
);

/**
* Get the address book for a network.
*
* @param _state - The Metamask state for the address book controller.
* @param chainId - The chain ID to get the address book for.
* @returns The address book for the network.
*/
export const getAddressBookByNetwork = createDeepEqualSelector(
[
getMemoizedFullAddressBook,
(_state: AddressBookMetaMaskState, chainId: `0x${string}`) => chainId,
],
(addressBook, chainId) => {
if (!addressBook[chainId]) {
return [];
}
return Object.values(addressBook[chainId]);
},
);

/* eslint-disable jsdoc/require-param */
/* eslint-disable jsdoc/check-param-names */
/**
* Get an address book entry for an address on a network.
*
* @param state - The Metamask state for the address book controller.
* @param address - The address to get the entry for.
* @param chainId - The chain ID to get the entry for.
* @returns The address book entry for the address on the network.
*/
/* eslint-enable jsdoc/require-param */
/* eslint-enable jsdoc/check-param-names */
export const getAddressBookEntryByNetwork = createDeepEqualSelector(
[
(
state: AddressBookMetaMaskState,
_address: string,
chainId: `0x${string}`,
) => getAddressBookByNetwork(state, chainId),
(_state, address) => address,
],
(addressBook, address) => {
return addressBook.find((contact) =>
isEqualCaseInsensitive(contact.address, address),
);
},
);
2 changes: 2 additions & 0 deletions ui/selectors/snaps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './address-book';
export * from './accounts';