feat: add Agent Registration File utilities#6
Conversation
Add src/registration/ module with: - types.ts: AgentRegistrationFile, ServiceEntry, RegistrationBinding - parse.ts: parseRegistrationFile() with full schema validation - build.ts: createRegistrationFile() builder with auto-set type URI - services.ts: findService() and findServices() lookup helpers - fetch.ts: fetchRegistrationFile() for HTTPS-only remote fetching - resolve.ts: resolveServiceEndpoint() full pipeline (on-chain -> fetch -> lookup) New export: @x402r/erc8004/registration Bumps version to 0.1.0-alpha.1. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
SDK ReviewFound 12 issues (reviewed: tests, conventions, dead code, SDK design): Critical (100)1. [Type Bug] erc8004/src/registration/types.ts Lines 20 to 21 in 42aa3fd Every other A consumer who reads an Fix: Important (75)2. [Conventions] Duplicated
Same 3. [Conventions] Parameters interfaces defined outside
In 4. [Conventions] erc8004/src/registration/parse.ts Line 45 in 42aa3fd The 5. [Conventions] erc8004/src/registration/types.ts Line 22 in 42aa3fd Could be a template literal type like 6. [SDK Design] erc8004/src/registration/build.ts Lines 10 to 19 in 42aa3fd Near-duplicate of the interface minus 7. [SDK Design] erc8004/src/registration/resolve.ts Lines 20 to 43 in 42aa3fd Inconsistent with 8. [SDK Design] Missing
9. [Tests] No test for empty erc8004/tests/registration.test.ts Lines 257 to 321 in 42aa3fd
10. [Tests] erc8004/tests/registration.test.ts Lines 200 to 320 in 42aa3fd
11. [Tests] erc8004/tests/registration.test.ts Lines 68 to 100 in 42aa3fd
12. [Tests] No fork test for
Generated with Claude Code using review-sdk skill |
…verage
- agentId: number → bigint in RegistrationBinding (type bug)
- Deduplicate REGISTRATION_TYPE constant into types.ts
- Move param interfaces to types.ts (convention alignment)
- CreateRegistrationFileParameters uses Omit<AgentRegistrationFile, 'type'>
- agentRegistry typed as CAIP-10 template literal
- resolveServiceEndpoint returns { endpoint, service, agentURI }
- parseRegistrationFile validates optional fields when present
- fetchRegistrationFile adds 10s timeout + 1MB size guard
- resolveServiceEndpoint wraps fetch errors with agent context
- Add size-limit entry for registration subpath
- Fix vi.stubGlobal leak with afterEach cleanup
- Add 10 new tests covering all changes
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
SDK Review — Re-review (post e9577f4)10 of 12 original issues fixed. 1 new regression found, 2 carried over. New Regression (85)[Type Bug] erc8004/src/registration/parse.ts Lines 87 to 91 in e9577f4 The upstream spec (x402-foundation/x402 PR #1024) says: "agentId is always a JSON string, even for EVM tokenIds that are numeric." A spec-compliant registration file has The coercion on line 79 ( Fix: if (
typeof binding.agentId !== 'bigint' &&
typeof binding.agentId !== 'number' &&
typeof binding.agentId !== 'string'
) {
throw new Error(
`registrations[${index}].agentId must be a string, number, or bigint`,
)
}Carried Over[Conventions] erc8004/src/registration/parse.ts Line 78 in e9577f4 Still present. Lower severity now with thorough validation — the cast is structurally necessary given [Tests] No fork test for Still no Resolved ✓
New additions (timeout, 1MB guard, error wrapping, optional field validation, bigint coercion) all look good. Generated with Claude Code using review-sdk skill |
The ERC-8004 spec mandates agentId as a JSON string. JSON.parse
produces { agentId: "42" } which the validator was rejecting.
Accept string, number, and bigint — all coerced to bigint.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
SDK Review — Genericity CheckThe PR claims "zero x402r references in src/" — true at the string level, but x402-ecosystem conventions leak in two places. [Genericity] erc8004/src/registration/types.ts Lines 6 to 16 in e9577f4 The ERC-8004 spec defines: Fix — use an index signature for non-spec fields: export interface AgentRegistrationFile {
type: typeof REGISTRATION_TYPE
name: string
description: string
image: string
services: ServiceEntry[]
active?: boolean
registrations?: RegistrationBinding[]
[key: string]: unknown // allow ecosystem-specific fields without hardcoding them
}x402-specific consumers access those fields with typed narrowing in their own layer (SDK plugin), not in this generic library. [Genericity] erc8004/src/registration/parse.ts Lines 46 to 73 in e9577f4 The parser rejects files where Fix — remove the Both fixes align with the plan's own rule: "@x402r/erc8004 stays generic. No x402 extension logic." Generated with Claude Code using review-sdk skill |
x402Support and supportedTrust are ecosystem conventions, not ERC-8004 spec fields. Replace with [key: string]: unknown index signature so non-spec consumers aren't forced into x402-specific types. Validation now only covers spec-mandated fields; extension fields pass through. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
SDK ReviewFound 4 issues (reviewed: tests, conventions, dead code, SDK design):
Generated with Claude Code using review-sdk skill |
parseRegistrationFile was coercing registrations[].agentId to bigint in-place, mutating the caller's object. Now reconstructs the bindings array via .map() instead. Also adds 4 tests: primitive binding entry, missing agentRegistry, invalid agentId type, and no-mutation check. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Response to review #4#1 (parse mutates input) — Fixed in eb95130. #2 (builder is trivial) — Keeping as-is. The builder auto-sets a 60-char type URI that callers shouldn't need to remember or copy-paste. The #3 (validateRegistrationBinding untested) — Fixed in eb95130. Added 3 tests: primitive binding entry, missing #4 (cross-module dependency) — Keeping as-is. The dependency is one-way ( |
SDK Review — Final (commit 4bdc529)Found 9 issues (reviewed: tests, conventions, dead code, SDK design): 1. [Correctness] Non-numeric string https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/src/registration/parse.ts#L56-L67
Fix — validate numeric strings in the validator, or wrap the if (typeof binding.agentId === 'string' && !/^\d+$/.test(binding.agentId)) {
throw new Error(
`registrations[${index}].agentId string must be a non-negative integer, got "${binding.agentId}"`
)
}2. [Conventions] https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/src/registration/build.ts#L11 Uses 3. [Security] Content-length size guard is bypassable (80) https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/src/registration/fetch.ts#L28-L30 The 1 MB guard only fires when the server sends a Fix — enforce on actual bytes, keep content-length as early-exit optimization: const text = await response.text()
if (text.length > 1_048_576) {
throw new Error('Registration file exceeds 1 MB size limit')
}
let json: unknown
try { json = JSON.parse(text) } catch { throw new Error('Response is not valid JSON') }4. [Conventions] https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/tests/registration.test.ts#L11 Hardcodes the URI string instead of importing 5. [Design] Cross-module coupling in https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/src/registration/resolve.ts#L2 The only file in 6. [Conventions] https://github.com/BackTrackCo/erc8004/blob/4bdc529e8b6e/tests/registration.test.ts#L371-L391 Structurally identical to 7. [Tests] Assertion inside mock body — silently passes if fetch never called (75) The "passes abort signal" test has 8. [Tests] Missing Every public function in 9. [Tests] No test for Two coercion tests exist (number→bigint, string→bigint) but the bigint→bigint path — which the validator explicitly accepts — has no test.
Generated with Claude Code using review-sdk skill |
…cleanup - Reject non-numeric string agentId (e.g. "abc") with descriptive error instead of raw SyntaxError from BigInt() - Rename params → parameters in build.ts (codebase convention) - Use response.text() + length check instead of content-length header for reliable size enforcement on chunked responses - Import REGISTRATION_TYPE in tests instead of hardcoding - Use shared mockPublic helper instead of local duplicate - Move abort signal assertion to top level (not inside mock body) - Add bigint→bigint coercion path test Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Summary
src/registration/module implementing the ERC-8004 Agent Registration File specparseRegistrationFile()— validates JSON against the spec schema (type URI, required fields, service entries)createRegistrationFile()— builder with auto-set type URIfindService()/findServices()— lookup services by name in a registration filefetchRegistrationFile()— fetches and validates from HTTPS URLsresolveServiceEndpoint()— full pipeline:resolveAgent(agentId)→ fetch registration file → find service → return endpoint@x402r/erc8004/registration0.1.0-alpha.1All code is generic ERC-8004 spec-aligned — zero
x402rreferences insrc/.Test plan
pnpm build && pnpm typecheck && pnpm test— 75 tests passgrep -r "x402r" src/returns zero matches🤖 Generated with Claude Code