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.010h ago| #1 | --- |
| #2 | name: opensea-tool-sdk |
| #3 | description: Build, register, and gate AI-callable tool endpoints using the OpenSea Tool Registry (ERC-8257) on Base. Scaffold HTTPS tools with JSON Schema interfaces, register them onchain, gate access via NFT ownership, subscriptions, trait gating, or x402 pay-per-call (USDC), and call gated tools. For querying OpenSea marketplace data use opensea-api instead. |
| #4 | homepage: https://github.com/ProjectOpenSea/tool-sdk |
| #5 | repository: https://github.com/ProjectOpenSea/tool-sdk |
| #6 | license: MIT |
| #7 | env: |
| #8 | OPENSEA_API_KEY: |
| #9 | description: API key for OpenSea REST API (tool discovery endpoints) |
| #10 | required: false |
| #11 | obtain: https://docs.opensea.io/reference/api-keys#instant-api-key-for-agents |
| #12 | PRIVATE_KEY: |
| #13 | description: Wallet private key for onchain registration and tool calls |
| #14 | required: false |
| #15 | RPC_URL: |
| #16 | description: RPC URL for Base mainnet (default https://mainnet.base.org) |
| #17 | required: false |
| #18 | dependencies: |
| #19 | - node >= 18.0.0 |
| #20 | --- |
| #21 | |
| #22 | # OpenSea Tool SDK |
| #23 | |
| #24 | Build, register, and gate AI-callable tool endpoints using the OpenSea Tool Registry (ERC-8257) on Base. |
| #25 | |
| #26 | ## When to use this skill (`scope_in`) |
| #27 | |
| #28 | Use `opensea-tool-sdk` when you need to: |
| #29 | |
| #30 | - Scaffold an AI-callable tool endpoint (HTTPS, JSON Schema, `.well-known` manifest) for Vercel, Cloudflare, or Express |
| #31 | - Register a tool onchain on the Base ToolRegistry so other agents can discover it |
| #32 | - Gate access via x402 pay-per-call (USDC) or predicates (ERC-721/ERC-1155 ownership, subscriptions, trait gating, ERC-20 balance, composites) |
| #33 | - Call a gated tool: EIP-3009 auth (`eip3009AuthenticatedFetch`), 402 payments (`paidFetch`), or both (`paidAuthenticatedFetch`) |
| #34 | - Search and discover registered tools via the OpenSea REST API |
| #35 | |
| #36 | ## When NOT to use this skill (`scope_out`, handoff) |
| #37 | |
| #38 | | Need | Use instead | |
| #39 | |---|---| |
| #40 | | Query NFT/token data, search, collection stats | `opensea-api` | |
| #41 | | Buy/sell NFTs | `opensea-marketplace` | |
| #42 | | Swap ERC20 tokens | `opensea-swaps` | |
| #43 | | Set up wallet signing providers | `opensea-wallet` | |
| #44 | |
| #45 | This SDK is for tool *providers and consumers*. To query OpenSea marketplace data (floor prices, listings, trades), use the [`opensea-api`](../opensea-api/SKILL.md) skill instead. |
| #46 | |
| #47 | ## Concepts |
| #48 | |
| #49 | | Term | Meaning | |
| #50 | |------|---------| |
| #51 | | **Tool** | An HTTPS endpoint with a JSON Schema interface, discoverable via `/.well-known/ai-tool/<slug>.json` | |
| #52 | | **Manifest** | JCS-canonicalized JSON describing the tool's name, endpoint, inputs, outputs, pricing, and access policy | |
| #53 | | **ToolRegistry** | Onchain contract (Base) where tools are registered with a manifest hash and optional access predicate | |
| #54 | | **Access Predicate** | An `IAccessPredicate` contract that gates who can invoke a tool (NFT ownership, subscriptions, trait gating, ERC-20 balance, composites) | |
| #55 | | **x402** | HTTP 402-based pay-per-call protocol (caller signs a USDC `TransferWithAuthorization`; server settles after execution) | |
| #56 | | **EIP-3009 auth** | Zero-value USDC `TransferWithAuthorization` signature used to authenticate callers for predicate-gated tools | |
| #57 | | **Facilitator** | Third-party service that verifies and settles x402 payments (PayAI or Coinbase CDP) | |
| #58 | |
| #59 | ## Deployed Contracts (Ethereum mainnet, Base, Shape, Abstract) |
| #60 | |
| #61 | Canonical v0.2 deployments — identical CREATE2 address on every supported chain. |
| #62 | |
| #63 | | Contract | Address | |
| #64 | |----------|---------| |
| #65 | | ToolRegistry (v0.2) | `0x265BB2DBFC0A8165C9A1941Eb1372F349baD2cf1` | |
| #66 | | ERC721OwnerPredicate (v0.2) | `0xc8721c9A776958FfFfEb602DA1b708bf1D318379` | |
| #67 | | ERC1155OwnerPredicate (v0.2) | `0x77373Dc3c1AE9A1e937eF3e5E08F4807D47c7c11` | |
| #68 | | SubscriptionPredicate (v0.2) | `0xCBe0cd9B1d99d95Baa9c58f2767246C52e461f25` | |
| #69 | | TraitGatedPredicate (v0.2) | `0x10abF07CfA34Bf22372C57f27e8bd9C2DCF93fA1` | |
| #70 | | ERC20BalancePredicate (v0.2) | `0x1a834FC48B5f6e119c62C12a98b32137bCFA77cD` | |
| #71 | |
| #72 | ## Tool Discovery [Beta] |
| #73 | |
| #74 | Search or look up registered tools via the OpenSea REST API. Requires `OPENSEA_API_KEY`. |
| #75 | |
| #76 | ```bash |
| #77 | # Reuse OPENSEA_API_KEY if already set; otherwise fetch an instant free-tier key |
| #78 | # (no signup — 60/min read, 5/min write, 30-day expiry) and SAVE it for reuse. |
| #79 | if [ -z "${OPENSEA_API_KEY:-}" ]; then |
| #80 | KEY_FILE="${OPENSEA_CONFIG_DIR:-$HOME/.opensea}/api_key" |
| #81 | if [ -s "$KEY_FILE" ]; then |
| #82 | export OPENSEA_API_KEY=$(cat "$KEY_FILE") # reuse cached key |
| #83 | else |
| #84 | api_key=$(curl -s -X POST https://api.opensea.io/api/v2/auth/keys | jq -r '.api_key') |
| #85 | mkdir -p "$(dirname "$KEY_FILE")" |
| #86 | (umask 077; printf '%s\n' "$api_key" > "$KEY_FILE") # save before using it |
| #87 | export OPENSEA_API_KEY="$api_key" |
| #88 | fi |
| #89 | fi |
| #90 | ``` |
| #91 | |
| #92 | Instant key creation is rate limited per IP, so always save a fetched key and |
| #93 | reuse it rather than re-fetching. The `opensea-api` skill ships an |
| #94 | `auth/opensea-resolve-key.sh` helper that does this (env → cached file → fetch + |
| #95 | save); see its "API key resolution" section. For higher rate limits, create a |
| #96 | full key at [Settings → Developer](https://docs.opensea.io/reference/api-keys). |
| #97 | |
| #98 | **List tools:** `GET /api/v2/tools` ([docs](https://docs.opensea.io/reference/list_tools)) |
| #99 | |
| #100 | | Parameter | Required | Description | |
| #101 | |-----------|----------|-------------| |
| #102 | | `sort_by` | No | Sort by: `newest` (default), `oldest` | |
| #103 | | `type` | No | Filter by access type: `open`, `nft_gated`, `token_gated`, `subscription`, `gated` | |
| #104 | | `limit` | No | Results per page (1–100) | |
| #105 | | `cursor` | No | Pagination cursor | |
| #106 | |
| #107 | **Search tools:** `GET /api/v2/tools/search` ([docs](https://docs.opensea.io/reference/search_tools)) |
| #108 | |
| #109 | | Parameter | Required | Description | |
| #110 | |-----------|----------|-------------| |
| #111 | | `query` | No | Search query text | |
| #112 | | `registry_chain` | No | Filter by registry chain ID | |
| #113 | | `tags` | No | Filter by tags | |
| #114 | | `access_type` | No | Filter by access type: `open`, `nft_gated`, `subscription` | |
| #115 | | `creator` | No | Filter by creator address | |
| #116 | | `sort_by` | No | Sort by: `relevance` (default), `newest`, `most_used` | |
| #117 | | `limit` | No | Results per page (1–200) | |
| #118 | | `cursor.value` | No | Pagination cursor | |
| #119 | |
| #120 | **Get a tool:** `GET /api/v2/tools/{registry_chain}/{registry_addr}/{tool_id}` ([docs](https://docs.opensea.io/reference/get_tool)) |
| #121 | |
| #122 | | Parameter | Required | Description | |
| #123 | |-----------|----------|-------------| |
| #124 | | `registry_chain` | Yes | Registry chain ID (e.g. `1`, `8453`) | |
| #125 | | `registry_addr` | Yes | Registry contract address | |
| #126 | | `tool_id` | Yes | Numeric tool ID | |
| #127 | |
| #128 | ```bash |
| #129 | # List tools sorted by newest |
| #130 | curl -s "https://api.opensea.io/api/v2/tools?sort_by=newest&limit=10" \ |
| #131 | -H "x-api-key: $OPENSEA_API_KEY" | jq |
| #132 | |
| #133 | # List tools filtered by type |
| #134 | curl -s "https://api.opensea.io/api/v2/tools?type=open&sort_by=oldest" \ |
| #135 | -H "x-api-key: $OPENSEA_API_KEY" | jq |
| #136 | |
| #137 | # Search tools by keyword |
| #138 | curl -s "https://api.opensea.io/api/v2/tools/search?query=nft" \ |
| #139 | -H "x-api-key: $OPENSEA_API_KEY" | jq |
| #140 | |
| #141 | # Get a specific tool on Base |
| #142 | curl -s "https://api.opensea.io/api/v2/tools/8453/0x265BB2DBFC0A8165C9A1941Eb1372F349baD2cf1/1" \ |
| #143 | -H "x-api-key: $OPENSEA_API_KEY" | jq |
| #144 | |
| #145 | # Filter by access type |
| #146 | curl -s "https://api.opensea.io/api/v2/tools/search?access_type=open&limit=10" \ |
| #147 | -H "x-api-key: $OPENSEA_API_KEY" | jq |
| #148 | ``` |
| #149 | |
| #150 | ## 1. Create a Tool |
| #151 | |
| #152 | ### 1a. Scaffold a project |
| #153 | |
| #154 | ```bash |
| #155 | npx @opensea/tool-sdk init --runtime vercel # or: cloudflare, express |
| #156 | ``` |
| #157 | |
| #158 | This generates: |
| #159 | - `src/manifest.ts` — tool manifest definition |
| #160 | - `src/handler.ts` — request handler with input/output schemas |
| #161 | - `api/index.ts` — framework adapter entry point |
| #162 | - `public/llms.txt` — agent-readable discovery page |
| #163 | - `api/well-known/[slug].ts` — serves the manifest at `/.well-known/ai-tool/<slug>.json` |
| #164 | |
| #165 | ### 1b. Define the manifest |
| #166 | |
| #167 | ```typescript |
| #168 | import { defineManifest } from "@opensea/tool-sdk" |
| #169 | |
| #170 | export const manifest = defineManifest({ |
| #171 | name: "My Tool", |
| #172 | description: "What this tool does", |
| #173 | endpoint: "https://my-tool.example.com/api", |
| #174 | creatorAddress: "0xYOUR_WALLET_ADDRESS", |
| #175 | inputs: { |
| #176 | type: "object", |
| #177 | properties: { |
| #178 | query: { type: "string", description: "Search query" }, |
| #179 | }, |
| #180 | required: ["query"], |
| #181 | }, |
| #182 | outputs: { |
| #183 | type: "object", |
| #184 | properties: { |
| #185 | result: { type: "string" }, |
| #186 | }, |
| #187 | }, |
| #188 | // Optional: add pricing for x402 paywall (see references/x402.md) |
| #189 | // pricing: paywall.pricing, |
| #190 | // Optional: add access requirements (see references/predicate-gating.md) |
| #191 | // access: { logic: "OR", requirements: [...] }, |
| #192 | }) |
| #193 | ``` |
| #194 | |
| #195 | ### 1c. Write the handler |
| #196 | |
| #197 | ```typescript |
| #198 | import { createToolHandler } from "@opensea/tool-sdk" |
| #199 | import { z } from "zod/v4" |
| #200 | import { manifest } from "./manifest.js" |
| #201 | |
| #202 | const InputSchema = z.object({ query: z.string() }) |
| #203 | const OutputSchema = z.object({ result: z.string() }) |
| #204 | |
| #205 | export const toolHandler = createToolHandler({ |
| #206 | manifest, |
| #207 | inputSchema: InputSchema, |
| #208 | outputSchema: OutputSchema, |
| #209 | // gates: [], // Add gates here (see references/x402.md and references/predicate-gating.md) |
| #210 | handler: async (input) => { |
| #211 | return { result: `Processed: ${input.query}` } |
| #212 | }, |
| #213 | }) |
| #214 | ``` |
| #215 | |
| #216 | ### 1d. Wire up the adapter |
| #217 | |
| #218 | **Vercel:** |
| #219 | ```typescript |
| #220 | import { toVercelHandler } from "@opensea/tool-sdk" |
| #221 | import { toolHandler } from "../src/handler.js" |
| #222 | export default toVercelHandler(toolHandler) |
| #223 | ``` |
| #224 | |
| #225 | **Express:** |
| #226 | ```typescript |
| #227 | import { toExpressHandler } from "@opensea/tool-sdk" |
| #228 | import { toolHandler } from "./handler.js" |
| #229 | app.post("/api", toExpressHandler(toolHandler)) |
| #230 | ``` |
| #231 | |
| #232 | **Cloudflare Workers:** |
| #233 | ```typescript |
| #234 | import { toolHandler } from "./handler.js" |
| #235 | export default { fetch: toolHandler } |
| #236 | ``` |
| #237 | |
| #238 | ## 2. Register a Tool Onchain |
| #239 | |
| #240 | ### 2a. Via CLI |
| #241 | |
| #242 | ```bash |
| #243 | # Set up wallet |
| #244 | export PRIVATE_KEY=0x... |
| #245 | export RPC_URL=https://mainnet.base.org |
| #246 | |
| #247 | # Register (open access — no predicate) |
| #248 | npx @opensea/tool-sdk register \ |
| #249 | --metadata https://my-tool.example.com/.well-known/ai-tool/my-tool.json \ |
| #250 | --network base |
| #251 | |
| #252 | # Register with an access predicate |
| #253 | npx @opensea/tool-sdk register \ |
| #254 | --metadata https://my-tool.example.com/.well-known/ai-tool/my-tool.json \ |
| #255 | --network base \ |
| #256 | --access-predicate 0xPREDICATE_ADDRESS |
| #257 | |
| #258 | # Dry run (no transaction) |
| #259 | npx @opensea/tool-sdk register --metadata ... --network base --dry-run |
| #260 | ``` |
| #261 | |
| #262 | The CLI: |
| #263 | 1. Fetches the manifest from `--metadata` URL |
| #264 | 2. Validates the manifest schema |
| #265 | 3. Verifies `manifest.creatorAddress` matches your wallet |
| #266 | 4. Computes the JCS keccak256 manifest hash |
| #267 | 5. Calls `ToolRegistry.registerTool(metadataURI, manifestHash, accessPredicate)` |
| #268 | 6. Returns the `toolId` from the `ToolRegistered` event |
| #269 | |
| #270 | ### 2b. Via SDK (programmatic) |
| #271 | |
| #272 | ```typescript |
| #273 | import { ToolRegistryClient, computeManifestHash } from "@opensea/tool-sdk" |
| #274 | import { createWalletFromEnv, walletAdapterToClient } from "@opensea/tool-sdk" |
| #275 | import { base } from "viem/chains" |
| #276 | |
| #277 | const adapter = createWalletFromEnv() |
| #278 | const walletClient = await walletAdapterToClient(adapter, base) |
| #279 | |
| #280 | const registry = new ToolRegistryClient({ |
| #281 | chain: base, |
| #282 | rpcUrl: "https://mainnet.base.org", |
| #283 | walletClient, |
| #284 | }) |
| #285 | |
| #286 | const { toolId, txHash } = await registry.registerTool({ |
| #287 | metadataURI: "https://my-tool.example.com/.well-known/ai-tool/my-tool.json", |
| #288 | manifest, // your ToolManifest object |
| #289 | accessPredicate: "0x0000...0000", // address(0) = open access |
| #290 | }) |
| #291 | |
| #292 | console.log(`Registered tool ${toolId} in tx ${txHash}`) |
| #293 | ``` |
| #294 | |
| #295 | ## 3. Gating tool access |
| #296 | |
| #297 | Tools can be gated three ways: |
| #298 | |
| #299 | | Gate | Mechanism | Reference | |
| #300 | |------|-----------|-----------| |
| #301 | | **x402 paywall** | Pay-per-call (USDC, EIP-3009) | [`references/x402.md`](references/x402.md) | |
| #302 | | **Predicate gate** | Onchain check (NFT, subscription, trait gating, ERC-20 balance, composite) | [`references/predicate-gating.md`](references/predicate-gating.md) | |
| #303 | | **Combined** | EIP-3009 auth and payment (predicate first, then x402) | [`references/predicate-gating.md`](references/predicate-gating.md) | |
| #304 | |
| #305 | For deployed predicate addresses, requirement encodings, and SDK helpers like `describeToolAccess` / `decodeRequirement`, see [`references/known-predicates.md`](references/known-predicates.md). |
| #306 | |
| #307 | ## 4. Wallet Setup |
| #308 | |
| #309 | The SDK supports multiple wallet providers via `@opensea/wallet-adapters`. Set environment variables and the SDK auto-detects the provider. See the [`opensea-wallet`](../opensea-wallet/SKILL.md) skill for the full provider table, env vars, setup walkthroughs, and signing-policy configuration. |
| #310 | |
| #311 | | Provider | Env vars | Best for | |
| #312 | |----------|----------|----------| |
| #313 | | Private Key | `PRIVATE_KEY`, `RPC_URL` | Local dev, scripts | |
| #314 | | Privy | `PRIVY_APP_ID`, `PRIVY_APP_SECRET`, `PRIVY_WALLET_ID` | Server wallets | |
| #315 | | Turnkey | `TURNKEY_API_PUBLIC_KEY`, `TURNKEY_API_PRIVATE_KEY`, `TURNKEY_ORGANIZATION_ID` | Enterprise signing | |
| #316 | | Fireblocks | `FIREBLOCKS_API_KEY`, `FIREBLOCKS_API_SECRET`, `FIREBLOCKS_VAULT_ACCOUNT_ID` | Institutional custody | |
| #317 | | Bankr | `BANKR_API_KEY` | Agent wallets (via HTTP API) | |
| #318 | |
| #319 | ```typescript |
| #320 | import { createWalletFromEnv } from "@opensea/tool-sdk" |
| #321 | |
| #322 | // Auto-detects: Privy > Fireblocks > Turnkey > Bankr > PrivateKey |
| #323 | const adapter = createWalletFromEnv() |
| #324 | const address = await adapter.getAddress() |
| #325 | ``` |
| #326 | |
| #327 | For Bankr (external signer): |
| #328 | |
| #329 | ```typescript |
| #330 | import { createBankrAccount } from "@opensea/tool-sdk" |
| #331 | |
| #332 | const account = await createBankrAccount("your-bankr-api-key") |
| #333 | // Use with eip3009AuthenticatedFetch or paidAuthenticatedFetch |
| #334 | ``` |
| #335 | |
| #336 | ## 5. Response Codes |
| #337 | |
| #338 | | Code | Meaning | Action | |
| #339 | |------|---------|--------| |
| #340 | | 200 | Success | Parse the JSON body per the manifest's `outputs` schema | |
| #341 | | 400 | Invalid input | Fix request body to match the manifest's `inputs` schema | |
| #342 | | 401 | Missing/invalid auth | Sign an EIP-3009 zero-value authorization and include `Authorization: EIP-3009 <token>` | |
| #343 | | 402 | Payment required | Read `body.accepts[0]` for payment requirements, sign and retry with `X-Payment` | |
| #344 | | 403 | Access denied | Inspect `body.predicate` to discover what's needed; acquire the required token/subscription | |
| #345 | | 405 | Method not allowed | Use POST | |
| #346 | | 500 | Internal tool error | Retry or contact the tool creator | |
| #347 | | 502 | Predicate/facilitator error | The upstream predicate or payment facilitator misbehaved; retry later | |
| #348 | |
| #349 | ## 6. Quick Reference: CLI Commands |
| #350 | |
| #351 | | Command | Purpose | |
| #352 | |---------|---------| |
| #353 | | `init` | Scaffold a new tool project | |
| #354 | | `validate` | Validate a manifest file | |
| #355 | | `hash` | Compute the JCS keccak256 hash of a manifest | |
| #356 | | `export` | Export the manifest as JSON | |
| #357 | | `register` | Register a tool onchain. Supports `--nft-gate`, `--erc20-gate` + `--erc20-min-balance`, or `--predicate-config` to bundle predicate setup with registration | |
| #358 | | `update-metadata` | Update a tool's metadata URI and manifest hash onchain | |
| #359 | | `inspect` | Look up a tool's onchain config by ID | |
| #360 | | `verify` | Verify a manifest against its onchain hash | |
| #361 | | `deploy` | Deploy a tool to Vercel | |
| #362 | | `auth` | Call a predicate-gated tool (EIP-3009) | |
| #363 | | `pay` | Call an x402-paid tool (USDC), with optional `--auth` for predicate-gated endpoints | |
| #364 | | `smoke` | Auto-detect gate type and call | |
| #365 | | `dry-run-gate` | Simulate an x402 gate check locally | |
| #366 | | `dry-run-predicate-gate` | Simulate a predicate gate check locally | |
| #367 | | `set-collections` | Set ERC-721 collection gate list for a tool | |
| #368 | | `get-collections` | Read ERC-721 collection gate list for a tool | |
| #369 | | `set-collection-tokens` | Set ERC-1155 collection + token ID gate for a tool | |
| #370 | | `configure-subscription` | Configure SubscriptionPredicate gate (collection + minTier) for a tool | |
| #371 | | `configure-trait-gating` | Configure TraitGatedPredicate gate (collection, traits contract, trait key, allowed values) for a tool | |
| #372 | | `get-trait-config` | Read trait gating configuration for a tool | |
| #373 | | `configure-erc20-gate` | Configure ERC20BalancePredicate gate (token, minBalance) for a tool | |
| #374 | | `get-erc20-config` | Read ERC-20 balance gating configuration for a tool | |
| #375 | |
| #376 | All CLI commands accept `--wallet-provider privy|turnkey|fireblocks|private-key` or auto-detect from env vars. |
| #377 | |
| #378 | ## 7. Usage Tracking |
| #379 | |
| #380 | Tool-sdk supports usage tracking via the `onInvocation` callback on `createToolHandler`. This fires after every successful invocation (post-settle, pre-response) with an `InvocationEvent` containing caller identity, payment status, and timing. |
| #381 | |
| #382 | ### createEip3009UsageReporter (recommended) |
| #383 | |
| #384 | `createEip3009UsageReporter` is the recommended `onInvocation` implementation. It reports tool usage via EIP-3009 zero-value `TransferWithAuthorization` signatures: |
| #385 | |
| #386 | - **Free / gated calls**: signs a zero-value authorization proving the operator controls the wallet, and POSTs with `verification_type: "eip3009_authorization"`. |
| #387 | - **Paid x402 calls**: POSTs with `verification_type: "x402_settlement"` and the settlement tx hash — no additional signature needed. |
| #388 | |
| #389 | ```typescript |
| #390 | import { createToolHandler, createEip3009UsageReporter } from "@opensea/tool-sdk" |
| #391 | import { createWalletClient, http } from "viem" |
| #392 | import { privateKeyToAccount } from "viem/accounts" |
| #393 | import { base } from "viem/chains" |
| #394 | |
| #395 | const walletClient = createWalletClient({ |
| #396 | account: privateKeyToAccount("0x..."), |
| #397 | chain: base, |
| #398 | transport: http(), |
| #399 | }) |
| #400 | |
| #401 | export const toolHandler = createToolHandler({ |
| #402 | manifest, |
| #403 | inputSchema: InputSchema, |
| #404 | outputSchema: OutputSchema, |
| #405 | onInvocation: createEip3009UsageReporter({ |
| #406 | walletClient, |
| #407 | chainId: 8453, |
| #408 | // optional: aggregatorUrl, tokenAddress, toolSlug, timeoutMs |
| #409 | }), |
| #410 | handler: async (input) => { |
| #411 | return { result: `Processed: ${input.query}` } |
| #412 | }, |
| #413 | }) |
| #414 | ``` |
| #415 | |
| #416 | ### onInvocation callback |
| #417 | |
| #418 | You can also provide a custom `onInvocation` callback for bespoke analytics: |
| #419 | |
| #420 | ```typescript |
| #421 | import { createToolHandler } from "@opensea/tool-sdk" |
| #422 | import type { InvocationEvent } from "@opensea/tool-sdk" |
| #423 | |
| #424 | export const toolHandler = createToolHandler({ |
| #425 | manifest, |
| #426 | inputSchema: InputSchema, |
| #427 | outputSchema: OutputSchema, |
| #428 | onInvocation: (event: InvocationEvent) => { |
| #429 | // event.callerAddress — verified caller wallet |
| #430 | // event.paid — whether x402 payment settled |
| #431 | // event.toolName — resolved tool name from manifest |
| #432 | // event.latencyMs — handler execution time |
| #433 | // event.timestamp — invocation timestamp |
| #434 | }, |
| #435 | handler: async (input) => { |
| #436 | return { result: `Processed: ${input.query}` } |
| #437 | }, |
| #438 | }) |
| #439 | ``` |
| #440 | |
| #441 | ## 8. End-to-End Examples |
| #442 | |
| #443 | ### Example A: Free open-access tool |
| #444 | |
| #445 | ```bash |
| #446 | # 1. Scaffold |
| #447 | npx @opensea/tool-sdk init --runtime vercel |
| #448 | # 2. Edit src/manifest.ts and src/handler.ts with your logic |
| #449 | # 3. Deploy |
| #450 | npx @opensea/tool-sdk deploy |
| #451 | # 4. Register (open access) |
| #452 | PRIVATE_KEY=0x... npx @opensea/tool-sdk register \ |
| #453 | --metadata https://my-tool.vercel.app/.well-known/ai-tool/my-tool.json \ |
| #454 | --network base |
| #455 | # 5. Call |
| #456 | curl -X POST https://my-tool.vercel.app/api \ |
| #457 | -H "Content-Type: application/json" \ |
| #458 | -d '{"query": "hello"}' |
| #459 | ``` |
| #460 | |
| #461 | ### Example B: x402 paid tool (pay-per-call only, no identity check) |
| #462 | |
| #463 | ```bash |
| #464 | # Server: add paywall gate (see references/x402.md) |
| #465 | # Call via CLI: |
| #466 | PRIVATE_KEY=0x... npx @opensea/tool-sdk pay \ |
| #467 | https://my-tool.vercel.app/api \ |
| #468 | --body '{"query": "hello"}' |
| #469 | ``` |
| #470 | |
| #471 | ### Example C: NFT-gated tool (identity check, no payment) |
| #472 | |
| #473 | ```bash |
| #474 | # Register with ERC721OwnerPredicate |
| #475 | PRIVATE_KEY=0x... npx @opensea/tool-sdk register \ |
| #476 | --metadata https://my-tool.vercel.app/.well-known/ai-tool/my-tool.json \ |
| #477 | --network base \ |
| #478 | --nft-gate 0xYOUR_COLLECTION_ADDRESS |
| #479 | |
| #480 | # Configure which collection(s) gate the tool (if not using --nft-gate): |
| #481 | npx @opensea/tool-sdk set-collections <TOOL_ID> 0xYOUR_COLLECTION_ADDRESS \ |
| #482 | --network base |
| #483 | |
| #484 | # Server: add predicateGate (see references/predicate-gating.md) |
| #485 | |
| #486 | # Call via CLI: |
| #487 | PRIVATE_KEY=0x... RPC_URL=https://mainnet.base.org \ |
| #488 | npx @opensea/tool-sdk auth \ |
| #489 | https://my-tool.vercel.app/api \ |
| #490 | --body '{"query": "hello"}' |
| #491 | ``` |
| #492 | |
| #493 | ### Example D: Subscription-gated tool |
| #494 | |
| #495 | ```bash |
| #496 | # Register with SubscriptionPredicate and configure in one shot: |
| #497 | PRIVATE_KEY=0x... npx @opensea/tool-sdk register \ |
| #498 | --metadata https://my-tool.vercel.app/.well-known/ai-tool/my-tool.json \ |
| #499 | --access-predicate 0xCBe0cd9B1d99d95Baa9c58f2767246C52e461f25 \ |
| #500 | --predicate-config '{"collection":"0xYOUR_SUBSCRIPTION_NFT","minTier":0}' \ |
| #501 | --network base |
| #502 | |
| #503 | # Or configure after registration: |
| #504 | npx @opensea/tool-sdk configure-subscription <TOOL_ID> 0xYOUR_SUBSCRIPTION_NFT \ |
| #505 | --min-tier 0 --network base |
| #506 | |
| #507 | # Call via CLI: |
| #508 | PRIVATE_KEY=0x... RPC_URL=https://mainnet.base.org \ |
| #509 | npx @opensea/tool-sdk auth \ |
| #510 | https://my-tool.vercel.app/api \ |
| #511 | --body '{"query": "hello"}' |
| #512 | ``` |
| #513 | |
| #514 | ### Example E: ERC-20 balance-gated tool |
| #515 | |
| #516 | ```bash |
| #517 | # Register with ERC20BalancePredicate and configure in one shot: |
| #518 | PRIVATE_KEY=0x... npx @opensea/tool-sdk register \ |
| #519 | --metadata https://my-tool.vercel.app/.well-known/ai-tool/my-tool.json \ |
| #520 | --network base \ |
| #521 | --erc20-gate 0xTOKEN_ADDRESS --erc20-min-balance 1000000000000000000 |
| #522 | |
| #523 | # Or configure after registration: |
| #524 | npx @opensea/tool-sdk configure-erc20-gate <TOOL_ID> 0xTOKEN_ADDRESS 1000000000000000000 \ |
| #525 | --network base |
| #526 | |
| #527 | # Call via CLI: |
| #528 | PRIVATE_KEY=0x... RPC_URL=https://mainnet.base.org \ |
| #529 | npx @opensea/tool-sdk auth \ |
| #530 | https://my-tool.vercel.app/api \ |
| #531 | --body '{"query": "hello"}' |
| #532 | ``` |
| #533 | |
| #534 | ### Example F: NFT-gated + paid tool (both gates) |
| #535 | |
| #536 | ```bash |
| #537 | # Server: add both predicateGate and paywall.gate (see references/predicate-gating.md) |
| #538 | # Call via CLI: |
| #539 | PRIVATE_KEY=0x... RPC_URL=https://mainnet.base.org \ |
| #540 | npx @opensea/tool-sdk pay --auth \ |
| #541 | https://my-tool.vercel.app/api \ |
| #542 | --body '{"query": "hello"}' |
| #543 | ``` |
| #544 | |
| #545 | ## References |
| #546 | |
| #547 | - [`references/x402.md`](references/x402.md): pay-per-call protocol, server-side paywall, `paidFetch` |
| #548 | - [`references/predicate-gating.md`](references/predicate-gating.md): EIP-3009-based access control, combined gates |
| #549 | - [`references/known-predicates.md`](references/known-predicates.md): deployed predicate contracts and SDK helpers |
| #550 | - [Tool SDK GitHub](https://github.com/ProjectOpenSea/tool-sdk) |
| #551 |