@agentcash/router 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.5.2",
3
+ "version": "1.7.0",
4
4
  "description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,16 +20,16 @@
20
20
  "types": "./dist/index.d.ts",
21
21
  "files": [
22
22
  "dist",
23
- ".claude/CLAUDE.md",
24
- ".claude/skills"
23
+ "AGENTS.md",
24
+ "README.md"
25
25
  ],
26
26
  "peerDependencies": {
27
27
  "@coinbase/x402": "^2.1.0",
28
- "@x402/core": "^2.9.0",
29
- "@x402/evm": "^2.9.0",
30
- "@x402/extensions": "^2.9.0",
31
- "@x402/svm": "^2.9.0",
32
- "mppx": "^0.6.5",
28
+ "@x402/core": "^2.11.0",
29
+ "@x402/evm": "^2.11.0",
30
+ "@x402/extensions": "^2.11.0",
31
+ "@x402/svm": "^2.11.0",
32
+ "mppx": "^0.6.16",
33
33
  "next": ">=15.0.0",
34
34
  "zod": "^4.0.0",
35
35
  "zod-openapi": "^5.0.0"
@@ -42,26 +42,17 @@
42
42
  "devDependencies": {
43
43
  "@changesets/cli": "^2.29.8",
44
44
  "@coinbase/x402": "^2.1.0",
45
- "@crossmint/lobster-cli": "^0.1.6",
46
45
  "@eslint/js": "^10.0.1",
47
- "@faremeter/facilitator": "0.17.1",
48
- "@faremeter/fetch": "^0.17.1",
49
- "@faremeter/info": "^0.17.1",
50
- "@faremeter/payment-solana": "^0.17.1",
51
- "@faremeter/types": "^0.17.1",
52
- "@faremeter/wallet-solana": "^0.17.1",
53
- "@faremeter/x-solana-settlement": "^0.4.0",
54
46
  "@modelcontextprotocol/sdk": "^1.26.0",
55
47
  "@solana/kit": "^5.1.0",
56
- "@solana/spl-token": "^0.4.14",
57
- "@solana/web3.js": "^1.98.4",
58
48
  "@types/node": "^22.0.0",
59
- "@x402/core": "^2.9.0",
60
- "@x402/evm": "^2.9.0",
61
- "@x402/extensions": "^2.9.0",
62
- "@x402/svm": "^2.9.0",
49
+ "@x402/core": "^2.11.0",
50
+ "@x402/evm": "^2.11.0",
51
+ "@x402/extensions": "^2.11.0",
52
+ "@x402/svm": "^2.11.0",
63
53
  "eslint": "^10.0.0",
64
- "mppx": "^0.6.5",
54
+ "knip": "^6.13.1",
55
+ "mppx": "^0.6.16",
65
56
  "next": "^15.0.0",
66
57
  "prettier": "^3.8.1",
67
58
  "react": "^19.0.0",
@@ -87,6 +78,7 @@
87
78
  "format:check": "prettier --check 'src/**/*.ts' 'tests/**/*.ts' '*.json' '*.mjs'",
88
79
  "test": "vitest run",
89
80
  "test:watch": "vitest",
90
- "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm build && pnpm test"
81
+ "knip": "knip",
82
+ "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm knip && pnpm build && pnpm test"
91
83
  }
92
84
  }
package/.claude/CLAUDE.md DELETED
@@ -1,229 +0,0 @@
1
- # CLAUDE.md — @agentcash/router
2
-
3
- Protocol-agnostic route framework for Next.js App Router APIs with x402 payment, MPP payment, SIWX identity auth, and API key auth. Used by stablestudio, enrichx402, x402email, agentfacilitator, agentupload.
4
-
5
- ## Guiding Principles
6
-
7
- 1. **Route definition is 3-6 lines.** Everything else is derived.
8
- 2. **Single source of truth.** Route registry drives discovery, OpenAPI, pricing, Bazaar schemas.
9
- 3. **Auth and pricing are orthogonal and composable.** `.paid()`, `.siwx()`, `.apiKey()`, `.unprotected()`.
10
- 4. **Observability is pluggable via RouterPlugin.** Zero boilerplate.
11
- 5. **The package owns the x402 server lifecycle.** Init, verify, settle.
12
- 6. **Convention over configuration.** Sane defaults for Base, USDC, exact scheme.
13
- 7. **Compose, don't reimplement.** Zero payment/auth protocol logic — delegates to `@x402/*`, `@coinbase/x402`, and `mppx`.
14
-
15
- ## Architecture
16
-
17
- **Orchestrate pipeline:** auth check -> body parse -> price resolve -> payment verify -> handler invoke -> settle -> finalize
18
-
19
- - `src/orchestrate.ts` — Full request lifecycle orchestration
20
- - `src/builder.ts` — Fluent RouteBuilder API
21
- - `src/handler.ts` — Safe handler invocation with error mapping
22
- - `src/types.ts` — Core types (RouteEntry, HandlerContext, HttpError)
23
- - `src/registry.ts` — Route registry (Map-backed, silent overwrite on duplicate keys)
24
- - `src/pricing.ts` — Price resolution (static, tiered, dynamic)
25
- - `src/plugin.ts` — Plugin hook system
26
- - `src/server.ts` — x402 server initialization
27
- - `src/auth/` — Auth modules (siwx.ts, api-key.ts, nonce.ts)
28
- - `src/protocols/` — Protocol handlers (x402.ts, detect.ts). MPP is handled via `mppx` high-level API (`Mppx.create` in index.ts)
29
- - `src/discovery/` — Auto-generated endpoints (well-known.ts, openapi.ts)
30
-
31
- ## Auth Modes
32
-
33
- Four auth modes, mutually exclusive (except `.apiKey()` composes with `.paid()`):
34
-
35
- ### `.paid(pricing)` — Payment required
36
- ```typescript
37
- .paid('0.01') // Static price
38
- .paid((body) => calcPrice(body)) // Dynamic pricing
39
- .paid({ field: 'tier', tiers: { basic: { price: '0.01' } } }) // Tiered
40
- ```
41
-
42
- ### `.siwx()` — Wallet identity required (no payment)
43
- ```typescript
44
- .siwx().handler(async ({ wallet }) => { /* wallet is verified */ })
45
- ```
46
-
47
- ### `.apiKey(resolver)` — API key / Bearer token auth
48
- For admin routes, cron jobs, internal services. Checks `X-API-Key` header OR `Authorization: Bearer <token>`.
49
-
50
- ```typescript
51
- // Admin route with API key
52
- export const GET = router
53
- .route('admin/users')
54
- .apiKey(async (key) => {
55
- const admin = await db.admin.findByKey(key);
56
- return admin ?? null; // null = 401, truthy = ctx.account
57
- })
58
- .handler(async ({ account }) => {
59
- // account is whatever resolver returned
60
- return db.user.findMany();
61
- });
62
-
63
- // Cron job with static secret
64
- export const POST = router
65
- .route('cron/cleanup')
66
- .apiKey((key) => key === process.env.CRON_SECRET ? { cron: true } : null)
67
- .handler(async () => { /* ... */ });
68
- ```
69
-
70
- **Headers accepted:** `X-API-Key: <key>` or `Authorization: Bearer <key>`
71
-
72
- **Composing with payment:** `.apiKey()` can layer on `.paid()` — auth runs first, payment second:
73
- ```typescript
74
- .apiKey(resolver).paid('0.01') // Must pass API key AND pay
75
- ```
76
-
77
- ### `.unprotected()` — No auth
78
- ```typescript
79
- .unprotected().handler(async () => { /* public endpoint */ })
80
- ```
81
-
82
- ## Pre-Payment Validation
83
-
84
- ### `.validate(fn)` — Async business validation before 402 challenge
85
-
86
- For checks that need DB lookups or external APIs before showing a price. Runs after body parsing, before the 402 challenge. Requires `.body()`.
87
-
88
- ```typescript
89
- // Domain registration with availability check
90
- router
91
- .route('domain/register')
92
- .paid(calculatePrice, { maxPrice: '10.00' })
93
- .body(RegisterSchema) // .body() before .validate() for type inference
94
- .validate(async (body) => {
95
- if (await isDomainTaken(body.domain)) {
96
- throw Object.assign(new Error('Domain already taken'), { status: 409 });
97
- }
98
- })
99
- .handler(async ({ body, wallet }) => {
100
- return registerDomain(body.domain, wallet);
101
- });
102
-
103
- // Rate limiting before payment
104
- router
105
- .route('api/expensive')
106
- .paid('1.00')
107
- .body(RequestSchema)
108
- .validate(async (body) => {
109
- const usage = await getUserUsage(body.userId);
110
- if (usage >= DAILY_LIMIT) {
111
- throw Object.assign(new Error('Daily limit reached'), { status: 429 });
112
- }
113
- })
114
- .handler(async ({ body }) => { ... });
115
- ```
116
-
117
- **Pipeline order:** `body parse → validate → 402 challenge → payment → handler`
118
-
119
- **Error handling:** Respects `.status` on thrown errors (default: 400). Use `Object.assign(new Error('msg'), { status: 409 })` for custom codes.
120
-
121
- **Works with all auth modes:** paid, siwx, apiKey, unprotected.
122
-
123
- ## Critical Rules
124
-
125
- - **Error handling:** Respect `.status` on any thrown error, not just `HttpError`. The `Object.assign(new Error(), { status })` pattern is universal in Node.js.
126
- - **SIWX challenge:** Must return a proper x402v2 challenge with `PAYMENT-REQUIRED` header and JSON body containing `extensions['sign-in-with-x']` with `domain`, `uri`, `version`, `chainId`, `type`, `nonce`, `issuedAt`.
127
- - **Discovery:** `authMode !== 'unprotected'` determines well-known visibility, not the protocol list. SIWX routes return 402 challenges and must be discoverable.
128
- - **OpenAPI:** Merge paths for multi-method endpoints (GET + DELETE on same path). Never overwrite.
129
- - **Duplicate route keys:** Registry silently overwrites (last-write-wins) with a dev-only `console.warn`. This is intentional — Next.js module loading order is non-deterministic during `next build`, so discovery stubs and real handlers may register the same key in either order. Prior art: ElysiaJS uses the identical pattern. See stablestudio `.claude/13_route-registry-dedup.md` for full research.
130
- - **Dynamic pricing (v0.3.1+):** Early body parsing with `request.clone()` enables accurate dynamic pricing. `maxPrice` is optional and acts as a safety net (cap + fallback). Body is parsed before 402 challenge generation when pricing function exists. See `.claude/15_router-dynamic-pricing-solution.md` for full design and derisking.
131
-
132
- ## Environment Variables
133
-
134
- ### Base URL
135
-
136
- `baseUrl` is **required** in `RouterConfig`. No auto-detection, no fallbacks. The realm is load-bearing for payment matching (MPP memo indexing, 402 challenge realm), so it must be explicitly set.
137
-
138
- ```typescript
139
- createRouter({
140
- baseUrl: process.env.BASE_URL!,
141
- // ...
142
- })
143
- ```
144
-
145
- If `baseUrl` is missing, `createRouter` throws immediately — in dev and prod. This ensures devs discover the issue on first `pnpm dev` rather than deploying with a wrong realm.
146
-
147
- ### CDP API Keys
148
-
149
- The router uses the default facilitator from `@coinbase/x402`, which requires CDP API keys in `process.env`:
150
-
151
- - `CDP_API_KEY_ID` — Coinbase Developer Platform API key ID
152
- - `CDP_API_KEY_SECRET` — CDP API key secret
153
-
154
- **Critical for Next.js apps with env validation (T3 stack, `@t3-oss/env-nextjs`):** These variables must be explicitly declared in your env schema. Next.js does not automatically expose all env vars to `process.env` — undeclared vars are invisible at runtime.
155
-
156
- ### MPP (Tempo) Environment Variables
157
-
158
- MPP payment verification requires an **authenticated** Tempo RPC endpoint. The public `https://rpc.tempo.xyz/` returns `401 Unauthorized`.
159
-
160
- - `TEMPO_RPC_URL` — Authenticated Tempo RPC URL (e.g. `https://user:pass@rpc.mainnet.tempo.xyz`)
161
-
162
- Alternatively, pass `rpcUrl` in the `mpp` config object to `createRouter()`. Without either, MPP on-chain verification fails with "unauthorized: authentication required".
163
-
164
- ### CDP Environment Variables
165
-
166
- Without these keys, the default facilitator cannot authenticate with CDP:
167
- - x402 server `initialize()` fails with "Failed to fetch supported kinds from facilitator: TypeError: fetch failed" or "Facilitator getSupported failed (401): Unauthorized"
168
- - All payment routes return empty 402 responses (no `PAYMENT-REQUIRED` header, no body)
169
-
170
- **Example env schema (T3/`@t3-oss/env-nextjs`):**
171
-
172
- ```typescript
173
- import { createEnv } from "@t3-oss/env-nextjs";
174
- import { z } from "zod";
175
-
176
- export const env = createEnv({
177
- server: {
178
- CDP_API_KEY_ID: z.string(),
179
- CDP_API_KEY_SECRET: z.string(),
180
- // ... other vars
181
- },
182
- runtimeEnv: {
183
- CDP_API_KEY_ID: process.env.CDP_API_KEY_ID,
184
- CDP_API_KEY_SECRET: process.env.CDP_API_KEY_SECRET,
185
- // ... other vars
186
- },
187
- });
188
- ```
189
-
190
- **Alternative:** Pass a custom facilitator config to `createRouter()` if you want to use a different facilitator URL or auth mechanism. But for most apps, the default CDP facilitator is correct.
191
-
192
- ## Version Stability
193
-
194
- The public API is **not stable**. Downstream consumers should pin exact versions (`"@agentcash/router": "0.2.0"`, not `"^0.2.0"`). Breaking changes will happen as we build out multi-protocol support and discover patterns across services. Semver will be respected once we hit 1.0.
195
-
196
- ## Build & Test
197
-
198
- ```bash
199
- pnpm build # tsup
200
- pnpm test # vitest
201
- pnpm typecheck # tsc --noEmit
202
- pnpm check # format + lint + typecheck + build + test
203
- ```
204
-
205
- ## Development Record
206
-
207
- The `.claude/` directory contains design docs, decision records, and bug analyses that document the reasoning behind the router's architecture. See `.claude/INDEX.md` for a table of contents.
208
-
209
- **Convention:** Every doc has a `Status` header. When you resolve work described in a doc, update its Status to `Resolved in vX.Y.Z` and update INDEX.md.
210
-
211
- ## Releasing
212
-
213
- This repo uses [changesets](https://github.com/changesets/changesets) for versioning and npm publishing.
214
-
215
- ### When doing work that should be released:
216
-
217
- 1. **Create a changeset** — Run `pnpm changeset` and describe the changes (patch/minor/major)
218
- 2. **Include the changeset file** in your PR (committed to `.changeset/`)
219
- 3. **Merge PR to main**
220
-
221
- ### What happens automatically:
222
-
223
- 1. When PRs with changesets merge to `main`, the `changesets/action` creates a **"chore: version packages"** PR that bumps `package.json` version and updates `CHANGELOG.md`
224
- 2. When that version PR is merged, the action **publishes to npm** automatically
225
-
226
- ### Troubleshooting
227
-
228
- - **Publish fails**: Check `NPM_TOKEN` secret is set and has write access to `@agentcash` scope
229
- - **No version PR created**: Ensure your PR included a `.changeset/*.md` file