The component library for rendering on-chain agent identity and reputation data.

Drop-in React components that fetch and display ERC-8004 agent data directly from the blockchain. No manual data wiring. No custom UI work.

Built for blockchain developers and AI coding agents.

View Components
Ethereumeip155:1
Baseeip155:8453
Polygoneip155:137
BNB Chaineip155:56
Monadeip155:143

Plus testnets — Base Sepolia, BSC Chapel, Monad Testnet. The chain is read off the agent identifier; no network config, no RPC URL, no wallet.

The problem

The plumbing is the work. You shouldn't have to write it.

Rendering one number from the ERC-8004 subgraph means knowing the deployment ID for the chain, the shape of the entity ID, which values come back as strings, and which rows are revoked. Multiply that by every field on the page.

By hand~50 lines, one field
const SUBGRAPH_IDS: Record<number, string> = {
  1: "FV6RR6y13rsnCxBAicKuQEwDp8ioEGiNaWaZUmvr1F8k",
  8453: "43s9hQRurMGjuYnC1r2ZwS6xSQktbFyXMPMqGKUFJojb",
  // ...one entry per chain you support
}

function useReputation(agentRegistry: string, agentId: number) {
  const [, chainId] = agentRegistry.split(":")
  const subgraphId = SUBGRAPH_IDS[Number(chainId)]
  if (!subgraphId) throw new Error("Unsupported chain")

  return useQuery({
    queryKey: ["reputation", chainId, agentId],
    queryFn: async () => {
      const res = await fetch(
        `https://gateway.thegraph.com/api/${KEY}/subgraphs/id/${subgraphId}`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            query: `query ($agent: String!) {
              // no precomputed average exists; rows are cumulative totals
              stats: agentFeedbackStats_collection(
                interval: day
                where: { agent: $agent }
                orderBy: timestamp
                orderDirection: desc
                first: 1
              ) {
                feedbackCreated
                feedbackRevoked
                valueDeltaSum
              }
            }`,
            // the entity id is chainId:agentId — not the registry string
            variables: { agent: `${chainId}:${agentId}` },
          }),
        }
      )

      const json = await res.json()
      if (json.errors?.length) throw new Error(json.errors[0].message)

      const row = json.data?.stats?.[0]
      if (!row) return null // agent exists, no feedback yet

      // BigInt and BigDecimal arrive as strings; derive the average yourself.
      // valueDeltaSum excludes revoked feedback — valueSum does not, and
      // pairing it with this denominator would inflate the score.
      const total =
        parseInt(row.feedbackCreated, 10) - parseInt(row.feedbackRevoked, 10)
      return {
        total,
        average: total > 0 ? parseFloat(row.valueDeltaSum) / total : 0,
      }
    },
  })
}

// ...then render loading, error, empty and not-found states.
// Then do all of it again for feedback, validation and identity.
With @p4n/erc8004-uievery field, every chain
import { ReputationScore } from "@p4n/erc8004-ui"

<ReputationScore
  agentRegistry="eip155:8453:0x8004...a432"
  agentId={888}
/>
  • Chain identifier parsed, subgraph endpoint resolved, API key injected
  • Only the fields the component renders are requested
  • Revoked feedback filtered out of every reputation query
  • String-encoded BigInt and BigDecimal values parsed and validated
  • IPFS, HTTPS and base64 data URIs all resolved
  • Loading, error, empty and not-found states rendered
  • Responses cached and duplicate queries collapsed, page-wide
Components

Sixteen components. Each one fetches its own data.

Nothing below is a mockup. Each component was handed the same two identifiers and queried Base in your browser as this page loaded.

Agent #888 · eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432Queried live from Base
AgentCarddocs →
Loading…
TagClouddocs →
Loading…
ReputationDistributiondocs →
Loading…
ReputationTimelinedocs →
Loading…
EndpointStatusdocs →
Loading…
FeedbackListdocs →
Loading…
ActivityLogdocs →
Loading…

The validation components aren't shown here — the Validation Registry isn't deployed to mainnet yet, so on Base they render their empty state by design. They're documented and previewable against testnet data.

Browse all componentsIdentity · Reputation · Validation · Activity
How it works

Self-contained components, not a data layer you have to learn.

There is no store to configure and no agent object to thread through your tree. Drop a component in, give it an identity, and it takes care of the rest.

Trustless by construction

Components accept identifiers, never display data. There is no prop that lets you put a score on screen that isn't on-chain.

Two props, or none

Pass agentRegistry and agentId, or wrap a subtree in AgentProvider once and let every component inside resolve its own identity.

Queries stay small

Each component asks for only the fields it renders — ReputationScore requests two, not the twenty FeedbackList needs.

Four states, always

Loading, error, empty and not-found are handled inside every component. An agent with no feedback renders an empty state, not a crash.

Themed with CSS variables

One block of custom properties retunes surfaces, accents, radii and the chart palette. Dark mode is a class, not a second stylesheet.

Cached, not re-fetched

TanStack Query backs every component, so duplicate queries collapse into one request and repeat renders come from cache.

For AI coding agents

Written to be read by the thing that writes your frontend.

Most teams shipping ERC-8004 are backend teams, and the UI work gets handed to Claude Code or Cursor. So the docs are published in a form an agent can consume directly — no scraping, no guessing at props.

Read https://erc8004-ui.vercel.app/llms.txt, then build me an agent profile page with @p4n/erc8004-ui.

Quickstart

Three steps, and the data is on screen.

A read-only Graph API key is the only credential involved. No wallet, no RPC provider, no indexer to run.

01

Install

The library plus its one peer dependency. React 18 or 19. TanStack Query is the cache the components run on — you install it, but you never have to configure it.

npm install @p4n/erc8004-ui @tanstack/react-query
02

Wrap your app once

ERC8004Provider holds infrastructure config only — your Graph API key, and optional subgraph overrides. It stores no agent data, and sets up its own query cache unless your app already has one.

import { ERC8004Provider } from "@p4n/erc8004-ui"

export function App() {
  return (
    <ERC8004Provider apiKey="your-graph-api-key">
      <Profile />
    </ERC8004Provider>
  )
}
03

Render an agent

Name the agent once and every component below it resolves its own identity, runs its own query, and shares the cache.

import {
  AgentProvider,
  AgentCard,
  ReputationScore,
  FeedbackList,
} from "@p4n/erc8004-ui"

function Profile() {
  return (
    <AgentProvider
      agentRegistry="eip155:8453:0x8004...a432"
      agentId={888}
    >
      <AgentCard />
      <ReputationScore />
      <FeedbackList />
    </AgentProvider>
  )
}