@armory-sh/middleware-hono 0.3.28 → 0.3.30

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 CHANGED
@@ -14,24 +14,47 @@ bun add @armory-sh/middleware-hono
14
14
 
15
15
  Armory enables HTTP API payments via EIP-3009 `transferWithAuthorization`. Accept payments from any x402-compatible client—Coinbase SDK, Armory SDK, or your own implementation.
16
16
 
17
- ## Key Exports
17
+ ## API Reference
18
+
19
+ ### Middleware
18
20
 
19
21
  ```typescript
20
22
  import {
21
- // Middleware
22
23
  paymentMiddleware,
23
24
  routeAwarePaymentMiddleware,
24
25
 
25
- // Requirements
26
- createPaymentRequirements,
27
-
28
26
  // Types
29
27
  type PaymentConfig,
30
28
  type RouteAwarePaymentConfig,
31
- type HonoPaymentContext,
29
+ type AugmentedContext,
32
30
  } from '@armory-sh/middleware-hono';
33
31
  ```
34
32
 
33
+ ### Payment Requirements
34
+
35
+ ```typescript
36
+ import {
37
+ createPaymentRequirements,
38
+
39
+ // Types
40
+ type ResolvedRequirementsConfig,
41
+ } from '@armory-sh/middleware-hono';
42
+ ```
43
+
44
+ ### Extensions
45
+
46
+ ```typescript
47
+ import {
48
+ buildExtensions,
49
+ extractExtension,
50
+
51
+ // Types
52
+ type ExtensionConfig,
53
+ } from '@armory-sh/middleware-hono';
54
+ ```
55
+
56
+ ---
57
+
35
58
  ## Quick Start
36
59
 
37
60
  ### Basic Middleware
@@ -168,6 +191,7 @@ interface RouteAwarePaymentConfig extends PaymentConfig {
168
191
  | Base Sepolia | 84532 |
169
192
  | SKALE Base | 1187947933 |
170
193
  | SKALE Base Sepolia | 324705682 |
194
+ | Ethereum Sepolia | 11155111 |
171
195
 
172
196
  ## License
173
197
 
package/dist/index.d.ts CHANGED
@@ -1,56 +1,46 @@
1
+ import { X402PaymentPayload, PaymentRequirementsV2 } from '@armory-sh/base';
1
2
  import { Context, Next } from 'hono';
2
- import { PaymentRequirementsV2, ValidationError, PaymentRequirements, X402PaymentPayload } from '@armory-sh/base';
3
- import { ExtensionConfig } from './extensions.js';
4
- export { buildExtensions, extractExtension } from './extensions.js';
3
+ export { ExtensionConfig, buildExtensions, extractExtension } from './extensions.js';
5
4
 
6
- /**
7
- * Simplified middleware API for easy x402 payment integration
8
- * Just specify payTo address and optional chains/tokens by name
9
- */
5
+ interface RouteAwarePaymentConfig extends PaymentConfig {
6
+ route?: string;
7
+ routes?: string[];
8
+ perRoute?: Record<string, Partial<PaymentConfig>>;
9
+ }
10
+ declare const routeAwarePaymentMiddleware: (config: RouteAwarePaymentConfig) => (c: Context, next: Next) => Promise<Response | undefined>;
10
11
 
11
12
  type NetworkId = string | number;
12
13
  type TokenId = string;
13
14
  interface PaymentConfig {
14
- payTo: string;
15
+ payTo?: string;
16
+ requirements?: PaymentRequirementsV2 | PaymentRequirementsV2[];
15
17
  chains?: NetworkId[];
16
18
  chain?: NetworkId;
17
19
  tokens?: TokenId[];
18
20
  token?: TokenId;
19
21
  amount?: string;
20
22
  maxTimeoutSeconds?: number;
21
- payToByChain?: Record<NetworkId, string>;
22
- payToByToken?: Record<NetworkId, Record<TokenId, string>>;
23
23
  facilitatorUrl?: string;
24
- facilitatorUrlByChain?: Record<NetworkId, string>;
25
- facilitatorUrlByToken?: Record<NetworkId, Record<TokenId, string>>;
26
- extensions?: ExtensionConfig;
24
+ facilitatorUrlByChain?: Record<string, string>;
25
+ facilitatorUrlByToken?: Record<string, Record<string, string>>;
26
+ extensions?: Record<string, unknown>;
27
27
  }
28
- interface ResolvedSimpleConfig {
28
+ interface ResolvedRequirementsConfig {
29
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 {
43
- requirements: PaymentRequirements;
44
- facilitatorUrl: string;
45
- network?: string;
30
+ error?: {
31
+ code: string;
32
+ message: string;
33
+ };
46
34
  }
47
- interface AugmentedRequest extends Request {
35
+ interface AugmentedContext extends Context {
48
36
  payment?: {
49
37
  payload: X402PaymentPayload;
50
38
  payerAddress: string;
51
39
  verified: boolean;
52
40
  };
53
41
  }
54
- declare const advancedPaymentMiddleware: (config: AdvancedPaymentConfig) => (c: Context, next: Next) => Promise<Response | void>;
42
+ declare function resolveFacilitatorUrlFromRequirement(config: PaymentConfig, requirement: PaymentRequirementsV2): string | undefined;
43
+ declare function createPaymentRequirements(config: PaymentConfig): ResolvedRequirementsConfig;
44
+ declare function paymentMiddleware(config: PaymentConfig): (c: Context, next: Next) => Promise<Response | undefined>;
55
45
 
56
- export { type AdvancedPaymentConfig, type AugmentedRequest, ExtensionConfig, type PaymentConfig, type RouteAwarePaymentConfig, advancedPaymentMiddleware, createPaymentRequirements, paymentMiddleware, routeAwarePaymentMiddleware };
46
+ export { type AugmentedContext, type PaymentConfig, type ResolvedRequirementsConfig, type RouteAwarePaymentConfig, createPaymentRequirements, paymentMiddleware, resolveFacilitatorUrlFromRequirement, routeAwarePaymentMiddleware };
package/dist/index.js CHANGED
@@ -1,294 +1,6 @@
1
- import { buildExtensions } from './chunk-XYM6YXAQ.js';
2
1
  export { buildExtensions, extractExtension } from './chunk-XYM6YXAQ.js';
3
- import { V2_HEADERS, safeBase64Encode, decodePayloadHeader, verifyPayment, createPaymentRequiredHeaders, settlePayment, createSettlementHeaders, matchRoute, PAYMENT_SIGNATURE_HEADER, TOKENS, registerToken, resolveNetwork, resolveToken, validateRouteConfig } from '@armory-sh/base';
2
+ import { matchRoute, V2_HEADERS, safeBase64Encode, decodePayloadHeader, findRequirementByAccepted, verifyPayment, createPaymentRequiredHeaders, settlePayment, createSettlementHeaders, resolveNetwork, isValidationError, resolveToken, createPaymentRequirements as createPaymentRequirements$1, PAYMENT_SIGNATURE_HEADER, PAYMENT_REQUIRED_HEADER, validateRouteConfig, TOKENS, registerToken, getSupported } from '@armory-sh/base';
4
3
 
5
- var isValidationError = (value) => {
6
- return typeof value === "object" && value !== null && "code" in value;
7
- };
8
- function ensureTokensRegistered() {
9
- for (const token of Object.values(TOKENS)) {
10
- try {
11
- registerToken(token);
12
- } catch {
13
- }
14
- }
15
- }
16
- function resolveChainTokenInputs(chains, tokens) {
17
- const resolvedNetworks = [];
18
- const resolvedTokens = [];
19
- const errors = [];
20
- const networkInputs = chains?.length ? chains : Object.keys({
21
- ethereum: 1,
22
- base: 8453,
23
- "base-sepolia": 84532,
24
- "skale-base": 1187947933,
25
- "skale-base-sepolia": 324705682,
26
- "ethereum-sepolia": 11155111
27
- });
28
- for (const networkId of networkInputs) {
29
- const resolved = resolveNetwork(networkId);
30
- if (isValidationError(resolved)) {
31
- errors.push(`Network "${networkId}": ${resolved.message}`);
32
- } else {
33
- resolvedNetworks.push(resolved);
34
- }
35
- }
36
- const tokenInputs = tokens?.length ? tokens : ["usdc"];
37
- for (const tokenId of tokenInputs) {
38
- let found = false;
39
- for (const network of resolvedNetworks) {
40
- const resolved = resolveToken(tokenId, network);
41
- if (isValidationError(resolved)) {
42
- continue;
43
- }
44
- resolvedTokens.push(resolved);
45
- found = true;
46
- break;
47
- }
48
- if (!found) {
49
- errors.push(`Token "${tokenId}" not found on any specified network`);
50
- }
51
- }
52
- if (errors.length > 0) {
53
- return {
54
- networks: resolvedNetworks,
55
- tokens: resolvedTokens,
56
- error: {
57
- code: "VALIDATION_FAILED",
58
- message: errors.join("; ")
59
- }
60
- };
61
- }
62
- return { networks: resolvedNetworks, tokens: resolvedTokens };
63
- }
64
- function toAtomicUnits(amount) {
65
- if (amount.includes(".")) {
66
- const [whole, fractional = ""] = amount.split(".");
67
- const paddedFractional = fractional.padEnd(6, "0").slice(0, 6);
68
- return `${whole}${paddedFractional}`.replace(/^0+/, "") || "0";
69
- }
70
- return `${amount}000000`;
71
- }
72
- function resolvePayTo(config, network, token) {
73
- const chainId = network.config.chainId;
74
- if (config.payToByToken) {
75
- for (const [chainKey, tokenMap] of Object.entries(config.payToByToken)) {
76
- const resolvedChain = resolveNetwork(chainKey);
77
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
78
- for (const [tokenKey, address] of Object.entries(tokenMap)) {
79
- const resolvedToken = resolveToken(tokenKey, network);
80
- if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
81
- return address;
82
- }
83
- }
84
- }
85
- }
86
- }
87
- if (config.payToByChain) {
88
- for (const [chainKey, address] of Object.entries(config.payToByChain)) {
89
- const resolvedChain = resolveNetwork(chainKey);
90
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
91
- return address;
92
- }
93
- }
94
- }
95
- return config.payTo;
96
- }
97
- function resolveFacilitatorUrl(config, network, token) {
98
- const chainId = network.config.chainId;
99
- if (config.facilitatorUrlByToken) {
100
- for (const [chainKey, tokenMap] of Object.entries(config.facilitatorUrlByToken)) {
101
- const resolvedChain = resolveNetwork(chainKey);
102
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
103
- for (const [tokenKey, url] of Object.entries(tokenMap)) {
104
- const resolvedToken = resolveToken(tokenKey, network);
105
- if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
106
- return url;
107
- }
108
- }
109
- }
110
- }
111
- }
112
- if (config.facilitatorUrlByChain) {
113
- for (const [chainKey, url] of Object.entries(config.facilitatorUrlByChain)) {
114
- const resolvedChain = resolveNetwork(chainKey);
115
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
116
- return url;
117
- }
118
- }
119
- }
120
- return config.facilitatorUrl;
121
- }
122
- function resolveFacilitatorUrlFromRequirement(config, requirement) {
123
- const chainId = parseInt(requirement.network.split(":")[1] || "0", 10);
124
- const assetAddress = requirement.asset.toLowerCase();
125
- if (config.facilitatorUrlByToken) {
126
- for (const [chainKey, tokenMap] of Object.entries(config.facilitatorUrlByToken)) {
127
- const resolvedChain = resolveNetwork(chainKey);
128
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
129
- for (const [, url] of Object.entries(tokenMap)) {
130
- const network = resolveNetwork(chainKey);
131
- if (!isValidationError(network)) {
132
- for (const tokenKey of Object.keys(tokenMap)) {
133
- const resolvedToken = resolveToken(tokenKey, network);
134
- if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === assetAddress) {
135
- return url;
136
- }
137
- }
138
- }
139
- }
140
- }
141
- }
142
- }
143
- if (config.facilitatorUrlByChain) {
144
- for (const [chainKey, url] of Object.entries(config.facilitatorUrlByChain)) {
145
- const resolvedChain = resolveNetwork(chainKey);
146
- if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
147
- return url;
148
- }
149
- }
150
- }
151
- return config.facilitatorUrl;
152
- }
153
- function createPaymentRequirements(config) {
154
- ensureTokensRegistered();
155
- const {
156
- payTo,
157
- chains,
158
- chain,
159
- tokens,
160
- token,
161
- amount = "1.0",
162
- maxTimeoutSeconds = 300
163
- } = config;
164
- const chainInputs = chain ? [chain] : chains;
165
- const tokenInputs = token ? [token] : tokens;
166
- const { networks, tokens: resolvedTokens, error } = resolveChainTokenInputs(
167
- chainInputs,
168
- tokenInputs
169
- );
170
- if (error) {
171
- return { requirements: [], error };
172
- }
173
- const requirements = [];
174
- for (const network of networks) {
175
- for (const resolvedToken of resolvedTokens) {
176
- if (resolvedToken.network.config.chainId !== network.config.chainId) {
177
- continue;
178
- }
179
- const atomicAmount = toAtomicUnits(amount);
180
- const tokenConfig = resolvedToken.config;
181
- const resolvedPayTo = resolvePayTo(config, network, resolvedToken);
182
- resolveFacilitatorUrl(config, network, resolvedToken);
183
- requirements.push({
184
- scheme: "exact",
185
- network: network.caip2,
186
- amount: atomicAmount,
187
- asset: tokenConfig.contractAddress,
188
- payTo: resolvedPayTo,
189
- maxTimeoutSeconds,
190
- extra: {
191
- name: tokenConfig.name,
192
- version: tokenConfig.version
193
- }
194
- });
195
- }
196
- }
197
- return { requirements };
198
- }
199
- function paymentMiddleware(config) {
200
- const { requirements, error } = createPaymentRequirements(config);
201
- console.log("[payment-middleware] Initialized with requirements:", JSON.stringify(requirements, null, 2));
202
- return async (c, next) => {
203
- if (error) {
204
- console.log("[payment-middleware] Configuration error:", error.message);
205
- c.status(500);
206
- return c.json({
207
- error: "Payment middleware configuration error",
208
- details: error.message
209
- });
210
- }
211
- const paymentHeader = c.req.header(V2_HEADERS.PAYMENT_SIGNATURE);
212
- console.log("[payment-middleware] Payment header present:", !!paymentHeader);
213
- if (!paymentHeader) {
214
- const resource = {
215
- url: c.req.url,
216
- description: "API Access",
217
- mimeType: "application/json"
218
- };
219
- const paymentRequired = {
220
- x402Version: 2,
221
- error: "Payment required",
222
- resource,
223
- accepts: requirements,
224
- extensions: config.extensions ? buildExtensions(config.extensions) : void 0
225
- };
226
- console.log("[payment-middleware] Returning 402 with requirements:", JSON.stringify(paymentRequired.accepts, null, 2));
227
- c.status(402);
228
- c.header(V2_HEADERS.PAYMENT_REQUIRED, safeBase64Encode(JSON.stringify(paymentRequired)));
229
- return c.json({
230
- error: "Payment required",
231
- accepts: requirements
232
- });
233
- }
234
- const primaryRequirement = requirements[0];
235
- if (!primaryRequirement) {
236
- console.log("[payment-middleware] No requirements configured");
237
- c.status(500);
238
- return c.json({ error: "Payment middleware configuration error", details: "No payment requirements configured" });
239
- }
240
- console.log("[payment-middleware] Primary requirement:", JSON.stringify(primaryRequirement, null, 2));
241
- let parsedPayload;
242
- try {
243
- parsedPayload = decodePayloadHeader(paymentHeader, {
244
- accepted: primaryRequirement
245
- });
246
- } catch {
247
- c.status(400);
248
- return c.json({ error: "Invalid payment payload" });
249
- }
250
- const facilitatorUrl = resolveFacilitatorUrlFromRequirement(config, primaryRequirement);
251
- console.log("[payment-middleware] Facilitator URL:", facilitatorUrl);
252
- if (!facilitatorUrl) {
253
- console.log("[payment-middleware] No facilitator URL configured");
254
- c.status(500);
255
- return c.json({ error: "Payment middleware configuration error", message: "Facilitator URL is required for verification" });
256
- }
257
- console.log("[payment-middleware] Verifying payment with facilitator...");
258
- const verifyResult = await verifyPayment(parsedPayload, primaryRequirement, { url: facilitatorUrl });
259
- console.log("[payment-middleware] Verify result:", JSON.stringify(verifyResult, null, 2));
260
- if (!verifyResult.isValid) {
261
- console.log("[payment-middleware] Verification failed:", verifyResult.invalidReason);
262
- c.status(402);
263
- c.header(V2_HEADERS.PAYMENT_REQUIRED, createPaymentRequiredHeaders(requirements)[V2_HEADERS.PAYMENT_REQUIRED]);
264
- return c.json({ error: "Payment verification failed", message: verifyResult.invalidReason });
265
- }
266
- console.log("[payment-middleware] Payment verified, setting payment context");
267
- c.set("payment", {
268
- payload: parsedPayload,
269
- payerAddress: verifyResult.payer,
270
- verified: true
271
- });
272
- await next();
273
- if (c.res.status >= 400) {
274
- return;
275
- }
276
- console.log("[payment-middleware] Settling payment...");
277
- const settleResult = await settlePayment(parsedPayload, primaryRequirement, { url: facilitatorUrl });
278
- console.log("[payment-middleware] Settle result:", JSON.stringify(settleResult, null, 2));
279
- if (!settleResult.success) {
280
- console.log("[payment-middleware] Settlement failed:", settleResult.errorReason);
281
- c.status(502);
282
- c.res = c.json({ error: "Settlement failed", details: settleResult.errorReason }, 502);
283
- return;
284
- }
285
- console.log("[payment-middleware] Payment settled, tx:", settleResult.transaction);
286
- const headers = createSettlementHeaders(settleResult);
287
- for (const [key, value] of Object.entries(headers)) {
288
- c.header(key, value);
289
- }
290
- };
291
- }
292
4
  var resolveRouteConfig = (config) => {
293
5
  const validationError = validateRouteConfig(config);
294
6
  if (validationError) {
@@ -323,7 +35,8 @@ var routeAwarePaymentMiddleware = (config) => {
323
35
  const path = new URL(c.req.url).pathname;
324
36
  const matchedRoute = routes.find((r) => matchRoute(r.pattern, path));
325
37
  if (!matchedRoute) {
326
- return next();
38
+ await next();
39
+ return;
327
40
  }
328
41
  const { requirements, error: requirementsError } = createPaymentRequirements(matchedRoute.config);
329
42
  if (requirementsError) {
@@ -359,7 +72,10 @@ var routeAwarePaymentMiddleware = (config) => {
359
72
  const primaryRequirement = requirements[0];
360
73
  if (!primaryRequirement) {
361
74
  c.status(500);
362
- return c.json({ error: "Payment middleware configuration error", details: "No payment requirements configured" });
75
+ return c.json({
76
+ error: "Payment middleware configuration error",
77
+ details: "No payment requirements configured"
78
+ });
363
79
  }
364
80
  let parsedPayload;
365
81
  try {
@@ -370,16 +86,43 @@ var routeAwarePaymentMiddleware = (config) => {
370
86
  c.status(400);
371
87
  return c.json({ error: "Invalid payment payload" });
372
88
  }
373
- const facilitatorUrl = matchedRoute.config.facilitatorUrl ?? resolveFacilitatorUrlFromRequirement(matchedRoute.config, primaryRequirement);
89
+ const selectedRequirement = findRequirementByAccepted(
90
+ requirements,
91
+ parsedPayload.accepted
92
+ );
93
+ if (!selectedRequirement) {
94
+ c.status(400);
95
+ return c.json({
96
+ error: "Invalid payment payload",
97
+ message: "Accepted requirement is not configured for this route"
98
+ });
99
+ }
100
+ const facilitatorUrl = matchedRoute.config.facilitatorUrl ?? resolveFacilitatorUrlFromRequirement(
101
+ matchedRoute.config,
102
+ selectedRequirement
103
+ );
374
104
  if (!facilitatorUrl) {
375
105
  c.status(500);
376
- return c.json({ error: "Payment middleware configuration error", message: "Facilitator URL is required for verification" });
106
+ return c.json({
107
+ error: "Payment middleware configuration error",
108
+ message: "Facilitator URL is required for verification"
109
+ });
377
110
  }
378
- const verifyResult = await verifyPayment(parsedPayload, primaryRequirement, { url: facilitatorUrl });
111
+ const verifyResult = await verifyPayment(
112
+ parsedPayload,
113
+ selectedRequirement,
114
+ { url: facilitatorUrl }
115
+ );
379
116
  if (!verifyResult.isValid) {
380
117
  c.status(402);
381
- c.header(V2_HEADERS.PAYMENT_REQUIRED, createPaymentRequiredHeaders(requirements)[V2_HEADERS.PAYMENT_REQUIRED]);
382
- return c.json({ error: "Payment verification failed", message: verifyResult.invalidReason });
118
+ c.header(
119
+ V2_HEADERS.PAYMENT_REQUIRED,
120
+ createPaymentRequiredHeaders(requirements)[V2_HEADERS.PAYMENT_REQUIRED]
121
+ );
122
+ return c.json({
123
+ error: "Payment verification failed",
124
+ message: verifyResult.invalidReason
125
+ });
383
126
  }
384
127
  c.set("payment", {
385
128
  payload: parsedPayload,
@@ -391,10 +134,17 @@ var routeAwarePaymentMiddleware = (config) => {
391
134
  if (c.res.status >= 400) {
392
135
  return;
393
136
  }
394
- const settleResult = await settlePayment(parsedPayload, primaryRequirement, { url: facilitatorUrl });
137
+ const settleResult = await settlePayment(
138
+ parsedPayload,
139
+ selectedRequirement,
140
+ { url: facilitatorUrl }
141
+ );
395
142
  if (!settleResult.success) {
396
143
  c.status(502);
397
- c.res = c.json({ error: "Settlement failed", details: settleResult.errorReason }, 502);
144
+ c.res = c.json(
145
+ { error: "Settlement failed", details: settleResult.errorReason },
146
+ 502
147
+ );
398
148
  return;
399
149
  }
400
150
  const headers = createSettlementHeaders(settleResult);
@@ -405,67 +155,228 @@ var routeAwarePaymentMiddleware = (config) => {
405
155
  };
406
156
 
407
157
  // src/index.ts
408
- var advancedPaymentMiddleware = (config) => {
409
- const { requirements, facilitatorUrl } = config;
158
+ var extensionCapabilityCache = /* @__PURE__ */ new Map();
159
+ var EXTENSION_CAPABILITY_TTL_MS = 5 * 60 * 1e3;
160
+ function ensureTokensRegistered() {
161
+ for (const token of Object.values(TOKENS)) {
162
+ try {
163
+ registerToken(token);
164
+ } catch {
165
+ }
166
+ }
167
+ }
168
+ function resolveFacilitatorUrlFromRequirement(config, requirement) {
169
+ const chainId = parseInt(requirement.network.split(":")[1] || "0", 10);
170
+ const assetAddress = requirement.asset.toLowerCase();
171
+ if (config.facilitatorUrlByToken) {
172
+ for (const [chainKey, tokenMap] of Object.entries(
173
+ config.facilitatorUrlByToken
174
+ )) {
175
+ const resolvedChain = resolveNetwork(chainKey);
176
+ if (isValidationError(resolvedChain) || resolvedChain.config.chainId !== chainId) {
177
+ continue;
178
+ }
179
+ for (const [tokenKey, url] of Object.entries(tokenMap)) {
180
+ const resolvedToken = resolveToken(tokenKey, resolvedChain);
181
+ if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === assetAddress) {
182
+ return url;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ if (config.facilitatorUrlByChain) {
188
+ for (const [chainKey, url] of Object.entries(
189
+ config.facilitatorUrlByChain
190
+ )) {
191
+ const resolvedChain = resolveNetwork(chainKey);
192
+ if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
193
+ return url;
194
+ }
195
+ }
196
+ }
197
+ return config.facilitatorUrl;
198
+ }
199
+ function createPaymentRequirements(config) {
200
+ if (config.requirements) {
201
+ return {
202
+ requirements: Array.isArray(config.requirements) ? config.requirements : [config.requirements]
203
+ };
204
+ }
205
+ if (!config.payTo) {
206
+ return {
207
+ requirements: [],
208
+ error: {
209
+ code: "VALIDATION_FAILED",
210
+ message: "Missing payment configuration: provide payTo or explicit requirements"
211
+ }
212
+ };
213
+ }
214
+ ensureTokensRegistered();
215
+ return createPaymentRequirements$1(config);
216
+ }
217
+ async function resolvePaymentRequiredExtensions(config, requirements) {
218
+ if (!config.extensions) {
219
+ return {};
220
+ }
221
+ let filtered = { ...config.extensions };
222
+ for (const requirement of requirements) {
223
+ const facilitatorUrl = resolveFacilitatorUrlFromRequirement(config, requirement);
224
+ if (!facilitatorUrl) {
225
+ continue;
226
+ }
227
+ const cacheKey = `${facilitatorUrl}|${requirement.network.toLowerCase()}`;
228
+ const now = Date.now();
229
+ let keys = extensionCapabilityCache.get(cacheKey);
230
+ if (!keys || keys.expiresAt <= now) {
231
+ try {
232
+ const supported = await getSupported({ url: facilitatorUrl });
233
+ const nextKeys = /* @__PURE__ */ new Set();
234
+ for (const kind of supported.kinds) {
235
+ if (kind.network.toLowerCase() !== requirement.network.toLowerCase()) {
236
+ continue;
237
+ }
238
+ if (kind.extra && typeof kind.extra === "object") {
239
+ for (const key of Object.keys(kind.extra)) {
240
+ nextKeys.add(key);
241
+ }
242
+ }
243
+ }
244
+ keys = { expiresAt: now + EXTENSION_CAPABILITY_TTL_MS, keys: nextKeys };
245
+ } catch {
246
+ keys = { expiresAt: now + EXTENSION_CAPABILITY_TTL_MS, keys: /* @__PURE__ */ new Set() };
247
+ }
248
+ extensionCapabilityCache.set(cacheKey, keys);
249
+ }
250
+ filtered = Object.fromEntries(
251
+ Object.entries(filtered).filter(([key]) => keys.keys.has(key))
252
+ );
253
+ if (Object.keys(filtered).length === 0) {
254
+ return {};
255
+ }
256
+ }
257
+ return filtered;
258
+ }
259
+ function paymentMiddleware(config) {
260
+ const { requirements, error } = createPaymentRequirements(config);
261
+ const resolvePaymentRequiredHeader = async () => createPaymentRequiredHeaders(requirements, {
262
+ extensions: await resolvePaymentRequiredExtensions(
263
+ config,
264
+ requirements
265
+ )
266
+ })[PAYMENT_REQUIRED_HEADER];
410
267
  return async (c, next) => {
268
+ if (error) {
269
+ c.status(500);
270
+ return c.json({
271
+ error: "Payment middleware configuration error",
272
+ details: error.message
273
+ });
274
+ }
275
+ const primaryRequirement = requirements[0];
276
+ if (!primaryRequirement) {
277
+ c.status(500);
278
+ return c.json({
279
+ error: "Payment middleware configuration error",
280
+ details: "No payment requirements configured"
281
+ });
282
+ }
411
283
  const paymentHeader = c.req.header(PAYMENT_SIGNATURE_HEADER);
412
284
  if (!paymentHeader) {
413
- const requiredHeaders = createPaymentRequiredHeaders(requirements);
285
+ const resource = {
286
+ url: c.req.url,
287
+ description: "API Access",
288
+ mimeType: "application/json"
289
+ };
290
+ const paymentRequired = {
291
+ x402Version: 2,
292
+ error: "Payment required",
293
+ resource,
294
+ accepts: requirements
295
+ };
414
296
  c.status(402);
415
- for (const [key, value] of Object.entries(requiredHeaders)) {
416
- c.header(key, value);
417
- }
297
+ c.header(
298
+ PAYMENT_REQUIRED_HEADER,
299
+ await resolvePaymentRequiredHeader() ?? safeBase64Encode(JSON.stringify(paymentRequired))
300
+ );
418
301
  return c.json({
419
302
  error: "Payment required",
420
- accepts: [requirements]
303
+ accepts: requirements
421
304
  });
422
305
  }
423
- let paymentPayload;
306
+ let parsedPayload;
424
307
  try {
425
- paymentPayload = decodePayloadHeader(paymentHeader, {
426
- accepted: requirements
308
+ parsedPayload = decodePayloadHeader(paymentHeader, {
309
+ accepted: primaryRequirement
427
310
  });
428
- } catch (e) {
311
+ } catch {
312
+ c.status(400);
313
+ return c.json({ error: "Invalid payment payload" });
314
+ }
315
+ const selectedRequirement = findRequirementByAccepted(
316
+ requirements,
317
+ parsedPayload.accepted
318
+ );
319
+ if (!selectedRequirement) {
429
320
  c.status(400);
430
- return c.json({ error: "Invalid payment payload", details: String(e) });
321
+ return c.json({
322
+ error: "Invalid payment payload",
323
+ message: "Accepted requirement is not configured for this endpoint"
324
+ });
431
325
  }
326
+ const facilitatorUrl = resolveFacilitatorUrlFromRequirement(
327
+ config,
328
+ selectedRequirement
329
+ );
432
330
  if (!facilitatorUrl) {
433
331
  c.status(500);
434
- return c.json({ error: "Payment middleware configuration error", message: "Facilitator URL is required for verification" });
332
+ return c.json({
333
+ error: "Payment middleware configuration error",
334
+ message: "Facilitator URL is required for verification"
335
+ });
435
336
  }
436
- const verifyResult = await verifyPayment(paymentPayload, requirements, { url: facilitatorUrl });
337
+ const verifyResult = await verifyPayment(
338
+ parsedPayload,
339
+ selectedRequirement,
340
+ { url: facilitatorUrl }
341
+ );
437
342
  if (!verifyResult.isValid) {
438
- const requiredHeaders = createPaymentRequiredHeaders(requirements);
439
343
  c.status(402);
440
- for (const [key, value] of Object.entries(requiredHeaders)) {
441
- c.header(key, value);
442
- }
344
+ c.header(
345
+ PAYMENT_REQUIRED_HEADER,
346
+ await resolvePaymentRequiredHeader()
347
+ );
443
348
  return c.json({
444
349
  error: "Payment verification failed",
445
350
  message: verifyResult.invalidReason
446
351
  });
447
352
  }
448
- const payerAddress = verifyResult.payer ?? paymentPayload.payload.authorization.from;
449
353
  c.set("payment", {
450
- payload: paymentPayload,
451
- payerAddress,
354
+ payload: parsedPayload,
355
+ payerAddress: verifyResult.payer,
452
356
  verified: true
453
357
  });
454
358
  await next();
455
359
  if (c.res.status >= 400) {
456
360
  return;
457
361
  }
458
- const settleResult = await settlePayment(paymentPayload, requirements, { url: facilitatorUrl });
362
+ const settleResult = await settlePayment(
363
+ parsedPayload,
364
+ selectedRequirement,
365
+ { url: facilitatorUrl }
366
+ );
459
367
  if (!settleResult.success) {
460
368
  c.status(502);
461
- c.res = c.json({ error: "Settlement failed", details: settleResult.errorReason }, 502);
369
+ c.res = c.json(
370
+ { error: "Settlement failed", details: settleResult.errorReason },
371
+ 502
372
+ );
462
373
  return;
463
374
  }
464
- const settlementHeaders = createSettlementHeaders(settleResult);
465
- for (const [key, value] of Object.entries(settlementHeaders)) {
375
+ const headers = createSettlementHeaders(settleResult);
376
+ for (const [key, value] of Object.entries(headers)) {
466
377
  c.header(key, value);
467
378
  }
468
379
  };
469
- };
380
+ }
470
381
 
471
- export { advancedPaymentMiddleware, createPaymentRequirements, paymentMiddleware, routeAwarePaymentMiddleware };
382
+ export { createPaymentRequirements, paymentMiddleware, resolveFacilitatorUrlFromRequirement, routeAwarePaymentMiddleware };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/middleware-hono",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "keywords": [
@@ -52,8 +52,8 @@
52
52
  "hono": "^4"
53
53
  },
54
54
  "dependencies": {
55
- "@armory-sh/base": "0.2.28",
56
- "@armory-sh/extensions": "0.1.9"
55
+ "@armory-sh/base": "0.2.30",
56
+ "@armory-sh/extensions": "0.1.11"
57
57
  },
58
58
  "devDependencies": {
59
59
  "bun-types": "latest",
@@ -64,6 +64,8 @@
64
64
  "scripts": {
65
65
  "build": "rm -rf dist && tsup",
66
66
  "build:dts": "rm -rf dist && tsc --declaration --skipLibCheck",
67
+ "lint": "bun run build",
68
+ "format": "bun run lint",
67
69
  "test": "bun test"
68
70
  }
69
71
  }