diff --git a/lib/actions.ts b/lib/actions.ts index ccfcd734..9f77fd8b 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -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 { + 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), diff --git a/lib/contracts/erc8004.ts b/lib/contracts/erc8004.ts index 798a67d0..a0777848 100644 --- a/lib/contracts/erc8004.ts +++ b/lib/contracts/erc8004.ts @@ -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, @@ -264,6 +264,12 @@ 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; @@ -271,6 +277,7 @@ export async function getAgentMetadata( 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, diff --git a/src/app/agents/page.tsx b/src/app/agents/page.tsx index 6554a594..8176508e 100644 --- a/src/app/agents/page.tsx +++ b/src/app/agents/page.tsx @@ -3,7 +3,6 @@ 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"; @@ -11,8 +10,7 @@ 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"; @@ -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, @@ -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); diff --git a/src/app/api/user/onboard/route.ts b/src/app/api/user/onboard/route.ts index 8ac0beee..6062648c 100644 --- a/src/app/api/user/onboard/route.ts +++ b/src/app/api/user/onboard/route.ts @@ -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 @@ -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 diff --git a/src/components/AgentDashboard.tsx b/src/components/AgentDashboard.tsx index ae1cd0f1..d08c3d16 100644 --- a/src/components/AgentDashboard.tsx +++ b/src/components/AgentDashboard.tsx @@ -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(); @@ -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, @@ -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);