diff --git a/ui/l2Deposits/DepositsTableItem.tsx b/ui/l2Deposits/DepositsTableItem.tsx
index 99ba9ad465..2c9f437ab7 100644
--- a/ui/l2Deposits/DepositsTableItem.tsx
+++ b/ui/l2Deposits/DepositsTableItem.tsx
@@ -56,7 +56,7 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
>;
+}
+
+const NameDomainDetails = ({ query }: Props) => {
+ const isLoading = query.isPlaceholderData;
+
+ const otherAddresses = Object.entries(query.data?.other_addresses ?? {});
+ const hasExpired = query.data?.expiry_date && dayjs(query.data.expiry_date).isBefore(dayjs());
+
+ return (
+
+ { query.data?.registration_date && (
+
+
+
+ { dayjs(query.data.registration_date).format('llll') }
+
+
+ ) }
+ { query.data?.expiry_date && (
+
+
+ { hasExpired && (
+ <>
+
+ { dayjs(query.data.expiry_date).fromNow() }
+
+
+ >
+ ) }
+
+ { dayjs(query.data.expiry_date).format('llll') }
+
+
+
+
+
+
+ ) }
+ { query.data?.registrant && (
+
+
+
+
+
+
+
+
+ ) }
+ { query.data?.owner && (
+
+
+
+
+
+
+
+
+ ) }
+ { query.data?.wrapped_owner && (
+
+
+
+
+
+
+
+
+ ) }
+ { query.data?.tokens.map((token) => (
+
+
+
+ )) }
+ { otherAddresses.length > 0 && (
+
+ { otherAddresses.map(([ type, address ]) => (
+
+ { type }
+
+
+ )) }
+
+ ) }
+
+ );
+};
+
+export default React.memo(NameDomainDetails);
diff --git a/ui/nameDomain/NameDomainExpiryStatus.tsx b/ui/nameDomain/NameDomainExpiryStatus.tsx
new file mode 100644
index 0000000000..4a6460d068
--- /dev/null
+++ b/ui/nameDomain/NameDomainExpiryStatus.tsx
@@ -0,0 +1,29 @@
+import { chakra } from '@chakra-ui/react';
+import React from 'react';
+
+import dayjs from 'lib/date/dayjs';
+
+interface Props {
+ date: string | undefined;
+}
+
+const NameDomainExpiryStatus = ({ date }: Props) => {
+ if (!date) {
+ return null;
+ }
+
+ const hasExpired = dayjs(date).isBefore(dayjs());
+
+ if (hasExpired) {
+ return Expired;
+ }
+
+ const diff = dayjs(date).diff(dayjs(), 'day');
+ if (diff < 30) {
+ return { diff } days left;
+ }
+
+ return Expires { dayjs(date).fromNow() };
+};
+
+export default React.memo(NameDomainExpiryStatus);
diff --git a/ui/nameDomain/NameDomainHistory.tsx b/ui/nameDomain/NameDomainHistory.tsx
new file mode 100644
index 0000000000..ba5747ad46
--- /dev/null
+++ b/ui/nameDomain/NameDomainHistory.tsx
@@ -0,0 +1,67 @@
+import { Box, Hide, Show } from '@chakra-ui/react';
+import { useRouter } from 'next/router';
+import React from 'react';
+
+import config from 'configs/app';
+import useApiQuery from 'lib/api/useApiQuery';
+import getQueryParamString from 'lib/router/getQueryParamString';
+import { ENS_DOMAIN_EVENT } from 'stubs/ENS';
+import DataListDisplay from 'ui/shared/DataListDisplay';
+
+import NameDomainHistoryListItem from './history/NameDomainHistoryListItem';
+import NameDomainHistoryTable from './history/NameDomainHistoryTable';
+import { getNextSortValue, type Sort, type SortField } from './history/utils';
+
+const NameDomainHistory = () => {
+ const router = useRouter();
+ const domainName = getQueryParamString(router.query.name);
+
+ const [ sort, setSort ] = React.useState();
+
+ const { isPlaceholderData, isError, data } = useApiQuery('domain_events', {
+ pathParams: { name: domainName, chainId: config.chain.id },
+ queryOptions: {
+ placeholderData: { items: Array(4).fill(ENS_DOMAIN_EVENT) },
+ },
+ });
+
+ const handleSortToggle = React.useCallback((event: React.MouseEvent) => {
+ if (isPlaceholderData) {
+ return;
+ }
+ const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined;
+
+ if (field) {
+ setSort(getNextSortValue(field));
+ }
+ }, [ isPlaceholderData ]);
+
+ const content = (
+ <>
+
+
+ { data?.items.map((item, index) => ) }
+
+
+
+
+
+ >
+ );
+
+ return (
+
+ );
+};
+
+export default React.memo(NameDomainHistory);
diff --git a/ui/nameDomain/history/NameDomainHistoryListItem.tsx b/ui/nameDomain/history/NameDomainHistoryListItem.tsx
new file mode 100644
index 0000000000..afa2e526b3
--- /dev/null
+++ b/ui/nameDomain/history/NameDomainHistoryListItem.tsx
@@ -0,0 +1,52 @@
+import { Skeleton } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomainEvent } from 'types/api/ens';
+
+import dayjs from 'lib/date/dayjs';
+import Tag from 'ui/shared/chakra/Tag';
+import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import TxEntity from 'ui/shared/entities/tx/TxEntity';
+import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
+
+type Props = EnsDomainEvent & {
+ isLoading?: boolean;
+}
+
+const NameDomainHistoryListItem = ({ isLoading, transaction_hash: transactionHash, timestamp, from_address: fromAddress, action }: Props) => {
+ return (
+
+ Txn hash
+
+
+
+
+ Age
+
+
+ { dayjs(timestamp).fromNow() }
+
+
+
+ { fromAddress && (
+ <>
+ From
+
+
+
+ >
+ ) }
+
+ { action && (
+ <>
+ Method
+
+ { action }
+
+ >
+ ) }
+
+ );
+};
+
+export default React.memo(NameDomainHistoryListItem);
diff --git a/ui/nameDomain/history/NameDomainHistoryTable.tsx b/ui/nameDomain/history/NameDomainHistoryTable.tsx
new file mode 100644
index 0000000000..f6560372eb
--- /dev/null
+++ b/ui/nameDomain/history/NameDomainHistoryTable.tsx
@@ -0,0 +1,60 @@
+import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomainEventsResponse } from 'types/api/ens';
+
+import IconSvg from 'ui/shared/IconSvg';
+import { default as Thead } from 'ui/shared/TheadSticky';
+
+import NameDomainHistoryTableItem from './NameDomainHistoryTableItem';
+import type { Sort } from './utils';
+import { sortFn } from './utils';
+
+interface Props {
+ data: EnsDomainEventsResponse | undefined;
+ isLoading?: boolean;
+ sort: Sort | undefined;
+ onSortToggle: (event: React.MouseEvent) => void;
+}
+
+const NameDomainHistoryTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
+ const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
+
+ return (
+
+
+
+ Txn hash |
+
+
+ { sort?.includes('timestamp') && (
+
+ ) }
+ Age
+
+ |
+ From |
+ Method |
+
+
+
+ {
+ data?.items
+ .slice()
+ .sort(sortFn(sort))
+ .map((item, index) => )
+ }
+
+
+ );
+};
+
+export default React.memo(NameDomainHistoryTable);
diff --git a/ui/nameDomain/history/NameDomainHistoryTableItem.tsx b/ui/nameDomain/history/NameDomainHistoryTableItem.tsx
new file mode 100644
index 0000000000..d52e4626ca
--- /dev/null
+++ b/ui/nameDomain/history/NameDomainHistoryTableItem.tsx
@@ -0,0 +1,37 @@
+import { Tr, Td, Skeleton } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomainEvent } from 'types/api/ens';
+
+import dayjs from 'lib/date/dayjs';
+import Tag from 'ui/shared/chakra/Tag';
+import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import TxEntity from 'ui/shared/entities/tx/TxEntity';
+
+type Props = EnsDomainEvent & {
+ isLoading?: boolean;
+}
+
+const NameDomainHistoryTableItem = ({ isLoading, transaction_hash: transactionHash, from_address: fromAddress, action, timestamp }: Props) => {
+
+ return (
+
+
+
+ |
+
+
+ { dayjs(timestamp).fromNow() }
+
+ |
+
+ { fromAddress && }
+ |
+
+ { action && { action } }
+ |
+
+ );
+};
+
+export default React.memo(NameDomainHistoryTableItem);
diff --git a/ui/nameDomain/history/utils.ts b/ui/nameDomain/history/utils.ts
new file mode 100644
index 0000000000..168050df2d
--- /dev/null
+++ b/ui/nameDomain/history/utils.ts
@@ -0,0 +1,27 @@
+import type { EnsDomainEvent } from 'types/api/ens';
+
+import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
+
+export type SortField = 'timestamp';
+export type Sort = `${ SortField }-asc` | `${ SortField }-desc`;
+
+const SORT_SEQUENCE: Record> = {
+ timestamp: [ 'timestamp-desc', 'timestamp-asc', undefined ],
+};
+
+export const getNextSortValue = (getNextSortValueShared).bind(undefined, SORT_SEQUENCE);
+
+export const sortFn = (sort: Sort | undefined) => (a: EnsDomainEvent, b: EnsDomainEvent) => {
+ switch (sort) {
+ case 'timestamp-asc': {
+ return b.timestamp.localeCompare(a.timestamp);
+ }
+
+ case 'timestamp-desc': {
+ return a.timestamp.localeCompare(b.timestamp);
+ }
+
+ default:
+ return 0;
+ }
+};
diff --git a/ui/nameDomains/NameDomainsActionBar.tsx b/ui/nameDomains/NameDomainsActionBar.tsx
new file mode 100644
index 0000000000..ff57107799
--- /dev/null
+++ b/ui/nameDomains/NameDomainsActionBar.tsx
@@ -0,0 +1,113 @@
+import { Checkbox, CheckboxGroup, HStack, Text } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomainLookupFiltersOptions } from 'types/api/ens';
+import type { PaginationParams } from 'ui/shared/pagination/types';
+
+import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
+import ActionBar from 'ui/shared/ActionBar';
+import FilterInput from 'ui/shared/filters/FilterInput';
+import PopoverFilter from 'ui/shared/filters/PopoverFilter';
+import Pagination from 'ui/shared/pagination/Pagination';
+import Sort from 'ui/shared/sort/Sort';
+
+import type { Sort as TSort } from './utils';
+import { SORT_OPTIONS } from './utils';
+
+interface Props {
+ pagination: PaginationParams;
+ searchTerm: string | undefined;
+ onSearchChange: (value: string) => void;
+ filterValue: EnsDomainLookupFiltersOptions;
+ onFilterValueChange: (nextValue: EnsDomainLookupFiltersOptions) => void;
+ sort: TSort | undefined;
+ onSortChange: (nextValue: TSort | undefined) => void;
+ isLoading: boolean;
+ isAddressSearch: boolean;
+}
+
+const NameDomainsActionBar = ({
+ searchTerm,
+ onSearchChange,
+ filterValue,
+ onFilterValueChange,
+ sort,
+ onSortChange,
+ isLoading,
+ isAddressSearch,
+ pagination,
+}: Props) => {
+ const isInitialLoading = useIsInitialLoading(isLoading);
+
+ const searchInput = (
+
+ );
+
+ const filter = (
+
+
+
+ Address
+
+ Owned by
+
+
+ Resolved to address
+
+ Status
+
+ Include expired
+
+
+
+
+ );
+
+ const sortButton = (
+
+ );
+
+ return (
+ <>
+
+ { filter }
+ { sortButton }
+ { searchInput }
+
+
+
+ { filter }
+ { searchInput }
+
+
+
+ >
+ );
+};
+
+export default React.memo(NameDomainsActionBar);
diff --git a/ui/nameDomains/NameDomainsListItem.tsx b/ui/nameDomains/NameDomainsListItem.tsx
new file mode 100644
index 0000000000..ac7275d9cc
--- /dev/null
+++ b/ui/nameDomains/NameDomainsListItem.tsx
@@ -0,0 +1,60 @@
+import { Skeleton } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomain } from 'types/api/ens';
+
+import dayjs from 'lib/date/dayjs';
+import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus';
+import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
+import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
+
+interface Props extends EnsDomain {
+ isLoading: boolean;
+}
+
+const NameDomainsListItem = ({ name, isLoading, resolved_address: resolvedAddress, registration_date: registrationDate, expiry_date: expiryDate }: Props) => {
+ return (
+
+ Domain
+
+
+
+
+ { resolvedAddress && (
+ <>
+ Address
+
+
+
+ >
+ ) }
+
+ { registrationDate && (
+ <>
+ Registered on
+
+
+ { dayjs(registrationDate).format('MMM DD YYYY HH:mm:ss A') }
+ { dayjs(registrationDate).fromNow() }
+
+
+ >
+ ) }
+
+ { expiryDate && (
+ <>
+ Expiration date
+
+
+ { dayjs(expiryDate).format('MMM DD YYYY HH:mm:ss A') }
+
+
+
+ >
+ ) }
+
+ );
+};
+
+export default React.memo(NameDomainsListItem);
diff --git a/ui/nameDomains/NameDomainsTable.tsx b/ui/nameDomains/NameDomainsTable.tsx
new file mode 100644
index 0000000000..ec46b7cec8
--- /dev/null
+++ b/ui/nameDomains/NameDomainsTable.tsx
@@ -0,0 +1,54 @@
+import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomainLookupResponse } from 'types/api/ens';
+
+import IconSvg from 'ui/shared/IconSvg';
+import { default as Thead } from 'ui/shared/TheadSticky';
+
+import NameDomainsTableItem from './NameDomainsTableItem';
+import { type Sort } from './utils';
+
+interface Props {
+ data: EnsDomainLookupResponse | undefined;
+ isLoading?: boolean;
+ sort: Sort | undefined;
+ onSortToggle: (event: React.MouseEvent) => void;
+}
+
+const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
+ const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
+
+ return (
+
+
+
+ Domain |
+ Address |
+
+
+ { sort?.includes('registration_date') && (
+
+ ) }
+ Registered on
+
+ |
+ Expiration date |
+
+
+
+ { data?.items.map((item, index) => ) }
+
+
+ );
+};
+
+export default React.memo(NameDomainsTable);
diff --git a/ui/nameDomains/NameDomainsTableItem.tsx b/ui/nameDomains/NameDomainsTableItem.tsx
new file mode 100644
index 0000000000..a4147ae8cf
--- /dev/null
+++ b/ui/nameDomains/NameDomainsTableItem.tsx
@@ -0,0 +1,45 @@
+import { chakra, Tr, Td, Skeleton } from '@chakra-ui/react';
+import React from 'react';
+
+import type { EnsDomain } from 'types/api/ens';
+
+import dayjs from 'lib/date/dayjs';
+import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus';
+import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
+
+type Props = EnsDomain & {
+ isLoading?: boolean;
+}
+
+const NameDomainsTableItem = ({ isLoading, name, resolved_address: resolvedAddress, registration_date: registrationDate, expiry_date: expiryDate }: Props) => {
+
+ return (
+
+
+
+ |
+
+ { resolvedAddress && }
+ |
+
+ { registrationDate && (
+
+ { dayjs(registrationDate).format('MMM DD YYYY HH:mm:ss A') }
+ { dayjs(registrationDate).fromNow() }
+
+ ) }
+ |
+
+ { expiryDate && (
+
+ { dayjs(expiryDate).format('MMM DD YYYY HH:mm:ss A') }
+
+
+ ) }
+ |
+
+ );
+};
+
+export default React.memo(NameDomainsTableItem);
diff --git a/ui/nameDomains/utils.ts b/ui/nameDomains/utils.ts
new file mode 100644
index 0000000000..c240e5c93e
--- /dev/null
+++ b/ui/nameDomains/utils.ts
@@ -0,0 +1,19 @@
+import type { EnsLookupSorting } from 'types/api/ens';
+
+import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
+import type { Option } from 'ui/shared/sort/Sort';
+
+export type SortField = EnsLookupSorting['sort'];
+export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }`;
+
+export const SORT_OPTIONS: Array |