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
11 changes: 11 additions & 0 deletions lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ export async function fetchAgentMetadata(
return meta;
}

/**
* Check if a user exists in the DB (any row, with or without agent_id).
* Returns true if a known user, false if completely unknown wallet.
*/
export async function checkUserExists(
address: string,
): Promise<boolean> {
const user = await getUserFromDB(address);
return user !== null;
}

/**
* Cache an externally registered agent by agentId.
* Use when the wallet is an NFT owner (not the bound agent wallet),
Expand Down
9 changes: 8 additions & 1 deletion lib/contracts/erc8004.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export async function getAgentMetadata(
});
if (agentId <= BigInt(0)) return null;

const [uri, owner] = await Promise.all([
const [uri, owner, agentWallet] = await Promise.all([
publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
Expand All @@ -264,13 +264,20 @@ export async function getAgentMetadata(
functionName: "ownerOf",
args: [agentId],
}).catch(() => undefined),
publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
functionName: "getAgentWallet",
args: [agentId],
}).catch(() => undefined),
]);
if (!uri) return null;

const parsed = await resolveAgentURI(uri as string);
return {
agentId: agentId.toString(),
owner: owner as string | undefined,
agentWallet: agentWallet as string | undefined,
name: (parsed.name as string) || "Unknown Agent",
description: (parsed.description as string) || "",
genre: (parsed.genre as string) || undefined,
Expand Down
19 changes: 13 additions & 6 deletions src/app/agents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
import { useState, useEffect, useRef } from "react";
import { useAccount, useReadContract } from "wagmi";
import { useQuery } from "@tanstack/react-query";
import { cacheAgentById } from "../../../lib/actions";
import { ConnectWallet } from "../../components/ConnectWallet";
import { AgentRegister } from "../../components/AgentRegister";
import { AgentManage } from "../../components/AgentManage";
import { AgentBuild } from "../../components/AgentBuild";
import { AgentDashboard } from "../../components/AgentDashboard";
import { erc8004Abi } from "../../../lib/contracts/erc8004";
import { ERC8004_REGISTRY } from "../../../lib/contracts/constants";
import type { User } from "../../../lib/supabase";
import { getAgentUserFromDB } from "../../../lib/actions";
import { getAgentUserFromDB, checkUserExists, cacheAgentById } from "../../../lib/actions";

type Tab = "register" | "build" | "dashboard";

Expand All @@ -32,8 +30,17 @@ export default function AgentsPage() {
const dbIsAgentWallet = dbAgentId != null && dbUser?.agent_wallet?.toLowerCase() === address?.toLowerCase();
const dbDetected = dbAgentId != null;

// RPC fallback: only if DB has no agent data
const needsRpcFallback = !dbLoading && !dbDetected && !!address;
// Check if user exists in DB at all (even without agent_id)
const { data: userExists, isLoading: userExistsLoading } = useQuery({
queryKey: ["user-exists", address],
queryFn: () => checkUserExists(address!),
enabled: !!address && !dbLoading && !dbDetected,
});

// RPC fallback: only for completely unknown wallets (no DB record at all)
// Known users with agent_id=NULL are definitively non-agents — zero RPC calls
// External registrations are detected via profile refresh (/api/user/onboard)
const needsRpcFallback = !dbLoading && !dbDetected && !userExistsLoading && userExists === false && !!address;

const { data: rpcAgentId, isLoading: rpcWalletLoading } = useReadContract({
address: ERC8004_REGISTRY,
Expand Down Expand Up @@ -79,7 +86,7 @@ export default function AgentsPage() {
}

const hasExistingAgent = detectedAgentId !== undefined && detectedRole !== undefined;
const detectLoading = dbLoading || (needsRpcFallback && (rpcWalletLoading || rpcBalanceLoading || (rpcHasNft && rpcTokenLoading)));
const detectLoading = dbLoading || (!dbDetected && userExistsLoading) || (needsRpcFallback && (rpcWalletLoading || rpcBalanceLoading || (rpcHasNft && rpcTokenLoading)));

// Auto-cache: when RPC fallback detects an agent not in DB, persist it
const cachedRef = useRef(false);
Expand Down
51 changes: 51 additions & 0 deletions src/app/api/user/onboard/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { getUserByWallet } from "../../../../../lib/farcaster-indexer";
import { lookupByAddress } from "../../../../../lib/farcaster";
import { fetchQuotientScore, isQuotientStale } from "../../../../../lib/quotient";
import { buildUserData } from "../../../../../lib/user-data";
import { getAgentMetadata, getAgentMetadataById, erc8004Abi } from "../../../../../lib/contracts/erc8004";
import { ERC8004_REGISTRY } from "../../../../../lib/contracts/constants";
import { publicClient } from "../../../../../lib/rpc";
import type { Address } from "viem";

const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes

Expand Down Expand Up @@ -110,6 +114,53 @@ export async function POST(request: NextRequest) {
quotientData,
});

// Check ERC-8004 agent status if not already cached
if (!existingUser?.agent_id) {
try {
// Check 1: is this wallet a bound agent wallet?
let agentMeta = await getAgentMetadata(normalizedAddress as Address);

// Check 2: does this wallet own an agent NFT? (owner with separate bound wallet)
if (!agentMeta) {
const balance = await publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
functionName: "balanceOf",
args: [normalizedAddress as Address],
}).catch(() => BigInt(0));

if (balance > BigInt(0)) {
const ownedTokenId = await publicClient.readContract({
address: ERC8004_REGISTRY,
abi: erc8004Abi,
functionName: "tokenOfOwnerByIndex",
args: [normalizedAddress as Address, BigInt(0)],
}).catch(() => undefined);

if (ownedTokenId !== undefined) {
agentMeta = await getAgentMetadataById(ownedTokenId);
}
}
}

if (agentMeta?.agentId) {
Object.assign(userData, {
agent_id: Number(agentMeta.agentId),
agent_name: agentMeta.name || null,
agent_description: agentMeta.description || null,
agent_genre: agentMeta.genre || null,
agent_llm_model: agentMeta.llmModel || null,
agent_owner: agentMeta.owner?.toLowerCase() || null,
agent_wallet: agentMeta.agentWallet && agentMeta.agentWallet !== "0x0000000000000000000000000000000000000000"
? agentMeta.agentWallet.toLowerCase() : null,
agent_registered_at: agentMeta.registeredAt || null,
});
}
} catch {
// Non-fatal — agent check is best-effort
}
}

// Upsert — update by existing row identity
if (existingUser) {
const { data, error } = await supabase
Expand Down
16 changes: 12 additions & 4 deletions src/components/AgentDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useQuery } from "@tanstack/react-query";
import Link from "next/link";
import { erc8004Abi } from "../../lib/contracts/erc8004";
import { ERC8004_REGISTRY } from "../../lib/contracts/constants";
import { getAgentUserFromDB, cacheAgentById } from "../../lib/actions";
import { getAgentUserFromDB, checkUserExists, cacheAgentById } from "../../lib/actions";

export function AgentDashboard() {
const { address } = useAccount();
Expand All @@ -24,8 +24,16 @@ export function AgentDashboard() {
const dbIsAgentWallet = dbDetected && dbUser?.agent_wallet?.toLowerCase() === address?.toLowerCase();
const dbAgentWallet = dbUser?.agent_wallet;

// RPC fallback: only if DB has no agent data
const needsRpcFallback = !dbLoading && !dbDetected && !!address;
// Check if user exists in DB at all (even without agent_id)
const { data: userExists, isLoading: userExistsLoading } = useQuery({
queryKey: ["user-exists-dashboard", address],
queryFn: () => checkUserExists(address!),
enabled: !!address && !dbLoading && !dbDetected,
});

// RPC fallback: only for completely unknown wallets (no DB record at all)
// Known users with agent_id=NULL are definitively non-agents — zero RPC calls
const needsRpcFallback = !dbLoading && !dbDetected && !userExistsLoading && userExists === false && !!address;

const { data: rpcAgentId, isLoading: rpcWalletLoading } = useReadContract({
address: ERC8004_REGISTRY,
Expand Down Expand Up @@ -88,7 +96,7 @@ export function AgentDashboard() {
}

const isAgent = agentId !== undefined;
const detectLoading = dbLoading || (needsRpcFallback && (rpcWalletLoading || rpcBalanceLoading || (rpcHasNft && rpcTokenLoading)));
const detectLoading = dbLoading || (!dbDetected && userExistsLoading) || (needsRpcFallback && (rpcWalletLoading || rpcBalanceLoading || (rpcHasNft && rpcTokenLoading)));

// Auto-cache: when RPC fallback detects an agent not in DB, persist it
const cachedRef = useRef(false);
Expand Down
Loading