# papertrade-yield: full documentation > Unofficial, not affiliated with Papertrade. High leverage can lose your whole margin. Not financial advice. Source: https://papertrade-yield.pages.dev/docs/ . Index: https://papertrade-yield.pages.dev/llms.txt --- # Papertrade Yield Papertrade Yield is an unofficial dashboard, JSON API and MCP server for **PAPER staking** on [Papertrade](https://papertrade.xyz), the on-chain synthetic perpetuals exchange on HyperEVM. It is not affiliated with Papertrade. High leverage can lose your whole margin, and nothing here is financial advice. PAPER is minted to traders who lose, and it is staked in the `PaperStaking` contract to earn USDC. This project answers the practical questions about that loop with real data: - How much USDC was actually paid to stakers in the last 24 hours, 7 days and 30 days? - What does that mean per million PAPER per day, given that PAPER has no market price? - Where is the mint curve right now, and how much PAPER does a given loss mint? - What is in a specific wallet: PAPER held, PAPER staked, pending rewards, lifetime claims? - What would staking, unstaking or claiming look like, step by step, before anything is signed? ## Three surfaces | Surface | Where | For | | --- | --- | --- | | Web app | [papertrade-yield.pages.dev](https://papertrade-yield.pages.dev) | People. Live data, a calculator and wallet-signed Stake, Unstake and Claim. | | JSON API | `/api/staking`, `/api/staking/claims`, `/api/staking/wallet` | Scripts and services. See the [reference](/docs/reference/). | | MCP server | `https://papertrade-yield.pages.dev/mcp` | AI agents. Seven read-only tools. See [MCP](/docs/mcp/). | ## What it reads Everything is read-only and comes from public sources: - The Papertrade API at `https://exchange.papertrade.xyz` (protocol summary, trading state, hourly protocol history, per-account staking history). - The HyperEVM chain (chain id 999) through public RPC providers, for the `PaperStaking` accumulator, `pendingReward(address)`, `PAPER.balanceOf(address)` and `Claimed` logs. ## What it never does Nothing in the server, the API or the MCP tools signs, sends, swaps, bridges, mints or pays. The MCP tool `plan_staking_action` returns an **unsigned plan** only. When a person stakes through the web app, their own browser wallet signs the EIP-712 intent and the Papertrade relayer submits it. See [Security and limits](/docs/security/). ## Where to go next 1. [Quickstart](/docs/quickstart/): read your first yield number in one command. 2. [Connect your AI](/docs/connect-your-ai/): copy-paste setup for Claude, Codex, ChatGPT, Gemini, Cursor and more. 3. [Concepts](/docs/concepts/): the Papertrade rules that make the numbers mean what they say. --- # Quickstart No account, API key or install is needed to read data. ## 1. Read the yield ```bash curl -s https://papertrade-yield.pages.dev/api/staking | jq '.yield.windows[] | {label, usdPer1MPaperPerDay, complete}' ``` Each window reports `usdPer1MPaperPerDay`: USDC paid to stakers per 1,000,000 staked PAPER per day, measured from real history. `complete: false` means the protocol has less history than the window asks for. ## 2. Call the MCP server The endpoint speaks MCP Streamable HTTP and is stateless, so there is no session to open. ```bash curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' ``` List the tools, then call one: ```bash curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_apr","arguments":{}}}' ``` ## 3. Connect your AI For Claude Code: ```bash claude mcp add --transport http papertrade-yield https://papertrade-yield.pages.dev/mcp ``` Then ask: "What did PAPER stakers earn per million PAPER over the last 7 days?" Other clients are on the [Connect your AI](/docs/connect-your-ai/) page. ## 4. Look at one wallet ```bash curl -s 'https://papertrade-yield.pages.dev/api/staking/wallet?address=0x0000000000000000000000000000000000000001' ``` Replace the address with any HyperEVM address. The response has raw 18-decimal values as strings. ## 5. Use the web app Open [papertrade-yield.pages.dev](https://papertrade-yield.pages.dev), paste an address for a read-only view, or connect a browser wallet to stake, unstake and claim. Every action opens a confirmation dialog first, and the wallet signs. --- # Concepts ## PAPER and PaperStaking Papertrade mints **PAPER** to traders on the losing side of a position, at a rate set by the **mint curve**. Holders stake PAPER in the `PaperStaking` contract and earn **USDC** that the protocol distributes. The relayer runs reward distribution. Users sign three actions: Stake, Unstake and Claim. | Item | Value | | --- | --- | | Chain | HyperEVM, chain id 999 | | PAPER | `0xe40f17915daa230324030003a197cdaef2261c0e` | | PaperStaking | `0xaad6c7b0cc3014fc80ffedae0ed7ce5967b0f016` | | Minimum stake | Read it live from `get_staking_stats` or `/api/staking` (`protocol.minimumStakePaper`). | ## Realized yield, not a forecast Every yield number here is **history**. For each hour, Papertrade reports the revenue that went to PAPER stakers (`paperRevenue`) and the staked PAPER. The yield for a window is the revenue divided by the mean staked PAPER (trapezoid rule over hourly samples), pro-rated to the window. It says what stakers earned, not what they will earn. ## Why per million PAPER per day PAPER has no market price, so a percentage return cannot be computed honestly. The headline unit is **USDC per 1,000,000 staked PAPER per day** (`usdPer1MPaperPerDay`). It is a plain USDC amount that needs no price assumption. ## APR needs an assumed price If you want a percentage, you supply the price: `get_apr` accepts `assumedPaperPriceUsd`. APR is then `usdPerPaperPerDay * 365 / assumedPrice`, **simple and not compounded**. The assumption is yours and the result is labeled as implied. ## Units - Raw on-chain amounts are 18-decimal integers ("wads"), returned as strings. Keep them as `BigInt` until display. - `accRewardPerShare` is scaled by 1e27. - Block time on HyperEVM is about one second, so 3,600 blocks is about one hour. The claims tool uses this to bound its log window. ## Staked balance is derived `PaperStaking` exposes `pendingReward(address)`, and the PAPER token exposes `balanceOf`, but there is no per-user stake getter in the contract ABI used here. The staked amount is the net of the wallet's `Staked` and `Unstaked` events from the Papertrade account history (up to eight pages of 75). `historyComplete: false` means the cap was hit and the figure may be partial. ## Reward history comes from the API, not logs `PaperStaking` emits no event when the reward accumulator grows, and public RPCs are not archive nodes. Reward history therefore comes from the Papertrade protocol history, cross-checked on chain against `accRewardPerShare` (the `reconstructionError` field). `Claimed` logs are real events and power the claims tool. ## Intents and signing Stake, Unstake and Claim are EIP-712 typed-data intents in the `PaperStaking` domain with the fields `user`, `amount`, `nonce` and `deadline`. The user's wallet signs and the Papertrade relayer submits. This project never holds keys. The MCP tool `plan_staking_action` shows the exact typed data to be signed, with nonce and deadline allocated at signing time. --- # API, CLI and SDK reference Base URL: `https://papertrade-yield.pages.dev`. All endpoints are `GET`, return JSON and send `access-control-allow-origin: *`. The machine-readable description is [`/openapi.json`](/openapi.json) (OpenAPI 3.1). The MCP endpoint is documented separately on the [MCP](/docs/mcp/) page. ## GET /api/staking Protocol staking state, mint curve, LP context and realized reward history. Cached for 60 seconds. ```bash curl -s https://papertrade-yield.pages.dev/api/staking | jq '.protocol' ``` | Field | Meaning | | --- | --- | | `generatedAt` | ISO time the snapshot was built. | | `source` | `apiBlock`, `historyAsOfMs` and `chainRead` (false when no RPC answered). | | `protocol` | `paperSupply`, `paperStaked`, `stakedShare` (0 to 1), `accRewardPerShareRaw` (scaled by 1e27), `pendingRewardsUsd`, `minimumStakePaper`, `onchain`. | | `mint` | `rate` (PAPER per $1 of loss basis), `trackedLpUsd`, `tailProgressUsd`, `onFlatBranch`, `curve` samples. | | `lp` | Liquidity and queue figures in USD. | | `yield` | `windows` (24h, 7d, 30d), hourly `series`, `cumulativeRewardsUsd`, `reconstructionError`, `method`, `pricing`. | Each yield window has `label`, `hours`, `hoursCovered`, `complete`, `rewardsUsd`, `avgStakedPaper`, `rewardPerPaper` and the headline `usdPer1MPaperPerDay`. ## GET /api/staking/claims USDC claimed on chain by stakers, bucketed per hour from `PaperStaking` `Claimed` logs. Cached for 5 minutes. | Parameter | Type | Default | Notes | | --- | --- | --- | --- | | `hours` | integer | 24 | 1 to 72. Anything else returns `400`. | Response: `hours`, `totalClaimedUsd`, `claims`, `uniqueClaimers`, `largestClaimUsd` and `buckets` (`t`, `claimedUsd`, `claims`). ## GET /api/staking/wallet On-chain `pendingReward(address)` and `PAPER.balanceOf(address)` for one wallet. Cached for 15 seconds per address. ```bash curl -s 'https://papertrade-yield.pages.dev/api/staking/wallet?address=0x0000000000000000000000000000000000000001' ``` Response: `address`, `pendingRewardRaw` (USDC, 18 decimals), `paperBalanceRaw` (PAPER, 18 decimals, unstaked only) and `readAt`. The staked amount and lifetime claims need the account history and are available through the MCP tool `get_wallet_staking`. ## GET /api/papertrade/{path} A same-origin read proxy to the public Papertrade API, because the upstream sends no CORS headers. The web app uses it. It forwards the public read paths (`/state/`, `/query/`, `/queue-rank/`, `/relayer/health`, including the live SSE stream) and the POST routes that carry intents the user's wallet has already signed. Nothing else is proxied (`404 not_proxied`), and responses are never cached (`cache-control: no-store`). The proxy holds no keys and signs nothing. ## Errors Errors are JSON: `{ "code": "...", "message": "..." }`. | Status | `code` | Meaning | | --- | --- | --- | | 400 | `invalid_address`, `invalid_hours` | The input failed validation. | | 502 | `upstream_unavailable`, `rpc_unavailable` | Papertrade or HyperEVM did not answer. Retry shortly. | ## CLI There is no standalone CLI. For local work use the npm scripts in the repository: `npm run dev:site` runs the whole site with Pages Functions on port 8795, and `npm run verify:signing` checks the intent wire format with a throwaway key against the live relayer (it must be refused). ## SDK Protocol math comes from [`papertrade-sdk`](https://github.com/nirholas/papertrade-sdk): `paperMintRate`, `pendingStakingRewardRaw`, the contract addresses and the EIP-712 types. This project uses it instead of reimplementing the math. The shared read-only loaders in `src/data.ts` (`loadSnapshot`, `loadClaims`, `loadWalletStaking`) are what both the JSON API and the MCP tools call, so they always agree. --- # MCP server Endpoint: `https://papertrade-yield.pages.dev/mcp` It is an MCP **Streamable HTTP** server, stateless and read-only. There are no sessions, no cookies and no auth. Every tool reads public Papertrade and HyperEVM data. No tool signs or sends anything. ## Transport behavior | Request | Response | | --- | --- | | `POST /mcp` with JSON-RPC 2.0 (single or batch of up to 20) | `application/json`. If `Accept` allows only `text/event-stream`, a single SSE `message` event. | | `notifications/*` and client responses | `202 Accepted`, empty body. | | `GET /mcp` with `Accept: text/event-stream` | `405`, `Allow: POST, OPTIONS`. | | `GET /mcp` otherwise | A JSON description of the server with links to docs. | | `DELETE /mcp` | `405`. | | `OPTIONS /mcp` | `204` with CORS (`*`), allowing `Mcp-Session-Id`, `Mcp-Protocol-Version` and `Authorization`. | Protocol versions `2025-06-18`, `2025-03-26` and `2024-11-05` are supported. `initialize` echoes the client's version when supported, otherwise answers `2025-06-18`. Capabilities are `{ "tools": { "listChanged": false } }`. `resources/list`, `resources/templates/list` and `prompts/list` return empty lists. Errors follow JSON-RPC: `-32700` parse error (HTTP 400), `-32600` invalid request, `-32601` unknown method, `-32602` unknown tool or bad params. Invalid tool **arguments** are reported as a normal result with `isError: true` so the model can correct itself. Requests over 64 KB return `413`. Calls to `tools/call` are limited to 60 per minute per client IP and return `429` with `Retry-After` and JSON-RPC code `-32000` beyond that. ## Tools All tools carry `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true` and `openWorldHint: true`. Results contain a text summary in `content` and the same data in `structuredContent`. | Tool | What it does | | --- | --- | | [`get_staking_stats`](#get_staking_stats) | PAPER staking stats | | [`get_apr`](#get_apr) | Realized yield and implied APR | | [`get_yield_history`](#get_yield_history) | Hourly staking reward history | | [`get_claims_activity`](#get_claims_activity) | On-chain claims activity | | [`get_wallet_staking`](#get_wallet_staking) | Wallet stake and rewards | | [`project_rewards`](#project_rewards) | Projected staking rewards calculator | | [`plan_staking_action`](#plan_staking_action) | Plan a stake, unstake or claim (unsigned) | ### get_staking_stats **PAPER staking stats.** Current PAPER staking state on Papertrade: total supply, staked amount and share, undistributed staker rewards, minimum stake, the current mint rate (PAPER minted per $1 of loss), liquidity context, and the realized USDC yield per 1M PAPER per day over 24h, 7d and 30d. PAPER has no market price, so yield is quoted per 1M PAPER, not as a percentage. Read only. This tool takes no arguments. Structured output fields: `generatedAt`, `paperSupply`, `paperStaked`, `stakedShare`, `pendingRewardsUsd`, `minimumStakePaper`, `accRewardPerShareUsdPerPaper`, `mintRate`, `onFlatBranch`, `trackedLpUsd`, `cumulativeRewardsUsd`, `yieldWindows`, `method`, `pricing`, `contracts`. ```json { "type": "object", "properties": {}, "additionalProperties": false } ``` ### get_apr **Realized yield and implied APR.** Realized staking yield for PAPER over 24h, 7d and 30d as USDC per 1M PAPER per day. PAPER has no market price, so a percentage APR exists only for a price you assume: pass assumedPaperPriceUsd to get a simple (non-compounding) implied APR for that price. Without a price, impliedAprPercent is null. Read only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `assumedPaperPriceUsd` | number (> 0) | no | A PAPER price in USD to test. It is an assumption, not a market quote. | | `window` | `"24h"` or `"7d"` or `"30d"` | no | Trailing window of realized yield to use. | Structured output fields: `assumedPaperPriceUsd`, `windows`, `method`, `pricing`. ```json { "type": "object", "properties": { "assumedPaperPriceUsd": { "type": "number", "exclusiveMinimum": 0, "description": "A PAPER price in USD to test. It is an assumption, not a market quote." }, "window": { "type": "string", "enum": [ "24h", "7d", "30d" ], "description": "Trailing window of realized yield to use." } }, "additionalProperties": false } ``` ### get_yield_history **Hourly staking reward history.** Hourly history of USDC distributed to PAPER stakers, newest last: reward in USD, mean staked PAPER, USDC per PAPER and the pace as USDC per 1M PAPER per day, for the trailing number of hours. Buckets before staking rewards began are omitted. Read only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `hours` | integer (min 1, max 720, default 48) | no | How many trailing hourly buckets to return. | Structured output fields: `hours`, `points`, `totalRewardsUsd`, `cumulativeRewardsUsd`. ```json { "type": "object", "properties": { "hours": { "type": "integer", "minimum": 1, "maximum": 720, "default": 48, "description": "How many trailing hourly buckets to return." } }, "additionalProperties": false } ``` ### get_claims_activity **On-chain claims activity.** USDC that stakers claimed on chain (PaperStaking Claimed events on HyperEVM) over the trailing 1 to 72 hours, bucketed per hour, with unique claimers and the largest claim. Claiming is a choice, so this differs from USDC distributed. Read only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `hours` | integer (min 1, max 72, default 24) | no | | Structured output fields: `hours`, `totalClaimedUsd`, `claims`, `uniqueClaimers`, `largestClaimUsd`, `buckets`. ```json { "type": "object", "properties": { "hours": { "type": "integer", "minimum": 1, "maximum": 72, "default": 24 } }, "additionalProperties": false } ``` ### get_wallet_staking **Wallet stake and rewards.** Staking position of one wallet: staked PAPER (net of the wallet's Staked and Unstaked history), unstaked PAPER balance and pending USDC reward (both read on chain now), lifetime USDC claimed, and the ten most recent staking events. Amounts are in PAPER and USDC; raw 18-decimal values are included. Read only: it never needs or accepts a key. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `address` | string (pattern `^0x[0-9a-fA-F]{40}$`) | yes | A 0x-prefixed 20-byte HyperEVM wallet address. Read only: this is a lookup, nothing is signed. | Structured output fields: `address`, `paperStaked`, `paperUnstakedBalance`, `pendingRewardUsd`, `lifetimeClaimedUsd`, `raw`, `historyComplete`, `recentEvents`, `readAt`. ```json { "type": "object", "properties": { "address": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$", "description": "A 0x-prefixed 20-byte HyperEVM wallet address. Read only: this is a lookup, nothing is signed." } }, "required": [ "address" ], "additionalProperties": false } ``` ### project_rewards **Projected staking rewards calculator.** Calculator: what a PAPER position would earn if the realized pace of a trailing window simply continued. Give either paperAmount (PAPER you hold or would stake) or lossUsd (a hypothetical trading loss, converted to PAPER at today's mint rate; liquidations mint on the full loss, normal closes on 98%). The result is an extrapolation of history, not a forecast: the pace and the mint rate both move. Add assumedPaperPriceUsd for an implied APR. Read only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `paperAmount` | number (> 0) | no | PAPER staked. Give this or lossUsd. | | `lossUsd` | number (> 0) | no | Hypothetical loss in USD. Give this or paperAmount. | | `liquidated` | boolean (default false) | no | With lossUsd: the position was liquidated rather than closed at a loss. | | `days` | integer (min 1, max 365, default 30) | no | | | `window` | `"24h"` or `"7d"` or `"30d"` | no | Trailing window of realized yield to use. | | `assumedPaperPriceUsd` | number (> 0) | no | | Structured output fields: `paperAmount`, `lossUsd`, `mintRate`, `window`, `windowComplete`, `usdPer1MPaperPerDay`, `days`, `projectedUsdPerDay`, `projectedUsdOverDays`, `projectedUsdPerYear`, `breakEvenDays`, `impliedAprPercent`, `caveat`. ```json { "type": "object", "properties": { "paperAmount": { "type": "number", "exclusiveMinimum": 0, "description": "PAPER staked. Give this or lossUsd." }, "lossUsd": { "type": "number", "exclusiveMinimum": 0, "description": "Hypothetical loss in USD. Give this or paperAmount." }, "liquidated": { "type": "boolean", "default": false, "description": "With lossUsd: the position was liquidated rather than closed at a loss." }, "days": { "type": "integer", "minimum": 1, "maximum": 365, "default": 30 }, "window": { "type": "string", "enum": [ "24h", "7d", "30d" ], "description": "Trailing window of realized yield to use." }, "assumedPaperPriceUsd": { "type": "number", "exclusiveMinimum": 0 } }, "additionalProperties": false } ``` ### plan_staking_action **Plan a stake, unstake or claim (unsigned).** Builds an UNSIGNED plan for staking, unstaking or claiming PAPER staking rewards: preflight checks against the wallet's real balance, staked amount and pending reward, the EIP-712 type and domain the wallet would be asked to sign, and a link to the app where the owner signs in their own wallet. This tool never signs, never submits and never moves funds; the nonce and deadline are only allocated when the owner signs. Read only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `action` | `"stake"` or `"unstake"` or `"claim"` | yes | | | `address` | string (pattern `^0x[0-9a-fA-F]{40}$`) | yes | A 0x-prefixed 20-byte HyperEVM wallet address. Read only: this is a lookup, nothing is signed. | | `amountPaper` | string (pattern `^[0-9]+(\.[0-9]{1,18})?$`) | no | PAPER amount as a decimal string. Required for stake and unstake, ignored for claim. | Structured output fields: `unsigned`, `signed`, `submitted`, `action`, `address`, `amountPaper`, `amountRaw`, `preflight`, `typedData`, `howToSign`, `signUrl`, `note`. ```json { "type": "object", "properties": { "action": { "type": "string", "enum": [ "stake", "unstake", "claim" ] }, "address": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$", "description": "A 0x-prefixed 20-byte HyperEVM wallet address. Read only: this is a lookup, nothing is signed." }, "amountPaper": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]{1,18})?$", "description": "PAPER amount as a decimal string. Required for stake and unstake, ignored for claim." } }, "required": [ "action", "address" ], "additionalProperties": false } ``` ## Examples Realized yield with an assumed price: ```bash curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_apr","arguments":{"assumedPaperPriceUsd":0.01,"window":"7d"}}}' ``` Project rewards for a loss, assuming it was liquidated: ```bash curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"project_rewards","arguments":{"lossUsd":500,"liquidated":true,"days":30}}}' ``` Plan a claim without signing it: ```bash curl -s https://papertrade-yield.pages.dev/mcp \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"plan_staking_action","arguments":{"action":"claim","address":"0x0000000000000000000000000000000000000001"}}}' ``` The plan contains `unsigned: true`, `signed: false`, `submitted: false`, preflight checks, the EIP-712 typed data to be signed, and a link to the web app where the wallet owner signs. ## Inspect it ```bash npx @modelcontextprotocol/inspector --cli https://papertrade-yield.pages.dev/mcp --transport http --method tools/list ``` --- # Connect your AI The MCP URL is the same everywhere: ```text https://papertrade-yield.pages.dev/mcp ``` It needs no API key or OAuth. Client configuration formats change, so each section names the format it was checked against. If a client rejects a snippet, check its current MCP docs and keep the URL. ## Claude Code ```bash claude mcp add --transport http papertrade-yield https://papertrade-yield.pages.dev/mcp ``` Add `--scope project` to write a shared `.mcp.json`, or `--scope user` for all projects. The file form: ```json { "mcpServers": { "papertrade-yield": { "type": "http", "url": "https://papertrade-yield.pages.dev/mcp" } } } ``` ## Claude Desktop and claude.ai Open Settings, then Connectors, click Add, choose **Add custom connector**, paste the URL and click Add. Connectors can also be enabled per conversation from the "Add files, connectors, and more" menu. For a Claude Desktop build that only loads local stdio servers, bridge with `mcp-remote` in `claude_desktop_config.json`: ```json { "mcpServers": { "papertrade-yield": { "command": "npx", "args": ["-y", "mcp-remote", "https://papertrade-yield.pages.dev/mcp"] } } } ``` ## Claude API (MCP connector) Send the beta header `anthropic-beta: mcp-client-2025-11-20`. Declare the server in `mcp_servers` and enable it with an `mcp_toolset` entry in `tools`: ```bash curl https://api.anthropic.com/v1/messages \ -H "content-type: application/json" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: mcp-client-2025-11-20" \ -d '{ "model": "claude-sonnet-5-5", "max_tokens": 1000, "messages": [{"role": "user", "content": "What did PAPER stakers earn per 1M PAPER per day this week?"}], "mcp_servers": [ {"type": "url", "url": "https://papertrade-yield.pages.dev/mcp", "name": "papertrade-yield"} ], "tools": [ {"type": "mcp_toolset", "mcp_server_name": "papertrade-yield"} ] }' ``` With the Python SDK use `client.beta.messages.create(..., mcp_servers=[...], tools=[{"type": "mcp_toolset", "mcp_server_name": "papertrade-yield"}], betas=["mcp-client-2025-11-20"])`. ## OpenAI Codex CLI ```bash codex mcp add papertrade-yield --url https://papertrade-yield.pages.dev/mcp ``` Or edit `~/.codex/config.toml`: ```toml [mcp_servers.papertrade-yield] url = "https://papertrade-yield.pages.dev/mcp" ``` ## OpenAI Responses API ```json { "model": "gpt-5", "input": "What is the realized PAPER staking yield over 7 days?", "tools": [ { "type": "mcp", "server_label": "papertrade_yield", "server_description": "Read-only PAPER staking and yield data for Papertrade (unofficial).", "server_url": "https://papertrade-yield.pages.dev/mcp", "require_approval": "never", "allowed_tools": ["get_staking_stats", "get_apr", "get_yield_history", "get_claims_activity", "get_wallet_staking", "project_rewards", "plan_staking_action"] } ] } ``` All tools are read-only, so `require_approval: "never"` is safe here. Use `"always"` if you prefer to review each call. ## ChatGPT In ChatGPT, enable **Developer mode** (Settings, Apps and Connectors, Advanced settings; availability depends on plan and OpenAI moves this setting from time to time). Then create a connector: give it a name, paste `https://papertrade-yield.pages.dev/mcp` as the server URL and choose no authentication. Enable the connector in a chat from the tools menu. ## Gemini CLI ```bash gemini mcp add --transport http papertrade-yield https://papertrade-yield.pages.dev/mcp ``` Or in `~/.gemini/settings.json`, which uses `httpUrl` for Streamable HTTP: ```json { "mcpServers": { "papertrade-yield": { "httpUrl": "https://papertrade-yield.pages.dev/mcp" } } } ``` ## Cursor `.cursor/mcp.json` in a project, or `~/.cursor/mcp.json` globally: ```json { "mcpServers": { "papertrade-yield": { "url": "https://papertrade-yield.pages.dev/mcp" } } } ``` ## VS Code `.vscode/mcp.json`: ```json { "servers": { "papertrade-yield": { "type": "http", "url": "https://papertrade-yield.pages.dev/mcp" } } } ``` Or run **MCP: Add Server** from the command palette and choose HTTP. ## Windsurf `~/.codeium/windsurf/mcp_config.json`. Windsurf uses `serverUrl` for remote servers: ```json { "mcpServers": { "papertrade-yield": { "serverUrl": "https://papertrade-yield.pages.dev/mcp" } } } ``` Press refresh in the MCP panel after saving. ## Zed In `settings.json`, under `context_servers`: ```json { "context_servers": { "papertrade-yield": { "url": "https://papertrade-yield.pages.dev/mcp" } } } ``` If Zed starts an OAuth prompt, the server does not need one. Use the `mcp-remote` bridge shown in the Claude Desktop section as a `command` entry instead. ## Cline `cline_mcp_settings.json` (MCP Servers panel, Configure MCP Servers): ```json { "mcpServers": { "papertrade-yield": { "url": "https://papertrade-yield.pages.dev/mcp", "type": "streamableHttp", "disabled": false } } } ``` ## Goose Run `goose configure`, choose Add Extension, then Remote Extension (Streamable HTTP), and enter the name and the URL. Or add it to `~/.config/goose/config.yaml`: ```yaml extensions: papertrade-yield: name: papertrade-yield type: streamable_http uri: https://papertrade-yield.pages.dev/mcp enabled: true timeout: 300 ``` For one session only: `goose session --with-streamable-http-extension https://papertrade-yield.pages.dev/mcp`. ## Continue `~/.continue/config.yaml` or a file in `.continue/mcpServers/`: ```yaml name: Papertrade Yield version: 0.0.1 schema: v1 mcpServers: - name: papertrade-yield type: streamable-http url: https://papertrade-yield.pages.dev/mcp ``` ## Any other agent framework (OpenAPI) Frameworks that import OpenAPI tools (LangChain, LlamaIndex, the OpenAI Agents SDK, Dify, n8n and similar) can load `https://papertrade-yield.pages.dev/openapi.json` directly. It describes the JSON API and the `/mcp` endpoint. The A2A agent card at `/.well-known/agent-card.json` lists the same capabilities as skills. See [Agent discovery](/docs/agent-discovery/). ## Try it Once connected, ask: - "What did PAPER stakers earn per million PAPER over the last 24 hours and 7 days?" - "If I assume PAPER is worth $0.02, what is the implied APR?" - "How much PAPER would a $300 liquidated loss mint, and what would it earn in 30 days?" - "Show wallet 0x... : staked PAPER, pending rewards, lifetime claims." - "Plan a claim for 0x... and tell me what I would need to sign." The last one returns a plan only. You sign in your own wallet, never through the AI. --- # Agent discovery All of these are real files with the right content type, not the app shell. | Path | Content type | What it is | | --- | --- | --- | | `/mcp` | `application/json` | The MCP server (Streamable HTTP). | | `/.well-known/mcp/server-card.json` | `application/json` | MCP server card (SEP-1649 shape): server info, transport endpoint, capabilities and the tool list. | | `/.well-known/mcp.json` | `application/json` | Alias of the server card. | | `/.well-known/agent-card.json` | `application/json` | A2A agent card with skills mapped from the MCP tools. | | `/.well-known/agent.json` | `application/json` | Alias of the agent card. | | `/.well-known/api-catalog` | `application/linkset+json` | RFC 9727 API catalog linking the OpenAPI document, the MCP endpoint, the docs and `llms.txt`. | | `/openapi.json` | `application/json` | OpenAPI 3.1 for the JSON API and `/mcp`. | | `/llms.txt` | `text/plain` | A short index of the site for language models, listing every docs page and its `.md` twin. | | `/llms-full.txt` | `text/plain` | The full docs inlined in one file. | | `/docs/*.md` | `text/markdown` | Every docs page as raw markdown at the page path plus `.md`. | | `/robots.txt` | `text/plain` | Crawl rules with a Content-Signal and explicit allows for the main AI crawlers. | | `/sitemap.xml` | `application/xml` | All pages, including docs. | The home page also sends `Link` response headers: `rel="service-desc"` for the OpenAPI document, `rel="api-catalog"` for the catalog and `rel="mcp"` for the MCP endpoint. ## Generated, not hand-written The server card, agent card, API catalog, `llms.txt`, `llms-full.txt` and the sitemap are generated at build time from the tool definitions in `src/mcp-tools.ts` and the markdown in `docs/`. The tool list in the cards therefore cannot drift from what `/mcp` serves. Run `npm run build:site` to regenerate them. ## Crawler policy `robots.txt` allows `GPTBot`, `ClaudeBot`, `Claude-User`, `OAI-SearchBot`, `Google-Extended` and `PerplexityBot`, and declares `Content-Signal: search=yes, ai-input=yes, ai-train=yes`. The data shown is public Papertrade data and the code is Apache-2.0. ## Registry metadata `server.json` at the repository root describes the server for the official MCP registry under the name `io.github.nirholas/papertrade-yield`, with a streamable-http remote pointing at the live `/mcp`. `glama.json` carries the Glama listing metadata. Publishing to either is a manual owner step. --- # Self-hosting on Cloudflare The site is static files plus Pages Functions. There is no database, no KV and no secret. You need Node 20 or newer and a Cloudflare account. ## Run locally ```bash git clone https://github.com/nirholas/papertrade-yield cd papertrade-yield npm install npm run dev:site ``` `dev:site` builds the site and starts `wrangler pages dev` on port 8795 from the `site/` directory. ## Build ```bash npm run build:site ``` This runs three steps: esbuild bundles `site/app.ts` to `site/public/app.js`, `scripts/build-docs.mjs` renders `docs/*.md` to `site/public/docs/`, and the same script writes the discovery files (server card, agent card, API catalog, `llms.txt`, `llms-full.txt`, `sitemap.xml`). ## Deploy ```bash cd site npx wrangler pages deploy --project-name --branch main ``` Create the project once with `npx wrangler pages project create --production-branch main`. Authenticate with `wrangler login` or a `CLOUDFLARE_API_TOKEN` that has Pages edit permission. ## Configuration | Item | Where | Default | | --- | --- | --- | | `PAPERTRADE_API_URL` | `site/wrangler.toml` `[vars]` | `https://exchange.papertrade.xyz` | | Public origin | `SITE_URL` in `src/mcp-tools.ts` and the scripts | `https://papertrade-yield.pages.dev` | If you host under another domain, change the origin where it appears (the build script reads it from `package.json` `homepage`) and rebuild so the cards, sitemap and canonical URLs match. ## Embedding The app route allows framing by the Papertrade OS host and other `*.pages.dev` pages. `?embed=1` hides the marketing chrome and shows only the product. If you embed it elsewhere, add your origin to `frame-ancestors` in `site/public/_headers`. ## Checks ```bash npm run typecheck npm test npm run build:site ``` --- # Security and limits ## Money safety - No endpoint, tool or script here signs, sends, swaps, bridges, mints or pays. - `plan_staking_action` returns an unsigned plan. Its output says `unsigned: true`, `signed: false` and `submitted: false`. - In the web app, the user's own browser wallet signs an EIP-712 intent after a confirmation dialog. The Papertrade relayer submits it. No private key ever reaches this site. - Do not paste a seed phrase or private key into any AI client. No part of this project asks for one. ## Untrusted data Strings that come from the chain or from the Papertrade API are data, never instructions. The MCP tools return them as values, the web app escapes them before rendering, and an AI client should treat them the same way. ## Limits | Limit | Value | | --- | --- | | MCP `tools/call` rate | 60 per minute per client IP, then `429` with `Retry-After`. | | MCP request body | 64 KB, then `413`. | | JSON-RPC batch size | 20. | | Claims window | 1 to 72 hours. | | Yield history | 1 to 720 hours. | | Wallet history | Up to 8 pages of 75 events, then `historyComplete: false`. | | Live wallet stream | One per page. The upstream stream is rate limited (HTTP 429). | The limiter is in memory per Cloudflare isolate, so it is a courtesy guard against loops, not a hard quota. Upstream calls per tool call are bounded. ## Caching `/api/staking` is cached 60 seconds, `/api/staking/claims` 5 minutes, `/api/staking/wallet` 15 seconds per address. MCP responses are not cached. Treat any figure as a recent read, not a quote. ## Headers The app sends a strict Content-Security-Policy (`script-src 'self'`, `connect-src 'self'`, no inline scripts), `X-Content-Type-Options: nosniff`, a restrictive `Referrer-Policy` and `Permissions-Policy`. `frame-ancestors` allows only the site itself, `https://papertrade-os.pages.dev` and `https://*.pages.dev`. Discovery files and the MCP endpoint send `access-control-allow-origin: *` because they are public and unauthenticated. Origin is never trusted for auth, and there are no cookies. ## Reporting a vulnerability Use the private advisory form: [github.com/nirholas/papertrade-yield/security/advisories/new](https://github.com/nirholas/papertrade-yield/security/advisories/new). See `SECURITY.md` in the repository. ## Disclaimer This is an unofficial project and is not affiliated with Papertrade. High leverage can lose your whole margin. Yield shown is realized history, never a promise, and nothing here is financial advice. --- # FAQ ## Why is there no APR on the home page? PAPER has no market price, and a percentage return needs one. The page shows USDC per million PAPER per day instead. Enter a price you want to test and it shows a simple implied APR for that price. ## Is the yield a forecast? No. It is the USDC paid to stakers in the past, divided by the average staked PAPER. Future yield depends on trading losses, staking participation and the protocol. ## Why do the staked amount and the claims come from account history? The contract exposes the pending reward and the token balance directly, but no per-user stake getter. The net of `Staked` and `Unstaked` events gives the stake. If a wallet has more than 600 events, `historyComplete` is `false`. ## Can the AI stake or claim for me? No. The tools are read-only. `plan_staking_action` shows what you would sign and checks balances. Signing happens in your wallet, in the web app. ## Which AI clients work? Any client that supports MCP over Streamable HTTP, and any framework that imports OpenAPI. See [Connect your AI](/docs/connect-your-ai/). ## Does the MCP server need a key? No. It is public and read-only, with a per-IP rate limit. ## A tool says the history is incomplete or a window is incomplete. Why? `complete: false` on a yield window means Papertrade has less history than the window you asked for. The figure covers all history so far and `hoursCovered` says how much. ## Where does the data come from? The public Papertrade API at `exchange.papertrade.xyz` and public HyperEVM RPC providers. Nothing is stored. ## Is this official? No. It is an unofficial project, not affiliated with Papertrade. Source: [github.com/nirholas/papertrade-yield](https://github.com/nirholas/papertrade-yield), licensed Apache-2.0. ## Can I open it inside Papertrade OS? Yes: [Open in Papertrade OS](https://papertrade-os.pages.dev/?open=yield). The app also supports `?embed=1` to show only the product. --- # Changelog ## 0.2.0 - 2026-10-11 - MCP server at `/mcp` (Streamable HTTP, stateless, JSON-RPC 2.0, batches, SSE reply, CORS, per-IP rate limit) with seven read-only tools: `get_staking_stats`, `get_apr`, `get_yield_history`, `get_claims_activity`, `get_wallet_staking`, `project_rewards` and `plan_staking_action` (an unsigned stake, unstake or claim plan only). - Shared read-only data loaders in `src/data.ts` now back both the JSON API and the MCP tools. - Docs site at `/docs/` with sidebar, table of contents, full-text search, copy buttons, dark and light themes, and a raw markdown twin for every page. Includes a Connect your AI guide for Claude, Codex, ChatGPT, Gemini, Cursor, VS Code, Windsurf, Zed, Cline, Goose and Continue. - Agent discovery: MCP server card, A2A agent card, RFC 9727 API catalog, `llms.txt`, `llms-full.txt`, robots with Content-Signal and explicit AI crawler allows, sitemap with docs, `Link` headers on the home page, `server.json` and `glama.json` for MCP registries. The OpenAPI document now describes `/mcp` and the discovery files. - Landing page: feature grid, works-with strip, connect-your-AI tabs, live tool list from `/mcp`, quickstart, and a footer with Docs, GitHub, llms.txt and MCP. - Embeddable in Papertrade OS: `?embed=1` shows only the product, an Open in Papertrade OS link, and `frame-ancestors` on the app route allows the OS host and `*.pages.dev`. - Tests for the MCP plumbing, the tools and the docs and discovery generator. ## 0.1.0 - 2026-10-10 - Protocol view: PAPER supply, staked and staked share, accRewardPerShare (API and on-chain cross-check), mint rate from the SDK `paperMintRate`, the mint curve with today marked, LP and queue context, minimum stake. - Reward history and realized yield: USDC paid to stakers per hour from the Papertrade protocol history, windows for 24h, 7d and 30d with the method stated, and yield quoted as USDC per 1M PAPER per day because PAPER has no price. Optional implied APR at a user-assumed price. - On-chain claims panel from PaperStaking `Claimed` logs. - Wallet view over EIP-6963 or a pasted read-only address: PAPER held, staked, pending rewards from the live stream and from `pendingReward`, lifetime claimed, estimated daily earnings. - Wallet-signed Stake, Unstake and Claim through `PapertradeTrader`, each with a confirmation dialog and live intent status. - Loss to PAPER calculator. - Pages Functions `/api/staking`, `/api/staking/claims`, `/api/staking/wallet`, `/openapi.json`. - Tests with recorded real fixtures for the APR math, event decoding, claims, curve and pending-reward math.