Address identity
Resolves an EVM address to an ENS or Base name with optional avatar, copy-to-clipboard, and explorer link.
AddressIdentity displays an ENS name or Basename and avatar for an EVM address. It uses AddressDisplay for copy and explorer actions, which always use the original address.
address-identity.tsxShow code
import { AddressIdentity } from "@/components/ui/address-identity"export function AddressIdentityDemo() { return ( <AddressIdentity address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" /> )}Installation
npx shadcn add @onchain-ui/address-identityRequirements
Add <Toaster /> to your root layout to show a toast when someone copies an address. The install includes @onchain-ui/sonner, which uses your app's CSS tokens and does not depend on next-themes. If you already have components/ui/sonner.tsx, the install uses that file.
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
)
}Dependencies
The registry item uses viem, sonner, and lucide-react.
npm install viem sonner lucide-reactUsage
import { AddressIdentity } from "@/components/ui/address-identity"
<AddressIdentity address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" />By default, AddressIdentity tries Base reverse resolution first, then ENS reverse resolution. If a name resolves, it also attempts to resolve the avatar record. If nothing resolves, it falls back to the truncated address.
Caching
The resolver caches lookup results in memory for the session. Concurrent lookups for the same address share one request.
- Failed lookups retry. The resolver does not cache RPC errors, so a lookup can retry on the next mount. It does cache successful responses that report no name for an address.
- Each resolver config has its own cache. Components using the same client instances and resolver settings share results and requests.
Because the cache is scoped to the client instances, create clients once at module scope and reuse them. Building clients inline on every render still works, but each new instance starts with an empty cache.
Custom RPC endpoints
The default resolvers use public RPC endpoints and fall back to a second endpoint if a request fails. Public endpoints can rate limit requests. For production apps, pass your own viem clients through resolverOptions.
import { createPublicClient, http } from "viem"
import { base, mainnet } from "viem/chains"
// Module scope: every component instance shares these clients and their cache.
const resolverOptions = {
mainnetClient: createPublicClient({
chain: mainnet,
transport: http("https://your-rpc-provider.example/mainnet"),
}),
baseClient: createPublicClient({
chain: base,
transport: http("https://your-rpc-provider.example/base"),
}),
}
<AddressIdentity address="0x..." resolverOptions={resolverOptions} />Both clients are optional. Pass only mainnetClient to keep the default Base endpoint, or only baseClient to keep the default mainnet endpoint.
Server-side resolution
By default, the browser starts the lookup after mount and shows the truncated address while it waits. To include the name in the initial HTML, resolve it on the server and pass the result as props. The resolvers depend on viem and can run outside React.
Pass resolveIdentity={false} with the resolved values to skip browser lookups.
In a Next.js server component:
import { AddressIdentity } from "@/components/ui/address-identity"
import { resolveOnchainIdentity } from "@/lib/onchain/resolvers"
import type { Address } from "viem"
export default async function ProfilePage({
params,
}: {
params: Promise<{ address: Address }>
}) {
const { address } = await params
const identity = await resolveOnchainIdentity(address)
return (
<AddressIdentity
address={address}
name={identity.name}
avatarUrl={identity.avatar}
resolveIdentity={false}
/>
)
}In a TanStack Start route loader:
import { createFileRoute } from "@tanstack/react-router"
import { AddressIdentity } from "@/components/ui/address-identity"
import { resolveOnchainIdentity } from "@/lib/onchain/resolvers"
import type { Address } from "viem"
export const Route = createFileRoute("/profile/$address")({
loader: ({ params }) => resolveOnchainIdentity(params.address as Address),
component: Profile,
})
function Profile() {
const identity = Route.useLoaderData()
return (
<AddressIdentity
address={identity.address}
name={identity.name}
avatarUrl={identity.avatar}
resolveIdentity={false}
/>
)
}On the server, the in-memory cache lasts for the process lifetime. Each process has its own cache. To share results with other queries in your app, see Data fetching.
Examples
Resolved name
Pass name and avatarUrl when you already have profile data from your own API or indexer.
address-identity-resolved.tsxShow code
import { AddressIdentity } from "@/components/ui/address-identity"export function AddressIdentityResolved() { return ( <AddressIdentity address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" name="vitalik.eth" avatarUrl="https://metadata.ens.domains/mainnet/avatar/vitalik.eth" /> )}Fallback only
Set resolveIdentity={false} to show the address without requesting a name or avatar.
address-identity-fallback.tsxShow code
import { AddressIdentity } from "@/components/ui/address-identity"export function AddressIdentityFallback() { return ( <AddressIdentity address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" resolveIdentity={false} /> )}No avatar
Set showAvatar={false} to display only the name or address and its actions.
address-identity-no-avatar.tsxShow code
import { AddressIdentity } from "@/components/ui/address-identity"export function AddressIdentityNoAvatar() { return ( <AddressIdentity address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" name="vitalik.eth" showAvatar={false} /> )}Resolver order
Set resolverOptions.reverseLookupOrder to try ENS before Basenames. You can also pass custom viem clients through resolverOptions.
<AddressIdentity
address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
resolverOptions={{
reverseLookupOrder: ["ens", "basename"],
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
address | Address | - | Required EVM wallet address |
name | string | null | - | Resolved name override |
avatarUrl | string | null | - | Avatar URL override |
resolveIdentity | boolean | true | Resolve ENS/Base name and avatar |
resolverOptions | OnchainResolverOptions | Base then ENS | Resolver clients, avatar gateways, and lookup order |
showAvatar | boolean | true | Show avatar or initials fallback |
truncate | boolean | true | Truncate the fallback address |
truncateChars | number | 4 | Characters shown at each end when fallback address is truncated |
showCopy | boolean | true | Show copy-to-clipboard behavior from AddressDisplay |
showExplorer | boolean | true | Show explorer link from AddressDisplay |
chainId | number | null | 1 | EVM chain id used to pick a known block explorer |
explorerUrl | string | Derived from chainId | Full explorer URL override for this address |
copyIcon | ReactNode | Lucide copy icon | Icon rendered before the label when copy is enabled |
explorerIcon | ReactNode | Lucide external link icon | Icon rendered for the explorer link |
className | string | - | Applied to the root wrapper |
avatarClassName | string | - | Applied to the avatar |
contentClassName | string | - | Applied to the inner AddressDisplay wrapper |
addressClassName | string | - | Applied to the address text or copy trigger |