@armory-sh/middleware-next 0.1.1
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 +110 -0
- package/dist/index.js +127 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @armory-sh/middleware-next
|
|
2
|
+
|
|
3
|
+
x402 payment middleware for Next.js App Router.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @armory-sh/middleware-next
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Setup
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// middleware.ts
|
|
17
|
+
import { paymentProxy, x402ResourceServer } from "@armory-sh/middleware-next";
|
|
18
|
+
|
|
19
|
+
const facilitatorClient = {
|
|
20
|
+
async verify(headers: Headers) {
|
|
21
|
+
// Implement verification logic
|
|
22
|
+
return { success: true, payerAddress: "0x..." };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
27
|
+
|
|
28
|
+
export const proxy = paymentProxy(
|
|
29
|
+
{
|
|
30
|
+
"/api/protected": {
|
|
31
|
+
accepts: {
|
|
32
|
+
scheme: "exact",
|
|
33
|
+
price: "1000000",
|
|
34
|
+
network: "eip155:8453",
|
|
35
|
+
payTo: "0x...",
|
|
36
|
+
},
|
|
37
|
+
description: "Access to protected API",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
resourceServer
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export const config = { matcher: ["/api/protected/:path*"] };
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Per-Route Configuration
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export const proxy = paymentProxy(
|
|
50
|
+
{
|
|
51
|
+
"/api/basic": {
|
|
52
|
+
accepts: { scheme: "exact", price: "1000000", network: "eip155:8453", payTo: "0x..." },
|
|
53
|
+
description: "Basic tier",
|
|
54
|
+
},
|
|
55
|
+
"/api/premium": {
|
|
56
|
+
accepts: { scheme: "exact", price: "50000000", network: "eip155:8453", payTo: "0x..." },
|
|
57
|
+
description: "Premium tier",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
resourceServer
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With Facilitator Settlement
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const facilitatorClient = {
|
|
68
|
+
async verify(headers: Headers) {
|
|
69
|
+
const response = await fetch("https://facilitator.com/verify", {
|
|
70
|
+
headers: Object.fromEntries(headers.entries()),
|
|
71
|
+
});
|
|
72
|
+
return response.json();
|
|
73
|
+
},
|
|
74
|
+
async settle(headers: Headers) {
|
|
75
|
+
const response = await fetch("https://facilitator.com/settle", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: Object.fromEntries(headers.entries()),
|
|
78
|
+
});
|
|
79
|
+
return response.json();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Route Patterns
|
|
85
|
+
|
|
86
|
+
- Exact match: `/api/users`
|
|
87
|
+
- Wildcard: `/api/*` (matches `/api/users`, `/api/posts/123`)
|
|
88
|
+
- Next.js style: `/protected/:path*` (matches `/protected/foo/bar`)
|
|
89
|
+
|
|
90
|
+
## API
|
|
91
|
+
|
|
92
|
+
### `paymentProxy(routes, resourceServer)`
|
|
93
|
+
|
|
94
|
+
Creates a payment proxy handler for Next.js middleware.
|
|
95
|
+
|
|
96
|
+
**Parameters:**
|
|
97
|
+
- `routes`: Record mapping route patterns to payment config
|
|
98
|
+
- `resourceServer`: x402ResourceServer instance
|
|
99
|
+
|
|
100
|
+
**Returns:** Next.js middleware function
|
|
101
|
+
|
|
102
|
+
### `x402ResourceServer`
|
|
103
|
+
|
|
104
|
+
Registers and manages payment schemes.
|
|
105
|
+
|
|
106
|
+
**Methods:**
|
|
107
|
+
- `register(chainId, scheme)`: Register a payment scheme
|
|
108
|
+
- `getRequirements(chainId)`: Get requirements for a chain
|
|
109
|
+
- `getAllRequirements()`: Get all registered requirements
|
|
110
|
+
- `getFacilitator()`: Get the facilitator client
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/resource-server.ts
|
|
2
|
+
var x402ResourceServer = class {
|
|
3
|
+
facilitator;
|
|
4
|
+
schemes;
|
|
5
|
+
constructor(facilitatorClient) {
|
|
6
|
+
this.facilitator = facilitatorClient;
|
|
7
|
+
this.schemes = /* @__PURE__ */ new Map();
|
|
8
|
+
}
|
|
9
|
+
register(chainId, scheme) {
|
|
10
|
+
this.schemes.set(chainId, scheme);
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
getRequirements(chainId) {
|
|
14
|
+
const scheme = this.schemes.get(chainId);
|
|
15
|
+
if (!scheme) {
|
|
16
|
+
throw new Error(`No scheme registered for chain: ${chainId}`);
|
|
17
|
+
}
|
|
18
|
+
return scheme.getRequirements();
|
|
19
|
+
}
|
|
20
|
+
getAllRequirements() {
|
|
21
|
+
return Array.from(this.schemes.values()).map((s) => s.getRequirements());
|
|
22
|
+
}
|
|
23
|
+
getFacilitator() {
|
|
24
|
+
return this.facilitator;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/proxy.ts
|
|
29
|
+
import {
|
|
30
|
+
safeBase64Encode,
|
|
31
|
+
V2_HEADERS,
|
|
32
|
+
findMatchingRoute
|
|
33
|
+
} from "@armory-sh/base";
|
|
34
|
+
function paymentProxy(routes, resourceServer) {
|
|
35
|
+
const routeConfigs = Object.entries(routes).map(
|
|
36
|
+
([pattern, config]) => ({ pattern, config })
|
|
37
|
+
);
|
|
38
|
+
return async (request) => {
|
|
39
|
+
const path = new URL(request.url).pathname;
|
|
40
|
+
const matchedRoute = findMatchingRoute(routeConfigs, path);
|
|
41
|
+
if (!matchedRoute) {
|
|
42
|
+
return new Response(
|
|
43
|
+
JSON.stringify({ error: "Route not found" }),
|
|
44
|
+
{ status: 404, headers: { "Content-Type": "application/json" } }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const paymentHeader = request.headers.get(V2_HEADERS.PAYMENT_SIGNATURE);
|
|
48
|
+
if (!paymentHeader) {
|
|
49
|
+
const config = matchedRoute.config;
|
|
50
|
+
const resource = {
|
|
51
|
+
url: request.url,
|
|
52
|
+
description: config.description || "API Access",
|
|
53
|
+
mimeType: "application/json"
|
|
54
|
+
};
|
|
55
|
+
const paymentRequired = {
|
|
56
|
+
x402Version: 2,
|
|
57
|
+
error: "Payment required",
|
|
58
|
+
resource,
|
|
59
|
+
accepts: [
|
|
60
|
+
{
|
|
61
|
+
scheme: config.accepts.scheme,
|
|
62
|
+
network: config.accepts.network,
|
|
63
|
+
amount: config.accepts.price,
|
|
64
|
+
asset: config.accepts.payTo,
|
|
65
|
+
payTo: config.accepts.payTo,
|
|
66
|
+
maxTimeoutSeconds: 300,
|
|
67
|
+
extra: {}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
return new Response(
|
|
72
|
+
JSON.stringify({
|
|
73
|
+
error: "Payment required",
|
|
74
|
+
accepts: paymentRequired.accepts
|
|
75
|
+
}),
|
|
76
|
+
{
|
|
77
|
+
status: 402,
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
[V2_HEADERS.PAYMENT_REQUIRED]: safeBase64Encode(
|
|
81
|
+
JSON.stringify(paymentRequired)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const facilitator = resourceServer.getFacilitator();
|
|
88
|
+
const verifyResult = await facilitator.verify(request.headers);
|
|
89
|
+
if (!verifyResult.success) {
|
|
90
|
+
return new Response(
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
error: "Payment verification failed",
|
|
93
|
+
details: verifyResult.error
|
|
94
|
+
}),
|
|
95
|
+
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return new Response(
|
|
99
|
+
JSON.stringify({
|
|
100
|
+
verified: true,
|
|
101
|
+
payerAddress: verifyResult.payerAddress
|
|
102
|
+
}),
|
|
103
|
+
{
|
|
104
|
+
status: 200,
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"X-Payment-Verified": "true",
|
|
108
|
+
"X-Payer-Address": verifyResult.payerAddress || ""
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/middleware.ts
|
|
116
|
+
function createMiddleware(routes, resourceServer) {
|
|
117
|
+
const proxy = paymentProxy(routes, resourceServer);
|
|
118
|
+
return async (request) => {
|
|
119
|
+
const response = await proxy(request);
|
|
120
|
+
return NextResponse.json(await response.json(), { status: response.status });
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
createMiddleware,
|
|
125
|
+
paymentProxy,
|
|
126
|
+
x402ResourceServer
|
|
127
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@armory-sh/middleware-next",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"bun": "./src/index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/thegreataxios/armory.git",
|
|
25
|
+
"directory": "packages/middleware-next"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"next": ">=13"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@armory-sh/base": "0.2.20"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"bun-types": "latest",
|
|
35
|
+
"next": "latest",
|
|
36
|
+
"typescript": "5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "rm -rf dist && tsup",
|
|
40
|
+
"build:dts": "rm -rf dist && tsc --emitDeclarationOnly",
|
|
41
|
+
"test": "bun test"
|
|
42
|
+
}
|
|
43
|
+
}
|