Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"use server";

import { lookupByAddress, type FarcasterProfile } from "./farcaster";
import {
getAgentMetadata as _getAgentMetadata,
type AgentMetadata,
} from "./contracts/erc8004";
import type { Address } from "viem";

/**
* Server action that resolves an Ethereum address to a Farcaster profile.
Expand All @@ -11,3 +16,12 @@ export async function getFarcasterProfile(
): Promise<FarcasterProfile | null> {
return lookupByAddress(address);
}

/**
* Server action that resolves ERC-8004 agent metadata from a wallet address.
*/
export async function fetchAgentMetadata(
address: string,
): Promise<AgentMetadata | null> {
return _getAgentMetadata(address as Address);
}
54 changes: 54 additions & 0 deletions lib/contracts/erc8004.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import { ERC8004_REGISTRY } from "./constants";
* ERC-8004 Agent Registry ABI — agent registration, wallet binding, and
* reverse lookup.
*/
export interface AgentMetadata {
name: string;
description: string;
genre?: string;
llmModel?: string;
registeredBy?: string;
registeredAt?: string;
}

export const erc8004Abi = [
// View
{
Expand All @@ -19,6 +28,13 @@ export const erc8004Abi = [
inputs: [{ name: "wallet", type: "address" }],
outputs: [{ name: "agentId", type: "uint256" }],
},
{
type: "function",
name: "agentURI",
stateMutability: "view",
inputs: [{ name: "agentId", type: "uint256" }],
outputs: [{ name: "", type: "string" }],
},
// Write — register a new agent
{
type: "function",
Expand Down Expand Up @@ -77,3 +93,41 @@ export async function detectWriterType(
return 0;
}
}

/**
* Resolve ERC-8004 agent metadata from an Ethereum address.
* Returns null if the address is not a registered agent or on any error.
*/
export async function getAgentMetadata(
walletAddress: Address,
): Promise<AgentMetadata | null> {
try {
const agentId = await publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
functionName: "agentIdByWallet",
args: [walletAddress],
});
if (agentId <= BigInt(0)) return null;

const uri = await publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
functionName: "agentURI",
args: [agentId],
});
if (!uri) return null;

const parsed = JSON.parse(uri as string) as Record<string, unknown>;
return {
name: (parsed.name as string) || "Unknown Agent",
description: (parsed.description as string) || "",
genre: (parsed.genre as string) || undefined,
llmModel: (parsed.llmModel as string) || (parsed.model as string) || undefined,
registeredBy: (parsed.registeredBy as string) || undefined,
registeredAt: (parsed.registeredAt as string) || undefined,
};
} catch {
return null;
}
}
3 changes: 3 additions & 0 deletions lib/farcaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface FarcasterProfile {
username: string;
displayName: string;
pfpUrl: string | null;
bio: string | null;
}

const STEEMHUNT_BASE = "https://fc.hunt.town";
Expand Down Expand Up @@ -40,6 +41,7 @@ async function steemhuntLookup(address: string): Promise<LookupResult> {
username: data.username,
displayName: data.displayName ?? data.username,
pfpUrl: data.pfpUrl ?? null,
bio: data.bio ?? data.profile?.bio?.text ?? null,
};
}

Expand All @@ -63,6 +65,7 @@ async function neynarLookup(address: string): Promise<LookupResult> {
username: user.username,
displayName: user.display_name ?? user.username,
pfpUrl: user.pfp_url ?? null,
bio: user.profile?.bio?.text ?? null,
};
}

Expand Down
7 changes: 5 additions & 2 deletions src/app/dashboard/writer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,12 @@ function WriterDonationHistory({ storylineId }: { storylineId: number }) {
className="text-muted flex items-center justify-between text-[10px]"
>
<div className="flex gap-2">
<span className="text-foreground">
<a
href={`/profile/${d.donor_address}`}
className="text-foreground hover:text-accent transition-colors"
>
{truncateAddress(d.donor_address)}
</span>
</a>
{d.block_timestamp && (
<time dateTime={d.block_timestamp}>
{new Date(d.block_timestamp).toLocaleDateString("en-US", {
Expand Down
Loading
Loading