Skip to content

Commit 37db12e

Browse files
CodySearsOSryanio
andcommitted
Release v0.28.0
Origin-SHA: bc74e084cd87ab22021450b305a106763c153794 Co-authored-by: Ryan Ghods <ryan@ryanio.com>
1 parent 7b6b5ec commit 37db12e

30 files changed

Lines changed: 283 additions & 122 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# @opensea/tool-sdk
22

3+
## 0.28.0
4+
5+
### Minor Changes
6+
7+
- f1db520: Add Robinhood Chain (4663) support: `--network robinhood` on all CLI commands (custom viem chain definition until viem ships one), 4663 added to every canonical `Deployment.chains` array in `chains.ts`, and skill/docs chain enumerations updated.
8+
9+
### Patch Changes
10+
11+
- ac4ac6f: tool-registry: add an explicit Etherscan v2 `url` to the `monad` entry in `foundry.toml`. Forge's built-in chain list doesn't know chain 143, so config resolution failed during `forge script --verify` and aborted verification for every chain in the run (including Robinhood).
12+
- 10dcac1: Fix x402 CAIP-2 network handling asymmetry. The server-side predicate gates now accept CAIP-2 (`eip155:8453`) and numeric (`8453`) network identifiers in the `X-Payment` payload — previously they only accepted the short names (`base`, `base-sepolia`) and rejected anything else as an "unsupported network", even though the client already tolerated CAIP-2 forms. Additionally, the client now normalizes the `network` field in v1 payment payloads to the canonical short name so that v1 gates (which key off short names) don't reject a verbatim CAIP-2 echo.
13+
- 6649847: Fix x402 usage reporting when the facilitator returns a CAIP-2/numeric network. The predicate gate's `settle()` now resolves the settlement network via `resolveNetwork`, so `settlementChainId` (used by the usage-reporting REST endpoint) is populated for `eip155:8453` / `8453` responses, not just the short name `base`.
14+
315
## 0.27.0
416

517
### Minor Changes

biome.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
{
22
"$schema": "node_modules/@biomejs/biome/configuration_schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
38
"files": {
49
"includes": [
510
"**/*.ts",
611
"**/*.js",
712
"**/*.json",
8-
"!**/node_modules",
9-
"!**/dist",
10-
"!**/lib",
11-
"!**/coverage",
12-
"!**/.nyc_output",
13-
"!packages/sdk/src/typechain",
13+
"!**/.claude",
1414
"!**/typechain",
15-
"!packages/api-types/src/generated.ts",
16-
"!**/pnpm-lock.yaml",
17-
"!**/package-lock.json"
15+
"!**/package-lock.json",
16+
"!**/src/generated.ts",
17+
"!**/src/*-generated.ts"
1818
]
1919
},
2020
"formatter": {
@@ -35,7 +35,7 @@
3535
"linter": {
3636
"enabled": true,
3737
"rules": {
38-
"recommended": true,
38+
"preset": "recommended",
3939
"suspicious": {
4040
"noConsole": "off",
4141
"noExplicitAny": "off"

docs/predicate-gating-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Use predicate gating when access should be tied to **who the caller is**. Use x4
2727

2828
Tool creators configure the predicate onchain (via `register --access-predicate` or direct contract calls). The `predicateGate` middleware picks it up at runtime — no code changes needed when the access policy changes.
2929

30-
The canonical `ERC721OwnerPredicate` (v0.2) is deployed on Ethereum mainnet, Base, Shape, Abstract, and Monad at `0xc8721c9A776958FfFfEb602DA1b708bf1D318379` (see `src/lib/onchain/chains.ts`).
30+
The canonical `ERC721OwnerPredicate` (v0.2) is deployed on Ethereum mainnet, Base, Shape, Abstract, Monad, and Robinhood Chain at `0xc8721c9A776958FfFfEb602DA1b708bf1D318379` (see `src/lib/onchain/chains.ts`).
3131

3232
## Prerequisites
3333

@@ -129,7 +129,7 @@ PRIVATE_KEY=0x... RPC_URL=https://mainnet.base.org npx @opensea/tool-sdk registe
129129
--dry-run
130130
```
131131

132-
For ERC-721 gating, use the canonical v0.2 `ERC721OwnerPredicate` at `0xc8721c9A776958FfFfEb602DA1b708bf1D318379` (Ethereum mainnet, Base, Shape, Abstract, Monad). After registration, call `setCollections(toolId, [collectionAddress])` on the predicate to configure which collections gate the tool.
132+
For ERC-721 gating, use the canonical v0.2 `ERC721OwnerPredicate` at `0xc8721c9A776958FfFfEb602DA1b708bf1D318379` (Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain). After registration, call `setCollections(toolId, [collectionAddress])` on the predicate to configure which collections gate the tool.
133133

134134
## Step 3: Verify the setup
135135

examples/nft-appraisal-tool/scripts/read-tool-registration.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ try {
6363
const res = await fetch(config.metadataURI)
6464
if (!res.ok) throw new Error(`fetch returned ${res.status}`)
6565
liveManifest = await res.json()
66-
// biome-ignore lint/suspicious/noExplicitAny: SDK validates shape internally
6766
const liveHash = computeManifestHash(liveManifest as any)
6867
checks.push([
6968
"manifestHash matches live manifest hash",

examples/wallet-personality-tool/src/opensea.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ function ethValueFromPayment(payment?: {
286286
decimals?: number
287287
quantity?: string
288288
}): { ethValue?: number; symbol?: string } {
289-
if (!payment || !payment.quantity) return {}
289+
if (!payment?.quantity) return {}
290290
const decimals = payment.decimals ?? 18
291291
const symbol = payment.symbol ?? "ETH"
292292
// Only treat ETH/WETH as eth-equivalent. Stable-priced sales (USDC etc.)

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/tool-sdk",
3-
"version": "0.27.0",
3+
"version": "0.28.0",
44
"type": "module",
55
"description": "SDK and CLI for building ERC-8257 compliant AI agent tools",
66
"repository": {
@@ -32,10 +32,10 @@
3232
"build": "tsup",
3333
"dev": "tsup --watch",
3434
"test": "vitest run",
35-
"lint": "biome check --config-path . .",
36-
"lint:fix": "biome check --config-path . --write .",
37-
"format": "biome format --config-path . --write .",
38-
"format:check": "biome format --config-path . .",
35+
"lint": "biome check .",
36+
"lint:fix": "biome check --write .",
37+
"format": "biome format --write .",
38+
"format:check": "biome format .",
3939
"type-check": "tsc --noEmit"
4040
},
4141
"dependencies": {
@@ -55,7 +55,7 @@
5555
"tsup": "8.5.1",
5656
"typescript": "6.0.2",
5757
"vitest": "3.2.4",
58-
"@biomejs/biome": "^2.4.10"
58+
"@biomejs/biome": "^2.5.2"
5959
},
6060
"engines": {
6161
"node": ">=18.0.0"

skill/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ This SDK is for tool *providers and consumers*. To query OpenSea marketplace dat
6767

6868
If you need your API on a subdomain, serve the manifest from that same subdomain (e.g., both on `api.example.com`).
6969

70-
## Deployed Contracts (Ethereum mainnet, Base, Shape, Abstract, Monad)
70+
## Deployed Contracts (Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain)
7171

7272
Canonical v0.2 deployments — identical CREATE2 address on every supported chain.
7373

skill/references/known-predicates.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Known Predicates
22

3-
These predicates are deployed on Ethereum mainnet, Base, Shape, Abstract, and Monad and available for any tool to use. They are multi-tenant: one deployment serves all tools, configured per `toolId`.
3+
These predicates are deployed on Ethereum mainnet, Base, Shape, Abstract, Monad, and Robinhood Chain and available for any tool to use. They are multi-tenant: one deployment serves all tools, configured per `toolId`.
44

55
## ERC721OwnerPredicate
66

@@ -283,7 +283,7 @@ if (decoded.type === "erc7496Trait") {
283283

284284
## ERC20BalancePredicate
285285

286-
Gates access based on holding a configurable minimum balance of an ERC-20 token. Multi-tenant: one deployment per chain, configured per `toolId`. Canonical deployment: `0x1a834FC48B5f6e119c62C12a98b32137bCFA77cD` (Ethereum mainnet, Base, Shape, Abstract, Monad). The CLI commands default to this address; pass `--predicate-address` only to override it.
286+
Gates access based on holding a configurable minimum balance of an ERC-20 token. Multi-tenant: one deployment per chain, configured per `toolId`. Canonical deployment: `0x1a834FC48B5f6e119c62C12a98b32137bCFA77cD` (Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain). The CLI commands default to this address; pass `--predicate-address` only to override it.
287287

288288
| Field | Value |
289289
|-------|-------|

src/__tests__/get-chain.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, expect, it } from "vitest"
2+
import { defaultRpcUrl, getChain } from "../cli/commands/get-chain.js"
3+
4+
describe("getChain", () => {
5+
it.each([
6+
["mainnet", 1],
7+
["base", 8453],
8+
["base-sepolia", 84532],
9+
["shape", 360],
10+
["abstract", 2741],
11+
["monad", 143],
12+
["robinhood", 4663],
13+
])("returns the %s chain with id %i", (network, chainId) => {
14+
expect(getChain(network).id).toBe(chainId)
15+
})
16+
17+
it("throws on an unsupported network", () => {
18+
expect(() => getChain("dogechain")).toThrow(
19+
"Unsupported network: dogechain",
20+
)
21+
})
22+
23+
it("defines robinhood with an RPC URL, explorer, and multicall3", () => {
24+
const chain = getChain("robinhood")
25+
expect(defaultRpcUrl(chain)).toBe("https://rpc.mainnet.chain.robinhood.com")
26+
expect(chain.blockExplorers?.default.url).toBe(
27+
"https://robinhoodchain.blockscout.com",
28+
)
29+
expect(chain.contracts?.multicall3?.address).toBe(
30+
"0xcA11bde05977b3631167028862bE2a173976CA11",
31+
)
32+
})
33+
})

src/__tests__/paid-predicate-gate.test.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ vi.mock("../lib/onchain/registry.js", () => ({
2020
ToolRegistryClient: class {
2121
tryHasAccess = mockTryHasAccess
2222
getToolConfig = mockGetToolConfig
23-
constructor() {}
2423
},
2524
}))
2625

@@ -93,13 +92,13 @@ function mockFacilitatorVerifySuccess() {
9392
)
9493
}
9594

96-
function mockFacilitatorSettleSuccess() {
95+
function mockFacilitatorSettleSuccess(network = "base") {
9796
mockFetch.mockResolvedValueOnce(
9897
new Response(
9998
JSON.stringify({
10099
success: true,
101100
transaction: "0xtxhash123",
102-
network: "base",
101+
network,
103102
}),
104103
{ status: 200 },
105104
),
@@ -292,6 +291,34 @@ describe("paidPredicateGate", () => {
292291
expect(settleCall[0]).toContain("/settle")
293292
})
294293

294+
it("resolves settlementChainId from a CAIP-2 facilitator network", async () => {
295+
const { paidPredicateGate } = await import(
296+
"../lib/middleware/predicate-gate.js"
297+
)
298+
mockFacilitatorVerifySuccess()
299+
const gate = paidPredicateGate({
300+
toolId: TEST_TOOL_ID,
301+
operatorAddress: TEST_OPERATOR,
302+
amountUsdc: "1.00",
303+
})
304+
const ctx: Partial<ToolContext> = { gates: {} }
305+
const request = new Request("https://example.com/api", {
306+
method: "POST",
307+
headers: { "X-Payment": makeXPaymentHeader() },
308+
})
309+
310+
const response = await gate.check(request, ctx)
311+
expect(response).toBeNull()
312+
313+
// Facilitator reports the CAIP-2 network form; the usage-reporting
314+
// chain id must still resolve so downstream reporters get a numeric id.
315+
mockFacilitatorSettleSuccess("eip155:8453")
316+
await gate.settle!(ctx as ToolContext)
317+
318+
expect(ctx.gates?.x402?.settlementTxHash).toBe("0xtxhash123")
319+
expect(ctx.gates?.x402?.settlementChainId).toBe(8453)
320+
})
321+
295322
it("returns 502 when registry call fails", async () => {
296323
const { paidPredicateGate } = await import(
297324
"../lib/middleware/predicate-gate.js"

0 commit comments

Comments
 (0)