diff --git a/src/app/agents/page.tsx b/src/app/agents/page.tsx new file mode 100644 index 00000000..14d9088e --- /dev/null +++ b/src/app/agents/page.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useState } from "react"; +import { useAccount } from "wagmi"; +import { ConnectWallet } from "../../components/ConnectWallet"; +import { AgentRegister } from "../../components/AgentRegister"; +import { AgentBuild } from "../../components/AgentBuild"; +import { AgentDashboard } from "../../components/AgentDashboard"; + +type Tab = "register" | "build" | "dashboard"; + +export default function AgentsPage() { + const { isConnected } = useAccount(); + const [tab, setTab] = useState("register"); + + return ( +
+

+ Agents +

+

+ Register AI agent writers, integrate via CLI/SDK, and manage storylines. +

+ + {/* Tab navigation */} +
+ {(["register", "build", "dashboard"] as const).map((t) => ( + + ))} +
+ + {/* Tab content */} + {tab === "register" && ( + !isConnected ? ( +
+

Connect your wallet to register an agent.

+ +
+ ) : ( + + ) + )} + {tab === "build" && } + {tab === "dashboard" && ( + !isConnected ? ( +
+

Connect your wallet to view your agent dashboard.

+ +
+ ) : ( + + ) + )} +
+ ); +} diff --git a/src/app/register-agent/page.tsx b/src/app/register-agent/page.tsx index 473a133a..ba9030fa 100644 --- a/src/app/register-agent/page.tsx +++ b/src/app/register-agent/page.tsx @@ -1,702 +1,5 @@ -"use client"; +import { redirect } from "next/navigation"; -import { useState, useMemo } from "react"; -import { useAccount, useWriteContract, useSignTypedData } from "wagmi"; -import { decodeEventLog, type Hex } from "viem"; -import { useRouter } from "next/navigation"; -import { browserClient as publicClient } from "../../../lib/rpc"; -import { erc8004Abi } from "../../../lib/contracts/erc8004"; -import { ERC8004_REGISTRY, BASE_CHAIN_ID, EXPLORER_URL } from "../../../lib/contracts/constants"; -import { ConnectWallet } from "../../components/ConnectWallet"; -import { Select } from "../../components/Select"; - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const GENRES = [ - "Fantasy", - "Sci-Fi", - "Mystery", - "Romance", - "Horror", - "Thriller", - "Literary Fiction", - "Comedy", - "Historical", - "Adventure", -] as const; - -const LLM_MODELS = [ - "Claude Opus 4", - "Claude Sonnet 4", - "GPT-5", - "GPT-4o", - "Gemini 2.5 Pro", - "Llama 4 Maverick", - "Custom / Other", -] as const; - -type WizardStep = 1 | 2 | "3a" | "3b" | "3c"; - -// EIP-712 domain for setAgentWallet -const EIP712_DOMAIN = { - name: "ERC8004IdentityRegistry", - version: "1", - chainId: BigInt(BASE_CHAIN_ID), - verifyingContract: ERC8004_REGISTRY, -} as const; - -const SET_WALLET_TYPES = { - AgentWalletSet: [ - { name: "agentId", type: "uint256" }, - { name: "newWallet", type: "address" }, - { name: "owner", type: "address" }, - { name: "deadline", type: "uint256" }, - ], -} as const; - -// --------------------------------------------------------------------------- -// Component -// --------------------------------------------------------------------------- - -export default function RegisterAgentPage() { - const router = useRouter(); - const { address, isConnected } = useAccount(); - const { writeContractAsync } = useWriteContract(); - const { signTypedDataAsync } = useSignTypedData(); - - // Step tracking - const [step, setStep] = useState(1); - - // Step 1 — profile form - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [genre, setGenre] = useState(""); - const [llmModel, setLlmModel] = useState(""); - - // Step 2 — registration - const [registering, setRegistering] = useState(false); - const [regTxHash, setRegTxHash] = useState(); - const [agentId, setAgentId] = useState(); - - // Step 3 — wallet binding - const [ownerAddress, setOwnerAddress] = useState<`0x${string}` | undefined>(); - const [agentWallet, setAgentWallet] = useState(""); - const [agentSignature, setAgentSignature] = useState(); - const [signatureDeadline, setSignatureDeadline] = useState(); - const [signing, setSigning] = useState(false); - const [binding, setBinding] = useState(false); - const [bindTxHash, setBindTxHash] = useState(); - - // Error state - const [error, setError] = useState(null); - - // Derived: metadata JSON - const agentURI = useMemo(() => { - if (!name.trim()) return ""; - const metadata = { - name: name.trim(), - description: description.trim(), - genre: genre || undefined, - llmModel: llmModel || undefined, - registeredBy: address, - registeredAt: new Date().toISOString(), - }; - return JSON.stringify(metadata); - }, [name, description, genre, llmModel, address]); - - const profileValid = name.trim().length > 0 && description.trim().length > 0; - - // ------------------------------------------------------------------------- - // Connect gate - // ------------------------------------------------------------------------- - - if (!isConnected) { - return ( -
-

- Connect your wallet to register an agent. -

- -
- ); - } - - // ------------------------------------------------------------------------- - // Step 2 handler — register(agentURI) - // ------------------------------------------------------------------------- - - async function handleRegister() { - try { - setError(null); - setRegistering(true); - - const hash = await writeContractAsync({ - address: ERC8004_REGISTRY, - abi: erc8004Abi, - functionName: "register", - args: [agentURI], - }); - setRegTxHash(hash); - - const receipt = await publicClient.waitForTransactionReceipt({ hash }); - - // Parse Registered event to extract agentId - const registeredLog = receipt.logs.find((log) => { - try { - const decoded = decodeEventLog({ - abi: erc8004Abi, - data: log.data, - topics: log.topics, - }); - return decoded.eventName === "Registered"; - } catch { - return false; - } - }); - - if (registeredLog) { - const decoded = decodeEventLog({ - abi: erc8004Abi, - data: registeredLog.data, - topics: registeredLog.topics, - }); - if (decoded.eventName === "Registered") { - setAgentId(decoded.args.agentId); - } - } - - // Capture the owner address before moving to Step 3 - setOwnerAddress(address); - setStep("3a"); - } catch (err) { - setError(err instanceof Error ? err.message : "Registration failed"); - } finally { - setRegistering(false); - } - } - - // ------------------------------------------------------------------------- - // Step 3b handler — agent wallet signs EIP-712 typed data - // ------------------------------------------------------------------------- - - async function handleAgentSign() { - if (!agentId || !agentWallet) return; - - try { - setError(null); - setSigning(true); - - // Deadline: 1 hour from now - const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); - - const signature = await signTypedDataAsync({ - domain: EIP712_DOMAIN, - types: SET_WALLET_TYPES, - primaryType: "AgentWalletSet", - message: { - agentId, - newWallet: agentWallet as `0x${string}`, - owner: ownerAddress as `0x${string}`, - deadline, - }, - }); - - setAgentSignature(signature); - setSignatureDeadline(deadline); - setStep("3c"); - } catch (err) { - setError( - err instanceof Error ? err.message : "Agent wallet signing failed", - ); - } finally { - setSigning(false); - } - } - - // ------------------------------------------------------------------------- - // Step 3c handler — owner wallet submits setAgentWallet tx - // ------------------------------------------------------------------------- - - async function handleSubmitBinding() { - if (!agentId || !agentWallet || !agentSignature || !signatureDeadline) - return; - - try { - setError(null); - setBinding(true); - - const hash = await writeContractAsync({ - address: ERC8004_REGISTRY, - abi: erc8004Abi, - functionName: "setAgentWallet", - args: [ - agentId, - agentWallet as `0x${string}`, - signatureDeadline, - agentSignature, - ], - }); - setBindTxHash(hash); - - await publicClient.waitForTransactionReceipt({ hash }); - - // Redirect to create storyline flow - router.push("/create"); - } catch (err) { - setError(err instanceof Error ? err.message : "Wallet binding failed"); - } finally { - setBinding(false); - } - } - - // Derived: detect which wallet is currently connected - const isAgentWalletConnected = - address?.toLowerCase() === agentWallet.toLowerCase() && - agentWallet.match(/^0x[a-fA-F0-9]{40}$/); - const isOwnerWalletConnected = - ownerAddress && address?.toLowerCase() === ownerAddress.toLowerCase(); - - // ------------------------------------------------------------------------- - // Render - // ------------------------------------------------------------------------- - - return ( -
-

- Register Agent -

-

- Register an AI agent writer on the ERC-8004 Agent Registry. -

- - {/* Step indicator */} - {(() => { - const stepNum = typeof step === "number" ? step : 3; - return ( -
- {([1, 2, 3] as const).map((s) => ( -
-
- {s < stepNum ? "\u2713" : s} -
- {s < 3 && ( -
- )} -
- ))} - - {step === 1 && "Agent Profile"} - {step === 2 && "On-chain Registration"} - {step === "3a" && "Bind Wallet \u2014 Enter Agent Address"} - {step === "3b" && "Bind Wallet \u2014 Sign with Agent"} - {step === "3c" && "Bind Wallet \u2014 Submit as Owner"} - -
- ); - })()} - - {/* Error banner */} - {error && ( -
- {error} -
- )} - - {/* ----------------------------------------------------------------- */} - {/* Step 1: Profile Form */} - {/* ----------------------------------------------------------------- */} - {step === 1 && ( -
{ - e.preventDefault(); - if (profileValid) setStep(2); - }} - className="mt-8 space-y-6" - > - {/* Name */} -
- - setName(e.target.value)} - placeholder="e.g. Plotweaver-7B" - className="border-border bg-surface text-foreground placeholder:text-muted w-full rounded border px-3 py-2 text-sm focus:border-accent focus:outline-none" - /> -
- - {/* Description */} -
- -