diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8189b5a --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(git rebase:*)" + ], + "additionalDirectories": [ + "/Users/andreagonzalez/ETHDENVER/Main/agentfi/.claude" + ] + } +} diff --git a/.gitignore b/.gitignore index b364a23..65083d4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ agents/**/*.pyc agents/.mypy_cache/ agents/.pytest_cache/ +# ─── Hedera ──────────────────────────────────────────────────────── +agents/hedera_token.json + # ─── pnpm ─────────────────────────────────────────────────────────── node_modules/ .pnpm-store/ diff --git a/STATUS_DEVB.md b/STATUS_DEVB.md new file mode 100644 index 0000000..c130722 --- /dev/null +++ b/STATUS_DEVB.md @@ -0,0 +1,75 @@ +# AgentFi — Dev B Progress Report +## Branch: feature/frontend +## Date: 2026-02-19 + +### Frontend Status + +| Item | Status | Details | +|------|--------|---------| +| layout.tsx (WagmiProvider) | ✅ Done | `` wraps WagmiProvider + RainbowKit + QueryClient. Chains configured: 0G (16600) + ADI (99999) via `chains.ts`. Sidebar + TopBar + DotGrid background. | +| page.tsx (Homepage) | ✅ Done | Rich hero with terminal typewriter animation (15 lines simulating agent execution), "Enter Marketplace" PixelTransition CTA, "View Dashboard" GlareHover CTA, CurvedLoop marquee, LogoCarousel multi-chain section. | +| marketplace/page.tsx | 🔧 Partial | Beautiful UI: search bar, category filters (All/DeFi/Risk/Yield), 6 agent cards (grid 3-col), iNFT banner. **But all data is hardcoded** (AGENTS array). Does NOT call `getListedAgents()` from contract. Hire buttons are PixelTransition visual only — no on-chain tx. | +| agent/[id]/page.tsx | ❌ Stub | Bare stub: shows "Agent #{id}" with 3 TODO comments. No metadata, no pricing, no HireButton integration. | +| my-agents/page.tsx | 🔧 Partial | Rich UI: wallet summary bar, 3 iNFT cards with metadata grid (model, capability, minted, owner, queries, earned), activity timeline (6 events), "Hire More" CTA. **All data hardcoded** — does NOT query user's tokens from contract. | +| dashboard/page.tsx | 🔧 Partial | Rich UI: agent roster (3 cards), live feed with typewriter animation, performance stats, allocation bars, quick actions. Uses `useAccount()` for address display. **All data hardcoded** — no real on-chain or backend queries. | +| AgentCard.tsx | ✅ Done | Component renders: name, description, pricePerHire, capabilities[], category badge. **However**, marketplace/page.tsx does NOT use this component — it has its own inline card rendering. AgentCard is unused in production. | +| HireButton.tsx | ❌ Stub | Has `agentId`, `price`, `onSuccess` props but `handleHire()` is a no-op. 3 TODO comments. No wagmi integration. | +| WalletConnect.tsx | ✅ Done | Full implementation: `useAccount`, `useConnect`, `useDisconnect`. Custom modal listing all connectors (MetaMask/Coinbase/WalletConnect). Loading spinner, address shortening, disconnect button. | +| useAgentData.ts | ❌ Stub | Returns `{ data: undefined, isLoading: false, isError: false }`. TODO comment to use `useReadContract` with AgentNFT ABI. | +| useHireAgent.ts | ❌ Stub | Returns `{ hireAgent: async () => {}, isPending: false, isSuccess: false }`. TODO comment to use `useWriteContract` with AgentMarketplace ABI. | +| useListedAgents.ts | ❌ Stub | Returns `{ agents: [], isLoading: false, isError: false }`. TODO comment to use `useReadContract` with AgentMarketplace ABI. | +| chains.ts | ✅ Done | `ogTestnet` (chainId 16600, RPC evmrpc-testnet.0g.ai) + `adiTestnet` (chainId 99999, RPC rpc.ab.testnet.adifoundation.ai). `wagmiConfig` via `getDefaultConfig` with both chains, SSR enabled. | +| contracts.ts | ✅ Done | Reads addresses from `deployments.json`. Exports `CONTRACT_ADDRESSES.AgentNFT` (0G), `CONTRACT_ADDRESSES.AgentMarketplace` (0G), `CONTRACT_ADDRESSES.AgentPayment` (ADI). | +| ABI files present | ✅ Done | All 3 ABIs present and valid: `AgentNFT.json` (766 lines, has `getAgentData`), `AgentMarketplace.json` (366 lines, has `hireAgent` + `getListedAgents`), `AgentPayment.json` (271 lines). | + +### Agents Status + +| Item | Status | Details | +|------|--------|---------| +| portfolio_analyzer.py | ✅ Done | `PortfolioAnalyzerAgent` extends `BaseAgent`. Calls `AsyncOpenAI` with gpt-4o-mini, system prompt for DeFi portfolio analysis, max_tokens=500. Error handling returns "Agent error: ...". Price: 0.5. | +| yield_optimizer.py | ✅ Done | `YieldOptimizerAgent` same pattern. System prompt for yield strategies with APY ranges. Price: 0.5. | +| risk_scorer.py | ✅ Done | `RiskScorerAgent` same pattern. System prompt for risk scoring 1-10. Price: 0.3. | +| orchestrator.py | ✅ Done | Full implementation: GPT-based routing (ROUTER_PROMPT generates JSON execution plan), `{step_N}` output injection between steps, agent registry, MockPaymentProvider integration (non-blocking). Max 4 steps. | +| api.py endpoints | ✅ Done | FastAPI with 5 endpoints: `GET /health`, `GET /agents` (list all), `POST /agents/{agent_id}/execute`, `POST /orchestrate`, `GET /payments/status`. CORS enabled for localhost:3000. Pydantic models for request/response. | +| mock_provider.py | ✅ Done | `MockPaymentProvider` extends `BasePaymentProvider`. Logs payments with from/to/amount/metadata. Returns success with mock transaction ID. Integrated into orchestrator flow. | + +### Demo Flow Test (PRD Core Demo Flow) + +| Step | Status | Blocker? | +|------|--------|----------| +| Step 1: Connect wallet | ✅ Works | RainbowKit modal, MetaMask/Coinbase/WalletConnect connectors. No blocker. | +| Step 2: Browse marketplace (3 agents) | 🔧 Visual only | Shows 6 hardcoded agents (not 3 from contract). `useListedAgents` is a stub. **Blocker: no on-chain data.** | +| Step 3: Hire agent (tx on-chain) | ❌ Not working | HireButton is a no-op stub. `useHireAgent` is a stub. No `hireAgent()` call on AgentMarketplace. **Blocker: no contract interaction.** | +| Step 4: Agent executes, result displayed | ❌ Not working | Backend agents work (OpenAI calls), but **frontend has zero connection to backend**. No fetch to localhost:8000. No API route. **Blocker: no frontend→backend integration.** | +| Step 5: My Agents shows owned iNFT | 🔧 Visual only | UI is polished but shows hardcoded iNFTs. `useAgentData` is a stub. **Blocker: no on-chain query for user's tokens.** | + +### Summary + +**What works well:** +- UI/UX is polished — homepage, marketplace, dashboard, my-agents all have cohesive gold/dark theme with animations (PixelTransition, GlareHover, typewriter, CurvedLoop) +- WagmiProvider + RainbowKit + chain config is correctly set up +- Contract addresses load from deployments.json +- ABIs are present and contain the expected functions +- Backend agents (Python) are fully functional with OpenAI integration +- Orchestrator chains agents with GPT-based planning +- Payment provider architecture is clean and extensible + +**What's broken / missing:** +1. **Wagmi hooks are all stubs** — useAgentData, useHireAgent, useListedAgents return empty/no-op values +2. **No frontend→backend connection** — zero fetch calls to the FastAPI agents service +3. **Agent detail page is a bare stub** — no metadata display, no hire flow +4. **Marketplace uses hardcoded data** — not reading from contract +5. **HireButton does nothing** — no on-chain transaction +6. **AgentCard component is unused** — marketplace renders its own inline cards + +### Blockers +- **Wagmi hooks need implementation** to connect UI to on-chain data (highest priority for demo) +- **Frontend→Backend bridge** needed to show agent execution results in the UI +- **Agent detail page** needs full build-out for the hire flow + +### What I Need From Dev A +- Confirm deployed contract addresses are final (currently in deployments.json: AgentNFT `0x10e3...4e882`, AgentMarketplace `0x1a9e...4246` on 0G, AgentPayment `0x10e3...4e882` on ADI) +- Confirm `getListedAgents()` returns the 3 demo agents (portfolio_analyzer, yield_optimizer, risk_scorer) — are they listed on-chain? +- Confirm `hireAgent(tokenId)` payable value — what's the expected price in wei/OG? +- Confirm `getAgentData(tokenId)` return shape — what fields does the struct contain? +- Are there any events emitted on hire (e.g., `AgentHired`) we should listen for in the UI? diff --git a/agents/HEDERA_INTEGRATION.md b/agents/HEDERA_INTEGRATION.md new file mode 100644 index 0000000..fefff75 --- /dev/null +++ b/agents/HEDERA_INTEGRATION.md @@ -0,0 +1,300 @@ +# Hedera Integration Plan — AgentFi + +Target: **Hedera Killer App (OpenClaw) bounty — $10,000** + +--- + +## SDK & Packages + +### 1. Hedera Agent Kit (Python) — core Hedera interactions +- **Repo:** https://github.com/hashgraph/hedera-agent-kit-py +- **PyPI:** `hedera-agent-kit` (already in requirements.txt) +- **Install:** `pip install hedera-agent-kit langchain langchain-openai langgraph python-dotenv` +- **What it does:** Plugin-based toolkit for HTS token creation, HBAR transfers, HCS topic management, account queries. Integrates with LangChain. +- **Status:** Actively maintained (last release Feb 2026). Python SDK announced Jan 2026. + +### 2. HOL Standards SDK (TypeScript) — agent registration + HCS-10 messaging +- **npm:** `@hashgraphonline/standards-sdk` (v0.1.145) +- **Install:** `pnpm add @hashgraphonline/standards-sdk` +- **What it does:** HCS-10 agent registration, messaging, topic management, HCS-11 profiles. +- **Important:** This is a **TypeScript/Node.js** package. No Python equivalent exists. +- **Decision needed:** We need a small Node.js script or sidecar for agent registration. See Architecture Decision below. + +### 3. HOL Standards Agent Kit (TypeScript) — higher-level agent tools +- **npm:** `@hashgraphonline/standards-agent-kit` +- **What it does:** Wraps standards-sdk with LangChain-compatible tools (RegisterAgentTool, FindRegistrationsTool, HCS10Builder). +- **Useful for:** Agent registration, connection management, message sending. + +--- + +## Authentication + +### How to create a Hedera testnet account +1. Go to https://portal.hedera.com/register/ +2. Confirm email, log in +3. Select **Testnet** from the network dropdown +4. Click **"Create Account"** (uses ED25519 keys by default) +5. Copy the **Account ID** (format: `0.0.XXXXXX`) and **DER-encoded private key** +6. Testnet accounts get up to **1,000 HBAR every 24 hours** (free refills) + +### Required env vars +```bash +# In .env (root) +HEDERA_ACCOUNT_ID=0.0.XXXXXX +HEDERA_PRIVATE_KEY=302e020100300506... # DER-encoded ED25519 key + +# In agents/.env +ACCOUNT_ID=0.0.XXXXXX # hedera-agent-kit uses these names +PRIVATE_KEY=302e020100... +``` + +--- + +## Integration Steps (ordered by priority) + +### 1. HTS Token Creation — "AGENTFI" fungible token +**Goal:** Create a fungible HTS token that agents earn when hired (judges check for HTS usage). + +**Approach:** Use `hedera-agent-kit-py` Core Token Plugin. + +```python +from hedera_agent_kit.plugins import core_token_plugin, core_token_plugin_tool_names +# CREATE_FUNGIBLE_TOKEN_TOOL creates a token via natural language or direct params +# Token: name="AgentFi Credits", symbol="AFC", decimals=2, initialSupply=100000 +``` + +Or via the Hedera Python SDK directly: +```python +from hiero_sdk_python import Client, Network, AccountId, PrivateKey, TokenCreateTransaction + +client = Client(Network(network="testnet")) +client.set_operator(account_id, private_key) + +tx = TokenCreateTransaction() +tx.set_token_name("AgentFi Credits") +tx.set_token_symbol("AFC") +tx.set_decimals(2) +tx.set_initial_supply(100_000) # 1,000.00 AFC +tx.set_treasury_account_id(account_id) +receipt = tx.execute(client).get_receipt(client) +token_id = receipt.token_id +``` + +**Design:** One shared token for the marketplace. Agents earn AFC tokens per hire. This is simpler and more demo-friendly than one token per agent. + +**Estimated effort:** 2h (create token, write helper, test on testnet) + +--- + +### 2. Agent Registration via HOL Standards SDK (HCS-10) +**Goal:** Register our 3 agents on the Hedera registry so they're discoverable. Judges specifically require "agents reachable via HCS-10" and "registered via HOL Standards SDK." + +**Approach:** Small Node.js registration script using `@hashgraphonline/standards-sdk`. + +```typescript +import { HCS10Client, AgentBuilder, AIAgentCapability } from '@hashgraphonline/standards-sdk'; + +const client = new HCS10Client({ + network: 'testnet', + operatorId: process.env.HEDERA_ACCOUNT_ID, + operatorPrivateKey: process.env.HEDERA_PRIVATE_KEY, + logLevel: 'info', +}); + +// Register Portfolio Analyzer +const agent = new AgentBuilder() + .setName('Portfolio Analyzer') + .setDescription('DeFi portfolio analysis agent — allocation tracking, concentration risk') + .setAgentType('autonomous') + .setCapabilities([AIAgentCapability.TEXT_GENERATION, AIAgentCapability.KNOWLEDGE_RETRIEVAL]) + .setModel('gpt-4o-mini') + .setNetwork('testnet'); + +const result = await client.createAndRegisterAgent(agent); +console.log('Inbound Topic:', result.inboundTopicId); +console.log('Outbound Topic:', result.outboundTopicId); +``` + +**What metadata is required:** +- name, description, agentType ('manual' | 'autonomous') +- capabilities (from AIAgentCapability enum) +- model (optional, for display) +- network ('testnet') + +**Estimated effort:** 3h (setup Node.js script, register 3 agents, save topic IDs) + +--- + +### 3. HCS-10 Messaging — Agent Communication +**Goal:** Demonstrate that agents can be reached via HCS-10 protocol (hard requirement). + +**HCS-10 message format:** +```json +{ + "p": "hcs-10", + "op": "message", // "connection_request" | "connection_created" | "message" + "data": "user query text here", + "operator_id": "topicId@accountId", + "m": "Optional memo" +} +``` + +**Connection workflow:** +1. Agent registers → gets inbound + outbound topics +2. User (or another agent) sends `connection_request` to agent's inbound topic +3. Agent creates a private Connection Topic +4. Agent responds with `connection_created` to user's inbound topic +5. Both exchange `message` ops on the Connection Topic + +**Implementation:** Wrap the HCS-10 flow in a Python adapter that: +- Receives a query from the FastAPI endpoint +- Submits an HCS-10 message to the agent's inbound topic (via Hedera SDK) +- Waits for response on the connection topic +- Returns the result + +**For demo purposes:** We can simplify — submit the user query as an HCS-10 message, let our backend agent process it via the existing orchestrator, and submit the response back as an HCS-10 message. The on-chain message trail is what judges check. + +**Estimated effort:** 4h (connection setup, message adapter, test roundtrip) + +--- + +### 4. Natural Language Interface +**Status:** Already covered by our orchestrator (GPT-4o-mini router). + +**What Hedera judges specifically want to see:** +- Users interacting with agents via natural language (not raw API calls) +- The frontend chat/input box sending queries → orchestrator → agent response +- This is already our core flow. No extra work needed. + +**What to highlight in the demo:** +- "Users type natural language queries in the AgentFi marketplace" +- "The orchestrator routes to the right agent(s) using GPT-4o-mini planning" +- "Results are returned in natural language with DeFi-specific insights" + +**Estimated effort:** 0h (already implemented) + +--- + +### 5. On-Chain Attestations for Agent Identity +**Goal:** Prove agent identity and trust on Hedera (judges check for this). + +**Approach:** Use HCS-11 Profile Standard (part of HOL SDK). Each registered agent gets: +- A Profile Topic containing its metadata (name, capabilities, model) +- An inbound/outbound topic pair serving as its identity anchor +- The registration in the HCS-2 registry topic = on-chain attestation + +**Additional attestation option:** After each agent execution, submit a HCS message with a hash of the result — creates an immutable audit trail of agent activity. + +**Estimated effort:** 1h (attestation messages after agent execution, integrated into orchestrator) + +--- + +## What Can Be Mocked + +| Feature | Mock OK? | Rationale | +|---------|----------|-----------| +| HTS token transfers per-hire | Partially | Create real token, but can mock per-hire transfers in demo if timing is tight. Judges want to see the token EXISTS on HTS. | +| HCS-10 full bidirectional messaging | Yes | Show message submission on-chain. Full async polling can be simplified. | +| UCP (Universal Commerce Protocol) | Yes | Bonus points only. Mention it in pitch, mock the format. | +| Agent-to-agent autonomous payments | Yes | Frame it as "future roadmap." | + +| Feature | MUST be real | Rationale | +|---------|-------------|-----------| +| HTS token creation | Yes | Judges will check TokenID on testnet explorer | +| Agent registration (HOL SDK) | Yes | Judges will check the HCS-2 registry | +| HCS-10 message on-chain | Yes | At least one message per agent must be visible on Hedera explorer | +| Natural language interface | Yes | This is our existing frontend — already real | +| Demo video on YouTube | Yes | Submission rejected without it | +| Pitch deck PDF | Yes | Required for the bounty submission | + +--- + +## Architecture Decision + +### Where does Hedera code live? + +``` +agents/ +├── hedera/ # NEW — all Hedera integration code +│ ├── __init__.py +│ ├── hts_service.py # HTS token creation + transfers +│ ├── hcs_messaging.py # HCS-10 message adapter (submit/read) +│ ├── attestation.py # On-chain attestation after execution +│ └── config.py # Hedera client setup, env loading +├── agents/ +│ ├── orchestrator.py # MODIFIED — add hedera hooks (post-execution) +│ └── ... # UNCHANGED — existing agents untouched +└── api.py # MODIFIED — add /hedera/* endpoints + +scripts/ +└── hedera/ + ├── register-agents.ts # Node.js script — HOL SDK agent registration + ├── create-token.ts # Node.js script — HTS token creation (alternative) + └── package.json # Minimal deps: @hashgraphonline/standards-sdk +``` + +### Does it modify existing agent code? +**No.** The 3 existing agents (portfolio_analyzer, yield_optimizer, risk_scorer) remain untouched. Hedera integration hooks into the orchestrator as a post-execution step: + +```python +# In orchestrator.py — after agent.execute(): +result = await agent.execute(agent_input) + +# NEW: Submit attestation to Hedera (non-blocking, like payment) +try: + await hedera_attestation.submit(agent_name, result_hash, token_id) +except Exception: + logger.warning("Hedera attestation failed (non-blocking)") +``` + +### How does it hook into the orchestrator? +1. **Pre-execution:** Validate HTS token balance (optional, can mock) +2. **Post-execution:** Submit attestation message to HCS topic +3. **Post-execution:** Transfer AFC tokens to agent's Hedera account (or mock) +4. **API layer:** New `/hedera/status` endpoint showing token + registration info + +--- + +## Risk Assessment + +### What could go wrong + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| HOL Standards SDK is JS-only, our backend is Python | HIGH | MEDIUM | Use a separate Node.js script for registration (one-time). Daily operations use hedera-agent-kit-py. | +| hedera-agent-kit-py is new (Jan 2026), may have bugs | MEDIUM | HIGH | Pin version. Have fallback using hiero-sdk-python directly for HTS. | +| Hedera testnet is down or slow during demo | LOW | CRITICAL | Pre-record all Hedera transactions. Show explorer screenshots in deck. Cache last-known-good responses. | +| HCS-10 message polling is slow (consensus latency) | MEDIUM | MEDIUM | Submit messages fire-and-forget. Don't block the UX on HCS response. Show tx hash as proof. | +| Token association required before transfers | LOW | LOW | Pre-associate all agent accounts during setup. | + +### Fallback plan +1. **If SDK is broken:** Use raw Hedera REST API / Mirror Node API for reads, submit transactions via hiero-sdk-python directly. +2. **If testnet is down during demo:** Show pre-recorded explorer screenshots. All Hedera calls are wrapped in try/catch with non-blocking fallback to mock. +3. **If time runs out:** Minimum viable Hedera = (1) create HTS token + (2) register agents on HCS-10 + (3) submit one attestation message. These 3 prove Hedera usage to judges. The rest is bonus. + +--- + +## Implementation Priority (time-boxed to ~10h) + +| Priority | Task | Effort | Blocker? | +|----------|------|--------|----------| +| P0 | Hedera testnet account + fund with HBAR | 15min | Everything else | +| P0 | Create AFC fungible token on HTS | 2h | Demo requirement | +| P0 | Register 3 agents via HOL SDK (Node.js script) | 3h | Bounty requirement | +| P1 | HCS-10 message submission after agent execution | 2h | Bounty requirement | +| P1 | On-chain attestation in orchestrator | 1h | Bounty requirement | +| P2 | HTS token transfer per-hire (real, not mock) | 1.5h | Nice to have | +| P3 | UCP format alignment | 0.5h | Bonus only | + +**Total estimated: ~10h** + +--- + +## Submission Checklist (Mandatory) + +- [ ] Demo video uploaded to YouTube +- [ ] Pitch deck PDF: team intro + project summary + roadmap + demo link +- [ ] HTS token visible on Hedera testnet explorer (https://hashscan.io/testnet) +- [ ] At least 3 agents registered in HCS-2 registry +- [ ] At least one HCS-10 message per agent visible on-chain +- [ ] Natural language interaction shown in demo diff --git a/agents/agents/orchestrator.py b/agents/agents/orchestrator.py index 0077f86..c9f28ee 100644 --- a/agents/agents/orchestrator.py +++ b/agents/agents/orchestrator.py @@ -2,6 +2,7 @@ import json import logging +import os from typing import Any from openai import AsyncOpenAI @@ -15,6 +16,8 @@ logger = logging.getLogger(__name__) +HEDERA_ENABLED = os.environ.get("HEDERA_ENABLED", "false").lower() == "true" + # Registry — add new agents here, nowhere else AGENT_REGISTRY: dict[str, BaseAgent] = { "portfolio_analyzer": PortfolioAnalyzerAgent(), @@ -58,9 +61,10 @@ async def _plan(self, query: str) -> list[dict[str, Any]]: content = response.choices[0].message.content or "{}" return json.loads(content)["steps"] - async def execute(self, query: str) -> str: + async def execute(self, query: str) -> dict[str, Any]: steps = await self._plan(query) outputs: list[str] = [] + hedera_proofs: list[dict] = [] for i, step in enumerate(steps): agent_name: str = step["agent"] @@ -90,6 +94,21 @@ async def execute(self, query: str) -> str: except Exception as pay_err: logger.warning("[orchestrator] payment failed (non-blocking): %s", pay_err) + # Hedera attestation — non-blocking, never crashes the flow + if HEDERA_ENABLED: + try: + from hedera.attestation import attest_execution + proof = await attest_execution(agent_name, agent_input, result) + hedera_proofs.append(proof) + except Exception as h_err: + logger.warning("[orchestrator] Hedera attestation skipped: %s", h_err) + outputs.append(result) - return outputs[-1] if outputs else "No result produced." + return { + "result": outputs[-1] if outputs else "No result produced.", + "hedera_proof": { + "hcs_messages": [p["hcs_tx"] for p in hedera_proofs if p.get("hcs_tx")], + "agents_used": [s["agent"] for s in steps], + }, + } diff --git a/agents/api.py b/agents/api.py index 285f26d..de07bcf 100644 --- a/agents/api.py +++ b/agents/api.py @@ -1,18 +1,22 @@ from __future__ import annotations +import json import logging +import os +from pathlib import Path from typing import Any import uvicorn from dotenv import load_dotenv -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel -from agents.orchestrator import AGENT_REGISTRY, AgentOrchestrator -from agents.payments.mock_provider import MockPaymentProvider +load_dotenv(Path(__file__).resolve().parent / ".env", override=True) # Must run before orchestrator import (reads HEDERA_ENABLED) -load_dotenv() +from fastapi import FastAPI # noqa: E402 +from fastapi.middleware.cors import CORSMiddleware # noqa: E402 +from pydantic import BaseModel # noqa: E402 + +from agents.orchestrator import AGENT_REGISTRY, AgentOrchestrator, HEDERA_ENABLED # noqa: E402 +from agents.payments.mock_provider import MockPaymentProvider # noqa: E402 logging.basicConfig( level=logging.INFO, @@ -47,6 +51,9 @@ class AgentInfo(BaseModel): price_per_call: float +# ── Core endpoints ────────────────────────────────────────────────── + + @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} @@ -70,8 +77,27 @@ async def execute_single(agent_id: str, body: ExecuteRequest) -> AgentResponse: agent = AGENT_REGISTRY.get(agent_id) if not agent: return AgentResponse(success=False, data=None, error=f"Unknown agent: {agent_id}") + result = await agent.execute(body.query) - return AgentResponse(success=True, data=result, error=None) + + # Hedera attestation for single-agent calls + hedera_proof = None + if HEDERA_ENABLED: + try: + from hedera.attestation import attest_execution + proof = await attest_execution(agent_id, body.query, result) + hedera_proof = { + "hcs_messages": [proof["hcs_tx"]] if proof.get("hcs_tx") else [], + "agents_used": [agent_id], + } + except Exception: + pass + + return AgentResponse( + success=True, + data={"result": result, "hedera_proof": hedera_proof}, + error=None, + ) @app.post("/orchestrate") @@ -79,8 +105,15 @@ async def orchestrate(body: ExecuteRequest) -> AgentResponse: # Payment provider is resolved here. # To switch to x402: instantiate X402PaymentProvider() instead. orchestrator = AgentOrchestrator(payment_provider=MockPaymentProvider()) - result = await orchestrator.execute(body.query) - return AgentResponse(success=True, data=result, error=None) + output = await orchestrator.execute(body.query) + return AgentResponse( + success=True, + data={ + "result": output["result"], + "hedera_proof": output.get("hedera_proof"), + }, + error=None, + ) @app.get("/payments/status") @@ -98,5 +131,67 @@ async def payment_status() -> AgentResponse: ) +# ── Hedera endpoints ─────────────────────────────────────────────── + + +@app.get("/hedera/status") +async def hedera_status() -> AgentResponse: + """Check Hedera integration status — token, registered agents, connectivity.""" + from hedera.attestation import get_agent_topics + + token_id = os.environ.get("HEDERA_TOKEN_ID", "") + agent_names = ["portfolio_analyzer", "yield_optimizer", "risk_scorer"] + agents_info = {} + for name in agent_names: + topics = get_agent_topics(name) + if topics.get("inbound"): + agents_info[name] = topics + + return AgentResponse( + success=True, + data={ + "enabled": HEDERA_ENABLED, + "network": "testnet", + "operator": os.environ.get("HEDERA_ACCOUNT_ID", ""), + "token_id": token_id, + "token_explorer": f"https://hashscan.io/testnet/token/{token_id}" if token_id else None, + "registered_agents": agents_info, + }, + error=None, + ) + + +@app.get("/hedera/agents/{agent_id}/topics") +async def get_agent_topics_endpoint(agent_id: str) -> AgentResponse: + """Get HCS-10 topic IDs for a registered agent.""" + from hedera.attestation import get_agent_topics + + topics = get_agent_topics(agent_id) + if not topics.get("inbound"): + return AgentResponse(success=False, data=None, error=f"Agent {agent_id} not registered on Hedera") + + return AgentResponse( + success=True, + data={ + "agent_id": agent_id, + **topics, + "inbound_explorer": f"https://hashscan.io/testnet/topic/{topics['inbound']}", + "outbound_explorer": f"https://hashscan.io/testnet/topic/{topics['outbound']}", + }, + error=None, + ) + + +@app.get("/hedera/registration") +async def hedera_registration() -> AgentResponse: + """Return cached registration results from the Node.js registration script.""" + results_path = Path(__file__).resolve().parent.parent / "scripts" / "hedera" / "registration-results.json" + if not results_path.exists(): + return AgentResponse(success=False, data=None, error="No registration results found. Run scripts/hedera/register-agents.js first.") + + results = json.loads(results_path.read_text()) + return AgentResponse(success=True, data=results, error=None) + + if __name__ == "__main__": uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True) diff --git a/agents/hedera/__init__.py b/agents/hedera/__init__.py new file mode 100644 index 0000000..d2f9a3c --- /dev/null +++ b/agents/hedera/__init__.py @@ -0,0 +1 @@ +"""Hedera integration — HTS tokens, HCS-10 messaging, attestations.""" diff --git a/agents/hedera/attestation.py b/agents/hedera/attestation.py new file mode 100644 index 0000000..e884312 --- /dev/null +++ b/agents/hedera/attestation.py @@ -0,0 +1,57 @@ +"""On-chain attestation — submit proof of agent execution to Hedera.""" + +from __future__ import annotations + +import hashlib +import logging +import os + +from hedera.service_factory import get_hcs_service + +logger = logging.getLogger(__name__) + +# Map agent names → env var prefixes for their Hedera topic IDs +_AGENT_ENV_PREFIX = { + "portfolio_analyzer": "HEDERA_PORTFOLIO_ANALYZER", + "yield_optimizer": "HEDERA_YIELD_OPTIMIZER", + "risk_scorer": "HEDERA_RISK_SCORER", +} + + +def get_agent_topics(agent_name: str) -> dict[str, str]: + """Return inbound/outbound topic IDs for a registered agent.""" + prefix = _AGENT_ENV_PREFIX.get(agent_name, "") + if not prefix: + return {} + return { + "account": os.environ.get(f"{prefix}_ACCOUNT", ""), + "inbound": os.environ.get(f"{prefix}_INBOUND_TOPIC", ""), + "outbound": os.environ.get(f"{prefix}_OUTBOUND_TOPIC", ""), + } + + +async def attest_execution(agent_name: str, query: str, result: str) -> dict: + """Submit execution proof to Hedera. Non-blocking — errors are logged, not raised.""" + proof: dict[str, str | None] = { + "hcs_tx": None, + } + + try: + topics = get_agent_topics(agent_name) + # Use inbound topic — open for anyone to submit (no submit_key). + # Outbound topics have a submit_key restricted to the agent's own key. + inbound_topic = topics.get("inbound", "") + + if not inbound_topic: + logger.debug("No inbound topic for %s — skipping attestation", agent_name) + return proof + + result_hash = hashlib.sha256(result.encode()).hexdigest() + hcs = get_hcs_service() + attestation_data = f"execution_proof|agent={agent_name}|hash={result_hash}" + proof["hcs_tx"] = hcs.submit_message(inbound_topic, agent_name, attestation_data) + + except Exception as e: + logger.warning("[Hedera] Attestation failed for %s (non-blocking): %s", agent_name, e) + + return proof diff --git a/agents/hedera/config.py b/agents/hedera/config.py new file mode 100644 index 0000000..1715a96 --- /dev/null +++ b/agents/hedera/config.py @@ -0,0 +1,45 @@ +"""Hedera client configuration — singleton shared by all Hedera services.""" + +from __future__ import annotations + +import logging +import os + +from hiero_sdk_python import AccountId, Client, Network, PrivateKey + +logger = logging.getLogger(__name__) + +_client: Client | None = None + + +def get_hedera_client() -> Client: + """Return a configured Hedera testnet client (cached singleton).""" + global _client + if _client is not None: + return _client + + account_id_str = os.environ.get("HEDERA_ACCOUNT_ID") or os.environ.get("ACCOUNT_ID") + private_key_str = os.environ.get("HEDERA_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY") + + if not account_id_str or not private_key_str: + raise RuntimeError( + "Missing Hedera credentials. " + "Set HEDERA_ACCOUNT_ID + HEDERA_PRIVATE_KEY in .env" + ) + + network = Network(network="testnet") + client = Client(network) + client.set_operator( + AccountId.from_string(account_id_str), + PrivateKey.from_string(private_key_str), + ) + + logger.info("Hedera client initialised for account %s (testnet)", account_id_str) + _client = client + return _client + + +def get_operator_account_id() -> AccountId: + """Return the operator AccountId from env (no client needed).""" + raw = os.environ.get("HEDERA_ACCOUNT_ID") or os.environ.get("ACCOUNT_ID", "") + return AccountId.from_string(raw) diff --git a/agents/hedera/create_token.py b/agents/hedera/create_token.py new file mode 100644 index 0000000..2af57bd --- /dev/null +++ b/agents/hedera/create_token.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""One-time script: create the AFC fungible token on Hedera testnet. + +Usage: + cd agents/ + python -m hedera.create_token + +Requires HEDERA_ACCOUNT_ID + HEDERA_PRIVATE_KEY in .env (or env vars). +""" + +from __future__ import annotations + +import json +import logging +import sys +from pathlib import Path + +from dotenv import load_dotenv + +# Load .env from agents/ directory +load_dotenv(Path(__file__).resolve().parent.parent / ".env") + +from hedera.hts_service import HTSService # noqa: E402 + +logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s] %(message)s", + datefmt="%H:%M:%S", +) +logger = logging.getLogger(__name__) + + +def main() -> None: + svc = HTSService() + + logger.info("Creating AgentFi Credits (AFC) token on Hedera testnet …") + token = svc.create_fungible_token( + name="AgentFi Credits", + symbol="AFC", + decimals=2, + initial_supply=100_000, # = 1,000.00 AFC + ) + + logger.info("✔ Token created successfully!") + logger.info(" Token ID : %s", token.token_id) + logger.info(" Name : %s", token.name) + logger.info(" Symbol : %s", token.symbol) + logger.info(" Decimals : %d", token.decimals) + logger.info(" Supply : %d (= %.2f %s)", token.initial_supply, token.initial_supply / 10**token.decimals, token.symbol) + logger.info("") + logger.info("View on HashScan: https://hashscan.io/testnet/token/%s", token.token_id) + + # Save token ID for later use + out_path = Path(__file__).resolve().parent.parent / "hedera_token.json" + payload = { + "token_id": token.token_id, + "name": token.name, + "symbol": token.symbol, + "decimals": token.decimals, + "initial_supply": token.initial_supply, + "explorer": f"https://hashscan.io/testnet/token/{token.token_id}", + } + out_path.write_text(json.dumps(payload, indent=2) + "\n") + logger.info("Saved token info to %s", out_path) + + +if __name__ == "__main__": + try: + main() + except Exception as exc: + logger.error("Token creation failed: %s", exc) + sys.exit(1) diff --git a/agents/hedera/hcs_messaging.py b/agents/hedera/hcs_messaging.py new file mode 100644 index 0000000..83a8322 --- /dev/null +++ b/agents/hedera/hcs_messaging.py @@ -0,0 +1,48 @@ +"""HCS-10 protocol message submission to Hedera.""" + +from __future__ import annotations + +import json +import logging + +from hiero_sdk_python import TopicId, TopicMessageSubmitTransaction + +from hedera.config import get_hedera_client, get_operator_account_id + +logger = logging.getLogger(__name__) + + +class HCSMessaging: + """Submit HCS-10 formatted messages to Hedera Consensus Service topics.""" + + def submit_message( + self, + topic_id_str: str, + agent_id: str, + message_data: str, + op: str = "message", + ) -> str: + """Submit an HCS-10 message to a topic. Returns transaction ID.""" + client = get_hedera_client() + operator = get_operator_account_id() + + hcs10_payload = json.dumps({ + "p": "hcs-10", + "op": op, + "data": message_data[:1024], + "operator_id": f"{topic_id_str}@{operator}", + "m": f"AgentFi:{agent_id}", + }) + + topic_id = TopicId.from_string(topic_id_str) + + tx = TopicMessageSubmitTransaction() + tx.set_topic_id(topic_id) + tx.set_message(hcs10_payload) + + logger.info("Submitting HCS-10 message to %s (agent=%s, op=%s)", topic_id_str, agent_id, op) + receipt = tx.execute(client) + + tx_id = str(receipt.transaction_id) if receipt.transaction_id else "unknown" + logger.info("HCS message submitted — tx: %s", tx_id) + return tx_id diff --git a/agents/hedera/hts_service.py b/agents/hedera/hts_service.py new file mode 100644 index 0000000..3128315 --- /dev/null +++ b/agents/hedera/hts_service.py @@ -0,0 +1,87 @@ +"""Real HTS service — creates tokens and executes transfers on Hedera testnet.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass + +from hiero_sdk_python import ( + AccountId, + TokenCreateTransaction, + TokenId, + TransferTransaction, +) + +from hedera.config import get_hedera_client, get_operator_account_id + +logger = logging.getLogger(__name__) + + +@dataclass +class TokenInfo: + token_id: str + name: str + symbol: str + decimals: int + initial_supply: int + + +class HTSService: + """Hedera Token Service — create fungible tokens and transfer them.""" + + def create_fungible_token( + self, + name: str = "AgentFi Credits", + symbol: str = "AFC", + decimals: int = 2, + initial_supply: int = 100_000, + ) -> TokenInfo: + """Create a new HTS fungible token. Returns token metadata.""" + client = get_hedera_client() + treasury = get_operator_account_id() + + tx = TokenCreateTransaction() + tx.set_token_name(name) + tx.set_token_symbol(symbol) + tx.set_decimals(decimals) + tx.set_initial_supply(initial_supply) + tx.set_treasury_account_id(treasury) + + logger.info("Creating HTS token %s (%s) supply=%d …", name, symbol, initial_supply) + receipt = tx.execute(client) + token_id = receipt.token_id + + logger.info("Token created: %s", token_id) + return TokenInfo( + token_id=str(token_id), + name=name, + symbol=symbol, + decimals=decimals, + initial_supply=initial_supply, + ) + + def transfer_tokens( + self, + token_id_str: str, + from_account: str, + to_account: str, + amount: int, + ) -> str: + """Transfer fungible tokens between accounts. Returns tx status.""" + client = get_hedera_client() + token_id = TokenId.from_string(token_id_str) + sender = AccountId.from_string(from_account) + receiver = AccountId.from_string(to_account) + + tx = TransferTransaction() + tx.add_token_transfer(token_id, sender, -amount) + tx.add_token_transfer(token_id, receiver, amount) + + logger.info( + "Transferring %d of %s: %s → %s", + amount, token_id_str, from_account, to_account, + ) + receipt = tx.execute(client) + + logger.info("Transfer complete — status: %s", receipt.status) + return str(receipt.status) diff --git a/agents/hedera/mock_hedera.py b/agents/hedera/mock_hedera.py new file mode 100644 index 0000000..91c14a1 --- /dev/null +++ b/agents/hedera/mock_hedera.py @@ -0,0 +1,78 @@ +"""Mock Hedera services — log actions without touching the network.""" + +from __future__ import annotations + +import logging +import time +from dataclasses import dataclass + +logger = logging.getLogger(__name__) + +_call_counter = 0 + + +def _next_id() -> int: + global _call_counter + _call_counter += 1 + return _call_counter + + +@dataclass +class MockTokenInfo: + token_id: str + name: str + symbol: str + decimals: int + initial_supply: int + + +class MockHTSService: + """Drop-in replacement for HTSService that never hits the network.""" + + def create_fungible_token( + self, + name: str = "AgentFi Credits", + symbol: str = "AFC", + decimals: int = 2, + initial_supply: int = 100_000, + ) -> MockTokenInfo: + fake_id = f"0.0.{9000 + _next_id()}" + logger.info("[MOCK] Created HTS token %s (%s) → %s", name, symbol, fake_id) + return MockTokenInfo( + token_id=fake_id, + name=name, + symbol=symbol, + decimals=decimals, + initial_supply=initial_supply, + ) + + def transfer_tokens( + self, + token_id_str: str, + from_account: str, + to_account: str, + amount: int, + ) -> str: + logger.info( + "[MOCK] Transfer %d of %s: %s → %s", + amount, token_id_str, from_account, to_account, + ) + return "SUCCESS" + + +class MockHCSMessaging: + """Drop-in replacement for HCSMessaging that never hits the network.""" + + def submit_message( + self, + topic_id_str: str, + agent_id: str, + message_data: str, + op: str = "message", + ) -> str: + fake_tx = f"0.0.{9000 + _next_id()}@{int(time.time())}.000" + logger.info( + "[MOCK] HCS-10 message to %s (agent=%s, op=%s) → tx %s", + topic_id_str, agent_id, op, fake_tx, + ) + return fake_tx diff --git a/agents/hedera/service_factory.py b/agents/hedera/service_factory.py new file mode 100644 index 0000000..036725d --- /dev/null +++ b/agents/hedera/service_factory.py @@ -0,0 +1,29 @@ +"""Factory — returns real or mock Hedera services based on HEDERA_ENABLED env var.""" + +from __future__ import annotations + +import os + + +def _is_enabled() -> bool: + return os.environ.get("HEDERA_ENABLED", "false").lower() == "true" + + +def get_hts_service(): + """Return HTSService (real) if HEDERA_ENABLED=true, else MockHTSService.""" + if _is_enabled(): + from hedera.hts_service import HTSService + return HTSService() + + from hedera.mock_hedera import MockHTSService + return MockHTSService() + + +def get_hcs_service(): + """Return HCSMessaging (real) if HEDERA_ENABLED=true, else MockHCSMessaging.""" + if _is_enabled(): + from hedera.hcs_messaging import HCSMessaging + return HCSMessaging() + + from hedera.mock_hedera import MockHCSMessaging + return MockHCSMessaging() diff --git a/agents/requirements.txt b/agents/requirements.txt index 09b3c6c..87b19fa 100644 --- a/agents/requirements.txt +++ b/agents/requirements.txt @@ -1,7 +1,8 @@ fastapi uvicorn[standard] openai -hedera-agent-kit +hedera-agent-kit>=3.2.0 +hiero-sdk-python>=0.1.9 python-dotenv pydantic httpx diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..52119b8 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,3 @@ +.next/ +node_modules/ +.env.local diff --git a/frontend/.tailwindcheck b/frontend/.tailwindcheck new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/frontend/.tailwindcheck @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 93932ac..c879f4f 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -143,3 +143,88 @@ NICE TO HAVE: - Desktop-first — don't waste time on mobile for hackathon - Error states: always user-friendly message, never raw JS errors in UI - Loading states: skeleton or spinner for every async operation + +## Sync Instructions — From Dev A (18 Fév soir) + +### What changed on `main` +- `contracts/src/` → 3 contracts finalized and deployed: + - `AgentNFT.sol` — ERC-7857 iNFT (0G Chain) + - `AgentMarketplace.sol` — listing + hiring (0G Chain) + - `AgentPayment.sol` — compliance payments (ADI Chain) +- `frontend/src/abi/` → 3 ABI JSON files synced (AgentNFT, AgentMarketplace, AgentPayment) +- `deployments.json` → live addresses on 0G (chainId 16600) + ADI (chainId 99999) +- `contracts/script/Seed.s.sol` → 3 agents minted and listed on 0G marketplace + +### Deployed Addresses +| Chain | Contract | Address | +|-------|----------|---------| +| 0G Testnet (16600) | AgentNFT | `0x10e3399025e930da7b4d4be71181157ccee4e882` | +| 0G Testnet (16600) | AgentMarketplace | `0x1a9e3f39cf83e53ca34933f81b409a92ad004246` | +| ADI Testnet (99999) | AgentPayment | `0x10e3399025e930da7b4d4be71181157ccee4e882` | + +### Seeded Agents on 0G Marketplace +| TokenId | Agent | Price/Hire | +|---------|-------|------------| +| 0 | Portfolio Analyzer | 0.001 ETH | +| 1 | Yield Optimizer | 0.001 ETH | +| 2 | Risk Scorer | 0.0005 ETH | + +⚠️ **TokenIds are 0-based** (0, 1, 2) — not 1-based. + +### Files you must NEVER edit +- `contracts/` — owned by Dev A +- `frontend/src/abi/` — auto-generated by sync-abis.sh +- `deployments.json` — updated by Dev A after each deploy + +### How to use the ABIs in wagmi hooks +```typescript +// Import ABI +import AgentNFTAbi from "@/abi/AgentNFT.json"; +import AgentMarketplaceAbi from "@/abi/AgentMarketplace.json"; + +// Import addresses from deployments.json via config +import { CONTRACT_ADDRESSES } from "@/config/contracts"; + +// Example: read agent data +const { data } = useReadContract({ + address: CONTRACT_ADDRESSES.AgentNFT, + abi: AgentNFTAbi, + functionName: "getAgentData", + args: [BigInt(tokenId)], + chainId: 16600, +}); + +// Example: hire agent (payable) +const { writeContract } = useWriteContract(); +writeContract({ + address: CONTRACT_ADDRESSES.AgentMarketplace, + abi: AgentMarketplaceAbi, + functionName: "hireAgent", + args: [BigInt(tokenId)], + value: parseEther("0.001"), + chainId: 16600, +}); +``` + +### Key contract interfaces for your hooks +**AgentNFT — read functions:** +- `getAgentData(uint256 tokenId)` → returns `(string modelHash, string systemPrompt, string capabilities, uint256 pricePerCall)` +- `ownerOf(uint256 tokenId)` → returns `address` +- `tokenURI(uint256 tokenId)` → returns `string` + +**AgentMarketplace — read functions:** +- `getListedAgents()` → returns array of `(uint256 tokenId, address owner, uint256 pricePerHire, bool active)` +- `getListing(uint256 tokenId)` → returns single listing struct + +**AgentMarketplace — write functions:** +- `hireAgent(uint256 tokenId)` — payable, send `value >= pricePerHire` + +**AgentPayment (ADI Chain) — write functions:** +- `pay(address recipient, uint256 agentTokenId)` — payable, both sender and recipient must be whitelisted + +### If contracts get redeployed +Dev A will merge into main and send you a message. You then: +1. `git checkout main && git pull origin main` +2. `git checkout your-branch && git rebase main` +3. New ABIs and addresses are automatically picked up +``` \ No newline at end of file diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index ab74b20..94be31c 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - transpilePackages: ["wagmi"], -}; + reactStrictMode: true, +} -export default nextConfig; +export default nextConfig diff --git a/frontend/package.json b/frontend/package.json index e32f707..a91d429 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,26 +9,31 @@ "lint": "next lint" }, "dependencies": { - "next": "14.2.21", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "wagmi": "^2.14.11", - "viem": "^2.21.54", + "@gsap/react": "^2.1.2", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-slot": "^1.1.1", "@rainbow-me/rainbowkit": "^2.2.3", "@tanstack/react-query": "^5.62.16", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "tailwind-merge": "^2.6.0" + "gsap": "^3.14.2", + "motion": "^12.34.2", + "next": "14.2.21", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-icons": "^5.5.0", + "tailwind-merge": "^2.6.0", + "viem": "^2.21.54", + "wagmi": "^2.14.11" }, "devDependencies": { - "typescript": "^5.7.3", - "tailwindcss": "^3.4.17", + "@types/node": "^20.17.12", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", - "@types/node": "^20.17.12", "autoprefixer": "^10.4.20", - "postcss": "^8.4.49" + "ogl": "^1.0.11", + "postcss": "^8.4.49", + "tailwindcss": "3", + "typescript": "^5.7.3" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 5c0210a..33ad091 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -1,9 +1,6 @@ -/** @type {import('postcss-load-config').Config} */ -const config = { +module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, -}; - -module.exports = config; +} diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 01be3ce..b2695ab 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -1,8 +1,272 @@ +"use client" + +import { useState, useEffect } from "react" +import { Space_Mono, DM_Sans } from "next/font/google" +import { useAccount } from "wagmi" +import GlareHover from "@/components/GlareHover" + +const spaceMono = Space_Mono({ subsets: ["latin"], weight: ["400", "700"] }) +const dmSans = DM_Sans({ subsets: ["latin"] }) + +function shortenAddress(address: string): string { + return `${address.slice(0, 4)}****${address.slice(-4)}` +} + +const AGENTS = [ + { name: "Portfolio Analyzer", status: "active" as const, nftId: "#0042", chain: "0G Chain", lastRun: "2 min ago", earnings: "0.012 ADI" }, + { name: "Yield Optimizer", status: "active" as const, nftId: "#0087", chain: "0G Chain", lastRun: "5 min ago", earnings: "0.008 ADI" }, + { name: "Risk Scorer", status: "idle" as const, nftId: "#0103", chain: "0G Chain", lastRun: "1 hr ago", earnings: "0.011 ADI" }, +] + +const FEED_LINES = [ + { time: "14:32", agent: "Portfolio Analyzer", msg: "Scanning wallet 0x3f...a2 \u2713", ok: true }, + { time: "14:31", agent: "Risk Scorer", msg: "ETH exposure: HIGH \u2014 score 7.2/10", ok: false }, + { time: "14:30", agent: "Yield Optimizer", msg: "Found: Aave v3 USDC pool \u2014 APY 12.4%", ok: true }, + { time: "14:28", agent: "Portfolio Analyzer", msg: "Rebalance recommendation generated", ok: true }, + { time: "14:25", agent: "ADI Chain", msg: "Payment 0.01 ADI settled \u2713", ok: true }, + { time: "14:22", agent: "0G Chain", msg: "iNFT #0042 state updated \u2713", ok: true }, + { time: "14:20", agent: "Hedera", msg: "Agent registered via HCS-10 \u2713", ok: true }, +] + +const STATS = [ + { label: "Total ADI Earned", value: "0.031 ADI" }, + { label: "Queries Executed", value: "47" }, + { label: "Avg Response Time", value: "3.2s" }, + { label: "iNFTs Owned", value: "3" }, +] + +const ALLOCATIONS = [ + { asset: "ETH", pct: 40 }, + { asset: "BTC", pct: 30 }, + { asset: "USDC", pct: 20 }, + { asset: "Other", pct: 10 }, +] + +const QUICK_ACTIONS = [ + { title: "Hire New Agent", desc: "Browse the marketplace", cta: "Explore \u2192", href: "/marketplace" }, + { title: "Transfer iNFT", desc: "Send agent to new owner", cta: "Transfer \u2192", href: "#" }, + { title: "View on 0G Chain", desc: "Inspect on-chain state", cta: "Explorer \u2192", href: "#" }, +] + export default function DashboardPage() { + const { address, isConnected } = useAccount() + const [visibleFeed, setVisibleFeed] = useState(0) + + useEffect(() => { + if (visibleFeed >= FEED_LINES.length) return + const timeout = setTimeout(() => { + setVisibleFeed(prev => prev + 1) + }, 350) + return () => clearTimeout(timeout) + }, [visibleFeed]) + + const cardStyle = { + background: "#241A0E", + border: "1px solid #3D2E1A", + borderRadius: 12, + padding: 20, + } + return ( -
-

DeFAI Dashboard

- {/* TODO: Portfolio charts and agent performance metrics */} +
+ + {/* ── Header ── */} +
+
+
+

+ Agents Dashboard +

+

+ Manage your autonomous AI agents +

+
+ {isConnected && address && ( +
+ {shortenAddress(address)} +
+ )} +
+
+ + {/* ── Section 1: Agent Roster ── */} +
+ {AGENTS.map((agent) => ( +
+ {/* Name + Status */} +
+ + {agent.name} + + + ● {agent.status === "active" ? "ACTIVE" : "IDLE"} + +
+ + {/* Details */} +
+
+ iNFT + {agent.nftId} +
+
+ Chain + {agent.chain} +
+
+ Last run + {agent.lastRun} +
+
+ Earned + {agent.earnings} +
+
+ + {/* Run Agent button */} + + + Run Agent + + +
+ ))} +
+ + {/* ── Section 2: Live Feed + Stats ── */} +
+ + {/* LEFT — Live Activity Feed */} +
+
+ Live Activity +
+
+ {FEED_LINES.slice(0, visibleFeed).map((line, i) => ( +
+ {line.time} + [{line.agent}] + {line.msg} +
+ ))} + {visibleFeed < FEED_LINES.length && ( +
+ +
+ )} +
+
+ + {/* RIGHT — Performance Stats + Allocation */} +
+
+ Performance +
+ + {/* Stat rows */} +
+ {STATS.map((stat) => ( +
+ {stat.label} + {stat.value} +
+ ))} +
+ + {/* Allocation bars */} +
+ ALLOCATION +
+
+ {ALLOCATIONS.map((item) => ( +
+
+ {item.asset} + {item.pct}% +
+
+
+
+
+ ))} +
+
+
+ + {/* ── Section 3: Quick Actions ── */} +
+ {QUICK_ACTIONS.map((action) => ( + + +
+ {action.title} +
+
+ {action.desc} +
+
+ {action.cta} +
+
+
+ ))} +
- ); + ) } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index b5c61c9..e94726f 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,3 +1,17 @@ +/* DO NOT MODIFY THIS FILE */ @tailwind base; @tailwind components; @tailwind utilities; + +.curved-text-gold { + fill: #C9A84C; + font-size: 18px; + font-family: monospace; + letter-spacing: 0.08em; + opacity: 0.8; +} + +@keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index cb36dae..10eaf22 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,29 +1,44 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import { Providers } from "@/components/Providers"; -import Navbar from "@/components/Navbar"; -import "./globals.css"; +import type { Metadata } from "next" +import "./globals.css" +import dynamic from "next/dynamic" +import { Providers } from "@/components/Providers" +import { SidebarProvider } from "@/components/ui/sidebar" -const inter = Inter({ subsets: ["latin"] }); +const AppSidebar = dynamic(() => import("@/components/AppSidebar"), { ssr: false }) +const TopBar = dynamic(() => import("@/components/TopBar"), { ssr: false }) +const DotGrid = dynamic(() => import("@/components/DotGrid"), { ssr: false }) export const metadata: Metadata = { title: "AgentFi", - description: "The banking system for autonomous AI agents", -}; + description: "The Banking System for Autonomous AI Agents", +} -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - - - - {children} - + + +
+ + + + +
+ {children} +
+
+
+
- ); + ) } diff --git a/frontend/src/app/marketplace/page.tsx b/frontend/src/app/marketplace/page.tsx index a596c8e..9abb457 100644 --- a/frontend/src/app/marketplace/page.tsx +++ b/frontend/src/app/marketplace/page.tsx @@ -1,62 +1,261 @@ -import AgentCard from "@/components/AgentCard"; - -const MOCK_AGENTS = [ - { - id: 1, - name: "Portfolio Analyzer", - description: - "Analyzes your DeFi portfolio composition, identifies concentration risk, and provides a detailed breakdown of your allocations.", - pricePerHire: "0.01", - capabilities: [ - "Portfolio Analysis", - "Allocation Breakdown", - "Risk Detection", - ], - category: "portfolio" as const, - }, - { - id: 2, - name: "Yield Optimizer", - description: - "Scans DeFi protocols to find the highest risk-adjusted yields based on your risk profile and investment horizon.", - pricePerHire: "0.015", - capabilities: [ - "Yield Scanning", - "APY Comparison", - "Protocol Risk Rating", - ], - category: "yield" as const, - }, - { - id: 3, - name: "Risk Scorer", - description: - "Scores any token or portfolio on a 1-10 risk scale using on-chain data, liquidity depth, and volatility metrics.", - pricePerHire: "0.008", - capabilities: ["Risk Scoring", "Volatility Analysis", "Liquidity Check"], - category: "risk" as const, - }, -]; +"use client" + +import { useState } from "react" +import { Space_Mono, DM_Sans } from "next/font/google" +import PixelTransition from "@/components/PixelTransition" + +const spaceMono = Space_Mono({ subsets: ["latin"], weight: ["400", "700"] }) +const dmSans = DM_Sans({ subsets: ["latin"] }) + +type Category = "All" | "DeFi" | "Risk" | "Yield" + +const CATEGORIES: Category[] = ["All", "DeFi", "Risk", "Yield"] + +const CATEGORY_COLORS: Record = { + DeFi: "rgba(201,168,76,0.1)", + Yield: "rgba(122,158,110,0.1)", + Risk: "rgba(196,122,90,0.1)", +} +const CATEGORY_TEXT: Record = { + DeFi: "#C9A84C", + Yield: "#7A9E6E", + Risk: "#C47A5A", +} + +const AGENTS = [ + { name: "Portfolio Analyzer", category: "DeFi" as const, desc: "Analyzes wallet composition and generates rebalancing recommendations", price: "0.01", rating: "4.9", queries: "1,247" }, + { name: "Yield Optimizer", category: "Yield" as const, desc: "Scans 50+ protocols to find highest APY opportunities for your assets", price: "0.008", rating: "4.8", queries: "892" }, + { name: "Risk Scorer", category: "Risk" as const, desc: "Real-time risk assessment of your DeFi positions with actionable alerts", price: "0.005", rating: "4.7", queries: "2,103" }, + { name: "Cross-Chain Arbitrage", category: "DeFi" as const, desc: "Identifies arbitrage opportunities across 0G, ADI, and EVM chains", price: "0.015", rating: "4.6", queries: "445" }, + { name: "Liquidity Manager", category: "Yield" as const, desc: "Manages LP positions automatically to maximize fee revenue", price: "0.012", rating: "4.5", queries: "334" }, + { name: "Compliance Monitor", category: "Risk" as const, desc: "Monitors transactions for FATF Travel Rule compliance on ADI Chain", price: "0.006", rating: "4.8", queries: "678" }, +] + +const BADGES = ["ERC-7857 Standard", "0G Chain", "Transferable"] export default function MarketplacePage() { + const [activeFilter, setActiveFilter] = useState("All") + const [search, setSearch] = useState("") + + const filtered = AGENTS.filter(a => { + const matchCat = activeFilter === "All" || a.category === activeFilter + const matchSearch = a.name.toLowerCase().includes(search.toLowerCase()) + return matchCat && matchSearch + }) + + const cardStyle = { + background: "#241A0E", + border: "1px solid #3D2E1A", + borderRadius: 12, + padding: 24, + transition: "border-color 0.2s", + } + + const hireButtonStyle = { + position: "absolute" as const, + inset: 0, + display: "flex", + alignItems: "center", + justifyContent: "center", + fontFamily: "monospace", + fontSize: 11, + fontWeight: "bold" as const, + letterSpacing: "0.08em", + borderRadius: 8, + cursor: "pointer", + } + return ( -
-
-
-

- Agent Marketplace -

-

- Hire specialized AI agents. Pay in ADI. Own them as iNFTs on 0G - Chain. -

+
+ + {/* ── Header ── */} +
+

+ Agent Marketplace +

+

+ Hire specialized AI agents. Pay with ADI. Own as iNFT. +

+
+ + {/* ── Section 1: Search & Filter bar ── */} +
+ setSearch(e.target.value)} + className={spaceMono.className} + style={{ + background: "#1A1208", + border: "1px solid #3D2E1A", + color: "#F5ECD7", + borderRadius: 8, + padding: "10px 16px", + width: 300, + fontSize: 13, + outline: "none", + }} + /> +
+ {CATEGORIES.map(cat => ( + + ))} +
+
+ + {/* ── Section 2: Agent Grid ── */} +
+ {filtered.map(agent => ( +
(e.currentTarget.style.borderColor = "#5C4422")} + onMouseOut={e => (e.currentTarget.style.borderColor = "#3D2E1A")} + > + {/* Top: name + category badge */} +
+ + {agent.name} + + + {agent.category} + +
+ + {/* Description */} +
+ {agent.desc} +
+ + {/* Stats row */} +
+ {"\u2605"} {agent.rating} + {agent.queries} queries +
+ + {/* Divider */} +
+ + {/* Bottom: price + Hire button */} +
+ + {agent.price} ADI + +
+ + Hire Agent {"\u2192"} +
+ } + secondContent={ +
+ Hire Agent {"\u2192"} +
+ } + /> +
+
+
+ ))} +
+ + {/* ── Section 3: Featured iNFT Banner ── */} +
+
+
+ Own Your Agent +
+
+ Every hired agent is minted as an iNFT on 0G Chain. Transfer it, sell it, or keep earning. +
-
- {MOCK_AGENTS.map((agent) => ( - +
+ {BADGES.map(badge => ( + + {badge} + ))}
-
- ); +
+ ) } diff --git a/frontend/src/app/my-agents/page.tsx b/frontend/src/app/my-agents/page.tsx index bcc1978..33b8fd0 100644 --- a/frontend/src/app/my-agents/page.tsx +++ b/frontend/src/app/my-agents/page.tsx @@ -1,8 +1,180 @@ +"use client" +import { useState } from "react" +import GlareHover from "@/components/GlareHover" +import PixelTransition from "@/components/PixelTransition" +import AnimatedContent from "@/components/AnimatedContent" + +const AGENTS = [ + { id: "#0042", name: "Portfolio Analyzer", model: "gpt-4o-mini", capabilities: "DeFi Analysis", minted: "Feb 18, 2026", queries: "47", earned: "0.012 ADI", chain: "0G Chain", status: "ACTIVE" }, + { id: "#0043", name: "Yield Optimizer", model: "gpt-4o-mini", capabilities: "Yield Farming", minted: "Feb 18, 2026", queries: "31", earned: "0.009 ADI", chain: "0G Chain", status: "ACTIVE" }, + { id: "#0044", name: "Risk Scorer", model: "gpt-4o-mini", capabilities: "Risk Assessment", minted: "Feb 18, 2026", queries: "89", earned: "0.010 ADI", chain: "0G Chain", status: "IDLE" }, +] + export default function MyAgentsPage() { + const [active, setActive] = useState(0) + const [direction, setDirection] = useState<'left' | 'right'>('right') + const [key, setKey] = useState(0) + + const prev = () => { + setDirection('left') + setActive(i => (i - 1 + AGENTS.length) % AGENTS.length) + setKey(k => k + 1) + } + + const next = () => { + setDirection('right') + setActive(i => (i + 1) % AGENTS.length) + setKey(k => k + 1) + } + + const agent = AGENTS[active] + return ( -
-

My Agents

- {/* TODO: Display owned iNFTs from 0G Chain */} -
- ); +
+ + {/* Title */} +
+

My Agents

+

Your iNFT collection on 0G Chain

+
+ + {/* Wallet Summary */} +
+ {[ + { label: "3 iNFTs Owned", color: "#F5ECD7" }, + { label: "0.031 ADI Earned Total", color: "#C9A84C" }, + { label: "0G Chain \u00B7 Testnet", color: "#9A8060" }, + ].map((stat, i) => ( + + {stat.label} + + ))} +
+ + {/* iNFT Carousel */} +
+
+ + {/* Prev arrow */} + + + {/* Active card — large center */} + +
+ {/* Gold top bar */} +
+ + {/* Top row */} +
+
+ {agent.id} +
+ + {"\u25CF"} {agent.status} + +
+ + {/* Name */} +

{agent.name}

+

ERC-7857 {"\u00B7"} {agent.chain}

+ + {/* Divider */} +
+ + {/* Metadata 2-col grid */} +
+ {[ + { label: "MODEL", value: agent.model }, + { label: "CAPABILITIES", value: agent.capabilities }, + { label: "MINTED", value: agent.minted }, + { label: "CHAIN", value: agent.chain }, + { label: "QUERIES RUN", value: agent.queries }, + { label: "ADI EARNED", value: agent.earned }, + ].map(f => ( +
+

{f.label}

+

{f.value}

+
+ ))} +
+ + {/* Action buttons */} +
+ Execute
} + secondContent={
Execute
} + /> + + Transfer {"\u2192"} + +
+
+ + + {/* Next arrow */} + +
+ + {/* Dot indicators */} +
+ {AGENTS.map((_, i) => ( +
+
+ + {/* Activity timeline */} +
+

iNFT Activity

+
+
+ {[ + { time: "14:32", title: "iNFT #0044 executed", detail: "Risk score computed: 7.2/10" }, + { time: "14:28", title: "Payment settled", detail: "0.005 ADI received from 0x8b...c3" }, + { time: "14:15", title: "iNFT #0043 executed", detail: "APY 12.4% found on Aave v3" }, + { time: "13:50", title: "iNFT #0042 executed", detail: "Rebalance recommendation generated" }, + { time: "13:20", title: "iNFT #0044 registered", detail: "Agent registered on Hedera via HCS-10" }, + { time: "12:00", title: "Collection minted", detail: "3 iNFTs minted on 0G Chain \u2713" }, + ].map((event, i) => ( +
+
+ {event.time} +

{event.title}

+

{event.detail}

+
+ ))} +
+
+ + {/* CTA Banner */} +
+
+

Expand your fleet

+

Add specialized agents to your iNFT collection

+
+ + Browse Marketplace {"\u2192"} + +
+ +
+ ) } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index cdd0d7b..10ad184 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,104 +1,111 @@ -import Link from "next/link"; -import { Space_Mono, DM_Sans } from "next/font/google"; +"use client" -const spaceMono = Space_Mono({ subsets: ["latin"], weight: ["400", "700"] }); -const dmSans = DM_Sans({ subsets: ["latin"] }); +import { useState, useEffect } from "react" +import dynamic from "next/dynamic" +import { Space_Mono, DM_Sans } from "next/font/google" +import PixelTransition from "@/components/PixelTransition" +import GlareHover from "@/components/GlareHover" +import { Button } from "@/components/ui/button" -const TERMINAL_TEXT = `AgentFi v1.0.0 -Connecting to ADI Chain... ✓ -Loading agent registry... ✓ -Agent: Portfolio Analyzer -Query: "Analyze my DeFi positions" -Running on Hedera via Agent Kit... -───────────────────────────── -RESULT: High ETH concentration -Risk Score: 7.2/10 -Recommendation: Rebalance → -yield_optimizer executing... -APY found: 12.4% (Aave v3) -───────────────────────────── -Payment: 0.01 ADI ✓ settled -iNFT #0042 minted on 0G ✓`; +const LogoCarousel = dynamic(() => import("@/components/LogoCarousel"), { ssr: false }) +const CurvedLoop = dynamic(() => import("@/components/CurvedLoop"), { ssr: false }) -const AGENTS = [ - { - name: "Portfolio Analyzer", - description: "Analyzes DeFi portfolio composition and allocation risk", - price: "0.01", - capabilities: ["Portfolio Analysis", "Risk Detection", "Allocation Breakdown"], - category: "portfolio" as const, - }, - { - name: "Yield Optimizer", - description: "Finds the highest risk-adjusted yields across DeFi protocols", - price: "0.015", - capabilities: ["Yield Scanning", "APY Comparison", "Protocol Rating"], - category: "yield" as const, - }, - { - name: "Risk Scorer", - description: "Scores any token or portfolio on a 1-10 risk scale", - price: "0.008", - capabilities: ["Risk Scoring", "Volatility Analysis", "Liquidity Check"], - category: "risk" as const, - }, -]; +const spaceMono = Space_Mono({ subsets: ["latin"], weight: ["400", "700"] }) +const dmSans = DM_Sans({ subsets: ["latin"] }) -const CATEGORY_STYLES = { - portfolio: "bg-cyan-500/10 text-cyan-400 border-cyan-500/30", - yield: "bg-green-500/10 text-green-400 border-green-500/30", - risk: "bg-amber-500/10 text-amber-400 border-amber-500/30", -}; +const LINES = [ + { text: "AgentFi v1.0.0", color: "#C9A84C" }, + { text: "Connecting to ADI Chain... ✓", color: "#7A9E6E" }, + { text: "Loading agent registry... ✓", color: "#7A9E6E" }, + { text: 'Agent: Portfolio Analyzer', color: "#F5ECD7" }, + { text: 'Query: "Analyze my DeFi positions"', color: "#9A8060" }, + { text: "Running on Hedera via Agent Kit...", color: "#9A8060" }, + { text: "─────────────────────", color: "#3D2E1A" }, + { text: "RESULT: High ETH concentration", color: "#F5ECD7" }, + { text: "Risk Score: 7.2/10", color: "#C47A5A" }, + { text: "Recommendation: Rebalance →", color: "#F5ECD7" }, + { text: "yield_optimizer executing...", color: "#9A8060" }, + { text: "APY found: 12.4% (Aave v3)", color: "#7A9E6E" }, + { text: "─────────────────────", color: "#3D2E1A" }, + { text: "Payment: 0.01 ADI ✓ settled", color: "#7A9E6E" }, + { text: "iNFT #0042 minted on 0G ✓", color: "#C9A84C" }, +] export default function HomePage() { - const charCount = TERMINAL_TEXT.length; + const [displayedLines, setDisplayedLines] = useState<{ text: string; color: string }[]>([]) + const [currentLine, setCurrentLine] = useState(0) + const [currentChar, setCurrentChar] = useState(0) + const [currentText, setCurrentText] = useState("") + + useEffect(() => { + if (currentLine >= LINES.length) return + + const line = LINES[currentLine] + + if (currentChar < line.text.length) { + const timeout = setTimeout(() => { + setCurrentText(prev => prev + line.text[currentChar]) + setCurrentChar(prev => prev + 1) + }, 28) + return () => clearTimeout(timeout) + } else { + const timeout = setTimeout(() => { + setDisplayedLines(prev => [...prev, { text: line.text, color: line.color }]) + setCurrentText("") + setCurrentChar(0) + setCurrentLine(prev => prev + 1) + }, 80) + return () => clearTimeout(timeout) + } + }, [currentLine, currentChar]) return ( <>