repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Mirrored from https://github.com/ProjectOpenSea/opensea-skill
stars
latest
clone command
git clone gitlawb://did:key:z6MkqRzA...RfoM/ProjectOpenSea-...git clone gitlawb://did:key:z6MkqRzA.../ProjectOpenSea-...fef93001Release v2.14.011h ago| #1 | # x402 Payment-Gated Tools (Pay-per-Call) |
| #2 | |
| #3 | x402 is the HTTP 402-based pay-per-call protocol used by tools that charge USDC per invocation. The caller signs an EIP-3009 `TransferWithAuthorization` and the server settles after executing the tool. |
| #4 | |
| #5 | ## How x402 works |
| #6 | |
| #7 | ``` |
| #8 | Agent Tool Server Facilitator |
| #9 | |--- POST /api ------------->| | |
| #10 | |<-- 402 + requirements -----| | |
| #11 | | | | |
| #12 | | (sign EIP-3009 USDC | | |
| #13 | | TransferWithAuthorization)| | |
| #14 | | | | |
| #15 | |--- POST /api ------------->| | |
| #16 | | X-Payment: <base64> |--- POST /verify -------------->| |
| #17 | | |<-- { isValid: true } ----------| |
| #18 | | | | |
| #19 | | | (execute tool handler) | |
| #20 | | | | |
| #21 | | |--- POST /settle -------------->| |
| #22 | | |<-- { success, txHash } --------| |
| #23 | |<-- 200 + result -----------| | |
| #24 | ``` |
| #25 | |
| #26 | 1. Agent calls the tool endpoint without payment |
| #27 | 2. Server returns `402` with `accepts[]` containing payment requirements (amount, asset, recipient, network) |
| #28 | 3. Agent signs an EIP-3009 `TransferWithAuthorization` for the requested USDC amount |
| #29 | 4. Agent retries the request with the `X-Payment` header containing the base64-encoded signed payload |
| #30 | 5. Server verifies the payment via the facilitator's `/verify` endpoint |
| #31 | 6. Server executes the tool handler |
| #32 | 7. Server settles the payment via the facilitator's `/settle` endpoint |
| #33 | 8. Server returns the result |
| #34 | |
| #35 | ## Build a tool with x402 paywall (server side) |
| #36 | |
| #37 | Use `defineToolPaywall` to keep the manifest price and the gate's enforced price in sync: |
| #38 | |
| #39 | ```typescript |
| #40 | import { createToolHandler, defineToolPaywall, defineManifest } from "@opensea/tool-sdk" |
| #41 | import { z } from "zod/v4" |
| #42 | |
| #43 | // 1. Define paywall config (single source of truth for price) |
| #44 | const paywall = defineToolPaywall({ |
| #45 | recipient: "0xYOUR_WALLET_ADDRESS", // where USDC is sent |
| #46 | amountUsdc: "0.01", // price per call in USDC |
| #47 | // network: "base", // default: "base" (or "base-sepolia" for testnet) |
| #48 | // facilitator: "payai", // default: "payai" (or "cdp" for Coinbase) |
| #49 | }) |
| #50 | |
| #51 | // 2. Include pricing in the manifest |
| #52 | const manifest = defineManifest({ |
| #53 | name: "My Paid Tool", |
| #54 | description: "A tool that costs $0.01 per call", |
| #55 | endpoint: "https://my-tool.example.com/api", |
| #56 | creatorAddress: "0xYOUR_WALLET_ADDRESS", |
| #57 | inputs: { type: "object", properties: { query: { type: "string" } }, required: ["query"] }, |
| #58 | outputs: { type: "object", properties: { result: { type: "string" } } }, |
| #59 | pricing: paywall.pricing, // advertised in manifest |
| #60 | }) |
| #61 | |
| #62 | // 3. Add the gate to the handler |
| #63 | export const toolHandler = createToolHandler({ |
| #64 | manifest, |
| #65 | inputSchema: z.object({ query: z.string() }), |
| #66 | outputSchema: z.object({ result: z.string() }), |
| #67 | gates: [paywall.gate], // enforced at runtime |
| #68 | handler: async (input) => ({ result: `Paid result: ${input.query}` }), |
| #69 | }) |
| #70 | ``` |
| #71 | |
| #72 | ### Facilitator options |
| #73 | |
| #74 | | Facilitator | Function | Auth required | Best for | |
| #75 | |-------------|----------|---------------|----------| |
| #76 | | PayAI | `payaiX402Gate()` | No | Prototyping, free to use | |
| #77 | | Coinbase CDP | `cdpX402Gate()` | Yes (JWT via `createAuthHeaders`) | Production | |
| #78 | |
| #79 | For lower-level control, use `payaiX402Gate()` or `cdpX402Gate()` directly instead of `defineToolPaywall()`. |
| #80 | |
| #81 | ## Call an x402-paid tool (agent/client side) |
| #82 | |
| #83 | ### Via CLI |
| #84 | |
| #85 | ```bash |
| #86 | PRIVATE_KEY=0x... npx @opensea/tool-sdk pay \ |
| #87 | https://my-tool.example.com/api \ |
| #88 | --body '{"query": "hello"}' |
| #89 | ``` |
| #90 | |
| #91 | ### Via SDK: `paidFetch` |
| #92 | |
| #93 | ```typescript |
| #94 | import { paidFetch, createWalletFromEnv } from "@opensea/tool-sdk" |
| #95 | |
| #96 | const adapter = createWalletFromEnv() // reads PRIVATE_KEY from env |
| #97 | |
| #98 | const res = await paidFetch("https://my-tool.example.com/api", { |
| #99 | signer: adapter, |
| #100 | method: "POST", |
| #101 | headers: { "Content-Type": "application/json" }, |
| #102 | body: JSON.stringify({ query: "hello" }), |
| #103 | // Safety guards: |
| #104 | maxAmount: "100000", // max 0.10 USDC (in 6-decimal base units) |
| #105 | allowedRecipients: ["0x..."], // only pay this address |
| #106 | }) |
| #107 | |
| #108 | const data = await res.json() |
| #109 | ``` |
| #110 | |
| #111 | `paidFetch` automatically: |
| #112 | 1. Makes the initial request |
| #113 | 2. Parses the 402 response for payment requirements |
| #114 | 3. Validates requirements against your safety guards (`maxAmount`, `allowedRecipients`, `allowedAssets`) |
| #115 | 4. Signs the EIP-3009 `TransferWithAuthorization` |
| #116 | 5. Retries with the `X-Payment` header |
| #117 | |
| #118 | ### Via raw HTTP (any language) |
| #119 | |
| #120 | ``` |
| #121 | POST /api HTTP/1.1 |
| #122 | Host: my-tool.example.com |
| #123 | Content-Type: application/json |
| #124 | |
| #125 | {"query": "hello"} |
| #126 | ``` |
| #127 | |
| #128 | If the response is `402`, read `body.accepts[0]` for payment requirements, sign the USDC transfer authorization, base64-encode the payment payload, and retry with `X-Payment: <base64>`. |
| #129 |