@armory-sh/middleware-hono 0.3.28 → 0.3.29
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/dist/index.d.ts +22 -33
- package/dist/index.js +190 -325
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,56 +1,45 @@
|
|
|
1
|
+
import { X402PaymentPayload, PaymentRequirementsV2 } from '@armory-sh/base';
|
|
1
2
|
import { Context, Next } from 'hono';
|
|
2
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
|
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<
|
|
25
|
-
facilitatorUrlByToken?: Record<
|
|
26
|
-
extensions?: ExtensionConfig;
|
|
24
|
+
facilitatorUrlByChain?: Record<string, string>;
|
|
25
|
+
facilitatorUrlByToken?: Record<string, Record<string, string>>;
|
|
27
26
|
}
|
|
28
|
-
interface
|
|
27
|
+
interface ResolvedRequirementsConfig {
|
|
29
28
|
requirements: PaymentRequirementsV2[];
|
|
30
|
-
error?:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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;
|
|
29
|
+
error?: {
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
46
33
|
}
|
|
47
|
-
interface
|
|
34
|
+
interface AugmentedContext extends Context {
|
|
48
35
|
payment?: {
|
|
49
36
|
payload: X402PaymentPayload;
|
|
50
37
|
payerAddress: string;
|
|
51
38
|
verified: boolean;
|
|
52
39
|
};
|
|
53
40
|
}
|
|
54
|
-
declare
|
|
41
|
+
declare function resolveFacilitatorUrlFromRequirement(config: PaymentConfig, requirement: PaymentRequirementsV2): string | undefined;
|
|
42
|
+
declare function createPaymentRequirements(config: PaymentConfig): ResolvedRequirementsConfig;
|
|
43
|
+
declare function paymentMiddleware(config: PaymentConfig): (c: Context, next: Next) => Promise<Response | undefined>;
|
|
55
44
|
|
|
56
|
-
export { type
|
|
45
|
+
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,
|
|
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 } 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
|
-
|
|
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({
|
|
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
|
|
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({
|
|
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(
|
|
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(
|
|
382
|
-
|
|
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(
|
|
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(
|
|
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,182 @@ var routeAwarePaymentMiddleware = (config) => {
|
|
|
405
155
|
};
|
|
406
156
|
|
|
407
157
|
// src/index.ts
|
|
408
|
-
|
|
409
|
-
const
|
|
158
|
+
function ensureTokensRegistered() {
|
|
159
|
+
for (const token of Object.values(TOKENS)) {
|
|
160
|
+
try {
|
|
161
|
+
registerToken(token);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function resolveFacilitatorUrlFromRequirement(config, requirement) {
|
|
167
|
+
const chainId = parseInt(requirement.network.split(":")[1] || "0", 10);
|
|
168
|
+
const assetAddress = requirement.asset.toLowerCase();
|
|
169
|
+
if (config.facilitatorUrlByToken) {
|
|
170
|
+
for (const [chainKey, tokenMap] of Object.entries(
|
|
171
|
+
config.facilitatorUrlByToken
|
|
172
|
+
)) {
|
|
173
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
174
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
175
|
+
for (const [, url] of Object.entries(tokenMap)) {
|
|
176
|
+
const network = resolveNetwork(chainKey);
|
|
177
|
+
if (!isValidationError(network)) {
|
|
178
|
+
for (const tokenKey of Object.keys(tokenMap)) {
|
|
179
|
+
const resolvedToken = resolveToken(tokenKey, network);
|
|
180
|
+
if (!isValidationError(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === assetAddress) {
|
|
181
|
+
return url;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (config.facilitatorUrlByChain) {
|
|
190
|
+
for (const [chainKey, url] of Object.entries(
|
|
191
|
+
config.facilitatorUrlByChain
|
|
192
|
+
)) {
|
|
193
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
194
|
+
if (!isValidationError(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
195
|
+
return url;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return config.facilitatorUrl;
|
|
200
|
+
}
|
|
201
|
+
function createPaymentRequirements(config) {
|
|
202
|
+
if (config.requirements) {
|
|
203
|
+
return {
|
|
204
|
+
requirements: Array.isArray(config.requirements) ? config.requirements : [config.requirements]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (!config.payTo) {
|
|
208
|
+
return {
|
|
209
|
+
requirements: [],
|
|
210
|
+
error: {
|
|
211
|
+
code: "VALIDATION_FAILED",
|
|
212
|
+
message: "Missing payment configuration: provide payTo or explicit requirements"
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
ensureTokensRegistered();
|
|
217
|
+
return createPaymentRequirements$1(config);
|
|
218
|
+
}
|
|
219
|
+
function paymentMiddleware(config) {
|
|
220
|
+
const { requirements, error } = createPaymentRequirements(config);
|
|
410
221
|
return async (c, next) => {
|
|
222
|
+
if (error) {
|
|
223
|
+
c.status(500);
|
|
224
|
+
return c.json({
|
|
225
|
+
error: "Payment middleware configuration error",
|
|
226
|
+
details: error.message
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const primaryRequirement = requirements[0];
|
|
230
|
+
if (!primaryRequirement) {
|
|
231
|
+
c.status(500);
|
|
232
|
+
return c.json({
|
|
233
|
+
error: "Payment middleware configuration error",
|
|
234
|
+
details: "No payment requirements configured"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
411
237
|
const paymentHeader = c.req.header(PAYMENT_SIGNATURE_HEADER);
|
|
412
238
|
if (!paymentHeader) {
|
|
413
|
-
const
|
|
239
|
+
const resource = {
|
|
240
|
+
url: c.req.url,
|
|
241
|
+
description: "API Access",
|
|
242
|
+
mimeType: "application/json"
|
|
243
|
+
};
|
|
244
|
+
const paymentRequired = {
|
|
245
|
+
x402Version: 2,
|
|
246
|
+
error: "Payment required",
|
|
247
|
+
resource,
|
|
248
|
+
accepts: requirements
|
|
249
|
+
};
|
|
414
250
|
c.status(402);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
251
|
+
c.header(
|
|
252
|
+
PAYMENT_REQUIRED_HEADER,
|
|
253
|
+
safeBase64Encode(JSON.stringify(paymentRequired))
|
|
254
|
+
);
|
|
418
255
|
return c.json({
|
|
419
256
|
error: "Payment required",
|
|
420
|
-
accepts:
|
|
257
|
+
accepts: requirements
|
|
421
258
|
});
|
|
422
259
|
}
|
|
423
|
-
let
|
|
260
|
+
let parsedPayload;
|
|
424
261
|
try {
|
|
425
|
-
|
|
426
|
-
accepted:
|
|
262
|
+
parsedPayload = decodePayloadHeader(paymentHeader, {
|
|
263
|
+
accepted: primaryRequirement
|
|
427
264
|
});
|
|
428
|
-
} catch
|
|
265
|
+
} catch {
|
|
429
266
|
c.status(400);
|
|
430
|
-
return c.json({ error: "Invalid payment payload"
|
|
267
|
+
return c.json({ error: "Invalid payment payload" });
|
|
431
268
|
}
|
|
269
|
+
const selectedRequirement = findRequirementByAccepted(
|
|
270
|
+
requirements,
|
|
271
|
+
parsedPayload.accepted
|
|
272
|
+
);
|
|
273
|
+
if (!selectedRequirement) {
|
|
274
|
+
c.status(400);
|
|
275
|
+
return c.json({
|
|
276
|
+
error: "Invalid payment payload",
|
|
277
|
+
message: "Accepted requirement is not configured for this endpoint"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const facilitatorUrl = resolveFacilitatorUrlFromRequirement(
|
|
281
|
+
config,
|
|
282
|
+
selectedRequirement
|
|
283
|
+
);
|
|
432
284
|
if (!facilitatorUrl) {
|
|
433
285
|
c.status(500);
|
|
434
|
-
return c.json({
|
|
286
|
+
return c.json({
|
|
287
|
+
error: "Payment middleware configuration error",
|
|
288
|
+
message: "Facilitator URL is required for verification"
|
|
289
|
+
});
|
|
435
290
|
}
|
|
436
|
-
const verifyResult = await verifyPayment(
|
|
291
|
+
const verifyResult = await verifyPayment(
|
|
292
|
+
parsedPayload,
|
|
293
|
+
selectedRequirement,
|
|
294
|
+
{ url: facilitatorUrl }
|
|
295
|
+
);
|
|
437
296
|
if (!verifyResult.isValid) {
|
|
438
|
-
const requiredHeaders = createPaymentRequiredHeaders(requirements);
|
|
439
297
|
c.status(402);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
298
|
+
c.header(
|
|
299
|
+
PAYMENT_REQUIRED_HEADER,
|
|
300
|
+
createPaymentRequiredHeaders(requirements)[PAYMENT_REQUIRED_HEADER]
|
|
301
|
+
);
|
|
443
302
|
return c.json({
|
|
444
303
|
error: "Payment verification failed",
|
|
445
304
|
message: verifyResult.invalidReason
|
|
446
305
|
});
|
|
447
306
|
}
|
|
448
|
-
const payerAddress = verifyResult.payer ?? paymentPayload.payload.authorization.from;
|
|
449
307
|
c.set("payment", {
|
|
450
|
-
payload:
|
|
451
|
-
payerAddress,
|
|
308
|
+
payload: parsedPayload,
|
|
309
|
+
payerAddress: verifyResult.payer,
|
|
452
310
|
verified: true
|
|
453
311
|
});
|
|
454
312
|
await next();
|
|
455
313
|
if (c.res.status >= 400) {
|
|
456
314
|
return;
|
|
457
315
|
}
|
|
458
|
-
const settleResult = await settlePayment(
|
|
316
|
+
const settleResult = await settlePayment(
|
|
317
|
+
parsedPayload,
|
|
318
|
+
selectedRequirement,
|
|
319
|
+
{ url: facilitatorUrl }
|
|
320
|
+
);
|
|
459
321
|
if (!settleResult.success) {
|
|
460
322
|
c.status(502);
|
|
461
|
-
c.res = c.json(
|
|
323
|
+
c.res = c.json(
|
|
324
|
+
{ error: "Settlement failed", details: settleResult.errorReason },
|
|
325
|
+
502
|
|
326
|
+
);
|
|
462
327
|
return;
|
|
463
328
|
}
|
|
464
|
-
const
|
|
465
|
-
for (const [key, value] of Object.entries(
|
|
329
|
+
const headers = createSettlementHeaders(settleResult);
|
|
330
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
466
331
|
c.header(key, value);
|
|
467
332
|
}
|
|
468
333
|
};
|
|
469
|
-
}
|
|
334
|
+
}
|
|
470
335
|
|
|
471
|
-
export {
|
|
336
|
+
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.
|
|
3
|
+
"version": "0.3.29",
|
|
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.
|
|
56
|
-
"@armory-sh/extensions": "0.1.
|
|
55
|
+
"@armory-sh/base": "0.2.29",
|
|
56
|
+
"@armory-sh/extensions": "0.1.10"
|
|
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
|
}
|