onchain-ui
Components

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.

Loading...
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-identity
Open in

Requirements

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-react

Usage

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.

Loading...
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.

Loading...
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.

Loading...
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

PropTypeDefaultDescription
addressAddress-Required EVM wallet address
namestring | null-Resolved name override
avatarUrlstring | null-Avatar URL override
resolveIdentitybooleantrueResolve ENS/Base name and avatar
resolverOptionsOnchainResolverOptionsBase then ENSResolver clients, avatar gateways, and lookup order
showAvatarbooleantrueShow avatar or initials fallback
truncatebooleantrueTruncate the fallback address
truncateCharsnumber4Characters shown at each end when fallback address is truncated
showCopybooleantrueShow copy-to-clipboard behavior from AddressDisplay
showExplorerbooleantrueShow explorer link from AddressDisplay
chainIdnumber | null1EVM chain id used to pick a known block explorer
explorerUrlstringDerived from chainIdFull explorer URL override for this address
copyIconReactNodeLucide copy iconIcon rendered before the label when copy is enabled
explorerIconReactNodeLucide external link iconIcon rendered for the explorer link
classNamestring-Applied to the root wrapper
avatarClassNamestring-Applied to the avatar
contentClassNamestring-Applied to the inner AddressDisplay wrapper
addressClassNamestring-Applied to the address text or copy trigger

On this page