{"_id":"@btx-tools/middleware-hono","_rev":"6-9d09de15860acd1ba08ce77ed50977a2","name":"@btx-tools/middleware-hono","dist-tags":{"latest":"1.1.0"},"versions":{"0.1.0":{"name":"@btx-tools/middleware-hono","version":"0.1.0","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","_id":"@btx-tools/middleware-hono@0.1.0","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"dist":{"shasum":"0463286dde82c84ee4a2d9e1e483ad0080ab1242","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-0.1.0.tgz","fileCount":9,"integrity":"sha512-PXlkAJfO46hHpTD1iLcjig9azW3n0nat8OF9VuVyLzPy/C/A/KjQZu2BdnjEH7JySCql5N0gDpCP5PXKiMTunQ==","signatures":[{"sig":"MEQCIESq1J5XOTEFBsLGvW5A6CW45RXtcncwr1uZerZciN1cAiBcRCm11y3NLxiDQ0ohs9t71IoWpKvILDqYHCu9Z9SaOg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47118},"main":"./dist/index.cjs","type":"module","_from":"file:btx-tools-middleware-hono-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"test":"vitest run","build":"tsup","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:unit":"vitest run tests/unit","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"_resolved":"/private/var/folders/56/l_0r7hgn6nb30xq5clyrvh_m0000gn/T/3f4094dbb421b798223f66307b537f5e/btx-tools-middleware-hono-0.1.0.tgz","_integrity":"sha512-PXlkAJfO46hHpTD1iLcjig9azW3n0nat8OF9VuVyLzPy/C/A/KjQZu2BdnjEH7JySCql5N0gDpCP5PXKiMTunQ==","repository":{"url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","type":"git","directory":"packages/middleware-hono"},"_npmVersion":"10.9.3","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.6.0","tsup":"^8.3.0","vitest":"^2.1.0","prettier":"^3.3.0","typescript":"^5.6.0","@types/node":"^22.0.0","@btx-tools/challenges-sdk":"0.1.0"},"peerDependencies":{"hono":"^4.0.0","@btx-tools/challenges-sdk":"^0.0.4 || ^0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/middleware-hono_0.1.0_1779483108265_0.1922640988204427","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@btx-tools/middleware-hono","version":"0.1.1","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","_id":"@btx-tools/middleware-hono@0.1.1","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"dist":{"shasum":"21a2007d4acb3fab6d67d5a2879c5be56ef5b12a","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-0.1.1.tgz","fileCount":9,"integrity":"sha512-teCnKyYvjiUup+1CoIlMhN2m8Zy/aBTewJofXOfJrAY1cYa8+Nd0knEP1nYnv2R3VceHZMw1HhKryslKuEe2LA==","signatures":[{"sig":"MEUCIQCRzJT7DkLUSoSSZG78Xn4OPv8CypGOwjNJrpXrAb8lCQIgeqv2WWOYvHAEOhUstWhf0SxXsdPrgsIq+NSBWGxM6Fo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":49996},"main":"./dist/index.cjs","type":"module","_from":"file:btx-tools-middleware-hono-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"test":"vitest run","build":"tsup","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:unit":"vitest run tests/unit","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"_resolved":"/private/var/folders/56/l_0r7hgn6nb30xq5clyrvh_m0000gn/T/c7db114623befefa844ae8167cf353e3/btx-tools-middleware-hono-0.1.1.tgz","_integrity":"sha512-teCnKyYvjiUup+1CoIlMhN2m8Zy/aBTewJofXOfJrAY1cYa8+Nd0knEP1nYnv2R3VceHZMw1HhKryslKuEe2LA==","repository":{"url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","type":"git","directory":"packages/middleware-hono"},"_npmVersion":"10.9.3","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.6.0","tsup":"^8.3.0","vitest":"^2.1.0","prettier":"^3.3.0","typescript":"^5.6.0","@types/node":"^22.0.0","@btx-tools/challenges-sdk":"0.1.1"},"peerDependencies":{"hono":"^4.0.0","@btx-tools/challenges-sdk":"^0.0.4 || ^0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/middleware-hono_0.1.1_1779484510766_0.2625468697743938","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@btx-tools/middleware-hono","version":"0.1.2","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","_id":"@btx-tools/middleware-hono@0.1.2","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"dist":{"shasum":"2eeddbfa500817fca56ee3810c482303092d725f","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-0.1.2.tgz","fileCount":9,"integrity":"sha512-QFXSqu88u/7/M8NdnyXv+8KyNgZRdtlG60LU0YAk/OMtbCkFLv6LbbjYkRzRn+v+s0QywQHD0SBL9hXQmFpnUg==","signatures":[{"sig":"MEYCIQCDxhtlNXS1/snA2Wt3erryER1pNLTmpuP417hVuqIk1QIhAMDVgy3LFQxic/fivbHLp3nfL0yVimhKVjB1Y+OCiKYO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51852},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"d255fafe72c92db3b6d13f4676d972ac50b9a18e","scripts":{"test":"vitest run","build":"tsup","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:unit":"vitest run tests/unit","test:watch":"vitest","type-check":"tsc --noEmit","prepublishOnly":"pnpm build"},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"repository":{"url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","type":"git","directory":"packages/middleware-hono"},"_npmVersion":"10.9.3","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.6.0","tsup":"^8.3.0","vitest":"^2.1.0","prettier":"^3.3.0","typescript":"^5.6.0","@types/node":"^22.0.0","@btx-tools/challenges-sdk":"workspace:*"},"peerDependencies":{"hono":"^4.0.0","@btx-tools/challenges-sdk":"^0.0.4 || ^0.1.0 || ^0.2.0 || ^0.3.0"},"_npmOperationalInternal":{"tmp":"tmp/middleware-hono_0.1.2_1779525733285_0.2738221954848936","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@btx-tools/middleware-hono","version":"1.0.0","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","_id":"@btx-tools/middleware-hono@1.0.0","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"dist":{"shasum":"d56e1fa2a8fce2dd011a2bd40a10e36a90d4626c","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-1.0.0.tgz","fileCount":9,"integrity":"sha512-Pa524PG+bEsazfvnYs+JkhRfZ8jmIpX04kuuHuruSymRIWyF1aZH6+J4IGXmYPrMYx8f/hoGjfw4w/dmLgpDpA==","signatures":[{"sig":"MEUCIQC676TW3HrLRHS1mqvIb/wsi2yzEZ/FsNbr9+OaIZz55QIgZuCewJZl7pD++8rjWlCeQrS/C7jg7eCPlRT2ehU6IBM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51822},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"d7f061cb6402e021ec6482ebc2f31fa1d1b24805","scripts":{"test":"vitest run","build":"tsup","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:unit":"vitest run tests/unit","test:watch":"vitest","type-check":"tsc --noEmit","prepublishOnly":"pnpm build"},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"repository":{"url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","type":"git","directory":"packages/middleware-hono"},"_npmVersion":"10.9.3","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.6.0","tsup":"^8.3.0","vitest":"^2.1.0","prettier":"^3.3.0","typescript":"^5.6.0","@types/node":"^22.0.0","@btx-tools/challenges-sdk":"workspace:*"},"peerDependencies":{"hono":"^4.0.0","@btx-tools/challenges-sdk":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/middleware-hono_1.0.0_1779528552186_0.4055259316860378","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@btx-tools/middleware-hono","version":"1.0.1","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","_id":"@btx-tools/middleware-hono@1.0.1","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"dist":{"shasum":"e252e1855a495e781ab6a888b81bb26742f828b3","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-1.0.1.tgz","fileCount":9,"integrity":"sha512-0equ2dRhKPgfH5VbnIjAaV/3mhY22NFZyAjammnN/ddtM4GGHiGz3afQZfwb7ZriIBhCkN9Fii1gpHAkLZ827Q==","signatures":[{"sig":"MEUCIDxtuQWtjaOOV/TYFvzsV/B6FviSPB7c6ukly+0OMy9RAiEA/w5KtqPRwhbFAdDWaj6VPnYBY4IgkVRUHMzNR+CA7hM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52609},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"318a4f5d83250e34db47187e25793e123725e919","scripts":{"test":"vitest run","build":"tsup","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:unit":"vitest run tests/unit","test:watch":"vitest","type-check":"tsc --noEmit","prepublishOnly":"pnpm build"},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"repository":{"url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","type":"git","directory":"packages/middleware-hono"},"_npmVersion":"10.9.3","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.6.0","tsup":"^8.3.0","vitest":"^2.1.0","prettier":"^3.3.0","typescript":"^5.6.0","@types/node":"^22.0.0","@btx-tools/challenges-sdk":"workspace:*"},"peerDependencies":{"hono":"^4.0.0","@btx-tools/challenges-sdk":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/middleware-hono_1.0.1_1779530009819_0.8356432134893452","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@btx-tools/middleware-hono","version":"1.1.0","description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","test:unit":"vitest run tests/unit","type-check":"tsc --noEmit","format":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","prepublishOnly":"pnpm build"},"peerDependencies":{"@btx-tools/challenges-sdk":"^1.2.0","hono":"^4.0.0"},"devDependencies":{"@btx-tools/challenges-sdk":"workspace:*","@types/node":"^22.0.0","hono":"^4.6.0","prettier":"^3.3.0","tsup":"^8.3.0","typescript":"^5.6.0","vitest":"^2.1.0"},"keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"repository":{"type":"git","url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","directory":"packages/middleware-hono"},"homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"publishConfig":{"access":"public"},"engines":{"node":">=20.19.0"},"_id":"@btx-tools/middleware-hono@1.1.0","gitHead":"c97392708f1f40da6aeb4add525239a4c49a912a","_nodeVersion":"22.20.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-pDzNUh4maJ5qRK+rd7fiJwuSh2xvUrllcGlDSJnn35hdUkNFDm5D7UBlJOWYlhmjxLwxfd+kSU6GPiPkLy8rJg==","shasum":"21b205f0101774e03cfc93c97d7780643f6b9e2d","tarball":"https://registry.npmjs.org/@btx-tools/middleware-hono/-/middleware-hono-1.1.0.tgz","fileCount":9,"unpackedSize":60873,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDO5oYxfHJzDfCAFWqgKJVnLitIBS9eHmCWSPhot84vRQIgb/vKjBQGauEKnfmo2AaV9fjuSpID3JuxEzKP3bLtVNU="}]},"_npmUser":{"name":"visitor-code","email":"visitor@friction.market"},"directories":{},"maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/middleware-hono_1.1.0_1779624866410_0.7580708722481948"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-22T20:51:48.090Z","modified":"2026-05-24T12:14:26.673Z","0.1.0":"2026-05-22T20:51:48.425Z","0.1.1":"2026-05-22T21:15:10.911Z","0.1.2":"2026-05-23T08:42:13.453Z","1.0.0":"2026-05-23T09:29:12.330Z","1.0.1":"2026-05-23T09:53:29.998Z","1.1.0":"2026-05-24T12:14:26.560Z"},"bugs":{"url":"https://github.com/btx-tools/btx-challenges-sdk/issues"},"author":{"name":"visitor-code","email":"visitor@friction.market"},"license":"MIT","homepage":"https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/middleware-hono#readme","keywords":["btx","hono","middleware","edge","cloudflare-workers","deno","admission","rate-limit","captcha","ai-gating","service-challenges","proof-of-work"],"repository":{"type":"git","url":"git+https://github.com/btx-tools/btx-challenges-sdk.git","directory":"packages/middleware-hono"},"description":"Hono middleware for @btx-tools/challenges-sdk — drop-in BTX service-challenge admission gate (Node + edge)","maintainers":[{"name":"visitor-code","email":"visitor@friction.market"}],"readme":"# @btx-tools/middleware-hono\n\nDrop-in **Hono** admission gate backed by BTX service challenges. Works on Node, Deno, Bun, **Cloudflare Workers**, and other edge runtimes Hono targets. Same flow + ergonomics as [`@btx-tools/middleware-express`](https://www.npmjs.com/package/@btx-tools/middleware-express) and [`@btx-tools/middleware-fastify`](https://www.npmjs.com/package/@btx-tools/middleware-fastify), tailored to Hono's middleware model + `c.set('btx', ...)` variables.\n\n📖 **[API Reference](https://btx-tools.github.io/btx-challenges-sdk/)** — TypeDoc for all `@btx-tools/*` SDK packages.\n\n> **New to BTX service challenges?** This puts a chain-anchored proof-of-work checkpoint in front of a route — the caller spends a few seconds of _verifiable compute_ instead of a CAPTCHA or a signup. Concept + `issue → solve → redeem` flow: see the [core SDK README](https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/core#what-is-this).\n>\n> **Prerequisites:** you need a reachable **BTX node (`btxd`)** — non-mining for fast (~1–4 s) solves; there's no hosted endpoint, so you can't use this with zero BTX infrastructure. (Edge note: a Worker/edge runtime can't reach `127.0.0.1` — point at a public/tunnelled `btxd`.) See [core → Prerequisites](https://github.com/btx-tools/btx-challenges-sdk/tree/main/packages/core#prerequisites-you-need-a-btx-node).\n\n> **End-to-end example**: a runnable adopter example is in [`examples/02-express-gate`](https://github.com/btx-tools/btx-challenges-sdk/tree/main/examples/02-express-gate) (Express-based; the wiring shape is structurally identical for Hono — swap the route + middleware call). A Hono-native parity example covering both Node and edge deploy is queued for the SDK Phase 3.5 roadmap.\n\n```bash\npnpm add @btx-tools/middleware-hono @btx-tools/challenges-sdk hono\n```\n\n## Quickstart\n\n```ts\nimport { Hono } from 'hono';\nimport { BtxChallengeClient } from '@btx-tools/challenges-sdk';\nimport { btxAdmission, type BtxAdmissionVariables } from '@btx-tools/middleware-hono';\n\nconst client = new BtxChallengeClient({\n  rpcUrl: 'http://127.0.0.1:19334',\n  rpcAuth: { user: 'rpcuser', pass: 'rpcpass' },\n});\n\nconst app = new Hono<{ Variables: BtxAdmissionVariables }>();\n\napp.post(\n  '/v1/generate',\n  btxAdmission({\n    client,\n    purpose: 'ai_inference_gate',\n    resource: (c) => `route:${c.req.path}`,\n    subject: async (c) => `tenant:${(await c.req.json()).tenant_id}`,\n    issueParams: { target_solve_time_s: 1.0, expires_in_s: 60 },\n    onError: (err, c) => c.var.logger?.error({ err }, 'btx admission error'),\n  }),\n  async (c) => {\n    const admit = c.get('btx').result;\n    return c.json({ ok: true, reason: admit.reason });\n  },\n);\n\nexport default app;\n```\n\n## ⚠️ Body consumption (read before async resolvers)\n\nHono's `c.req.json()` is **one-shot** — once consumed, the body stream is gone. If your `resource` / `subject` resolver does `await c.req.json()`, the route handler downstream **cannot read the body again** and will throw `BodyAlreadyUsedError`.\n\n❌ **This breaks**:\n\n```ts\n(btxAdmission({\n  // ...\n  resource: async (c) => `model:${(await c.req.json()).model}`,\n}),\n  async (c) => {\n    const body = await c.req.json(); // ← throws — body already consumed!\n    return c.json({ ok: true });\n  });\n```\n\n✅ **Two safe patterns**:\n\n```ts\n// Pattern 1: cache the body once at the top, pass through context\napp.post('/v1/generate', async (c, next) => {\n  c.set('body', await c.req.json());\n  return next();\n});\napp.post('/v1/generate',\n  btxAdmission({\n    // ...\n    resource: (c) => `model:${(c.get('body') as { model: string }).model}`,\n  }),\n  async (c) => {\n    const body = c.get('body');\n    return c.json({ ok: true, body });\n  },\n);\n\n// Pattern 2: derive resolver inputs from headers, not body\nbtxAdmission({\n  // ...\n  resource: (c) => `model:${c.req.header('x-model') ?? 'default'}`,\n}),\n```\n\n## How it works\n\nStateless **echo-the-challenge** flow:\n\n1. **First request** has no proof headers → middleware calls `client.issue()` → replies `402 Payment Required` with `X-BTX-Challenge` header containing the challenge JSON + a body listing the headers the client should add on retry.\n2. **Client solves** the challenge (locally or via RPC) and **retries** with `X-BTX-Challenge` (echoed), `X-BTX-Proof-Nonce`, `X-BTX-Proof-Digest`.\n3. Middleware calls `client.redeem()` → if `result.valid === true`, sets `c.set('btx', { result })` and yields to `await next()` (route handler runs). Else replies `403`.\n\nNo server-side challenge store. Scales horizontally; the challenge JSON rides in the `X-BTX-Challenge` header on retry (~3-5 KB). Check edge-runtime header-size limits — Cloudflare Workers and Fastly accept large headers, but Vercel Edge caps at smaller sizes.\n\n## API\n\n### `btxAdmission(opts): MiddlewareHandler`\n\nReturns a Hono middleware function to attach per-route.\n\n#### Options\n\n| Field            | Type                                                | Notes                                                                                                             |\n| ---------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |\n| `client`         | `BtxChallengeClient`                                | required. Construct once at boot.                                                                                 |\n| `purpose`        | `string \\| (c) => string \\| (c) => Promise<string>` | required. Logical purpose label. Async resolver supported so you can `await c.req.json()`.                        |\n| `resource`       | `string \\| (c) => string \\| (c) => Promise<string>` | required.                                                                                                         |\n| `subject`        | `string \\| (c) => string \\| (c) => Promise<string>` | required.                                                                                                         |\n| `issueParams`    | `Partial<IssueParams>`                              | optional.                                                                                                         |\n| `onAdmit`        | `(c, result) => void`                               | optional. Fires on successful admission.                                                                          |\n| `onError`        | `(err, c) => void`                                  | optional. Fires when `client.issue()` or `client.redeem()` throws. Re-thrown to Hono's `onError`. Audit ref: D-1. |\n| `isProofPresent` | `(c) => boolean`                                    | optional. Predicate override.                                                                                     |\n\n### `BtxAdmissionVariables`\n\nType the Hono instance with this for `c.get('btx')` type narrowing:\n\n```ts\nconst app = new Hono<{ Variables: BtxAdmissionVariables }>();\n```\n\nAfter admission, `c.get('btx')` is `{ result: VerifyResult } | undefined`.\n\n### Header constants\n\n| Constant              | Value                  |\n| --------------------- | ---------------------- |\n| `HEADER_CHALLENGE`    | `'x-btx-challenge'`    |\n| `HEADER_CHALLENGE_ID` | `'x-btx-challenge-id'` |\n| `HEADER_PROOF_NONCE`  | `'x-btx-proof-nonce'`  |\n| `HEADER_PROOF_DIGEST` | `'x-btx-proof-digest'` |\n\n## Error handling\n\nWhen `client.issue()` or `client.redeem()` throws (e.g., btxd RPC down, network error), the middleware:\n\n1. Calls `opts.onError(err, c)` if provided\n2. Re-throws — Hono's `app.onError()` handler kicks in\n\nUse `app.onError()` to map BTX errors to your preferred response shape:\n\n```ts\napp.onError((err, c) => {\n  if (err instanceof BtxNetworkError) return c.json({ error: 'btxd unreachable' }, 503);\n  return c.json({ error: 'internal' }, 500);\n});\n```\n\n## Edge-runtime notes\n\n### Network reachability\n\n`BtxChallengeClient` uses `fetch()` to reach btxd's JSON-RPC endpoint. **Edge runtimes cannot reach `127.0.0.1`** — they're sandboxed away from the host loopback. You need a **publicly reachable** btxd RPC URL:\n\n- **Cloudflare Tunnel** (Argo Tunnel) — runs in front of your btxd, gives you a stable HTTPS URL the Worker can call\n- **Public RPC proxy** — terminate TLS at Caddy/nginx in front of btxd, expose on a real DNS name\n- **Self-hosted relay** with a public IP + Basic auth (verify `rpcallowip` in btx.conf permits the egress IP)\n\nDo **not** put btxd's RPC port directly on the public internet without auth + TLS termination.\n\n### Runtime-specific\n\n- **Cloudflare Workers / Pages**: works once reachability is solved. `fetch()` is native; no Node polyfills needed.\n- **Deno Deploy**: same — Web `fetch()` is standard.\n- **Bun**: works natively (also accepts a Node btxd via localhost when self-hosting Bun on the same box).\n- **Vercel Edge**: works for typical challenge sizes. **Header-size limits vary across edge platforms** — Vercel, Cloudflare, and Fastly all have different caps for incoming headers. The `X-BTX-Challenge` header is ~3-5 KB for default difficulty; check your platform's documentation if you set high `target_solve_time_s` or run into preflight errors. For very large challenges, consider a stateful challenge-store middleware variant.\n\n## CORS\n\nThe `X-BTX-Challenge`, `X-BTX-Proof-Nonce`, and `X-BTX-Proof-Digest` headers are **custom**, which triggers a CORS preflight for any browser-originated fetch. Configure Hono's built-in `cors` middleware:\n\n```ts\nimport { cors } from 'hono/cors';\napp.use(\n  '/v1/*',\n  cors({\n    origin: 'https://your-frontend.example',\n    allowHeaders: [\n      'content-type',\n      'x-btx-challenge',\n      'x-btx-challenge-id',\n      'x-btx-proof-nonce',\n      'x-btx-proof-digest',\n    ],\n    exposeHeaders: [\n      'x-btx-challenge', // so the browser can READ the 402's challenge header\n    ],\n  }),\n);\n```\n\nWithout `exposeHeaders` including `x-btx-challenge`, the browser sees the 402 status but **cannot** read the challenge JSON from the response header (Web Fetch hides non-CORS-safelisted response headers by default).\n\n## Requirements\n\n- **Node.js** ≥ 18.17 (when running on Node)\n- **Hono** ^4.0.0 (peer dep)\n- **@btx-tools/challenges-sdk** ^0.0.4 (peer dep)\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n","readmeFilename":"README.md"}