diff --git a/lib/actions.ts b/lib/actions.ts index 9f77fd8b..e2085473 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -12,19 +12,21 @@ import type { Address } from "viem"; /** * Server action that resolves an Ethereum address to a Farcaster profile. * Prefers cached DB data, falls back to live API. + * Accepts an optional pre-fetched dbUser to avoid redundant DB lookups. */ export async function getFarcasterProfile( address: string, + dbUser?: User | null, ): Promise { // Try DB first (only if user has a FID — wallet-only users have no Farcaster profile) - const dbUser = await getUserFromDB(address); - if (dbUser && dbUser.fid != null) { + const user = dbUser !== undefined ? dbUser : await getUserFromDB(address); + if (user && user.fid != null) { return { - fid: dbUser.fid, - username: dbUser.username ?? "", - displayName: dbUser.display_name ?? dbUser.username ?? "", - pfpUrl: dbUser.pfp_url ?? null, - bio: dbUser.bio ?? null, + fid: user.fid, + username: user.username ?? "", + displayName: user.display_name ?? user.username ?? "", + pfpUrl: user.pfp_url ?? null, + bio: user.bio ?? null, }; } // Fallback to live API @@ -34,12 +36,17 @@ export async function getFarcasterProfile( /** * Server action that resolves ERC-8004 agent metadata from a wallet address. * Checks DB cache first, falls back to RPC. Caches externally registered agents. + * Accepts an optional pre-fetched dbUser to avoid redundant DB lookups. */ export async function fetchAgentMetadata( address: string, + preloadedUser?: User | null, ): Promise { - // DB-first: check cached agent data (agent-specific lookup) - const dbUser = await getAgentUserFromDB(address); + // DB-first: check cached agent data. + // Use preloaded user if it has agent_id; otherwise fall back to agent-specific lookup. + const dbUser = preloadedUser?.agent_id != null + ? preloadedUser + : await getAgentUserFromDB(address); if (dbUser?.agent_id != null) { return { agentId: String(dbUser.agent_id), @@ -83,6 +90,26 @@ export async function fetchAgentMetadata( return meta; } +/** + * Fetch the full user profile in a single DB lookup. + * Returns dbUser, fcProfile, and agentMeta derived from one shared row. + * External API fallbacks still fire when DB data is missing. + */ +export async function getFullUserProfile( + address: string, +): Promise<{ + dbUser: User | null; + fcProfile: FarcasterProfile | null; + agentMeta: AgentMetadata | null; +}> { + const dbUser = await getUserFromDB(address); + const [fcProfile, agentMeta] = await Promise.all([ + getFarcasterProfile(address, dbUser), + fetchAgentMetadata(address, dbUser), + ]); + return { dbUser, fcProfile, agentMeta }; +} + /** * 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. diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index f3423298..a7ce53cc 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -8,7 +8,7 @@ import { formatUnits, type Address } from "viem"; import Link from "next/link"; import { supabase, type Storyline, type Donation, type TradeHistory, type User } from "../../../../lib/supabase"; import { STORY_FACTORY, RESERVE_LABEL, EXPLORER_URL, MCV2_BOND, PLOT_TOKEN } from "../../../../lib/contracts/constants"; -import { getFarcasterProfile, fetchAgentMetadata, getUserFromDB } from "../../../../lib/actions"; +import { getFullUserProfile } from "../../../../lib/actions"; import { truncateAddress } from "../../../../lib/utils"; import { formatPrice } from "../../../../lib/format"; import { getTokenPrice, mcv2BondAbi, erc20Abi, type TokenPriceInfo } from "../../../../lib/price"; @@ -30,23 +30,18 @@ export default function ProfilePage() { const [tab, setTab] = useState("stories"); - // DB user data (cached profiles) - const { data: dbUser } = useQuery({ - queryKey: ["db-user", address], - queryFn: () => getUserFromDB(address), + // Unified profile fetch: single DB lookup, derives FC + agent from shared result + const { data: fullProfile, isLoading: profileLoading } = useQuery({ + queryKey: ["full-profile", address], + queryFn: () => getFullUserProfile(address), }); - const { data: fcProfile, isLoading: fcLoading } = useQuery({ - queryKey: ["fc-profile", address], - queryFn: () => getFarcasterProfile(address), - }); - - const { data: agentMeta, isLoading: agentLoading } = useQuery({ - queryKey: ["agent-meta", address], - queryFn: () => fetchAgentMetadata(address), - }); - - const isAgent = !agentLoading && agentMeta !== null && agentMeta !== undefined; + const dbUser = fullProfile?.dbUser ?? null; + const fcProfile = fullProfile?.fcProfile ?? null; + const fcLoading = profileLoading; + const agentMeta = fullProfile?.agentMeta ?? null; + const agentLoading = profileLoading; + const isAgent = !profileLoading && agentMeta !== null && agentMeta !== undefined; // Cumulative claimed royalties (on-chain) const { data: claimedRoyalties } = useQuery({