AI-Powered DeFi Trading on Base
Basement is an AI trading assistant which lets you swap tokens on Base using natural language. Sign in with Google or email — no MetaMask or browser extension needed. Tell the AI what you want ("buy $50 of UNI", "diversify into DeFi blue chips"), review the proposed swap plan, and click Execute. The server handles everything from there, even if you close your browser.
+-------------------+ +------------------------+ +--------------------+
| Client (React) | ----> | Next.js API Routes | ----> | Uniswap Trade API |
| | | | | (Base) |
| Privy Auth | | /api/agent (Claude) | +--------------------+
| Zustand Store | | /api/execute (Swaps) |
| Status Polling | | /api/quote (Proxy) | +--------------------+
| | | /api/swap (Proxy) | ----> | Privy Wallet API |
+-------------------+ | /api/order (Proxy) | | (Server Signing) |
| /api/approval(Proxy) | +--------------------+
+------------------------+
Key insight: The user's embedded wallet is created and managed by Privy. When the user clicks Execute, the server signs and broadcasts transactions using Privy's server-side wallet API — no browser wallet interaction needed.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Auth & Wallets | Privy (embedded wallets, email/Google login) |
| AI | Claude (Anthropic) with tool use |
| DEX | Uniswap Trade API (Classic + UniswapX) |
| Chain | Base (Coinbase L2) |
| Blockchain | viem (public client, wallet client) |
| State | Zustand |
| Styling | Tailwind CSS |
| Animations | Framer Motion |
| Notifications | Sonner |
- Node.js 18+
- npm or yarn
- A Privy account
- A Uniswap Trade API key
- An Anthropic API key
- An Alchemy API key (for Base RPC)
- A CoinGecko API key (for prices)
git clone <repo-url>
cd basement
npm install --legacy-peer-deps- Create an app at dashboard.privy.io
- Login methods — enable Email and Google under "Login Methods"
- Embedded wallets — enable "Create embedded wallet on login" for Ethereum
- Generate a P256 key pair for server-side wallet signing:
This outputs a
npx @privy-io/node generate-p256-key-pair
publicKeyandprivateKey(base64-encoded PKCS8) - Register a Key Quorum — in the Privy dashboard under "Server Wallets", create a key quorum using the public key from step 4. Note the Key Quorum ID.
- Copy the App ID, App Secret, Authorization Private Key (from step 4), and Key Quorum ID into your
.env.local
Copy .env.example to .env.local and fill in values:
cp .env.example .env.local| Variable | Description | Where to get it |
|---|---|---|
UNISWAP_API_KEY |
Uniswap Trade API key | hub.uniswap.org |
ANTHROPIC_API_KEY |
Claude API key | console.anthropic.com |
ALCHEMY_API_KEY |
Alchemy API key (server-side RPC) | alchemy.com |
NEXT_PUBLIC_ALCHEMY_API_KEY |
Alchemy RPC URL (client-side, optional) | Same as above |
COINGECKO_API_KEY |
CoinGecko API key (token prices) | coingecko.com/en/api |
NEXT_PUBLIC_PRIVY_APP_ID |
Privy App ID | Privy Dashboard > Settings |
PRIVY_APP_SECRET |
Privy App Secret | Privy Dashboard > Settings |
PRIVY_AUTHORIZATION_KEY |
Base64-encoded P256 private key | Generated in step 4 above |
NEXT_PUBLIC_PRIVY_KEY_QUORUM_ID |
Key Quorum ID for signer delegation | Privy Dashboard > Server Wallets |
npm run devOpen http://localhost:3000.
basement/
├── app/
│ ├── layout.tsx # Root layout with fonts
│ ├── page.tsx # Main page (sidebar + chat)
│ ├── providers.tsx # PrivyProvider + Toaster
│ ├── globals.css # Tailwind + custom properties
│ └── api/
│ ├── agent/route.ts # Claude AI agent with tool use
│ ├── execute/route.ts # Server-side swap execution + polling
│ ├── quote/route.ts # Uniswap quote proxy
│ ├── swap/route.ts # Uniswap classic swap proxy
│ ├── order/route.ts # Uniswap UniswapX order proxy
│ └── approval/route.ts # Token approval check proxy
├── components/
│ ├── layout/
│ │ ├── Header.tsx # Logo + chain badge + Privy auth
│ │ ├── Sidebar.tsx # Portfolio sidebar
│ │ └── MainArea.tsx # Chat + swap plan area
│ ├── agent/
│ │ ├── AgentChat.tsx # Chat message list
│ │ ├── IntentInput.tsx # Natural language input
│ │ ├── MessageBubble.tsx # Individual message
│ │ └── ThinkingIndicator.tsx
│ ├── portfolio/
│ │ ├── PortfolioHeader.tsx # Total value display
│ │ ├── TokenList.tsx # Token balances
│ │ ├── AllocationChart.tsx # Pie chart
│ │ └── ActivityFeed.tsx # Recent swaps
│ ├── swap/
│ │ ├── SwapPlanCard.tsx # Proposed swap plan UI
│ │ ├── SwapRow.tsx # Individual swap in plan
│ │ ├── SwapStatus.tsx # Status indicator
│ │ └── ExecutionTracker.tsx# Progress during execution
│ └── ui/ # Shared UI primitives
│ ├── Badge.tsx
│ ├── Button.tsx
│ ├── Card.tsx
│ ├── Input.tsx
│ ├── Skeleton.tsx
│ └── TokenIcon.tsx
└── lib/
├── privy/
│ └── server.ts # PrivyClient singleton + wallet helpers
├── hooks/
│ ├── usePrivyAuth.ts # Privy auth state + auto-delegation
│ ├── useAgent.ts # Chat with AI agent
│ ├── usePortfolio.ts # Portfolio data fetching
│ └── useSwapExecution.ts # Server execution + polling
├── ai/
│ ├── prompt.ts # System prompt + tool definitions
│ ├── tools.ts # Tool call execution
│ └── quoteEnricher.ts # Quote enrichment for AI
├── uniswap/
│ ├── client.ts # Axios clients (direct + proxy)
│ ├── types.ts # TypeScript types for Uniswap API
│ ├── serverExecutor.ts # Server-side swap execution
│ ├── quote.ts # Quote helper
│ ├── approval.ts # Approval check helper
│ ├── swap.ts # Classic swap helper
│ └── order.ts # UniswapX order helper
├── web3/
│ ├── tokens.ts # Base token registry
│ ├── balances.ts # Portfolio balance fetching
│ └── prices.ts # CoinGecko price fetching
└── store/
└── useStore.ts # Zustand global state
User clicks "Sign In" in the header. Privy opens a modal with email and Google options. On first login, an embedded Ethereum wallet is automatically created on Base. The wallet is then delegated to the server for server-side signing.
User types something like "buy $50 of UNI" or "diversify $200 into DeFi tokens" in the chat input.
The message is sent to /api/agent, which calls Claude with tool definitions for:
get_token_info— lookup token metadataget_quote— fetch Uniswap quotescreate_swap_plan— build a multi-swap plan
Claude uses these tools to understand the intent, look up tokens, get live quotes, and construct a SwapPlan with one or more swaps.
The AI returns a swap plan card showing each swap (token pair, amount, estimated output). The user reviews and can accept or dismiss.
When the user clicks Execute:
- Client sends
POST /api/executewith the plan + Privy JWT - Server verifies the JWT, gets the user's embedded wallet
- Server starts execution in the background (fire-and-forget)
- For each swap: check approval → get quote → sign permit → submit swap/order
- All signing happens server-side via Privy's wallet API
- Client polls
GET /api/execute?id=...every 1.5s for status updates
The swap plan card shows real-time progress: queued → approving → signing → pending → confirmed/failed. Toast notifications appear on completion or failure.
Because execution runs on the server, the user can close their browser. When they reopen, if they still have the executionId, they can check status.
Why Privy over wagmi/RainbowKit?
- Retail-friendly auth: email and Google sign-in, no wallet extension needed
- Embedded wallets with server-side signing delegation
- Enables "click Execute and close browser" UX
Why server-side execution?
- User doesn't need to keep the browser open for multi-swap plans
- No MetaMask popups for each approval/signature
- More reliable — server retries, no browser tab suspension
In-memory state limitations
- Execution status is stored in a
Mapin the API route - Lost on server restart or new deployment
- For production: use Vercel KV, Redis, or a database
- Push to GitHub
- Import in vercel.com
- Add all environment variables in Vercel project settings
- Set Function Max Duration to 300s (requires Vercel Pro for multi-swap plans)
- Deploy
- Single-swap plans complete in ~10-30s (within default 60s limit)
- Multi-swap plans with UniswapX order polling can take >60s
- The
/api/executeroute setsmaxDuration = 300(Vercel Pro) - For Vercel Hobby: stick to 1-2 swap plans, or split into sequential calls
AI agent endpoint. Processes natural language and returns a response with optional swap plan.
Body: { message: string, userId?: string, conversationHistory?: Array }
Response: { type: 'text' | 'swap_plan', message: string, plan?: SwapPlan }
Start server-side swap execution. Requires Privy JWT.
Headers: Authorization: Bearer <privy-jwt>
Body: { plan: SwapPlan }
Response: { executionId: string }
Poll execution status.
Response: { planStatus: string, swapStatuses: Record<string, { status, txHash?, error? }> }
Proxy to Uniswap Trade API /quote.
Body: { tokenIn, tokenOut, amount, swapper, ... }
Proxy to Uniswap Trade API /swap.
Body: { quote, signature?, permitData? }
Proxy to Uniswap Trade API /order.
Body: { quote, signature, permitData? }
Proxy to Uniswap Trade API /orders for UniswapX order status.
Proxy to Uniswap Trade API /check_approval.
Body: { walletAddress, token, amount, chainId }
- Uniswap Foundation — AI-powered natural language trading using Uniswap Trade API (Classic + UniswapX) on Base
- 0g Labs — Decentralized AI agent infrastructure for DeFi trading