@armory-sh/middleware-hono 0.3.18 → 0.3.20

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 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,10 +1,47 @@
1
1
  import { Context, Next } from 'hono';
2
- import { PaymentPayloadV2, PaymentRequirements } from '@armory-sh/base';
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
- interface PaymentMiddlewareConfig {
6
+ /**
7
+ * Simplified middleware API for easy x402 payment integration
8
+ * Just specify payTo address and optional chains/tokens by name
9
+ */
10
+
11
+ type NetworkId = string | number;
12
+ type TokenId = string;
13
+ interface PaymentConfig {
14
+ payTo: string;
15
+ chains?: NetworkId[];
16
+ chain?: NetworkId;
17
+ tokens?: TokenId[];
18
+ token?: TokenId;
19
+ amount?: string;
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;
27
+ }
28
+ interface ResolvedSimpleConfig {
29
+ requirements: PaymentRequirementsV2[];
30
+ error?: ValidationError;
31
+ }
32
+ declare function createPaymentRequirements(config: PaymentConfig): ResolvedSimpleConfig;
33
+ declare function paymentMiddleware(config: PaymentConfig): (c: Context, next: Next) => Promise<Response | void>;
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
+
42
+ interface AdvancedPaymentConfig {
5
43
  requirements: PaymentRequirements;
6
44
  facilitatorUrl?: string;
7
- skipVerification?: boolean;
8
45
  network?: string;
9
46
  }
10
47
  interface AugmentedRequest extends Request {
@@ -14,6 +51,6 @@ interface AugmentedRequest extends Request {
14
51
  verified: boolean;
15
52
  };
16
53
  }
17
- declare const paymentMiddleware: (config: PaymentMiddlewareConfig) => (c: Context, next: Next) => Promise<Response | void>;
54
+ declare const advancedPaymentMiddleware: (config: AdvancedPaymentConfig) => (c: Context, next: Next) => Promise<Response | void>;
18
55
 
19
- export { type AugmentedRequest, type PaymentMiddlewareConfig, paymentMiddleware };
56
+ export { type AdvancedPaymentConfig, type AugmentedRequest, ExtensionConfig, type PaymentConfig, type RouteAwarePaymentConfig, advancedPaymentMiddleware, createPaymentRequirements, paymentMiddleware, routeAwarePaymentMiddleware };
package/dist/index.js CHANGED
@@ -1,12 +1,394 @@
1
1
  // src/index.ts
2
2
  import {
3
3
  createPaymentRequiredHeaders,
4
- createSettlementHeaders,
5
4
  PAYMENT_SIGNATURE_HEADER,
6
5
  decodePaymentV2
7
6
  } from "@armory-sh/base";
8
- var paymentMiddleware = (config) => {
9
- const { requirements, facilitatorUrl, skipVerification = false, network = "base" } = config;
7
+
8
+ // src/simple.ts
9
+ import {
10
+ resolveNetwork,
11
+ resolveToken,
12
+ safeBase64Encode,
13
+ V2_HEADERS,
14
+ registerToken
15
+ } from "@armory-sh/base";
16
+ import {
17
+ TOKENS
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
78
+ var isValidationError = (value) => {
79
+ return typeof value === "object" && value !== null && "code" in value;
80
+ };
81
+ function ensureTokensRegistered() {
82
+ for (const token of Object.values(TOKENS)) {
83
+ try {
84
+ registerToken(token);
85
+ } catch {
86
+ }
87
+ }
88
+ }
89
+ function resolveChainTokenInputs(chains, tokens) {
90
+ const resolvedNetworks = [];
91
+ const resolvedTokens = [];
92
+ const errors = [];
93
+ const networkInputs = chains?.length ? chains : Object.keys({
94
+ ethereum: 1,
95
+ base: 8453,
96
+ "base-sepolia": 84532,
97
+ "skale-base": 1187947933,
98
+ "skale-base-sepolia": 324705682,
99
+ "ethereum-sepolia": 11155111
100
+ });
101
+ for (const networkId of networkInputs) {
102
+ const resolved = resolveNetwork(networkId);
103
+ if (isValidationError(resolved)) {
104
+ errors.push(`Network "${networkId}": ${resolved.message}`);
105
+ } else {
106
+ resolvedNetworks.push(resolved);
107
+ }
108
+ }
109
+ const tokenInputs = tokens?.length ? tokens : ["usdc"];
110
+ for (const tokenId of tokenInputs) {
111
+ let found = false;
112
+ for (const network of resolvedNetworks) {
113
+ const resolved = resolveToken(tokenId, network);
114
+ if (isValidationError(resolved)) {
115
+ continue;
116
+ }
117
+ resolvedTokens.push(resolved);
118
+ found = true;
119
+ break;
120
+ }
121
+ if (!found) {
122
+ errors.push(`Token "${tokenId}" not found on any specified network`);
123
+ }
124
+ }
125
+ if (errors.length > 0) {
126
+ return {
127
+ networks: resolvedNetworks,
128
+ tokens: resolvedTokens,
129
+ error: {
130
+ code: "VALIDATION_FAILED",
131
+ message: errors.join("; ")
132
+ }
133
+ };
134
+ }
135
+ return { networks: resolvedNetworks, tokens: resolvedTokens };
136
+ }
137
+ function toAtomicUnits(amount) {
138
+ if (amount.includes(".")) {
139
+ const [whole, fractional = ""] = amount.split(".");
140
+ const paddedFractional = fractional.padEnd(6, "0").slice(0, 6);
141
+ return `${whole}${paddedFractional}`.replace(/^0+/, "") || "0";
142
+ }
143
+ return `${amount}000000`;
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
+ }
195
+ function createPaymentRequirements(config) {
196
+ ensureTokensRegistered();
197
+ const {
198
+ payTo,
199
+ chains,
200
+ chain,
201
+ tokens,
202
+ token,
203
+ amount = "1.0",
204
+ maxTimeoutSeconds = 300
205
+ } = config;
206
+ const chainInputs = chain ? [chain] : chains;
207
+ const tokenInputs = token ? [token] : tokens;
208
+ const { networks, tokens: resolvedTokens, error } = resolveChainTokenInputs(
209
+ chainInputs,
210
+ tokenInputs
211
+ );
212
+ if (error) {
213
+ return { requirements: [], error };
214
+ }
215
+ const requirements = [];
216
+ for (const network of networks) {
217
+ for (const resolvedToken of resolvedTokens) {
218
+ if (resolvedToken.network.config.chainId !== network.config.chainId) {
219
+ continue;
220
+ }
221
+ const atomicAmount = toAtomicUnits(amount);
222
+ const tokenConfig = resolvedToken.config;
223
+ const resolvedPayTo = resolvePayTo(config, network, resolvedToken);
224
+ const resolvedFacilitatorUrl = resolveFacilitatorUrl(config, network, resolvedToken);
225
+ requirements.push({
226
+ scheme: "exact",
227
+ network: network.caip2,
228
+ amount: atomicAmount,
229
+ asset: tokenConfig.contractAddress,
230
+ payTo: resolvedPayTo,
231
+ maxTimeoutSeconds,
232
+ extra: {
233
+ name: tokenConfig.name,
234
+ version: tokenConfig.version,
235
+ ...resolvedFacilitatorUrl && { facilitatorUrl: resolvedFacilitatorUrl }
236
+ }
237
+ });
238
+ }
239
+ }
240
+ return { requirements };
241
+ }
242
+ function paymentMiddleware(config) {
243
+ const { requirements, error } = createPaymentRequirements(config);
244
+ return async (c, next) => {
245
+ if (error) {
246
+ c.status(500);
247
+ return c.json({
248
+ error: "Payment middleware configuration error",
249
+ details: error.message
250
+ });
251
+ }
252
+ const paymentHeader = c.req.header(V2_HEADERS.PAYMENT_SIGNATURE);
253
+ if (!paymentHeader) {
254
+ const resource = {
255
+ url: c.req.url,
256
+ description: "API Access",
257
+ mimeType: "application/json"
258
+ };
259
+ const paymentRequired = {
260
+ x402Version: 2,
261
+ error: "Payment required",
262
+ resource,
263
+ accepts: requirements,
264
+ extensions: config.extensions ? buildExtensions(config.extensions) : void 0
265
+ };
266
+ c.status(402);
267
+ c.header(V2_HEADERS.PAYMENT_REQUIRED, safeBase64Encode(JSON.stringify(paymentRequired)));
268
+ return c.json({
269
+ error: "Payment required",
270
+ accepts: requirements
271
+ });
272
+ }
273
+ const payload = paymentHeader;
274
+ let parsedPayload;
275
+ try {
276
+ const decoded = atob(payload);
277
+ parsedPayload = JSON.parse(decoded);
278
+ } catch {
279
+ try {
280
+ parsedPayload = JSON.parse(payload);
281
+ } catch {
282
+ c.status(400);
283
+ return c.json({ error: "Invalid payment payload" });
284
+ }
285
+ }
286
+ c.set("payment", {
287
+ payload: parsedPayload,
288
+ verified: false
289
+ });
290
+ return next();
291
+ };
292
+ }
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
+
389
+ // src/index.ts
390
+ var advancedPaymentMiddleware = (config) => {
391
+ const { requirements, facilitatorUrl, network = "base" } = config;
10
392
  return async (c, next) => {
11
393
  const paymentHeader = c.req.header(PAYMENT_SIGNATURE_HEADER);
12
394
  if (!paymentHeader) {
@@ -22,11 +404,8 @@ var paymentMiddleware = (config) => {
22
404
  }
23
405
  let paymentPayload = null;
24
406
  try {
25
- console.log("[x402 Debug] Raw payment header:", paymentHeader);
26
407
  paymentPayload = decodePaymentV2(paymentHeader);
27
- console.log("[x402 Debug] Decoded payload:", JSON.stringify(paymentPayload, null, 2));
28
408
  } catch (e) {
29
- console.error("[x402 Debug] Decode error:", e);
30
409
  c.status(400);
31
410
  return c.json({ error: "Invalid payment payload", details: String(e) });
32
411
  }
@@ -35,23 +414,19 @@ var paymentMiddleware = (config) => {
35
414
  return c.json({ error: "Invalid payment payload" });
36
415
  }
37
416
  const payerAddress = paymentPayload.payload.authorization.from;
38
- const settlement = {
39
- success: true,
40
- transaction: "",
41
- network
42
- };
43
- const settlementHeaders = createSettlementHeaders(settlement);
44
- for (const [key, value] of Object.entries(settlementHeaders)) {
45
- c.header(key, value);
46
- }
47
417
  c.set("payment", {
48
418
  payload: paymentPayload,
49
419
  payerAddress,
50
- verified: !skipVerification
420
+ verified: true
51
421
  });
52
422
  return next();
53
423
  };
54
424
  };
55
425
  export {
56
- paymentMiddleware
426
+ advancedPaymentMiddleware,
427
+ buildExtensions,
428
+ createPaymentRequirements,
429
+ extractExtension,
430
+ paymentMiddleware,
431
+ routeAwarePaymentMiddleware
57
432
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/middleware-hono",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
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.18"
36
+ "@armory-sh/base": "0.2.20",
37
+ "@armory-sh/extensions": "0.1.1"
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
  },