From d562971811a51ba63eff212358f4b6b802f8d057 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 27 Mar 2026 14:38:40 +0000 Subject: [PATCH 1/2] [#293] Add Agent Identity card to profile trust dashboard Display full agent metadata (ID, name, description, model, genre, registration date, owner when different from bound wallet) in a new trust dashboard card for writer_type=1 profiles. Extends AgentMetadata interface with agentId and owner fields, fetched in parallel from the ERC-8004 registry. Non-agent profiles unaffected. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/contracts/erc8004.ts | 24 ++++++++++---- src/app/profile/[address]/page.tsx | 53 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/lib/contracts/erc8004.ts b/lib/contracts/erc8004.ts index bb3a9968..ebb82e74 100644 --- a/lib/contracts/erc8004.ts +++ b/lib/contracts/erc8004.ts @@ -11,6 +11,8 @@ import { ERC8004_REGISTRY } from "./constants"; * reverse lookup. */ export interface AgentMetadata { + agentId?: string; + owner?: string; name: string; description: string; genre?: string; @@ -209,16 +211,26 @@ export async function getAgentMetadata( }); if (agentId <= BigInt(0)) return null; - const uri = await publicClient.readContract({ - address: ERC8004_REGISTRY, - abi: erc8004Abi, - functionName: "agentURI", - args: [agentId], - }); + const [uri, owner] = await Promise.all([ + publicClient.readContract({ + address: ERC8004_REGISTRY, + abi: erc8004Abi, + functionName: "agentURI", + args: [agentId], + }), + publicClient.readContract({ + address: ERC8004_REGISTRY, + abi: erc8004Abi, + functionName: "ownerOf", + args: [agentId], + }).catch(() => undefined), + ]); if (!uri) return null; const parsed = JSON.parse(uri as string) as Record; return { + agentId: agentId.toString(), + owner: owner as string | undefined, name: (parsed.name as string) || "Unknown Agent", description: (parsed.description as string) || "", genre: (parsed.genre as string) || undefined, diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index a71bb653..f3423298 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -372,6 +372,59 @@ function ProfileHeader({ )} + {/* Agent Identity card — shown for registered agents */} + {isAgent && agentMeta && ( +
+ Agent Identity +
+ {agentMeta.agentId && ( +
+ Agent ID: + {agentMeta.agentId} +
+ )} +
+ Name: + {agentMeta.name} +
+ {agentMeta.description && ( +
+ Description: + {agentMeta.description} +
+ )} + {agentMeta.llmModel && ( +
+ Model: + {agentMeta.llmModel} +
+ )} + {agentMeta.genre && ( +
+ Genre: + {agentMeta.genre} +
+ )} + {agentMeta.registeredAt && ( +
+ Registered: + + {new Date(agentMeta.registeredAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} + +
+ )} + {agentMeta.owner && agentMeta.owner.toLowerCase() !== address.toLowerCase() && ( +
+ Owner: + + {agentMeta.owner.slice(0, 6)}...{agentMeta.owner.slice(-4)} + +
+ )} +
+
+ )} + {/* Wallet identity card — always shown */}
Wallet From 0eabc3fa55f2d3a90ab1aaee57cd1ae01c24b878 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 27 Mar 2026 14:42:08 +0000 Subject: [PATCH 2/2] [#293] Extract shared resolveAgentURI helper for non-JSON URIs Move URI resolution logic (raw JSON, data:, https://, ipfs://) into a shared resolveAgentURI() function in erc8004.ts. Used by both getAgentMetadata (server-side) and AgentManage (client-side) so agents registered via other apps with non-JSON URIs display correctly everywhere. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/contracts/erc8004.ts | 25 ++++++++++++++++++++++++- src/components/AgentManage.tsx | 23 ++--------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/contracts/erc8004.ts b/lib/contracts/erc8004.ts index ebb82e74..aa7a6fe4 100644 --- a/lib/contracts/erc8004.ts +++ b/lib/contracts/erc8004.ts @@ -195,6 +195,29 @@ export async function detectWriterType( } } +/** + * Resolve an agent URI string to a parsed JSON object. + * Handles raw JSON, data: URIs (base64 + URL-encoded), https://, and ipfs://. + */ +export async function resolveAgentURI(uri: string): Promise> { + if (uri.startsWith("{")) { + return JSON.parse(uri); + } + if (uri.startsWith("data:")) { + const comma = uri.indexOf(","); + const payload = comma >= 0 ? uri.slice(comma + 1) : uri; + return JSON.parse( + uri.includes("base64") ? atob(payload) : decodeURIComponent(payload), + ); + } + // https:// or ipfs:// + const fetchUrl = uri.startsWith("ipfs://") + ? uri.replace("ipfs://", "https://ipfs.io/ipfs/") + : uri; + const res = await fetch(fetchUrl); + return (await res.json()) as Record; +} + /** * Resolve ERC-8004 agent metadata from an Ethereum address. * Returns null if the address is not a registered agent or on any error. @@ -227,7 +250,7 @@ export async function getAgentMetadata( ]); if (!uri) return null; - const parsed = JSON.parse(uri as string) as Record; + const parsed = await resolveAgentURI(uri as string); return { agentId: agentId.toString(), owner: owner as string | undefined, diff --git a/src/components/AgentManage.tsx b/src/components/AgentManage.tsx index a8d14f22..8d8b4894 100644 --- a/src/components/AgentManage.tsx +++ b/src/components/AgentManage.tsx @@ -4,7 +4,7 @@ import { useState, useEffect, useMemo } from "react"; import { useAccount, useWriteContract, useReadContract, useSignTypedData } from "wagmi"; import { type Hex, type Address, zeroAddress } from "viem"; import { browserClient as publicClient } from "../../lib/rpc"; -import { erc8004Abi, type AgentMetadata } from "../../lib/contracts/erc8004"; +import { erc8004Abi, resolveAgentURI, type AgentMetadata } from "../../lib/contracts/erc8004"; import { ERC8004_REGISTRY, BASE_CHAIN_ID, EXPLORER_URL } from "../../lib/contracts/constants"; import { Select } from "./Select"; @@ -98,26 +98,7 @@ export function AgentManage({ agentId, role }: AgentManageProps) { }); if (cancelled) return; if (!uri) { setMetadata(null); return; } - const uriStr = uri as string; - let parsed: Record; - if (uriStr.startsWith("{")) { - // Raw JSON stored directly in the URI field - parsed = JSON.parse(uriStr); - } else if (uriStr.startsWith("data:")) { - // data: URI — extract the base64/json payload - const comma = uriStr.indexOf(","); - const payload = comma >= 0 ? uriStr.slice(comma + 1) : uriStr; - parsed = JSON.parse( - uriStr.includes("base64") ? atob(payload) : decodeURIComponent(payload), - ); - } else { - // https:// or ipfs:// — fetch the metadata JSON - const fetchUrl = uriStr.startsWith("ipfs://") - ? uriStr.replace("ipfs://", "https://ipfs.io/ipfs/") - : uriStr; - const res = await fetch(fetchUrl); - parsed = (await res.json()) as Record; - } + const parsed = await resolveAgentURI(uri as string); setMetadata({ name: (parsed.name as string) || "Unknown Agent", description: (parsed.description as string) || "",