Skip to content

Commit 4e536f1

Browse files
committed
Release v0.17.1
Origin-SHA: 79f4a52d1d8639104c612776a2b17f839fb9a992
1 parent 7ab6900 commit 4e536f1

12 files changed

Lines changed: 136 additions & 10 deletions

File tree

CHANGELOG.md

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

3+
## 0.17.1
4+
5+
### Patch Changes
6+
7+
- Thread `--rpc-url` through to registry reads in CLI commands. Previously the flag only applied to writes; read paths (status, get, list) fell back to the default RPC endpoint.
8+
39
## 0.17.0
410

511
### Minor Changes
62.7 KB
Loading

examples/nft-appraisal-tool/src/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export function buildHolderManifest(opts: BuildManifestOptions) {
9595
name: "nft-appraiser-chonks",
9696
description: `${APPRAISAL_DESCRIPTION} Discounted tier for CHONK holders on Base.`,
9797
endpoint: opts.endpoint,
98+
image: "https://nft-appraisal-tool.vercel.app/chonk.png",
9899
inputs: APPRAISAL_INPUTS,
99100
outputs: appraisalJsonSchema,
100101
creatorAddress: opts.creator,
34.1 KB
Loading

examples/token-nft-overlap-tool/src/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function buildManifest(opts: BuildManifestOptions) {
2424
"Finds wallets that hold both a specified ERC-20 token and NFTs from a collection. " +
2525
"Returns the overlap set with holdings data for each wallet. Free for holders.",
2626
endpoint: opts.endpoint,
27+
image: "https://token-nft-overlap-tool.vercel.app/token-nft-overlap.png",
2728
inputs: {
2829
type: "object",
2930
properties: {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/tool-sdk",
3-
"version": "0.17.0",
3+
"version": "0.17.1",
44
"type": "module",
55
"description": "SDK and CLI for building ERC-8257 compliant AI agent tools",
66
"repository": {

src/__tests__/inspect.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ const mockGetToolConfig = vi.fn(async () => ({
2323

2424
const mockTryHasAccess = vi.fn(async () => ({ ok: true, granted: true }))
2525

26+
const registryCtorConfigs: Record<string, unknown>[] = []
27+
2628
vi.mock("../lib/onchain/registry.js", () => ({
2729
ToolRegistryClient: class {
30+
constructor(config: Record<string, unknown>) {
31+
registryCtorConfigs.push(config)
32+
}
2833
getToolConfig = mockGetToolConfig
2934
tryHasAccess = mockTryHasAccess
3035
},
@@ -47,6 +52,7 @@ afterEach(() => {
4752
mockGetToolConfig.mockClear()
4853
mockTryHasAccess.mockClear()
4954
mockReadContract.mockReset()
55+
registryCtorConfigs.length = 0
5056
})
5157

5258
describe("inspect command", () => {
@@ -75,6 +81,34 @@ describe("inspect command", () => {
7581
logSpy.mockRestore()
7682
})
7783

84+
it("threads --rpc-url through to the registry client", async () => {
85+
vi.stubGlobal(
86+
"fetch",
87+
vi.fn(
88+
async () =>
89+
new Response(JSON.stringify(VALID_MANIFEST), { status: 200 }),
90+
),
91+
)
92+
93+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
94+
95+
const { inspectCommand } = await import("../cli/commands/inspect.js")
96+
97+
await inspectCommand.parseAsync([
98+
"node",
99+
"inspect",
100+
"--tool-id",
101+
"1",
102+
"--rpc-url",
103+
"http://localhost:9999",
104+
])
105+
106+
expect(registryCtorConfigs).toHaveLength(1)
107+
expect(registryCtorConfigs[0].rpcUrl).toBe("http://localhost:9999")
108+
109+
logSpy.mockRestore()
110+
})
111+
78112
it("reports MISMATCH when computed hash differs from onchain hash", async () => {
79113
mockGetToolConfig.mockResolvedValueOnce({
80114
creator: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",

src/__tests__/register.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ class ExitError extends Error {
2929
}
3030
}
3131

32+
const registryCtorConfigs: Record<string, unknown>[] = []
33+
const mockRegisterTool = vi.fn(async () => ({
34+
toolId: 7n,
35+
txHash: "0xtxhash",
36+
}))
37+
38+
vi.mock("../lib/onchain/registry.js", () => ({
39+
ToolRegistryClient: class {
40+
constructor(config: Record<string, unknown>) {
41+
registryCtorConfigs.push(config)
42+
}
43+
registerTool = mockRegisterTool
44+
},
45+
}))
46+
47+
vi.mock("@opensea/wallet-adapters/viem", () => ({
48+
walletAdapterToViemClient: vi.fn(async () => ({})),
49+
}))
50+
3251
function mockFetch(manifest: object) {
3352
return vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
3453
new Response(JSON.stringify(manifest), {
@@ -58,6 +77,8 @@ describe("register creatorAddress validation", () => {
5877
exitSpy.mockRestore()
5978
errorSpy.mockRestore()
6079
logSpy.mockRestore()
80+
registryCtorConfigs.length = 0
81+
mockRegisterTool.mockClear()
6182
if (originalPrivateKey !== undefined) {
6283
process.env.PRIVATE_KEY = originalPrivateKey
6384
} else {
@@ -197,6 +218,8 @@ describe("register --access-predicate + --predicate-config (F4d)", () => {
197218
exitSpy.mockRestore()
198219
errorSpy.mockRestore()
199220
logSpy.mockRestore()
221+
registryCtorConfigs.length = 0
222+
mockRegisterTool.mockClear()
200223
if (originalPrivateKey !== undefined) {
201224
process.env.PRIVATE_KEY = originalPrivateKey
202225
} else {
@@ -332,4 +355,30 @@ describe("register --access-predicate + --predicate-config (F4d)", () => {
332355

333356
fetchSpy.mockRestore()
334357
})
358+
359+
it("threads --rpc-url through to the registry client", async () => {
360+
process.env.PRIVATE_KEY = TEST_PRIVATE_KEY
361+
process.env.RPC_URL = "http://localhost:8545"
362+
const fetchSpy = mockFetch(validManifest)
363+
364+
const { registerCommand } = await import("../cli/commands/register.js")
365+
366+
await registerCommand.parseAsync([
367+
"node",
368+
"register",
369+
"--metadata",
370+
"https://test.example.com/.well-known/ai-tools/test-tool.json",
371+
"--rpc-url",
372+
"http://localhost:9999",
373+
"--yes",
374+
])
375+
376+
expect(mockRegisterTool).toHaveBeenCalled()
377+
// The registry client waits for the tx receipt on its internal public
378+
// client, so it must use the explicit RPC, never viem's chain default.
379+
expect(registryCtorConfigs).toHaveLength(1)
380+
expect(registryCtorConfigs[0].rpcUrl).toBe("http://localhost:9999")
381+
382+
fetchSpy.mockRestore()
383+
})
335384
})

src/__tests__/update-metadata.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ const mockGetToolConfig = vi.fn(async () => ({
2323
accessPredicate: "0x0000000000000000000000000000000000000000",
2424
}))
2525

26+
const registryCtorConfigs: Record<string, unknown>[] = []
27+
2628
vi.mock("../lib/onchain/registry.js", () => ({
2729
ToolRegistryClient: class {
30+
constructor(config: Record<string, unknown>) {
31+
registryCtorConfigs.push(config)
32+
}
2833
updateToolMetadata = mockUpdateToolMetadata
2934
getToolConfig = mockGetToolConfig
3035
},
@@ -49,6 +54,7 @@ afterEach(() => {
4954
vi.unstubAllGlobals()
5055
mockUpdateToolMetadata.mockClear()
5156
mockGetToolConfig.mockClear()
57+
registryCtorConfigs.length = 0
5258
delete process.env.PRIVATE_KEY
5359
delete process.env.RPC_URL
5460
})
@@ -119,4 +125,33 @@ describe("update-metadata command", () => {
119125

120126
logSpy.mockRestore()
121127
})
128+
129+
it("threads --rpc-url through to both registry clients", async () => {
130+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
131+
132+
const { updateMetadataCommand } = await import(
133+
"../cli/commands/update-metadata.js"
134+
)
135+
136+
await updateMetadataCommand.parseAsync([
137+
"node",
138+
"update-metadata",
139+
"--tool-id",
140+
"42",
141+
"--metadata",
142+
"https://example.com/manifest.json",
143+
"--rpc-url",
144+
"http://localhost:9999",
145+
"--yes",
146+
])
147+
148+
// Both the ownership-check (read-only) client and the transaction
149+
// client must use the explicit RPC, never viem's chain default.
150+
expect(registryCtorConfigs).toHaveLength(2)
151+
for (const config of registryCtorConfigs) {
152+
expect(config.rpcUrl).toBe("http://localhost:9999")
153+
}
154+
155+
logSpy.mockRestore()
156+
})
122157
})

src/cli/commands/inspect.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface InspectOptions {
2525
toolId: string
2626
network: string
2727
checkAccess?: string
28+
rpcUrl?: string
2829
}
2930

3031
export const inspectCommand = new Command("inspect")
@@ -41,6 +42,7 @@ export const inspectCommand = new Command("inspect")
4142
"--check-access <address>",
4243
"Check whether an address has access to the tool",
4344
)
45+
.option("--rpc-url <url>", "RPC endpoint for onchain reads")
4446
.action(async (options: InspectOptions) => {
4547
if (!options.toolId) {
4648
console.error(pc.red("Error: --tool-id is required"))
@@ -56,7 +58,7 @@ export const inspectCommand = new Command("inspect")
5658
}
5759

5860
const chain = getChain(options.network)
59-
const client = new ToolRegistryClient({ chain })
61+
const client = new ToolRegistryClient({ chain, rpcUrl: options.rpcUrl })
6062

6163
console.log(pc.cyan("Reading onchain tool config...\n"))
6264

@@ -78,7 +80,7 @@ export const inspectCommand = new Command("inspect")
7880
if (config.accessPredicate !== zeroAddress) {
7981
const publicClient = createPublicClient({
8082
chain,
81-
transport: http(),
83+
transport: http(options.rpcUrl),
8284
})
8385

8486
let predicateName: string | undefined

0 commit comments

Comments
 (0)