diff --git a/HLD_DIAGRAM.md b/HLD_DIAGRAM.md new file mode 100644 index 0000000..45ae996 --- /dev/null +++ b/HLD_DIAGRAM.md @@ -0,0 +1,105 @@ +```mermaid +graph TB + %% --- User Layer --- + User((๐Ÿ‘ค User)) + + %% --- Frontend System --- + subgraph Frontend ["๐Ÿ–ฅ๏ธ Agentropolis Frontend (Next.js 14 + Phaser 3)"] + direction TB + Landing[Landing Page] + Game[Game Canvas] + + subgraph GameScenes ["๐ŸŽฎ Game Scenes"] + City[City View (Isometric)] + Council[Council Room (Roundtable)] + end + + subgraph CoreComponents ["๐Ÿงฉ Core Components"] + Session[Session Provider] + Wallet[Wallet Provider] + Swap[Swap Handler] + end + + subgraph IntegrationLibs ["๐Ÿ“š Integration Libraries"] + YellowLib[Yellow (State Channels)] + ENS_Lib[ENS (Identity)] + ERC8004_Lib[ERC-8004 (Registry)] + x402_Lib[x402 (Micropayments)] + UniExecutor[Uniswap Executor] + ClankerLib[Clanker SDK] + end + end + + %% --- Backend / AI Layer --- + subgraph AI_Layer ["๐Ÿง  AI & Backend Layer"] + Groq[Groq LLM API] + + subgraph Agents ["๐Ÿค– AI Council Agents"] + Alpha[๐ŸŽฏ Alpha Hunter] + Risk[๐Ÿ›ก๏ธ Risk Sentinel] + Macro[๐Ÿ”ฎ Macro Oracle] + Devil[๐Ÿ˜ˆ Devil's Advocate] + Clerk[๐Ÿ“‹ Council Clerk] + end + + ExternalAgent[๐ŸŒ External Agent (BYOA)] + end + + %% --- Blockchain Layer --- + subgraph Blockchain ["โ›“๏ธ Base Sepolia & On-Chain"] + subgraph Registries ["Infrastructure"] + Registry[ERC-8004 Registry] + ENSReg[ENS Registry] + end + + subgraph Execution ["DeFi Execution"] + UniversalRouter[Uniswap Universal Router] + PoolManager[Pool Manager] + end + + subgraph Hooks ["๐Ÿช Custom Uniswap Hooks"] + FeeHook[CouncilFeeHook] + SentinelHook[SentimentOracleHook] + GuardHook[SwapGuardHook] + end + end + + %% --- Connections --- + User -->|Connects| Wallet + User -->|Interacts| Game + + Game --> City + City -->|Click Council| Council + + %% Deploy Flow + City -->|Query Agents| ERC8004_Lib + ERC8004_Lib -->|Read| Registry + City -->|Micropayment| YellowLib + + %% Deliberation Flow + Council -->|Prompt| Agents + Agents -->|Inference| Groq + Agents -->|Consult| ExternalAgent + ExternalAgent -.->|Payment| x402_Lib + + %% Execution Flow + Agents -->|Consensus Proposal| Clerk + Clerk -->|Synthesized Plan| Swap + Swap -->|Execute| UniExecutor + UniExecutor -->|V4_SWAP| UniversalRouter + UniversalRouter --> PoolManager + PoolManager --> FeeHook + PoolManager --> SentinelHook + PoolManager --> GuardHook + + %% Styling + classDef frontend fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef backend fill:#f3e5f5,stroke:#4a148c,stroke-width:2px; + classDef blockchain fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px; + classDef user fill:#fff9c4,stroke:#fbc02d,stroke-width:2px; + + class Frontend,GameScenes,CoreComponents,IntegrationLibs frontend; + class AI_Layer,Agents,Groq,ExternalAgent backend; + class Blockchain,Registries,Execution,Hooks blockchain; + class User user; +``` diff --git a/SIMPLE_EXPLANATION.md b/SIMPLE_EXPLANATION.md new file mode 100644 index 0000000..7351b4b --- /dev/null +++ b/SIMPLE_EXPLANATION.md @@ -0,0 +1,9 @@ +# What Problem Does Agentropolis Solve? + +The problem with Decentralized Finance (DeFi) today is that itโ€™s overwhelmingly complex and lonely. Imagine trying to make investment decisions while staring at confusing charts, cryptic codes, and endless spreadsheets, knowing a single wrong click could cost you money. It feels like piloting a spaceship without a manual. Most people want to participate but are scared off by the technical jargon and lack of guidance. They don't have a team of financial experts to double-check their logic or explain risks in plain English. + +# How Agentropolis Works (The "SimCity for Finance" Solution) + +Agentropolis transforms this intimidating experience into a friendly, city-building game. Instead of navigating confusing menus, you simply **build a digital city and hire a team of AI assistants** who live there. Think of them as your personal financial council. You walk your character into a "Council Room" where cute, animated characters (like a cautious "Risk Manager" or an ambitious "Profit Hunter") are sitting around a table. You just ask them a simple question like, *"How should I invest $100 safely?"* + +The magic happens next: The AI agents **debate your question in real-time**. You watch them discuss the pros and consโ€”your Risk Manager might say, *"Wait, the market is too volatile right now!"* while the Profit Hunter argues, *"But the potential returns are high!"* They simplify complex data into a clear conversation you can understand. Once they agree on a plan, they present it to you. You simply click **"Approve,"** and the game handles all the complicated technical steps securely in the background. It turns finance from a solitary, stressful task into a collaborative, gamified experience where you always have a team of experts by your side. diff --git a/TECHNICAL_WALKTHROUGH.md b/TECHNICAL_WALKTHROUGH.md new file mode 100644 index 0000000..066b784 --- /dev/null +++ b/TECHNICAL_WALKTHROUGH.md @@ -0,0 +1,307 @@ +# Agentropolis โ€” Complete Technical Deep-Dive + +> **"SimCity meets DeFi."** โ€” A gamified DeFi trading platform built for HackMoney 2026 where AI agents collaborate in a virtual city, debate trading strategies in a council room, and execute real on-chain swaps. + +--- + +## What It Does (The Core Loop) + +```mermaid +flowchart LR + A["๐Ÿ”— Connect Wallet"] --> B["โšก Start Yellow Session"] + B --> C["๐Ÿค– Deploy Agents from ERC-8004 Registry"] + C --> D["๐Ÿ›๏ธ Enter Council Room"] + D --> E["๐Ÿง  AI Agents Deliberate via Groq LLM"] + E --> F["๐Ÿ“œ User Reviews Proposal"] + F -->|Approve| G["๐Ÿฆ„ Execute Uniswap v4 Swap On-Chain"] + F -->|Reject| D + G --> H["๐Ÿ”š End Session โ†’ Settlement"] +``` + +The user interacts with a **cyberpunk isometric city** rendered via Phaser 3. They deploy AI agents (discovered from an on-chain ERC-8004 registry), watch them debate in a council room, and approve/reject trade proposals that execute **real Uniswap v4 swaps on Base Sepolia**. + +--- + +## Tech Stack + +| Layer | Technology | Purpose | +|-------|-----------|---------| +| **Frontend** | Next.js 14 (App Router), React 18 | Core web framework | +| **Game Engine** | Phaser 3 + Three.js + React Three Fiber | Isometric city & 3D council view | +| **Styling** | Tailwind CSS | UI components | +| **Web3** | wagmi v3, viem v2 | Wallet connection, contract interactions | +| **AI** | Groq SDK โ†’ `llama-3.3-70b-versatile` | Multi-agent deliberation | +| **State Channels** | Yellow Network (`@erc7824/nitrolite`) | Off-chain micropayments | +| **DEX** | Uniswap v4 Universal Router | On-chain swaps | +| **Token Launch** | Clanker SDK v4 | Memecoin deployment with v4 hooks | +| **Identity** | ENS (Ethereum Name Service) | Name resolution, avatar, config storage | +| **Micropayments** | x402 Protocol | HTTP-level payments for external agents | +| **Smart Contracts** | Solidity 0.8.26, Foundry | Uniswap v4 custom hooks | +| **Monorepo** | Turborepo + Bun | Build orchestration | +| **Chain** | Base Sepolia (testnet), Ethereum Sepolia (ENS) | Deployment target | + +--- + +## Architecture + +```mermaid +graph TB + subgraph Frontend["Frontend (Next.js + Phaser/Three.js)"] + LP["Landing Page"] + CV["CityView3D โ€” Isometric City"] + CR["CouncilRoom3D โ€” Roundtable Deliberation"] + SH["SwapHandler โ€” Event Listener"] + SP["SessionProvider โ€” Yellow Channel"] + UI["UserIdentity โ€” ENS Display"] + AS["AgentSettings โ€” BYOA Config"] + end + + subgraph API["API Routes (Next.js)"] + AL["/api/agents/list"] + AC["/api/agents/council"] + AP["/api/agents/propose"] + AT["/api/agents/launch-token"] + end + + subgraph Integrations["Integration Libraries"] + YL["Yellow Network โ€” State Channels"] + UNI["Uniswap v4 โ€” Swap Executor"] + ENS["ENS โ€” Identity + Text Records"] + ERC["ERC-8004 โ€” Agent Discovery"] + X4["x402 โ€” HTTP Micropayments"] + CL["Clanker โ€” Token Launches"] + end + + subgraph Contracts["Solidity Hooks (Base Sepolia)"] + CFH["CouncilFeeHook โ€” Dynamic Fees"] + SOH["SentimentOracleHook โ€” Market Oracle"] + SGH["SwapGuardHook โ€” Risk Limits"] + end + + CV --> CR + CR -->|"User Prompt"| AC + AC -->|"Groq LLM"| AP + AP -->|"Proposal"| SH + SH -->|"Execute"| UNI + UNI --> Contracts + SP --> YL + AL --> ERC + AT --> CL + AS -->|"BYOA Endpoint"| X4 + UI --> ENS +``` + +--- + +## Monorepo Structure + +``` +agentropolis/ +โ”œโ”€โ”€ apps/ +โ”‚ โ”œโ”€โ”€ web/ # Main Next.js 14 application +โ”‚ โ”‚ โ”œโ”€โ”€ app/ # Pages (landing, app, docs) +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # Landing page +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ app/page.tsx # Main game view +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ api/agents/ # 4 API routes +โ”‚ โ”‚ โ”œโ”€โ”€ components/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ game/ # Phaser + Three.js game views +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CityView3D.tsx # Isometric city (Three.js) +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CouncilRoom3D.tsx # Council deliberation (Three.js) +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ GameComponent.tsx # Phaser wrapper +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ scenes/ # Phaser scenes (City, Council) +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ SessionProvider.tsx # Yellow session management +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ SwapHandler.tsx # Uniswap execution bridge +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ UserIdentity.tsx # ENS name + avatar +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ AgentSettings.tsx # BYOA endpoint config +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ PlaceOrderModal.tsx # Order placement UI +โ”‚ โ”‚ โ””โ”€โ”€ lib/ +โ”‚ โ”‚ โ”œโ”€โ”€ uniswap/ # Swap + LP execution +โ”‚ โ”‚ โ”œโ”€โ”€ yellow/ # State channel management +โ”‚ โ”‚ โ”œโ”€โ”€ agents/ # AI council + strategies +โ”‚ โ”‚ โ”œโ”€โ”€ ens/ # ENS resolution + text records +โ”‚ โ”‚ โ”œโ”€โ”€ erc8004/ # On-chain agent registry +โ”‚ โ”‚ โ”œโ”€โ”€ clanker/ # Token launch via Clanker SDK +โ”‚ โ”‚ โ””โ”€โ”€ x402/ # HTTP micropayment wrapper +โ”‚ โ”œโ”€โ”€ new/ # Alternate frontend (WIP) +โ”‚ โ””โ”€โ”€ web-3d/ # Standalone 3D experiment +โ”œโ”€โ”€ packages/ +โ”‚ โ”œโ”€โ”€ contracts/ # Foundry project +โ”‚ โ”‚ โ””โ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ CouncilFeeHook.sol # Dynamic fee hook +โ”‚ โ”‚ โ”œโ”€โ”€ SentimentOracleHook.sol # Market sentiment oracle +โ”‚ โ”‚ โ””โ”€โ”€ SwapGuardHook.sol # Swap size limiter +โ”‚ โ””โ”€โ”€ shared/ # Shared TypeScript types +โ”‚ โ””โ”€โ”€ src/types.ts # All domain interfaces +โ”œโ”€โ”€ turbo.json # Turborepo config +โ””โ”€โ”€ package.json # Root workspace config +``` + +--- + +## Deep-Dive: Key Subsystems + +### 1. ๐Ÿง  AI Council System โ€” [council.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/agents/council.ts) + +The **core differentiator** โ€” a multi-agent deliberation engine using **Groq LLM** (`llama-3.3-70b-versatile`). + +**5 AI Agent Personas:** + +| Agent | Role | Behavior | +|-------|------|----------| +| ๐ŸŽฏ Alpha Hunter | Seeks yield | Optimistic, supports high APY opportunities | +| ๐Ÿ›ก๏ธ Risk Sentinel | Risk assessor | Conservative, **can VETO** dangerous proposals | +| ๐Ÿ”ฎ Macro Oracle | Market context | Neutral, provides sentiment and timing analysis | +| ๐Ÿ˜ˆ Devil's Advocate | Challenges | Skeptical, presents worst-case scenarios | +| ๐Ÿ“‹ Council Clerk | Synthesizer | Creates the final proposal from all opinions | + +**Flow:** +1. User enters a prompt (e.g., "I want passive income from my 0.1 ETH") +2. Each agent receives the prompt + previous agents' opinions (sequential context passing) +3. Each outputs structured JSON: `{ opinion, reasoning, confidence, suggestedStrategy }` +4. Clerk synthesizes into a `TradeProposal` with: strategy type, token pair, amounts, risk level +5. Consensus is computed: `unanimous | majority | contested | vetoed` + +**Strategy Types:** `swap`, `dca`, `lp_full_range`, `lp_concentrated`, `token_launch` + +**BYOA (Bring Your Own Agent):** Users can plug in external AI agent endpoints. Calls are made via x402 micropayments with SSRF protection and 10s timeout. + +--- + +### 2. ๐Ÿฆ„ Uniswap v4 Executor โ€” [executor.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/uniswap/executor.ts) + +Executes **real on-chain swaps** on Base Sepolia via the Universal Router. + +**Key technical details:** +- Uses `V4_SWAP` command (`0x10`) with the Universal Router ABI +- Encodes V4 actions: `SWAP_EXACT_IN_SINGLE` โ†’ `SETTLE_ALL` โ†’ `TAKE_ALL` +- Handles ERC-20 approvals, balance checks, gas simulation +- 3% default slippage protection +- Pre-flight simulation via `eth_call` before sending transactions +- Returns `SwapReceipt` with realized amounts and slippage vs. quote + +**Contract Addresses (Base Sepolia):** +- Universal Router: `0x492E6456D9528771018DeB9E87ef7750EF184104` +- Pool Manager: `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408` +- WETH: `0x4200000000000000000000000000000000000006` +- USDC: `0x036CbD53842c5426634e7929541eC2318f3dCF7e` + +--- + +### 3. โšก Yellow Network Integration โ€” [channel.tsx](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/yellow/channel.tsx) + +Implements **state channel sessions** for gasless, instant micro-actions using `@erc7824/nitrolite`. + +**Lifecycle:** +1. **Deposit** โ†’ User deposits funds to Yellow's custody contract on-chain +2. **Create Channel** โ†’ Opens a state channel via WebSocket to ClearNode +3. **Off-chain Transfers** โ†’ Agent deployments are instant micropayments (no gas) +4. **Close Channel** โ†’ Settles all state deltas back on-chain + +Has both `createRealChannelManager` (production) and `createMockChannelManager` (demo mode). + +--- + +### 4. ๐Ÿ“› ENS Integration โ€” [textRecords.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/ens/textRecords.ts) + +- **Identity display**: Resolves ENS names and avatars for the connected wallet +- **Config persistence**: Saves/loads BYOA agent endpoint URL to ENS text records +- **Subnames**: Support for ENS subname management + +--- + +### 5. ๐Ÿค– ERC-8004 Agent Registry โ€” [client.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/erc8004/client.ts) + +Discovers AI agents from an **on-chain registry** on Base Sepolia: +- Queries `IdentityRegistry` contract for agent metadata URIs (supports IPFS) +- Queries `ReputationRegistry` for reputation scores (0-100) +- Links to 8004scan.io for block explorer viewing +- Falls back to mock agents if registry is empty + +--- + +### 6. ๐Ÿ’ฐ x402 Micropayments โ€” [client.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/x402/client.ts) + +HTTP-level micropayment protocol for paying external BYOA agents: +- Wraps native `fetch` with `@x402/fetch` for automatic payment negotiation +- Uses EVM `exact` scheme with the connected wallet's `signTypedData` +- Transparent to the calling code โ€” just use the wrapped fetch + +--- + +### 7. ๐Ÿš€ Clanker Token Launcher โ€” [client.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/apps/web/lib/clanker/client.ts) + +Launches memecoins with Uniswap v4 hooks via the Clanker SDK: +- Deploys ERC-20 token with metadata, reward recipients, and optional vault locking +- Integrated into the council flow โ€” agents can propose token launches +- Requires Base Mainnet (falls back to mock on testnet) + +--- + +## Smart Contracts (Uniswap v4 Hooks) + +Three custom **Uniswap v4 hooks** written in Solidity 0.8.26 using Foundry: + +### [CouncilFeeHook.sol](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/packages/contracts/src/CouncilFeeHook.sol) +- **Hook type:** `beforeSwap` +- **What it does:** AI council votes on swap fees; the hook dynamically overrides the LP fee using `LPFeeLibrary.OVERRIDE_FEE_FLAG` +- **Range:** 0.001% โ€“ 1% (10โ€“10,000 bps) + +### [SentimentOracleHook.sol](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/packages/contracts/src/SentimentOracleHook.sol) +- **Hook type:** `afterSwap` +- **What it does:** Records council's market sentiment (-100 to +100) as an on-chain oracle after each swap +- **API:** `getSentiment()` returns `(score, swapCount, lastUpdated, reason)` + +### [SwapGuardHook.sol](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/packages/contracts/src/SwapGuardHook.sol) +- **Hook type:** `beforeSwap` +- **What it does:** Risk Sentinel agent sets `maxSwapSize`; any swap exceeding it is **reverted** +- **Safety:** Minimum 0.01 ETH threshold to prevent lockout + +--- + +## Shared Type System โ€” [types.ts](file:///Users/utkarshraj/Documents/Summer_of_bitcoin/Agentropolis/packages/shared/src/types.ts) + +Key interfaces powering the entire flow: + +| Type | Purpose | +|------|---------| +| `TradeProposal` | Full swap proposal with pair, amounts, risk, deliberation, execution plan | +| `TokenLaunchProposal` | Memecoin launch proposal with name, symbol, vault % | +| `DeliberationResult` | Council messages + consensus + vote tally | +| `CouncilMessage` | Individual agent opinion with role, confidence, reasoning | +| `ExecutionPlan` | Pre-flight plan: quote + slippage + simulation result | +| `SwapReceipt` | Post-execution: tx hash, balances, realized slippage | +| `AgentProfile` | Agent identity from ERC-8004 with strategy & reputation | +| `YellowSession` | State channel session metadata | + +--- + +## Sponsor Integration Summary + +| Sponsor | Prize Track | What's Implemented | +|---------|------------|-------------------| +| **Yellow Network** | $15,000 | State channel sessions via Nitrolite, gasless agent deployment, on-chain settlement | +| **Uniswap Foundation** | $10,000 | V4_SWAP command encoding, Universal Router execution, 3 custom hooks, LP + concentrated liquidity | +| **ENS** | $5,000 | Reverse resolution, avatar display, text record storage for BYOA config, subnames | + +--- + +## How to Run + +```bash +# Install dependencies +bun install + +# Build shared package +cd packages/shared && bun run build && cd ../.. + +# Run dev server +cd apps/web && bun run dev +# โ†’ Open http://localhost:3000 +``` + +**Environment Variables** (`apps/web/.env.local`): +``` +GROQ_API_KEY=your_key # AI deliberation (or use GROQ_MOCK=true) +NEXT_PUBLIC_UNISWAP_MOCK=true # Mock swaps for demo +ERC8004_MOCK=true # Mock agent registry +``` diff --git a/apps/web/components/AppPageContent.tsx b/apps/web/components/AppPageContent.tsx index 7423c4a..6719e98 100644 --- a/apps/web/components/AppPageContent.tsx +++ b/apps/web/components/AppPageContent.tsx @@ -5,6 +5,7 @@ import dynamic from 'next/dynamic' import { ConnectButton } from '@/components/ConnectButton' import { SessionStatus } from '@/components/SessionProvider' import { SwapHandler } from '@/components/SwapHandler' +import { SessionEconomics } from '@/components/SessionEconomics' import { GuestMode } from '@/components/GuestMode' import { useGame } from '@/contexts/GameContext' @@ -55,7 +56,7 @@ export function AppPageContent() {
{/* Yellow Session Status */} - + {/* Live Status */}
Status @@ -148,6 +149,7 @@ export function AppPageContent() { {/* Functional Components */} +
) diff --git a/apps/web/components/LimitOrderManager.tsx b/apps/web/components/LimitOrderManager.tsx index 035a257..4182930 100644 --- a/apps/web/components/LimitOrderManager.tsx +++ b/apps/web/components/LimitOrderManager.tsx @@ -1,85 +1,85 @@ -'use client' +// 'use client' -import { useState, useEffect } from 'react' -import { PlaceOrderModal, type LimitOrder } from './PlaceOrderModal' -import { placeLimitOrder, claimLimitOrder } from '../lib/uniswap/limit-orders' -import { toast } from 'sonner' +// import { useState, useEffect } from 'react' +// import { PlaceOrderModal, type LimitOrder } from './PlaceOrderModal' +// import { placeLimitOrder, claimLimitOrder } from '../lib/uniswap/limit-orders' +// import { toast } from 'sonner' -export function LimitOrderManager() { - const [isPlaceModalOpen, setIsPlaceModalOpen] = useState(false) +// export function LimitOrderManager() { +// const [isPlaceModalOpen, setIsPlaceModalOpen] = useState(false) - useEffect(() =\u003e { - // Listen for game events - if(typeof window === 'undefined') return +// useEffect(() =\u003e { +// // Listen for game events +// if(typeof window === 'undefined') return - const handleClaim = async (event: Event) =\u003e { - const customEvent = event as CustomEvent - const orderId = customEvent.detail || (event as any).detail +// const handleClaim = async (event: Event) =\u003e { +// const customEvent = event as CustomEvent +// const orderId = customEvent.detail || (event as any).detail - console.log('[LimitOrderManager] Claiming order:', orderId) +// console.log('[LimitOrderManager] Claiming order:', orderId) - const result = await claimLimitOrder(orderId) +// const result = await claimLimitOrder(orderId) - if (result.success) { - toast.success('Order claimed successfully!', { - description: `TxID: ${result.txHash?.slice(0, 10)}...`, - }) - } else { - toast.error('Failed to claim order', { - description: result.error, - }) - } -} +// if (result.success) { +// toast.success('Order claimed successfully!', { +// description: `TxID: ${result.txHash?.slice(0, 10)}...`, +// }) +// } else { +// toast.error('Failed to claim order', { +// description: result.error, +// }) +// } +// } -const handleShowDetails = (event: Event) =\u003e { - const customEvent = event as CustomEvent - const order = customEvent.detail as LimitOrder +// const handleShowDetails = (event: Event) =\u003e { +// const customEvent = event as CustomEvent +// const order = customEvent.detail as LimitOrder - toast.info(`Order ${order.direction === 'buy' ? 'Buy' : 'Sell'} @ $${order.targetPrice}`, { - description: `Status: ${order.status}`, -}) - } +// toast.info(`Order ${order.direction === 'buy' ? 'Buy' : 'Sell'} @ $${order.targetPrice}`, { +// description: `Status: ${order.status}`, +// }) +// } -window.addEventListener('claimLimitOrder' as any, handleClaim) -window.addEventListener('showLimitOrderDetails' as any, handleShowDetails) +// window.addEventListener('claimLimitOrder' as any, handleClaim) +// window.addEventListener('showLimitOrderDetails' as any, handleShowDetails) -return () =\u003e { - window.removeEventListener('claimLimitOrder' as any, handleClaim) - window.removeEventListener('showLimitOrderDetails' as any, handleShowDetails) -} - }, []) +// return () =\u003e { +// window.removeEventListener('claimLimitOrder' as any, handleClaim) +// window.removeEventListener('showLimitOrderDetails' as any, handleShowDetails) +// } +// }, []) -const handlePlaceOrder = async (order: LimitOrder) =\u003e { - console.log('[LimitOrderManager] Placing order:', order) +// const handlePlaceOrder = async (order: LimitOrder) =\u003e { +// console.log('[LimitOrderManager] Placing order:', order) -const result = await placeLimitOrder(order) +// const result = await placeLimitOrder(order) -if (result.success) { - toast.success('Trade Tower constructed!', { - description: 'Your limit order is now active in the city.', - }) -} else { - toast.error('Failed to place order', { - description: result.error || 'Unknown error', - }) -} - } +// if (result.success) { +// toast.success('Trade Tower constructed!', { +// description: 'Your limit order is now active in the city.', +// }) +// } else { +// toast.error('Failed to place order', { +// description: result.error || 'Unknown error', +// }) +// } +// } -return ( -\u003c\u003e -{/* Button to open modal - can be placed in header or elsewhere */ } -\u003cbutton -onClick = {() =\u003e setIsPlaceModalOpen(true)} -className = "fixed bottom-6 right-6 z-40 px-6 py-3 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-full shadow-lg transition-all flex items-center gap-2" -\u003e - ๐Ÿ—๏ธ Place Limit Order -\u003c / button\u003e +// return ( +// \u003c\u003e +// {/* Button to open modal - can be placed in header or elsewhere */ } +// \u003cbutton +// onClick = {() =\u003e setIsPlaceModalOpen(true)} +// className = "fixed bottom-6 right-6 z-40 px-6 py-3 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-full shadow-lg transition-all flex items-center gap-2" +// \u003e +// ๐Ÿ—๏ธ Place Limit Order +// \u003c / button\u003e -\u003cPlaceOrderModal -isOpen = { isPlaceModalOpen } -onClose = {() =\u003e setIsPlaceModalOpen(false)} -onPlaceOrder = { handlePlaceOrder } - /\u003e -\u003c /\u003e - ) -} +// \u003cPlaceOrderModal +// isOpen = { isPlaceModalOpen } +// onClose = {() =\u003e setIsPlaceModalOpen(false)} +// onPlaceOrder = { handlePlaceOrder } +// /\u003e +// \u003c /\u003e +// ) +// } diff --git a/apps/web/components/PlaceOrderModal.tsx b/apps/web/components/PlaceOrderModal.tsx index b2fa5f0..8643a34 100644 --- a/apps/web/components/PlaceOrderModal.tsx +++ b/apps/web/components/PlaceOrderModal.tsx @@ -44,6 +44,17 @@ export function PlaceOrderModal({ isOpen, onClose, onPlaceOrder }: PlaceOrderMod if (!targetPrice || !amount) return setIsPlacing(true) + + // Charge Yellow fee for limit order (soft โ€” don't block on failure) + if (typeof window !== 'undefined' && window.agentropolis?.isSessionActive?.()) { + try { + await window.agentropolis.chargeAction('limit_order', '0.005') + console.log('[PlaceOrderModal] Limit order fee charged: 0.005 ytest.USD') + } catch (err) { + console.warn('[PlaceOrderModal] Limit order fee charge failed (proceeding anyway):', err) + } + } + try { const order: LimitOrder = { id: `order-${Date.now()}`, @@ -144,8 +155,8 @@ export function PlaceOrderModal({ isOpen, onClose, onPlaceOrder }: PlaceOrderMod + + {/* Dashboard Panel */} + {isOpen && ( +
+ {/* Header */} +
+
+
+
Session Economics
+
+ {state.balance} yUSD +
+
+ +
+
+ + {/* Summary Stats */} +
+
+
Total Spent
+
{totalSpent.toFixed(3)}
+
+
+
Actions
+
{chargeHistory.length}
+
+
+
Types
+
{sortedTypes.length}
+
+
+ + {/* Breakdown by Type */} + {sortedTypes.length > 0 && ( +
+
Cost Breakdown
+
+ {sortedTypes.map(([type, data]) => { + const info = getActionInfo(type) + const percentage = totalSpent > 0 ? (data.total / totalSpent) * 100 : 0 + return ( +
+
+ + {info.emoji} {info.label} + ร—{data.count} + + {data.total.toFixed(3)} +
+
+
+
+
+ ) + })} +
+
+ )} + + {/* Transaction History */} +
+
+
Transaction Log
+
+ {[...chargeHistory].reverse().map((entry, i) => { + const info = getActionInfo(entry.type) + return ( +
+
+ {info.emoji} + {info.label} +
+
+ -{entry.amount} + {formatTime(entry.timestamp)} +
+
+ ) + })} +
+
+
+
+ )} + + ) +} diff --git a/apps/web/components/SessionProvider.tsx b/apps/web/components/SessionProvider.tsx index 3ac85c2..baacf0b 100644 --- a/apps/web/components/SessionProvider.tsx +++ b/apps/web/components/SessionProvider.tsx @@ -13,6 +13,12 @@ import { } from '@/lib/yellow/channel' import { YELLOW_DEFAULTS, formatYtestUsd } from '@/lib/yellow/constants' +export interface ChargeEntry { + type: string + amount: string + timestamp: number +} + export type SessionStatus = 'disconnected' | 'connecting' | 'active' | 'settling' | 'settled' | 'error' export interface SessionState { @@ -23,6 +29,7 @@ export interface SessionState { depositTxHash?: string error?: string isDeposited: boolean + chargeHistory: ChargeEntry[] } interface SessionContextValue { @@ -34,6 +41,7 @@ interface SessionContextValue { chargeAgentDeploy: () => Promise executeTransfer: (destination: string, amount: bigint) => Promise withdraw: (amount?: bigint) => Promise + chargeHistory: ChargeEntry[] isLoading: boolean } @@ -54,7 +62,7 @@ function mapChannelStatusToSession(channelStatus: ChannelStatus): SessionStatus return mapping[channelStatus] } -function createSessionState(channelState: ChannelState, actionBalance: bigint): SessionState { +function createSessionState(channelState: ChannelState, actionBalance: bigint, chargeHistory: ChargeEntry[]): SessionState { return { status: mapChannelStatusToSession(channelState.status), balance: formatYtestUsd(actionBalance), @@ -63,6 +71,7 @@ function createSessionState(channelState: ChannelState, actionBalance: bigint): depositTxHash: channelState.txHash, error: channelState.error, isDeposited: channelState.depositAmount > BigInt(0), + chargeHistory, } } @@ -77,6 +86,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { balance: BigInt(0), }) const [actionBalance, setActionBalance] = useState(BigInt(0)) + const [chargeHistory, setChargeHistory] = useState([]) useEffect(() => { const mgr = createChannelManager( @@ -88,7 +98,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { setActionBalance(mgr.state.balance) }, [publicClient, walletClient]) - const state = createSessionState(channelState, actionBalance) + const state = createSessionState(channelState, actionBalance, chargeHistory) const isLoading = ['approving', 'depositing', 'connecting', 'creating', 'closing'].includes(channelState.status) @@ -169,6 +179,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { setActionBalance(result.newBalance) setChannelState(manager.state) + setChargeHistory(prev => [...prev, { type, amount, timestamp: Date.now() }]) console.log(`[Session] ${type} charged: -${amount}, balance: ${formatYtestUsd(result.newBalance)}`) }, [manager, channelState.status, actionBalance]) @@ -195,6 +206,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { if (result.success) { setActionBalance(result.newBalance) setChannelState(manager.state) + setChargeHistory(prev => [...prev, { type: 'agent-deploy', amount: formatYtestUsd(deployCost), timestamp: Date.now() }]) } return result @@ -237,6 +249,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { chargeAgentDeploy, executeTransfer, withdraw, + chargeHistory, isLoading, }}> {children} diff --git a/apps/web/components/SwapHandler.tsx b/apps/web/components/SwapHandler.tsx index b5452f0..82d0416 100644 --- a/apps/web/components/SwapHandler.tsx +++ b/apps/web/components/SwapHandler.tsx @@ -30,6 +30,18 @@ export function SwapHandler() { setResult({ status: 'quoting' }) + // Charge Yellow fee for swap/LP execution (soft โ€” don't block on failure) + if (typeof window !== 'undefined' && window.agentropolis?.isSessionActive?.()) { + try { + const isLp = proposal.strategyType === 'lp_full_range' || proposal.strategyType === 'lp_concentrated' + const chargeType = isLp ? 'lp_position' : 'swap' + await window.agentropolis.chargeAction(chargeType, '0.005') + console.log(`[SwapHandler] Yellow fee charged: ${chargeType} = 0.005 ytest.USD`) + } catch (err) { + console.warn('[SwapHandler] Yellow fee charge failed (proceeding anyway):', err) + } + } + try { setResult({ status: 'pending' }) const executionResult = await execute(proposal) diff --git a/apps/web/components/game/CouncilRoom3D.tsx b/apps/web/components/game/CouncilRoom3D.tsx index 9069d9e..58239c6 100644 --- a/apps/web/components/game/CouncilRoom3D.tsx +++ b/apps/web/components/game/CouncilRoom3D.tsx @@ -91,6 +91,16 @@ export default function CouncilRoom3D({ onBack }: { onBack: () => void }) { setProposal(null) actions.startDeliberation() + // Charge Yellow fee for deliberation (soft โ€” don't block on failure) + if (typeof window !== 'undefined' && window.agentropolis?.isSessionActive?.()) { + try { + await window.agentropolis.chargeAction('deliberation', '0.005') + console.log('[CouncilRoom3D] Deliberation fee charged: 0.005 ytest.USD') + } catch (err) { + console.warn('[CouncilRoom3D] Deliberation fee charge failed (proceeding anyway):', err) + } + } + // Mock Sequence const activeAgents = ['alphaHunter', 'riskSentinel', 'macroOracle', 'devilsAdvocate', 'councilClerk'] diff --git a/apps/web/components/game/GameComponent.tsx b/apps/web/components/game/GameComponent.tsx index 6d0c839..736d8c2 100644 --- a/apps/web/components/game/GameComponent.tsx +++ b/apps/web/components/game/GameComponent.tsx @@ -12,6 +12,7 @@ declare global { interface Window { agentropolis?: { chargeAgentDeploy: () => Promise<{ success: boolean; error?: string }> + chargeAction: (type: string, amount: string) => Promise<{ success: boolean; error?: string }> getBalance: () => string isSessionActive: () => boolean } @@ -27,7 +28,7 @@ interface TokenLaunchResult { export default function GameComponent() { const gameRef = useRef(null) - const { state, chargeAgentDeploy } = useSession() + const { state, chargeAgentDeploy, chargeAction } = useSession() const [tokenLaunch, setTokenLaunch] = useState({ status: 'idle' }) const handleChargeAgentDeploy = useCallback(async () => { @@ -36,7 +37,7 @@ export default function GameComponent() { } const result = await chargeAgentDeploy() - + if (!result.success) { console.log('[GameComponent] Agent deploy charge failed:', result.error) return { success: false, error: result.error } @@ -46,10 +47,36 @@ export default function GameComponent() { return { success: true } }, [state.status, chargeAgentDeploy]) + const handleChargeAction = useCallback(async (type: string, amount: string) => { + if (state.status !== 'active') { + return { success: false, error: 'Session not active. Start a Yellow session first.' } + } + + try { + await chargeAction(type, amount) + console.log(`[GameComponent] Action charged: ${type} = ${amount} ytest.USD`) + return { success: true } + } catch (err) { + const error = err instanceof Error ? err.message : 'Charge failed' + console.log(`[GameComponent] Action charge failed: ${error}`) + return { success: false, error } + } + }, [state.status, chargeAction]) + const handleTokenLaunch = useCallback(async (proposal: TokenLaunchProposal) => { console.log('[GameComponent] Token launch approved:', proposal.tokenSymbol) setTokenLaunch({ status: 'pending' }) + // Charge Yellow fee for token launch (soft โ€” don't block on failure) + if (state.status === 'active') { + try { + await chargeAction('token_launch', '0.005') + console.log('[GameComponent] Token launch fee charged: 0.005 ytest.USD') + } catch (err) { + console.warn('[GameComponent] Token launch fee charge failed (proceeding anyway):', err) + } + } + try { const response = await fetch('/api/agents/launch-token', { method: 'POST', @@ -75,11 +102,12 @@ export default function GameComponent() { } setTimeout(() => setTokenLaunch({ status: 'idle' }), 8000) - }, []) + }, [chargeAction, state.status]) useEffect(() => { window.agentropolis = { chargeAgentDeploy: handleChargeAgentDeploy, + chargeAction: handleChargeAction, getBalance: () => state.balance, isSessionActive: () => state.status === 'active', } @@ -87,7 +115,7 @@ export default function GameComponent() { return () => { delete window.agentropolis } - }, [handleChargeAgentDeploy, state.balance, state.status]) + }, [handleChargeAgentDeploy, handleChargeAction, state.balance, state.status]) useEffect(() => { if (gameRef.current) return @@ -106,7 +134,7 @@ export default function GameComponent() { } gameRef.current = new Phaser.Game(config) - + gameRef.current.events.on('openCouncil', (agents?: Array<{ id: string; name: string }>) => { gameRef.current?.scene.start('CouncilScene', { agents: agents ?? [] }) }) diff --git a/apps/web/components/game/scenes/CouncilScene.ts b/apps/web/components/game/scenes/CouncilScene.ts index 379ee11..66074c4 100644 --- a/apps/web/components/game/scenes/CouncilScene.ts +++ b/apps/web/components/game/scenes/CouncilScene.ts @@ -128,16 +128,16 @@ export class CouncilScene extends Phaser.Scene { private drawRoundtable(x: number, y: number) { const graphics = this.add.graphics() - + graphics.fillStyle(0x78350f, 1) graphics.fillEllipse(x, y + 20, 400, 180) - + graphics.fillStyle(0x92400e, 1) graphics.fillEllipse(x, y, 400, 180) - + graphics.lineStyle(3, 0xfbbf24, 0.5) graphics.strokeEllipse(x, y, 400, 180) - + const innerGraphics = this.add.graphics() innerGraphics.fillStyle(0x451a03, 1) innerGraphics.fillEllipse(x, y, 300, 130) @@ -234,39 +234,39 @@ export class CouncilScene extends Phaser.Scene { private createProposalCard(x: number, y: number) { this.proposalCard = this.add.container(x, y) - + const cardWidth = 500 const cardHeight = 160 - + const bg = this.add.graphics() bg.fillStyle(0x1e293b, 1) bg.fillRoundedRect(-cardWidth / 2, -cardHeight / 2, cardWidth, cardHeight, 12) bg.lineStyle(2, 0x475569) bg.strokeRoundedRect(-cardWidth / 2, -cardHeight / 2, cardWidth, cardHeight, 12) this.proposalCard.add(bg) - + const loadingText = this.add.text(0, 0, 'Waiting for proposal...', { fontSize: '16px', color: '#94a3b8', fontFamily: 'Arial', }).setOrigin(0.5) this.proposalCard.add(loadingText) - + this.approveBtn = this.createButton(x - 80, y + 110, 'APPROVE', 0x22c55e, () => this.handleApprove()) this.rejectBtn = this.createButton(x + 80, y + 110, 'REJECT', 0xef4444, () => this.handleReject()) - + this.approveBtn.setVisible(false) this.rejectBtn.setVisible(false) } private createButton(x: number, y: number, text: string, color: number, onClick: () => void): Phaser.GameObjects.Container { const container = this.add.container(x, y) - + const bg = this.add.graphics() bg.fillStyle(color, 1) bg.fillRoundedRect(-60, -18, 120, 36, 8) container.add(bg) - + const label = this.add.text(0, 0, text, { fontSize: '14px', color: '#ffffff', @@ -274,25 +274,25 @@ export class CouncilScene extends Phaser.Scene { fontStyle: 'bold', }).setOrigin(0.5) container.add(label) - + container.setInteractive(new Phaser.Geom.Rectangle(-60, -18, 120, 36), Phaser.Geom.Rectangle.Contains) - + container.on('pointerover', () => { bg.clear() bg.fillStyle(Phaser.Display.Color.ValueToColor(color).lighten(20).color, 1) bg.fillRoundedRect(-60, -18, 120, 36, 8) this.input.setDefaultCursor('pointer') }) - + container.on('pointerout', () => { bg.clear() bg.fillStyle(color, 1) bg.fillRoundedRect(-60, -18, 120, 36, 8) this.input.setDefaultCursor('default') }) - + container.on('pointerdown', onClick) - + return container } @@ -528,9 +528,19 @@ export class CouncilScene extends Phaser.Scene { repeat: -1, }) + // Charge Yellow fee for deliberation (soft โ€” don't block on failure) + if (typeof window !== 'undefined' && window.agentropolis?.isSessionActive?.()) { + try { + await window.agentropolis.chargeAction('deliberation', '0.005') + console.log('[CouncilScene] Deliberation fee charged: 0.005 ytest.USD') + } catch (err) { + console.warn('[CouncilScene] Deliberation fee charge failed (proceeding anyway):', err) + } + } + try { const headers: Record = { 'Content-Type': 'application/json' } - + // Guest session key matches GuestMode.tsx const guestSessionData = typeof localStorage !== 'undefined' ? localStorage.getItem('agentropolis_guest_session') : null if (guestSessionData) { @@ -595,9 +605,9 @@ export class CouncilScene extends Phaser.Scene { this.displayProposal(data.proposal) } catch (err) { console.error('[CouncilScene] Deliberation failed:', err) - + this.deliberationRetries++ - + if (this.loadingText) { if (this.deliberationRetries < MAX_DELIBERATION_RETRIES) { this.loadingText.setText(`โŒ Deliberation failed. Retrying (${this.deliberationRetries}/${MAX_DELIBERATION_RETRIES})...`) @@ -674,9 +684,9 @@ export class CouncilScene extends Phaser.Scene { private displayProposal(proposal: Proposal) { this.currentProposal = proposal - + if (!this.proposalCard) return - + while (this.proposalCard.list.length > 1) { const child = this.proposalCard.list[this.proposalCard.list.length - 1] if (child instanceof Phaser.GameObjects.GameObject) child.destroy() @@ -686,10 +696,10 @@ export class CouncilScene extends Phaser.Scene { this.proposalCard.setVisible(true) this.proposalCard.setAlpha(0) this.proposalCard.setScale(0.9) - + const riskColors: Record = { low: 0x22c55e, medium: 0xeab308, high: 0xef4444 } const riskColor = riskColors[proposal.riskLevel] || 0x94a3b8 - + const riskIndicator = this.add.graphics() riskIndicator.fillStyle(riskColor, 1) riskIndicator.fillRoundedRect(-250, -80, 6, 160, 3) @@ -708,7 +718,7 @@ export class CouncilScene extends Phaser.Scene { duration: 300, ease: 'Back.easeOut', }) - + this.approveBtn?.setVisible(true) this.rejectBtn?.setVisible(true) @@ -754,7 +764,7 @@ export class CouncilScene extends Phaser.Scene { fontStyle: 'bold', }).setOrigin(0.5) this.proposalCard.add(swapText) - + const reasonText = this.add.text(0, 15, proposal.reasoning.substring(0, 80), { fontSize: '12px', color: '#94a3b8', @@ -776,8 +786,8 @@ export class CouncilScene extends Phaser.Scene { fontStyle: 'bold', }).setOrigin(0.5) this.proposalCard.add(title) - - const tokenText = this.add.text(0, -25, + + const tokenText = this.add.text(0, -25, `${proposal.tokenName} ($${proposal.tokenSymbol})`, { fontSize: '20px', color: '#ffffff', @@ -785,7 +795,7 @@ export class CouncilScene extends Phaser.Scene { fontStyle: 'bold', }).setOrigin(0.5) this.proposalCard.add(tokenText) - + const descText = this.add.text(0, 5, proposal.tokenDescription, { fontSize: '12px', color: '#94a3b8', @@ -836,9 +846,9 @@ export class CouncilScene extends Phaser.Scene { private handleApprove() { if (!this.currentProposal) return - + console.log('[CouncilScene] Proposal approved:', this.currentProposal.id) - + if (isTokenLaunchProposal(this.currentProposal)) { this.game.events.emit('tokenLaunchApproved', this.currentProposal) this.showResult('๐Ÿš€ Launching token...', 0xf97316) @@ -889,5 +899,5 @@ export class CouncilScene extends Phaser.Scene { }) } - update() {} + update() { } }