diff --git a/docs/ai-agents/setup/agent-builder-codes.mdx b/docs/ai-agents/guides/agent-builder-codes.mdx similarity index 99% rename from docs/ai-agents/setup/agent-builder-codes.mdx rename to docs/ai-agents/guides/agent-builder-codes.mdx index 89ff1f0fa..a7eec443a 100644 --- a/docs/ai-agents/setup/agent-builder-codes.mdx +++ b/docs/ai-agents/guides/agent-builder-codes.mdx @@ -2,7 +2,6 @@ title: "Builder Codes for Agents" description: "Register your agent on Base.dev and append a Builder Code to every transaction to measure onchain activity." keywords: ["builder code", "Base.dev", "ERC-8021", "attribution", "onchain activity", "agent attribution", "dataSuffix", "builder codes", "Base builder codes", "agent onchain"] -tag: "NEW" --- [Base.dev](https://base.dev) is the canonical registry for agents on Base. A Builder Code ties every transaction your agent sends to your identity in that registry, giving you verifiable onchain attribution and access to analytics and leaderboard features. diff --git a/docs/ai-agents/guides/batch-calls.mdx b/docs/ai-agents/guides/batch-calls.mdx new file mode 100644 index 000000000..bc0b692cc --- /dev/null +++ b/docs/ai-agents/guides/batch-calls.mdx @@ -0,0 +1,66 @@ +--- +title: "Execute Contract Calls" +description: "Batch multiple contract interactions into a single user approval using send_calls and Base MCP" +keywords: ["send_calls MCP", "batch contract calls AI", "EIP-5792 AI", "DeFi AI agent", "Moonwell AI Base", "batch transaction AI"] +--- + +import { AcceptingPaymentsDemo } from "/snippets/AcceptingPaymentsDemo.jsx" + + + +## What it does + +`send_calls` submits a batch of EIP-5792 contract calls to your Base Account in a single approval. Use it for DeFi interactions, multi-step operations, and NFT mints that go beyond simple send or swap. + +The most common use case: [protocol plugins](/ai-agents/plugins/native-plugins) like Moonwell prepare a `calls` array (including token approvals and deposits), and you pass it directly to `send_calls` — everything executes atomically in one approval. Moonwell works entirely via `web_request`, with no additional MCP server required. + +## What you can ask + +With the [Moonwell plugin](/ai-agents/plugins/native-plugins): + +```text +Find the best USDC market on Base and supply 100 USDC +``` + +```text +Borrow 500 USDC against my collateral on Moonwell +``` + +```text +Repay all my Moonwell debt +``` + +## How it works + + + + Protocol plugins like Moonwell return a `calls` array and `chainId` from their prepare endpoints. The calls include any required token approvals and the protocol interaction itself. + + + Passes the `calls` array and hex `chainId` to Base MCP. + + + Open the `keys.coinbase.com` link to review all calls in the batch before signing. + + + All calls in the batch execute atomically — if one fails, none go through. + + + +## Parameters + +| Parameter | Required | What it does | +|-----------|----------|-------------| +| `chainId` | Yes | Hex chain ID: `0x2105` for Base mainnet, `0x14a34` for Base Sepolia | +| `calls` | Yes | Array of `{ to, value?, data? }` objects | + +## Related guides + + + + Morpho, Moonwell, Uniswap, and Avantis — full orchestration patterns in the skill repo. + + + Sign individual messages and typed data. + + diff --git a/docs/ai-agents/guides/check-balance.mdx b/docs/ai-agents/guides/check-balance.mdx new file mode 100644 index 000000000..4e6bd53a4 --- /dev/null +++ b/docs/ai-agents/guides/check-balance.mdx @@ -0,0 +1,52 @@ +--- +title: "Check Balance & Portfolio" +description: "View your token balances, portfolio value, and wallet details using Base MCP" +keywords: ["check balance AI", "get_portfolio MCP", "get_wallets MCP", "Base wallet balance AI assistant"] +--- + +import { DataFetchingDemo } from "/snippets/DataFetchingDemo.jsx" + + + +## What you can ask + +```text +Show me my wallets +``` + +```text +What is my USDC balance? +``` + +```text +Show my full portfolio +``` + +```text +What tokens do I have in my wallet? +``` + +## How it works + +**`get_wallets`** — lists all wallets in your wallet group (your Base Account plus any agent wallets). + +**`get_portfolio`** — returns portfolio value and per-asset breakdown for your connected wallet. + +| Parameter | What it does | +|-----------|-------------| +| `chain` | Filter by chain: `base` or `ethereum` | +| `query` | Filter by token name or symbol (e.g. "USDC") | +| `includePnl` | Include unrealized/realized P&L per asset | + +**`search_tokens`** — resolve a token symbol or name to its contract address and decimals. Useful before sending less common tokens. + +## Related guides + + + + Send ETH or any ERC-20 from your Base Account. + + + See past sends, swaps, and receives. + + diff --git a/docs/ai-agents/guides/index.mdx b/docs/ai-agents/guides/index.mdx new file mode 100644 index 000000000..60b7270c2 --- /dev/null +++ b/docs/ai-agents/guides/index.mdx @@ -0,0 +1,26 @@ +--- +title: "Guides" +description: "Step-by-step guides for common things to do with Base MCP" +keywords: ["Base MCP guides", "send tokens AI", "swap tokens AI", "check balance AI", "sign messages AI"] +--- + + + + View your token balances, portfolio value, and wallet details. + + + Send ETH or any ERC-20 to an address, ENS name, or basename. + + + Swap between any two tokens via the Coinbase swap service. + + + Browse past transactions and filter by asset or date range. + + + Sign EIP-712 typed data and plain messages for authentication. + + + Batch multiple contract interactions into a single user approval. + + diff --git a/docs/ai-agents/guides/send-tokens.mdx b/docs/ai-agents/guides/send-tokens.mdx new file mode 100644 index 000000000..3f1e30a0e --- /dev/null +++ b/docs/ai-agents/guides/send-tokens.mdx @@ -0,0 +1,73 @@ +--- +title: "Send Tokens" +description: "Send ETH or any ERC-20 token to an address, ENS name, basename, or cb.id using Base MCP" +keywords: ["send tokens AI", "send USDC AI assistant", "send ETH AI", "Base MCP send", "ENS basename send AI"] +--- + +import { TradeExecutionDemo } from "/snippets/TradeExecutionDemo.jsx" + + + +## What you can ask + +```text +Send 10 USDC to alice.base.eth +``` + +```text +Transfer 0.01 ETH to 0x1234...abcd +``` + +```text +Pay bob.eth 5 USDC +``` + +```text +Send 50 DEGEN to vitalik.eth +``` + +## How it works + +The `send` tool constructs a transaction from your Base Account and requires your approval at `keys.coinbase.com`. Nothing is sent until you confirm. + +| Parameter | Required | What it does | +|-----------|----------|-------------| +| `recipient` | Yes | Address, ENS name, basename (e.g. `alice.base.eth`), cb.id name, or username | +| `amount` | Yes | Human-readable decimal (e.g. `"10.5"`) | +| `asset` | Yes | Token symbol (`ETH`, `USDC`) or ERC-20 contract address | +| `chain` | Yes | Network to send on (e.g. `base`, `arbitrum`, `optimism`, `ethereum`) | +| `decimals` | When using contract address | Required when `asset` is a contract address | + + +For common tokens like ETH and USDC, just use the symbol — no contract address needed. For less common tokens, your assistant will call `search_tokens` first to resolve the address and decimals automatically. + + +## Approval flow + +Every send requires a manual approval: + + + + The transaction is constructed but not yet broadcast. + + + Open the `keys.coinbase.com` link to review the recipient, amount, and fee. + + + Confirm the transaction in the approval UI. Nothing is sent without your explicit confirmation. + + + Your assistant polls `get_request_status` and reports success once the transaction is confirmed onchain. + + + +## Related guides + + + + Exchange one token for another. + + + Verify your balance before sending. + + diff --git a/docs/ai-agents/guides/sign-messages.mdx b/docs/ai-agents/guides/sign-messages.mdx new file mode 100644 index 000000000..e0d990b36 --- /dev/null +++ b/docs/ai-agents/guides/sign-messages.mdx @@ -0,0 +1,60 @@ +--- +title: "Sign Messages" +description: "Sign EIP-712 typed data and plain messages with your Base Account using Base MCP" +keywords: ["sign message AI", "EIP-712 sign AI", "personal_sign AI", "Base MCP sign", "sign typed data AI assistant"] +--- + +import { SignMessagesDemo } from "/snippets/SignMessagesDemo.jsx" + + + +## What it does + +The `sign` tool requests a cryptographic signature from your Base Account. Like all write tools, it requires your approval at `keys.coinbase.com`. + +Two signature types are supported: + +| Type | Standard | Use case | +|------|----------|---------| +| `0x45` | personal_sign (EIP-191) | Simple text messages, SIWE auth challenges | +| `0x01` | EIP-712 typed data | Structured data, permit signatures, protocol auth | + +## What you can ask + +```text +Sign this message: "I agree to the terms of service" +``` + +```text +Sign in to this app using my Base Account +``` + +Signing is usually invoked by protocols or integrations, not directly prompted by users. Your assistant will handle the signing flow when a service requests it. + +## How it works + + + + Passes the message type and payload to Base MCP. + + + Open `keys.coinbase.com` to review what you're signing — the message content is shown in full. + + + Confirm the signature in the approval UI. + + + Your assistant polls `get_request_status` to retrieve the completed signature, then passes it to the requesting service. + + + +## Related guides + + + + Batch multiple contract interactions into one approval. + + + Morpho, Moonwell, Uniswap, and Avantis — signing in the flow with DeFi protocols. + + diff --git a/docs/ai-agents/guides/swap-tokens.mdx b/docs/ai-agents/guides/swap-tokens.mdx new file mode 100644 index 000000000..cb186ef02 --- /dev/null +++ b/docs/ai-agents/guides/swap-tokens.mdx @@ -0,0 +1,57 @@ +--- +title: "Swap Tokens" +description: "Swap between any two tokens on Base via the Coinbase swap service using Base MCP" +keywords: ["swap tokens AI", "token swap AI assistant", "USDC ETH swap AI", "Base MCP swap", "DeFi swap AI"] +--- + +import { TradingQuickstartDemo } from "/snippets/TradingQuickstartDemo.jsx" + + + +## What you can ask + +```text +Swap 100 USDC for ETH on Base +``` + +```text +Buy $50 of ETH with USDC +``` + +```text +Trade 0.01 ETH for USDC +``` + +```text +Convert all my USDC to ETH +``` + +## How it works + +The `swap` tool routes through the Coinbase swap service and requires your approval at `keys.coinbase.com`. Swaps are only supported on mainnet — not on testnets. + +| Parameter | Required | What it does | +|-----------|----------|-------------| +| `fromAsset` | Yes | Token to swap from — symbol (`USDC`) or contract address | +| `toAsset` | Yes | Token to swap to — symbol (`ETH`) or contract address | +| `amount` | Yes | Amount of `fromAsset` to swap (human-readable decimal) | +| `chain` | Yes | `base` — testnets are not supported | + + +Testnet swaps are not supported. If you need to test, use `send` on `base-sepolia` instead. + + +## Approval flow + +Same as sending — every swap requires approval at `keys.coinbase.com`. Your assistant will give you a link to review the swap details (input amount, expected output, price impact) before anything is signed. + +## Related guides + + + + Send tokens directly to another address. + + + Verify balances before swapping. + + diff --git a/docs/ai-agents/guides/view-history.mdx b/docs/ai-agents/guides/view-history.mdx new file mode 100644 index 000000000..5452051a6 --- /dev/null +++ b/docs/ai-agents/guides/view-history.mdx @@ -0,0 +1,64 @@ +--- +title: "View Transaction History" +description: "Browse past transactions, filter by asset, and paginate through your onchain history using Base MCP" +keywords: ["transaction history AI", "get_transaction_history MCP", "Base wallet history AI", "onchain history AI assistant"] +--- + +import { AgentRegistrationDemo } from "/snippets/AgentRegistrationDemo.jsx" + + + +## What you can ask + +```text +Show my recent transactions on Base +``` + +```text +Show my last 10 USDC transactions +``` + +```text +What did I send last week? +``` + +```text +Check the transaction history for 0x1234...abcd +``` + +## How it works + +`get_transaction_history` returns transactions in reverse chronological order (newest first) for any public wallet address. + +| Parameter | What it does | +|-----------|-------------| +| `address` | Address to query — defaults to your Base Account | +| `chain` | Network to query (e.g. `base`, `arbitrum`, `ethereum`) — defaults to `base` | +| `asset` | Filter to a specific token (e.g. `USDC`, `ETH`) | +| `limit` | Number of transactions per page (1–200, default 50) | +| `cursor` | Pagination cursor from the previous response's `nextCursor` | + + +Date range filtering is not supported — paginate through results to find transactions from a specific period. + + +## Pagination + +When `hasMore` is `true` in the response, more transactions exist. Ask your assistant to load more: + +```text +Show me the next page of transactions +``` + +Your assistant will use the `nextCursor` value from the previous response automatically. + +## Related guides + + + + View current balances alongside history. + + + Send tokens from your Base Account. + + diff --git a/docs/ai-agents/index.mdx b/docs/ai-agents/index.mdx index 9e9d47dfa..ba3f9be5a 100644 --- a/docs/ai-agents/index.mdx +++ b/docs/ai-agents/index.mdx @@ -1,65 +1,69 @@ --- -title: "AI Agents on Base" -description: "Build AI agents that trade, earn, and transact autonomously on Base" -keywords: ["AI agents Base", "x402 protocol", "onchain agents", "Base L2", "Build with AI", "OpenClaw", "trading agent", "payment agent"] +title: "Base MCP" +description: "Give your AI assistant a wallet. Base MCP connects any AI to your Base Account — check balances, send funds, swap tokens, and sign messages." +keywords: ["Base MCP", "AI agent wallet", "mcp.base.org", "AI assistant wallet", "onchain AI"] --- -import { AgentPaymentDemo } from "/snippets/AgentPaymentDemo.jsx" +import { WalletSetupDemo } from "/snippets/WalletSetupDemo.jsx" -Base gives your AI agent the tools to operate as an independent economic actor: a wallet to hold and spend funds, identity standards so other agents and services can trust it, and payment protocols for services and commerce. +Base MCP gives your AI assistant direct access to your [Base Account](https://base.org/account) — a smart wallet. Connect once and your assistant can check balances, send funds, swap tokens, sign messages, and execute contract calls across multiple networks. Every transaction requires your approval. +## Demo -## Mock Demo + - - -### High-level flow - -Here's how the agent you can build from these guides operates end-to-end — from wallet setup and identity registration to making authenticated, paid API requests using the [x402 protocol](https://www.x402.org). +## How it works ```mermaid sequenceDiagram - participant Agent - participant Wallet as Agent Wallet - participant Registry as Identity Registry - participant API as Paid API - participant Facilitator as x402 Facilitator - - Agent->>Wallet: Set up wallet (Bankr / CDP / Sponge) - Wallet-->>Agent: Address + signing capability - - Agent->>Registry: Register identity (ERC-8004 + Basename) - Registry-->>Agent: Agent ID published onchain - - Agent->>API: HTTP request - API-->>Agent: 402 Payment Required - Note over API,Agent: PAYMENT-REQUIRED header includes amount, token, and network - Agent->>Wallet: Sign payment payload - Wallet-->>Agent: Signed payment - Agent->>API: Retry with PAYMENT-SIGNATURE header - API->>Facilitator: Verify and settle payment - Facilitator-->>API: Payment confirmed - API-->>Agent: Response data + participant User + participant AI as AI Assistant + participant MCP as Base MCP + participant BA as Base Account + participant Approve as keys.coinbase.com + + User->>AI: "Send 10 USDC to alice.base.eth" + AI->>MCP: send(recipient, amount, asset, chain) + MCP->>BA: Construct transaction + BA-->>MCP: approvalUrl + requestId + MCP-->>AI: { approvalUrl, requestId } + AI-->>User: "Please approve: [link]" + User->>Approve: Opens link, approves + AI->>MCP: get_request_status(requestId) + MCP-->>AI: confirmed + AI-->>User: "Done — 10 USDC sent" ``` -## How this section is organized - -The AI agents section covers the full stack for building an autonomous onchain agent: +## What you can do -- **[Quickstarts](/ai-agents/quickstart/payments)** — End-to-end walkthroughs to get a working agent running in minutes. Start here if you're new. -- **[Setup](/ai-agents/setup/wallet-setup)** — Give your agent a wallet and a registered onchain identity. Wallets let it hold funds and authorize transactions; registration lets other agents and services verify who they're dealing with. -- **[Payments](/ai-agents/payments/pay-for-services-with-x402)** — Use x402 to pay for API access per-request in stablecoins, with no subscriptions or API keys required. Or gate your own endpoints to charge other agents. -- **[Trading](/ai-agents/trading/data-fetching)** — Fetch live market data and execute token swaps on Base. -- **[Skills](/ai-agents/skills)** — Installable knowledge packs that give your AI coding assistant deep context on Base APIs, tooling, and migration paths. + + + Send ETH or any ERC-20 token to addresses, ENS names, basenames, and cb.id names. + + + Swap between any tokens via the Coinbase swap service directly from your assistant. + + + Sign EIP-712 typed data and plain messages for authentication and protocol interactions. + + + Batch multiple contract interactions into a single user approval. + + -## Choose your path +## Get started - - Build an agent that pays for API access with stablecoins and charges other agents for your services. Get running in under 10 minutes. + + Connect mcp.base.org to your AI assistant in under 2 minutes. - - - Build an agent that fetches live market data and executes token swaps automatically on Base. + + Step-by-step guides for sending, swapping, checking balance, and more. + + + How the Base MCP skill works and how plugins like Morpho, Moonwell, Uniswap, and Avantis extend it. + + + Build your own plugin that produces unsigned calldata and executes through Base MCP's send_calls. - \ No newline at end of file + diff --git a/docs/ai-agents/llms-full.txt b/docs/ai-agents/llms-full.txt index 6e6a89e08..34d32797e 100644 --- a/docs/ai-agents/llms-full.txt +++ b/docs/ai-agents/llms-full.txt @@ -1,128 +1,95 @@ # https://docs.base.org/ai-agents/llms-full.txt -## AI Agents — Deep Guide for LLMs +## Base MCP — Deep Guide for LLMs -> Build AI agents that trade, earn, and transact autonomously on Base: a wallet to hold and spend funds, identity standards so other agents can trust it, payment protocols for per-request API access, and skills that give AI coding assistants deep context on Base APIs. +> Base MCP is a remote MCP server at `https://mcp.base.org` that connects any AI assistant to a Base Account smart wallet. Reads (balances, history, quotes) return instantly; every write (send, swap, sign, contract call) returns an `approvalUrl` the user opens at `keys.coinbase.com` and confirms before it executes. No API keys, no private keys in the assistant's context. ### What you can do here -- Give your agent a wallet to hold stablecoins and sign transactions (Bankr, CDP Agentic Wallet, Sponge Wallet) -- Enable your agent to pay for services automatically using the x402 protocol -- Gate your own endpoints to charge other agents per request -- Register and verify agent identity onchain via ERC-8004 -- Fetch live market data and execute token swaps using Flashblocks -- Install Base Skills so your AI coding assistant can build and migrate Base apps +- Connect Base MCP to Claude, ChatGPT, Claude Code, Codex, Cursor, or Hermes via a single URL or CLI command +- Check balances, portfolio value, and transaction history across Base +- Send ETH or any ERC-20 to addresses, ENS names, basenames, or cb.id names +- Swap between any tokens via the Coinbase swap service +- Sign EIP-191 personal messages and EIP-712 typed data (SIWE, permits, protocol auth) +- Batch multiple contract calls into a single approval via `send_calls` +- Extend with protocol plugins (Morpho, Moonwell, Uniswap, Avantis) or your own custom plugin +- Append Builder Codes to transactions for onchain attribution and revenue share ## Navigation (with brief descriptions) ### Overview -- [AI Agents on Base](https://docs.base.org/ai-agents/index.md) — Overview, high-level flow diagram, and section index +- [Base MCP](https://docs.base.org/ai-agents/index.md) — Overview of capabilities, the approval flow, and section index ### Quickstart -- [Get Started with Payments](https://docs.base.org/ai-agents/quickstart/payments.md) — Zero to a working agent that makes and accepts x402 payments in under 10 minutes (uses OpenClaw) -- [Build a Trading Agent](https://docs.base.org/ai-agents/quickstart/trading.md) — Fetch live market data and execute swaps automatically on Base - -### Setup -- [Wallet Setup](https://docs.base.org/ai-agents/setup/wallet-setup.md) — Compare Bankr, CDP Agentic Wallet, and Sponge Wallet; choose the right one for your agent -- [Agent Builder Codes](https://docs.base.org/ai-agents/setup/agent-builder-codes.md) — Register for onchain attribution with builder codes -- [Agent Registration](https://docs.base.org/ai-agents/setup/agent-registration.md) — Publish your agent's identity to the ERC-8004 registry with a Basename - -### Payments -- [Pay for Services with x402](https://docs.base.org/ai-agents/payments/pay-for-services-with-x402.md) — Client-side x402: automatic stablecoin payment on 402 response, no subscriptions or API keys -- [Accepting Payments (x402)](https://docs.base.org/ai-agents/payments/accepting-payments.md) — Server-side x402: gate endpoints with OpenClaw monetize-service skill, Sponge payment links, or x402-express - -### Trading -- [Data Fetching](https://docs.base.org/ai-agents/trading/data-fetching.md) — Fetch live market data from onchain and offchain sources using Flashblocks pending state -- [Trade Execution](https://docs.base.org/ai-agents/trading/trade-execution.md) — Execute swaps with wallet-native tools; Flashblocks patterns, fee calibration, and onchain signals - -### Skills -- [Skills Overview](https://docs.base.org/ai-agents/skills/index.md) — Installable capabilities: wallets, payments, market data, and swap execution; agent picks the right skill automatically - -#### Wallets -- [Bankr](https://docs.base.org/ai-agents/skills/wallets/bankr.md) — Self-custodied wallet across Base, ETH, Solana, Polygon, Unichain; gas sponsored; built-in swap and token launch tools; install via `install the bankr skill from https://github.com/BankrBot/skills` -- [CDP Agentic Wallet](https://docs.base.org/ai-agents/skills/wallets/cdp-agentic-wallet.md) — Email OTP auth, no private keys in agent context; 7-skill bundle covering auth, fund, send, trade, and x402 search/pay/monetize; install via `npx skills add coinbase/agentic-wallet-skills` -- [Sponge Wallet](https://docs.base.org/ai-agents/skills/wallets/sponge-wallet.md) — Multi-chain wallet (Base, ETH, Solana) with native x402 proxy, swaps, cross-chain bridges via deBridge, Polymarket/Hyperliquid trading, and banking via Bridge.xyz; register via REST API - -#### Payments -- [CDP Payment Skills](https://docs.base.org/ai-agents/skills/payments/cdp-payment-skills.md) — Full x402 loop: `search-for-service` (find APIs), `pay-for-service` (auto-pay 402), `monetize-service` (gate own endpoints); included in CDP Agentic Wallet bundle -- [Sponge x402](https://docs.base.org/ai-agents/skills/payments/sponge-x402.md) — Sponge's built-in proxy: discover services, sign payment, retry transparently; also creates reusable payment links for your own services; included with Sponge Wallet - -#### Trading -- [CoinGecko](https://docs.base.org/ai-agents/skills/trading/coingecko.md) — Live price feeds, market cap, volume, and OHLCV via x402; pay per call in USDC through any wallet skill; no API key or subscription -- [Alchemy Agentic Gateway](https://docs.base.org/ai-agents/skills/trading/alchemy-agentic-gateway.md) — Token balances, NFT metadata, portfolio data, token prices, decoded tx history at `https://x402.alchemy.com`; authenticated via SIWE, paid per call via x402; install via `npx @alchemy/x402 wallet generate` -- [Swap Execution](https://docs.base.org/ai-agents/skills/trading/swap-execution.md) — Token swaps via wallet-native tools; Bankr and CDP sponsor gas; Sponge bridges cross-chain via deBridge; all three support preconf simulation before execution - -#### Base Chain & Migrations +- [Quickstart](https://docs.base.org/ai-agents/quickstart.md) — Connect `mcp.base.org` to your assistant in 2 minutes; tabs for Claude, ChatGPT, Claude Code, Codex, Cursor, Hermes + +### Guides +- [Check Balance & Portfolio](https://docs.base.org/ai-agents/guides/check-balance.md) — Read tools: list wallets, fetch token balances, total portfolio USD value +- [Send Tokens](https://docs.base.org/ai-agents/guides/send-tokens.md) — `send` tool: ETH or any ERC-20 to a 0x address, ENS name, basename, or cb.id name +- [Swap Tokens](https://docs.base.org/ai-agents/guides/swap-tokens.md) — `swap` tool: any-to-any via the Coinbase swap service, returns route and price impact before approval +- [View Transaction History](https://docs.base.org/ai-agents/guides/view-history.md) — Paginated history with asset and date filters +- [Sign Messages](https://docs.base.org/ai-agents/guides/sign-messages.md) — `sign` tool: type `0x45` for personal_sign (EIP-191, SIWE) and `0x01` for EIP-712 typed data (permits, protocol auth) +- [Execute Contract Calls](https://docs.base.org/ai-agents/guides/batch-calls.md) — `send_calls` tool: array of `{ to, data, value }` items committed under one user approval; the primitive plugins build on +- [Builder Codes for Agents](https://docs.base.org/ai-agents/guides/agent-builder-codes.md) — Register on Base.dev and append your builder code to every tx for attribution and revenue share + +### Plugins +- [Moonwell](https://docs.base.org/ai-agents/plugins/moonwell.md) — Supply, borrow, and claim on Moonwell using `web_request` + Base Account; no extra MCP server required +- [Morpho](https://docs.base.org/ai-agents/plugins/morpho.md) — Vaults and Morpho Blue markets via the Morpho MCP at `mcp.morpho.org`; prepare unsigned calldata, sign through Base MCP +- [Uniswap](https://docs.base.org/ai-agents/plugins/uniswap.md) — Swaps and LP position management on Base via the Uniswap API; no extra MCP server required +- [Avantis](https://docs.base.org/ai-agents/plugins/avantis.md) — Perpetual futures trading on Base via Avantis; no extra MCP server required +- [Custom Plugin](https://docs.base.org/ai-agents/plugins/custom-plugin.md) — Author a plugin that returns unsigned calldata for Base MCP's `send_calls` to execute under one approval ## Key Concepts (excerpts) Source: `https://docs.base.org/ai-agents/index.md` -Base gives your AI agent the tools to operate as an independent economic actor: -- **Wallet**: Hold stablecoins, send payments, and sign transactions onchain — without exposing private keys in the agent's prompt context -- **Payments**: Pay for API access automatically with stablecoins using the x402 pay-per-request protocol -- **Identity**: Register in the ERC-8004 public directory so other agents and services can discover and verify your agent -- **Trading**: Use Flashblocks for 200ms preconfirmed state and exposed L1/L2 fees for cost-vs-speed tradeoffs -- **Skills**: Install wallet, payment, data, and swap skills to give your agent ready-to-use onchain capabilities - -Source: `https://docs.base.org/ai-agents/setup/wallet-setup.md` +Base MCP gives your AI assistant direct access to your Base Account — a smart wallet on Base. Connect once and your assistant can check balances, send funds, swap tokens, and sign messages. Every transaction requires your approval. -An onchain wallet gives your agent the ability to hold funds, authorize transactions, and sign messages. Never generate a wallet by prompting the agent to create a random private key — the key will appear in conversation context where it can be logged or leaked. +Approval flow for any write: +1. You ask the assistant to do something (e.g. "Send 10 USDC to alice.base.eth") +2. The assistant calls a Base MCP tool (e.g. `send`) +3. Base MCP constructs the transaction and returns `{ approvalUrl, requestId }` +4. The assistant shows you the approval URL +5. You open `keys.coinbase.com`, review the full transaction, and approve +6. The assistant polls `get_request_status(requestId)` until it confirms +7. The assistant reports the result -Wallet options: -- **Bankr** — Cross-chain wallet (Base, Ethereum, Solana, Polygon, Unichain); built-in swap tools; gas sponsored on supported chains. Install: `install the bankr skill from https://github.com/BankrBot/skills` -- **CDP Agentic Wallet** — Email OTP auth, no API keys in agent; native x402 support. Install: `npx skills add coinbase/agentic-wallet-skills` -- **Sponge Wallet** — REST API for wallet operations; built-in swap and x402 payment links. API at `https://api.wallet.paysponge.com` +Source: `https://docs.base.org/ai-agents/quickstart.md` -Source: `https://docs.base.org/ai-agents/payments/pay-for-services-with-x402.md` +Two installation paths: +- **Remote MCP** — Add `https://mcp.base.org` as a custom connector / MCP server in Claude, ChatGPT, Claude Code (`claude mcp add --transport http base https://mcp.base.org`), Codex (`codex mcp add base --url https://mcp.base.org/`), Cursor (deeplink), or Hermes. +- **Skill bundle** — Download or install the `base-mcp` skill (`npx skills add base/skills --skill base-mcp -a `) so the assistant gets curated context on every tool, prompt patterns, and approval handling. -The x402 protocol lets agents pay for API access automatically: -1. Agent sends HTTP request to a paid endpoint -2. Server responds with `402 Payment Required` and payment requirements -3. Agent signs a payment payload and retries with `PAYMENT-SIGNATURE` header -4. Facilitator verifies and settles the payment onchain -5. Server delivers the response +First call to any wallet tool opens `keys.coinbase.com` to authorize your Base Account. Click Allow once; subsequent writes still require per-transaction approval. -Source: `https://docs.base.org/ai-agents/payments/accepting-payments.md` +Source: `https://docs.base.org/ai-agents/guides/sign-messages.md` -Gate your endpoints with x402 to charge other agents: -- **OpenClaw monetize-service skill** — Simplest option; single prompt to expose a paid endpoint: `Set up a paid endpoint for my market data at $0.01 per request` -- **Sponge Wallet payment links** — Reusable payment links via REST API -- **x402-express** — `npm install x402-express express` for custom Express middleware +The `sign` tool requests a cryptographic signature from your Base Account. Two signature types: -Host a `SKILL.md` at `/.well-known/SKILL.md` to make your endpoint discoverable by agents. +| Type | Standard | Use case | +|------|----------|----------| +| `0x45` | personal_sign (EIP-191) | Plain text, SIWE auth challenges | +| `0x01` | EIP-712 typed data | Structured data, permits, protocol auth | -Source: `https://docs.base.org/ai-agents/trading/trade-execution.md` +Like all write tools, signing requires approval at `keys.coinbase.com` — the full message content is displayed before you confirm. -Base-specific trading advantages: -- **Flashblocks**: 200ms preconfirmed block state, 10× faster than the 2-second block -- **Exposed L1/L2 fee structure**: Enables explicit cost-vs-speed tradeoffs +Source: `https://docs.base.org/ai-agents/guides/batch-calls.md` -Fee calibration: -``` -maxFeePerGas = nextBaseFee * 2 + maxPriorityFeePerGas -``` +`send_calls` is the contract-call primitive. Pass an array of `{ to, data, value }` items and the entire batch executes under a single user approval. Plugins (Morpho, Moonwell, Uniswap, Avantis, custom) build on this — they generate the unsigned calldata, Base MCP signs and sends. -L1 fee decision rule — if this ratio exceeds 0.8, L1 fee dominates; consider deferring small trades: -``` -l1FeeUpperBound / (gasLimit × maxFeePerGas + l1FeeUpperBound) > 0.8 -``` +Source: `https://docs.base.org/ai-agents/plugins/morpho.md` -Key patterns: -- Connect to `mainnet-preconf.base.org` for all reads and submissions -- Simulate with `eth_simulateV1` against preconfirmed state before signing -- Poll `base_transactionStatus` (not `eth_getTransactionReceipt`) for ~200ms inclusion status -- Use `eth_feeHistory` over last 5–10 blocks with reward percentiles `[50, 90]` for fee calibration +Morpho handles the protocol layer; Base Account handles signing. Flow: +1. Assistant queries the Morpho MCP for vaults/markets/positions +2. Assistant asks Morpho to *prepare* a deposit/withdraw/borrow/repay — returns unsigned calldata +3. Assistant passes the calldata to Base MCP's `send_calls` +4. User approves at `keys.coinbase.com`; tx broadcasts +This pattern (prepare-then-sign) generalizes to any plugin: the protocol MCP or API produces calldata, Base MCP executes it. -## Skills (excerpts) +Source: `https://docs.base.org/ai-agents/plugins/custom-plugin.md` -Source: `https://docs.base.org/ai-agents/skills/index.md` +To author your own plugin, expose tools that return unsigned `{ to, data, value }` calls and let Base MCP's `send_calls` execute them. Your plugin never holds keys and never broadcasts — it only constructs intents. The user always sees the full call list at `keys.coinbase.com` before approving. -Skills are installable capabilities that give your AI agent specific onchain functionality — a wallet, payment handling, market data access, or trade execution. Install a skill and your agent knows what to do without any custom integration code. +Source: `https://docs.base.org/ai-agents/guides/agent-builder-codes.md` -Available skill categories: -- **Wallets** — Bankr, CDP Agentic Wallet, Sponge Wallet -- **Payments** — CDP Payment Skills (`search-for-service`, `pay-for-service`, `monetize-service`), Sponge x402 proxy -- **Trading** — CoinGecko price feeds, Alchemy Agentic Gateway, Swap Execution (Bankr/CDP/Sponge) -- **Base Chain** — Builder codes, network config, contract deployment, node ops -- **Migrations** — Farcaster mini app → web app, MiniKit → Farcaster, OnchainKit migration +Register your agent on Base.dev to get a Builder Code, then have it append the code to every transaction it submits. Builder Codes attribute onchain activity back to the agent (and its developer) for analytics and revenue-share programs without changing the transaction's behavior. diff --git a/docs/ai-agents/llms.txt b/docs/ai-agents/llms.txt index 557918f53..33aaa1df3 100644 --- a/docs/ai-agents/llms.txt +++ b/docs/ai-agents/llms.txt @@ -1,42 +1,27 @@ # https://docs.base.org/ai-agents/llms.txt -## AI Agents Documentation +## Base MCP Documentation -> Build AI agents that trade, earn, and transact autonomously on Base — with wallets, payments, identity, and skills built in. +> Base MCP connects any AI assistant to your Base Account — check balances, send funds, swap tokens, sign messages, and execute contract calls. Every write requires your approval at keys.coinbase.com. ## Overview -- [AI Agents on Base](https://docs.base.org/ai-agents/index.md) — What you can build and how the pieces fit together +- [Base MCP](https://docs.base.org/ai-agents/index.md) — What you can do with Base MCP and how the approval flow works ## Quickstart -- [Get Started with Payments](https://docs.base.org/ai-agents/quickstart/payments.md) — Build an agent that makes and accepts x402 payments in under 10 minutes -- [Build a Trading Agent](https://docs.base.org/ai-agents/quickstart/trading.md) — Fetch live market data and execute token swaps on Base - -## Setup -- [Wallet Setup](https://docs.base.org/ai-agents/setup/wallet-setup.md) — Give your agent a wallet to hold funds and sign transactions (Bankr, CDP, Sponge) -- [Agent Builder Codes](https://docs.base.org/ai-agents/setup/agent-builder-codes.md) — Register your agent with builder codes for onchain attribution -- [Agent Registration](https://docs.base.org/ai-agents/setup/agent-registration.md) — Register and verify your agent's identity onchain via ERC-8004 - -## Payments -- [Pay for Services with x402](https://docs.base.org/ai-agents/payments/pay-for-services-with-x402.md) — Use x402 to pay for API access per-request in stablecoins, no API keys required -- [Accepting Payments (x402)](https://docs.base.org/ai-agents/payments/accepting-payments.md) — Gate your agent's endpoints to charge other agents per request - -## Trading -- [Data Fetching](https://docs.base.org/ai-agents/trading/data-fetching.md) — Fetch live market data from onchain and offchain sources -- [Trade Execution](https://docs.base.org/ai-agents/trading/trade-execution.md) — Execute token swaps using Flashblocks and Base-specific onchain signals - -## Skills -- [Skills Overview](https://docs.base.org/ai-agents/skills/index.md) — Installable capabilities that give your AI agent a wallet, payments, market data access, or trade execution - -### Wallets -- [Bankr](https://docs.base.org/ai-agents/skills/wallets/bankr.md) — Cross-chain wallet with built-in swaps, gas sponsorship, and token launching (Base, ETH, Solana, Polygon, Unichain) -- [CDP Agentic Wallet](https://docs.base.org/ai-agents/skills/wallets/cdp-agentic-wallet.md) — Email OTP–authenticated wallet on Base with x402 payments built in -- [Sponge Wallet](https://docs.base.org/ai-agents/skills/wallets/sponge-wallet.md) — Multi-chain wallet with native x402 proxy, swaps, bridges, and banking - -### Payments -- [CDP Payment Skills](https://docs.base.org/ai-agents/skills/payments/cdp-payment-skills.md) — Discover, pay for, and monetize x402 API services via the CDP Agentic Wallet -- [Sponge x402](https://docs.base.org/ai-agents/skills/payments/sponge-x402.md) — Sponge's built-in x402 proxy — discover and pay for services automatically - -### Trading -- [CoinGecko](https://docs.base.org/ai-agents/skills/trading/coingecko.md) — Price feeds, market cap, and OHLCV data via x402 — no API key required -- [Alchemy Agentic Gateway](https://docs.base.org/ai-agents/skills/trading/alchemy-agentic-gateway.md) — Token balances, NFT metadata, portfolio data, and prices via x402 SIWE auth -- [Swap Execution](https://docs.base.org/ai-agents/skills/trading/swap-execution.md) — Execute token swaps using wallet-native skills across Bankr, CDP, and Sponge +- [Quickstart](https://docs.base.org/ai-agents/quickstart.md) — Connect mcp.base.org to your AI assistant in under 2 minutes + +## Guides +- [Check Balance & Portfolio](https://docs.base.org/ai-agents/guides/check-balance.md) — View token balances, portfolio value, and wallet details +- [Send Tokens](https://docs.base.org/ai-agents/guides/send-tokens.md) — Send ETH or any ERC-20 to an address, ENS name, basename, or cb.id +- [Swap Tokens](https://docs.base.org/ai-agents/guides/swap-tokens.md) — Swap between any two tokens on Base via the Coinbase swap service +- [View Transaction History](https://docs.base.org/ai-agents/guides/view-history.md) — Browse and filter past transactions on your Base Account +- [Sign Messages](https://docs.base.org/ai-agents/guides/sign-messages.md) — Sign EIP-712 typed data and personal messages with your Base Account +- [Execute Contract Calls](https://docs.base.org/ai-agents/guides/batch-calls.md) — Batch multiple contract interactions into a single user approval via `send_calls` +- [Builder Codes for Agents](https://docs.base.org/ai-agents/guides/agent-builder-codes.md) — Register your agent on Base.dev and append a builder code to every transaction + +## Plugins +- [Moonwell](https://docs.base.org/ai-agents/plugins/moonwell.md) — Lending and borrowing on Moonwell via `web_request` and Base Account (no extra MCP server) +- [Morpho](https://docs.base.org/ai-agents/plugins/morpho.md) — Lending and vault operations via the Morpho MCP, executed through your Base Account +- [Uniswap](https://docs.base.org/ai-agents/plugins/uniswap.md) — Token swaps and LP position management on Base using the Uniswap API and Base Account +- [Avantis](https://docs.base.org/ai-agents/plugins/avantis.md) — Perpetual futures trading on Base using Avantis and Base Account +- [Custom Plugin](https://docs.base.org/ai-agents/plugins/custom-plugin.md) — Build your own plugin that produces unsigned calldata and executes through Base MCP's `send_calls` diff --git a/docs/ai-agents/payments/accepting-payments.mdx b/docs/ai-agents/payments/accepting-payments.mdx deleted file mode 100644 index 8dcea8224..000000000 --- a/docs/ai-agents/payments/accepting-payments.mdx +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: "Accepting Payments (x402)" -description: "Gate your agent's endpoints with x402 to charge other agents per request" -keywords: ["x402 server", "accept agent payments", "monetize API agent", "x402-express", "monetize-service skill", "OpenClaw x402 server"] ---- - -import { AcceptingPaymentsDemo } from "/snippets/AcceptingPaymentsDemo.jsx" - -You can build agent services that charge other agents for access using x402. When a client calls your endpoint without paying, you return `402` with payment requirements. Once the client pays, the facilitator verifies the payment and your server delivers the response. - -## Mock Demo - - - -## Option 1 — OpenClaw monetize-service skill (simplest) - -With the CDP Agentic Wallet skills installed, expose a paid endpoint with a single prompt: - -```bash Terminal -npx skills add coinbase/agentic-wallet-skills -``` - -Then ask your agent: - -```text -Set up a paid endpoint for my market data at $0.01 per request -``` - -The `monetize-service` skill configures the x402 gating and deploys the endpoint. No server code required. - -[CDP Agentic Wallet skills →](https://docs.cdp.coinbase.com/agentic-wallet/skills) - -## Option 2 — Sponge Wallet payment links - -Create a reusable x402 payment link that other agents pay before accessing your service: - -```bash Terminal -curl -X POST "https://api.wallet.paysponge.com/api/payment-links" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{ - "amount": "0.01", - "description": "Access to my market data API" - }' -``` - -Share the returned payment link URL with clients. Check payment status: - -```bash Terminal -curl "https://api.wallet.paysponge.com/api/payment-links/{paymentLinkId}" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" -``` - -[Sponge Wallet docs →](https://wallet.paysponge.com/skill.md) - -## Option 3 — Custom server with x402-express - -Use the `x402-express` package to add payment gating to any Express endpoint: - -```bash Terminal -npm install x402-express express -``` - -```typescript TypeScript -import express from "express"; -import { paymentMiddleware } from "x402-express"; - -const app = express(); - -app.use( - paymentMiddleware( - "0xYourAgentWalletAddress", - { - "/api/data": { - price: "$0.01", - network: "base-sepolia", - }, - } - ) -); - -app.get("/api/data", (req, res) => { - res.json({ data: "premium content" }); -}); - -app.listen(3000); -``` - -The middleware returns `402` to unpaid callers. The CDP facilitator handles verification and onchain settlement. - -[x402 seller quickstart →](https://docs.cdp.coinbase.com/x402/docs/client-server-model) - -## Configuring facilitators - -A facilitator is the off-chain service that verifies payment payloads and settles payments onchain. Two options: - -| Facilitator | When to use | -|-------------|-------------| -| **CDP facilitator** (default) | Production — requires CDP API key, supports Base and Solana | -| **Public testnet facilitator** | Development — no API key, Base Sepolia only | - -The public testnet facilitator endpoint is `https://www.x402.org/facilitator`. Switch to the CDP facilitator for mainnet. See the [x402 client-server model](https://docs.cdp.coinbase.com/x402/docs/client-server-model) for full endpoint details. - -## Pricing and payment terms - -- Set prices in USD (e.g., `"$0.01"`) — the middleware converts to the appropriate token amount -- Payments settle in USDC on Base by default -- There is no minimum payment — even fractions of a cent are supported -- Your wallet receives payment directly — no platform fee from the protocol - -## Make your endpoint discoverable - -Host a `SKILL.md` file at `/.well-known/SKILL.md` describing your endpoint's inputs, outputs, pricing, and authentication requirements. Agents discover your service by checking this path. - -```markdown SKILL.md template -# Your Agent Service - -## Description -What your service does, in plain language. - -## Endpoints - -### GET /api/data -- **Description:** Returns premium market data -- **Payment:** $0.01 per request via x402 (Base, USDC) -- **Output:** JSON with current prices and volume - -## Authentication -x402 payment required. No API key needed. -``` - -Register your service in the ERC-8004 registry so agents can discover it by category — see [agent registration](/ai-agents/setup/agent-registration). - -## Related - - - - How x402 works from the client side. - - - - Facilitator endpoints and protocol addresses. - - diff --git a/docs/ai-agents/payments/pay-for-services-with-x402.mdx b/docs/ai-agents/payments/pay-for-services-with-x402.mdx deleted file mode 100644 index 6153baaab..000000000 --- a/docs/ai-agents/payments/pay-for-services-with-x402.mdx +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: "Pay for APIs & Services (x402)" -description: "How x402 works, how your agent makes payments, and which networks and tokens are supported" -keywords: ["x402 protocol", "autonomous agent payments", "machine payments protocol", "programmatic payments", "AI agent payments", "agentic payments", "HTTP 402 payment required", "x402 Base"] ---- - -import { X402PayDemo } from "/snippets/x402PayDemo.jsx" - -x402 is a payment protocol built on HTTP status code `402 Payment Required` — a code in the HTTP spec since the 1990s, finally put to use. It lets agents pay for API access with stablecoins per-request, with no subscriptions or API keys required. - -## Mock Demo - - - -## Payment flow - - - - Your agent sends a standard HTTP request to an API endpoint, just like any other API call. - - - - Instead of returning data, the server responds with a `402` status code and includes payment requirements in the `PAYMENT-REQUIRED` header: how much it costs, which token, and on which network. - - - - Your agent's wallet constructs a signed payment payload and resubmits the request with a `PAYMENT-SIGNATURE` header. No human approval needed. - - - - The server verifies the payment via a facilitator, settles onchain, and returns the requested data. The entire flow takes seconds. - - - -Any agent with a funded wallet can pay for any x402-enabled API — no pre-existing relationship or account required. - -[Learn more about x402 →](https://docs.cdp.coinbase.com/x402/docs/client-server-model) - -## Making x402 requests - -### CDP Agentic Wallet - -With the [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) skills installed, your agent handles x402 payments automatically: - -```bash Terminal -npx skills add coinbase/agentic-wallet-skills -``` - -**Discover available services** using the [`search-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/search-for-service) skill: - -```bash Terminal -npx awal@latest x402 bazaar search "weather forecast" -``` - -**Call a paid service** using the [`pay-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) skill: - -```bash Terminal -npx awal@latest x402 pay https://example.com/api/weather \ - -X POST \ - -d '{"query": "New York"}' -``` - -Or prompt your agent directly: - -```text -Find APIs for sentiment analysis -Call that weather API and get the forecast for New York -``` - -The [`search-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/search-for-service) and [`pay-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) skills handle discovery, payment, and retries. - -[CDP Agentic Wallet skills →](https://docs.cdp.coinbase.com/agentic-wallet/skills) - -### Sponge Wallet - -[Sponge Wallet](https://www.paysponge.com) has a built-in x402 proxy that discovers services and handles payment automatically: - -**Step 1 — Discover a service:** - -```bash Terminal -curl "https://api.wallet.paysponge.com/api/discover?query=weather+forecast" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" -``` - -**Step 2 — Get service details** (required — do not skip): - -```bash Terminal -curl "https://api.wallet.paysponge.com/api/discover/{serviceId}" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" -``` - -This returns the `baseUrl`, endpoint paths, parameters, and pricing. - -**Step 3 — Call the service** (payment is automatic): - -```bash Terminal -curl -X POST "https://api.wallet.paysponge.com/api/x402/fetch" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{ - "url": "https://{baseUrl}/{endpointPath}", - "method": "POST", - "body": { "query": "New York" }, - "preferred_chain": "base" - }' -``` - -[Sponge Wallet](https://www.paysponge.com) detects the `402`, pays in USDC from your wallet, and returns the API response. - -[Sponge Wallet docs →](https://wallet.paysponge.com/skill.md) - -## Error handling and retries - -| Status | Meaning | What to do | -|--------|---------|------------| -| `402` | Payment required | Parse `PAYMENT-REQUIRED` header and pay | -| `402` with `X-Payment-Error` | Payment rejected | Check wallet balance and token approval | -| `408` | Payment timeout | Retry with same or higher payment | -| `429` | Rate limit | Back off and retry after the indicated delay | - -The [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) [`pay-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) skill and [Sponge Wallet's](https://www.paysponge.com) x402 proxy handle retries automatically. If you're building a custom client, parse the `PAYMENT-REQUIRED` header, construct the signed payload, and retry with the `PAYMENT-SIGNATURE` header. - - - If you're using [Bankr](https://bankr.bot), no setup is required. Just prompt it directly: - - ```text - Get the current ETH price from a paid data source - Find a weather API and get the forecast for New York - Get the token balances for 0xYourAddress on Base - ``` - - Bankr handles service discovery, payment, and data extraction automatically. - - -## Video Tutorial - - -## Related - - - - Gate your own endpoints and charge other agents per request. - - - - Facilitator endpoints and protocol addresses. - - diff --git a/docs/ai-agents/plugins/custom-plugins.mdx b/docs/ai-agents/plugins/custom-plugins.mdx new file mode 100644 index 000000000..a2cd83120 --- /dev/null +++ b/docs/ai-agents/plugins/custom-plugins.mdx @@ -0,0 +1,199 @@ +--- +title: "Custom Plugins" +description: "Build your own plugin that produces unsigned calldata and executes through Base MCP's send_calls" +keywords: ["custom Base MCP plugin", "AI agent plugin Base", "send_calls custom protocol", "Base MCP web_request plugin", "build MCP plugin"] +--- + +A plugin is a markdown spec that teaches your assistant how to call an external API (or another MCP server), translate the response into a `calls` array, and execute it through Base MCP's `send_calls`. The [native plugins](/ai-agents/plugins/native-plugins) — Morpho, Moonwell, Uniswap, and Avantis — all follow the same shape. This page shows how to write your own. + +## When you need one + +Write a plugin when your protocol either has an HTTP "tx-builder" that returns unsigned calldata, or exposes its own MCP server. If it has neither, you can still use SDKs to encode the call directly, but it might not be useful for Claude and ChatGPT consumer apps. + +## Anatomy of a plugin + +Every plugin file contains four sections: + + + + A `STOP` notice that forces the assistant to complete Base MCP onboarding (`get_wallets`, disclaimer) before doing anything else. The user's wallet address — needed for every prepare call — is only confirmed during detection. + + + Document the GET endpoints that return state — balances, positions, market data — and the units they use. + + POST endpoints are not supported in Claude and ChatGPT consumer apps. + + + + Document the endpoints (or MCP tools) that return unsigned calldata. State the exact response shape so the assistant knows which fields map to `to`, `value`, and `data`. + + + Show the assistant how to convert the prepare response into the `calls` array passed to `send_calls`. + + + + +Native plugins have access to the `web_request` tool from Base MCP, which allows them to make GET and POST requests to any hostname. +Custom plugins do not have access to the `web_request` tool, so they must use GET endpoints to be compatible with Claude and ChatGPT consumer apps. + + +## How it works + +```mermaid +sequenceDiagram + participant User + participant AI as AI Assistant + participant API as Your API + participant BA as Base MCP + + User->>AI: "Do on " + AI->>API: GET /read (validate state) + API-->>AI: state + AI->>API: GET /prepare/?from=
&... + API-->>AI: { to, value, data, chainId } + AI->>BA: send_calls(chainId, calls=[...]) + BA-->>AI: { approvalUrl, requestId } + AI-->>User: "Please approve: [link]" + User-->>AI: approved + AI->>BA: get_request_status(requestId) + BA-->>AI: confirmed +``` + +## Build it + +### 1. Pick a response shape + +Your prepare endpoint should return a single object with the fields `send_calls` needs. Two common shapes: + +**Envelope** (Avantis-style): + +```json +{ + "ok": true, + "data": { + "to": "0x...", + "value": "0x0", + "data": "0x...", + "chainId": 8453 + } +} +``` + +**Ordered batch** (Moonwell-style) — for when approval, enter-market, and the action are separate calls: + +```json +{ + "transactions": [ + { "step": "approve", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 }, + { "step": "action", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 } + ] +} +``` + +Either works. The batch shape is preferable when allowance or registration steps must run before the action — `send_calls` executes them atomically in one approval. + +### 2. Write the plugin spec + +Use this template as `plugins/my-protocol.md` in your skill, or as an `.mdx` page if you're publishing docs. + +````markdown +# My Protocol Plugin + +> [!IMPORTANT] +> ## STOP — COMPLETE ONBOARDING BEFORE USING THIS PLUGIN +> +> Before calling any My Protocol endpoint, you MUST complete the Base MCP onboarding flow: +> 1. Call `get_wallets` (Detection) +> 2. Present wallet status and disclaimer (Onboarding) +> +> The user's wallet address — required by every prepare call — is only confirmed during Detection. + +My Protocol is a . Fetch unsigned calldata from the My Protocol API, then execute via Base MCP's `send_calls`. + +**Fetching calldata:** the My Protocol API is not on the Base MCP `web_request` allowlist. Construct the prepare URL as a GET with all parameters in the query string. If `web_request` rejects it, fetch through whatever capability the harness exposes, or ask the user to paste the response into the chat. Then continue with `send_calls`. + +**Supported chain:** Base mainnet (`8453` / `0x2105`). + +--- + +## Read endpoints + +``` +GET https://api.myprotocol.xyz/v1/state/
+``` + +## Prepare endpoint + +``` +GET https://api.myprotocol.xyz/v1/prepare/?from=
&amount= +``` + +Response: + +```json +{ + "transactions": [ + { "step": "approve", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 }, + { "step": "action", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 } + ] +} +``` + +## send_calls mapping + +Pass every `transactions[*]` to `send_calls`: + +```json +{ + "chainId": "0x2105", + "calls": [ + { "to": "", "value": "", "data": "" } + ] +} +``` + +## Orchestration pattern + +``` +1. get_wallets -> address +2. Fetch GET /state/
-> validate balances/preconditions +3. Fetch GET /prepare/?from=
&amount= + (if web_request rejects the host, fetch directly or ask the user to paste the JSON) +4. send_calls(chainId=0x2105, calls from transactions[]) +5. User approves -> get_request_status(requestId) +``` +```` + +### 3. Wire it into `send_calls` + +The contract between your prepare endpoint and Base MCP is exactly this object: + +```json +{ + "chainId": "0x2105", + "calls": [ + { "to": "0x...", "value": "0x0", "data": "0x..." } + ] +} +``` + +`chainId` is hex (`0x2105` for Base mainnet, `0x14a34` for Base Sepolia). `value` defaults to `0x0` if omitted. The assistant calls `send_calls` once with the full batch — the user approves once, and all calls execute atomically. + +## Patterns to copy + +| Pattern | When to use | Example | +|---------|-------------|---------| +| Single-call envelope | One action, one tx | [Avantis](https://github.com/base/skills/blob/main/skills/base-mcp/plugins/avantis.md) | +| Ordered batch | Approval + action must be atomic | [Moonwell](https://github.com/base/skills/blob/main/skills/base-mcp/plugins/moonwell.md) | +| Sibling MCP server | Protocol already runs an MCP server with `prepare_*` tools | [Morpho](https://github.com/base/skills/blob/main/skills/base-mcp/plugins/morpho.md) | +| Multi-endpoint flow | Quote, approve, swap as separate calls | [Uniswap](https://github.com/base/skills/blob/main/skills/base-mcp/plugins/uniswap.md) | +## Related + + + + Full guide to `send_calls` and batching. + + + Reference implementations for the ordered-batch, sibling-MCP, and multi-endpoint patterns. + + diff --git a/docs/ai-agents/plugins/index.mdx b/docs/ai-agents/plugins/index.mdx new file mode 100644 index 000000000..c8fb71fcb --- /dev/null +++ b/docs/ai-agents/plugins/index.mdx @@ -0,0 +1,102 @@ +--- +title: "Overview" +description: "How the Base MCP Skill works and how plugins extend it" +keywords: ["Base MCP skill", "Anthropic Skills", "SKILL.md", "Base MCP plugins", "send_calls plugin pattern"] +--- + + +This page describes how the Base MCP Skill and Plugins work under the hood. If you just want to install it in Claude Desktop, ChatGPT, Cursor, or Claude Code, head to the [Quickstart](/ai-agents/quickstart). + + +## Why a skill on top of the MCP server + +The MCP server exposes capabilities. Without context, models misuse them — calling write tools without warning the user, skipping approval, inventing parameters, or failing to detect that the server isn't connected at all. The skill closes that gap. Specifically, `SKILL.md` adds: + +- **Detection and onboarding** — every session starts with `get_wallets` so the assistant knows whether the MCP is connected and which Base account it's working with. +- **Approval mode** — write tools (`send`, `swap`, `sign`, `send_calls`) return `{ approvalUrl, requestId }`. The skill tells the model to present the link, wait, then poll `get_request_status` — never to claim success before confirmation. +- **Tone rules** — load-bearing language conventions (e.g. "onchain", never "web3") and a beginner/sophisticated detection heuristic so responses match the user. +- **A plugin contract** — a documented prepare → `send_calls` pattern that lets external protocols extend the skill without modifying the MCP server. + +## How SKILL.md is loaded + +Skills use progressive disclosure. The model loads `SKILL.md` at session start (cheap — ~100 lines) and reads `references/*.md` and `plugins/*.md` only when a relevant task arises. + +The shape of the Base MCP skill: + + + + + + + + + + + + + + + + + + + + +`SKILL.md` itself defines the session flow: **Detection → Tone → Onboarding → Tools table → Plugins table**. When the user asks to swap tokens, the model pulls in `references/tools.md`. When they ask about a Morpho vault, the model pulls in `plugins/morpho.md`. Nothing else is in context. + +Read the canonical file at [`skills/base-mcp/SKILL.md`](https://github.com/base/skills/blob/main/skills/base-mcp/SKILL.md). + +## How plugins extend the skill + +A plugin is a markdown spec — one file in `plugins/` — that teaches the assistant how to drive an external protocol through `send_calls`. The contract is the same whether the protocol exposes an HTTP "tx-builder" or its own sibling MCP server: + +```mermaid +sequenceDiagram + participant User + participant AI as AI Assistant + participant Protocol as Protocol API / MCP + participant BA as Base MCP + + User->>AI: "Do on " + AI->>Protocol: read state (balances, markets, positions) + Protocol-->>AI: state + AI->>Protocol: prepare (unsigned calldata) + Protocol-->>AI: { to, value, data, chainId } + AI->>BA: send_calls(chainId, calls=[...]) + BA-->>AI: { approvalUrl, requestId } + AI-->>User: "Please approve: [link]" + User-->>AI: approved + AI->>BA: get_request_status(requestId) + BA-->>AI: confirmed +``` + +Every plugin file follows the same four-section shape: + + + + A `STOP` notice forcing the assistant to complete Base MCP detection and onboarding before touching the plugin's tools. + + + The GET endpoints (or read tools) that return state — balances, positions, market data. + + + The endpoints (or `prepare_*` tools) that return unsigned calldata, with the exact response shape so the model knows which fields map to `to`, `value`, and `data`. + + + How to turn the prepare response into the `calls` array passed to Base MCP's `send_calls`. + + + +Base MCP passes the calldata for the user to sign and submit. The protocol never touches private keys. + +## Native vs custom plugins + + + + Morpho, Moonwell, Uniswap, Avantis — authored by the Base team and shipped with the skill. + + + Write your own markdown spec for any protocol with an HTTP tx-builder or MCP server. + + + diff --git a/docs/ai-agents/plugins/native/aerodrome.mdx b/docs/ai-agents/plugins/native/aerodrome.mdx new file mode 100644 index 000000000..6f1e9993c --- /dev/null +++ b/docs/ai-agents/plugins/native/aerodrome.mdx @@ -0,0 +1,43 @@ +--- +title: "Aerodrome" +description: "Token swaps and basic-pool liquidity on Aerodrome (the leading DEX on Base) via sugar-sdk + Base MCP. CLI-only." +keywords: ["Aerodrome plugin", "Base MCP Aerodrome", "Aerodrome swap Base", "Aerodrome LP", "sugar-sdk", "Velodrome SDK"] +--- + +The Aerodrome plugin covers token swaps and basic-pool (vAMM/sAMM) liquidity provision on Base. It uses the [Velodrome sugar-sdk](https://github.com/velodrome-finance/sugar-sdk) Python library locally to discover pools, build swap routes, and prepare deposit/withdraw/stake/claim calldata. Calldata is then submitted through Base MCP's `send_calls` for user approval. + +**Chain:** Base mainnet. + +**Operations:** swap quote/execute (basic pools), basic pool deposit/withdraw, position queries, gauge stake/unstake, claim emissions/fees. + + +**CLI-only plugin.** This plugin runs Python locally via a Bash/shell tool. It works in **Claude Code, Codex, Cursor terminal**, and similar CLI harnesses — it does **not** work in chat-only environments (ChatGPT, Claude.ai) because there's no shell to run sugar-sdk in. + + +## Try it + +```text Swap +Swap 0.001 ETH for USDC on Aerodrome +``` + +```text Provide liquidity +Add 0.001 ETH and matching USDC to the vAMM-WETH/USDC pool on Aerodrome +``` + +```text Withdraw +Withdraw all my Aerodrome basic LP positions +``` + +## Pattern + +sugar-sdk's write methods (`swap_from_quote`, `deposit`, `withdraw`, `stake`, `claim_emissions`) normally sign and broadcast transactions with a local private key. The plugin monkey-patches `sign_and_send_tx` to capture the unsigned `{to, data, value}` instead, then passes the captured calls to Base MCP's `send_calls` for user approval. The same bridge handles ERC-20 approvals (USDC/WETH), Universal Router swap execution, and Router LP operations. + + +The public `https://mainnet.base.org` RPC enforces a 10-call-per-batch limit and rate-limits concurrent batches, which breaks sugar-sdk's default `asyncio.gather` pagination. The plugin reference includes a `patches.py` that switches to sequential batching to work around this. For production usage prefer a paid RPC (Alchemy, QuickNode). + + +## Reference + + + Setup, RPC compatibility patches, calldata-bridge code, swap/LP orchestration patterns, and what works vs. what doesn't on the public RPC. + diff --git a/docs/ai-agents/plugins/native/avantis.mdx b/docs/ai-agents/plugins/native/avantis.mdx new file mode 100644 index 000000000..8b9ccf71e --- /dev/null +++ b/docs/ai-agents/plugins/native/avantis.mdx @@ -0,0 +1,43 @@ +--- +title: "Avantis" +description: "Perpetual futures on Base via the Avantis tx-builder" +keywords: ["Avantis plugin", "Base MCP Avantis", "perpetual futures Base", "Avantis perps", "Base perps trading"] +--- + +Avantis is a perpetual futures DEX on Base mainnet. The plugin fetches unsigned calldata from the Avantis tx-builder (`tx-builder.avantisfi.com`) and executes it through Base MCP's `send_calls`. Collateral is USDC; ETH is used only for gas and execution fees. + +**Chain:** Base mainnet. + +**Operations:** open trade (market, limit, stop-limit, zero-fee), close, cancel, update margin, set TP/SL, approve USDC, set/remove delegate, plus reads for pairs, positions, limit orders, and PnL history. + +## Try it + +```text Open long +Open a 10x long BTC/USD with 100 USDC collateral on Avantis +``` + +```text Limit order +Place a limit long on ETH/USD at 3000 with 50 USDC at 5x +``` + +```text Close +Close my BTC/USD position on Avantis +``` + +```text Set TP/SL +Set TP to 100000 and SL to 80000 on my BTC long +``` + +## Pattern + +Every prepare endpoint returns a single-call envelope (`{ ok, data: { to, value, data, chainId } }`) that maps directly to `send_calls`. Approval and trade can be batched into one approval. The plugin reads `/v2/trading` to validate pair, leverage, and minimum notional before building the open call, and reads `core /user-data` to resolve real position/order indices for management actions. + + +`tx-builder.avantisfi.com`, `data.avantisfi.com`, `core.avantisfi.com`, and `api.avantisfi.com` must be on the Base MCP `web_request` allowlist. They already are for the hosted MCP at `mcp.base.org`. + + +## Reference + + + Endpoint inventory, parameters, unit/scaling rules, batching guidance, and error handling. + diff --git a/docs/ai-agents/plugins/native/bankr.mdx b/docs/ai-agents/plugins/native/bankr.mdx new file mode 100644 index 000000000..b77cb94de --- /dev/null +++ b/docs/ai-agents/plugins/native/bankr.mdx @@ -0,0 +1,43 @@ +--- +title: "Bankr" +description: "Discover the latest token launches on Base via the Bankr API and buy them with Base MCP's swap tool." +keywords: ["Bankr plugin", "Base MCP Bankr", "token launches Base", "buy new tokens Base", "Doppler launches"] +--- + +The Bankr plugin uses the [Bankr](https://bankr.bot) public API to surface the latest deployed token launches on Base, then routes the actual purchase through Base MCP's `swap` tool. Bankr is the discovery layer; the swap is a regular `swap` call paying ETH (or USDC) for the target ERC-20. + +**Chain:** Base mainnet. + +**Operations:** list latest launches, filter by deployer or recency, and buy a chosen token with `swap`. + +## Try it + +```text Browse +Show me the latest token launches on Base +``` + +```text Filter +Are there any launches from @0xtinylabs in the last hour? +``` + +```text Buy +Buy 0.001 ETH worth of the newest token on Bankr +``` + +## Pattern + +The plugin makes one `web_request` to `https://api.bankr.bot/token-launches` for the discovery feed, filters/presents the results client-side, and waits for the user to pick a token and amount. The buy itself is a single Base MCP `swap` call (`tokenFrom=ETH` or `USDC`, `tokenTo=`) — same approval flow as any other write. + + +The Bankr feed is unfiltered. Listed tokens are not vetted, audited, or endorsed by Base — many are low-liquidity meme launches. Always confirm symbol, address, and amount with the user before swapping, and use elevated slippage (5%+ default) for fresh launches. + + + +`api.bankr.bot` must be on the Base MCP `web_request` allowlist. If a request is rejected, fall back to the harness's HTTP/fetch tool if one is available. + + +## Reference + + + API response shape, orchestration steps, symbol-collision and adversarial-metadata safety notes, and slippage thresholds tuned for new launches. + diff --git a/docs/ai-agents/plugins/native/index.mdx b/docs/ai-agents/plugins/native/index.mdx new file mode 100644 index 000000000..33e5a4585 --- /dev/null +++ b/docs/ai-agents/plugins/native/index.mdx @@ -0,0 +1,77 @@ +--- +title: "Overview" +description: "Plugins authored by the Base team that ship with the Base MCP skill" +keywords: ["Base MCP plugins", "Morpho plugin", "Moonwell plugin", "Uniswap plugin", "Avantis plugin", "Aerodrome plugin", "Virtuals plugin", "Bankr plugin"] +--- + +Seven plugins ship in the Base MCP skill — Morpho, Moonwell, Uniswap, Avantis, Aerodrome, Virtuals, and Bankr. They're authored by the Base team and live alongside `SKILL.md` in [`github.com/base/skills`](https://github.com/base/skills/tree/main/skills/base-mcp/plugins). The assistant loads each spec on demand when a relevant request comes in. + +All follow the same prepare → `send_calls` (or `swap`) contract described in the [Overview](/ai-agents/plugins). The plugin spec is the single source of truth; the cards below are pointers, not duplicates. + +## The plugins + + + + Lending and vaults on Base via Morpho's sibling MCP server. Deposit, borrow, withdraw, supply/withdraw collateral. + + + Compound v2 lending on Base and Optimism. Supply, borrow, withdraw, repay — approval and action batched into one approval. + + + Token swaps (proxy-approval, no Permit2) and V2/V3/V4 LP position management on Base. + + + Perpetual futures on Base. Open, close, manage margin, set TP/SL, plus market, limit, stop-limit, and zero-fee orders. + + + Token swaps and basic-pool liquidity on Aerodrome via sugar-sdk. Requires a CLI harness (Claude Code, Codex, Cursor terminal). + + + Create and operate Virtuals (ACP) AI agents — payment cards, email identities, agent management — signed in via Base MCP. + + + Discover the latest token launches on Base via the Bankr API and buy them with Base MCP's `swap` tool. + + + +## Using a native plugin + + + + Connect `mcp.base.org` and load the skill in your client. See the [Quickstart](/ai-agents/quickstart) for Claude, Claude Desktop, ChatGPT, Cursor, and Claude Code. + + + Just describe what you want. The assistant pulls the relevant plugin spec into context automatically. + + ```text Morpho + Find the best USDC vault on Base by APY and deposit 100 USDC + ``` + + ```text Moonwell + Borrow 500 USDC against my collateral on Moonwell + ``` + + ```text Uniswap + Create a ETH/USDC LP position on Uniswap + ``` + + ```text Avantis + Open a 10x long BTC/USD with 100 USDC collateral + ``` + + + The plugin returns unsigned calldata; Base MCP's `send_calls` wraps it into a single approval. Open the `approvalUrl` at `keys.coinbase.com`, approve, and prompt the assistant again and it will poll `get_request_status` until confirmed. + + + + +Plugins that use `web_request` only reach protocols whose hostnames are on the Base MCP allowlist. The four native plugins above are already covered. To call a protocol that isn't, see [Build a custom plugin](/ai-agents/plugins/custom-plugin). + + +## Build your own + + + + Same prepare → `send_calls` pattern, written as a markdown spec for any protocol with an HTTP tx-builder or MCP server. + + diff --git a/docs/ai-agents/plugins/native/moonwell.mdx b/docs/ai-agents/plugins/native/moonwell.mdx new file mode 100644 index 000000000..13cf3c184 --- /dev/null +++ b/docs/ai-agents/plugins/native/moonwell.mdx @@ -0,0 +1,39 @@ +--- +title: "Moonwell" +description: "Compound v2 lending on Base and Optimism via the Moonwell HTTP API" +keywords: ["Moonwell plugin", "Base MCP Moonwell", "Moonwell lending", "Compound v2 Base", "Moonwell borrow"] +--- + +Moonwell is a Compound v2 lending protocol on Base and Optimism. The plugin reads positions and rates from `api.moonwell.fi` and prepares unsigned calldata that Base MCP executes atomically through `send_calls` — including the `approve` and `enter-market` steps that precede each action. + +**Chains:** Base (8453), Optimism (10). + +**Operations:** supply, withdraw, borrow, repay, plus reads for markets, rates, positions, health, rewards, and token balances. + +## Try it + +```text Supply +Supply 100 USDC on Moonwell +``` + +```text Borrow +Borrow 500 USDC against my collateral on Moonwell +``` + +```text Health check +What's my Moonwell health factor on Base? +``` + +## Pattern + +The Moonwell API returns an ordered `transactions[]` array — `approve`, `enter-market`, then the protocol action. The plugin maps all entries into a single `send_calls` batch so the user approves once. + + +`api.moonwell.fi` must be on the Base MCP `web_request` allowlist. It already is for the hosted MCP at `mcp.base.org`. + + +## Reference + + + Endpoint inventory, response shapes, mToken notes, and health factor guide. + diff --git a/docs/ai-agents/plugins/native/morpho.mdx b/docs/ai-agents/plugins/native/morpho.mdx new file mode 100644 index 000000000..8365d7add --- /dev/null +++ b/docs/ai-agents/plugins/native/morpho.mdx @@ -0,0 +1,48 @@ +--- +title: "Morpho" +description: "Lending and vaults on Base via Morpho's sibling MCP server" +keywords: ["Morpho plugin", "Base MCP Morpho", "Morpho lending", "Morpho vaults", "Base lending"] +--- + +Morpho is a lending protocol on Base. The plugin drives the [Morpho MCP server](https://mcp.morpho.org/) for vault discovery, position queries, and prepare-style tools that return unsigned calldata. Base MCP's `send_calls` wraps the calldata into a single approval. + +**Chain:** Base mainnet. + +**Operations:** deposit, withdraw, supply, borrow, repay, supply/withdraw collateral, plus reads for vaults, markets, and positions. + +## Install alongside Base MCP + +```json mcp.json +{ + "mcpServers": { + "base-account": { "url": "https://mcp.base.org" }, + "morpho": { "url": "https://mcp.morpho.org/" } + } +} +``` + +Claude Code: `claude mcp add morpho --transport http https://mcp.morpho.org/` + +## Try it + +```text Find a vault +Find the best USDC vault on Base by APY and deposit 100 USDC +``` + +```text Check positions +Show all my Morpho positions on Base +``` + +```text Health check +Check if my Morpho borrow position is healthy +``` + +## Pattern + +Morpho's `prepare_*` tools return `{ calls, chainId }` — passed straight to `send_calls`, which returns an approval URL. The assistant polls `get_request_status` once you confirm. + +## Reference + + + Tool inventory, response shapes, and orchestration details. + diff --git a/docs/ai-agents/plugins/native/uniswap.mdx b/docs/ai-agents/plugins/native/uniswap.mdx new file mode 100644 index 000000000..f857f06d5 --- /dev/null +++ b/docs/ai-agents/plugins/native/uniswap.mdx @@ -0,0 +1,39 @@ +--- +title: "Uniswap" +description: "Token swaps and V2/V3/V4 LP positions on Base via the Uniswap trade and liquidity APIs" +keywords: ["Uniswap plugin", "Base MCP Uniswap", "Uniswap swap Base", "Uniswap LP", "Uniswap V4 Base"] +--- + +The Uniswap plugin covers token swaps (proxy-approval flow, no Permit2 signing) and LP position management for V2, V3, and V4 on Base. It fetches unsigned calldata from Uniswap's trade and liquidity APIs and executes it through Base MCP's `send_calls`. + +**Chain:** Base mainnet. + +**Operations:** swap quote/approval/execute; create, increase, decrease V3/V4 positions; create V2 positions; collect LP fees. + +## Try it + +```text Swap +Swap 100 USDC for ETH on Base +``` + +```text Create LP +Create a V4 ETH/USDC LP position on Base with 0.1 ETH +``` + +```text Collect fees +Collect fees from my Uniswap LP positions +``` + +## Pattern + +Swap flow is three calls — `/check_approval`, `/quote`, `/swap` — batched into one `send_calls` so approval and swap execute together. LP flow follows the same shape: `/lp/pool_info` (if needed), `/lp/check_approval`, then the action endpoint (`/lp/create`, `/lp/increase`, `/lp/decrease`, `/lp/claim_fees`). + + +`trade-api.gateway.uniswap.org` and `liquidity.api.uniswap.org` must be on the Base MCP `web_request` allowlist. They already are for the hosted MCP at `mcp.base.org`. + + +## Reference + + + Endpoint inventory, headers, response shapes, and orchestration for swap and LP flows. + diff --git a/docs/ai-agents/plugins/native/virtuals.mdx b/docs/ai-agents/plugins/native/virtuals.mdx new file mode 100644 index 000000000..557d2c6f4 --- /dev/null +++ b/docs/ai-agents/plugins/native/virtuals.mdx @@ -0,0 +1,62 @@ +--- +title: "Virtuals" +description: "Create and operate Virtuals (ACP) AI agents — payment cards, email identities, agent management — signed in via Base MCP." +keywords: ["Virtuals plugin", "ACP", "Agent Commerce Protocol", "Virtuals MCP", "Base MCP Virtuals", "agent cards", "agent email"] +--- + +The Virtuals plugin connects Base MCP to the [Virtuals](https://virtuals.io) Agent Commerce Protocol (ACP) MCP server. ACP is a platform for creating and operating autonomous AI agents that transact onchain, hold payment cards, and own email identities. Base MCP's wallet is used only to sign the SIWE login challenge — every subsequent Virtuals tool call carries a session JWT. + +**Server:** `https://mcp.acp.virtuals.io/` + +**Operations:** agent management (create / list / prepare-launch), agent cards (signup, issue, set limits, 3DS), agent email (identity, inbox, search, compose, reply, OTP/link extraction). + +## Try it + +```text Sign in +Log me into Virtuals +``` + +```text List agents +List all my Virtuals agents +``` + +```text Create everything +Create a Virtuals agent with email and a payment card +``` + +## Pattern + +Virtuals is **session-authenticated**: every tool requires a `token` parameter obtained via SIWE. The plugin orchestrates the round trip — `get_wallets` → `login_start` → `sign` (Base MCP) → user approves → `get_request_status` → `login_complete` — then reuses the JWT for the rest of the session. Use `login_refresh` when the ~1 hour token expires. + + +The Coinbase Smart Wallet sometimes returns an ERC-6492 wrapped signature instead of a plain ERC-1271 one, which Virtuals rejects with `Invalid SIWE signature`. Re-run the auth flow — repeated approvals typically resolve to a plain ERC-1271 signature within a few attempts. Don't try to unwrap the envelope manually. + + + +After auth, Virtuals operations route through the Virtuals backend (card issuance, email, agent ops) — not through Base MCP. Only the SIWE signature uses Base MCP. Don't echo card numbers, 3DS codes, OTPs, or email bodies to chat unless the user explicitly asks. + + +## Installation + +Run Base MCP and Virtuals side by side: + +```json +{ + "mcpServers": { + "base-account": { "url": "https://mcp.base.org" }, + "virtuals": { "url": "https://mcp.acp.virtuals.io/" } + } +} +``` + +Claude Code: + +```bash Terminal +claude mcp add virtuals --transport http https://mcp.acp.virtuals.io/ +``` + +## Reference + + + Step-by-step SIWE auth flow, troubleshooting for the six common signature-verification failure modes, and orchestration recipes for agent / card / email operations. + diff --git a/docs/ai-agents/quickstart.mdx b/docs/ai-agents/quickstart.mdx new file mode 100644 index 000000000..0d8c65dd4 --- /dev/null +++ b/docs/ai-agents/quickstart.mdx @@ -0,0 +1,231 @@ +--- +title: "Get Started with Base MCP" +description: "Connect Base MCP to your agent in under 2 minutes" +keywords: ["Base MCP quickstart", "mcp.base.org setup", "Claude Desktop MCP", "ChatGPT MCP", "Claude Code MCP wallet", "Cursor MCP", "Codex MCP", "Hermes MCP"] +--- + +import { WalletSetupDemo } from "/snippets/WalletSetupDemo.jsx" +import { AuthApprovalDemo } from "/snippets/AuthApprovalDemo.jsx" + +## Demo + + + +## Steps + + + + + + + Add to Claude + + + Works in Claude.ai (web, iOS, Android) and Claude Desktop. Click the button above, or: + + 1. Open **Customize → Connectors → Add custom connector** + 2. The **Add custom connector** modal opens + 3. Fill in: + - **Name**: `Base` + - **Remote MCP server URL**: `https://mcp.base.org` + 4. Click **Add** + 5. Next hit **Connect**, you will be directed to `keys.coinbase.com` to connect your Base Account. Click **Allow** once to authorize: + + + + + + Add to ChatGPT + + + Click the button above, or open **Settings → Connectors** manually. Then: + + 1. Enable **Developer Mode** if prompted (under Advanced) + 2. Click **Create** to open the **New App** modal + 3. Fill in: + - **Name**: `Base` + - **Description** (optional): `Wallet and onchain tools for Base` + - **MCP Server URL**: `https://mcp.base.org` + - **Authentication**: `OAuth` + 4. Check **I understand and want to continue** on the risk warning + 5. Click **Create** + 6. You will be automatically redirected to `keys.coinbase.com` to connect your Base Account. Click **Allow** once to authorize. + + + Run this in your terminal to add the server to the current project: + + ```bash Terminal + claude mcp add --transport http base https://mcp.base.org + ``` + + To install globally (available across all your projects): + + ```bash Terminal + claude mcp add --transport http --scope user base https://mcp.base.org + ``` + + Verify it connected: + + ```bash Terminal + claude mcp list + ``` + + The `base` server will show with a tool count once active. You can also run `/mcp` inside a Claude Code session to see server status. + + + ```bash Terminal + codex mcp add base --url https://mcp.base.org/ + ``` + + Or add to your `codex.toml`: + + ```toml codex.toml + [mcp_servers.base] + url = "https://mcp.base.org/" + ``` + + + + Add to Cursor + + + Or add manually to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): + + ```json mcp.json + { + "mcpServers": { + "base": { + "url": "https://mcp.base.org" + } + } + } + ``` + + Restart Cursor, then open **Settings → MCP** to confirm `base` shows as active. + + + Hand the agent this quickstart and let it install itself: + + ```text Prompt + Install the Base MCP server from https://docs.base.org/ai-agents/quickstart + ``` + + Hermes will fetch the page, write the entry to `~/.hermes/config.yaml`, and reload — no manual editing needed. + + **Manual install** — if you'd rather edit the config yourself: + + ```yaml ~/.hermes/config.yaml + mcp_servers: + base: + url: "https://mcp.base.org" + ``` + + Then start a Hermes chat (or run `/reload-mcp` inside an existing session) and Hermes will discover the tools automatically. + + + + + + + The `base-mcp` skill extends your assistant with pre-built prompts and workflows for wallet operations, token transfers, and DeFi interactions on Base. + + + + **Option 1: Paste this prompt into a new conversation** + + + Use Base MCP. Read `https://base-a060aa97-youssef-update-agents.mintlify.app/ai-agents/skills/SKILL.md` and follow its onboarding. If your built-in web tool can't fetch the link, use the `web_request` tool from Base MCP instead. + + + Nothing to install — Claude reads the skill on the fly and fetches each reference or plugin file only when it needs one. + + **Option 2: Install as a persistent skill** + + + Download for Claude + + + Click the button above to download `base-mcp.zip`, then: + + 1. In Claude Desktop or Claude.ai, open [**Customize → Skills**](https://claude.ai/customize/skills) + 2. Click **Upload skill** and select the downloaded `base-mcp.zip` + 3. Toggle the skill on + + Claude activates the skill automatically when relevant to your prompt. See [Use skills in Claude](https://support.claude.com/en/articles/12512180-use-skills-in-claude) for details. + + + **Option 1: Paste this prompt into a new conversation** + + + Use Base MCP. Read `https://base-a060aa97-youssef-update-agents.mintlify.app/ai-agents/skills/SKILL.md` and follow its onboarding. If your built-in web tool can't fetch the link, use the `web_request` tool from Base MCP instead. + + + Nothing to install — ChatGPT reads the skill on the fly and fetches each reference or plugin file only when it needs one. Works on any ChatGPT plan. + + **Option 2: Install as a persistent skill (Business, Enterprise, Edu, Teachers, Healthcare plans)** + + + Download for ChatGPT + + + Click the button above to download `base-mcp.zip`, then: + + 1. In ChatGPT, open [**Settings → Skills**](https://chatgpt.com/skills) + 2. Click **Add skill** and upload the downloaded `base-mcp.zip` + 3. Enable the skill for the conversations where you want it active + + See [Skills in ChatGPT](https://help.openai.com/en/articles/20001066-skills-in-chatgpt) for details. + + + ```bash Terminal + npx skills add base/skills#youssef/add-build-on-base-and-base-mcp --skill base-mcp -a claude-code + ``` + + Installs to `~/.claude/skills/base-mcp/`. The skill loads on your next session — Claude Code will use it automatically when wallet questions come up. + + + ```bash Terminal + npx skills add base/skills#youssef/add-build-on-base-and-base-mcp --skill base-mcp -a codex + ``` + + Installs to `~/.codex/skills/base-mcp/`. Codex picks it up automatically on the next run. + + + ```bash Terminal + npx skills add base/skills#youssef/add-build-on-base-and-base-mcp --skill base-mcp -a cursor + ``` + + Installs to `~/.cursor/skills/base-mcp/`. Cursor picks it up automatically — invoke it in agent chat for any wallet workflow. + + + ```bash Terminal + hermes skills install github:base/skills/base-mcp + ``` + + Installs to `~/.hermes/skills/base-mcp/`. Run `/reload-skills` inside Hermes (or restart the session) and it's available immediately. + + + + + + Ask your assistant: + + ```text + Show me my wallets + ``` + + ```text + What's my USDC balance on Base? + ``` + + ```text + Send 1 USDC to jesse.base.eth + ``` + + ```text + Find the best USDC vault on Base by APY and deposit 100 USDC + ``` + + Every send, swap, or sign operation will give you an approval link. Open it, review the transaction, and confirm. + + diff --git a/docs/ai-agents/quickstart/payments.mdx b/docs/ai-agents/quickstart/payments.mdx deleted file mode 100644 index 79dd9f679..000000000 --- a/docs/ai-agents/quickstart/payments.mdx +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Get Started with Payments" -description: "Build an agent that makes and accepts x402 payments on Base in under 10 minutes" -keywords: ["x402 quickstart", "agent payments", "OpenClaw payments", "AI agent pay for API", "accept payments agent"] ---- - -import { PaymentsQuickstartDemo } from "/snippets/PaymentsQuickstartDemo.jsx" - -This guide gets you from zero to a working agent that makes a paid x402 API request and exposes its own paid endpoint — all in under 10 minutes. - -## Mock Demo - - - - -## Install OpenClaw - - - - **macOS / Linux:** - - ```bash Terminal - curl -fsSL https://openclaw.ai/install.sh | bash - ``` - - **Windows (PowerShell):** - - ```powershell Terminal - iwr -useb https://openclaw.ai/install.ps1 | iex - ``` - - Verify the installation: - - ```bash Terminal - openclaw --version - ``` - - - - ```bash Terminal - openclaw onboard --install-daemon - ``` - - Follow the prompts to connect your LLM provider and set up your wallet plugin. - - - - ```bash Terminal - openclaw gateway --port 18789 - ``` - - Open `http://127.0.0.1:18789/` to verify the gateway is live. - - - - -**Claude Code, Codex, and OpenCode users** — the wallet skills and x402 payment tools below are compatible with any skills-enabled AI coding tool. Run the same `npx skills add` commands and the capabilities work identically. - - -## Configure a wallet - -Install the CDP Agentic Wallet skills: - -```bash Terminal -npx skills add coinbase/agentic-wallet-skills -``` - -Then authenticate: - -```text -Sign in to my wallet with your@email.com -``` - -Or use a [Sponge Wallet](https://www.paysponge.com) with native x402 support — see [wallet setup](/ai-agents/setup/wallet-setup) for all options. - -## Make your first x402 payment - -Ask your agent to discover and call a paid API: - -```text -Find a weather API and get the forecast for New York -``` - -The `search-for-service` and `pay-for-service` skills handle discovery, payment, and retries automatically. Your agent pays in USDC, gets the data, and returns the result — no API keys, no subscriptions. - -To make a direct x402 request with Sponge Wallet: - -```bash Terminal -curl -X POST "https://api.wallet.paysponge.com/api/x402/fetch" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{ - "url": "https://api.example.com/paid-endpoint", - "method": "GET", - "preferred_chain": "base" - }' -``` - -## Gate your own endpoint with x402 - -Ask your agent to expose a paid API: - -```text -Set up a paid endpoint for my market data at $0.01 per request -``` - -The `monetize-service` skill (`npx skills add coinbase/agentic-wallet-skills`) configures the x402 gating and deploys the endpoint. Other agents can now discover and pay for your service automatically. - -Or use `x402-express` for a custom server — see [Accepting payments](/ai-agents/payments/accepting-payments). - -## Next steps - - - - Deep dive into how x402 works, supported networks, and error handling. - - - - Set up server-side x402 gating with OpenClaw skills or custom middleware. - - - - Configure Bankr, CDP, or Sponge for your agent. - - - - Register your agent for discoverability and identity verification. - - diff --git a/docs/ai-agents/quickstart/trading.mdx b/docs/ai-agents/quickstart/trading.mdx deleted file mode 100644 index 23219ecb1..000000000 --- a/docs/ai-agents/quickstart/trading.mdx +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: "Get Started with Trading" -description: "Build an agent that fetches live market data and executes token swaps on Base" -keywords: ["trading agent quickstart", "OpenClaw trading", "Base trading agent", "token swap agent", "x402 market data"] ---- - -import { TradingQuickstartDemo } from "/snippets/TradingQuickstartDemo.jsx" - -This guide gets you from zero to a working agent that fetches live price data via x402 and executes a token swap on Base. - -## Mock Demo - - - - -## Install OpenClaw - - - - **macOS / Linux:** - - ```bash Terminal - curl -fsSL https://openclaw.ai/install.sh | bash - ``` - - **Windows (PowerShell):** - - ```powershell Terminal - iwr -useb https://openclaw.ai/install.ps1 | iex - ``` - - Verify: - - ```bash Terminal - openclaw --version - ``` - - - - ```bash Terminal - openclaw onboard --install-daemon - ``` - - - - ```bash Terminal - openclaw gateway --port 18789 - ``` - - - - -**Claude Code, Codex, and OpenCode users** — the wallet and trading skills below are compatible with any skills-enabled AI coding tool. Run the same `npx skills add` commands from within your tool. - - -## Configure a trading wallet - -Bankr provides cross-chain wallet access with gas sponsorship and built-in swap support — ideal for trading agents: - -```text -install the bankr skill from https://github.com/BankrBot/skills -``` - -Create a dedicated account at [bankr.bot](https://bankr.bot) and generate an API key at [bankr.bot/api](https://bankr.bot/api). - -For [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) or [Sponge Wallet](https://www.paysponge.com), see [wallet setup](/ai-agents/setup/wallet-setup). - -## Execute a swap - -Ask your agent to execute a trade using wallet-native tools: - -```text -Buy $50 of ETH on Base -``` - -With Bankr or CDP Agentic Wallet, built-in swap tools handle the trade — no external DEX integration needed. The wallet constructs, signs, and broadcasts the transaction. - -For best execution on Base, use the preconf endpoint to simulate before signing: - -```text -Simulate buying $50 of ETH then execute if the price impact is below 1% -``` - -## Next steps - - - - Full catalog of x402 data sources for trading agents. - - - - Flashblocks timing, fee calibration, and failure modes on Base. - - - - Configure Bankr, CDP, or Sponge for your agent. - - - - How x402 works for data fetching and payments. - - diff --git a/docs/ai-agents/setup/agent-registration.mdx b/docs/ai-agents/setup/agent-registration.mdx deleted file mode 100644 index ac68e1217..000000000 --- a/docs/ai-agents/setup/agent-registration.mdx +++ /dev/null @@ -1,188 +0,0 @@ ---- -title: "Agent Registration & Identity" -description: "Register your agent's identity onchain, get a Basename, and authenticate with services using Sign In With Agent (SIWA)" -keywords: ["ERC-8004", "agent registration", "Basename agent", "agent identity", "SIWA", "Sign In With Agent", "ERC-8128", "onchain identity Base"] ---- - -import { AgentRegistrationDemo } from "/snippets/AgentRegistrationDemo.jsx" - -When your agent calls a service, how does that service know it's really your agent? When your agent receives a response, how does it know the response is legitimate? Registration and identity verification solve both problems. - -## Mock Demo - - - -## Why register? - -- **Discoverability** — other agents and services can find your agent by querying the public registry -- **SIWA authentication** — services use your registration to verify that requests genuinely come from your agent -- **Reputation** — the reputation registry accumulates trust signals that other agents can query before interacting with yours - -## Get a Basename for your agent - -A Basename gives your agent a human-readable identity (e.g., `myagent.base.eth`) that resolves to its wallet address. Register at [base.org/names](https://www.base.org/names). - -## Register in the ERC-8004 registry - -The [ERC-8004](https://www.8004.org/) standard is an onchain NFT registry where agents publish their identity: name, description, endpoints, and public key. - - - - Use 8004scan.io to register and explore registered agents through a web interface — no code required. - - - Use the Agent0 SDK to register your agent programmatically and manage its onchain profile. - - - -A registry entry includes your agent's name and description, the endpoints where it can be reached, and a key pair that ties the agent's identity to its cryptographic credentials. - -The canonical registry addresses are at [8004scan.io](https://www.8004scan.io). - -[Learn more about ERC-8004 →](https://www.8004.org/) - -## Verify identity at runtime (ERC-8128) - -Registration tells the world your agent exists. The **ERC-8128** standard lets your agent prove it's really yours with every request: - - - - Your agent registers its identity and public key in the ERC-8004 registry. This is a one-time setup step. - - - - When your agent calls a service, it signs the request using its private key. This creates a cryptographic signature unique to that specific request. - - - - The service looks up your agent's public key from the registry, then checks the signature. If it matches, the service knows the request came from your registered agent. - - - -This works in both directions — your agent can verify a service's responses are authentic by checking the service's signature against the registry. - -[ERC-8128 specification →](https://erc8128.slice.so/concepts/overview) - -## Sign In With Agent (SIWA) - -[SIWA](https://siwa.id) bundles ERC-8004 registration and ERC-8128 signing into a single SDK — similar to how "Sign in with Google" bundles OAuth into one integration. - - -**Start with SIWA** for the simplest integration path. Use ERC-8004 and ERC-8128 directly only if you need fine-grained control over registration and verification. - - -### Agent-side integration - -Install the SIWA SDK: - -```bash Terminal -npm install @buildersgarden/siwa -``` - -Choose a signer based on your wallet provider. SIWA supports private keys, Bankr, Circle, Openfort, Privy, and smart contract accounts: - -```typescript TypeScript -import { createLocalAccountSigner } from "@buildersgarden/siwa/signer"; -import { privateKeyToAccount } from "viem/accounts"; - -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); -const signer = createLocalAccountSigner(account); -``` - -Request a nonce from the service, then sign and submit a SIWA message: - -```typescript TypeScript -import { signSIWAMessage } from "@buildersgarden/siwa"; - -// Step 1: Request a nonce from the service -const nonceResponse = await fetch("https://api.example.com/siwa/nonce", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ address: await signer.getAddress() }), -}); -const { nonce, issuedAt } = await nonceResponse.json(); - -// Step 2: Sign the SIWA message -const { message, signature } = await signSIWAMessage( - { - domain: "api.example.com", - uri: "https://api.example.com/siwa", - agentId: 42, // your ERC-8004 token ID - agentRegistry: "eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432", - chainId: 8453, - nonce, - issuedAt, - }, - signer -); - -// Step 3: Submit for verification and receive a receipt -const verifyResponse = await fetch("https://api.example.com/siwa/verify", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message, signature }), -}); -const { receipt } = await verifyResponse.json(); -``` - -Use the receipt to sign subsequent requests with ERC-8128: - -```typescript TypeScript -import { signAuthenticatedRequest } from "@buildersgarden/siwa/erc8128"; - -const request = new Request("https://api.example.com/action", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "transfer" }), -}); - -const signedRequest = await signAuthenticatedRequest(request, receipt, signer, 8453); -const response = await fetch(signedRequest); -``` - -### Server-side verification - -Services that accept SIWA-authenticated agents implement two endpoints: one to issue nonces and one to verify signatures. - -```typescript TypeScript -import { verifySIWA, createSIWANonce } from "@buildersgarden/siwa"; -import { createReceipt } from "@buildersgarden/siwa/receipt"; -import { createPublicClient, http } from "viem"; -import { base } from "viem/chains"; - -const client = createPublicClient({ chain: base, transport: http() }); - -// POST /siwa/nonce -const { nonce, issuedAt } = await createSIWANonce({ address, agentId, agentRegistry }, client); - -// POST /siwa/verify -const result = await verifySIWA(message, signature, "api.example.com", nonceValid, client); -if (result.success) { - const { receipt } = createReceipt({ address: result.address, agentId: result.agentId }); - return { receipt }; -} -``` - -SIWA ships drop-in middleware for Express, Next.js, Hono, and Fastify. See the [SIWA documentation](https://siwa.id/docs) for framework-specific examples, replay protection, and x402 payment integration. - -## Video Tutorial - - - -## Related - - - - Browse and verify registered agents and registry addresses on Base. - - - - Pay for API access with stablecoins using the x402 protocol. - - diff --git a/docs/ai-agents/setup/wallet-setup.mdx b/docs/ai-agents/setup/wallet-setup.mdx deleted file mode 100644 index 2c48d5a80..000000000 --- a/docs/ai-agents/setup/wallet-setup.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: "Wallet Setup for Agents" -description: "Give your AI agent a dedicated wallet on Base to hold funds, send payments, sign messages, and interact with onchain protocols securely." -keywords: ["AI agent wallet", "Bankr wallet", "CDP agentic wallet", "Sponge wallet", "onchain AI agent", "agent wallet setup"] ---- - -import { WalletSetupDemo } from "/snippets/WalletSetupDemo.jsx" - -An onchain wallet gives your agent the ability to hold funds, authorize transactions, and sign messages. Without one, your agent can read data but can't pay for services, receive payments, or prove its identity. - -## Mock Demo - - - -## Why your agent needs a dedicated wallet - -An AI agent *could* generate its own wallet by creating a random private key through prompting, but this is unsafe: - -- The private key appears in the agent's conversation context, where it can be logged, cached, or leaked through middleware -- Any system with access to the agent's prompt history could extract the key -- If the key is compromised, all funds in the wallet are permanently lost - -Dedicated wallet services solve this by managing keys in secure infrastructure separated from your agent's runtime. Your agent requests transactions without ever seeing the private key. - -## Choose a wallet - - - - Every Bankr agent gets a cross-chain wallet on Base, Ethereum, Solana, Polygon, and Unichain. Gas is sponsored on supported chains. Built-in swap tools make Bankr ideal for trading agents. - - Install the Bankr skill from your agent chat: - - ```text - install the bankr skill from https://github.com/BankrBot/skills - ``` - - Create a dedicated account at [bankr.bot](https://bankr.bot) and generate an API key at [bankr.bot/api](https://bankr.bot/api). Don't use your personal Bankr account — keep your agent's wallet separate. - - [Bankr docs →](https://docs.bankr.bot) - - - - Coinbase's standalone wallet for AI agents. Authentication is via email OTP — no API keys required in your agent. Private keys stay in Coinbase's secure infrastructure. Native x402 payment support is built in. - - Install the pre-built wallet skills: - - ```bash Terminal - npx skills add coinbase/agentic-wallet-skills - ``` - - Then ask your agent to authenticate: - - ```text - Sign in to my wallet with your@email.com - ``` - - Skills include: `authenticate-wallet`, `fund`, `send-usdc`, `trade`, `pay-for-service`, `monetize-service`, and more. Operates on Base. - - [CDP Agentic Wallet docs →](https://docs.cdp.coinbase.com/agentic-wallet/welcome) - - - - A multi-chain agent wallet with native x402 payment handling, built-in token swaps, and cross-chain bridges across Base, Ethereum, and Solana. Sponge's x402 proxy handles discovery, payment, and retries automatically. - - Register your agent to get a wallet immediately: - - ```bash Terminal - curl -X POST https://api.wallet.paysponge.com/api/agents/register \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{"name": "my-agent", "agentFirst": true}' - ``` - - Store the returned `apiKey`: - - ```bash Terminal - export SPONGE_API_KEY=sponge_live_... - ``` - - [Sponge Wallet docs →](https://wallet.paysponge.com/skill.md) - - - - -**Claude Code, Codex, and OpenCode users** — the wallet skills above work with any skills-enabled AI coding tool. Run the same `npx skills add` command from within your coding tool to get the same onchain capabilities. - - -## What your agent can do with a wallet - -Once your agent has a wallet, it can: - -- **Hold stablecoins**: receive and store USDC or other tokens -- **Send and receive payments**: transfer funds to other wallets, pay for API access, or receive payments for services -- **Sign messages**: cryptographically prove that a message or request came from your agent (used for identity verification) -- **Interact with protocols**: call smart contracts to swap tokens, provide liquidity, or use other onchain services - - - -## Next step - - - Register your agent for discoverability and identity verification. - diff --git a/docs/ai-agents/skills/README.md b/docs/ai-agents/skills/README.md new file mode 100644 index 000000000..95c45820a --- /dev/null +++ b/docs/ai-agents/skills/README.md @@ -0,0 +1,26 @@ +--- +title: "Base MCP Skill — Disclaimer" +description: "Terms, risks, and limitations for using the Base MCP skill with AI agents." +--- + +# Base MCP — Disclaimer + +> ⚠️ **Important: Read Before Use** + +**What this is.** The Base MCP is a hosted Model Context Protocol server operated by Base that lets AI agents interact with a user's Base Account in a user-authorized way. After authenticating and connecting to the MCP server, the MCP server can read account state and construct transactions for the user to approve and sign. The MCP server itself does not sign or broadcast transactions. By using this MCP server, you agree to the Base Account and Base App Terms of Service. + +**Third-party AI hosts, agents, and protocols.** The Base MCP is designed to be used with third-party AI hosts and may, depending on the skills loaded into the AI host, prepare transactions that interact with third-party onchain protocols. Those AI hosts and third-party protocols are not operated by Base and are governed by their own terms of service, privacy policies, and (where applicable) jurisdictional eligibility requirements. You are solely responsible for reviewing and complying with each third party's terms and confirming you are eligible to use them. + +**Not official third-party software.** Skills or plugins in the base-skills repo that reference third-party protocols (e.g., Uniswap, Morpho, Moonwell, Avantis) are authored by Base for use with the Base MCP. They are not official software of, endorsed by, or operated by those third parties. Inclusion of a skill in this repository does not constitute an endorsement, audit, or guarantee of the underlying protocol. Each third-party protocol is governed by its own terms of service and privacy policy, which the user is solely responsible for reviewing and complying with. + +**AI outputs may be inaccurate.** AI agents can misinterpret instructions, hallucinate parameters (including amounts, recipients, and contract addresses), or be influenced by adversarial inputs encountered in API responses, web content, or other sources. The Base MCP relies on the AI agent to interpret your intent correctly. Base does not validate or guarantee AI agent outputs. Review every action proposed by an AI agent before approving it. + +**Not professional advice.** Nothing produced by or in connection with the Base MCP — including any output of an AI agent using the service — constitutes investment, financial, legal, tax, or other professional advice. + +**Your responsibility for compliance.** You are solely responsible for ensuring that your use of the Base MCP, any connected AI host or agent, your wallet, and any third-party protocol complies with all laws and regulations applicable to you, including sanctions, securities, derivatives, tax, and consumer protection laws. + +**Smart contract and onchain risk.** Interacting with onchain protocols may result in partial or total loss of funds due to smart contract vulnerabilities, oracle failures, liquidations, slippage, MEV, governance actions, network congestion, protocol shutdowns, or other risks. Base does not control any third-party onchain protocol that you choose to interact with through the Base MCP. + +**Service availability and changes.** The Base MCP is provided "AS IS" and "AS AVAILABLE." Base may modify, suspend, or discontinue the service or any feature of it at any time and with or without notice. Base does not warrant that the service will be error-free, uninterrupted, secure, or compatible with any particular AI host, wallet, or third-party protocol. + +**Limitation of liability.** To the maximum extent permitted by applicable law, Base and its affiliates and their respective officers, directors, employees, and agents will not be liable for any indirect, incidental, special, consequential, exemplary, or punitive damages, or for any loss of profits, revenues, data, or digital assets, arising out of or related to your use of (or inability to use) the Base MCP or any third-party AI host, agent, or protocol used in connection with it, whether based on warranty, contract, tort (including negligence), statute, or any other legal theory. diff --git a/docs/ai-agents/skills/SKILL.md b/docs/ai-agents/skills/SKILL.md new file mode 100644 index 000000000..63aac0f20 --- /dev/null +++ b/docs/ai-agents/skills/SKILL.md @@ -0,0 +1,79 @@ +--- +title: "Base MCP Skill" +description: "Base MCP — gives your AI assistant access to a Base account via the Base MCP server (mcp.base.org). Wallet, portfolio, sending, swapping, signing, batched contract calls, and transaction history on Base." +name: base-mcp +version: 0.1.0 +--- + +# Base MCP + +> [!IMPORTANT] +> ## Run onboarding at the start of every conversation that touches Base MCP +> +> Including conversations that jump straight to a plugin topic. Onboarding is short — see below. + +## Detection + +The Base MCP exposes its tools to the harness when connected. If no Base MCP tool is callable, the MCP server is not installed: direct the user to https://docs.base.org/ai-agents/quickstart (or load [references/install.md](references/install.md) for app-specific steps) and stop. + +If Base MCP tools are available, load [references/tone.md](references/tone.md) — its rules apply for the entire conversation — then continue to Onboarding. If sibling files aren't readable (e.g. you were handed just this `SKILL.md` body), fetch the same relative path from `https://base-a060aa97-youssef-update-agents.mintlify.app/ai-agents/skills/references/tone.md` using the harness's HTTP/fetch tool, or the `web_request` tool from Base MCP if no native web tool is available. The same fallback applies to every other reference and plugin link in this file (see "Loading referenced files" below). + +## Onboarding + +Keep it short. Do this once per session, before doing real work: + +1. **Briefly mention what's available** — one or two sentences. The user has a Base account wallet and can do things like check balances, send and swap tokens, sign messages, batch contract calls, and (if installed) use partner plugins for DeFi, swaps, and other onchain actions. Do not enumerate every tool — the agent discovers tools and plugins directly from the MCP. + +2. **Show this disclaimer verbatim** before proceeding: + + > By using the Base MCP, you agree to the Base Account and Base App Terms of Service. Plugins available in the Base repo are authored by Base, not by the third-party protocols they reference. + +3. **Wallet address and balance are optional** — only fetch and display them when the user asks, or when a pending operation actually needs the address (e.g., a write call, a position lookup). Don't volunteer a wallet dump up front. + +## Tools + +The Base MCP advertises its own tool catalog to the harness. Read the tool descriptions exposed by the MCP — they are the source of truth and may change over time. Do not assume a fixed list; do not preload a tool catalog from this skill. + +Two patterns deserve their own references because they span multiple tools: + +| Topic | Reference | +|-------|-----------| +| Approval flow (for any write tool that returns an approval URL) | [references/approval-mode.md](references/approval-mode.md) | +| Batched contract calls (EIP-5792) | [references/batch-calls.md](references/batch-calls.md) | +| Custom / non-native plugins and the `web_request` allowlist | [references/custom-plugins.md](references/custom-plugins.md) | +| Platform install steps | [references/install.md](references/install.md) | +| Tone and language rules | [references/tone.md](references/tone.md) | + +### Loading referenced files + +- **Default — local.** Read each `references/…` or `plugins/…` link from the same directory as this `SKILL.md`. +- **Fallback — web.** If the sibling file isn't readable, fetch the same relative path from `https://base-a060aa97-youssef-update-agents.mintlify.app/ai-agents/skills/` using the harness's HTTP tool. If no harness HTTP tool exists, use the `web_request` tool from Base MCP. +- **Lazy.** Only load a reference or plugin when the conversation actually needs it. Don't preload the catalog. + +## Plugins + +Plugins extend Base MCP with partner-specific functionality (lending, swaps, perps, etc.). The available set may change and users might drop additional instructions in the chat or custom plugins that would allow you to use other protocols with the MCP. + +Plugins currently maintained alongside this skill (the **native plugins**): + +| Plugin | Reference | +|--------|-----------| +| Morpho | [plugins/morpho.md](plugins/morpho.md) | +| Moonwell | [plugins/moonwell.md](plugins/moonwell.md) | +| Uniswap | [plugins/uniswap.md](plugins/uniswap.md) | +| Avantis | [plugins/avantis.md](plugins/avantis.md) | +| Virtuals | [plugins/virtuals.md](plugins/virtuals.md) | +| Aerodrome (CLI-only) | [plugins/aerodrome.md](plugins/aerodrome.md) | +| Bankr | [plugins/bankr.md](plugins/bankr.md) | + +Load a plugin reference only when the user's request matches it, following the same local-first, web-fallback rule as references (see [Loading referenced files](#loading-referenced-files) above). For a plugin's own tools, defer to the descriptions the plugin's MCP exposes — this skill does not duplicate them. + +### Native plugins vs. custom / user-supplied plugins + +Native plugins are allowlisted in the Base MCP `web_request` tool and work everywhere. Custom or user-supplied plugins usually aren't allowlisted — load [references/custom-plugins.md](references/custom-plugins.md) for the decision tree on which HTTP path to use (harness HTTP tool vs. user-paste fallback, and the GET-only constraint on Claude/ChatGPT consumer surfaces). + +## Installation + +```bash +npx skills add base/skills --skill base-mcp +``` diff --git a/docs/ai-agents/skills/index.mdx b/docs/ai-agents/skills/index.mdx deleted file mode 100644 index 6ffde202c..000000000 --- a/docs/ai-agents/skills/index.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Overview" -description: "Installable agent capabilities for wallets, payments, and trading on Base" -keywords: ["agent skills", "Bankr", "CDP agentic wallet", "Sponge wallet", "x402 skills", "trading agent", "Base agent"] ---- - -Skills are installable knowledge packs that give your AI agent specific onchain capabilities — a wallet, payment handling, market data access, or trade execution. Install a skill and your agent knows what to do without any custom integration code. - - - - Cross-chain wallet with built-in swaps, gas sponsorship, and token launching. - - - - Email-authenticated wallet on Base with x402 payment skills bundled in. - - - - Multi-chain wallet with native x402 proxy, swaps, bridges, and banking. - - - - Discover, pay for, and monetize x402 API services via CDP Agentic Wallet. - - - - Sponge's built-in x402 proxy — discover and pay for services automatically. - - - - Price feeds, market cap, and OHLCV data via x402 — no API key required. - - - - Token balances, NFT metadata, portfolio data, and prices via x402 SIWE auth. - - - - Execute token swaps using wallet-native skills across Bankr, CDP, and Sponge. - - diff --git a/docs/ai-agents/skills/payments/cdp-payment-skills.mdx b/docs/ai-agents/skills/payments/cdp-payment-skills.mdx deleted file mode 100644 index e49427ac7..000000000 --- a/docs/ai-agents/skills/payments/cdp-payment-skills.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: "CDP Payment Skills" -description: "Skills for discovering, paying for, and monetizing x402 API services via the CDP Agentic Wallet" -keywords: ["CDP payment skills", "search-for-service skill", "pay-for-service skill", "monetize-service skill", "x402 bazaar skill"] ---- - -Three skills from the CDP Agentic Wallet bundle cover the full x402 commerce loop: finding paid APIs, calling them with automatic payment, and gating your own endpoints to charge other agents. - -## Install - -```bash Terminal -npx skills add coinbase/agentic-wallet-skills -``` - -Included in the same package as the [CDP Agentic Wallet](/ai-agents/skills/wallets/cdp-agentic-wallet). No separate install needed. - -## What the skill covers - -| Skill | What it does | -|-------|-------------| -| `search-for-service` | Search the x402 Bazaar for paid APIs by keyword or browse by network | -| `pay-for-service` | Make a paid x402 request — handles 402, payment, and retry automatically | -| `monetize-service` | Deploy a paid endpoint that other agents can discover and pay per request | - -## Example prompts - -```text -Find APIs for sentiment analysis -``` - -```text -Get the current ETH price from a paid data source -``` - -```text -Call that weather API and get the forecast for New York -``` - -```text -Set up a paid endpoint for my market data at $0.01 per request -``` - -## Reference - -- [search-for-service →](https://docs.cdp.coinbase.com/agentic-wallet/skills/search-for-service) -- [pay-for-service →](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) -- [CDP Agentic Wallet skills →](https://docs.cdp.coinbase.com/agentic-wallet/skills) diff --git a/docs/ai-agents/skills/payments/sponge-x402.mdx b/docs/ai-agents/skills/payments/sponge-x402.mdx deleted file mode 100644 index 0708d7116..000000000 --- a/docs/ai-agents/skills/payments/sponge-x402.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "Sponge x402" -description: "Skill for discovering and paying for x402 services automatically using Sponge Wallet's built-in proxy" -keywords: ["Sponge x402 skill", "x402 proxy skill", "automatic x402 payment skill", "discover x402 service skill", "paysponge x402 skill"] ---- - -Sponge's built-in x402 proxy handles service discovery, payment signing, and retries automatically. The skill lets your agent find and pay for any x402-gated API in a single step — no manual 402 handling, no retry logic. - -## Install - -Included with [Sponge Wallet](/ai-agents/skills/wallets/sponge-wallet). No separate install needed. - -## What the skill covers - -| Capability | Description | -|------------|-------------| -| **Service discovery** | Search the x402 catalog by keyword | -| **Automatic payment** | Detect a 402, pay in USDC, and retry transparently | -| **Payment links** | Create reusable payment links to gate your own services | - -## Example prompts - -```text -Find a weather API and pay for it using Sponge -``` - -```text -Search for sentiment analysis APIs in the x402 catalog -``` - -```text -Create a payment link for my market data service at $0.01 per request -``` - -## Reference - -- [Sponge Wallet skill doc →](https://wallet.paysponge.com/skill.md) diff --git a/docs/ai-agents/skills/plugins/aerodrome.md b/docs/ai-agents/skills/plugins/aerodrome.md new file mode 100644 index 000000000..2c1279417 --- /dev/null +++ b/docs/ai-agents/skills/plugins/aerodrome.md @@ -0,0 +1,416 @@ +--- +title: "Aerodrome Plugin" +description: "Skill plugin reference for swapping and providing liquidity on Aerodrome through Base MCP. CLI-only — uses sugar-sdk Python to build calldata." +--- + +# Aerodrome Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Aerodrome flow. + +> [!WARNING] +> ## CLI-only plugin +> +> This plugin uses **sugar-sdk** (a Python library) to build Aerodrome calldata locally, then submits it via Base MCP's `send_calls`. It only works in harnesses that have a Bash/CLI tool — **Claude Code, Codex, Cursor's terminal, etc.** It does **not** work on chat-only surfaces (ChatGPT, Claude.ai) because those have no shell to run Python in. If you detect a chat-only environment, tell the user this plugin requires CLI/terminal access and stop. + +Aerodrome is the leading DEX on Base (a Velodrome fork). This plugin uses the [velodrome-finance/sugar-sdk](https://github.com/velodrome-finance/sugar-sdk) Python library to discover pools, build swap routes, and prepare deposit/withdraw/stake/claim calldata. Calldata is then submitted to Base MCP's `send_calls` for user approval. + +No additional MCP server is required. + +**Prerequisite:** Python 3 (3.11+ recommended) available on the user's machine. The harness must be able to run `pip` and execute Python scripts via Bash. + +**Chain:** Base mainnet (chainId `8453` / `0x2105`) + +--- + +## Architecture + +``` +sugar-sdk (Python, queries + calldata builder) + ↓ + monkey-patch sign_and_send_tx to capture {to, data, value} instead of signing + ↓ + pass captured calls to Base MCP send_calls + ↓ + user approves → get_request_status confirms +``` + +Key contracts on Base (from sugar-sdk config): + +| Contract | Address | +|----------|---------| +| Sugar (read-only data) | `0x69dD9db6d8f8E7d83887A704f447b1a584b599A1` | +| Router (V2 LP ops) | `0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43` | +| Universal Router (swaps) | `0x01D40099fCD87C018969B0e8D4aB1633Fb34763C` | +| Slipstream (CL pools) | `0x0AD09A66af0154a84e86F761313d02d0abB6edd5` | +| NFPM (CL positions) | `0x827922686190790b37229fd06084350E74485b72` | +| AERO token | `0x940181a94A35A4569E4529A3CDfB74e38FD98631` | +| WETH | `0x4200000000000000000000000000000000000006` | +| USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | + +--- + +## Setup + +Run these once per project. If a venv already exists at `~/aerodrome-mcp/venv`, skip to step 3. + +```bash +mkdir -p ~/aerodrome-mcp && cd ~/aerodrome-mcp +python3 -m venv venv +./venv/bin/pip install --quiet "setuptools<74" # pkg_resources for sugar-sdk's setup.py +./venv/bin/pip install --quiet --no-build-isolation \ + "git+https://github.com/velodrome-finance/sugar-sdk.git@main" +``` + +**Why these flags:** +- `setuptools<74` — sugar-sdk's `setup.py` imports `pkg_resources`, which was removed from setuptools 74+. +- `--no-build-isolation` — forces pip to use our patched setuptools during the build instead of the latest. + +--- + +## Public RPC limitations + +The public `https://mainnet.base.org` RPC enforces two limits that break sugar-sdk's default batching: + +1. **Max 10 calls per JSON-RPC batch** (returns `{"code": -32014, "message": "maximum 10 calls in 1 batch"}`) +2. **Rate-limits concurrent batch requests** (returns `{"code": -32016, "message": "over rate limit"}` when too many batches fire via `asyncio.gather`) + +Apply these patches at the start of every sugar-sdk script to make it work on the public RPC. For production usage, prefer a paid RPC (Alchemy, QuickNode) and skip the patches. + +```python +# patches.py +import asyncio +from sugar.chains import AsyncChain, CommonChain +from sugar.helpers import chunk +from sugar.price import Price + +async def _safe_apaginate(self, f): + """Sequential single-call paginator — rate-limit-safe for public RPCs.""" + all_results = [] + for offset in range(0, self.settings.pools_count_upper_bound, self.settings.pool_page_size): + try: + async with self.web3.batch_requests() as batcher: + batcher.add(f(self.settings.pool_page_size, offset)) + results = await batcher.async_execute() + for r in results: + if isinstance(r, list): + all_results.extend(r) + except Exception: + pass # skip rate-limited / failed pages + return all_results + +async def _safe_get_prices(self, tokens): + """Fetches native ETH + stable token in a guaranteed first batch, then the rest.""" + tokens = list(tokens) + connectors = self.settings.connector_tokens_addrs + rates = {} + ref_idx = [i for i, t in enumerate(tokens) + if t.symbol == self.settings.native_token_symbol + or t.token_address == self.settings.stable_token_addr] + if ref_idx: + try: + async with self.web3.batch_requests() as batcher: + batcher.add(self.prices.functions.getManyRatesToEthWithCustomConnectors( + [tokens[i].wrapped_token_address or tokens[i].token_address for i in ref_idx], + False, connectors, 10)) + results = await batcher.async_execute() + if results and isinstance(results[0], list): + for pos, i in enumerate(ref_idx): + rates[i] = results[0][pos] + except Exception: + pass + non_ref = [(i, t) for i, t in enumerate(tokens) if i not in rates] + for batch in chunk(non_ref, self.settings.price_batch_size): + idxs, tkns = zip(*batch) + try: + async with self.web3.batch_requests() as batcher: + batcher.add(self.prices.functions.getManyRatesToEthWithCustomConnectors( + [t.wrapped_token_address or t.token_address for t in tkns], + False, connectors, 10)) + results = await batcher.async_execute() + if results and isinstance(results[0], list): + for pos, i in enumerate(idxs): + rates[i] = results[0][pos] + except Exception: + for i in idxs: rates[i] = 0 + return [rates.get(i, 0) for i in range(len(tokens))] + +def _safe_prepare_prices(self, tokens, prices): + """Handles usd_rate=0 fallback gracefully.""" + eth_decimals = self.settings.native_token_decimals + rates_in_eth = {} + for cnt, rate in enumerate(prices): + t = tokens[cnt] + nr = rate if t.decimals == eth_decimals else ( + rate // (10 ** (eth_decimals - t.decimals)) if t.decimals < eth_decimals + else rate * (10 ** (t.decimals - eth_decimals))) + rates_in_eth[t.token_address] = nr + eth_rate = rates_in_eth.get(self.settings.native_token_symbol, 0) + usd_rate = rates_in_eth.get(self.settings.stable_token_addr, 0) + if usd_rate == 0 or eth_rate == 0: + return [Price(token=t, price=0.0) for t in tokens] + eth_usd_price = (eth_rate * 10 ** eth_decimals) // usd_rate + return [Price(token=t, + price=(rates_in_eth.get(t.token_address, 0) * eth_usd_price // 10 ** eth_decimals) / 10 ** eth_decimals) + for t in tokens] + +def _safe_quote_chunked(): + """Chunk paths into batches of ≤10 to respect public RPC batch limits.""" + async def _impl(_self, from_token, to_token, amount_in, pools, paths): + all_quotes = [] + for path_batch in chunk(paths, 10): + pool_batch = _self.paths_to_pools(pools, path_batch) + try: + async with _self.web3.batch_requests() as batcher: + batcher, inputs = _self.prepare_quote_batch( + from_token, to_token, batcher, pool_batch, amount_in, path_batch) + results = await batcher.async_execute() + all_quotes.extend(_self.prepare_quotes(inputs, results)) + except Exception: + pass + return all_quotes + return _impl + +def apply_patches(): + AsyncChain.apaginate = _safe_apaginate + AsyncChain._get_prices = _safe_get_prices + AsyncChain._get_quotes_for_paths = _safe_quote_chunked() + CommonChain.prepare_prices = _safe_prepare_prices +``` + +--- + +## Calldata bridge: intercepting sign_and_send_tx + +sugar-sdk's write methods (`swap`, `deposit`, `withdraw`, `stake`, `claim_emissions`) all call `self.sign_and_send_tx(contract_fn, value=...)` internally, which signs with `SUGAR_PK` and broadcasts via `eth_sendRawTransaction`. We override it to capture the unsigned `{to, data, value}` instead, then hand the captured calls to `send_calls` for user approval. + +```python +# bridge.py +import os, asyncio +os.environ.setdefault("SUGAR_PK", "0x" + "aa" * 32) # dummy — we never actually sign + +from sugar.chains import AsyncChain + +BASE_WALLET = "" + +class _FakeAccount: + address = BASE_WALLET # sugar-sdk reads .address in several places + +_captured = [] + +async def _capture(self, tx, value: int = 0, wait: bool = True): + tx_dict = await tx.build_transaction({ + "from": BASE_WALLET, "value": value, "nonce": 0, "gas": 800_000, + }) + _captured.append({ + "to": tx_dict["to"], + "data": tx_dict.get("data", "0x"), + "value": hex(value), # use the value param — tx_dict["value"] may be 0 for payable fns + }) + return {"transactionHash": b"\x00" * 32, "status": 1} # fake receipt + +def install_bridge(base_wallet_addr): + global BASE_WALLET + BASE_WALLET = base_wallet_addr + _FakeAccount.address = base_wallet_addr + AsyncChain.sign_and_send_tx = _capture + AsyncChain.account = property(lambda self: _FakeAccount()) + _captured.clear() + +def captured_calls(): + return list(_captured) +``` + +--- + +## Tokens: native ETH vs ERC-20 WETH + +Sugar-SDK exposes both. They behave very differently for swaps: + +- **Native ETH token** (`symbol="ETH"`, `token_address="ETH"`, `wrapped_token_address=WETH`). Pass this when the user wants to spend native ETH (msg.value). `swap_from_quote` sets `value = quote.input.amount_in`. +- **WETH ERC-20** (`symbol="WETH"`, `token_address=WETH`, `wrapped_token_address=None`). Pass this only when the user already holds WETH. The router uses ERC-20 approval; msg.value is 0. + +Pick the right one based on what the user is actually holding. For "swap 0.001 ETH to USDC" → use the native ETH token. + +```python +eth_native = next(t for t in tokens if t.symbol == "ETH" and t.wrapped_token_address) +weth_erc20 = next(t for t in tokens if t.symbol == "WETH") # only if user holds WETH +``` + +--- + +## Orchestration patterns + +### Swap (ETH → USDC, native) + +```python +import asyncio +from sugar.chains import AsyncBaseChain +import patches, bridge + +patches.apply_patches() +bridge.install_bridge("") + +async def build(): + async with AsyncBaseChain(rpc_uri="https://mainnet.base.org") as chain: + tokens = await chain.get_all_tokens() + eth = next(t for t in tokens if t.symbol == "ETH" and t.wrapped_token_address) + usdc = next(t for t in tokens if t.token_address == "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913") + quote = await chain.get_quote(eth, usdc, amount=int(0.001 * 1e18)) + await chain.swap_from_quote(quote, slippage=0.02) + return bridge.captured_calls() + +calls = asyncio.run(build()) +# calls = [{to: WETH, data: approve(...), value: 0x0}, +# {to: UniversalRouter, data: execute(...), value: 0x38d7ea4c68000}] +``` + +The first captured call is a WETH approval to the Universal Router. For a **native ETH** swap, this approval is technically unnecessary (the router accepts msg.value directly). You can drop it to reduce gas — submit only the Universal Router execute call. For a **WETH ERC-20** swap, the approve is required. + +Submit via Base MCP: + +```json +{ "chain": "base", "calls": [] } +``` + +### Swap (USDC → AERO) + +```python +quote = await chain.get_quote(usdc, aero, amount=int(1 * 1e6)) +await chain.swap_from_quote(quote, slippage=0.02) +# captures: USDC approve(Universal Router, ...) + Universal Router execute (value=0x0) +``` + +Batch both calls in one `send_calls` so the approve and swap execute atomically. + +### Basic pool deposit (vAMM/sAMM) + +Sugar's `get_pools()` requires prices for all tokens, which is slow on the public RPC. For a known pool, scan `Sugar.all()` directly for the LP address: + +```python +from sugar.pool import LiquidityPool + +ETH_USDC_VAMM = "0xcDAC0d6c6C59727a65F871236188350531885C43" # known vAMM-WETH/USDC + +tokens = await chain.get_all_tokens() +token_map = {t.token_address: t for t in tokens} +ref_prices = await chain.get_prices([eth_native, usdc_token]) +price_map = {p.token.token_address: p for p in ref_prices} + +full_pool = None +for offset in range(0, 9000, chain.settings.pool_page_size): + batch = await chain.sugar.functions.all(chain.settings.pool_page_size, offset, 0).call() + if not batch: break + for raw in batch: + if raw[0].lower() == ETH_USDC_VAMM.lower(): + full_pool = LiquidityPool.from_tuple(raw, token_map, price_map, + chain_id=chain.chain_id, chain_name=chain.name) + break + if full_pool: break + +# Quote and build deposit calldata +amount_eth = int(0.001 * 1e18) +if full_pool.token0.token_address.lower() == "0x4200000000000000000000000000000000000006": + q = await chain.quote_basic_deposit(full_pool, amount_token0=amount_eth) +else: + q = await chain.quote_basic_deposit(full_pool, amount_token1=amount_eth) +await chain.deposit(q, slippage=0.02) +# captures: USDC approve(Router, ...) + Router.addLiquidityETH(...) with value=msg.value +``` + +The deposit calldata includes a **30-minute deadline** — if the user takes longer to approve, rebuild the calldata. + +### Withdraw / stake / claim + +```python +positions = await chain.get_positions(BASE_WALLET) + +# Withdraw 50% of a basic position +from sugar.withdraw import Withdrawal +w = Withdrawal.from_position(positions[0], fraction=0.5) +await chain.withdraw(w, slippage=0.02) + +# Stake an unstaked basic LP in its gauge +unstaked = [p for p in positions if p.gauge and not p.staked] +if unstaked: await chain.stake(unstaked[0]) + +# Claim emissions from a staked position +staked = [p for p in positions if p.staked] +if staked: await chain.claim_emissions(staked[0]) +``` + +Each captured set of calls maps directly to `send_calls`. + +--- + +## What works vs. what doesn't + +Tested behaviors on Base mainnet (public RPC, 2026-05): + +| Capability | Status | Notes | +|------------|--------|-------| +| `get_all_tokens` | ✅ Works | Returns ~4-7k tokens depending on RPC stability | +| `get_pools_for_swaps` | ✅ Works | Returns ~5k basic pools — no CL pools | +| `get_quote` (basic pools) | ✅ Works | Multi-hop routing | +| `get_prices` (small samples) | ✅ Works | Include native ETH + USDC in input | +| `get_prices` (all tokens) | ⚠️ Flaky | Public RPC rate limits cause USDC fallback to 0 → all-zero prices | +| `get_pools()` (full) | ⚠️ Slow / flaky | Needs full price set; better to scan `Sugar.all()` for specific LPs | +| Basic pool ops (swap/deposit/withdraw) | ✅ Works | Via Universal Router (swap) / Router (LP) | +| `get_positions` | ✅ Works | Returns 0 for fresh wallets | +| CL pool quotes / deposit | ❌ Not accessible | Sugar's `all()` does not enumerate Slipstream CL pools. Build CL calldata directly against NFPM contract instead. | +| Native ETH swap | ✅ Works | Use the native ETH token (`symbol="ETH"`, `wrapped_token_address=WETH`) — WETH approve is captured but unnecessary; drop it from the batch | + +For production usage: install a paid RPC (Alchemy, QuickNode), drop the rate-limit patches, and the full pool/price flows become reliable. + +--- + +## Example prompts + +**Swap 0.001 ETH for USDC** +1. `get_wallets` → address. +2. Run sugar-sdk swap script with the native ETH token; capture calldata. +3. `send_calls` with just the Universal Router execute call (drop the WETH approve for native swaps). +4. Open the approval URL; poll `get_request_status` after the user acts. + +**Swap 1 USDC for AERO** +1. `get_wallets` → address. +2. Run sugar-sdk swap script with USDC → AERO; capture both calls (approve + execute). +3. `send_calls` with both calls batched. +4. Open approval URL; poll. + +**Provide liquidity to vAMM-WETH/USDC with 0.001 ETH** +1. `get_wallets` → address. +2. Scan `Sugar.all()` for `0xcDAC0d6c6C59727a65F871236188350531885C43`. +3. Quote `0.001 ETH` deposit → returns required USDC amount. +4. Capture approve + `addLiquidityETH` calls. +5. `send_calls` immediately (deadline is 30 min from build time). +6. Open approval URL; poll. + +**Withdraw a position** +1. `get_wallets` → address. +2. Run sugar-sdk `get_positions` → pick a position. +3. Build `Withdrawal.from_position(pos, fraction=1.0)` and capture. +4. `send_calls` (approve LP token + `removeLiquidity`). +5. Open approval URL; poll. + +--- + +## Slippage warnings + +Same thresholds as other DEX plugins. Pass `slippage=0.01` (1%) by default to `swap_from_quote`, `deposit`, `withdraw`: + +| Tolerance | Level | Action | +| --- | --- | --- | +| ≤ 1% | Normal | Proceed. | +| > 1% and ≤ 5% | Elevated | Mention the value and ask the user to confirm. | +| > 5% and ≤ 20% | High | Warn that the trade can fill significantly below quote. Require explicit confirmation. | +| > 20% | Very high | Strongly warn; do not submit without the user re-confirming the exact number. | + +--- + +## Notes + +- Sugar-SDK versions: pin to a specific commit/tag for reproducibility. The `@main` branch tested against here has `get_positions`, `withdraw`, `stake`, `unstake`, `claim_emissions`, `claim_fees`. The `@v0.3.1` tag does **not** include these — install from `main` for full functionality. +- The captured `from` address must match the address that ultimately signs the `send_calls` request — fetch it via Base MCP's `get_wallets` and pass it to `install_bridge()`. +- For CL pool operations (Slipstream / V3-style with tick ranges), sugar-sdk's `quote_concentrated_deposit` + `deposit` work if you can construct a full `LiquidityPool` object for the CL pool. Since `Sugar.all()` doesn't enumerate them, you'd need to fetch CL pool state directly from the Slipstream factory or NFPM contract — out of scope for this plugin's current scope. +- Always use `chain: "base"` (string) with `send_calls`, not the numeric chainId. diff --git a/docs/ai-agents/skills/plugins/avantis.md b/docs/ai-agents/skills/plugins/avantis.md new file mode 100644 index 000000000..ee34b4307 --- /dev/null +++ b/docs/ai-agents/skills/plugins/avantis.md @@ -0,0 +1,547 @@ +--- +title: "Avantis Plugin" +description: "Skill plugin reference for trading perpetual futures on Avantis through Base MCP." +--- + +# Avantis Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Avantis endpoint. The user's wallet address — used as `trader` in every tx-builder call — is fetched lazily when needed. + +Avantis is a perpetual futures DEX on Base mainnet (`chainId` 8453). Use `web_request` to fetch unsigned calldata from the Avantis tx-builder, then preview or execute it with account MCP `send_calls`. + +Do not sign, approve, or submit transactions unless the user explicitly asks. Generating calldata and `send_calls` approval links is safe, but the user must approve any real transaction. + +Prerequisite: `tx-builder.avantisfi.com`, `data.avantisfi.com`, `core.avantisfi.com`, and `api.avantisfi.com` must be in the account MCP `web_request` allowlist. + +No API key or Authorization header is required for the documented public endpoints. + +--- + +## API Services + +| Service | Base URL | Purpose | +| --- | --- | --- | +| tx-builder | `https://tx-builder.avantisfi.com` | GET-only unsigned calldata builder for Avantis Trading and USDC calls | +| data | `https://data.avantisfi.com/v2/trading` | Pair configs, leverage rules, fees, open interest, market status | +| core | `https://core.avantisfi.com` | Current open positions, limit orders, and open interest | +| history | `https://api.avantisfi.com` | Closed/all trade history, PnL, referral stats, market-order settlement status | + +Source of truth for tx-builder shape: + +``` +GET https://tx-builder.avantisfi.com/openapi.json +GET https://tx-builder.avantisfi.com/docs +``` + +--- + +## Base-Only Rules + +- All tx-builder calldata is for Base mainnet only. +- All tx-builder responses return `chainId: 8453`. +- There is no supported chain selector query parameter. +- Collateral is USDC only. ETH is used only for gas and Avantis execution fee `value`. +- Canonical Base USDC: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`. +- Default USDC spender is Avantis `TradingStorage`: `0x8a311D7048c35985aa31C131B9A13e03a5f7422d`. + +Fetch current contract addresses when needed: + +``` +GET https://tx-builder.avantisfi.com/addresses +``` + +--- + +## tx-builder Response Shape + +All calldata-producing tx-builder endpoints return an envelope: + +```json +{ + "ok": true, + "data": { + "to": "0x44914408af82bC9983bbb330e3578E1105e11d4e", + "from": "0x1111111111111111111111111111111111111111", + "data": "0x19cde9a1...", + "value": "0x13e52b9abe000", + "chainId": 8453, + "description": "Open long BTC/USD 10x with 100 USDC (market)", + "meta": {} + } +} +``` + +Only `response.data.to`, `response.data.value`, and `response.data.data` are passed to account MCP `send_calls`. + +```json +{ + "chain": "base", + "calls": [ + { + "to": "", + "value": "", + "data": "" + } + ] +} +``` + +`from` is informational and identifies who must sign. If `delegate=` is used, `from` becomes the delegate address. `nonce` and gas fields are intentionally omitted; the wallet manages them. + +--- + +## Units And Scaling + +tx-builder request inputs use human decimals: + +| Surface | Unit behavior | +| --- | --- | +| tx-builder `collateralUsdc`, `amountUsdc`, `openPrice`, `takeProfit`, `stopLoss`, `leverage`, `slippagePercent` | Human decimals, not raw scaled integers | +| data API `/v2/trading` | Human decimals | +| core `/user-data` positions and limit orders | Raw strings: USDC fields divide by `1e6`, price and leverage fields divide by `1e10` | +| history API | Mixed, mostly human decimals; check each endpoint shape | +| tx-builder response `value` | Hex wei string | + +Do not pass `1e6` USDC base units or `1e10` price units to tx-builder query parameters. + +--- + +## Orchestration Pattern + +For an open trade: + +``` +get_wallets -> trader address +web_request GET /v2/trading -> validate pair, market status, leverage, and min position +web_request GET /user-data?trader=... -> inspect existing positions/orders +web_request GET /token/approve if allowance may be missing -> send_calls preview +web_request GET /trade/open -> send_calls preview +poll /v2/market-order-initiated/status/ only after a real tx is submitted +web_request GET /user-data?trader=... -> confirm final state only after execution +``` + +For management actions (close, cancel, margin, TP/SL), always read `core /user-data` first and use a real `positions[].index` or `limitOrders[].index`. The tx-builder can encode calldata for a requested index even if that position/order does not exist, so preflight is required to avoid likely reverts. + +--- + +## Step 1 - Validate Pair, Leverage, Liquidity + +``` +GET https://data.avantisfi.com/v2/trading +``` + +Top-level shape: + +```json +{ + "dataVersion": 1.5, + "pairInfos": { "1": {} }, + "groupInfo": { "0": {} }, + "pairCount": 102 +} +``` + +Use `pairInfos[""]` to inspect a pair. Important fields: + +| Field | Meaning | +| --- | --- | +| `index` | Pair index used by tx-builder and onchain calls | +| `from`, `to` | Symbol components, for example `BTC` and `USD` | +| `isPairListed` | Must be true to open new trades | +| `leverages.minLeverage`, `leverages.maxLeverage` | Fixed-fee leverage envelope for `market`, `limit`, `stop_limit` | +| `leverages.pnlMinLeverage`, `leverages.pnlMaxLeverage` | ZFP leverage envelope for `market_zero_fee` | +| `pairMinLevPosUSDC` | Minimum notional: `collateralUsdc * leverage` | +| `pairOI`, `pairMaxOI` | Pair open interest and cap | +| `groupIndex` | Lookup key into `groupInfo` | +| `feed.attributes.is_open` or `feed.attributes.isOpen` | Market-open flag | +| `lazerFeed.state` | `stable` generally maps to `priceSourcing=1` for Lazer endpoints | + +Minimum position check (BELOW_MIN_POS): + +The tx-builder rejects with `400 BAD_REQUEST` when `collateralUsdc * leverage < pairMinLevPosUSDC`. This is the `BELOW_MIN_POS` condition. Always validate before calling `/trade/open`: + +``` +positionSize = collateralUsdc * leverage +if positionSize < pair.pairMinLevPosUSDC -> BELOW_MIN_POS error, increase collateral or leverage +``` + +To compute the minimum collateral required for a given leverage: + +``` +minCollateral = ceil(pair.pairMinLevPosUSDC / leverage) +``` + +Example: `pairMinLevPosUSDC=100`, `leverage=10` → minimum `collateralUsdc` is `10`. With `leverage=1` → minimum is `100`. + +Liquidity check: + +``` +pairAvailable = pairMaxOI - pairOI +groupAvailable = groupInfo[pair.groupIndex].groupMaxOI - groupInfo[pair.groupIndex].groupOI +available = min(pairAvailable, groupAvailable) +positionSize = collateralUsdc * leverage +``` + +For BTC/USD around a small test amount, `collateralUsdc=1` and `leverage=100` can satisfy the 100 USDC minimum notional for `market_zero_fee` when 100x is inside the ZFP leverage range. + +--- + +## Step 2 - Check User Positions And Limit Orders + +``` +GET https://core.avantisfi.com/user-data?trader=
+``` + +Response: + +```json +{ + "positions": [], + "limitOrders": [] +} +``` + +Key fields: + +| Field | Scaling | Use | +| --- | --- | --- | +| `pairIndex` | none | tx-builder `pairIndex` | +| `index` | none | tx-builder `tradeIndex` | +| `buy` | none | `true` is long, `false` is short | +| `collateral` | divide by `1e6` | Use as `collateralUsdc` when closing full size | +| `leverage` | divide by `1e10` | Display and validation | +| `openPrice`, `tp`, `sl`, `liquidationPrice` | divide by `1e10` | Display and TP/SL decisions | +| `isPnl` | none | true means ZFP trade | + +Unknown or malformed traders can return empty arrays rather than an error. + +--- + +## Step 3 - Approve USDC + +Exact approval: + +``` +GET https://tx-builder.avantisfi.com/token/approve + ?trader=
+ &amountUsdc=1 +``` + +Unlimited approval: + +``` +GET https://tx-builder.avantisfi.com/token/approve?trader=
+``` + +Optional custom spender: + +``` +GET https://tx-builder.avantisfi.com/token/approve + ?trader=
+ &amountUsdc=100 + &spender=
+``` + +`spender` defaults to `TradingStorage`. Pass the returned `response.data` call to `send_calls`. Approval must be confirmed onchain before trade calls that require allowance can succeed, unless approval and action are submitted as a valid batch and the wallet/account contract supports the batch. + +--- + +## Step 4 - Open A Trade + +``` +GET https://tx-builder.avantisfi.com/trade/open + ?trader=
+ &pair=BTC/USD + &side=long + &orderType=market + &collateralUsdc=100 + &leverage=10 + &slippagePercent=1 +``` + +Parameters: + +| Parameter | Required | Notes | +| --- | --- | --- | +| `trader` | yes | EVM address that owns the position | +| `pair` or `pairIndex` | yes | Pair symbols accept `/`, `-`, or `_`, for example `BTC/USD`, `btc-usd`, `BTC_USD` | +| `side` | yes | `long` or `short` | +| `orderType` | no | `market`, `limit`, `stop_limit`, or `market_zero_fee`; default is `market` | +| `collateralUsdc` | yes | Human-decimal USDC, must be greater than zero | +| `leverage` | yes | Human multiplier; pair envelope is enforced | +| `slippagePercent` | no | Human percent, default `1`; must be greater than 0 and <= 100 | +| `openPrice` | required for limit/stop_limit | Human-decimal price; optional market override | +| `takeProfit` | no | Human-decimal TP price | +| `stopLoss` | no | Human-decimal SL price | +| `executionFeeEth` | no | Default about `0.00035` ETH; max 1 ETH | +| `delegate` | no | Wraps call in `Trading.delegatedAction(trader, calldata)` | +| `skipValidation` | no | `true` bypasses pre-trade checks; avoid unless explicitly requested | + +Order types: + +| `orderType` | Meaning | +| --- | --- | +| `market` | Fixed-fee market open; price is auto-resolved if `openPrice` omitted | +| `limit` | Limit order; `openPrice` required | +| `stop_limit` | Stop-limit order; `openPrice` required | +| `market_zero_fee` | Zero-Fee Protocol / ZFP market open; uses `pnlMinLeverage` and `pnlMaxLeverage` | + +Example ZFP around a small notional: + +``` +GET https://tx-builder.avantisfi.com/trade/open + ?trader=
+ &pair=BTC/USD + &side=long + &orderType=market_zero_fee + &collateralUsdc=1 + &leverage=100 + &slippagePercent=1 +``` + +Example limit: + +``` +GET https://tx-builder.avantisfi.com/trade/open + ?trader=
+ &pair=BTC/USD + &side=long + &orderType=limit + &openPrice=90000 + &collateralUsdc=2 + &leverage=50 + &takeProfit=100000 + &stopLoss=80000 +``` + +--- + +## Step 5 - Close, Cancel, Margin, TP/SL + +Read `core /user-data` first. Use real indices from the returned arrays. + +Close full or partial collateral: + +``` +GET https://tx-builder.avantisfi.com/trade/close + ?trader=
+ &pairIndex= + &tradeIndex= + &collateralUsdc= +``` + +Cancel a resting limit or stop-limit order: + +``` +GET https://tx-builder.avantisfi.com/trade/cancel + ?trader=
+ &pairIndex= + &tradeIndex= +``` + +Deposit or withdraw margin: + +``` +GET https://tx-builder.avantisfi.com/margin/update + ?trader=
+ &pairIndex= + &tradeIndex= + &action=deposit + &collateralUsdc=1 +``` + +`action` is `deposit` or `withdraw`. If `priceUpdateData` and `priceSourcing` are omitted, tx-builder fetches required Pyth bytes server-side. + +Set TP and SL together: + +``` +GET https://tx-builder.avantisfi.com/tpsl/update + ?trader=
+ &pairIndex= + &tradeIndex= + &takeProfit=100000 + &stopLoss=80000 +``` + +`takeProfit` is required and must be greater than zero. `stopLoss` is required; pass `stopLoss=0` to clear SL. + +Open limit order modification is not exposed as a current tx-builder endpoint. To replace a resting limit order, cancel the existing order with `/trade/cancel`, then create a new `/trade/open` limit or stop-limit order. + +--- + +## Delegated Trading + +Set a delegate: + +``` +GET https://tx-builder.avantisfi.com/delegate/set + ?trader=
+ &delegate= +``` + +Remove a delegate: + +``` +GET https://tx-builder.avantisfi.com/delegate/remove?trader=
+``` + +After a delegate is set, trade-side tx-builder endpoints accept `delegate=`. The response `from` becomes the delegate, and the delegate signs/broadcasts. The position still belongs to `trader`. + +--- + +## Batching With send_calls + +`send_calls` accepts multiple Base calls: + +```json +{ + "chain": "base", + "calls": [ + { + "to": "", + "value": "", + "data": "" + }, + { + "to": "", + "value": "", + "data": "" + } + ] +} +``` + +Useful preview batches: + +- Approval plus open trade. +- Approval plus margin deposit. +- Cancel resting order plus create replacement limit order. +- Multiple independent generated calls, if all are Base calls and logically safe to preview together. + +Keep approval before the action that needs allowance. Do not combine calls from different chains. + +--- + +## Settlement Polling + +Market opens and closes settle after the submitted transaction emits an initiated event. Only poll when you have a real tx hash from a submitted transaction. + +``` +GET https://api.avantisfi.com/v2/market-order-initiated/status/ +``` + +Expected logical statuses: + +- `executed` +- `canceled` +- `pending` + +Unknown hashes can return HTTP 200 with: + +```json +{ + "success": false, + "errorMessage": "Market order not found for the given transaction hash" +} +``` + +Use exponential backoff and stop after a reasonable timeout. Do not claim a position opened or closed until onchain state or the settlement API confirms it. + +--- + +## Query Trade History And PnL + +History endpoints use a legacy envelope: + +```json +{ "success": true } +{ "success": false, "errorMessage": "..." } +``` + +Always check `success` before reading data. + +| Endpoint | Purpose | +| --- | --- | +| `GET https://api.avantisfi.com/v2/history/portfolio/history/
/0/20` | Closed trades, paginated; limit max 20 | +| `GET https://api.avantisfi.com/v2/history/portfolio/all/
/0/20` | All trades, open and closed | +| `GET https://api.avantisfi.com/v2/history/portfolio/top/
` | Top 3 by net PnL | +| `GET https://api.avantisfi.com/v2/history/portfolio/top/
/5` | Top N by net PnL | +| `GET https://api.avantisfi.com/v2/history/portfolio/profit-loss/
` | Aggregate PnL | +| `GET https://api.avantisfi.com/v2/history/portfolio/profit-loss/
/grouped` | Aggregate PnL by pair | +| `GET https://api.avantisfi.com/v2/history/referral/stats/
` | Referral stats | + +Observed edge case: for a wallet with no visible portfolio, some history endpoints may return `success:false` with `Unable to get the portfolio.` while others return `success:true` with empty data. Treat this as an empty/unknown portfolio unless the user expected existing history. + +PnL convention: + +- For ZFP trades, prefer `_mapped_netPnl` where present. +- For fixed-fee trades, prefer `_mapped_grossPnl` where present. + +--- + +## Error Handling + +tx-builder errors: + +```json +{ + "ok": false, + "error": { + "code": "BAD_REQUEST", + "message": "Position size 0.01 USDC is below the minimum of 100 USDC for BTC/USD (collateral 0.01 x leverage 1)" + } +} +``` + +Common tx-builder error codes: + +| Code | Meaning | +| --- | --- | +| `VALIDATION_ERROR` | Query shape problem: bad address, missing required field, numeric range error | +| `BAD_REQUEST` | Domain validation failed: min position, leverage envelope, liquidity, invalid TP/SL | +| `UPSTREAM_ERROR` | Data or price feed dependency failed | +| `NOT_FOUND` | Unknown route or pair index | +| `INTERNAL_ERROR` | Unexpected service error | + +Recommended handling: + +- Surface validation messages directly. +- For `/trade/open`, inspect `meta.validation` on success and show the user the position size, min position, leverage envelope, and available liquidity when useful. +- For history endpoints, check `success`; if false, show `errorMessage`. +- For management actions, do not rely on tx-builder to prove the position/order exists. Verify with `core /user-data`. + +BELOW_MIN_POS recovery: + +When the error message indicates a minimum position violation (`collateral * leverage < pairMinLevPosUSDC`), do not retry blindly. Compute what is needed and suggest corrections: + +``` +minPositionUsdc = pair.pairMinLevPosUSDC // from data API or meta.validation.minPositionUsdc +minCollateral = ceil(minPositionUsdc / requestedLeverage) +minLeverage = ceil(minPositionUsdc / requestedCollateral) +``` + +Present the user with concrete options: increase collateral to `minCollateral`, increase leverage to `minLeverage` (within the pair envelope), or both. Do not silently adjust parameters without user confirmation. + +--- + +## Current tx-builder Endpoint Inventory + +| Endpoint | Calldata? | Purpose | +| --- | --- | --- | +| `GET /` | No | Service index | +| `GET /health` | No | Health and chainId | +| `GET /addresses` | No | Contract addresses | +| `GET /pairs` | No | Pair summaries | +| `GET /pairs/` | No | Single pair details | +| `GET /trade/open` | Yes | Open market, ZFP, limit, or stop-limit trade | +| `GET /trade/close` | Yes | Close a trade | +| `GET /trade/cancel` | Yes | Cancel a resting limit or stop-limit order | +| `GET /margin/update` | Yes | Deposit or withdraw collateral | +| `GET /tpsl/update` | Yes | Update TP and SL | +| `GET /delegate/set` | Yes | Set delegate | +| `GET /delegate/remove` | Yes | Remove delegate | +| `GET /token/approve` | Yes | Approve USDC | +| `GET /docs` | No | Swagger UI | +| `GET /openapi.json` | No | OpenAPI spec | + diff --git a/docs/ai-agents/skills/plugins/bankr.md b/docs/ai-agents/skills/plugins/bankr.md new file mode 100644 index 000000000..29c4d6a38 --- /dev/null +++ b/docs/ai-agents/skills/plugins/bankr.md @@ -0,0 +1,245 @@ +--- +title: "Bankr Plugin" +description: "Skill plugin reference for discovering the latest token launches on Base via the Bankr API and buying them with Base MCP's swap tool." +--- + +# Bankr Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Bankr flow. This plugin reads from the Bankr public API and then routes the actual purchase through Base MCP's `swap` tool — there is no separate Bankr MCP server. + +[Bankr](https://bankr.bot) is a launch and discovery surface for tokens on Base. The public API exposes the latest deployed token launches (name, symbol, contract address, deployer, links). This plugin uses that feed to surface fresh launches to the user, then buys the selected token through Base MCP's `swap` tool — Bankr is only the discovery layer; the swap is a regular Base MCP `swap` call paying ETH (or USDC) for the target ERC-20. + +No additional MCP server is required. + +**Prerequisite:** `api.bankr.bot` must be on the Base MCP `web_request` allowlist. If requests are rejected, inform the user and fall back to the harness's HTTP/fetch tool if one is available. + +**Chain:** Base mainnet (chainId `8453` / `0x2105`) + +--- + +## API + +Base URL: `https://api.bankr.bot` + +### `GET /token-launches` + +Returns the most recent token launches on Base, newest first. No auth required, no query parameters. + +```json +{ + "launches": [ + { + "activityId": "6a1067ea1d736e44884096d5", + "status": "deployed", + "launchType": "doppler", + "tokenName": "Whop", + "tokenSymbol": "WHOP", + "chain": "base", + "tokenAddress": "0xe7d8e68525af7e10a16724bbd3001c0828828ba3", + "poolId": "0x2fee469c920ad9cd8d7fed1510c6034531e0f9fb7c94dbeea35623a358b7580f", + "txHash": "0xc989ca12...", + "deployer": { + "walletAddress": "0x67cb...", + "xUsername": "TheLordSherlock", + "xProfileImageUrl": "https://pbs.twimg.com/..." + }, + "feeRecipient": { "walletAddress": "0xccebfd...." }, + "tweetUrl": "https://x.com/i/status/...", + "websiteUrl": "https://whop.com", + "metadataUri": "ipfs://bafkrei...", + "timestamp": 1779460074566 + } + ] +} +``` + +Field notes: + +- `tokenAddress` — the ERC-20 contract on Base. Pass this verbatim to `swap` as `tokenTo`. +- `status` — always `"deployed"` in the current feed; treat anything else as a non-tradable preview and skip. +- `chain` — always `"base"` in the current feed; skip anything else. +- `launchType` — currently `"doppler"` (Doppler v3/v4 pools). Other values may appear later; the swap path is the same as long as the token has a tradeable pool. +- `timestamp` — milliseconds since epoch (note: more than 13 digits in the sample because the API uses a high-precision counter; treat as monotonically decreasing in array order). +- `deployer.xUsername`, `tweetUrl`, `websiteUrl` — optional context to surface to the user before they buy. + +The API returns roughly 50 launches per call. There is no pagination parameter; if you need older launches, you'll see them shift out as new ones land. + +### `GET /token-launches/{tokenAddress}` + +Returns a single launch's metadata by token contract address. The address is case-insensitive (the API lowercases it on the response). No auth required. + +```text Example +GET https://api.bankr.bot/token-launches/0x32F66Ec2Ffb26d262058965cf294F951e47F8ba3 +``` + +```json +{ + "launch": { + "activityId": "69b0716db2c1b3e9b71c7290", + "status": "deployed", + "launchType": "doppler", + "tokenName": "AGNT SOCIAL", + "tokenSymbol": "AGNT", + "chain": "base", + "imageUri": "ipfs://bafkrei...", + "tokenAddress": "0x32f66ec2ffb26d262058965cf294f951e47f8ba3", + "poolId": "0xebe171fc...", + "txHash": "0x58155b40...", + "deployer": { + "walletAddress": "0x58584e...", + "xUsername": "SirKekius67", + "xProfileImageUrl": "https://pbs.twimg.com/..." + }, + "feeRecipient": { "walletAddress": "0xe8737f...", "xUsername": "Tuteth_" }, + "tweetUrl": "https://x.com/...", + "metadataUri": "ipfs://bafkrei...", + "timestamp": 1773171053648 + } +} +``` + +Same field shape as items in the list endpoint, with one addition: + +- `imageUri` — IPFS URI for the token's image/logo (only returned by the single-launch endpoint, not the list endpoint). + +Use this endpoint when the user names a token by **address** (instead of picking from the latest-launches list) — for confirmation before swapping, or to enrich an address the user pasted from elsewhere. If the address isn't in Bankr's index the API returns a 404; fall back to a regular swap and warn that the token wasn't found in the Bankr launches feed. + +--- + +## Orchestration + +```text +1. web_request GET https://api.bankr.bot/token-launches +2. Filter to status="deployed" and chain="base", take the first N (default 5–10) +3. Show the user a compact list (symbol — name, deployer @handle, age) +4. Wait for the user to pick one and confirm an amount +5. get_wallets → address (only if not already cached) +6. swap (Base MCP) with tokenFrom=ETH (or USDC), tokenTo=, amount= +7. Open the approvalUrl +8. get_request_status only after the user acts +``` + +Do not auto-buy. Always require an explicit "buy X amount of " confirmation from the user before calling `swap` — the launches feed contains low-liquidity and meme tokens, and the swap is irreversible. + +### Discovery call + +```text +web_request: + method: GET + url: https://api.bankr.bot/token-launches +``` + +Filter client-side: + +```js +const fresh = response.launches + .filter((l) => l.status === "deployed" && l.chain === "base") + .slice(0, 10); +``` + +### Presenting launches to the user + +Surface enough context that the user can judge whether to buy — at minimum: symbol, name, deployer handle (if any), website/tweet link, and how recent the launch is. Do **not** echo the full IPFS metadata or all 50 entries; that's noise. + +Example summary line per launch: + +```text +WHOP — Whop · by @TheLordSherlock · launched 2m ago · whop.com + 0xe7d8e68525af7e10a16724bbd3001c0828828ba3 +``` + +### Swap call + +The actual purchase is a regular Base MCP `swap` call. Read the `swap` tool's own parameter descriptions from the MCP — they are the source of truth. Typical shape: + +```json +{ + "chain": "base", + "tokenFrom": "0x0000000000000000000000000000000000000000", + "tokenTo": "", + "amount": "", + "slippage": 0.05 +} +``` + +- `tokenFrom`: native ETH = `0x0000000000000000000000000000000000000000`; USDC on Base = `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`. +- `amount`: base units. For 0.001 ETH pass `"1000000000000000"`; for 1 USDC pass `"1000000"`. +- `slippage`: see [Slippage Warnings](#slippage-warnings). New launches frequently need elevated slippage — warn before submitting. + +The `swap` tool returns an `approvalUrl` and `requestId` like any other write call. Surface the URL to the user neutrally ("Approve Swap"), then poll `get_request_status` once they've acted. The full approval/polling pattern is in [`../references/approval-mode.md`](../references/approval-mode.md). + +--- + +## Example Prompts + +**Show me the latest token launches on Base** +1. `web_request` GET `https://api.bankr.bot/token-launches`. +2. Filter to `status="deployed"` and `chain="base"`; take the top 10. +3. Show symbol, name, deployer handle, website/tweet, and contract address. +4. Do **not** auto-buy. Ask the user which one (and how much) they want. + +**Buy 0.001 ETH worth of the newest token on Bankr** +1. `web_request` GET `https://api.bankr.bot/token-launches`. +2. Take `launches[0]` (or the first one matching `status="deployed"`). +3. Show: symbol, name, address, deployer. Ask the user to confirm — "Buy 0.001 ETH of `` (`
`)?". +4. On confirmation: `swap` with `tokenFrom=ETH`, `tokenTo=`, `amount="1000000000000000"`, `slippage=0.05`. +5. Open the approval URL; poll `get_request_status` once the user has approved. + +**Buy 5 USDC of $WHOP** +1. `web_request` GET `https://api.bankr.bot/token-launches`. +2. Find the entry with `tokenSymbol="WHOP"`; if multiple, prefer the most recent and confirm the contract address with the user. +3. `swap` with `tokenFrom=USDC`, `tokenTo=`, `amount="5000000"`, `slippage=0.05`. +4. Open the approval URL; poll. + +**Are there any launches from @0xtinylabs in the last hour?** +1. `web_request` GET `https://api.bankr.bot/token-launches`. +2. Filter by `deployer.xUsername === "0xtinylabs"` and `timestamp` within the last hour (use the array's relative ordering — the feed is newest first). +3. List matches with symbol, name, address, tweet/website. + +**What is this token? 0x32F66Ec2Ffb26d262058965cf294F951e47F8ba3** +1. `web_request` GET `https://api.bankr.bot/token-launches/0x32F66Ec2Ffb26d262058965cf294F951e47F8ba3`. +2. If 200: summarize `tokenName`, `tokenSymbol`, deployer handle, tweet/website, and launch age from `timestamp`. +3. If 404: tell the user the address isn't in Bankr's launches index; offer to swap anyway via the regular `swap` flow with extra confirmation. + +**Buy 0.001 ETH of 0x32F66Ec2Ffb26d262058965cf294F951e47F8ba3** +1. `web_request` GET `https://api.bankr.bot/token-launches/0x32F66Ec2Ffb26d262058965cf294F951e47F8ba3` to confirm symbol/name/deployer. +2. Show those details and ask the user to confirm — "Buy 0.001 ETH of `` (`
`)?". +3. On confirmation: `swap` with `tokenFrom=ETH`, `tokenTo=
`, `amount="1000000000000000"`, `slippage=0.05`. +4. Open the approval URL; poll. + +--- + +## Slippage Warnings + +New launches commonly have thin liquidity and volatile prices. Use the same thresholds as other DEX plugins, but be more vocal about elevated values for Bankr launches specifically: + +| Tolerance | Level | Action | +| --- | --- | --- | +| ≤ 1% | Normal | Often **not enough** for fresh launches — the swap may revert. | +| > 1% and ≤ 5% | Elevated | Reasonable default for newly launched tokens. Mention the value. | +| > 5% and ≤ 20% | High | Warn that the trade may fill significantly below quote and is a likely sandwich target. Require explicit confirmation. | +| > 20% | Very high | Strongly warn; do not submit without the user re-confirming the exact number. | + +If the user did not specify a slippage, default to `0.05` (5%) for Bankr launches and call that out before submitting. + +--- + +## Safety Notes + +- **Symbol collisions.** Multiple launches can share the same symbol (the sample feed contains three `simstudioai` launches with different symbols and addresses). Always disambiguate by `tokenAddress` and confirm with the user before swapping. +- **No endorsement.** The Bankr feed is unfiltered. The Base MCP and this plugin do not vet, endorse, or audit listed tokens — many are low-liquidity, short-lived, or meme tokens. Mention this once before the first buy of a session. +- **Adversarial metadata.** Token names, symbols, deployer handles, and website URLs are user-supplied and can be misleading or impersonate legitimate projects. Don't follow links from the feed; surface them to the user for context only. +- **Address case.** Pass `tokenAddress` to `swap` verbatim — lowercased addresses from the API work fine; do not re-checksum or modify them. +- **Buy size.** Do not propose a default buy amount. The user must specify the amount. + +--- + +## Notes + +- Native ETH address: `0x0000000000000000000000000000000000000000` +- USDC on Base: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` +- WETH on Base: `0x4200000000000000000000000000000000000006` +- Token amounts are base units: USDC = 1e6 per token, ETH/WETH = 1e18 per token. Base units for arbitrary launched tokens depend on the token's `decimals()` — most Doppler launches use 18, but if a swap returns a quote that's wildly off, fetch the token's decimals from the Base MCP portfolio or via an `eth_call` before retrying. +- Always use `chain: "base"` (string) with `swap`, not the numeric chainId. +- The feed updates frequently (new launches every few minutes during peak hours). If the user asks "what's brand new", fetch again rather than reusing an earlier response. diff --git a/docs/ai-agents/skills/plugins/moonwell.md b/docs/ai-agents/skills/plugins/moonwell.md new file mode 100644 index 000000000..36f61526d --- /dev/null +++ b/docs/ai-agents/skills/plugins/moonwell.md @@ -0,0 +1,163 @@ +--- +title: "Moonwell Plugin" +description: "Skill plugin reference for lending on Moonwell through Base MCP." +--- + +# Moonwell Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Moonwell endpoint. The user's wallet address — required for `prepare` and position queries — is fetched lazily when needed. + +Moonwell is a Compound v2 lending protocol on Base and Optimism. Use `web_request` to call the Moonwell HTTP API to read positions/rates and prepare unsigned calldata, then execute via `send_calls`. + +No additional MCP server required — everything goes through `web_request` + `send_calls`. + +**Prerequisite:** `api.moonwell.fi` must be in the MCP server's `web_request` allowlist. If requests to that hostname are rejected, inform the user that the Moonwell API is not yet whitelisted on this MCP instance. + +**Supported chains:** Base (8453), Optimism (10). + +--- + +## Orchestration Pattern + +``` +web_request(https://api.moonwell.fi/v1/prepare/?...) + → { data: { transactions: [ { to, data, value, chainId }, ... ] } } + ↓ +send_calls(chainId, calls mapped from transactions[]) + → approvalUrl + requestId + ↓ +User approves at the returned approval URL (present as "Approve Transaction" — see ../references/approval-mode.md) + ↓ +get_request_status(requestId) → confirmed +``` + +Steps in `transactions[]` are ordered — `approve` and `enter-market` come before the protocol action. Execute them as a single `send_calls` batch. + +--- + +## Read Endpoints (use web_request GET) + +``` +GET https://api.moonwell.fi/v1/markets?chain=base +GET https://api.moonwell.fi/v1/markets/USDC?chain=base +GET https://api.moonwell.fi/v1/rates?chain=base&asset=USDC +GET https://api.moonwell.fi/v1/yield?chain=base&sort=apy&min-tvl=1000000&limit=5 +GET https://api.moonwell.fi/v1/positions/
?chain=base +GET https://api.moonwell.fi/v1/health/
?chain=base +GET https://api.moonwell.fi/v1/rewards/
?chain=base +GET https://api.moonwell.fi/v1/token-balance/
?chain=base&asset=USDC +``` + +Market reads are edge-cached 30 s. User-scoped reads (`positions`, `health`, `rewards`, `token-balance`) are never cached. + +`/positions` returns an array — one entry per market. Use `?active=true` to filter out markets where both `suppliedUsd` and `borrowedUsd` are zero. + +--- + +## Prepare Endpoints (use web_request → send_calls) + +Verbs: `supply`, `withdraw`, `borrow`, `repay`. + +**GET form** (query params): + +``` +GET https://api.moonwell.fi/v1/prepare/supply?chain=base&asset=USDC&amountDecimal=100&from=
+``` + +**POST form** (JSON body — pass as the `body` object parameter to `web_request`): + +```json +{ + "url": "https://api.moonwell.fi/v1/prepare/supply", + "method": "POST", + "headers": { "content-type": "application/json" }, + "body": { "chain": "base", "asset": "USDC", "amountDecimal": "100", "from": "
" } +} +``` + +Both return identical response shapes. Use GET when simpler; use POST when the body is complex. + +### Key parameters + +| Field | Notes | +|-------|-------| +| `chain` | `base` (default), `optimism`, or chain ID | +| `asset` | Symbol: `USDC`, `WETH`, `ETH` (alias for WETH) | +| `amountDecimal` | Human-readable string, e.g. `"100"`. Use this **or** `amount` (base units), never both. | +| `from` | User's wallet address (from `get_wallets`) | + +### Response → send_calls mapping + +```json +{ + "data": { + "transactions": [ + { "step": "approve", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 }, + { "step": "enter-market", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 }, + { "step": "moonwell-supply", "to": "0x...", "data": "0x...", "value": "0x0", "chainId": 8453 } + ] + } +} +``` + +Pass all items as the `calls` array to `send_calls`, using `chainId` from any transaction item (`0x2105` for Base mainnet). + +--- + +## Example Flows + +### Supply 100 USDC on Base + +``` +1. get_wallets → address +2. web_request GET /token-balance/
?chain=base&asset=USDC → confirm balance ≥ 100 +3. web_request GET /prepare/supply?chain=base&asset=USDC&amountDecimal=100&from=
+4. send_calls(chainId=0x2105, calls from transactions[]) +5. User approves → get_request_status(requestId) +``` + +### Borrow USDC against collateral + +``` +1. get_wallets → address +2. web_request GET /health/
?chain=base → verify health > 1.5 +3. web_request GET /prepare/borrow?chain=base&asset=USDC&amountDecimal=50&from=
+4. send_calls(chainId=0x2105, calls from transactions[]) +5. User approves → get_request_status(requestId) +``` + +### Check positions and health + +``` +1. get_wallets → address +2. web_request GET /positions/
?chain=base&active=true → show per-market balances +3. web_request GET /health/
?chain=base → show health factor +``` + +--- + +## Protocol Notes + +- **mTokens** — ERC-20 receipt tokens (mUSDC, mWETH…); exchange rate accrues over time +- **WETH special-case** — borrow/withdraw deliver native ETH; supply/repay require ERC-20 WETH. Both `asset=ETH` and `asset=WETH` resolve to the same mWETH market +- **Compound v2 error codes** — `mint`, `borrow`, `repay` return non-zero codes for business-logic failures without reverting. Check the onchain receipt after broadcast +- **Base has two mUSDC entries** — the current market and a deprecated bridged-USDC market. Disambiguate by `marketAddress` or `deprecated: true` + +### Health factor guide + +| Value | Status | +|-------|--------| +| `> 1.5` | Healthy | +| `1.1 – 1.5` | Caution | +| `< 1.1` | Liquidation risk | +| `null` | No borrows | + +--- + +## Chain IDs for send_calls + +| Chain | chainId param | +|-------|--------------| +| Base mainnet | `0x2105` | +| Optimism | `0xa` | diff --git a/docs/ai-agents/skills/plugins/morpho.md b/docs/ai-agents/skills/plugins/morpho.md new file mode 100644 index 000000000..d4e6e0285 --- /dev/null +++ b/docs/ai-agents/skills/plugins/morpho.md @@ -0,0 +1,72 @@ +--- +title: "Morpho Plugin" +description: "Skill plugin reference for lending on Morpho through Base MCP." +--- + +# Morpho Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Morpho tool — the user's wallet address (required by Morpho write/position calls) is fetched lazily, and the disclaimer must be shown once per session. + +Morpho is a lending protocol on Base. The Morpho MCP server prepares lending operations (deposit, borrow, withdraw, repay, supply collateral) and returns unsigned calldata that is then executed via Base MCP's batched-calls tool. + +The exact list of Morpho tools, their parameters, and supported chains are exposed by the Morpho MCP itself — read its tool descriptions rather than relying on a fixed catalog in this file. Tools may be added, renamed, or removed over time. + +## MCP Server + +URL: `https://mcp.morpho.org/` + +## Installation (alongside Base MCP) + +```json +{ + "mcpServers": { + "base-account": { "url": "https://mcp.base.org" }, + "morpho": { "url": "https://mcp.morpho.org/" } + } +} +``` + +Claude Code: `claude mcp add morpho --transport http https://mcp.morpho.org/` + +## Orchestration Pattern + +Morpho's prepare-style tools (deposit, withdraw, supply, borrow, repay, supply/withdraw collateral) return an unsigned `calls` array plus a `chainId`. Pass them straight to Base MCP's batched-calls tool. + +``` +morpho prepare tool → { calls: [...], chainId } + ↓ +batched-calls tool (chainId, calls) → approval URL + request ID + ↓ user approves +status-poll tool (request ID) → confirmed +``` + +See [../references/batch-calls.md](../references/batch-calls.md) and [../references/approval-mode.md](../references/approval-mode.md). + +## Example Prompts + +``` +Find the best USDC vault on Base by APY and deposit 100 USDC +``` +1. Query Morpho vaults filtered by USDC, sorted by APY. +2. Call the Morpho prepare-deposit tool for the chosen vault and amount. +3. Pass the returned `calls` + `chainId` to Base MCP's batched-calls tool. +4. Hand the user the approval link; once they confirm, poll the request status. + +``` +Show all my Morpho positions on Base +``` +1. Fetch the user's address (if not already known). +2. Call the Morpho positions tool with that address. + +``` +Check if my Morpho borrow position is healthy +``` +1. Fetch the user's address. +2. Call the Morpho positions tool and report the health factor from the response. + +## Important Notes + +- Morpho's prepare tools typically simulate before returning — review simulation output before submitting the batch. +- Use the Morpho simulation tool for novel or large operations. +- Always check the supported-chains tool for the current list rather than assuming a fixed set. diff --git a/docs/ai-agents/skills/plugins/uniswap.md b/docs/ai-agents/skills/plugins/uniswap.md new file mode 100644 index 000000000..625e8ced2 --- /dev/null +++ b/docs/ai-agents/skills/plugins/uniswap.md @@ -0,0 +1,470 @@ +--- +title: "Uniswap Plugin" +description: "Skill plugin reference for swapping and LPing on Uniswap through Base MCP." +--- + +# Uniswap Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Uniswap endpoint. The user's wallet address — passed as `walletAddress` in every swap and LP call — is fetched lazily when needed. + +Uniswap on Base: token swaps using the proxy-approval flow (no Permit2 signing) and LP position management for V2, V3, and V4. Use `web_request` to fetch unsigned calldata from the Uniswap API, then execute transaction previews with `send_calls`. + +No additional MCP server is required. + +**Prerequisite:** `trade-api.gateway.uniswap.org` and `liquidity.api.uniswap.org` must be in the MCP server's `web_request` allowlist. If requests are rejected by the allowlist, inform the user. + +**Chain:** Base mainnet (chainId `8453` / `0x2105`) + +--- + +## Auth Headers + +Use these headers for all requests: + +```json +{ + "Content-Type": "application/json", + "x-api-key": "NeoYO3V50_koJAipDEalYWbMO1XMaFPAQmpOm6_Npo0" +} +``` + +For the swap proxy-approval flow, also include this header on **all** swap endpoints: `/check_approval`, `/quote`, and `/swap`. + +```json +{ + "x-permit2-disabled": "true" +} +``` + +Without `x-permit2-disabled`, Uniswap can return Permit2 or Universal Router behavior instead of the proxy-approval flow described here. + +--- + +## Swap Flow: Proxy Approval, No Permit2 + +Base URL: `https://trade-api.gateway.uniswap.org/v1` + +```text +POST /check_approval -> if approval non-null, include approval calldata in send_calls +POST /quote -> get best route, read-only +POST /swap -> get unsigned swap tx, include swap calldata in send_calls +``` + +### 1. `POST /check_approval` + +Headers: auth headers plus `"x-permit2-disabled": "true"`. + +```json +{ + "walletAddress": "
", + "token": "", + "amount": "", + "chainId": 8453, + "includeGasInfo": true +} +``` + +Response: + +```json +{ + "approval": { + "to": "", + "data": "", + "value": "0x00", + "chainId": 8453 + } +} +``` + +`approval` can be `null`, especially for native ETH. If it is non-null, pass it to `send_calls`. You can batch the approval and swap together after `/swap` returns. + +### 2. `POST /quote` + +Headers: auth headers plus `"x-permit2-disabled": "true"`. + +```json +{ + "type": "EXACT_INPUT", + "amount": "", + "tokenIn": "
", + "tokenOut": "
", + "tokenInChainId": 8453, + "tokenOutChainId": 8453, + "swapper": "
", + "autoSlippage": "DEFAULT", + "protocols": ["V4", "V3", "V2"], + "routingPreference": "BEST_PRICE" +} +``` + +Use `"slippageTolerance": <0-20>` instead of `autoSlippage` if the user specifies slippage. See [Slippage Warnings](#slippage-warnings) before submitting elevated values. + +Response includes a top-level `quote` object plus metadata. Keep the response as a flat object for `/swap`; do not nest it under a `quote` key. + +### 3. `POST /swap` + +Headers: auth headers plus `"x-permit2-disabled": "true"`. + +Use the `/quote` response as the body, but remove any null or absent permit fields before sending: + +```js +const swapBody = { ...quoteResponse }; +if (swapBody.permitData == null) delete swapBody.permitData; +if (swapBody.permitTransaction == null) delete swapBody.permitTransaction; +delete swapBody.signature; +``` + +Do not send `signature`, and do not send `permitData` or `permitTransaction` when they are `null`. + +Response: + +```json +{ + "swap": { + "to": "", + "data": "", + "value": "0x00", + "chainId": 8453 + }, + "gasFee": "..." +} +``` + +### Swap `send_calls` + +Approval and swap can be sent separately or batched in one `send_calls` preview: + +```json +{ + "chain": "base", + "calls": [ + { "to": "", "value": "", "data": "" }, + { "to": "", "value": "", "data": "" } + ] +} +``` + +### Swap Orchestration + +```text +1. get_wallets -> address; convert tokenIn amount to base units +2. web_request POST /check_approval with x-permit2-disabled +3. web_request POST /quote with x-permit2-disabled +4. Build swapBody from quoteResponse and remove null permit fields +5. web_request POST /swap with x-permit2-disabled +6. send_calls("base", approval + swap calls if approval exists, otherwise swap only) +7. Open the approvalUrl if requested; do not approve unless the user explicitly asks +8. get_request_status only after the user acts +``` + +--- + +## LP Flow + +Base URL: `https://liquidity.api.uniswap.org/lp` + +Use this host for LP endpoints in this plugin environment. Do not switch to `api.uniswap.org` or `trade-api.gateway.uniswap.org/v1/lp/...` unless that host is explicitly available to the API key and MCP allowlist. + +### Pool Discovery: `POST /lp/pool_info` + +Use pool discovery before creating V3/V4 LP positions. `poolReference` must be a valid pool address for V3 or pool ID for V4. + +```json +{ + "protocol": "V4", + "chainId": 8453, + "poolParameters": { + "tokenAddressA": "0x0000000000000000000000000000000000000000", + "tokenAddressB": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "fee": 3000, + "tickSpacing": 60 + } +} +``` + +Known Base V4 ETH/USDC `fee=3000`, `tickSpacing=60` pool reference observed in testing: + +```text +0xe070797535b13431808f8fc81fdbe7b41362960ed0b55bc2b6117c49c51b7eb9 +``` + +Pool references can change by pair, fee, tick spacing, and protocol. Prefer querying `/lp/pool_info` instead of hard-coding a pool reference unless the user explicitly selected a pool. + +### Approval Step: `POST /lp/check_approval` + +Use this before LP create/increase/decrease operations. The body uses `lpTokens` as an array of token/amount objects. + +```json +{ + "protocol": "V4", + "walletAddress": "
", + "chainId": 8453, + "lpTokens": [ + { + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "" + }, + { + "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amount": "" + } + ], + "action": "CREATE", + "generatePermitAsTransaction": true, + "simulateTransaction": true +} +``` + +`action`: `"CREATE"` | `"INCREASE"` | `"DECREASE"` | `"MIGRATE"` + +Response: + +```json +{ + "requestId": "...", + "transactions": [ + { + "transaction": { + "to": "...", + "from": "...", + "data": "...", + "value": "0x00", + "chainId": 8453 + }, + "cancelApproval": false, + "action": "CREATE" + } + ] +} +``` + +If `transactions` is empty, no approval transaction is needed. If it has entries, map every `transactions[*].transaction` to `send_calls`. + +```js +const approvalCalls = approvalResponse.transactions.map((entry) => ({ + to: entry.transaction.to, + value: entry.transaction.value ?? "0x0", + data: entry.transaction.data +})); +``` + +### Create V3/V4 Position: `POST /lp/create` + +```json +{ + "walletAddress": "
", + "chainId": 8453, + "protocol": "V4", + "existingPool": { + "token0Address": "0x0000000000000000000000000000000000000000", + "token1Address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "poolReference": "0xe070797535b13431808f8fc81fdbe7b41362960ed0b55bc2b6117c49c51b7eb9" + }, + "independentToken": { + "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amount": "1000000" + }, + "tickBounds": { + "tickLower": -200400, + "tickUpper": -199200 + }, + "simulateTransaction": false, + "nativeTokenBalance": "1000000000000000" +} +``` + +Use `newPool` instead of `existingPool` when creating a pool: + +```json +{ + "token0Address": "
", + "token1Address": "
", + "fee": 3000, + "tickSpacing": 60, + "initialPrice": "" +} +``` + +Response: + +```json +{ + "create": { "to": "...", "data": "...", "value": "...", "chainId": 8453 }, + "token0": { "tokenAddress": "...", "amount": "..." }, + "token1": { "tokenAddress": "...", "amount": "..." }, + "adjustedMinPrice": "...", + "adjustedMaxPrice": "...", + "tickLower": -200400, + "tickUpper": -199200 +} +``` + +Prefer `tickBounds` when possible. `priceBounds` can be accepted by the API, but its price units are easy to misread; validate carefully before using it. + +### Manage Existing Positions + +| Action | Endpoint | Key params | +| --- | --- | --- | +| Add liquidity | `POST /lp/increase` | `walletAddress`, `chainId`, `protocol`, `token0Address`, `token1Address`, `nftTokenId`, `independentToken { tokenAddress, amount }` | +| Remove liquidity | `POST /lp/decrease` | `walletAddress`, `chainId`, `protocol`, `token0Address`, `token1Address`, `nftTokenId`, `liquidityPercentageToDecrease` (0-100) | +| Collect fees | `POST /lp/claim_fees` | `walletAddress`, `chainId`, `protocol`, `tokenId`; optional `simulateTransaction` | +| Create V2 position | `POST /lp/create_classic` | `walletAddress`, `poolParameters { token0Address, token1Address, chainId }`, `independentToken { tokenAddress, amount }` | + +Optional on LP operation endpoints: `"slippageTolerance": ` where `0.5` means 0.5%. See [Slippage Warnings](#slippage-warnings) before submitting elevated values. + +Important: LP APIs can return calldata for syntactically valid `nftTokenId` values even if the connected wallet may not own the position. Treat generated calldata as a transaction preview input, not proof of ownership or guaranteed execution. + +### Claim Fees: `POST /lp/claim_fees` + +```json +{ + "protocol": "V4", + "walletAddress": "
", + "chainId": 8453, + "tokenId": "", + "simulateTransaction": false +} +``` + +Response: + +```json +{ + "claim": { "to": "...", "data": "...", "value": "0x00", "chainId": 8453 }, + "token0": { "tokenAddress": "...", "amount": "..." }, + "token1": { "tokenAddress": "...", "amount": "..." } +} +``` + +No approval step is needed for fee collection. + +### Create V2 Position: `POST /lp/create_classic` + +```json +{ + "walletAddress": "
", + "poolParameters": { + "token0Address": "0x4200000000000000000000000000000000000006", + "token1Address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "chainId": 8453 + }, + "independentToken": { + "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amount": "1000000" + }, + "simulateTransaction": false +} +``` + +For V2 create, `/lp/check_approval` may return multiple approval transactions. Batch all approvals plus the `create` transaction if you want one `send_calls` approval link. + +### LP `send_calls` + +For any LP operation response field such as `create`, `increase`, `decrease`, or `claim`, map to: + +```json +{ + "chain": "base", + "calls": [ + { "to": "", "value": "", "data": "" } + ] +} +``` + +For approval + action batching: + +```js +const operationTx = + operationResponse.create ?? + operationResponse.increase ?? + operationResponse.decrease ?? + operationResponse.claim; + +const calls = [ + ...approvalResponse.transactions.map((entry) => ({ + to: entry.transaction.to, + value: entry.transaction.value ?? "0x0", + data: entry.transaction.data + })), + { + to: operationTx.to, + value: operationTx.value ?? "0x0", + data: operationTx.data + } +]; +``` + +### LP Orchestration + +```text +1. get_wallets -> address +2. For V3/V4 create, call /lp/pool_info to discover poolReference +3. Build LP token amount list in base units +4. web_request POST /lp/check_approval +5. web_request POST /lp/create, /lp/increase, /lp/decrease, /lp/claim_fees, or /lp/create_classic +6. send_calls("base", approval calls + operation call) +7. Open the approvalUrl if requested; do not approve unless the user explicitly asks +8. get_request_status only after the user acts +``` + +--- + +## Example Prompts + +**Swap 1 USDC to WETH on Base** +1. `get_wallets` -> address; use USDC address `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`, amount `1000000`. +2. `web_request POST /check_approval` with `x-permit2-disabled: true`. +3. `web_request POST /quote` with `x-permit2-disabled: true`. +4. Remove null permit fields from quote response. +5. `web_request POST /swap` with `x-permit2-disabled: true`. +6. `send_calls` approval + swap, or swap only if no approval was returned. + +**Create a V4 ETH/USDC LP position on Base** +1. `get_wallets` -> address. +2. `web_request POST /lp/pool_info` to find the pool reference. +3. `web_request POST /lp/check_approval` using `lpTokens` array. +4. `web_request POST /lp/create` using `existingPool.poolReference` and `tickBounds`. +5. `send_calls` approval transactions plus create transaction. + +**Add liquidity to an existing V4 position** +1. `get_wallets` -> address. +2. Confirm the wallet owns the `nftTokenId` or warn that generated calldata may still fail. +3. `web_request POST /lp/check_approval` with action `"INCREASE"`. +4. `web_request POST /lp/increase`. +5. `send_calls` approval transactions plus increase transaction. + +**Collect LP fees** +1. `get_wallets` -> address. +2. `web_request POST /lp/claim_fees` with `tokenId`. +3. `send_calls` claim transaction. + +--- + +## Slippage Warnings + +High slippage tolerance exposes the user to worse fills and sandwich/MEV attacks. Before calling `/swap` or any LP endpoint with an elevated value, warn the user and get explicit confirmation: + +| Tolerance | Level | Action | +| --- | --- | --- | +| ≤ 1% | Normal | Proceed. | +| > 1% and ≤ 5% | Elevated | Mention the value and ask the user to confirm. | +| > 5% and ≤ 20% | High | Warn that the trade can fill significantly below quote and is a likely sandwich target. Require explicit confirmation. | +| > 20% | Very high | Strongly warn; do not submit without the user re-confirming the exact number. | + +Apply the same thresholds to swap and LP slippage. If the user did not specify a value, prefer `autoSlippage: "DEFAULT"` on swaps rather than picking a high number. + +--- + +## Notes + +- Native ETH address: `0x0000000000000000000000000000000000000000` +- USDC on Base: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` +- WETH on Base: `0x4200000000000000000000000000000000000006` +- Token amounts are base units: USDC = 1e6 per token, ETH/WETH = 1e18 per token. +- Use `chain: "base"` with `send_calls`, not numeric chain id. +- For swap proxy approval, include `x-permit2-disabled: true` on `/check_approval`, `/quote`, and `/swap`. +- For swap proxy approval, remove null `permitData` and `permitTransaction` fields before calling `/swap`. +- `/quote` response fields must be spread directly into `/swap` body, not nested under a `quote` key. +- Do not send `signature` in the proxy swap flow. diff --git a/docs/ai-agents/skills/plugins/virtuals.md b/docs/ai-agents/skills/plugins/virtuals.md new file mode 100644 index 000000000..be9a65ffa --- /dev/null +++ b/docs/ai-agents/skills/plugins/virtuals.md @@ -0,0 +1,141 @@ +--- +title: "Virtuals Plugin" +description: "Skill plugin reference for creating and operating Virtuals (ACP) AI agents through the Virtuals MCP, signed in via Base MCP." +--- + +# Virtuals Plugin + +> [!IMPORTANT] +> Complete the short Base MCP onboarding flow defined in `SKILL.md` before calling any Virtuals tool. Virtuals is **session-authenticated**: every tool requires a JWT `token` parameter obtained via a SIWE login that the user must approve through Base MCP. Run the [Auth flow](#auth-flow) once per session and reuse the token for subsequent calls. + +Virtuals (ACP — Agent Commerce Protocol) is a platform for creating and operating autonomous AI agents that can transact onchain, hold payment cards, and own email identities. The Virtuals MCP server prepares and executes agent management, agent card, and agent email operations. The Base MCP wallet is used only to sign the SIWE login challenge — Virtuals does not route onchain transactions through Base MCP after auth. + +The exact list of Virtuals tools, their parameters, and the capabilities they expose are advertised by the Virtuals MCP itself — read the tool descriptions rather than relying on a fixed catalog in this file. Tools may be added, renamed, or removed. + +## MCP Server + +URL: `https://mcp.acp.virtuals.io/` + +## Installation (alongside Base MCP) + +```json +{ + "mcpServers": { + "base-account": { "url": "https://mcp.base.org" }, + "virtuals": { "url": "https://mcp.acp.virtuals.io/" } + } +} +``` + +Claude Code: `claude mcp add virtuals --transport http https://mcp.acp.virtuals.io/` + +## Capabilities Overview + +Three main capability groups (consult the MCP tool catalog for the current exact tool names): + +- **Agent management** — create agents, list your agents, prepare and poll the launch flow. +- **Agent cards** — sign agents up for payment cards, issue cards, set spend limits, manage 3DS codes, update card profile, set up payment methods. +- **Agent email** — give agents an email identity, read/search the inbox, fetch threads/attachments, compose and reply to emails, extract OTPs or links from messages. + +## Auth Flow + +Virtuals authentication is stateless from the MCP's perspective — no session is stored server-side. Every authenticated tool requires the JWT `token` from `login_complete` as an explicit parameter. Run this flow **once at the start of the session** and reuse the token until it expires (~1 hour); use `login_refresh` with the refresh token thereafter. + +``` +get_wallets (Base MCP) → baseAccount.address + ↓ +login_start (Virtuals) → SIWE message (with nonce + 30-min expiry) + ↓ +sign type=personal_sign (Base MCP) → approvalUrl + requestId + ↓ user approves at the link +get_request_status (Base MCP) → { signature, status: "signed" } + ↓ +login_complete (Virtuals) message + signature → { token, refreshToken, walletAddress } + ↓ +Reuse `token` as the `token` parameter on every subsequent Virtuals tool call. +``` + +### Step-by-step + +1. **Fetch the wallet address.** Call Base MCP `get_wallets` and use `baseAccount.address`. +2. **Start login.** Call Virtuals `login_start` with that address. Returns the SIWE `message` to sign and a `nonce` valid for 30 minutes. +3. **Sign with Base MCP.** Call `sign` with `type: "personal_sign"` and `data: { message: }`. Returns an `approvalUrl` and `requestId`. +4. **Present the approval link.** Show the user the approval URL as **"Approve Sign-In"** (or similar neutral language — see [../references/approval-mode.md](../references/approval-mode.md)). Wait for them to confirm. +5. **Poll for the signature.** Call Base MCP `get_request_status` once after the user confirms; the result includes the `signature` value. +6. **Complete login.** Call Virtuals `login_complete` with the **exact** `message` from step 2 and the `signature` from step 5. Returns `{ token, refreshToken, walletAddress }`. +7. **Store and reuse the token.** Pass `token` as the `token` parameter on every subsequent Virtuals tool call. Refresh with `login_refresh` once the JWT expires. + +## Troubleshooting + +The Base MCP wallet is a Coinbase Smart Wallet (contract account, not a plain EOA). The SIWE signing path has a few sharp edges — these are the failure modes we've observed. + +### 1. `Invalid SIWE signature` (401) on `login_complete` + +The most common cause: the signature returned by Base MCP `sign` is an **ERC-6492 wrapped** signature (used for counterfactual or contract-deployment-attached signing), and the Virtuals verifier expects a plain **ERC-1271** signature. + +You can recognize ERC-6492 wrapped signatures by: +- Length: thousands of hex characters (the inner ERC-1271 signature is much shorter). +- They end with the magic suffix `6492649264926492649264926492649264926492649264926492649264926492`. + +**Recovery:** restart the auth flow from `login_start`. Subsequent approvals from the same wallet can produce a plain ERC-1271 signature that Virtuals accepts; the wrapping behavior isn't deterministic from one approval to the next. If it keeps returning ERC-6492 wrapped signatures, ask the user to confirm via the same approval URL again — repeated retries typically resolve to a plain ERC-1271 signature within a few attempts. + +Do **not** try to unwrap the ERC-6492 envelope manually and submit just the inner bytes — Virtuals rejects that too, because the inner Coinbase Smart Wallet signature format isn't a vanilla ERC-1271 `(r, s, v)` either. + +### 2. Address case mismatch + +Pass the wallet address to `login_start` exactly as returned by Base MCP `get_wallets`. The returned address is lowercase, which is fine — Virtuals normalises it. But when calling `login_complete`, **the `message` you pass must be the verbatim string returned by `login_start`** (which uses the EIP-55 checksummed address). Do not lowercase or otherwise reformat the message. A single character change in casing inside the message hashes to a different value than what was signed and the server will return `Invalid SIWE signature`. + +Verified working pattern: +- `login_start` input: lowercase address (e.g. `0xca8f1eb...`) → fine +- `login_complete` `message`: verbatim string from the `login_start` response (contains the checksummed `0xCa8F1eB...`) → required + +### 3. Nonce expired + +SIWE nonces expire 30 minutes after `login_start`. If the user took a long time to approve, or you waited and tried again later, `login_complete` will fail. Restart from `login_start` to get a fresh nonce — do not reuse an old one. + +### 4. Message whitespace / newlines + +The SIWE `message` field in `sign` (as `data.message`) and the `message` field in `login_complete` must be byte-identical to what `login_start` returned. JSON-escape `\n` correctly when embedding in the `sign` tool's `data` payload. When passing the message to `login_complete`, use real newlines (the same characters the JSON `\n` escapes decoded to). Any mismatch — extra trailing whitespace, CRLF vs LF, etc. — breaks the hash and yields `Invalid SIWE signature`. + +### 5. Wallet not deployed on Base + +ERC-1271 verification calls the wallet contract on Base. If the Coinbase Smart Wallet has never been activated on Base mainnet, the contract isn't deployed and verification will fail even with a structurally correct signature. Confirm deployment by checking `eth_getCode` for the address on Base mainnet — non-empty bytecode means it's deployed. Most Base MCP users will already have a deployed wallet; if not, ask the user to perform a no-op transaction first (any Base transaction will deploy the wallet). + +### 6. Token expired mid-session + +JWTs returned by `login_complete` expire after about an hour. When a Virtuals tool returns a 401, call `login_refresh` with the stored `refreshToken` to get a new access token; only re-run the full SIWE flow if the refresh token is also rejected. + +## Example Prompts + +``` +Log me into Virtuals +``` +1. `get_wallets` (Base MCP) → `baseAccount.address`. +2. `login_start` (Virtuals) with that address → SIWE message. +3. `sign` (Base MCP) with `type: "personal_sign"`, `data: { message }` → approval URL. +4. Show the user the approval link; wait for confirmation. +5. `get_request_status` (Base MCP) → signature. +6. `login_complete` (Virtuals) with the message + signature → token. +7. Confirm: *"You're signed in. Your Virtuals session is good for about an hour."* + +``` +List all my Virtuals agents +``` +1. Ensure session token is current (run [Auth flow](#auth-flow) if not). +2. Call the Virtuals agent-list tool with `token`. + +``` +Create a Virtuals agent with email and a payment card +``` +1. Ensure session token. +2. Call the Virtuals agent-create tool. +3. For email: call the email-identity creation tool with the new `agentId`. +4. For a card: call the card signup tool, poll until verified, then issue a card and set spend limits. + +## Important Notes + +- **Stateless auth.** The token must be passed to every Virtuals tool — the MCP server doesn't remember it between calls. +- **Session scope.** Only one wallet can be authenticated per token. To switch wallets, run the full SIWE flow again with the new address. +- **No onchain operations through Base MCP after auth.** The Base MCP wallet is only used for the SIWE signature. Virtuals' tools (card issuance, email, agent ops) operate against Virtuals' own backend. +- **Sensitive outputs.** Agent card details and email contents can include private information. Don't echo card numbers, 3DS codes, OTPs, or email bodies to chat unless the user has clearly asked for them. +- **Wallet vs base account.** Use `baseAccount.address` from `get_wallets` for SIWE — not the agent wallets (`agentWallets[]`), which are session-scoped delegations for transactional flows. diff --git a/docs/ai-agents/skills/references/approval-mode.md b/docs/ai-agents/skills/references/approval-mode.md new file mode 100644 index 000000000..7a8e18205 --- /dev/null +++ b/docs/ai-agents/skills/references/approval-mode.md @@ -0,0 +1,37 @@ +--- +title: "Approval Mode" +description: "Skill reference for how Base MCP returns approval URLs and request IDs for every write call." +--- + +> Today the Base MCP exposes a single execution mode for write tools: **approval mode** (the user manually approves each transaction via a returned URL). Other modes may be added later. Treat the tool descriptions exposed by the MCP as the source of truth — if a future write tool returns a different shape or skips the approval step, follow what the MCP describes, not this file. + +# Approval Mode + +In approval mode, every write call (send, swap, sign, batched calls, and any plugin-prepared transaction routed through Base MCP) returns an **approval URL** plus a **request ID**. The user opens the URL, approves the transaction in their wallet UI, and then the agent polls the request ID for completion. + +## Flow + +1. **Call the write tool.** The response includes: + - an approval URL (the field name is on the MCP response — typically `approvalUrl`) + - a request ID (typically `requestId`) +2. **Show the user the link.** Present it as **"Approve Transaction"** (or similar neutral language). Do not name or describe the wallet provider behind the link, even when the URL hostname suggests one — the underlying wallet UI is an implementation detail and may change. Just give the user the link to click. + - Beginner-friendly phrasing: _"Open this to approve the transaction: [Approve Transaction]()"_ + - Terse phrasing: _"[Approve Transaction]()"_ +3. **In CLI harnesses, also open the link automatically.** When you're running in an environment with a Bash/shell tool (Claude Code, Codex, Cursor terminal, etc.), don't just print the URL — also open it in the user's default browser so they don't have to click. Always print the link too as a fallback, then run the platform-appropriate open command: + - macOS: `open ""` + - Linux: `xdg-open ""` (fall back to `wslview` under WSL) + - Windows: `start "" ""` (or via PowerShell: `Start-Process ""`) + + Skip this step on chat-only surfaces (ChatGPT, Claude.ai) — they don't have a shell, so just show the link. +4. **Wait for the user to confirm they approved.** Don't poll in a tight loop while they're still acting. +5. **Call the status-poll tool** (typically `get_request_status`) with the request ID once. +6. **Only report success** when the status tool confirms completion. + +## Common mistakes + +- Reporting success before the status tool confirms it — the user may not have approved yet. +- Skipping the approval link — the transaction cannot complete without user action. +- Naming the wallet/approval provider, or surfacing the raw hostname as the link text — say "Approve Transaction". +- Polling the status tool in a tight loop instead of once after the user confirms. +- Forgetting to also auto-open the link in CLI harnesses where a shell is available — printing alone makes the user copy-paste unnecessarily. +- Trying to auto-open in chat-only harnesses where no shell exists — that just produces an error. diff --git a/docs/ai-agents/skills/references/batch-calls.md b/docs/ai-agents/skills/references/batch-calls.md new file mode 100644 index 000000000..a65ebaa84 --- /dev/null +++ b/docs/ai-agents/skills/references/batch-calls.md @@ -0,0 +1,40 @@ +--- +title: "Batched Contract Calls" +description: "Skill reference for Base MCP's EIP-5792 batched contract calls." +--- + +# Batched Contract Calls (EIP-5792) + +Base MCP exposes a batched-calls tool (typically `send_calls`) that submits multiple EIP-5792 `wallet_sendCalls` for a single user approval. Use it for arbitrary contract interactions, multi-step transactions, or any flow that combines an approval with a follow-up action. + +> **Batching is preferred whenever a flow involves a token approval followed by a protocol action** (approve + deposit, approve + supply, approve + swap, etc.). Also batch whenever a plugin or protocol endpoint returns multiple transactions in a single response. Don't split these into sequential single-`send` calls when one batched approval can execute them atomically. + +## When to use + +- Protocol interactions not covered by `send` or `swap` (DeFi, NFT mints, approvals, governance actions). +- Combining multiple operations into one user approval. +- Executing a transaction array returned by a plugin's prepare-style endpoint — pass the array straight through. + +## Shape + +The MCP advertises the exact parameter names and types — defer to its tool description. The general shape is: + +- A `chainId` (hex with `0x` prefix — `0x2105` for Base mainnet, `0x14a34` for Base Sepolia). +- A `calls` array of `{ to, value, data }` objects: + - `to` — target address, `0x`-prefixed (required) + - `value` — hex ETH in wei (e.g. `0x0`), optional + - `data` — calldata hex, optional + +## Approval flow + +Same as any write tool: the response returns an approval URL and request ID. See [approval-mode.md](approval-mode.md). + +## Generic orchestration + +``` +plugin or protocol API → { transactions: [...] } (or equivalent) + ↓ map each transaction to a { to, value, data } call +batched-calls tool (chainId, calls) → approval URL + request ID + ↓ user approves +status-poll tool (request ID) → confirmed +``` diff --git a/docs/ai-agents/skills/references/custom-plugins.md b/docs/ai-agents/skills/references/custom-plugins.md new file mode 100644 index 000000000..38f5aa113 --- /dev/null +++ b/docs/ai-agents/skills/references/custom-plugins.md @@ -0,0 +1,49 @@ +--- +title: "Custom Plugins and the web_request Allowlist" +description: "Skill reference for how Base MCP routes plugin HTTP calls and which surfaces are allowlisted." +--- + +# Custom / Non-Native Plugins and the `web_request` Allowlist + +The native plugins shipped with this skill (Morpho, Moonwell, Uniswap, Avantis) have their HTTP APIs **allowlisted in the Base MCP `web_request` tool**. This matters because Claude and ChatGPT restrict which APIs an agent can call directly from their surface — `web_request` is what makes those calls possible. + +Custom or user-supplied plugins (anything outside the four native ones) are almost certainly **not** in the allowlist and will be rejected by `web_request`. + +## Priority order for HTTP calls + +Use this order **for every plugin call — native or not**: + +### 1. Harness HTTP tool (preferred whenever available) + +If the current environment lets you call HTTP APIs directly — e.g. Claude Code, Codex, Cursor, or any harness where you have a fetch / curl / shell tool — **use that tool first**, even for native plugins. It supports any HTTP method (GET, POST, etc.), avoids the allowlist entirely, and gives you the full response without round-tripping through the MCP. + +Only fall back to `web_request` if you don't have a usable HTTP tool in the current harness. + +### 2. `web_request` (when no harness HTTP tool exists) + +If the harness has no direct HTTP capability, route the call through Base MCP's `web_request`: + +- **Native plugin host** — works out of the box (the host is allowlisted). +- **Non-native plugin host** — will be rejected. Do not silently retry. Move to path 3. + +### 3. User-paste fallback (Claude / ChatGPT consumer surfaces, non-native hosts) + +Claude and ChatGPT *can* fetch GET URLs themselves, but for security reasons they will only fetch URLs that the **user has pasted into the chat**. The agent cannot freely construct and fetch arbitrary URLs on its own. + +That makes the fallback effectively **GET-only**: there's no equivalent escape hatch for POST/PUT/DELETE with custom headers and a body, because the user can't realistically inject those into the chat in a fetchable form. + +So for non-native plugins on Claude / ChatGPT consumer surfaces: + +- **Only GET-style APIs are viable.** If the protocol's API requires POST or other write methods to retrieve calldata, surface this limitation to the user — explain that their environment can't perform the fetch and that they would need a harness with HTTP tools (e.g. Claude Code) to proceed. +- For GET endpoints: + 1. Construct the full URL with every query parameter encoded inline (address, amount, slippage, chain, etc.). + 2. Show the URL to the user and ask them to paste it back into the chat. Once pasted, you can fetch it yourself — that's the security model these surfaces enforce. + 3. Parse the response and continue the flow (e.g. map returned calldata into the batched-calls tool, then walk through the approval flow — see [approval-mode.md](approval-mode.md) and [batch-calls.md](batch-calls.md)). + +## Decision summary + +| Situation | What to do | +|-----------|------------| +| Any plugin, harness has an HTTP tool (Claude Code, Codex, Cursor, …) | **Use the harness's HTTP tool first.** Any method is fine. Don't route through `web_request` when a direct call is available. | +| Native plugin, no harness HTTP tool | Use `web_request` — the host is allowlisted. | +| Non-native plugin, no harness HTTP tool (Claude / ChatGPT consumer apps) | GET only. Construct the URL, ask the user to paste it into the chat so you're allowed to fetch it, then parse the response. If the API needs POST, tell the user this surface can't support it. | diff --git a/docs/ai-agents/skills/references/install.md b/docs/ai-agents/skills/references/install.md new file mode 100644 index 000000000..474b5e583 --- /dev/null +++ b/docs/ai-agents/skills/references/install.md @@ -0,0 +1,159 @@ +--- +title: "Installing Base MCP" +description: "Skill reference for installing the Base MCP server in Claude, ChatGPT, Cursor, Codex, and other surfaces." +--- + +# Installing Base MCP + +> Canonical source: ****. That page is kept up to date with the latest one-click install links, deep-links, and connector flows for each surface. Send the user there first; the instructions below are a backup so the agent can still walk a user through install without leaving the chat. + +The MCP server URL is the same everywhere: **`https://mcp.base.org`** + +--- + +## Claude (Claude.ai web, Claude Desktop, iOS, Android) + +One-click add: + +``` +https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Base&connectorUrl=https%3A%2F%2Fmcp.base.org +``` + +Or manually: + +1. Open **Customize → Connectors → Add custom connector** +2. Fill in: + - **Name**: `Base` + - **Remote MCP server URL**: `https://mcp.base.org` +3. Click **Add** + +A browser tab opens to authorize on first use — sign in with Base. + +--- + +## ChatGPT + +Open (or **Settings → Connectors**), then: + +1. Enable **Developer Mode** if prompted (under Advanced) +2. Click **Create** → **New App** modal opens +3. Fill in: + - **Name**: `Base` + - **Description** (optional): `Wallet and onchain tools for Base` + - **MCP Server URL**: `https://mcp.base.org` + - **Authentication**: `OAuth` +4. Check **I understand and want to continue** on the risk warning +5. Click **Create** + +ChatGPT prompts for authorization the first time a wallet tool is called. + +--- + +## Claude Code + +Add to the current project: + +```bash +claude mcp add --transport http base https://mcp.base.org +``` + +Install globally across all projects: + +```bash +claude mcp add --transport http --scope user base https://mcp.base.org +``` + +Verify: + +```bash +claude mcp list +``` + +The `base` server shows with a tool count once active. Inside a session, `/mcp` also shows server status. + +--- + +## Codex + +```bash +codex mcp add base --url https://mcp.base.org/ +``` + +Or in `codex.toml`: + +```toml +[mcp_servers.base] +url = "https://mcp.base.org/" +``` + +--- + +## Cursor + +Deep link install: + +``` +cursor://anysphere.cursor-deeplink/mcp/install?name=base&config=eyJ1cmwiOiJodHRwczovL21jcC5iYXNlLm9yZyJ9 +``` + +Or manually in `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): + +```json +{ + "mcpServers": { + "base": { + "url": "https://mcp.base.org" + } + } +} +``` + +Restart Cursor, then **Settings → MCP** to confirm `base` is active. + +--- + +## Hermes + +Hand the agent the quickstart and let it install itself: + +``` +Install the Base MCP server from https://docs.base.org/ai-agents/quickstart +``` + +Manual install — edit `~/.hermes/config.yaml`: + +```yaml +mcp_servers: + base: + url: "https://mcp.base.org" +``` + +Then start a new chat (or `/reload-mcp` in an existing one). + +--- + +## Authorization (first use) + +The first time a wallet tool is called, an auth modal opens for the user to authorize the Base Account. Click **Allow** once. (See the live demo on the quickstart page.) After that, write operations still require per-transaction approval — see [approval-mode.md](approval-mode.md). + +--- + +## Did it work? + +Ask the assistant: + +> Show me my wallets + +If it replies with a wallet address, the MCP is connected. If it says it doesn't have wallet tools, the MCP isn't loaded — retry the install steps or fall back to the quickstart link. + +--- + +## Troubleshooting + +| Symptom | Try | +|---------|-----| +| No browser tab for sign-in | Open `https://mcp.base.org` directly, sign in, then re-add the server. | +| "Integration not found" / "Tool not available" | Restart the app — the server may not have finished loading. | +| Integrations / Connectors tab missing | App version is too old — update to the latest. | +| `web_request` rejects a hostname | The hostname isn't in the allowlist. For native plugins, this shouldn't happen; for custom plugins see [custom-plugins.md](custom-plugins.md). | +| Anything else | Send the user to . | diff --git a/docs/ai-agents/skills/references/tone.md b/docs/ai-agents/skills/references/tone.md new file mode 100644 index 000000000..a3a9c5a86 --- /dev/null +++ b/docs/ai-agents/skills/references/tone.md @@ -0,0 +1,49 @@ +--- +title: "Tone" +description: "Skill reference for the language and tone rules an agent should follow when using Base MCP." +--- + +# Tone + +These rules apply for the entire conversation. Load this file at session start. + +## Language rules (always enforced) + +- Write **onchain** — never "on-chain" or "on chain" +- Never use the word **web3** +- Never say "on-chain" in any form + +## Detecting user sophistication + +Infer from available signals — do not ask the user directly. + +**Sophisticated user signals:** +- Harness is Claude Code, Cursor, or a direct API/SDK integration +- User pastes raw addresses, calldata, or hex values +- User uses precise protocol terminology (e.g. "health factor", "calldata", "EIP-712", "send_calls") + +**Beginner user signals:** +- Harness is Claude.ai app or ChatGPT desktop/web +- User asks "how do I", "what is", "can you help me" +- No address or technical data pasted; plain conversational language + +## Stating the assumed level + +At the start of the first substantive response, briefly state the assumed level so the user can correct it if wrong: + +- Beginner assumed: *"I'll keep things straightforward — let me know if you want more technical detail."* +- Sophisticated assumed: state nothing; just proceed with terse, precise responses. + +## Beginner mode + +- Use plain terms: "your wallet address", "approve the transaction in your browser", "this may take a few seconds to confirm" +- Avoid raw hex, ABI references, and protocol jargon without a plain-English explanation alongside +- Explain approval steps in order: "First open this link, then come back and let me know when you've approved it" +- Use friendly formatting: short paragraphs, bullet points for steps + +## Sophisticated mode + +- Be terse and precise +- Skip hand-holding and step-by-step preamble +- Use parameter names and return field names directly (e.g. "`approvalUrl`", "`requestId`") +- Omit explanations the user clearly already knows diff --git a/docs/ai-agents/skills/trading/alchemy-agentic-gateway.mdx b/docs/ai-agents/skills/trading/alchemy-agentic-gateway.mdx deleted file mode 100644 index 5825974c3..000000000 --- a/docs/ai-agents/skills/trading/alchemy-agentic-gateway.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: "Alchemy Agentic Gateway" -description: "Skill for accessing Alchemy's blockchain APIs — token balances, NFTs, portfolio data, and prices — via x402 SIWE auth" -keywords: ["Alchemy agentic gateway skill", "Alchemy x402 skill", "onchain data agent skill", "SIWE x402 skill", "alchemy skill agent"] ---- - -The Alchemy Agentic Gateway skill gives your agent access to Alchemy's enhanced blockchain APIs at `https://x402.alchemy.com` — token balances, NFT metadata, portfolio data, token prices, and decoded transaction history — authenticated via Sign-In With Ethereum (SIWE) and paid per call via x402. - -## Install - -```bash Terminal -npx @alchemy/x402 wallet generate -``` - -Then follow the skill setup to sign a SIWE auth token. The full setup guide is in the skill reference below. - -## What the skill covers - -| Data | Description | -|------|-------------| -| **Token balances** | ERC-20 balances for any address on Base | -| **NFT metadata** | Token attributes, images, and ownership | -| **Portfolio data** | Multi-chain token holdings with metadata | -| **Token prices** | Current prices by symbol | -| **Transaction history** | Decoded asset transfers | - -## Example prompts - -```text -Check the token balances at 0xYourAddress on Base -``` - -```text -Get the portfolio for 0xYourAddress across all chains -``` - -```text -What's the current price of ETH and USDC? -``` - -```text -Show me the NFTs owned by 0xYourAddress -``` - -## Reference - -- [Alchemy Agentic Gateway skill →](https://github.com/alchemyplatform/skills/tree/main/skills/agentic-gateway) diff --git a/docs/ai-agents/skills/trading/coingecko.mdx b/docs/ai-agents/skills/trading/coingecko.mdx deleted file mode 100644 index bdbf7bd4f..000000000 --- a/docs/ai-agents/skills/trading/coingecko.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "CoinGecko" -description: "Skill for fetching live crypto price feeds, market cap, and OHLCV data via x402 — no API key required" -keywords: ["CoinGecko skill", "price feed skill", "crypto market data x402 skill", "agent data source skill", "CoinGecko x402 agent"] ---- - -CoinGecko provides cryptocurrency price feeds, market cap data, and historical OHLCV via x402-enabled endpoints. Your agent pays per call in USDC — no API key, no subscription, no rate-limit tier. - -## Install - -Access CoinGecko data through your existing wallet skill: - -- **CDP Agentic Wallet** — use the `pay-for-service` skill (`npx skills add coinbase/agentic-wallet-skills`) -- **Sponge Wallet** — use the built-in x402 proxy (no extra install) -- **Bankr** — prompt directly, Bankr handles discovery and payment - -## What the skill covers - -| Data | Description | -|------|-------------| -| **Price feeds** | Current price for any asset in USD or other currencies | -| **Market data** | Market cap, volume, and circulating supply | -| **Historical OHLCV** | Open/high/low/close/volume over any time range | - -## Example prompts - -```text -Get the current ETH price from CoinGecko -``` - -```text -Get current prices for ETH, BTC, and SOL -``` - -```text -Fetch the 7-day OHLCV for Bitcoin from CoinGecko -``` - -```text -What's the market cap of Ethereum? -``` - -## Reference - -- [pay-for-service skill →](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) -- [CoinGecko API docs →](https://docs.coingecko.com) diff --git a/docs/ai-agents/skills/trading/swap-execution.mdx b/docs/ai-agents/skills/trading/swap-execution.mdx deleted file mode 100644 index 8a6d005ea..000000000 --- a/docs/ai-agents/skills/trading/swap-execution.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Swap Execution" -description: "Skills for executing token swaps on Base using wallet-native tools across Bankr, CDP Agentic Wallet, and Sponge" -keywords: ["swap execution skill", "token swap agent skill", "Bankr swap skill", "CDP trade skill", "Sponge swap skill"] ---- - -Bankr, CDP Agentic Wallet, and Sponge Wallet all include built-in swap skills — no external DEX integration required. Install any of the three wallet skills and your agent can trade tokens on Base with a simple prompt. - -## Install - -Choose the wallet skill that fits your agent: - -| Wallet | Install | -|--------|---------| -| **Bankr** | `install the bankr skill from https://github.com/BankrBot/skills` | -| **CDP Agentic Wallet** | `npx skills add coinbase/agentic-wallet-skills` | -| **Sponge Wallet** | Register via `https://api.wallet.paysponge.com/api/agents/register` | - -## What the skill covers - -| Capability | Bankr | CDP | Sponge | -|------------|-------|-----|--------| -| Token swaps on Base | ✓ | ✓ | ✓ | -| Cross-chain swaps | ✓ | — | ✓ (via deBridge) | -| Gas sponsorship | ✓ | ✓ | — | -| Preconf simulation | ✓ | ✓ | ✓ | - -## Example prompts - -```text -Buy $50 of ETH on Base -``` - -```text -Swap 100 USDC for ETH at market price -``` - -```text -Simulate buying $50 of ETH then execute if the price impact is below 1% -``` - -```text -Trade 0.01 ETH for USDC -``` - -## Reference - -- [Bankr docs →](https://docs.bankr.bot) -- [CDP trade skill →](https://docs.cdp.coinbase.com/agentic-wallet/skills) -- [Sponge skill doc →](https://wallet.paysponge.com/skill.md) diff --git a/docs/ai-agents/skills/wallets/bankr.mdx b/docs/ai-agents/skills/wallets/bankr.mdx deleted file mode 100644 index 051841362..000000000 --- a/docs/ai-agents/skills/wallets/bankr.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Bankr" -description: "Skill that gives your AI agent a cross-chain wallet with built-in swaps, gas sponsorship, and token launching" -keywords: ["Bankr skill", "agent wallet skill", "cross-chain wallet agent", "Bankr AI agent", "bankr.bot skill"] ---- - -The Bankr skill gives your AI agent a self-custodied wallet across Base, Ethereum, Solana, Polygon, and Unichain. Gas is sponsored on supported chains, swap tools handle trades natively, and agents can launch tokens and earn trading fees to fund their own compute. - -## Install - -From your agent chat: - -```text -install the bankr skill from https://github.com/BankrBot/skills -``` - -Then create a dedicated account at [bankr.bot](https://bankr.bot) and generate an API key at [bankr.bot/api](https://bankr.bot/api). - -## What the skill covers - -| Capability | Description | -|------------|-------------| -| **Cross-chain wallet** | Hold and transfer funds on Base, ETH, Solana, Polygon, and Unichain | -| **Gas sponsorship** | Transactions on supported chains don't require ETH for gas | -| **Token swaps** | Buy and sell tokens with wallet-native tools — no DEX integration needed | -| **Token launching** | Deploy tokens on Base and redirect trading fees to the agent | -| **Trading signals** | Base network signals via the `bankr-signals` skill | - -## Example prompts - -```text -Buy $50 of ETH on Base -``` - -```text -Swap 100 USDC for ETH at market price -``` - -```text -Check my wallet balance across all chains -``` - -```text -Send 10 USDC to 0xRecipientAddress -``` - -## Reference - -- [Bankr docs →](https://docs.bankr.bot) -- [Bankr skills on GitHub →](https://github.com/BankrBot/skills) diff --git a/docs/ai-agents/skills/wallets/cdp-agentic-wallet.mdx b/docs/ai-agents/skills/wallets/cdp-agentic-wallet.mdx deleted file mode 100644 index 27f44d1a9..000000000 --- a/docs/ai-agents/skills/wallets/cdp-agentic-wallet.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "CDP Agentic Wallet" -description: "Skill that gives your AI agent an email-authenticated wallet on Base with x402 payments built in" -keywords: ["CDP agentic wallet skill", "coinbase agent wallet skill", "awal skill", "x402 agent skill", "npx skills add coinbase"] ---- - -The CDP Agentic Wallet skill gives your AI agent a managed wallet on Base authenticated via email OTP — no private keys in the agent's context. The skill bundle includes seven capabilities covering authentication, funding, sending, trading, and x402 payments. - -## Install - -```bash Terminal -npx skills add coinbase/agentic-wallet-skills -``` - -## What the skill covers - -| Skill | What it does | -|-------|-------------| -| `authenticate-wallet` | Sign in via email OTP — no private key required | -| `fund` | Add USDC via Coinbase Onramp | -| `send-usdc` | Send USDC to an Ethereum address or ENS name | -| `trade` | Swap tokens on Base | -| `search-for-service` | Find x402-compatible APIs in the Bazaar marketplace | -| `pay-for-service` | Make a paid x402 request automatically | -| `monetize-service` | Deploy a paid endpoint that charges other agents | - -## Example prompts - -```text -Sign in to my wallet with your@email.com -``` - -```text -Send 10 USDC to 0xRecipientAddress -``` - -```text -Trade 0.01 ETH for USDC -``` - -```text -Find a weather API and get the forecast for New York -``` - -```text -Set up a paid endpoint for my market data at $0.01 per request -``` - -## Reference - -- [CDP Agentic Wallet docs →](https://docs.cdp.coinbase.com/agentic-wallet/welcome) -- [Skill reference →](https://docs.cdp.coinbase.com/agentic-wallet/skills) diff --git a/docs/ai-agents/skills/wallets/sponge-wallet.mdx b/docs/ai-agents/skills/wallets/sponge-wallet.mdx deleted file mode 100644 index f90cc9b79..000000000 --- a/docs/ai-agents/skills/wallets/sponge-wallet.mdx +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: "Sponge Wallet" -description: "Skill that gives your AI agent a multi-chain wallet with native x402 payments, swaps, bridges, and banking" -keywords: ["Sponge wallet skill", "paysponge skill", "x402 proxy skill", "multi-chain agent wallet skill", "sponge skill.md"] ---- - -The Sponge Wallet skill gives your AI agent a multi-chain wallet covering Base, Ethereum, and Solana. It includes a native x402 payment proxy, token swaps, cross-chain bridges, prediction market trading, and banking integrations. - -## Install - -Register your agent to receive a wallet and API key: - -```text -Register my agent with Sponge Wallet using the API at https://api.wallet.paysponge.com/api/agents/register -``` - -Or read the full skill reference your agent can follow: - -```text -Read the Sponge skill doc at https://wallet.paysponge.com/skill.md -``` - -## What the skill covers - -| Capability | Description | -|------------|-------------| -| **Multi-chain wallet** | Hold and transfer funds on Base, Ethereum, and Solana | -| **Native x402 proxy** | Discover services, pay, and get responses in one step | -| **Token swaps** | Swap tokens on Base and Solana DEXs | -| **Cross-chain bridges** | Bridge assets across chains via deBridge | -| **Prediction markets** | Trade on Polymarket and Hyperliquid | -| **Banking** | Virtual bank accounts and ACH payouts via Bridge.xyz | - -## Example prompts - -```text -Check my Sponge wallet balance -``` - -```text -Swap 100 USDC for ETH on Base using Sponge -``` - -```text -Find a weather API and pay for it using my Sponge wallet -``` - -```text -Bridge 50 USDC from Base to Solana -``` - -## Reference - -- [Sponge Wallet skill doc →](https://wallet.paysponge.com/skill.md) diff --git a/docs/ai-agents/trading/data-fetching.mdx b/docs/ai-agents/trading/data-fetching.mdx deleted file mode 100644 index 317e5da3e..000000000 --- a/docs/ai-agents/trading/data-fetching.mdx +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: "Fetching Market Data" -description: "Use x402 to access live market data from CoinGecko, Alchemy, and other sources — no API key management required" -keywords: ["x402 market data", "trading agent data", "CoinGecko x402", "Alchemy x402", "pay per call data", "agent data fetching"] ---- - -import { DataFetchingDemo } from "/snippets/DataFetchingDemo.jsx" - -x402 is well-suited for trading data: your agent pays per call in USDC, with no API key registration, subscription management, or rate-limit tiers to negotiate. The agent gets the data, the provider gets paid — that's the entire relationship. - -## Mock Demo - - - -## Why x402 for market data - -- **No API key management** — your agent pays on demand; no credentials to rotate or store securely -- **Pay only for what you use** — no monthly subscription for data you might not always need -- **Works across any wallet** — [Sponge Wallet's](https://www.paysponge.com) x402 proxy, [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) skills, and `x402-axios` all handle payment automatically -- **Composable** — your agent can call multiple data providers in the same session, paying each separately - -## Discovering x402-compatible endpoints - -x402-compatible services publish a discovery document at `/.well-known/x402.json`. You can also browse the Bazaar catalog for a curated list of x402 data providers. - - - - Use the [`search-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/search-for-service) skill to search the x402 Bazaar: - - ```bash Terminal - npx awal@latest x402 bazaar search "price feed" - ``` - - To browse all available resources: - - ```bash Terminal - npx awal@latest x402 bazaar list --network base - ``` - - - ```bash Terminal - curl "https://api.wallet.paysponge.com/api/discover?query=price+feed" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" - ``` - - - -## Example x402 data sources - -### CoinGecko — price feeds - -CoinGecko provides cryptocurrency price feeds, market cap data, and historical OHLCV via x402-enabled endpoints. - -Fetch the current ETH price: - - - - Use the [`pay-for-service`](https://docs.cdp.coinbase.com/agentic-wallet/skills/pay-for-service) skill: - - ```bash Terminal - npx awal@latest x402 pay "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd" - ``` - - - ```bash Terminal - curl -X POST "https://api.wallet.paysponge.com/api/x402/fetch" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{ - "url": "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd", - "method": "GET", - "preferred_chain": "base" - }' - ``` - - - -### Alchemy — onchain data - -Alchemy exposes enhanced APIs for onchain data — token balances, NFT metadata, mempool visibility, and decoded transaction history — via their [Agentic Gateway](https://github.com/alchemyplatform/skills/tree/main/skills/agentic-gateway) at `https://x402.alchemy.com`. - - - Alchemy's x402 gateway uses SIWE wallet authentication via their own [`@alchemy/x402`](https://github.com/alchemyplatform/skills/tree/main/skills/agentic-gateway) CLI — not a generic x402 bazaar endpoint. Follow the setup steps below before making requests. - - -**Step 1 — Install and set up a wallet:** - -```bash Terminal -npx @alchemy/x402 wallet generate -``` - -**Step 2 — Generate a SIWE auth token:** - -```bash Terminal -npx @alchemy/x402 sign --private-key ./wallet-key.txt > siwe-token.txt -``` - -**Step 3 — Fetch token balances on Base:** - - - - ```bash Terminal - TOKEN=$(cat siwe-token.txt) - - curl -s -X POST "https://x402.alchemy.com/base-mainnet/v2" \ - -H "Content-Type: application/json" \ - -H "Authorization: SIWE $TOKEN" \ - -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "alchemy_getTokenBalances", - "params": ["0xYourAddress"] - }' - ``` - - - ```bash Terminal - TOKEN=$(cat siwe-token.txt) - - curl -s -X POST "https://x402.alchemy.com/data/v1/assets/tokens/by-address" \ - -H "Content-Type: application/json" \ - -H "Authorization: SIWE $TOKEN" \ - -d '{"addresses": ["0xYourAddress"], "withMetadata": true}' - ``` - - - ```bash Terminal - TOKEN=$(cat siwe-token.txt) - - curl -s -G "https://x402.alchemy.com/prices/v1/tokens/by-symbol" \ - --data-urlencode "symbols=ETH" \ - --data-urlencode "symbols=USDC" \ - -H "Authorization: SIWE $TOKEN" - ``` - - - -If the gateway returns `402`, handle payment with the CLI and retry: - -```bash Terminal -PAYMENT_SIG=$(npx @alchemy/x402 pay --private-key ./wallet-key.txt --payment-required "$PAYMENT_REQUIRED") -``` - -See the full [Alchemy Agentic Gateway skill](https://github.com/alchemyplatform/skills/tree/main/skills/agentic-gateway) for wallet bootstrap, payment handling, and SDK integration. - -## Making data calls inside an OpenClaw agent - -Install the [Sponge Wallet](https://www.paysponge.com) skill or [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) skills, then prompt your agent directly: - -```text -Get the current prices of ETH, BTC, and SOL from a paid data source -``` - -```text -Check the token balances at 0xYourWatchAddress on Base -``` - -The agent handles service discovery, payment, and data extraction. For a custom implementation using `x402-axios`: - -```bash Terminal -npm install x402-axios axios -``` - -```typescript TypeScript -import { withPaymentInterceptor } from "x402-axios"; -import axios from "axios"; - -// walletClient must be a viem-compatible WalletClient -const client = withPaymentInterceptor(axios.create(), walletClient); - -// If the endpoint returns 402, the interceptor pays automatically and retries -const response = await client.get("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"); -console.log(response.data); -``` - -The interceptor handles the full 402 → pay → retry flow. For wiring a viem `WalletClient` to your setup, see the [x402 client docs](https://docs.cdp.coinbase.com/x402/docs/client-server-model). - -## Related - - - - Execute swaps using Flashblocks timing and Base fee calibration. - - - - Full x402 flow, supported networks, and error handling. - - diff --git a/docs/ai-agents/trading/trade-execution.mdx b/docs/ai-agents/trading/trade-execution.mdx deleted file mode 100644 index 6d5675bea..000000000 --- a/docs/ai-agents/trading/trade-execution.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: "Trade Execution on Base" -description: "Base-specific patterns, fee calibration, and onchain signals for trading agents" -keywords: ["Flashblocks trading", "trading agent Base", "base_transactionStatus", "onchain trading agent EVM", "L1 L2 fee optimization Base", "token swap agent"] ---- - -import { TradeExecutionDemo } from "/snippets/TradeExecutionDemo.jsx" - -Base offers two structural advantages for trading agents: **Flashblocks** (200ms preconfirmed block state, 10× faster than the 2-second block) and an exposed L1/L2 fee structure that enables explicit cost-vs-speed tradeoffs. This page covers only what is unique to Base — general EVM execution patterns are in the [Ethereum documentation](https://ethereum.org/developers/docs), and Base's RPC methods are in the [API Reference](/base-chain/api-reference/rpc-overview). - -## Mock Demo - - - -## Executing swaps with wallet-native tools - -[Bankr](https://bankr.bot), [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/), and [Sponge Wallet](https://www.paysponge.com) all include built-in swap capabilities — no external DEX integration needed. - -With [Bankr](https://bankr.bot): - -```text -Buy $50 of ETH on Base -Swap 100 USDC for ETH at market price -``` - -With [CDP Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/) skills: - -```text -Buy $50 of ETH -Trade 0.01 ETH for USDC -``` - -With [Sponge Wallet](https://www.paysponge.com): - -```bash Terminal -curl -X POST "https://api.wallet.paysponge.com/api/swap" \ - -H "Authorization: Bearer $SPONGE_API_KEY" \ - -H "Sponge-Version: 0.2.1" \ - -H "Content-Type: application/json" \ - -d '{ - "fromToken": "USDC", - "toToken": "ETH", - "amount": "100", - "chain": "base" - }' -``` - -## Execution patterns - -### Use the preconf endpoint for all reads and submissions - -Always connect to `mainnet-preconf.base.org` (or `sepolia-preconf.base.org` for testnet) rather than the standard endpoint. This is the only way to access Flashblocks `pending` state, preconfirmed transaction streams, and `base_transactionStatus`. See [Flashblocks endpoints](/base-chain/flashblocks/app-integration#rpc-endpoints) for the full list. - -### Simulate against preconfirmed state before signing - -Call [`eth_simulateV1`](/base-chain/api-reference/flashblocks-api/eth_simulateV1) against the preconf endpoint with `"blockStateCalls"` targeting the `pending` block before signing any transaction. This catches reverts against current Flashblocks state rather than the last sealed block — which may be up to 2 seconds stale by the time your transaction lands. - -### Poll `base_transactionStatus` instead of blocking on a receipt - -After submission, [`base_transactionStatus`](/base-chain/api-reference/flashblocks-api/base_transactionStatus) returns preconfirmed inclusion status within a single Flashblock interval (~200ms). Blocking on `eth_getTransactionReceipt` waits for a full sealed block (up to 2 seconds). For latency-sensitive strategies, poll `base_transactionStatus` first and only fall back to the receipt for finality confirmation. - -## Fee calibration - -### L2 priority fee heuristics - -Use [`eth_feeHistory`](/base-chain/api-reference/rpc-overview#eth_feehistory) over the last 5–10 blocks with reward percentiles `[50, 90]`: - -- **50th percentile reward** — standard inclusion within 1–3 Flashblocks -- **90th percentile reward** — fast inclusion within the next Flashblock -- **`maxFeePerGas`** — set to `nextBaseFee * 2 + maxPriorityFeePerGas` to survive base fee increases across multiple blocks without overpaying - -### L1 fee decision rule - -Before signing, estimate the L1 cost via the [GasPriceOracle](/base-chain/network-information/network-fees#querying-the-l1-fee). If the ratio below exceeds `0.8` and trade size is small, the L1 fee dominates total cost — consider deferring until Ethereum congestion subsides: - -``` -l1FeeUpperBound / (gasLimit × maxFeePerGas + l1FeeUpperBound) > 0.8 -``` - -## Base-specific failure modes - -Most transaction errors are standard EVM. These are the Base-specific cases that require different handling: - -| Failure | What it means | What to do | -| ------- | ------------- | ---------- | -| `replacement transaction underpriced` | Replacing a pending tx requires ≥10% higher `maxFeePerGas` **and** `maxPriorityFeePerGas` | Increase both by ≥10%, resubmit with same nonce | -| DA throttling (no error code returned) | Sequencer is rate-limiting L2 throughput to manage L1 data availability costs | Fee-bumping does not resolve this — wait for the throttle to lift | - -For the full error taxonomy, see [Troubleshooting Transactions](/base-chain/network-information/troubleshooting-transactions). - -**Nonce for concurrent submissions:** Always fetch with `eth_getTransactionCount(address, "pending")`. For parallel submissions, fetch once and increment locally — fetching fresh per call returns the same value for all concurrent requests. - -## Strategy signals - -Base exposes data that most chains don't provide at this resolution: - -| Signal | How to access | Opportunity category | -| ------ | ------------- | -------------------- | -| Preconfirmed pending block state (200ms early) | `eth_getBlockByNumber("pending")` on preconf endpoint | Detect price impact before block seals | -| Preconfirmed transaction stream | `eth_subscribe("newFlashblockTransactions")` | First-mover on large trades entering the block | -| Preconfirmed logs | `eth_subscribe("pendingLogs", { address, topics })` | Detect liquidation thresholds, oracle updates before finality | -| Fee spike detection | `eth_feeHistory` trend over last 5 blocks | Identify congestion onset; adjust routing or pause | -| L1 base fee trend | `GasPriceOracle.l1BaseFee()` polled over time | Time submissions to minimize L1 data cost | -| Block gas utilization | `gasUsedRatio` from `eth_feeHistory` | Predict upcoming base fee increases | - -**The Flashblocks advantage:** Most chains expose a pool of pending transactions with uncertain ordering. On Base, the Flashblocks endpoint gives agents a deterministic, ordered view of the next 200ms of chain state before it finalizes. This is an information edge that does not exist on chains with mempool-only visibility. - -## Related - -- [Flashblocks API Reference](/base-chain/api-reference/rpc-overview#flashblocks-api) — `eth_simulateV1`, `base_transactionStatus`, subscription types -- [Flashblocks Integration Guide](/base-chain/flashblocks/app-integration) — endpoints, library setup (Wagmi, Viem, Ethers) -- [Block Building](/base-chain/network-information/block-building) — Flashblocks timing, per-transaction gas maximum -- [Network Fees](/base-chain/network-information/network-fees) — EIP-1559 parameters, GasPriceOracle methods -- [JSON-RPC API Reference](/base-chain/api-reference/rpc-overview) — complete RPC method reference -- [Troubleshooting Transactions](/base-chain/network-information/troubleshooting-transactions) — full error taxonomy and retry policies diff --git a/docs/docs.json b/docs/docs.json index 241800d27..2344d1227 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -680,60 +680,37 @@ }, { "group": "Quickstart", - "pages": [ - "ai-agents/quickstart/payments", - "ai-agents/quickstart/trading" - ] + "pages": ["ai-agents/quickstart"] }, { - "group": "Setup", + "group": "Guides [WIP]", "pages": [ - "ai-agents/setup/wallet-setup", - "ai-agents/setup/agent-builder-codes", - "ai-agents/setup/agent-registration" + "ai-agents/guides/check-balance", + "ai-agents/guides/send-tokens", + "ai-agents/guides/swap-tokens", + "ai-agents/guides/view-history", + "ai-agents/guides/sign-messages", + "ai-agents/guides/batch-calls" ] }, { - "group": "Payments", + "group": "Skill & Plugins [WIP]", "pages": [ - "ai-agents/payments/pay-for-services-with-x402", - "ai-agents/payments/accepting-payments" - ] - }, - { - "group": "Trading", - "pages": [ - "ai-agents/trading/data-fetching", - "ai-agents/trading/trade-execution" - ] - }, - { - "group": "Skills", - "pages": [ - "ai-agents/skills/index", - { - "group": "Wallets", - "pages": [ - "ai-agents/skills/wallets/bankr", - "ai-agents/skills/wallets/cdp-agentic-wallet", - "ai-agents/skills/wallets/sponge-wallet" - ] - }, + "ai-agents/plugins/index", { - "group": "Payments", + "group": "Native Plugins", "pages": [ - "ai-agents/skills/payments/cdp-payment-skills", - "ai-agents/skills/payments/sponge-x402" + "ai-agents/plugins/native/index", + "ai-agents/plugins/native/morpho", + "ai-agents/plugins/native/moonwell", + "ai-agents/plugins/native/uniswap", + "ai-agents/plugins/native/avantis", + "ai-agents/plugins/native/aerodrome", + "ai-agents/plugins/native/virtuals", + "ai-agents/plugins/native/bankr" ] }, - { - "group": "Trading", - "pages": [ - "ai-agents/skills/trading/coingecko", - "ai-agents/skills/trading/alchemy-agentic-gateway", - "ai-agents/skills/trading/swap-execution" - ] - } + "ai-agents/plugins/custom-plugins" ] } ] @@ -799,7 +776,70 @@ ] }, "redirects": [ - + { + "source": "/ai-agents/quickstart/payments", + "destination": "/ai-agents/quickstart" + }, + { + "source": "/ai-agents/quickstart/trading", + "destination": "/ai-agents/quickstart" + }, + { + "source": "/ai-agents/setup/wallet-setup", + "destination": "/ai-agents/setup" + }, + { + "source": "/ai-agents/setup/agent-registration", + "destination": "/ai-agents/index" + }, + { + "source": "/ai-agents/payments/pay-for-services-with-x402", + "destination": "/ai-agents/payments" + }, + { + "source": "/ai-agents/payments/accepting-payments", + "destination": "/ai-agents/payments" + }, + { + "source": "/ai-agents/tools", + "destination": "/ai-agents/guides" + }, + { + "source": "/ai-agents/tools/index", + "destination": "/ai-agents/guides" + }, + { + "source": "/ai-agents/trading/data-fetching", + "destination": "/ai-agents/guides" + }, + { + "source": "/ai-agents/trading/trade-execution", + "destination": "/ai-agents/guides" + }, + { + "source": "/ai-agents/skills/wallets/bankr", + "destination": "/ai-agents/tools" + }, + { + "source": "/ai-agents/skills/wallets/sponge-wallet", + "destination": "/ai-agents/tools" + }, + { + "source": "/ai-agents/skills/payments/sponge-x402", + "destination": "/ai-agents/payments" + }, + { + "source": "/ai-agents/skills/trading/coingecko", + "destination": "/ai-agents/tools" + }, + { + "source": "/ai-agents/skills/trading/alchemy-agentic-gateway", + "destination": "/ai-agents/tools" + }, + { + "source": "/ai-agents/skills/trading/swap-execution", + "destination": "/ai-agents/tools" + }, { "source": "/base-chain/reference/json-rpc-api", "destination": "/base-chain/api-reference/rpc-overview" diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 6725d0223..14cdf83fc 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -369,25 +369,21 @@ const client = createPublicClient({ chain: base, transport: http() }) - [SignInWithBaseButton](https://docs.base.org/base-account/reference/ui-elements/sign-in-with-base-button): Pre-built React component for user authentication with Base Account ## AI Agents -- [AI Agents on Base](https://docs.base.org/ai-agents/index): Build AI agents that trade, earn, and transact autonomously on Base -- [Overview](https://docs.base.org/ai-agents/skills/index): Installable agent capabilities for wallets, payments, and trading on Base -- [Accepting Payments (x402)](https://docs.base.org/ai-agents/payments/accepting-payments): Gate your agent's endpoints with x402 to charge other agents per request -- [Pay for APIs & Services (x402)](https://docs.base.org/ai-agents/payments/pay-for-services-with-x402): How x402 works, how your agent makes payments, and which networks and tokens are supported -- [Get Started with Payments](https://docs.base.org/ai-agents/quickstart/payments): Build an agent that makes and accepts x402 payments on Base in under 10 minutes -- [Get Started with Trading](https://docs.base.org/ai-agents/quickstart/trading): Build an agent that fetches live market data and executes token swaps on Base -- [Builder Codes for Agents](https://docs.base.org/ai-agents/setup/agent-builder-codes): Register your agent on Base.dev and append a Builder Code to every transaction to measure onchain activity. -- [Agent Registration & Identity](https://docs.base.org/ai-agents/setup/agent-registration): Register your agent's identity onchain, get a Basename, and authenticate with services using Sign In With Agent (SIWA) -- [Wallet Setup for Agents](https://docs.base.org/ai-agents/setup/wallet-setup): Give your AI agent a dedicated wallet on Base to hold funds, send payments, sign messages, and interact with onchain protocols securely. -- [CDP Payment Skills](https://docs.base.org/ai-agents/skills/payments/cdp-payment-skills): Skills for discovering, paying for, and monetizing x402 API services via the CDP Agentic Wallet -- [Sponge x402](https://docs.base.org/ai-agents/skills/payments/sponge-x402): Skill for discovering and paying for x402 services automatically using Sponge Wallet's built-in proxy -- [Alchemy Agentic Gateway](https://docs.base.org/ai-agents/skills/trading/alchemy-agentic-gateway): Skill for accessing Alchemy's blockchain APIs — token balances, NFTs, portfolio data, and prices — via x402 SIWE auth -- [CoinGecko](https://docs.base.org/ai-agents/skills/trading/coingecko): Skill for fetching live crypto price feeds, market cap, and OHLCV data via x402 — no API key required -- [Swap Execution](https://docs.base.org/ai-agents/skills/trading/swap-execution): Skills for executing token swaps on Base using wallet-native tools across Bankr, CDP Agentic Wallet, and Sponge -- [Bankr](https://docs.base.org/ai-agents/skills/wallets/bankr): Skill that gives your AI agent a cross-chain wallet with built-in swaps, gas sponsorship, and token launching -- [CDP Agentic Wallet](https://docs.base.org/ai-agents/skills/wallets/cdp-agentic-wallet): Skill that gives your AI agent an email-authenticated wallet on Base with x402 payments built in -- [Sponge Wallet](https://docs.base.org/ai-agents/skills/wallets/sponge-wallet): Skill that gives your AI agent a multi-chain wallet with native x402 payments, swaps, bridges, and banking -- [Fetching Market Data](https://docs.base.org/ai-agents/trading/data-fetching): Use x402 to access live market data from CoinGecko, Alchemy, and other sources — no API key management required -- [Trade Execution on Base](https://docs.base.org/ai-agents/trading/trade-execution): Base-specific patterns, fee calibration, and onchain signals for trading agents +- [Guides](https://docs.base.org/ai-agents/guides/index): Step-by-step guides for common things to do with Base MCP +- [Base MCP](https://docs.base.org/ai-agents/index): Give your AI assistant a wallet. Base MCP connects any AI to your Base Account — check balances, send funds, swap tokens, and sign messages. +- [Builder Codes for Agents](https://docs.base.org/ai-agents/guides/agent-builder-codes): Register your agent on Base.dev and append a Builder Code to every transaction to measure onchain activity. +- [Execute Contract Calls](https://docs.base.org/ai-agents/guides/batch-calls): Batch multiple contract interactions into a single user approval using send_calls and Base MCP +- [Check Balance & Portfolio](https://docs.base.org/ai-agents/guides/check-balance): View your token balances, portfolio value, and wallet details using Base MCP +- [Send Tokens](https://docs.base.org/ai-agents/guides/send-tokens): Send ETH or any ERC-20 token to an address, ENS name, basename, or cb.id using Base MCP +- [Sign Messages](https://docs.base.org/ai-agents/guides/sign-messages): Sign EIP-712 typed data and plain messages with your Base Account using Base MCP +- [Swap Tokens](https://docs.base.org/ai-agents/guides/swap-tokens): Swap between any two tokens on Base via the Coinbase swap service using Base MCP +- [View Transaction History](https://docs.base.org/ai-agents/guides/view-history): Browse past transactions, filter by asset, and paginate through your onchain history using Base MCP +- [Avantis](https://docs.base.org/ai-agents/plugins/avantis): Add perpetual futures trading to your AI assistant on Base using Avantis and Base Account — no additional MCP server required +- [Custom Plugin](https://docs.base.org/ai-agents/plugins/custom-plugin): Build your own plugin that produces unsigned calldata and executes through Base MCP's send_calls +- [Moonwell](https://docs.base.org/ai-agents/plugins/moonwell): Add Moonwell lending operations to your AI assistant using web_request and Base Account — no additional MCP server required +- [Morpho](https://docs.base.org/ai-agents/plugins/morpho): Add Morpho lending and vault operations to your AI assistant via the Morpho MCP, executed through your Base Account +- [Uniswap](https://docs.base.org/ai-agents/plugins/uniswap): Add token swaps and LP position management to your AI assistant on Base using the Uniswap API and Base Account — no additional MCP server required +- [Quickstart](https://docs.base.org/ai-agents/quickstart): Connect Base MCP to your agent in under 2 minutes ## Apps - [Build an app on Base](https://docs.base.org/apps/index): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. diff --git a/docs/llms.txt b/docs/llms.txt index 9e763930c..3ed544c86 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,6 +1,5 @@ # Base Documentation - > Build on Base — Coinbase's Ethereum L2. Smart Wallet, OnchainKit, MiniKit, Base Chain RPCs, and AI Agents. This index points AI assistants at the canonical page for each topic; follow the links for full context. ## Get Started @@ -282,25 +281,21 @@ - [SignInWithBaseButton](https://docs.base.org/base-account/reference/ui-elements/sign-in-with-base-button): Pre-built React component for user authentication with Base Account ## AI Agents -- [AI Agents on Base](https://docs.base.org/ai-agents/index): Build AI agents that trade, earn, and transact autonomously on Base -- [Overview](https://docs.base.org/ai-agents/skills/index): Installable agent capabilities for wallets, payments, and trading on Base -- [Accepting Payments (x402)](https://docs.base.org/ai-agents/payments/accepting-payments): Gate your agent's endpoints with x402 to charge other agents per request -- [Pay for APIs & Services (x402)](https://docs.base.org/ai-agents/payments/pay-for-services-with-x402): How x402 works, how your agent makes payments, and which networks and tokens are supported -- [Get Started with Payments](https://docs.base.org/ai-agents/quickstart/payments): Build an agent that makes and accepts x402 payments on Base in under 10 minutes -- [Get Started with Trading](https://docs.base.org/ai-agents/quickstart/trading): Build an agent that fetches live market data and executes token swaps on Base -- [Builder Codes for Agents](https://docs.base.org/ai-agents/setup/agent-builder-codes): Register your agent on Base.dev and append a Builder Code to every transaction to measure onchain activity. -- [Agent Registration & Identity](https://docs.base.org/ai-agents/setup/agent-registration): Register your agent's identity onchain, get a Basename, and authenticate with services using Sign In With Agent (SIWA) -- [Wallet Setup for Agents](https://docs.base.org/ai-agents/setup/wallet-setup): Give your AI agent a dedicated wallet on Base to hold funds, send payments, sign messages, and interact with onchain protocols securely. -- [CDP Payment Skills](https://docs.base.org/ai-agents/skills/payments/cdp-payment-skills): Skills for discovering, paying for, and monetizing x402 API services via the CDP Agentic Wallet -- [Sponge x402](https://docs.base.org/ai-agents/skills/payments/sponge-x402): Skill for discovering and paying for x402 services automatically using Sponge Wallet's built-in proxy -- [Alchemy Agentic Gateway](https://docs.base.org/ai-agents/skills/trading/alchemy-agentic-gateway): Skill for accessing Alchemy's blockchain APIs — token balances, NFTs, portfolio data, and prices — via x402 SIWE auth -- [CoinGecko](https://docs.base.org/ai-agents/skills/trading/coingecko): Skill for fetching live crypto price feeds, market cap, and OHLCV data via x402 — no API key required -- [Swap Execution](https://docs.base.org/ai-agents/skills/trading/swap-execution): Skills for executing token swaps on Base using wallet-native tools across Bankr, CDP Agentic Wallet, and Sponge -- [Bankr](https://docs.base.org/ai-agents/skills/wallets/bankr): Skill that gives your AI agent a cross-chain wallet with built-in swaps, gas sponsorship, and token launching -- [CDP Agentic Wallet](https://docs.base.org/ai-agents/skills/wallets/cdp-agentic-wallet): Skill that gives your AI agent an email-authenticated wallet on Base with x402 payments built in -- [Sponge Wallet](https://docs.base.org/ai-agents/skills/wallets/sponge-wallet): Skill that gives your AI agent a multi-chain wallet with native x402 payments, swaps, bridges, and banking -- [Fetching Market Data](https://docs.base.org/ai-agents/trading/data-fetching): Use x402 to access live market data from CoinGecko, Alchemy, and other sources — no API key management required -- [Trade Execution on Base](https://docs.base.org/ai-agents/trading/trade-execution): Base-specific patterns, fee calibration, and onchain signals for trading agents +- [Guides](https://docs.base.org/ai-agents/guides/index): Step-by-step guides for common things to do with Base MCP +- [Base MCP](https://docs.base.org/ai-agents/index): Give your AI assistant a wallet. Base MCP connects any AI to your Base Account — check balances, send funds, swap tokens, and sign messages. +- [Builder Codes for Agents](https://docs.base.org/ai-agents/guides/agent-builder-codes): Register your agent on Base.dev and append a Builder Code to every transaction to measure onchain activity. +- [Execute Contract Calls](https://docs.base.org/ai-agents/guides/batch-calls): Batch multiple contract interactions into a single user approval using send_calls and Base MCP +- [Check Balance & Portfolio](https://docs.base.org/ai-agents/guides/check-balance): View your token balances, portfolio value, and wallet details using Base MCP +- [Send Tokens](https://docs.base.org/ai-agents/guides/send-tokens): Send ETH or any ERC-20 token to an address, ENS name, basename, or cb.id using Base MCP +- [Sign Messages](https://docs.base.org/ai-agents/guides/sign-messages): Sign EIP-712 typed data and plain messages with your Base Account using Base MCP +- [Swap Tokens](https://docs.base.org/ai-agents/guides/swap-tokens): Swap between any two tokens on Base via the Coinbase swap service using Base MCP +- [View Transaction History](https://docs.base.org/ai-agents/guides/view-history): Browse past transactions, filter by asset, and paginate through your onchain history using Base MCP +- [Avantis](https://docs.base.org/ai-agents/plugins/avantis): Add perpetual futures trading to your AI assistant on Base using Avantis and Base Account — no additional MCP server required +- [Custom Plugin](https://docs.base.org/ai-agents/plugins/custom-plugin): Build your own plugin that produces unsigned calldata and executes through Base MCP's send_calls +- [Moonwell](https://docs.base.org/ai-agents/plugins/moonwell): Add Moonwell lending operations to your AI assistant using web_request and Base Account — no additional MCP server required +- [Morpho](https://docs.base.org/ai-agents/plugins/morpho): Add Morpho lending and vault operations to your AI assistant via the Morpho MCP, executed through your Base Account +- [Uniswap](https://docs.base.org/ai-agents/plugins/uniswap): Add token swaps and LP position management to your AI assistant on Base using the Uniswap API and Base Account — no additional MCP server required +- [Quickstart](https://docs.base.org/ai-agents/quickstart): Connect Base MCP to your agent in under 2 minutes ## Apps - [Build an app on Base](https://docs.base.org/apps/index): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. diff --git a/docs/snippets/AcceptingPaymentsDemo.jsx b/docs/snippets/AcceptingPaymentsDemo.jsx index 99ca4b529..ec5a22cb8 100644 --- a/docs/snippets/AcceptingPaymentsDemo.jsx +++ b/docs/snippets/AcceptingPaymentsDemo.jsx @@ -1,128 +1,755 @@ -import { useState, useEffect, useRef } from "react"; - export const AcceptingPaymentsDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const steps = [ - { delay: 350, left: [{ t: "> npm install x402-express express", c: "active" }], right: [ - { t: "── Middleware Config ───────────────────", c: "dim" }, - { t: "{", c: "code" }, - ]}, - { delay: 650, left: [{ t: " ✓ x402-express installed", c: "success" }], right: [ - { t: ' "path": "/api/data",', c: "code" }, - { t: ' "price": "$0.01",', c: "code" }, - { t: ' "network": "base",', c: "code" }, - { t: ' "payTo": "0x742d...c4f2"', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 500, left: [{ t: "> configure x402 middleware...", c: "active" }], right: [] }, - { delay: 600, left: [{ t: " ✓ paymentMiddleware applied", c: "success" }], right: [] }, - { delay: 400, left: [{ t: " ✓ endpoint live: /api/data · $0.01/req", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Live endpoint ───────────────────────", c: "dim" }, - { t: " GET /api/data → 402 (unpaid)", c: "muted" }, - { t: " GET /api/data + sig → 200 (paid)", c: "success" }, - ]}, - { delay: 900, left: [{ t: " waiting for requests...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Incoming request ────────────────────", c: "dim" }, - { t: "GET /api/data", c: "code" }, - { t: "X-Payment-Sig: 0xc3d1...f891", c: "success" }, - ]}, - { delay: 700, left: [{ t: " ← payment verified · 0.01 USDC", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Payment verified ────────────────────", c: "dim" }, - { t: " amount: 0.01 USDC ✓", c: "success" }, - { t: " sig valid: true ✓", c: "success" }, - ]}, - { delay: 400, left: [{ t: " ✓ serving data · balance +0.01 USDC", c: "success", bold: true }], right: [] }, - ]; - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); + + + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. + + const ACCENT = "#D97757"; + + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; }; - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; + }; - const renderLine = (item, i) => { - if (!item.t) return
; + const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); + + // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+
+ +
); }; - return ( -
-
-
- {">"}_ + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue} +
+ )} +
+
+ To + {preview.to} +
- Accepting Payments -
- -
+ ); + + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
+
+ {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
+
+ ); -
-
- Agent + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} + ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor} +
+ )} +
+ ); + + if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
+ )}
+ ); + + return null; + }; + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+ ); + + return ( +
+
e.stopPropagation()} + style={{ + background: mbg, + borderRadius: 16, + border: `1px solid #1f1d1b`, + width: 320, maxWidth: "100%", + maxHeight: "calc(100% - 8px)", + overflowY: "auto", + boxShadow: "0 24px 80px rgba(0,0,0,0.85)", + }} + > + {/* Header */} +
+ + {isSign ? "Sign" : "Review"} + + + + + +
+ + {/* Demo banner */} +
+ + + + + DEMO · Not a real {isSign ? "signature" : "transaction"} + +
-
- {done && ( - + +
+
+
+ ); + }; + + + const examples = [ + { + prompt: "Find the best USDC vault on Base and deposit 100 USDC", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "morpho", action: "list_vaults", args: { asset: "USDC", chain: "base", sortBy: "APY" } } }, + { delay: 500, type: "text", text: "Steakhouse USDC has the best yield. Preparing deposit via send_calls..." }, + { delay: 300, type: "rows", rows: [ + { token: "Morpho · Steakhouse USDC", amount: "8.42% APY", value: "$24.1M TVL · winner" }, + { token: "Morpho · Re7 USDC", amount: "7.91% APY", value: "$18.7M TVL" }, + { token: "Morpho · Flagship USDC", amount: "7.34% APY", value: "$42.1M TVL" }, + ]}, + { delay: 500, type: "tool", tool: { server: "morpho", action: "prepare_deposit", args: { vault: "Steakhouse USDC", amount: "100 USDC" } } }, + { delay: 350, type: "tool", tool: { server: "base-account", action: "send_calls", args: { chainId: "0x2105", calls: "[approve, deposit]" } } }, + { delay: 450, type: "approval", preview: { type: "deposit", asset: "USDC", amount: "100", usdValue: "~$100.00", vault: "Steakhouse USDC", apy: "8.42%" } }, + { delay: 1100, type: "confirm", text: "Deposited 100 USDC into Steakhouse USDC · earning 8.42% APY" }, + ], + }, + { + prompt: "Supply 0.5 ETH as collateral and borrow 1000 USDC", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "morpho", action: "get_market", args: { collateral: "ETH", loan: "USDC", chain: "base" } } }, + { delay: 500, type: "text", text: "Found Morpho ETH/USDC market on Base. Batching supply + borrow..." }, + { delay: 500, type: "tool", tool: { server: "morpho", action: "prepare_supply_collateral", args: { asset: "ETH", amount: "0.5", borrow: "1000 USDC" } } }, + { delay: 350, type: "tool", tool: { server: "base-account", action: "send_calls", args: { chainId: "0x2105", calls: "[supplyCollateral, borrow]" } } }, + { delay: 450, type: "approval", preview: { type: "borrow", collateralAsset: "ETH", collateralAmount: "0.5", loanAsset: "USDC", loanAmount: "1000", healthFactor: "2.1" } }, + { delay: 1100, type: "confirm", text: "Supplied 0.5 ETH · borrowed 1000 USDC · health factor 2.1" }, + ], + }, + { + prompt: "Repay all my Morpho USDC debt", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "morpho", action: "get_position", args: { market: "ETH/USDC", chain: "base" } } }, + { delay: 500, type: "text", text: "You owe 1002.14 USDC including accrued interest. Preparing full repayment..." }, + { delay: 500, type: "tool", tool: { server: "morpho", action: "prepare_repay", args: { amount: "1002.14 USDC", market: "ETH/USDC" } } }, + { delay: 350, type: "tool", tool: { server: "base-account", action: "send_calls", args: { chainId: "0x2105", calls: "[approve, repayAll]" } } }, + { delay: 450, type: "approval", preview: { type: "repay", asset: "USDC", amount: "1002.14", usdValue: "~$1,002.14", market: "Morpho ETH/USDC" } }, + { delay: 1100, type: "confirm", text: "Repaid 1002.14 USDC · Morpho position closed" }, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } + }; + + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); + return ( + + ); + }; + + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + + return ( +
+ {modalPreview && setModalPreview(null)} />} + + +
+ + Morpho + Base MCP + +
+ {activeIdx !== null && ( + )}
+ +
+ {!ex && ( +
+
+ Try asking once mcp.base.org and mcp.morpho.org are connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}} +
+ +
+
+ + Write a message... + Sonnet 4.6 + +
+
+ Demo · Morpho prepares calls, Base Account signs them +
+
); }; diff --git a/docs/snippets/AgentPaymentDemo.jsx b/docs/snippets/AgentPaymentDemo.jsx index 5224d02e1..37d3e385a 100644 --- a/docs/snippets/AgentPaymentDemo.jsx +++ b/docs/snippets/AgentPaymentDemo.jsx @@ -1,438 +1,751 @@ -import { useState, useEffect, useRef } from "react"; - export const AgentPaymentDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", - }; + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; - const flows = { - wallet_cdp: [ - { delay: 350, left: [{ t: "> npx skills add coinbase/agentic-wallet-skills", c: "active" }], right: [ - { t: "── wallet.config.json ──────────────────", c: "dim" }, - { t: "{", c: "code" }, - ]}, - { delay: 550, left: [{ t: " ✓ skill installed", c: "success" }], right: [ - { t: ' "provider": "coinbase",', c: "code" }, - { t: ' "network": "base",', c: "code" }, - { t: ' "skills": ["agentic-wallet"]', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 500, left: [{ t: "> Sign in with your@email.com", c: "active" }], right: [] }, - { delay: 650, left: [{ t: " ← OTP sent · checking...", c: "muted" }], right: [] }, - { delay: 600, left: [{ t: " ✓ CDP wallet connected 0x4a3f...b7c1", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " address: 0x4a3f...b7c1", c: "success" }, - { t: " network: base-mainnet", c: "success" }, - ]}, - ], - wallet_sponge: [ - { delay: 350, left: [{ t: "> curl -X POST https://api.wallet.paysponge.com/...", c: "active" }], right: [ - { t: "── POST /api/agents/register ───────────", c: "dim" }, - { t: "Host: api.wallet.paysponge.com", c: "muted" }, - { t: "Content-Type: application/json", c: "muted" }, - ]}, - { delay: 750, left: [{ t: ' ← {"apiKey": "sponge_live_..."}', c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── 200 OK ──────────────────────────────", c: "dim" }, - { t: "{", c: "code" }, - { t: ' "apiKey": "sponge_live_abc...xyz",', c: "code" }, - { t: ' "walletId": "wlt_a1b2c3d4"', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 450, left: [{ t: "> export SPONGE_API_KEY=sponge_live_...", c: "active" }], right: [] }, - { delay: 450, left: [{ t: " ✓ Sponge wallet ready", c: "success" }], right: [] }, - ], - wallet_bankr: [ - { delay: 350, left: [{ t: "> install bankr skill from github.com/BankrBot/skills", c: "active" }], right: [ - { t: "── github.com/BankrBot/skills ──────────", c: "dim" }, - { t: "GET /releases/latest", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ Bankr skill installed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " tag: bankr-wallet-v2.1.0", c: "success" }, - { t: " size: 142 KB", c: "muted" }, - ]}, - { delay: 500, left: [{ t: " ✓ Bankr wallet connected", c: "success" }], right: [ - { t: " ready: true", c: "success" }, - ]}, - ], - pay: [ - { delay: 350, left: [{ t: "> Find and fetch ETH price from a paid source", c: "active" }], right: [ - { t: "── Request ─────────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "Host: api.coingecko.com", c: "muted" }, - ]}, - { delay: 600, left: [{ t: " → GET api.coingecko.com/simple/price", c: "muted" }], right: [] }, - { delay: 650, left: [{ t: " ← 402 Payment Required · 0.001 USDC", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Response 1 ──────────────────────────", c: "dim" }, - { t: "HTTP/1.1 402 Payment Required", c: "muted" }, - { t: 'X-Payment-Required: {', c: "muted" }, - { t: ' "amount": "0.001", "asset": "USDC"', c: "code" }, - { t: '}', c: "muted" }, - ]}, - { delay: 650, left: [{ t: " paying via wallet...", c: "muted" }], right: [] }, - { delay: 600, left: [{ t: " ✓ tx confirmed", c: "success" }], right: [] }, - { delay: 500, left: [{ t: " → retrying with payment signature", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Request 2 ───────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "X-Payment-Sig: 0x1a9f...c4e2", c: "success" }, - ]}, - { delay: 600, left: [{ t: " ← 200 OK", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Response 2 ──────────────────────────", c: "dim" }, - { t: "HTTP/1.1 200 OK", c: "success" }, - { t: '{"ethereum":{"usd":2847.32}}', c: "code" }, - ]}, - { delay: 300, left: [{ t: " ETH $2,847.32 ↑ 2.3%", c: "code", bold: true }], right: [] }, - ], - getpaid: [ - { delay: 350, left: [{ t: "> Set up a paid endpoint at $0.01 per request", c: "active" }], right: [ - { t: "── x402 middleware config ──────────────", c: "dim" }, - { t: "{", c: "code" }, - { t: ' "path": "/market-data",', c: "code" }, - { t: ' "price": "0.01",', c: "code" }, - { t: ' "asset": "USDC"', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 600, left: [{ t: " creating x402 middleware...", c: "muted" }], right: [] }, - { delay: 500, left: [{ t: " ✓ endpoint live: api.myagent.com/market-data", c: "success" }], right: [] }, - { delay: 400, left: [{ t: " ✓ payTo: 0x742d...c4f2", c: "success" }], right: [] }, - { delay: 800, left: [{ t: " waiting for requests...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Incoming request ────────────────────", c: "dim" }, - { t: "GET /market-data", c: "code" }, - { t: "X-Payment-Sig: 0xc3d1...f891", c: "success" }, - ]}, - { delay: 700, left: [{ t: " ← incoming payment · 0.01 USDC", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Payment verified ────────────────────", c: "dim" }, - { t: " amount: 0.01 USDC ✓", c: "success" }, - { t: " sig valid: true ✓", c: "success" }, - ]}, - { delay: 450, left: [{ t: " ✓ payment verified · serving data", c: "success" }], right: [] }, - ], - swap_cdp: [ - { delay: 350, left: [{ t: " ✓ using CDP wallet 0x4a3f...b7c1", c: "success" }], right: [ - { t: "── Swap quote ───────────────────────────", c: "dim" }, - { t: "POST /v1/swap/quote", c: "code" }, - { t: "Host: api.developer.coinbase.com", c: "muted" }, - ]}, - { delay: 500, left: [{ t: "> Buy $50 of ETH on Base", c: "active" }], right: [] }, - { delay: 600, left: [{ t: " fetching quote...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Quote response ───────────────────────", c: "dim" }, - { t: '{"fromAmount":"50.00 USDC",', c: "code" }, - { t: ' "toAmount":"0.01756 ETH",', c: "code" }, - { t: ' "priceImpact":"0.12%"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: " ← 0.01756 ETH · price impact 0.12%", c: "muted" }], right: [] }, - { delay: 550, left: [{ t: " impact below 1% — executing...", c: "muted" }], right: [] }, - { delay: 700, left: [{ t: " ✓ tx 0xb4f2...91ca confirmed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Transaction ──────────────────────────", c: "dim" }, - { t: " hash: 0xb4f2...91ca", c: "success" }, - { t: " status: confirmed", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - ]}, - { delay: 400, left: [{ t: " ✓ received 0.01756 ETH", c: "success" }], right: [] }, - ], - swap_sponge: [ - { delay: 350, left: [{ t: " ✓ using Sponge wallet", c: "success" }], right: [ - { t: "── Swap quote ───────────────────────────", c: "dim" }, - { t: "POST /swap/quote", c: "code" }, - { t: "Host: api.wallet.paysponge.com", c: "muted" }, - ]}, - { delay: 500, left: [{ t: "> Buy $50 of ETH on Base", c: "active" }], right: [] }, - { delay: 600, left: [{ t: " fetching quote...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Quote response ───────────────────────", c: "dim" }, - { t: '{"fromAmount":"50.00 USDC",', c: "code" }, - { t: ' "toAmount":"0.01756 ETH",', c: "code" }, - { t: ' "priceImpact":"0.12%"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: " ← 0.01756 ETH · price impact 0.12%", c: "muted" }], right: [] }, - { delay: 550, left: [{ t: " impact below 1% — executing...", c: "muted" }], right: [] }, - { delay: 700, left: [{ t: " ✓ tx 0xb4f2...91ca confirmed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Transaction ──────────────────────────", c: "dim" }, - { t: " hash: 0xb4f2...91ca", c: "success" }, - { t: " status: confirmed", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - ]}, - { delay: 400, left: [{ t: " ✓ received 0.01756 ETH", c: "success" }], right: [] }, - ], - swap_bankr: [ - { delay: 350, left: [{ t: " ✓ using Bankr wallet", c: "success" }], right: [ - { t: "── Swap quote ───────────────────────────", c: "dim" }, - { t: "POST /swap/quote", c: "code" }, - { t: "Host: api.bankr.bot", c: "muted" }, - ]}, - { delay: 500, left: [{ t: "> Buy $50 of ETH on Base", c: "active" }], right: [] }, - { delay: 600, left: [{ t: " fetching quote...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Quote response ───────────────────────", c: "dim" }, - { t: '{"fromAmount":"50.00 USDC",', c: "code" }, - { t: ' "toAmount":"0.01756 ETH",', c: "code" }, - { t: ' "priceImpact":"0.12%"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: " ← 0.01756 ETH · price impact 0.12%", c: "muted" }], right: [] }, - { delay: 550, left: [{ t: " impact below 1% — executing...", c: "muted" }], right: [] }, - { delay: 700, left: [{ t: " ✓ tx 0xb4f2...91ca confirmed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Transaction ──────────────────────────", c: "dim" }, - { t: " hash: 0xb4f2...91ca", c: "success" }, - { t: " status: confirmed", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - ]}, - { delay: 400, left: [{ t: " ✓ received 0.01756 ETH", c: "success" }], right: [] }, - ], + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const tabs = ["Setup Wallet", "Pay & Get Paid", "Swap"]; - const rightLabels = ["Wallet Config", "HTTP Trace", "Quote Details"]; - const intros = [ - "> Setting up your agent wallet...", - "> Choose a flow:", - "> Which wallet should execute the swap?", - ]; - const [activeTab, setActiveTab] = useState(0); - const [choice1, setChoice1] = useState(null); - const [choice2, setChoice2] = useState(null); - const [leftLines, setLeftLines] = useState([]); - const [rightLines,setRightLines]= useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - useEffect(() => { if (rightRef.current) rightRef.current.scrollTop = rightRef.current.scrollHeight; }, [rightLines]); - - const reset = (tab) => { - const next = tab !== undefined ? tab : activeTab; - setActiveTab(next); - setChoice1(null); - setChoice2(null); - setLeftLines([]); - setRightLines([]); - setRunning(false); - setDone(false); - }; - useEffect(() => { - setLeftLines([]); - setRightLines([]); - setChoice1(null); - setChoice2(null); - setRunning(false); - setDone(false); - const t = setTimeout(() => setLeftLines([{ t: intros[activeTab], c: "muted" }]), 350); - return () => clearTimeout(t); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeTab]); - - const animateFlow = (key) => { - const steps = flows[key]; - if (!steps) return; - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); - }; - - const pickWallet = (w) => { - if (choice1) return; - setChoice1(w); - setLeftLines(prev => [...prev, { t: " [" + w.toUpperCase() + "]", c: "active" }]); - setTimeout(() => animateFlow("wallet_" + w), 150); - }; - const pickPay = (p) => { - if (choice2) return; - setChoice2(p); - const label = p === "pay" ? "Pay for a service" : "Get paid"; - setLeftLines(prev => [...prev, { t: " [" + label + "]", c: "active" }]); - setTimeout(() => animateFlow(p), 150); - }; + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. - const pickSwap = (w) => { - if (choice1) return; - setChoice1(w); - setLeftLines(prev => [...prev, { t: " [" + w.toUpperCase() + "]", c: "active" }]); - setTimeout(() => animateFlow("swap_" + w), 150); - }; + const ACCENT = "#D97757"; - const renderLine = (item, i) => { - if (!item.t) return
; - return ( -
- {item.t} -
- ); + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; }; - const btnBase = { - fontFamily: mono, fontSize: 12, - color: "#60a5fa", background: "transparent", - border: "1px solid #27272a", borderRadius: 4, - cursor: "pointer", padding: "1px 8px", - marginRight: 6, lineHeight: "20px", - }; - const onBtnEnter = (e) => { - e.currentTarget.style.color = "#e4e4e7"; - e.currentTarget.style.background = "#1c1c1e"; - e.currentTarget.style.borderColor = "#3f3f46"; - }; - const onBtnLeave = (e) => { - e.currentTarget.style.color = "#60a5fa"; - e.currentTarget.style.background = "transparent"; - e.currentTarget.style.borderColor = "#27272a"; + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; }; - const showWalletBtns = activeTab === 0 && !choice1 && leftLines.length > 0; - const showPayBtns = activeTab === 1 && !choice2 && leftLines.length > 0; - const showSwapBtns = activeTab === 2 && !choice1 && leftLines.length > 0; - const isLastTab = activeTab === 2; - const footerLabel = isLastTab ? "\u21ba Play again" : "Next: " + tabs[activeTab + 1] + " \u2192"; - - return ( -
- - {/* Tab bar */} -
+ const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); -
- {">"}_ -
+ const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); - {tabs.map((tab, i) => { - const active = i === activeTab; - return ( - - ); - })} + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); -
+ // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); + return ( +
+ ); + }; - {/* Terminal pane */} -
-
- Agent -
-
- {leftLines.map(renderLine)} - - {showWalletBtns && ( -
- pick: - - - + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue}
)} - {showPayBtns && ( -
- pick: - - +
+
+ To + {preview.to} +
+
+ ); + + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
- )} - {showSwapBtns && ( -
- pick: - - - + {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
+
+ ); + + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
+
+ ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor}
)} +
+ ); - {running && ( -
{"▋"}
+ if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
)}
+ ); + + return null; + }; + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+ ); - {/* Footer */} -
- {done && ( - + +
+
+
+ ); + }; + + + const examples = [ + { + prompt: "Show me my wallets", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_wallets", args: {} } }, + { delay: 600, type: "text", text: "You have 2 wallets connected to Base MCP:" }, + { delay: 250, type: "rows", rows: [ + { token: "Base Account", amount: "0x4a3f…b7c1", value: "in session · approval mode" }, + { token: "Agent Wallet", amount: "0x9c2d…e4f8", value: "not in session" }, + ]}, + { delay: 400, type: "confirm", text: "Connected · ready to send, swap, and sign" }, + ], + }, + { + prompt: "What's my USDC balance on Base?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_portfolio", args: { chain: "base" } } }, + { delay: 600, type: "text", text: "Your current portfolio on Base:" }, + { delay: 250, type: "rows", rows: [ + { token: "USDC", amount: "245.80", value: "$245.80" }, + { token: "ETH", amount: "0.0412", value: "$148.33" }, + { token: "WETH", amount: "0.0100", value: "$36.02" }, + ]}, + { delay: 400, type: "confirm", text: "Total: $430.15 on Base" }, + ], + }, + { + prompt: "Send 5 USDC to alice.base.eth", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "send", args: { to: "alice.base.eth", asset: "USDC", amount: "5", chain: "base" } } }, + { delay: 500, type: "text", text: "Resolved alice.base.eth → 0x71C7…976F. Approve to send:" }, + { delay: 250, type: "approval", preview: { type: "send", asset: "USDC", amount: "5", usdValue: "~$5.00", to: "alice.base.eth" } }, + { delay: 1100, type: "confirm", text: "Sent 5 USDC to alice.base.eth" }, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } + }; + + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); + return ( + + ); + }; + + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + + return ( +
+ {modalPreview && setModalPreview(null)} />} + + +
+ + Base MCP + +
+ {activeIdx !== null && ( + )}
+
+ {!ex && ( +
+
+ Try asking your assistant once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}} +
+ +
+
+ + Write a message... + Sonnet 4.6 + +
+
+ Demo · Your assistant approves every transaction at keys.coinbase.com +
+
); }; diff --git a/docs/snippets/AgentRegistrationDemo.jsx b/docs/snippets/AgentRegistrationDemo.jsx index 9b83e2053..591baadb6 100644 --- a/docs/snippets/AgentRegistrationDemo.jsx +++ b/docs/snippets/AgentRegistrationDemo.jsx @@ -1,122 +1,256 @@ -import { useState, useEffect, useRef } from "react"; export const AgentRegistrationDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const steps = [ - { delay: 400, left: [{ t: "> npm install @buildersgarden/siwa", c: "active" }], right: [ - { t: "── ERC-8004 Registry ───────────────────", c: "dim" }, - { t: "0x8004A169FB4...9f432", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ @buildersgarden/siwa installed", c: "success" }], right: [] }, - { delay: 500, left: [{ t: "> Register agent identity onchain...", c: "active" }], right: [] }, - { delay: 800, left: [{ t: " signing registration tx...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Registering ─────────────────────────", c: "dim" }, - { t: " name: my-trading-agent", c: "code" }, - { t: " endpoint: https://agent.example.com", c: "code" }, - { t: " pubKey: 0x04c2...f9a1", c: "code" }, - ]}, - { delay: 900, left: [{ t: " ✓ tx confirmed · token ID: 4219", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Registry entry ──────────────────────", c: "dim" }, - { t: " tokenId: 4219", c: "success" }, - { t: " owner: 0x4a3f...b7c1", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - ]}, - { delay: 600, left: [{ t: "> Claim Basename: myagent.base.eth", c: "active" }], right: [] }, - { delay: 700, left: [{ t: " ✓ myagent.base.eth registered", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Basename ────────────────────────────", c: "dim" }, - { t: " name: myagent.base.eth", c: "success" }, - { t: " address: 0x4a3f...b7c1", c: "success" }, - ]}, + const examples = [ + { + prompt: "Show my last 5 transactions on Base", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_transaction_history", args: { chain: "base", limit: "5" } } }, + { delay: 600, type: "text", text: "Here are your 5 most recent transactions:" }, + { delay: 250, type: "rows", rows: [ + { token: "Sent 10 USDC", amount: "−$10.00", value: "alice.base.eth · 2 min ago" }, + { token: "Swapped", amount: "−$180.41", value: "100 USDC → 0.035 ETH · 1 hr ago" }, + { token: "Received USDC", amount: "+$50.00", value: "from coinbase.com · 3 hr ago" }, + { token: "Sent 5 USDC", amount: "−$5.00", value: "bob.eth · 1 day ago" }, + { token: "Received USDC", amount: "+$100.00", value: "from 0x9f3a…2e01 · 2 days ago" }, + ]}, + ], + }, + { + prompt: "Show all my USDC sends from the last week", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_transaction_history", args: { asset: "USDC", type: "send", limit: "10" } } }, + { delay: 600, type: "text", text: "3 USDC sends in the last 7 days:" }, + { delay: 250, type: "rows", rows: [ + { token: "alice.base.eth", amount: "−10 USDC", value: "2 min ago" }, + { token: "bob.eth", amount: "−5 USDC", value: "1 day ago" }, + { token: "marketplace.base", amount: "−25 USDC", value: "3 days ago" }, + ]}, + { delay: 400, type: "confirm", text: "Total sent: 40 USDC over 3 transactions" }, + ], + }, + { + prompt: "What did I receive this month?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_transaction_history", args: { type: "receive", limit: "50" } } }, + { delay: 600, type: "text", text: "You received funds across 4 transactions this month:" }, + { delay: 250, type: "rows", rows: [ + { token: "coinbase.com", amount: "+150 USDC", value: "3 hr ago" }, + { token: "0x9f3a…2e01", amount: "+100 USDC", value: "2 days ago" }, + { token: "merchant.base", amount: "+27.40 USDC", value: "5 days ago" }, + { token: "friend.base", amount: "+25 USDC", value: "8 days ago" }, + ]}, + { delay: 400, type: "confirm", text: "Total received: 302.40 USDC this month" }, + ], + }, ]; - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + examples[idx].events.forEach((e, i) => { + cumulative += e.delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + }); }; - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); }; + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); - const renderLine = (item, i) => { - if (!item.t) return
; + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+ ); }; + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + return ( -
-
-
- {">"}_ -
- Agent Registration +
+ + +
+ + Base MCP +
- + {activeIdx !== null && ( + + )}
-
-
- Agent -
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} -
+
+ {!ex && ( +
+
+ Ask your assistant about your transaction history once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}}
-
- {done && ( - - )} + Write a message... + Sonnet 4.6 + +
+
+ Demo · Read-only — no approval required for transaction history +
); diff --git a/docs/snippets/AuthApprovalDemo.jsx b/docs/snippets/AuthApprovalDemo.jsx new file mode 100644 index 000000000..bfd6274b4 --- /dev/null +++ b/docs/snippets/AuthApprovalDemo.jsx @@ -0,0 +1,202 @@ + +// Auth approval demo — mock of the keys.coinbase.com Allow modal shown +// on first wallet-tool use. The client name is hardcoded because Mintlify +// does not expose the active selection to JSX snippets. (Cross-tab +// sync would require wrapping this in another visible Tabs block.) +export const AuthApprovalDemo = ({ client = "Claude" }) => { + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + + const c = { + bg: "#0a0a0a", + cardBorder: "#1c1c1c", + rowBorder: "#1f1f1f", + text: "#ffffff", + muted: "#9a9a9a", + dim: "#6b6b6b", + accent: "#a796f7", + accentRing: "#c8bcff", + btnDark: "#1c1c1c", + iconBlue: "#2a64ff", + iconBlue2: "#5b8eff", + permIconBg: "#1c1c1c", + permIconFg: "#d2d2d2", + }; + + const permissions = [ + { label: "View address, balances & activity." }, + { label: "Request approval for transactions." } + ]; + + const EyeIcon = () => ( + + + + + ); + const SendIcon = () => ( + + + + ); + const SignIcon = () => ( + + + + ); + const ChainIcon = () => ( + + + + + ); + const icons = [, , , ]; + + return ( +
+ + +
+ {/* Signed-in row */} +
+ + + Signed in as 0x71Dc…7244 + +
+ + {/* Body */} +
+ {/* Icons */} +
+
+ + + + + +
+
+ + + + +
+
+ +

+ Allow {client} to access your account. +

+ +

+ By continuing, you allow {client} to: +

+ +
+ {permissions.map((p, i) => ( +
+
+ {icons[i]} +
+ + {p.label} + +
+ ))} +
+
+ +
+ + + + + + + + +
+
+ +
+ Preview · Shown at keys.coinbase.com on first wallet-tool use +
+
+ ); +}; diff --git a/docs/snippets/DataFetchingDemo.jsx b/docs/snippets/DataFetchingDemo.jsx index 36355f730..b348d6955 100644 --- a/docs/snippets/DataFetchingDemo.jsx +++ b/docs/snippets/DataFetchingDemo.jsx @@ -1,128 +1,255 @@ -import { useState, useEffect, useRef } from "react"; export const DataFetchingDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const steps = [ - { delay: 350, left: [{ t: "> Discover price feed service...", c: "active" }], right: [ - { t: "── Service Discovery ───────────────────", c: "dim" }, - { t: "GET /api/discover?query=price+feed", c: "code" }, - { t: "Host: api.wallet.paysponge.com", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ← found: api.coingecko.com · 0.001 USDC", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: '{"id":"coingecko-price",', c: "code" }, - { t: ' "baseUrl":"api.coingecko.com",', c: "code" }, - { t: ' "price":"0.001 USDC/req"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: "> Fetch ETH price...", c: "active" }], right: [ - { t: "", c: "dim" }, - { t: "── API Request ─────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - ]}, - { delay: 600, left: [{ t: " ← 402 · paying 0.001 USDC...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "HTTP/1.1 402 Payment Required", c: "muted" }, - { t: "X-Payment-Sig: pending...", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ payment sent · retrying...", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── API Response ────────────────────────", c: "dim" }, - { t: "HTTP/1.1 200 OK", c: "success" }, - ]}, - { delay: 500, left: [{ t: " ← 200 OK · data returned", c: "success" }], right: [ - { t: '{"ethereum":{', c: "code" }, - { t: ' "usd": 2847.32,', c: "code" }, - { t: ' "usd_24h_change": 2.31', c: "code" }, - { t: "} }", c: "code" }, - ]}, - { delay: 350, left: [{ t: " ETH $2,847.32 ↑ 2.3%", c: "code", bold: true }], right: [] }, + const examples = [ + { + prompt: "What's my balance on Base?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_portfolio", args: { chain: "base" } } }, + { delay: 600, type: "text", text: "Your current portfolio on Base:" }, + { delay: 250, type: "rows", rows: [ + { token: "USDC", amount: "245.80", value: "$245.80" }, + { token: "ETH", amount: "0.0412", value: "$148.33" }, + { token: "WETH", amount: "0.0100", value: "$36.02" }, + ]}, + { delay: 400, type: "confirm", text: "Total: $430.15 on Base" }, + ], + }, + { + prompt: "What tokens do I have in my wallet?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_wallets", args: {} } }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "get_portfolio", args: { chain: "base" } } }, + { delay: 500, type: "text", text: "Tokens in your wallet on Base:" }, + { delay: 250, type: "rows", rows: [ + { token: "USDC", amount: "245.80", value: "$245.80" }, + { token: "ETH", amount: "0.0412", value: "$148.33" }, + { token: "WETH", amount: "0.0100", value: "$36.02" }, + ]}, + { delay: 400, type: "confirm", text: "3 tokens found on Base" }, + ], + }, + { + prompt: "What's my total balance across all chains?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "get_portfolio", args: { chain: "base" } } }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "get_portfolio", args: { chain: "ethereum" } } }, + { delay: 500, type: "text", text: "Your balances across networks:" }, + { delay: 250, type: "rows", rows: [ + { token: "Base", amount: "$430.15", value: "USDC · ETH · WETH" }, + { token: "Ethereum", amount: "$284.20", value: "ETH · USDC" }, + ]}, + { delay: 400, type: "confirm", text: "Total: $714.35 across all chains" }, + ], + }, ]; - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + examples[idx].events.forEach((e, i) => { + cumulative += e.delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + }); }; - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); }; + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); - const renderLine = (item, i) => { - if (!item.t) return
; + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+ ); }; + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + return ( -
-
-
- {">"}_ -
- Data Fetching +
+ + +
+ + Base MCP +
- + {activeIdx !== null && ( + + )}
-
-
- Agent -
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} -
+
+ {!ex && ( +
+
+ Check your wallet balance once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}}
-
- {done && ( - - )} + Write a message... + Sonnet 4.6 + +
+
+ Demo · Read-only — no approval required to check balances +
); diff --git a/docs/snippets/PaymentsQuickstartDemo.jsx b/docs/snippets/PaymentsQuickstartDemo.jsx deleted file mode 100644 index 0b9050d56..000000000 --- a/docs/snippets/PaymentsQuickstartDemo.jsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useState, useEffect, useRef } from "react"; - -export const PaymentsQuickstartDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", - }; - - const steps = [ - { delay: 350, left: [{ t: "> npx skills add coinbase/agentic-wallet-skills", c: "active" }], right: [ - { t: "── Installing skills ───────────────────", c: "dim" }, - { t: "authenticate-wallet", c: "muted" }, - { t: "pay-for-service", c: "muted" }, - { t: "monetize-service", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ skills installed", c: "success" }], right: [ - { t: "search-for-service", c: "muted" }, - { t: "send-usdc trade", c: "muted" }, - ]}, - { delay: 500, left: [{ t: "> Sign in with you@email.com", c: "active" }], right: [] }, - { delay: 650, left: [{ t: " ✓ wallet authenticated 0x4a3f...b7c1", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Wallet ready ────────────────────────", c: "dim" }, - { t: " address: 0x4a3f...b7c1", c: "success" }, - { t: " balance: 10.00 USDC", c: "success" }, - ]}, - { delay: 500, left: [{ t: "> Find and fetch ETH price from a paid source", c: "active" }], right: [ - { t: "", c: "dim" }, - { t: "── HTTP Trace ──────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "Host: api.coingecko.com", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ← 402 Payment Required · 0.001 USDC", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "HTTP/1.1 402 Payment Required", c: "muted" }, - { t: 'X-Payment-Required: {"amount":"0.001"}', c: "code" }, - ]}, - { delay: 550, left: [{ t: " paying 0.001 USDC via wallet...", c: "muted" }], right: [] }, - { delay: 600, left: [{ t: " ✓ tx confirmed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Retry with payment ──────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "X-Payment-Sig: 0x1a9f...c4e2", c: "success" }, - ]}, - { delay: 600, left: [{ t: " ← 200 OK · ETH $2,847.32 ↑ 2.3%", c: "success", bold: true }], right: [ - { t: "", c: "dim" }, - { t: "HTTP/1.1 200 OK", c: "success" }, - { t: '{"ethereum":{"usd":2847.32}}', c: "code" }, - ]}, - ]; - - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); - }; - - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps - - const renderLine = (item, i) => { - if (!item.t) return
; - return ( -
{item.t}
- ); - }; - - return ( -
-
-
- {">"}_ -
- Payments Quickstart -
- -
- -
-
- Agent -
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} -
-
- -
- {done && ( - - )} -
-
- ); -}; diff --git a/docs/snippets/SignMessagesDemo.jsx b/docs/snippets/SignMessagesDemo.jsx new file mode 100644 index 000000000..1ca38f8ac --- /dev/null +++ b/docs/snippets/SignMessagesDemo.jsx @@ -0,0 +1,726 @@ +export const SignMessagesDemo = () => { + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", + }; + + + + + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. + + const ACCENT = "#D97757"; + + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; + }; + + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; + }; + + const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); + + // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); + return ( +
+ +
+ ); + }; + + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue} +
+ )} +
+
+ To + {preview.to} +
+
+ ); + + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
+
+ {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
+
+ ); + + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
+
+ ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor} +
+ )} +
+ ); + + if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
+ )} +
+ ); + + return null; + }; + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+
+ ); + + return ( +
+
e.stopPropagation()} + style={{ + background: mbg, + borderRadius: 16, + border: `1px solid #1f1d1b`, + width: 320, maxWidth: "100%", + maxHeight: "calc(100% - 8px)", + overflowY: "auto", + boxShadow: "0 24px 80px rgba(0,0,0,0.85)", + }} + > + {/* Header */} +
+ + {isSign ? "Sign" : "Review"} + + + + + +
+ + {/* Demo banner */} +
+ + + + + DEMO · Not a real {isSign ? "signature" : "transaction"} + +
+ + {/* Preview */} +
+ {renderPreview()} +
+ + {/* Field rows */} +
+ + + 0x71Dc…7244 + + } + /> + {!isSign && ( + + + 0x71Dc…7244 + + + } + /> + )} + +
+ Base + + } + /> + {!isSign && ( + {"< $0.01"}} + /> + )} +
+ + {/* Buttons */} +
+ + +
+
+
+ ); + }; + + + const examples = [ + { + prompt: "Sign this message: I accept the terms of service", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "sign", args: { type: "personal_sign", message: "I accept the terms of service" } } }, + { delay: 500, type: "text", text: "Signing with your Base Account. Approve to generate signature:" }, + { delay: 250, type: "approval", preview: { type: "sign-message", message: "I accept the terms of service" } }, + { delay: 1100, type: "confirm", text: "Signed · sig 0x4f2a…c38e9b…8c91" }, + ], + }, + { + prompt: "Sign in to this app with my Base Account", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "sign", args: { type: "personal_sign", standard: "EIP-4361", domain: "app.example.com" } } }, + { delay: 500, type: "text", text: "Signing in to app.example.com using Sign-In with Ethereum (SIWE):" }, + { delay: 250, type: "approval", preview: { type: "sign-siwe", domain: "app.example.com" } }, + { delay: 1100, type: "confirm", text: "Signed in to app.example.com · session valid" }, + ], + }, + { + prompt: "Sign a Uniswap permit2 authorization", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "sign", args: { type: "EIP-712", standard: "permit2", token: "USDC", spender: "Uniswap" } } }, + { delay: 500, type: "text", text: "Signing typed permit2 data for Uniswap. Review and approve:" }, + { delay: 250, type: "approval", preview: { type: "sign-permit", token: "USDC", spender: "Uniswap", amount: "1000 USDC" } }, + { delay: 1100, type: "confirm", text: "Permit2 signed · Uniswap can spend up to 1000 USDC" }, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } + }; + + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); + return ( + + ); + }; + + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + + return ( +
+ {modalPreview && setModalPreview(null)} />} + + +
+ + Base MCP + +
+ {activeIdx !== null && ( + + )} +
+ +
+ {!ex && ( +
+
+ Sign messages and typed data once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}} +
+ +
+
+ + Write a message... + Sonnet 4.6 + +
+
+ Demo · Every signature requires your approval at keys.coinbase.com +
+
+
+ ); +}; diff --git a/docs/snippets/TradeExecutionDemo.jsx b/docs/snippets/TradeExecutionDemo.jsx index 959b3898e..7e9601b03 100644 --- a/docs/snippets/TradeExecutionDemo.jsx +++ b/docs/snippets/TradeExecutionDemo.jsx @@ -1,135 +1,727 @@ -import { useState, useEffect, useRef } from "react"; - export const TradeExecutionDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const steps = [ - { delay: 350, left: [{ t: "> Connect to mainnet-preconf.base.org...", c: "active" }], right: [ - { t: "── Flashblocks State ───────────────────", c: "dim" }, - { t: "eth_getBlockByNumber(\"pending\")", c: "code" }, - { t: "Host: mainnet-preconf.base.org", c: "muted" }, - ]}, - { delay: 650, left: [{ t: " ✓ preconf endpoint connected (200ms)", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: '{"pending": {', c: "code" }, - { t: ' "number": "0x1B2A4F2",', c: "code" }, - { t: ' "baseFeePerGas": "0x2386F26FC10"', c: "code" }, - { t: "} }", c: "code" }, - ]}, - { delay: 500, left: [{ t: "> Simulate buying $50 ETH (preconf state)...", c: "active" }], right: [ - { t: "", c: "dim" }, - { t: "── eth_simulateV1 ──────────────────────", c: "dim" }, - { t: "blockStateCalls: [pending]", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ simulation passed · no revert", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: '{"results":[{"status":"0x1",', c: "code" }, - { t: ' "gasUsed":"0x14c08"}]}', c: "code" }, - ]}, - { delay: 450, left: [{ t: "> Get swap quote · price impact check...", c: "active" }], right: [ - { t: "", c: "dim" }, - { t: "── Quote Details ───────────────────────", c: "dim" }, - { t: '{"fromAmount":"50.00 USDC",', c: "code" }, - { t: ' "toAmount":"0.01756 ETH",', c: "code" }, - { t: ' "priceImpact":"0.12%"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: " impact 0.12% < 1% — executing...", c: "muted" }], right: [] }, - { delay: 750, left: [{ t: " ✓ tx 0xb4f2...91ca confirmed (1 Flashblock)", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── base_transactionStatus ──────────────", c: "dim" }, - { t: " status: preconfirmed", c: "success" }, - { t: " hash: 0xb4f2...91ca", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - { t: " latency: ~200ms", c: "muted" }, - ]}, - { delay: 350, left: [{ t: " ✓ received 0.01756 ETH", c: "success", bold: true }], right: [] }, - ]; - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); + + + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. + + const ACCENT = "#D97757"; + + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; + }; + + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; }; - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); - const renderLine = (item, i) => { - if (!item.t) return
; + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); + + // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+
+ +
); }; - return ( -
-
-
- {">"}_ + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue} +
+ )} +
+
+ To + {preview.to} +
- Trade Execution -
- -
+ ); -
-
- Agent + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
+
+ {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} + ); + + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
+ ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor} +
+ )} +
+ ); + + if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
+ )} +
+ ); + + return null; + }; + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+ ); + + return ( +
+
e.stopPropagation()} + style={{ + background: mbg, + borderRadius: 16, + border: `1px solid #1f1d1b`, + width: 320, maxWidth: "100%", + maxHeight: "calc(100% - 8px)", + overflowY: "auto", + boxShadow: "0 24px 80px rgba(0,0,0,0.85)", + }} + > + {/* Header */} +
+ + {isSign ? "Sign" : "Review"} + + + + + +
+ + {/* Demo banner */} +
+ + + + + DEMO · Not a real {isSign ? "signature" : "transaction"} + +
+ + {/* Preview */} +
+ {renderPreview()} +
-
- {done && ( - + +
+
+
+ ); + }; + + + const examples = [ + { + prompt: "Send 10 USDC to alice.base.eth", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "send", args: { to: "alice.base.eth", asset: "USDC", amount: "10", chain: "base" } } }, + { delay: 500, type: "text", text: "Resolved alice.base.eth → 0x71C7…976F. Approve to send:" }, + { delay: 250, type: "approval", preview: { type: "send", asset: "USDC", amount: "10", usdValue: "~$10.00", to: "alice.base.eth" } }, + { delay: 1100, type: "confirm", text: "Sent 10 USDC to alice.base.eth" }, + ], + }, + { + prompt: "Send 50 DEGEN to bob.base.eth", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "search_tokens", args: { query: "DEGEN", chain: "base" } } }, + { delay: 500, type: "text", text: "Found DEGEN at 0x4ed4…9fa2. Sending to bob.base.eth..." }, + { delay: 400, type: "tool", tool: { server: "base-account", action: "send", args: { to: "bob.base.eth", asset: "0x4ed4…9fa2", amount: "50", chain: "base" } } }, + { delay: 250, type: "approval", preview: { type: "send", asset: "DEGEN", amount: "50", usdValue: "~$0.45", to: "bob.base.eth" } }, + { delay: 1100, type: "confirm", text: "Sent 50 DEGEN to bob.base.eth" }, + ], + }, + { + prompt: "Send 0.01 ETH to jesse.base.eth", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "send", args: { to: "jesse.base.eth", asset: "ETH", amount: "0.01", chain: "base" } } }, + { delay: 500, type: "text", text: "Resolved jesse.base.eth → 0xd8dA…6045. Approve to send:" }, + { delay: 250, type: "approval", preview: { type: "send", asset: "ETH", amount: "0.01", usdValue: "~$25.40", to: "jesse.base.eth" } }, + { delay: 1100, type: "confirm", text: "Sent 0.01 ETH to jesse.base.eth" }, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } + }; + + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); + return ( + + ); + }; + + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + + return ( +
+ {modalPreview && setModalPreview(null)} />} + + +
+ + Base MCP + +
+ {activeIdx !== null && ( + )}
+ +
+ {!ex && ( +
+
+ Send tokens to any address or name once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}} +
+ +
+
+ + Write a message... + Sonnet 4.6 + +
+
+ Demo · Every send requires your approval at keys.coinbase.com +
+
); }; diff --git a/docs/snippets/TradingQuickstartDemo.jsx b/docs/snippets/TradingQuickstartDemo.jsx index 2fe9f3744..06e5c2a0a 100644 --- a/docs/snippets/TradingQuickstartDemo.jsx +++ b/docs/snippets/TradingQuickstartDemo.jsx @@ -1,128 +1,751 @@ -import { useState, useEffect, useRef } from "react"; - export const TradingQuickstartDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", header: "#262624", border: "#34322f", inputBg: "#2a2926", + text: "#f5f4ed", body: "#e8e4dc", muted: "#a8a39d", dim: "#6b6663", + accent: "#D97757", bubble: "#2c2b28", bubbleText: "#f5f4ed", + code: "#e89972", codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", toolBorder: "#3a3835", success: "#a3c585", }; - const steps = [ - { delay: 350, left: [{ t: "> install bankr skill from github.com/BankrBot/skills", c: "active" }], right: [ - { t: "── Bankr Wallet ────────────────────────", c: "dim" }, - { t: "GET /releases/latest", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ Bankr skill installed", c: "success" }], right: [ - { t: " tag: bankr-wallet-v2.1.0", c: "success" }, - { t: " chains: base, eth, solana", c: "muted" }, - { t: " gas: sponsored", c: "muted" }, - ]}, - { delay: 500, left: [{ t: " ✓ Bankr wallet connected wlt_bankr_...", c: "success" }], right: [] }, - { delay: 500, left: [{ t: "> Buy $50 of ETH on Base", c: "active" }], right: [ - { t: "", c: "dim" }, - { t: "── Swap Quote ──────────────────────────", c: "dim" }, - { t: "POST /v1/swap/quote", c: "code" }, - { t: "Host: api.bankr.bot", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " fetching quote...", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: '{"fromAmount":"50.00 USDC",', c: "code" }, - { t: ' "toAmount":"0.01756 ETH",', c: "code" }, - { t: ' "priceImpact":"0.12%"}', c: "code" }, - ]}, - { delay: 500, left: [{ t: " ← 0.01756 ETH · price impact 0.12%", c: "muted" }], right: [] }, - { delay: 500, left: [{ t: " impact below 1% — executing...", c: "muted" }], right: [] }, - { delay: 750, left: [{ t: " ✓ tx 0xb4f2...91ca confirmed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Transaction ─────────────────────────", c: "dim" }, - { t: " hash: 0xb4f2...91ca", c: "success" }, - { t: " status: confirmed", c: "success" }, - { t: " block: 28,419,042", c: "muted" }, - ]}, - { delay: 400, left: [{ t: " ✓ received 0.01756 ETH", c: "success", bold: true }], right: [] }, - ]; - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); + + + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. + + const ACCENT = "#D97757"; + + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; }; - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; + }; - const renderLine = (item, i) => { - if (!item.t) return
; + const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); + + // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+
+ +
); }; - return ( -
-
-
- {">"}_ + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue} +
+ )} +
+
+ To + {preview.to} +
- Trading Quickstart -
- -
+ ); + + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
+
+ {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
+
+ ); -
-
- Agent + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} + ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor} +
+ )} +
+ ); + + if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
+ )}
+ ); + + return null; + }; + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+ ); + + return ( +
+
e.stopPropagation()} + style={{ + background: mbg, + borderRadius: 16, + border: `1px solid #1f1d1b`, + width: 320, maxWidth: "100%", + maxHeight: "calc(100% - 8px)", + overflowY: "auto", + boxShadow: "0 24px 80px rgba(0,0,0,0.85)", + }} + > + {/* Header */} +
+ + {isSign ? "Sign" : "Review"} + + + + + +
+ + {/* Demo banner */} +
+ + + + + DEMO · Not a real {isSign ? "signature" : "transaction"} + +
-
- {done && ( - + +
+
+
+ ); + }; + + + const examples = [ + { + prompt: "Swap 100 USDC for ETH on Base", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "swap", args: { fromAsset: "USDC", toAsset: "ETH", amount: "100", chain: "base" } } }, + { delay: 500, type: "text", text: "Quote ready: 0.03512 ETH · price impact 0.09%. Approve to swap:" }, + { delay: 250, type: "approval", preview: { type: "swap", fromAsset: "USDC", fromAmount: "100", fromUsd: "~$100.00", toAsset: "ETH", toAmount: "0.03512", toUsd: "~$100.00" } }, + { delay: 1100, type: "confirm", text: "Swapped 100 USDC → 0.03512 ETH" }, + ], + }, + { + prompt: "Swap 0.05 ETH to USDC — best rate", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "aerodrome", action: "get_quote", args: { from: "0.05 ETH", to: "USDC" } } }, + { delay: 700, type: "tool", tool: { server: "uniswap-v3", action: "get_quote", args: { from: "0.05 ETH", to: "USDC" } } }, + { delay: 600, type: "text", text: "Routed across two Base DEXes — Uniswap v3 has the better fill:" }, + { delay: 250, type: "rows", rows: [ + { token: "Aerodrome", amount: "178.92 USDC", value: "0.30% fee · slippage 0.18%" }, + { token: "Uniswap v3 (best)", amount: "179.41 USDC", value: "0.05% fee · slippage 0.12%" }, + ]}, + { delay: 700, type: "tool", tool: { server: "base-account", action: "swap", args: { route: "ETH→USDC", min_out: "179.20 USDC" } } }, + { delay: 450, type: "approval", preview: { type: "swap", fromAsset: "ETH", fromAmount: "0.05", fromUsd: "~$127.00", toAsset: "USDC", toAmount: "179.41", toUsd: "~$179.41" } }, + { delay: 1100, type: "confirm", text: "Swapped 0.05 ETH → 179.41 USDC on Uniswap v3" }, + ], + }, + { + prompt: "Convert 200 USDC to cbBTC", + events: [ + { delay: 380, type: "thinking" }, + { delay: 550, type: "tool", tool: { server: "base-account", action: "search_tokens", args: { query: "cbBTC", chain: "base" } } }, + { delay: 500, type: "tool", tool: { server: "base-account", action: "swap", args: { fromAsset: "USDC", toAsset: "cbBTC", amount: "200", chain: "base" } } }, + { delay: 500, type: "text", text: "Quote ready: 0.00210 cbBTC at current rates. Approve to swap:" }, + { delay: 250, type: "approval", preview: { type: "swap", fromAsset: "USDC", fromAmount: "200", fromUsd: "~$200.00", toAsset: "cbBTC", toAmount: "0.00210", toUsd: "~$199.50" } }, + { delay: 1100, type: "confirm", text: "Swapped 200 USDC → 0.00210 cbBTC" }, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } + }; + + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed + ? + : } + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + {k}: "{v}"{i < arr.length - 1 && , } + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => )} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); + return ( + + ); + }; + + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); + }; + + return ( +
+ {modalPreview && setModalPreview(null)} />} + + +
+ + Base MCP + +
+ {activeIdx !== null && ( + )}
+ +
+ {!ex && ( +
+
+ Swap any token on Base once mcp.base.org is connected: +
+
+ {examples.map((e, i) => pick(i)}>{e.prompt})} +
+
+ )} + {ex && <>{ex.prompt}{renderEvents()}} +
+ +
+
+ + Write a message... + Sonnet 4.6 + +
+
+ Demo · Every swap requires your approval at keys.coinbase.com +
+
); }; diff --git a/docs/snippets/WalletSetupDemo.jsx b/docs/snippets/WalletSetupDemo.jsx index 752e5098b..25d9d8b8b 100644 --- a/docs/snippets/WalletSetupDemo.jsx +++ b/docs/snippets/WalletSetupDemo.jsx @@ -1,196 +1,900 @@ -import { useState, useEffect, useRef } from "react"; - export const WalletSetupDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", + const sans = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif"; + const serif = "'Tiempos Headline','Iowan Old Style','Source Serif Pro',ui-serif,Georgia,serif"; + const mono = "ui-monospace,'SF Mono','Cascadia Code',Menlo,Monaco,Consolas,monospace"; + + const c = { + bg: "#1f1e1d", + header: "#262624", + border: "#34322f", + inputBg: "#2a2926", + text: "#f5f4ed", + body: "#e8e4dc", + muted: "#a8a39d", + dim: "#6b6663", + accent: "#D97757", + bubble: "#2c2b28", + bubbleText: "#f5f4ed", + code: "#e89972", + codeBg: "rgba(217,119,87,0.12)", + toolBg: "#272622", + toolBorder: "#3a3835", + success: "#a3c585", }; - const flows = { - cdp: [ - { delay: 400, left: [{ t: "> npx skills add coinbase/agentic-wallet-skills", c: "active" }], right: [ - { t: "── wallet.config.json ──────────────────", c: "dim" }, - { t: "{", c: "code" }, - ]}, - { delay: 600, left: [{ t: " ✓ skill installed", c: "success" }], right: [ - { t: ' "provider": "coinbase",', c: "code" }, - { t: ' "network": "base",', c: "code" }, - { t: ' "skills": ["agentic-wallet"]', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 500, left: [{ t: "> Sign in to my wallet with you@email.com", c: "active" }], right: [] }, - { delay: 700, left: [{ t: " ← OTP sent · checking...", c: "muted" }], right: [] }, - { delay: 650, left: [{ t: " ✓ CDP wallet connected 0x4a3f...b7c1", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " address: 0x4a3f...b7c1", c: "success" }, - { t: " network: base-mainnet", c: "success" }, - { t: " status: ready", c: "success" }, - ]}, - ], - sponge: [ - { delay: 400, left: [{ t: "> curl -X POST https://api.wallet.paysponge.com/...", c: "active" }], right: [ - { t: "── POST /api/agents/register ───────────", c: "dim" }, - { t: "Host: api.wallet.paysponge.com", c: "muted" }, - { t: "Content-Type: application/json", c: "muted" }, - ]}, - { delay: 750, left: [{ t: ' ← {"apiKey": "sponge_live_..."}', c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── 200 OK ──────────────────────────────", c: "dim" }, - { t: "{", c: "code" }, - { t: ' "apiKey": "sponge_live_abc...xyz",', c: "code" }, - { t: ' "walletId": "wlt_a1b2c3d4"', c: "code" }, - { t: "}", c: "code" }, - ]}, - { delay: 450, left: [{ t: "> export SPONGE_API_KEY=sponge_live_...", c: "active" }], right: [] }, - { delay: 400, left: [{ t: " ✓ Sponge wallet ready", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " chains: base, ethereum, solana", c: "success" }, - { t: " x402: enabled", c: "success" }, - ]}, - ], - bankr: [ - { delay: 400, left: [{ t: "> install bankr skill from github.com/BankrBot/skills", c: "active" }], right: [ - { t: "── github.com/BankrBot/skills ──────────", c: "dim" }, - { t: "GET /releases/latest", c: "muted" }, - ]}, - { delay: 700, left: [{ t: " ✓ Bankr skill installed", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " tag: bankr-wallet-v2.1.0", c: "success" }, - { t: " size: 142 KB", c: "muted" }, - ]}, - { delay: 500, left: [{ t: " ✓ Bankr wallet connected wlt_bankr_...", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: " chains: base, eth, solana, polygon", c: "success" }, - { t: " gas: sponsored", c: "success" }, - { t: " swaps: built-in", c: "success" }, - ]}, - ], + + + + // Shared Coinbase Wallet "Review" modal + Approve Transaction button used + // across the ai-agents demos. Supports asset-transfer previews (send, swap, + // deposit, borrow, repay) and signing previews (sign-message, sign-siwe, + // sign-permit). Positioned absolute inside the parent demo container so it + // doesn't fight with the Mintlify navbar's z-index. + + const ACCENT = "#D97757"; + + const tokenBg = (ticker) => { + if (!ticker) return ACCENT; + const t = ticker.toUpperCase(); + if (t === "USDC") return "#2775CA"; + if (t === "ETH" || t === "WETH") return "#627EEA"; + if (t === "CBBTC" || t === "BTC") return "#F7931A"; + if (t === "DEGEN") return "#A06CFF"; + if (t === "POL") return "#8247E5"; + return ACCENT; }; - const [choice, setChoice] = useState(null); - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const reset = () => { - setChoice(null); - setLeftLines([{ t: "> Choose a wallet provider:", c: "muted" }]); - setRightLines([]); - setRunning(false); - setDone(false); + const tokenGlow = (ticker) => { + if (!ticker) return "rgba(217,119,87,0.14)"; + const t = ticker.toUpperCase(); + if (t === "USDC") return "rgba(39,117,202,0.14)"; + if (t === "ETH" || t === "WETH") return "rgba(98,126,234,0.14)"; + if (t === "CBBTC" || t === "BTC") return "rgba(247,147,26,0.14)"; + if (t === "DEGEN") return "rgba(160,108,255,0.14)"; + return "rgba(217,119,87,0.14)"; }; - useEffect(() => { reset(); }, []); // eslint-disable-line react-hooks/exhaustive-deps - - const animateFlow = (key) => { - const steps = flows[key]; - if (!steps) return; - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); + const BigTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + const SmallTokenAvatar = ({ ticker }) => ( +
+ + {(ticker || "??").slice(0, 2).toUpperCase()} + +
+ ); + + // Wallet avatar — wow-face emoji style in a blue gradient circle + const CBAvatar = () => ( +
+ + + +
+ ); + + // Sign-icon avatar for signing flows — pen-on-paper in a purple gradient circle + const SignAvatar = () => ( +
+ + + +
+ ); + + const ApprovalButton = ({ preview, onApprove, label }) => { + const [hover, setHover] = useState(false); + return ( +
+ +
+ ); + }; + + const TxModal = ({ preview, onConfirm, onCancel }) => { + const mbg = "#0a0a0a"; + const mcard = "#1a1816"; + const mhair = "#1f1d1b"; + const mwhite = "#ffffff"; + const mvalue = "#a09b95"; + const msub = "#7a7470"; + + const isSign = preview.type && preview.type.startsWith("sign"); + + const renderPreview = () => { + if (preview.type === "send") return ( +
+
+ +
+
+ {preview.amount} {preview.asset} +
+ {preview.usdValue && ( +
+ {preview.usdValue} +
+ )} +
+
+ To + {preview.to} +
+
+ ); + + if (preview.type === "swap") return ( +
+
+ +
+
You send
+
+ {preview.fromAmount} {preview.fromAsset} +
+
+ {preview.fromUsd && ( +
{preview.fromUsd}
+ )} +
+
+
+ + + +
+
+
+ +
+
You receive
+
+ {preview.toAmount} {preview.toAsset} +
+
+ {preview.toUsd && ( +
{preview.toUsd}
+ )} +
+
+ ); + + if (preview.type === "deposit") return ( +
+
+ +
+
You deposit
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ Into +
+
{preview.vault}
+ {preview.apy && ( +
{preview.apy} APY
+ )} +
+
+
+ ); + + if (preview.type === "borrow") return ( +
+
+ +
+
Supply collateral
+
+ {preview.collateralAmount} {preview.collateralAsset} +
+
+
+
+ +
+
You borrow
+
+ {preview.loanAmount} {preview.loanAsset} +
+
+
+ {preview.healthFactor && ( +
+ Health factor + {preview.healthFactor} +
+ )} +
+ ); + + if (preview.type === "repay") return ( +
+
+ +
+
You repay
+
+ {preview.amount} {preview.asset} +
+
+ {preview.usdValue && ( +
{preview.usdValue}
+ )} +
+
+ To market + {preview.market} +
+
+ ); + + if (preview.type === "sign-message") return ( +
+
+ +
+
+ Sign message +
+
+ personal_sign +
+
+ "{preview.message}" +
+
+ ); + + if (preview.type === "sign-siwe") return ( +
+
+ +
+
+ Sign in with Ethereum +
+
+ EIP-4361 · session login +
+
+
+ Domain + {preview.domain} +
+
+ ); + + if (preview.type === "sign-permit") return ( +
+
+ +
+
+ Approve token spending +
+
+ EIP-712 · Permit2 +
+
+
+ Token +
+ + {preview.token} +
+
+
+ Spender + {preview.spender} +
+ {preview.amount && ( +
+ Allowance + {preview.amount} +
+ )} +
+ ); + + return null; }; - next(); + + const FieldRow = ({ label, right }) => ( +
+ {label} +
{right}
+
+ ); + + return ( +
+
e.stopPropagation()} + style={{ + background: mbg, + borderRadius: 16, + border: `1px solid #1f1d1b`, + width: 320, maxWidth: "100%", + maxHeight: "calc(100% - 8px)", + overflowY: "auto", + boxShadow: "0 24px 80px rgba(0,0,0,0.85)", + }} + > + {/* Header */} +
+ + {isSign ? "Sign" : "Review"} + + + + + +
+ + {/* Demo banner */} +
+ + + + + DEMO · Not a real {isSign ? "signature" : "transaction"} + +
+ + {/* Preview */} +
+ {renderPreview()} +
+ + {/* Field rows */} +
+ + + 0x71Dc…7244 + + } + /> + {!isSign && ( + + + 0x71Dc…7244 + + + } + /> + )} + +
+ Base + + } + /> + {!isSign && ( + {"< $0.01"}} + /> + )} +
+ + {/* Buttons */} +
+ + +
+
+
+ ); }; - const pick = (w) => { - if (choice) return; - setChoice(w); - const label = w === "cdp" ? "CDP" : w === "sponge" ? "Sponge" : "Bankr"; - setLeftLines(prev => [...prev, { t: " [" + label + "]", c: "active" }]); - setTimeout(() => animateFlow(w), 150); + + const examples = [ + { + prompt: "Send 5 USDC to jesse.base.eth", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "base-account", action: "send", args: { to: "jesse.base.eth", asset: "USDC", amount: "5" } } }, + { delay: 550, type: "text", text: "Resolved jesse.base.eth → 0xd8dA…6045. Approve to send:" }, + { delay: 250, type: "approval", preview: { type: "send", asset: "USDC", amount: "5", usdValue: "$5.00", to: "jesse.base.eth" } }, + { delay: 1100, type: "confirm", text: "Sent 5 USDC to jesse.base.eth" }, + ], + }, + { + prompt: "Swap 0.05 ETH to USDC on Base — best rate", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "aerodrome", action: "get_quote", args: { from: "0.05 ETH", to: "USDC" } } }, + { delay: 700, type: "tool", tool: { server: "uniswap-v3", action: "get_quote", args: { from: "0.05 ETH", to: "USDC" } } }, + { delay: 600, type: "text", text: "Routed across two Base DEXes — Uniswap v3 has the better fill:" }, + { delay: 250, type: "rows", rows: [ + { token: "Aerodrome", amount: "178.92 USDC", value: "0.30% fee · slippage 0.18%" }, + { token: "Uniswap v3 (best)", amount: "179.41 USDC", value: "0.05% fee · slippage 0.12%" }, + ]}, + { delay: 700, type: "tool", tool: { server: "uniswap-v3", action: "prepare_swap", args: { route: "ETH→USDC", min_out: "179.20 USDC" } } }, + { delay: 450, type: "approval", preview: { type: "swap", fromAsset: "ETH", fromAmount: "0.05", fromUsd: "~$127.00", toAsset: "USDC", toAmount: "179.41", toUsd: "~$179.41" } }, + { delay: 1100, type: "confirm", text: "Swapped 0.05 ETH → 179.41 USDC on Uniswap v3" }, + ], + }, + { + prompt: "Find the best USDC yield on Base and deposit 100", + events: [ + { delay: 380, type: "thinking" }, + { delay: 600, type: "tool", tool: { server: "morpho", action: "list_vaults", args: { asset: "USDC", chain: "base" } } }, + { delay: 700, type: "tool", tool: { server: "moonwell", action: "list_markets", args: { asset: "USDC", chain: "base" } } }, + { delay: 600, type: "text", text: "Compared Morpho vaults and Moonwell markets — top USDC yields on Base:" }, + { delay: 250, type: "rows", rows: [ + { token: "Morpho · Steakhouse USDC", amount: "8.42% APY", value: "$24.1M TVL · winner" }, + { token: "Morpho · Re7 USDC", amount: "7.91% APY", value: "$18.7M TVL" }, + { token: "Moonwell · USDC market", amount: "5.13% APY", value: "$41.2M supplied" }, + ]}, + { delay: 700, type: "tool", tool: { server: "morpho", action: "prepare_deposit", args: { vault: "Steakhouse USDC", amount: "100 USDC" } } }, + { delay: 450, type: "approval", preview: { type: "deposit", asset: "USDC", amount: "100", usdValue: "~$100.00", vault: "Steakhouse USDC", apy: "8.42%" } }, + { delay: 1100, type: "confirm", text: "Deposited 100 USDC into Steakhouse USDC · earning 8.42% APY" }, + ], + }, + { + prompt: "What chains are supported by Base MCP?", + events: [ + { delay: 380, type: "thinking" }, + { delay: 500, type: "text", text: "Base MCP supports 8 mainnets and 2 testnets:" }, + { delay: 200, type: "rows", rows: [ + { token: "Base", amount: "Mainnet", value: "" }, + { token: "Arbitrum", amount: "Mainnet", value: "" }, + { token: "Optimism", amount: "Mainnet", value: "" }, + { token: "Zora", amount: "Mainnet", value: "" }, + { token: "Polygon", amount: "Mainnet", value: "" }, + { token: "BNB Chain", amount: "Mainnet", value: "" }, + { token: "Avalanche", amount: "Mainnet", value: "" }, + { token: "Ethereum", amount: "Mainnet", value: "" }, + { token: "Base Sepolia", amount: "Testnet", value: "" }, + { token: "Sepolia", amount: "Testnet", value: "" }, + ]}, + ], + }, + ]; + + const [activeIdx, setActiveIdx] = useState(null); + const [eventIdx, setEventIdx] = useState(0); + const [modalPreview, setModalPreview] = useState(null); + const scrollRef = useRef(null); + const timersRef = useRef([]); + + const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; }; + + useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [eventIdx, activeIdx]); + useEffect(() => () => clearTimers(), []); + + const pick = (idx) => { + if (activeIdx !== null) return; + setActiveIdx(idx); + setEventIdx(0); + clearTimers(); + let cumulative = 0; + const events = examples[idx].events; + // Schedule events up to and including the approval step. After that, the + // demo pauses and waits for the user to actually click Confirm in the modal. + for (let i = 0; i < events.length; i++) { + cumulative += events[i].delay; + timersRef.current.push(setTimeout(() => setEventIdx(i + 1), cumulative)); + if (events[i].type === "approval") break; + } }; - const renderLine = (item, i) => { - if (!item.t) return
; + const reset = () => { clearTimers(); setActiveIdx(null); setEventIdx(0); setModalPreview(null); }; + + const handleConfirm = () => { + setModalPreview(null); + clearTimers(); + if (activeIdx !== null) setEventIdx(examples[activeIdx].events.length); + }; + + const ex = activeIdx !== null ? examples[activeIdx] : null; + + // ----- UI bits ----- + + const TrafficLights = () => ( +
+ + + +
+ ); + + const UserBubble = ({ children }) => ( +
+
{children}
+
+ ); + + const ToolCall = ({ tool, completed }) => ( +
+
+ + {completed ? ( + + ) : ( + + + + + )} + + + {tool.server} + · + {tool.action} + ( + {Object.entries(tool.args).map(([k, v], i, arr) => ( + + {k}: + "{v}" + {i < arr.length - 1 && , } + + ))} + ) + +
+
+ ); + + const Thinking = () => ( +
+ + {[0, 1, 2].map(i => ( + + ))} + + Thinking +
+ ); + + const ResponseText = ({ children, top }) => ( +
{children}
+ ); + + const ResponseRows = ({ rows }) => ( +
+ {rows.map((r, i) => ( +
+ + {r.token} + {r.amount} + {r.value} +
+ ))} +
+ ); + + const Confirm = ({ text }) => ( +
+ + + + {text} +
+ ); + + const ChipBtn = ({ onClick, children }) => { + const [hover, setHover] = useState(false); return ( -
{item.t}
+ ); }; - const btnBase = { - fontFamily: mono, fontSize: 12, - color: "#60a5fa", background: "transparent", - border: "1px solid #27272a", borderRadius: 4, - cursor: "pointer", padding: "1px 8px", - marginRight: 6, lineHeight: "20px", + + // Render the events shown so far for the active example + const renderEvents = () => { + if (!ex) return null; + const shown = ex.events.slice(0, eventIdx); + return shown.map((event, i) => { + if (event.type === "thinking") { + if (i < shown.length - 1) return null; + return ; + } + if (event.type === "tool") { + const hasLater = shown.slice(i + 1).some(e => e.type !== "thinking"); + return ; + } + if (event.type === "text") return {event.text}; + if (event.type === "rows") return ; + if (event.type === "approval") return ; + if (event.type === "confirm") return ; + return null; + }); }; - const onEnter = (e) => { e.currentTarget.style.color = "#e4e4e7"; e.currentTarget.style.background = "#1c1c1e"; e.currentTarget.style.borderColor = "#3f3f46"; }; - const onLeave = (e) => { e.currentTarget.style.color = "#60a5fa"; e.currentTarget.style.background = "transparent"; e.currentTarget.style.borderColor = "#27272a"; }; return ( -
- {/* Chrome bar */} -
-
- {">"}_ -
- Wallet Setup +
+ {/* keyframes + responsive */} + + + {/* Transaction approval modal */} + {modalPreview && ( + setModalPreview(null)} + /> + )} + + {/* Header */} +
+ + + Base MCP + +
- + {activeIdx !== null && ( + + )}
- {/* Terminal pane */} -
-
- Agent -
-
- {leftLines.map(renderLine)} - {!choice && leftLines.length > 0 && ( -
- pick: - - - + {/* Chat area */} +
+ {!ex && ( +
+
+ Try asking your assistant once mcp.base.org is connected:
- )} - {running &&
{"▋"}
} -
+
+ {examples.map((e, i) => ( + pick(i)}>{e.prompt} + ))} +
+
+ )} + + {ex && ( + <> + {ex.prompt} + {renderEvents()} + + )}
- {/* Footer */} -
- {done && ( - - )} + + Write a message... + + + Sonnet 4.6 + + + + + + +
+
+ Demo · Your assistant approves every transaction at keys.coinbase.com +
); diff --git a/docs/snippets/x402PayDemo.jsx b/docs/snippets/x402PayDemo.jsx deleted file mode 100644 index 7e5da9440..000000000 --- a/docs/snippets/x402PayDemo.jsx +++ /dev/null @@ -1,126 +0,0 @@ -import { useState, useEffect, useRef } from "react"; - -export const X402PayDemo = () => { - const mono = "ui-monospace,'Cascadia Code','Source Code Pro',Menlo,Monaco,Consolas,monospace"; - const col = { - dim: "#3f3f46", - muted: "#52525b", - active: "#60a5fa", - success: "#34d399", - code: "#d4d4d8", - }; - - const steps = [ - { delay: 350, left: [{ t: "> GET api.coingecko.com/api/v3/simple/price", c: "active" }], right: [ - { t: "── Request 1 ───────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "Host: api.coingecko.com", c: "muted" }, - ]}, - { delay: 650, left: [{ t: " ← 402 Payment Required", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Response 1 ──────────────────────────", c: "dim" }, - { t: "HTTP/1.1 402 Payment Required", c: "muted" }, - { t: "X-Payment-Required: {", c: "muted" }, - { t: ' "amount": "0.001",', c: "code" }, - { t: ' "asset": "USDC",', c: "code" }, - { t: ' "network": "base"', c: "code" }, - { t: "}", c: "muted" }, - ]}, - { delay: 500, left: [{ t: " paying 0.001 USDC via wallet...", c: "muted" }], right: [] }, - { delay: 700, left: [{ t: " ✓ tx confirmed 0x1a9f...c4e2", c: "success" }], right: [] }, - { delay: 450, left: [{ t: " → retrying with payment signature", c: "muted" }], right: [ - { t: "", c: "dim" }, - { t: "── Request 2 ───────────────────────────", c: "dim" }, - { t: "GET /api/v3/simple/price?ids=ethereum", c: "code" }, - { t: "X-Payment-Sig: 0x1a9f...c4e2", c: "success" }, - ]}, - { delay: 600, left: [{ t: " ← 200 OK", c: "success" }], right: [ - { t: "", c: "dim" }, - { t: "── Response 2 ──────────────────────────", c: "dim" }, - { t: "HTTP/1.1 200 OK", c: "success" }, - { t: '{"ethereum":{"usd":2847.32}}', c: "code" }, - ]}, - { delay: 300, left: [{ t: " ETH $2,847.32 ↑ 2.3%", c: "code", bold: true }], right: [] }, - ]; - - const [leftLines, setLeftLines] = useState([]); - const [rightLines, setRightLines] = useState([]); - const [running, setRunning] = useState(false); - const [done, setDone] = useState(false); - const [blink, setBlink] = useState(true); - const leftRef = useRef(null); - const rightRef = useRef(null); - - useEffect(() => { - const t = setInterval(() => setBlink(b => !b), 530); - return () => clearInterval(t); - }, []); - - useEffect(() => { if (leftRef.current) leftRef.current.scrollTop = leftRef.current.scrollHeight; }, [leftLines]); - - const play = () => { - setLeftLines([]); - setRightLines([]); - setRunning(true); - setDone(false); - let i = 0; - const next = () => { - if (i >= steps.length) { setRunning(false); setDone(true); return; } - const s = steps[i]; - setTimeout(() => { - if (s.left && s.left.length) setLeftLines(prev => [...prev, ...s.left]); - if (s.right && s.right.length) setRightLines(prev => [...prev, ...s.right]); - i++; - next(); - }, s.delay); - }; - next(); - }; - - useEffect(() => { setTimeout(play, 350); }, []); // eslint-disable-line react-hooks/exhaustive-deps - - const renderLine = (item, i) => { - if (!item.t) return
; - return ( -
{item.t}
- ); - }; - - return ( -
-
-
- {">"}_ -
- x402 Pay Flow -
- -
- -
-
- Agent -
-
- {leftLines.map(renderLine)} - {running &&
{"▋"}
} -
-
- -
- {done && ( - - )} -
-
- ); -};