@tanakayuto/intmax402-hono 0.1.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/README.md +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/middleware.d.ts +12 -0
- package/dist/middleware.js +63 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @tanakayuto/intmax402-hono
|
|
2
|
+
|
|
3
|
+
[Hono](https://hono.dev/) middleware for the INTMAX402 protocol — HTTP 402 Payment Required / identity verification via INTMAX zkRollup.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🦄 **Hono-native** — typed middleware with `MiddlewareHandler<Env>`
|
|
8
|
+
- ⚡ **Edge-ready** — works on Cloudflare Workers, Deno Deploy, Bun, and Node.js
|
|
9
|
+
- 🔐 **Identity & Payment modes** — same config as Express adapter
|
|
10
|
+
- 🧠 **AI-agent friendly** — designed for autonomous agent-to-agent payments
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @tanakayuto/intmax402-hono hono
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Hono } from "hono"
|
|
22
|
+
import { intmax402, Intmax402Env } from "@tanakayuto/intmax402-hono"
|
|
23
|
+
|
|
24
|
+
const app = new Hono<Intmax402Env>()
|
|
25
|
+
|
|
26
|
+
// Free route
|
|
27
|
+
app.get("/free", (c) => c.json({ message: "free access" }))
|
|
28
|
+
|
|
29
|
+
// Identity-gated route
|
|
30
|
+
app.get(
|
|
31
|
+
"/premium",
|
|
32
|
+
intmax402({ mode: "identity", secret: process.env.INTMAX402_SECRET! }),
|
|
33
|
+
(c) => c.json({
|
|
34
|
+
message: "verified",
|
|
35
|
+
address: c.get("intmax402").address,
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Payment-gated route
|
|
40
|
+
app.get(
|
|
41
|
+
"/paid",
|
|
42
|
+
intmax402({
|
|
43
|
+
mode: "payment",
|
|
44
|
+
secret: process.env.INTMAX402_SECRET!,
|
|
45
|
+
serverAddress: process.env.SERVER_ADDRESS!,
|
|
46
|
+
amount: "1.0",
|
|
47
|
+
chainId: 137,
|
|
48
|
+
}),
|
|
49
|
+
(c) => c.json({ message: "paid content" })
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
export default app
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Hono vs Express
|
|
56
|
+
|
|
57
|
+
| Feature | Express | Hono |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Runtime | Node.js only | Node / Workers / Deno / Bun |
|
|
60
|
+
| Typing | `req.intmax402` (augmented) | `c.get("intmax402")` (typed via `Env`) |
|
|
61
|
+
| Edge support | ❌ | ✅ Cloudflare Workers ready |
|
|
62
|
+
| Bundle size | Heavy | Ultralight |
|
|
63
|
+
|
|
64
|
+
### Typed Context
|
|
65
|
+
|
|
66
|
+
Hono uses a generic `Env` type for context variables. Import `Intmax402Env` and pass it to your `Hono` instance:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Intmax402Env } from "@tanakayuto/intmax402-hono"
|
|
70
|
+
|
|
71
|
+
const app = new Hono<Intmax402Env>()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
After a successful auth middleware call, `c.get("intmax402")` returns:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
{
|
|
78
|
+
address: string // verified Ethereum address
|
|
79
|
+
verified: boolean // always true if middleware passed
|
|
80
|
+
txHash?: string // present in payment mode
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Cloudflare Workers
|
|
85
|
+
|
|
86
|
+
No changes needed for Workers — Hono's fetch handler works natively:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// worker.ts
|
|
90
|
+
export default app
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.intmax402 = void 0;
|
|
4
|
+
var middleware_1 = require("./middleware");
|
|
5
|
+
Object.defineProperty(exports, "intmax402", { enumerable: true, get: function () { return middleware_1.intmax402; } });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MiddlewareHandler } from "hono";
|
|
2
|
+
import { INTMAX402Config } from "@tanakayuto/intmax402-core";
|
|
3
|
+
export type Intmax402Env = {
|
|
4
|
+
Variables: {
|
|
5
|
+
intmax402: {
|
|
6
|
+
address: string;
|
|
7
|
+
verified: boolean;
|
|
8
|
+
txHash?: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function intmax402(config: INTMAX402Config): MiddlewareHandler<Intmax402Env>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.intmax402 = intmax402;
|
|
4
|
+
const intmax402_core_1 = require("@tanakayuto/intmax402-core");
|
|
5
|
+
const crypto_1 = require("@tanakayuto/intmax402-express/dist/crypto");
|
|
6
|
+
const verify_payment_1 = require("@tanakayuto/intmax402-express/dist/verify-payment");
|
|
7
|
+
function intmax402(config) {
|
|
8
|
+
return async (c, next) => {
|
|
9
|
+
const url = new URL(c.req.url);
|
|
10
|
+
const authHeader = c.req.header("authorization");
|
|
11
|
+
const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim()
|
|
12
|
+
?? c.req.header("x-real-ip")
|
|
13
|
+
?? "unknown";
|
|
14
|
+
if (!authHeader) {
|
|
15
|
+
const nonce = (0, intmax402_core_1.generateNonce)(config.secret, ip, url.pathname, config.bindIp ?? false);
|
|
16
|
+
const statusCode = config.mode === "payment" ? 402 : 401;
|
|
17
|
+
return c.json({
|
|
18
|
+
error: config.mode === "payment" ? "Payment Required" : "Unauthorized",
|
|
19
|
+
protocol: "INTMAX402",
|
|
20
|
+
mode: config.mode,
|
|
21
|
+
}, statusCode, {
|
|
22
|
+
"WWW-Authenticate": buildWWWAuthenticate(nonce, config),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const credential = (0, intmax402_core_1.parseAuthorization)(authHeader);
|
|
26
|
+
if (!credential)
|
|
27
|
+
return c.json({ error: "Invalid authorization header" }, 401);
|
|
28
|
+
if (!(0, intmax402_core_1.verifyNonce)(credential.nonce, config.secret, ip, url.pathname, config.bindIp ?? false)) {
|
|
29
|
+
return c.json({ error: "Invalid or expired nonce" }, 401);
|
|
30
|
+
}
|
|
31
|
+
if (config.allowList?.length) {
|
|
32
|
+
if (!config.allowList.includes(credential.address.toLowerCase())) {
|
|
33
|
+
return c.json({ error: "Address not in allow list" }, 403);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const isValidSig = (0, crypto_1.verifySignature)(credential.signature, credential.nonce, credential.address);
|
|
37
|
+
if (!isValidSig)
|
|
38
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
39
|
+
if (config.mode === "payment") {
|
|
40
|
+
if (!credential.txHash)
|
|
41
|
+
return c.json({ error: "Payment transaction hash required" }, 402);
|
|
42
|
+
if (!config.serverAddress || !config.amount)
|
|
43
|
+
return c.json({ error: "Server misconfigured" }, 500);
|
|
44
|
+
const result = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
|
|
45
|
+
if (!result.valid)
|
|
46
|
+
return c.json({ error: result.error ?? "Payment verification failed" }, 402);
|
|
47
|
+
}
|
|
48
|
+
c.set("intmax402", { address: credential.address, verified: true, txHash: credential.txHash });
|
|
49
|
+
await next();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildWWWAuthenticate(nonce, config) {
|
|
53
|
+
let header = `INTMAX402 realm="intmax402", nonce="${nonce}", mode="${config.mode}"`;
|
|
54
|
+
if (config.serverAddress)
|
|
55
|
+
header += `, serverAddress="${config.serverAddress}"`;
|
|
56
|
+
if (config.amount)
|
|
57
|
+
header += `, amount="${config.amount}"`;
|
|
58
|
+
if (config.tokenAddress)
|
|
59
|
+
header += `, tokenAddress="${config.tokenAddress}"`;
|
|
60
|
+
if (config.chainId)
|
|
61
|
+
header += `, chainId="${config.chainId}"`;
|
|
62
|
+
return header;
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanakayuto/intmax402-hono",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/zaq2989/intmax402"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"intmax",
|
|
19
|
+
"http-402",
|
|
20
|
+
"payment",
|
|
21
|
+
"hono",
|
|
22
|
+
"cloudflare-workers",
|
|
23
|
+
"ai-agent"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@tanakayuto/intmax402-core": "0.2.2",
|
|
28
|
+
"@tanakayuto/intmax402-express": "0.2.2",
|
|
29
|
+
"ethers": "^6.0.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"hono": "^4.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.4.0",
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"hono": "^4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"clean": "rm -rf dist"
|
|
42
|
+
}
|
|
43
|
+
}
|