@armory-sh/middleware-hono 0.3.19 → 0.3.21
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 +220 -0
- package/dist/extensions.d.ts +39 -0
- package/dist/extensions.js +60 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +217 -6
- package/package.json +9 -2
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# @armory-sh/middleware-hono
|
|
2
|
+
|
|
3
|
+
x402 payment middleware for Hono applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @armory-sh/middleware-hono
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Simple payment middleware for Hono
|
|
14
|
+
- Route-aware payment configuration
|
|
15
|
+
- Multi-network, multi-token support
|
|
16
|
+
- Per-route facilitator configuration
|
|
17
|
+
- Full TypeScript support
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Hono } from "hono";
|
|
23
|
+
import { paymentMiddleware } from "@armory-sh/middleware-hono";
|
|
24
|
+
|
|
25
|
+
const app = new Hono();
|
|
26
|
+
|
|
27
|
+
app.use("/*", paymentMiddleware({
|
|
28
|
+
payTo: "0xYourAddress...",
|
|
29
|
+
chain: "base",
|
|
30
|
+
token: "usdc",
|
|
31
|
+
amount: "1.0",
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
app.get("/api/data", (c) => {
|
|
35
|
+
const payment = c.get("payment");
|
|
36
|
+
return c.json({
|
|
37
|
+
data: "protected data",
|
|
38
|
+
payerAddress: payment?.payload?.authorization?.from,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Route-Aware Middleware
|
|
44
|
+
|
|
45
|
+
Configure different payment requirements for different routes:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { routeAwarePaymentMiddleware } from "@armory-sh/middleware-hono";
|
|
49
|
+
|
|
50
|
+
const app = new Hono();
|
|
51
|
+
|
|
52
|
+
// Single route with wildcard
|
|
53
|
+
app.use("/api/premium/*", routeAwarePaymentMiddleware({
|
|
54
|
+
routes: ["/api/premium/*"],
|
|
55
|
+
payTo: "0xYourAddress...",
|
|
56
|
+
amount: "$5.00",
|
|
57
|
+
network: "base"
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Multiple routes with per-route configuration
|
|
61
|
+
app.use("/api/*", routeAwarePaymentMiddleware({
|
|
62
|
+
routes: ["/api/basic", "/api/premium/*"],
|
|
63
|
+
payTo: "0xYourAddress...",
|
|
64
|
+
amount: "$1.00", // Default amount
|
|
65
|
+
network: "base",
|
|
66
|
+
perRoute: {
|
|
67
|
+
"/api/premium/*": {
|
|
68
|
+
amount: "$5.00", // Override for premium routes
|
|
69
|
+
network: "ethereum",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Configuration Options
|
|
76
|
+
|
|
77
|
+
### PaymentConfig
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface PaymentConfig {
|
|
81
|
+
payTo: string; // Payment recipient address
|
|
82
|
+
chain?: string | number; // Network (name or chain ID)
|
|
83
|
+
chains?: Array<string | number>; // Multiple networks
|
|
84
|
+
token?: string; // Token symbol
|
|
85
|
+
tokens?: string[]; // Multiple tokens
|
|
86
|
+
amount?: string; // Amount (default: "1.0")
|
|
87
|
+
maxTimeoutSeconds?: number; // Payment timeout (default: 300)
|
|
88
|
+
|
|
89
|
+
// Per-chain configuration
|
|
90
|
+
payToByChain?: Record<string, string>;
|
|
91
|
+
facilitatorUrlByChain?: Record<string, string>;
|
|
92
|
+
|
|
93
|
+
// Per-token-per-chain configuration
|
|
94
|
+
payToByToken?: Record<string, Record<string, string>>;
|
|
95
|
+
facilitatorUrlByToken?: Record<string, Record<string, string>>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### RouteAwarePaymentConfig
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface RouteAwarePaymentConfig extends PaymentConfig {
|
|
103
|
+
route?: string; // Single exact route (no wildcards)
|
|
104
|
+
routes?: string[]; // Multiple routes (allows wildcards)
|
|
105
|
+
perRoute?: Record<string, Partial<PaymentConfig>>;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Payment Context
|
|
110
|
+
|
|
111
|
+
The middleware adds payment information to the Hono context:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
app.get("/api/data", (c) => {
|
|
115
|
+
const payment = c.get("payment");
|
|
116
|
+
// payment.payload: PaymentPayloadV2
|
|
117
|
+
// payment.verified: boolean
|
|
118
|
+
// payment.route: string (only for route-aware middleware)
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Input Formats
|
|
123
|
+
|
|
124
|
+
**Networks** - Use any format:
|
|
125
|
+
```typescript
|
|
126
|
+
'base' // name
|
|
127
|
+
8453 // chain ID
|
|
128
|
+
'eip155:8453' // CAIP-2
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Tokens** - Use any format:
|
|
132
|
+
```typescript
|
|
133
|
+
'usdc' // symbol (case-insensitive)
|
|
134
|
+
'0x8335...' // EVM address
|
|
135
|
+
'eip155:8453/erc20:0x8335...' // CAIP Asset ID
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Route Pattern Matching
|
|
139
|
+
|
|
140
|
+
- **Exact**: `/api/users` - matches only `/api/users`
|
|
141
|
+
- **Wildcard**: `/api/*` - matches `/api/users`, `/api/posts/123`
|
|
142
|
+
- **Parameterized**: `/api/users/:id` - matches `/api/users/123`
|
|
143
|
+
|
|
144
|
+
Priority order: Exact matches > Parameterized routes > Wildcard routes
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
### Multi-Network Support
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
app.use("/*", paymentMiddleware({
|
|
152
|
+
payTo: "0xYourAddress...",
|
|
153
|
+
chains: ["base", "ethereum", "skale-base"],
|
|
154
|
+
tokens: ["usdc", "eurc"],
|
|
155
|
+
amount: "1.0"
|
|
156
|
+
}));
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Per-Chain Configuration
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
app.use("/*", paymentMiddleware({
|
|
163
|
+
payTo: "0xDefaultAddress...",
|
|
164
|
+
payToByChain: {
|
|
165
|
+
base: "0xBaseAddress...",
|
|
166
|
+
ethereum: "0xEthAddress...",
|
|
167
|
+
},
|
|
168
|
+
chain: "base",
|
|
169
|
+
token: "usdc"
|
|
170
|
+
}));
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Route-Specific Pricing
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
app.use("/api/*", routeAwarePaymentMiddleware({
|
|
177
|
+
routes: ["/api/basic", "/api/pro", "/api/enterprise"],
|
|
178
|
+
payTo: "0xYourAddress...",
|
|
179
|
+
amount: "$1.00",
|
|
180
|
+
network: "base",
|
|
181
|
+
perRoute: {
|
|
182
|
+
"/api/pro": {
|
|
183
|
+
amount: "$5.00",
|
|
184
|
+
},
|
|
185
|
+
"/api/enterprise": {
|
|
186
|
+
amount: "$50.00",
|
|
187
|
+
network: "ethereum",
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## API
|
|
194
|
+
|
|
195
|
+
### `paymentMiddleware(config)`
|
|
196
|
+
|
|
197
|
+
Creates a basic payment middleware for Hono.
|
|
198
|
+
|
|
199
|
+
**Parameters:**
|
|
200
|
+
- `config`: Payment configuration
|
|
201
|
+
|
|
202
|
+
**Returns:** Hono middleware function
|
|
203
|
+
|
|
204
|
+
### `createPaymentRequirements(config)`
|
|
205
|
+
|
|
206
|
+
Creates payment requirements from configuration (for advanced use).
|
|
207
|
+
|
|
208
|
+
**Parameters:**
|
|
209
|
+
- `config`: Payment configuration
|
|
210
|
+
|
|
211
|
+
**Returns:** Object with `requirements` array and optional `error`
|
|
212
|
+
|
|
213
|
+
### `routeAwarePaymentMiddleware(config)`
|
|
214
|
+
|
|
215
|
+
Creates a route-aware payment middleware for Hono.
|
|
216
|
+
|
|
217
|
+
**Parameters:**
|
|
218
|
+
- `config`: Route-aware payment configuration
|
|
219
|
+
|
|
220
|
+
**Returns:** Hono middleware function
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Extensions } from '@armory-sh/base';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extension handling for Hono middleware
|
|
5
|
+
* Integrates with @armory-sh/extensions package
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type BazaarDiscoveryConfig = {
|
|
9
|
+
input?: unknown;
|
|
10
|
+
inputSchema?: Record<string, unknown>;
|
|
11
|
+
bodyType?: "json" | "form-data" | "text";
|
|
12
|
+
output?: {
|
|
13
|
+
example?: unknown;
|
|
14
|
+
schema?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type SIWxExtensionConfig = {
|
|
18
|
+
domain?: string;
|
|
19
|
+
resourceUri?: string;
|
|
20
|
+
network?: string | string[];
|
|
21
|
+
statement?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
expirationSeconds?: number;
|
|
24
|
+
};
|
|
25
|
+
type Extension<T = unknown> = {
|
|
26
|
+
info: T;
|
|
27
|
+
schema: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
interface ExtensionConfig {
|
|
30
|
+
bazaar?: BazaarDiscoveryConfig;
|
|
31
|
+
signInWithX?: SIWxExtensionConfig;
|
|
32
|
+
}
|
|
33
|
+
interface PaymentConfigWithExtensions {
|
|
34
|
+
extensions?: ExtensionConfig;
|
|
35
|
+
}
|
|
36
|
+
declare function buildExtensions(config: ExtensionConfig): Extensions;
|
|
37
|
+
declare function extractExtension<T>(extensions: Extensions | undefined, key: string): Extension<T> | null;
|
|
38
|
+
|
|
39
|
+
export { type ExtensionConfig, type PaymentConfigWithExtensions, buildExtensions, extractExtension };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/extensions.ts
|
|
2
|
+
var declareDiscoveryExtension = (config = {}) => {
|
|
3
|
+
const info = {};
|
|
4
|
+
if (config.input !== void 0) {
|
|
5
|
+
info.input = config.input;
|
|
6
|
+
}
|
|
7
|
+
if (config.inputSchema !== void 0) {
|
|
8
|
+
info.inputSchema = config.inputSchema;
|
|
9
|
+
}
|
|
10
|
+
if (config.output !== void 0) {
|
|
11
|
+
info.output = config.output;
|
|
12
|
+
}
|
|
13
|
+
return { info, schema: { type: "object" } };
|
|
14
|
+
};
|
|
15
|
+
var declareSIWxExtension = (config = {}) => {
|
|
16
|
+
const info = {};
|
|
17
|
+
if (config.domain !== void 0) {
|
|
18
|
+
info.domain = config.domain;
|
|
19
|
+
}
|
|
20
|
+
if (config.resourceUri !== void 0) {
|
|
21
|
+
info.resourceUri = config.resourceUri;
|
|
22
|
+
}
|
|
23
|
+
if (config.network !== void 0) {
|
|
24
|
+
info.network = config.network;
|
|
25
|
+
}
|
|
26
|
+
if (config.statement !== void 0) {
|
|
27
|
+
info.statement = config.statement;
|
|
28
|
+
}
|
|
29
|
+
if (config.version !== void 0) {
|
|
30
|
+
info.version = config.version;
|
|
31
|
+
}
|
|
32
|
+
if (config.expirationSeconds !== void 0) {
|
|
33
|
+
info.expirationSeconds = config.expirationSeconds;
|
|
34
|
+
}
|
|
35
|
+
return { info, schema: { type: "object" } };
|
|
36
|
+
};
|
|
37
|
+
function buildExtensions(config) {
|
|
38
|
+
const extensions = {};
|
|
39
|
+
if (config.bazaar) {
|
|
40
|
+
extensions.bazaar = declareDiscoveryExtension(config.bazaar);
|
|
41
|
+
}
|
|
42
|
+
if (config.signInWithX) {
|
|
43
|
+
extensions["sign-in-with-x"] = declareSIWxExtension(config.signInWithX);
|
|
44
|
+
}
|
|
45
|
+
return extensions;
|
|
46
|
+
}
|
|
47
|
+
function extractExtension(extensions, key) {
|
|
48
|
+
if (!extensions || typeof extensions !== "object") {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const extension = extensions[key];
|
|
52
|
+
if (!extension || typeof extension !== "object") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return extension;
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
buildExtensions,
|
|
59
|
+
extractExtension
|
|
60
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Context, Next } from 'hono';
|
|
2
2
|
import { PaymentRequirementsV2, ValidationError, PaymentRequirements, PaymentPayloadV2 } from '@armory-sh/base';
|
|
3
|
+
import { ExtensionConfig } from './extensions.js';
|
|
4
|
+
export { buildExtensions, extractExtension } from './extensions.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Simplified middleware API for easy x402 payment integration
|
|
@@ -16,6 +18,12 @@ interface PaymentConfig {
|
|
|
16
18
|
token?: TokenId;
|
|
17
19
|
amount?: string;
|
|
18
20
|
maxTimeoutSeconds?: number;
|
|
21
|
+
payToByChain?: Record<NetworkId, string>;
|
|
22
|
+
payToByToken?: Record<NetworkId, Record<TokenId, string>>;
|
|
23
|
+
facilitatorUrl?: string;
|
|
24
|
+
facilitatorUrlByChain?: Record<NetworkId, string>;
|
|
25
|
+
facilitatorUrlByToken?: Record<NetworkId, Record<TokenId, string>>;
|
|
26
|
+
extensions?: ExtensionConfig;
|
|
19
27
|
}
|
|
20
28
|
interface ResolvedSimpleConfig {
|
|
21
29
|
requirements: PaymentRequirementsV2[];
|
|
@@ -24,10 +32,16 @@ interface ResolvedSimpleConfig {
|
|
|
24
32
|
declare function createPaymentRequirements(config: PaymentConfig): ResolvedSimpleConfig;
|
|
25
33
|
declare function paymentMiddleware(config: PaymentConfig): (c: Context, next: Next) => Promise<Response | void>;
|
|
26
34
|
|
|
35
|
+
interface RouteAwarePaymentConfig extends PaymentConfig {
|
|
36
|
+
route?: string;
|
|
37
|
+
routes?: string[];
|
|
38
|
+
perRoute?: Record<string, Partial<PaymentConfig>>;
|
|
39
|
+
}
|
|
40
|
+
declare const routeAwarePaymentMiddleware: (config: RouteAwarePaymentConfig) => (c: Context, next: Next) => Promise<Response | void>;
|
|
41
|
+
|
|
27
42
|
interface AdvancedPaymentConfig {
|
|
28
43
|
requirements: PaymentRequirements;
|
|
29
44
|
facilitatorUrl?: string;
|
|
30
|
-
skipVerification?: boolean;
|
|
31
45
|
network?: string;
|
|
32
46
|
}
|
|
33
47
|
interface AugmentedRequest extends Request {
|
|
@@ -39,4 +53,4 @@ interface AugmentedRequest extends Request {
|
|
|
39
53
|
}
|
|
40
54
|
declare const advancedPaymentMiddleware: (config: AdvancedPaymentConfig) => (c: Context, next: Next) => Promise<Response | void>;
|
|
41
55
|
|
|
42
|
-
export { type AdvancedPaymentConfig, type AugmentedRequest, type PaymentConfig, advancedPaymentMiddleware, createPaymentRequirements, paymentMiddleware };
|
|
56
|
+
export { type AdvancedPaymentConfig, type AugmentedRequest, ExtensionConfig, type PaymentConfig, type RouteAwarePaymentConfig, advancedPaymentMiddleware, createPaymentRequirements, paymentMiddleware, routeAwarePaymentMiddleware };
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,65 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
TOKENS
|
|
18
18
|
} from "@armory-sh/base";
|
|
19
|
+
|
|
20
|
+
// src/extensions.ts
|
|
21
|
+
var declareDiscoveryExtension = (config = {}) => {
|
|
22
|
+
const info = {};
|
|
23
|
+
if (config.input !== void 0) {
|
|
24
|
+
info.input = config.input;
|
|
25
|
+
}
|
|
26
|
+
if (config.inputSchema !== void 0) {
|
|
27
|
+
info.inputSchema = config.inputSchema;
|
|
28
|
+
}
|
|
29
|
+
if (config.output !== void 0) {
|
|
30
|
+
info.output = config.output;
|
|
31
|
+
}
|
|
32
|
+
return { info, schema: { type: "object" } };
|
|
33
|
+
};
|
|
34
|
+
var declareSIWxExtension = (config = {}) => {
|
|
35
|
+
const info = {};
|
|
36
|
+
if (config.domain !== void 0) {
|
|
37
|
+
info.domain = config.domain;
|
|
38
|
+
}
|
|
39
|
+
if (config.resourceUri !== void 0) {
|
|
40
|
+
info.resourceUri = config.resourceUri;
|
|
41
|
+
}
|
|
42
|
+
if (config.network !== void 0) {
|
|
43
|
+
info.network = config.network;
|
|
44
|
+
}
|
|
45
|
+
if (config.statement !== void 0) {
|
|
46
|
+
info.statement = config.statement;
|
|
47
|
+
}
|
|
48
|
+
if (config.version !== void 0) {
|
|
49
|
+
info.version = config.version;
|
|
50
|
+
}
|
|
51
|
+
if (config.expirationSeconds !== void 0) {
|
|
52
|
+
info.expirationSeconds = config.expirationSeconds;
|
|
53
|
+
}
|
|
54
|
+
return { info, schema: { type: "object" } };
|
|
55
|
+
};
|
|
56
|
+
function buildExtensions(config) {
|
|
57
|
+
const extensions = {};
|
|
58
|
+
if (config.bazaar) {
|
|
59
|
+
extensions.bazaar = declareDiscoveryExtension(config.bazaar);
|
|
60
|
+
}
|
|
61
|
+
if (config.signInWithX) {
|
|
62
|
+
extensions["sign-in-with-x"] = declareSIWxExtension(config.signInWithX);
|
|
63
|
+
}
|
|
64
|
+
return extensions;
|
|
65
|
+
}
|
|
66
|
+
function extractExtension(extensions, key) {
|
|
67
|
+
if (!extensions || typeof extensions !== "object") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const extension = extensions[key];
|
|
71
|
+
if (!extension || typeof extension !== "object") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return extension;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/simple.ts
|
|
19
78
|
var isValidationError = (value) => {
|
|
20
79
|
return typeof value === "object" && value !== null && "code" in value;
|
|
21
80
|
};
|
|
@@ -83,6 +142,56 @@ function toAtomicUnits(amount) {
|
|
|
83
142
|
}
|
|
84
143
|
return `${amount}000000`;
|
|
85
144
|
}
|
|
145
|
+
function resolvePayTo(config, network, token) {
|
|
146
|
+
const chainId = network.config.chainId;
|
|
147
|
+
if (config.payToByToken) {
|
|
148
|
+
for (const [chainKey, tokenMap] of Object.entries(config.payToByToken)) {
|
|
149
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
150
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
151
|
+
for (const [tokenKey, address] of Object.entries(tokenMap)) {
|
|
152
|
+
const resolvedToken = resolveToken(tokenKey, network);
|
|
153
|
+
if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
|
|
154
|
+
return address;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (config.payToByChain) {
|
|
161
|
+
for (const [chainKey, address] of Object.entries(config.payToByChain)) {
|
|
162
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
163
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
164
|
+
return address;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return config.payTo;
|
|
169
|
+
}
|
|
170
|
+
function resolveFacilitatorUrl(config, network, token) {
|
|
171
|
+
const chainId = network.config.chainId;
|
|
172
|
+
if (config.facilitatorUrlByToken) {
|
|
173
|
+
for (const [chainKey, tokenMap] of Object.entries(config.facilitatorUrlByToken)) {
|
|
174
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
175
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
176
|
+
for (const [tokenKey, url] of Object.entries(tokenMap)) {
|
|
177
|
+
const resolvedToken = resolveToken(tokenKey, network);
|
|
178
|
+
if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
|
|
179
|
+
return url;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (config.facilitatorUrlByChain) {
|
|
186
|
+
for (const [chainKey, url] of Object.entries(config.facilitatorUrlByChain)) {
|
|
187
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
188
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
189
|
+
return url;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return config.facilitatorUrl;
|
|
194
|
+
}
|
|
86
195
|
function createPaymentRequirements(config) {
|
|
87
196
|
ensureTokensRegistered();
|
|
88
197
|
const {
|
|
@@ -111,16 +220,19 @@ function createPaymentRequirements(config) {
|
|
|
111
220
|
}
|
|
112
221
|
const atomicAmount = toAtomicUnits(amount);
|
|
113
222
|
const tokenConfig = resolvedToken.config;
|
|
223
|
+
const resolvedPayTo = resolvePayTo(config, network, resolvedToken);
|
|
224
|
+
const resolvedFacilitatorUrl = resolveFacilitatorUrl(config, network, resolvedToken);
|
|
114
225
|
requirements.push({
|
|
115
226
|
scheme: "exact",
|
|
116
227
|
network: network.caip2,
|
|
117
228
|
amount: atomicAmount,
|
|
118
229
|
asset: tokenConfig.contractAddress,
|
|
119
|
-
payTo,
|
|
230
|
+
payTo: resolvedPayTo,
|
|
120
231
|
maxTimeoutSeconds,
|
|
121
232
|
extra: {
|
|
122
233
|
name: tokenConfig.name,
|
|
123
|
-
version: tokenConfig.version
|
|
234
|
+
version: tokenConfig.version,
|
|
235
|
+
...resolvedFacilitatorUrl && { facilitatorUrl: resolvedFacilitatorUrl }
|
|
124
236
|
}
|
|
125
237
|
});
|
|
126
238
|
}
|
|
@@ -148,7 +260,8 @@ function paymentMiddleware(config) {
|
|
|
148
260
|
x402Version: 2,
|
|
149
261
|
error: "Payment required",
|
|
150
262
|
resource,
|
|
151
|
-
accepts: requirements
|
|
263
|
+
accepts: requirements,
|
|
264
|
+
extensions: config.extensions ? buildExtensions(config.extensions) : void 0
|
|
152
265
|
};
|
|
153
266
|
c.status(402);
|
|
154
267
|
c.header(V2_HEADERS.PAYMENT_REQUIRED, safeBase64Encode(JSON.stringify(paymentRequired)));
|
|
@@ -178,9 +291,104 @@ function paymentMiddleware(config) {
|
|
|
178
291
|
};
|
|
179
292
|
}
|
|
180
293
|
|
|
294
|
+
// src/routes.ts
|
|
295
|
+
import {
|
|
296
|
+
safeBase64Encode as safeBase64Encode2,
|
|
297
|
+
V2_HEADERS as V2_HEADERS2,
|
|
298
|
+
matchRoute,
|
|
299
|
+
validateRouteConfig
|
|
300
|
+
} from "@armory-sh/base";
|
|
301
|
+
var resolveRouteConfig = (config) => {
|
|
302
|
+
const validationError = validateRouteConfig(config);
|
|
303
|
+
if (validationError) {
|
|
304
|
+
return { routes: [], error: validationError };
|
|
305
|
+
}
|
|
306
|
+
const { route, routes, perRoute, ...baseConfig } = config;
|
|
307
|
+
const routePatterns = route ? [route] : routes || [];
|
|
308
|
+
const resolvedRoutes = [];
|
|
309
|
+
for (const pattern of routePatterns) {
|
|
310
|
+
const perRouteOverride = perRoute?.[pattern];
|
|
311
|
+
const mergedConfig = {
|
|
312
|
+
...baseConfig,
|
|
313
|
+
...perRouteOverride
|
|
314
|
+
};
|
|
315
|
+
resolvedRoutes.push({
|
|
316
|
+
pattern,
|
|
317
|
+
config: mergedConfig
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return { routes: resolvedRoutes };
|
|
321
|
+
};
|
|
322
|
+
var routeAwarePaymentMiddleware = (config) => {
|
|
323
|
+
const { routes, error } = resolveRouteConfig(config);
|
|
324
|
+
return async (c, next) => {
|
|
325
|
+
if (error) {
|
|
326
|
+
c.status(500);
|
|
327
|
+
return c.json({
|
|
328
|
+
error: "Payment middleware configuration error",
|
|
329
|
+
details: error.message
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
const path = new URL(c.req.url).pathname;
|
|
333
|
+
const matchedRoute = routes.find((r) => matchRoute(r.pattern, path));
|
|
334
|
+
if (!matchedRoute) {
|
|
335
|
+
return next();
|
|
336
|
+
}
|
|
337
|
+
const { requirements, error: requirementsError } = createPaymentRequirements(matchedRoute.config);
|
|
338
|
+
if (requirementsError) {
|
|
339
|
+
c.status(500);
|
|
340
|
+
return c.json({
|
|
341
|
+
error: "Payment middleware configuration error",
|
|
342
|
+
details: requirementsError.message
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const paymentHeader = c.req.header(V2_HEADERS2.PAYMENT_SIGNATURE);
|
|
346
|
+
if (!paymentHeader) {
|
|
347
|
+
const resource = {
|
|
348
|
+
url: c.req.url,
|
|
349
|
+
description: "API Access",
|
|
350
|
+
mimeType: "application/json"
|
|
351
|
+
};
|
|
352
|
+
const paymentRequired = {
|
|
353
|
+
x402Version: 2,
|
|
354
|
+
error: "Payment required",
|
|
355
|
+
resource,
|
|
356
|
+
accepts: requirements
|
|
357
|
+
};
|
|
358
|
+
c.status(402);
|
|
359
|
+
c.header(
|
|
360
|
+
V2_HEADERS2.PAYMENT_REQUIRED,
|
|
361
|
+
safeBase64Encode2(JSON.stringify(paymentRequired))
|
|
362
|
+
);
|
|
363
|
+
return c.json({
|
|
364
|
+
error: "Payment required",
|
|
365
|
+
accepts: requirements
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
let parsedPayload;
|
|
369
|
+
try {
|
|
370
|
+
const decoded = atob(paymentHeader);
|
|
371
|
+
parsedPayload = JSON.parse(decoded);
|
|
372
|
+
} catch {
|
|
373
|
+
try {
|
|
374
|
+
parsedPayload = JSON.parse(paymentHeader);
|
|
375
|
+
} catch {
|
|
376
|
+
c.status(400);
|
|
377
|
+
return c.json({ error: "Invalid payment payload" });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
c.set("payment", {
|
|
381
|
+
payload: parsedPayload,
|
|
382
|
+
verified: false,
|
|
383
|
+
route: matchedRoute.pattern
|
|
384
|
+
});
|
|
385
|
+
return next();
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
|
|
181
389
|
// src/index.ts
|
|
182
390
|
var advancedPaymentMiddleware = (config) => {
|
|
183
|
-
const { requirements, facilitatorUrl,
|
|
391
|
+
const { requirements, facilitatorUrl, network = "base" } = config;
|
|
184
392
|
return async (c, next) => {
|
|
185
393
|
const paymentHeader = c.req.header(PAYMENT_SIGNATURE_HEADER);
|
|
186
394
|
if (!paymentHeader) {
|
|
@@ -209,13 +417,16 @@ var advancedPaymentMiddleware = (config) => {
|
|
|
209
417
|
c.set("payment", {
|
|
210
418
|
payload: paymentPayload,
|
|
211
419
|
payerAddress,
|
|
212
|
-
verified:
|
|
420
|
+
verified: true
|
|
213
421
|
});
|
|
214
422
|
return next();
|
|
215
423
|
};
|
|
216
424
|
};
|
|
217
425
|
export {
|
|
218
426
|
advancedPaymentMiddleware,
|
|
427
|
+
buildExtensions,
|
|
219
428
|
createPaymentRequirements,
|
|
220
|
-
|
|
429
|
+
extractExtension,
|
|
430
|
+
paymentMiddleware,
|
|
431
|
+
routeAwarePaymentMiddleware
|
|
221
432
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@armory-sh/middleware-hono",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"bun": "./src/index.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./extensions": {
|
|
16
|
+
"types": "./dist/extensions.d.ts",
|
|
17
|
+
"bun": "./src/extensions.ts",
|
|
18
|
+
"default": "./dist/extensions.js"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
@@ -28,10 +33,12 @@
|
|
|
28
33
|
"hono": "^4"
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
|
-
"@armory-sh/base": "0.2.
|
|
36
|
+
"@armory-sh/base": "0.2.21",
|
|
37
|
+
"@armory-sh/extensions": "0.1.2"
|
|
32
38
|
},
|
|
33
39
|
"devDependencies": {
|
|
34
40
|
"bun-types": "latest",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
35
42
|
"typescript": "5.9.3",
|
|
36
43
|
"hono": "^4"
|
|
37
44
|
},
|