@parallel-protocol/x402 0.5.0
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/LICENSE +21 -0
- package/README.md +610 -0
- package/dist/chunk-2UEAVMCH.js +17 -0
- package/dist/chunk-FDQULXKG.js +452 -0
- package/dist/chunk-W3SMR5LQ.cjs +465 -0
- package/dist/chunk-XUNNMCO5.cjs +20 -0
- package/dist/express.cjs +131 -0
- package/dist/express.d.cts +19 -0
- package/dist/express.d.ts +19 -0
- package/dist/express.js +128 -0
- package/dist/fastify.cjs +79 -0
- package/dist/fastify.d.cts +16 -0
- package/dist/fastify.d.ts +16 -0
- package/dist/fastify.js +72 -0
- package/dist/hono.cjs +77 -0
- package/dist/hono.d.cts +18 -0
- package/dist/hono.d.ts +18 -0
- package/dist/hono.js +74 -0
- package/dist/index.cjs +54 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +1 -0
- package/dist/next.cjs +81 -0
- package/dist/next.d.cts +17 -0
- package/dist/next.d.ts +17 -0
- package/dist/next.js +78 -0
- package/dist/types-CUi55YSx.d.cts +95 -0
- package/dist/types-CUi55YSx.d.ts +95 -0
- package/package.json +145 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var chains = require('@parallel-protocol/chains');
|
|
5
|
+
|
|
6
|
+
// src/errors.ts
|
|
7
|
+
var X402_ERROR_CODES = {
|
|
8
|
+
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
9
|
+
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
10
|
+
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
11
|
+
};
|
|
12
|
+
var X402ConfigError = class extends Error {
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "X402ConfigError";
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var X402RuntimeError = class extends Error {
|
|
20
|
+
constructor(code) {
|
|
21
|
+
super(code);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.name = "X402RuntimeError";
|
|
24
|
+
}
|
|
25
|
+
code;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/utils.ts
|
|
29
|
+
var EIP155_NETWORKS = {
|
|
30
|
+
ethereum: "eip155:1",
|
|
31
|
+
base: "eip155:8453",
|
|
32
|
+
avalanche: "eip155:43114",
|
|
33
|
+
hyperevm: "eip155:999"
|
|
34
|
+
};
|
|
35
|
+
function parseDecimalAmount(amount, decimals) {
|
|
36
|
+
const multiplier = 10n ** BigInt(decimals);
|
|
37
|
+
const parts = amount.split(".");
|
|
38
|
+
const whole = parts[0] ?? "0";
|
|
39
|
+
const fraction = (parts[1] ?? "").padEnd(decimals, "0").slice(0, decimals);
|
|
40
|
+
return BigInt(whole) * multiplier + BigInt(fraction || "0");
|
|
41
|
+
}
|
|
42
|
+
function parsePrice(price, decimals = 18) {
|
|
43
|
+
if (typeof price === "bigint") return price;
|
|
44
|
+
const match = price.match(/^(\d+(?:\.\d+)?)$/);
|
|
45
|
+
if (match?.[1]) return parseDecimalAmount(match[1], decimals);
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Invalid price format: "${price}". Expected "0.01" or a bigint.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function validateAddress(address) {
|
|
51
|
+
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
52
|
+
}
|
|
53
|
+
function toEip155Network(network) {
|
|
54
|
+
if (network.startsWith("eip155:")) return network;
|
|
55
|
+
return EIP155_NETWORKS[network] ?? `eip155:${network}`;
|
|
56
|
+
}
|
|
57
|
+
function encodeBase64(obj) {
|
|
58
|
+
return Buffer.from(JSON.stringify(obj)).toString("base64");
|
|
59
|
+
}
|
|
60
|
+
function decodeBase64(str) {
|
|
61
|
+
return JSON.parse(Buffer.from(str, "base64").toString("utf8"));
|
|
62
|
+
}
|
|
63
|
+
function matchRoute(path, routes) {
|
|
64
|
+
if (routes[path]) return routes[path];
|
|
65
|
+
let bestMatch;
|
|
66
|
+
let bestLength = 0;
|
|
67
|
+
for (const [pattern, config] of Object.entries(routes)) {
|
|
68
|
+
if (path.startsWith(`${pattern}/`) && pattern.length > bestLength) {
|
|
69
|
+
bestMatch = config;
|
|
70
|
+
bestLength = pattern.length;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return bestMatch;
|
|
74
|
+
}
|
|
75
|
+
var ethereumAddressSchema = zod.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Invalid Ethereum address");
|
|
76
|
+
var txHashSchema = zod.z.string().regex(/^0x[0-9a-fA-F]{64}$/);
|
|
77
|
+
var priceSchema = zod.z.union([
|
|
78
|
+
zod.z.bigint().positive("Price must be greater than 0"),
|
|
79
|
+
zod.z.string().regex(/^\d+(?:\.\d+)?$/, 'Price must be in format "0.01"').refine((price) => Number(price) > 0, "Price must be greater than 0")
|
|
80
|
+
]);
|
|
81
|
+
var facilitatorConfigSchema = zod.z.object({
|
|
82
|
+
url: zod.z.string().refine((v) => URL.canParse(v), "Invalid facilitator URL"),
|
|
83
|
+
apiKey: zod.z.string().optional()
|
|
84
|
+
});
|
|
85
|
+
var routeConfigSchema = zod.z.object({
|
|
86
|
+
price: priceSchema,
|
|
87
|
+
network: zod.z.string().min(1, "Network is required"),
|
|
88
|
+
payTo: ethereumAddressSchema,
|
|
89
|
+
acceptedTokens: zod.z.array(ethereumAddressSchema).optional(),
|
|
90
|
+
description: zod.z.string().optional()
|
|
91
|
+
});
|
|
92
|
+
var paymentMiddlewareConfigSchema = zod.z.object({
|
|
93
|
+
facilitator: facilitatorConfigSchema,
|
|
94
|
+
routes: zod.z.record(zod.z.string(), routeConfigSchema).refine((r) => Object.keys(r).length > 0, "At least one route is required")
|
|
95
|
+
});
|
|
96
|
+
var facilitatorResponseSchema = zod.z.discriminatedUnion("success", [
|
|
97
|
+
zod.z.object({
|
|
98
|
+
success: zod.z.literal(true),
|
|
99
|
+
txHash: txHashSchema,
|
|
100
|
+
route: zod.z.string(),
|
|
101
|
+
chain: zod.z.string(),
|
|
102
|
+
gasSponsored: zod.z.boolean(),
|
|
103
|
+
networkId: zod.z.string()
|
|
104
|
+
}),
|
|
105
|
+
zod.z.object({
|
|
106
|
+
success: zod.z.literal(false),
|
|
107
|
+
error: zod.z.object({
|
|
108
|
+
code: zod.z.string(),
|
|
109
|
+
message: zod.z.string(),
|
|
110
|
+
hint: zod.z.string().optional()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
]);
|
|
114
|
+
var verifyResponseSchema = zod.z.object({
|
|
115
|
+
isValid: zod.z.literal(true)
|
|
116
|
+
});
|
|
117
|
+
var settleSuccessSchema = zod.z.object({
|
|
118
|
+
txHash: txHashSchema,
|
|
119
|
+
route: zod.z.string(),
|
|
120
|
+
gasSponsored: zod.z.boolean()
|
|
121
|
+
});
|
|
122
|
+
var settleErrorSchema = zod.z.object({
|
|
123
|
+
error: zod.z.string(),
|
|
124
|
+
message: zod.z.string()
|
|
125
|
+
});
|
|
126
|
+
var settleResponseSchema = zod.z.union([
|
|
127
|
+
settleSuccessSchema,
|
|
128
|
+
settleErrorSchema
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// src/client.ts
|
|
132
|
+
var FACILITATOR_TIMEOUT_MS = 1e4;
|
|
133
|
+
var SETTLE_TIMEOUT_MS = 6e4;
|
|
134
|
+
function decodePaymentHeader(paymentHeader) {
|
|
135
|
+
try {
|
|
136
|
+
return decodeBase64(paymentHeader);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new X402RuntimeError(X402_ERROR_CODES.INVALID_PAYMENT);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var FacilitatorClient = class {
|
|
142
|
+
constructor(config) {
|
|
143
|
+
this.config = config;
|
|
144
|
+
}
|
|
145
|
+
config;
|
|
146
|
+
async verify(paymentHeader, paymentRequirements) {
|
|
147
|
+
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
148
|
+
let json;
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(`${this.config.url}/x402/verify`, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: this.buildHeaders(),
|
|
153
|
+
body: JSON.stringify({ paymentPayload, paymentRequirements }),
|
|
154
|
+
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const errorBody = await response.json().catch(() => null);
|
|
158
|
+
const code = errorBody?.error ?? X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
|
|
159
|
+
throw new X402RuntimeError(code);
|
|
160
|
+
}
|
|
161
|
+
json = await response.json();
|
|
162
|
+
} catch (e) {
|
|
163
|
+
if (e instanceof X402RuntimeError) throw e;
|
|
164
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
verifyResponseSchema.parse(json);
|
|
168
|
+
} catch {
|
|
169
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async settle(paymentHeader, paymentRequirements) {
|
|
173
|
+
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
174
|
+
let json;
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch(`${this.config.url}/x402/settle`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: this.buildHeaders(),
|
|
179
|
+
body: JSON.stringify({ paymentPayload, paymentRequirements }),
|
|
180
|
+
signal: AbortSignal.timeout(SETTLE_TIMEOUT_MS)
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const errorBody = await response.json().catch(() => null);
|
|
184
|
+
const code = errorBody?.error ?? "SUBMISSION_FAILED";
|
|
185
|
+
const message = errorBody?.message ?? "Settlement failed";
|
|
186
|
+
return { success: false, error: { code, message } };
|
|
187
|
+
}
|
|
188
|
+
json = await response.json();
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (e instanceof X402RuntimeError) throw e;
|
|
191
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const parsed = settleResponseSchema.parse(json);
|
|
195
|
+
if ("txHash" in parsed) {
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
txHash: parsed.txHash,
|
|
199
|
+
route: parsed.route,
|
|
200
|
+
chain: paymentRequirements.network,
|
|
201
|
+
gasSponsored: parsed.gasSponsored,
|
|
202
|
+
networkId: toEip155Network(paymentRequirements.network)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: { code: parsed.error, message: parsed.message }
|
|
208
|
+
};
|
|
209
|
+
} catch {
|
|
210
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async pay(paymentHeader) {
|
|
214
|
+
const headers = this.buildHeaders();
|
|
215
|
+
headers["X-PAYMENT"] = paymentHeader;
|
|
216
|
+
let json;
|
|
217
|
+
try {
|
|
218
|
+
const response = await fetch(`${this.config.url}/x402/pay`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers,
|
|
221
|
+
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
222
|
+
});
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
225
|
+
}
|
|
226
|
+
json = await response.json();
|
|
227
|
+
} catch (e) {
|
|
228
|
+
if (e instanceof X402RuntimeError) throw e;
|
|
229
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
return facilitatorResponseSchema.parse(json);
|
|
233
|
+
} catch {
|
|
234
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
buildHeaders() {
|
|
238
|
+
const headers = {
|
|
239
|
+
"Content-Type": "application/json"
|
|
240
|
+
};
|
|
241
|
+
if (this.config.apiKey) {
|
|
242
|
+
headers["X-API-Key"] = this.config.apiKey;
|
|
243
|
+
}
|
|
244
|
+
return headers;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
var PARALLEL_TOKENS = {
|
|
248
|
+
ethereum: {
|
|
249
|
+
usdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
|
|
250
|
+
susdp: "0x0d45b129dc868963025Db79A9074EA9c9e32Cae4",
|
|
251
|
+
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
252
|
+
frxUSD: "0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29",
|
|
253
|
+
sfrxUSD: "0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6",
|
|
254
|
+
USDe: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
|
|
255
|
+
sUSDe: "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497"
|
|
256
|
+
},
|
|
257
|
+
base: {
|
|
258
|
+
usdp: "0x76A9A0062ec6712b99B4f63bD2b4270185759dd5",
|
|
259
|
+
susdp: "0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7",
|
|
260
|
+
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
261
|
+
USDS: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc",
|
|
262
|
+
sUSDS: "0x5875eEE11Cf8398102FdAd704C9E96607675467a"
|
|
263
|
+
},
|
|
264
|
+
avalanche: {
|
|
265
|
+
usdp: "0x9eE1963f05553eF838604Dd39403be21ceF26AA4",
|
|
266
|
+
susdp: "0x9d92c21205383651610f90722131655a5b8ed3e0",
|
|
267
|
+
usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
268
|
+
ygamiUSDC: "0x9fD32FD5e32C6B95483d36C5E724C5C5250Ce010"
|
|
269
|
+
},
|
|
270
|
+
hyperevm: {
|
|
271
|
+
usdp: "0xBE65F0F410A72BeC163dC65d46c83699e957D588",
|
|
272
|
+
susdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
|
|
273
|
+
usdc: "0xb88339cb7199b77e23db6e890353e22632ba630f",
|
|
274
|
+
USDe: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
|
|
275
|
+
sUSDe: "0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2"
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function getDefaultAcceptedTokens(network) {
|
|
279
|
+
const entry = PARALLEL_TOKENS[network];
|
|
280
|
+
if (!entry) return void 0;
|
|
281
|
+
return [entry.usdp, entry.usdc, entry.susdp];
|
|
282
|
+
}
|
|
283
|
+
function getTokenDecimals(network, address) {
|
|
284
|
+
return chains.getTokenDecimals(network, address);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/middleware.ts
|
|
288
|
+
function resolveAcceptedTokens(config) {
|
|
289
|
+
if (config.acceptedTokens?.length) {
|
|
290
|
+
return config.acceptedTokens;
|
|
291
|
+
}
|
|
292
|
+
const defaults = getDefaultAcceptedTokens(config.network);
|
|
293
|
+
if (defaults) return defaults;
|
|
294
|
+
throw new X402ConfigError(
|
|
295
|
+
`acceptedTokens is required for network "${config.network}" \u2014 no defaults available for this chain`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function resolveDecimals(config, asset) {
|
|
299
|
+
const decimals = config.decimals ?? getTokenDecimals(config.network, asset);
|
|
300
|
+
if (decimals === void 0) {
|
|
301
|
+
throw new X402ConfigError(
|
|
302
|
+
`Unknown decimals for token ${asset} on "${config.network}" \u2014 add it to the @parallel-protocol/chains catalog or set the route's \`decimals\` explicitly`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return decimals;
|
|
306
|
+
}
|
|
307
|
+
function buildPaymentRequired(url, config, resolvedTokens) {
|
|
308
|
+
const network = toEip155Network(config.network);
|
|
309
|
+
const accepts = resolvedTokens.map((asset) => {
|
|
310
|
+
const decimals = resolveDecimals(config, asset);
|
|
311
|
+
return {
|
|
312
|
+
scheme: "exact",
|
|
313
|
+
network,
|
|
314
|
+
asset,
|
|
315
|
+
amount: parsePrice(config.price, decimals).toString(),
|
|
316
|
+
payTo: config.payTo,
|
|
317
|
+
maxTimeoutSeconds: 300,
|
|
318
|
+
extra: { decimals }
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
x402Version: 2,
|
|
323
|
+
resource: {
|
|
324
|
+
url,
|
|
325
|
+
description: config.description,
|
|
326
|
+
mimeType: "application/json"
|
|
327
|
+
},
|
|
328
|
+
accepts
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function buildFacilitatorRequirements(config, resolvedTokens) {
|
|
332
|
+
const asset = resolvedTokens[0];
|
|
333
|
+
const decimals = resolveDecimals(config, asset);
|
|
334
|
+
return {
|
|
335
|
+
maxAmountRequired: parsePrice(config.price, decimals).toString(),
|
|
336
|
+
// Informational only: the facilitator validates the amount + the on-chain
|
|
337
|
+
// payload (the paid token lives in the payload / swapParams), and ignores
|
|
338
|
+
// `asset`. resolveAcceptedTokens guarantees at least one entry.
|
|
339
|
+
asset,
|
|
340
|
+
payTo: config.payTo,
|
|
341
|
+
network: config.network
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function paymentRequiredResponse(url, config, resolvedTokens, error) {
|
|
345
|
+
const paymentRequired = buildPaymentRequired(url, config, resolvedTokens);
|
|
346
|
+
if (error) paymentRequired.error = error;
|
|
347
|
+
return {
|
|
348
|
+
status: 402,
|
|
349
|
+
headers: {
|
|
350
|
+
"Content-Type": "application/json",
|
|
351
|
+
"payment-required": encodeBase64(paymentRequired),
|
|
352
|
+
"access-control-expose-headers": "payment-required"
|
|
353
|
+
},
|
|
354
|
+
body: paymentRequired
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function createPaymentGate(config) {
|
|
358
|
+
try {
|
|
359
|
+
paymentMiddlewareConfigSchema.parse(config);
|
|
360
|
+
} catch (cause) {
|
|
361
|
+
throw new X402ConfigError(
|
|
362
|
+
"Invalid paymentMiddleware config \u2014 check your routes, facilitator URL and payTo addresses",
|
|
363
|
+
cause
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const client = new FacilitatorClient(config.facilitator);
|
|
367
|
+
return async (adapter) => {
|
|
368
|
+
if (adapter.getMethod().toUpperCase() === "OPTIONS")
|
|
369
|
+
return { type: "pass" };
|
|
370
|
+
const routeConfig = matchRoute(adapter.getPath(), config.routes);
|
|
371
|
+
if (!routeConfig) return { type: "pass" };
|
|
372
|
+
const resolvedTokens = resolveAcceptedTokens(routeConfig);
|
|
373
|
+
const paymentHeader = adapter.getHeader("payment-signature") ?? adapter.getHeader("x-payment");
|
|
374
|
+
if (!paymentHeader) {
|
|
375
|
+
return {
|
|
376
|
+
type: "error",
|
|
377
|
+
result: paymentRequiredResponse(
|
|
378
|
+
adapter.getUrl(),
|
|
379
|
+
routeConfig,
|
|
380
|
+
resolvedTokens
|
|
381
|
+
)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const requirements = buildFacilitatorRequirements(
|
|
385
|
+
routeConfig,
|
|
386
|
+
resolvedTokens
|
|
387
|
+
);
|
|
388
|
+
try {
|
|
389
|
+
await client.verify(paymentHeader, requirements);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
const code = e instanceof X402RuntimeError ? e.code : X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
|
|
392
|
+
return {
|
|
393
|
+
type: "error",
|
|
394
|
+
result: paymentRequiredResponse(
|
|
395
|
+
adapter.getUrl(),
|
|
396
|
+
routeConfig,
|
|
397
|
+
resolvedTokens,
|
|
398
|
+
code
|
|
399
|
+
)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
type: "verified",
|
|
404
|
+
settle: () => client.settle(paymentHeader, requirements)
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function createPaymentMiddleware(config) {
|
|
409
|
+
const gate = createPaymentGate(config);
|
|
410
|
+
return async (adapter) => {
|
|
411
|
+
const gateResult = await gate(adapter);
|
|
412
|
+
if (gateResult.type === "pass") return { pass: true };
|
|
413
|
+
if (gateResult.type === "error") return gateResult.result;
|
|
414
|
+
let handlerResult;
|
|
415
|
+
try {
|
|
416
|
+
handlerResult = await adapter.runHandler();
|
|
417
|
+
} catch {
|
|
418
|
+
return { pass: true, handlerRan: true };
|
|
419
|
+
}
|
|
420
|
+
if (handlerResult.status >= 200 && handlerResult.status < 300) {
|
|
421
|
+
let settled = false;
|
|
422
|
+
try {
|
|
423
|
+
const settleResponse = await gateResult.settle();
|
|
424
|
+
if (settleResponse.success) {
|
|
425
|
+
handlerResult.setResponseHeader(
|
|
426
|
+
"payment-response",
|
|
427
|
+
encodeBase64(settleResponse)
|
|
428
|
+
);
|
|
429
|
+
handlerResult.setResponseHeader(
|
|
430
|
+
"access-control-expose-headers",
|
|
431
|
+
"payment-response"
|
|
432
|
+
);
|
|
433
|
+
settled = true;
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
if (!settled) {
|
|
438
|
+
handlerResult.discardResponse();
|
|
439
|
+
return {
|
|
440
|
+
status: 402,
|
|
441
|
+
headers: { "Content-Type": "application/json" },
|
|
442
|
+
body: {
|
|
443
|
+
error: "PAYMENT_SETTLEMENT_FAILED",
|
|
444
|
+
message: "Payment could not be confirmed after multiple attempts. Please retry."
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
handlerResult.sendResponse();
|
|
450
|
+
return { pass: true, handlerRan: true };
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
exports.FacilitatorClient = FacilitatorClient;
|
|
455
|
+
exports.PARALLEL_TOKENS = PARALLEL_TOKENS;
|
|
456
|
+
exports.X402ConfigError = X402ConfigError;
|
|
457
|
+
exports.X402RuntimeError = X402RuntimeError;
|
|
458
|
+
exports.X402_ERROR_CODES = X402_ERROR_CODES;
|
|
459
|
+
exports.createPaymentGate = createPaymentGate;
|
|
460
|
+
exports.createPaymentMiddleware = createPaymentMiddleware;
|
|
461
|
+
exports.encodeBase64 = encodeBase64;
|
|
462
|
+
exports.getDefaultAcceptedTokens = getDefaultAcceptedTokens;
|
|
463
|
+
exports.parsePrice = parsePrice;
|
|
464
|
+
exports.toEip155Network = toEip155Network;
|
|
465
|
+
exports.validateAddress = validateAddress;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/timeout.ts
|
|
4
|
+
var HANDLER_TIMEOUT_MS = 3e4;
|
|
5
|
+
var HANDLER_TIMEOUT = /* @__PURE__ */ Symbol("x402:handler-timeout");
|
|
6
|
+
async function raceTimeout(promise, ms = HANDLER_TIMEOUT_MS) {
|
|
7
|
+
let timer;
|
|
8
|
+
const timeout = new Promise((resolve) => {
|
|
9
|
+
timer = setTimeout(() => resolve(HANDLER_TIMEOUT), ms);
|
|
10
|
+
timer.unref?.();
|
|
11
|
+
});
|
|
12
|
+
try {
|
|
13
|
+
return await Promise.race([promise, timeout]);
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.HANDLER_TIMEOUT = HANDLER_TIMEOUT;
|
|
20
|
+
exports.raceTimeout = raceTimeout;
|
package/dist/express.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkXUNNMCO5_cjs = require('./chunk-XUNNMCO5.cjs');
|
|
4
|
+
var chunkW3SMR5LQ_cjs = require('./chunk-W3SMR5LQ.cjs');
|
|
5
|
+
|
|
6
|
+
// src/express/adapter.ts
|
|
7
|
+
var TIMEOUT_BODY = JSON.stringify({
|
|
8
|
+
error: "HANDLER_TIMEOUT",
|
|
9
|
+
message: "The resource handler did not respond in time."
|
|
10
|
+
});
|
|
11
|
+
var ExpressAdapter = class {
|
|
12
|
+
constructor(req, res, next) {
|
|
13
|
+
this.req = req;
|
|
14
|
+
this.res = res;
|
|
15
|
+
this.next = next;
|
|
16
|
+
}
|
|
17
|
+
req;
|
|
18
|
+
res;
|
|
19
|
+
next;
|
|
20
|
+
getHeader(name) {
|
|
21
|
+
const value = this.req.header(name);
|
|
22
|
+
return Array.isArray(value) ? value[0] : value;
|
|
23
|
+
}
|
|
24
|
+
getMethod() {
|
|
25
|
+
return this.req.method;
|
|
26
|
+
}
|
|
27
|
+
getPath() {
|
|
28
|
+
return this.req.path;
|
|
29
|
+
}
|
|
30
|
+
getUrl() {
|
|
31
|
+
return `${this.req.protocol}://${this.req.headers.host}${this.req.originalUrl}`;
|
|
32
|
+
}
|
|
33
|
+
async runHandler() {
|
|
34
|
+
const res = this.res;
|
|
35
|
+
const next = this.next;
|
|
36
|
+
const originalWriteHead = res.writeHead.bind(res);
|
|
37
|
+
const originalWrite = res.write.bind(res);
|
|
38
|
+
const originalEnd = res.end.bind(res);
|
|
39
|
+
const buffered = [];
|
|
40
|
+
let settled = false;
|
|
41
|
+
let discarded = false;
|
|
42
|
+
let endCalled;
|
|
43
|
+
const endPromise = new Promise((resolve) => {
|
|
44
|
+
endCalled = resolve;
|
|
45
|
+
});
|
|
46
|
+
const restore = () => {
|
|
47
|
+
res.writeHead = originalWriteHead;
|
|
48
|
+
res.write = originalWrite;
|
|
49
|
+
res.end = originalEnd;
|
|
50
|
+
};
|
|
51
|
+
res.writeHead = ((...args) => {
|
|
52
|
+
if (discarded) return res;
|
|
53
|
+
if (!settled) {
|
|
54
|
+
buffered.push(["writeHead", args]);
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
return originalWriteHead(...args);
|
|
58
|
+
});
|
|
59
|
+
res.write = ((...args) => {
|
|
60
|
+
if (discarded) return true;
|
|
61
|
+
if (!settled) {
|
|
62
|
+
buffered.push(["write", args]);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return originalWrite(...args);
|
|
66
|
+
});
|
|
67
|
+
res.end = ((...args) => {
|
|
68
|
+
if (discarded) return res;
|
|
69
|
+
if (!settled) {
|
|
70
|
+
buffered.push(["end", args]);
|
|
71
|
+
endCalled();
|
|
72
|
+
return res;
|
|
73
|
+
}
|
|
74
|
+
return originalEnd(...args);
|
|
75
|
+
});
|
|
76
|
+
next();
|
|
77
|
+
const outcome = await chunkXUNNMCO5_cjs.raceTimeout(endPromise);
|
|
78
|
+
if (outcome === chunkXUNNMCO5_cjs.HANDLER_TIMEOUT) {
|
|
79
|
+
discarded = true;
|
|
80
|
+
return {
|
|
81
|
+
status: 504,
|
|
82
|
+
setResponseHeader: (name, value) => {
|
|
83
|
+
if (!res.headersSent) res.setHeader(name, value);
|
|
84
|
+
},
|
|
85
|
+
sendResponse: () => {
|
|
86
|
+
if (res.headersSent) return;
|
|
87
|
+
originalWriteHead(504, { "Content-Type": "application/json" });
|
|
88
|
+
originalEnd(TIMEOUT_BODY);
|
|
89
|
+
},
|
|
90
|
+
discardResponse: () => {
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
status: res.statusCode,
|
|
96
|
+
setResponseHeader: (name, value) => res.setHeader(name, value),
|
|
97
|
+
sendResponse: () => {
|
|
98
|
+
settled = true;
|
|
99
|
+
restore();
|
|
100
|
+
for (const [method, args] of buffered) {
|
|
101
|
+
if (method === "writeHead") originalWriteHead(...args);
|
|
102
|
+
else if (method === "write") originalWrite(...args);
|
|
103
|
+
else if (method === "end") originalEnd(...args);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
discardResponse: () => {
|
|
107
|
+
settled = true;
|
|
108
|
+
restore();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/express/index.ts
|
|
115
|
+
function paymentMiddleware(config) {
|
|
116
|
+
const middleware = chunkW3SMR5LQ_cjs.createPaymentMiddleware(config);
|
|
117
|
+
return async (req, res, next) => {
|
|
118
|
+
const result = await middleware(new ExpressAdapter(req, res, next));
|
|
119
|
+
if ("pass" in result) {
|
|
120
|
+
if (result.handlerRan) return;
|
|
121
|
+
return next();
|
|
122
|
+
}
|
|
123
|
+
for (const [key, value] of Object.entries(result.headers)) {
|
|
124
|
+
res.setHeader(key, value);
|
|
125
|
+
}
|
|
126
|
+
res.status(result.status).json(result.body);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
exports.ExpressAdapter = ExpressAdapter;
|
|
131
|
+
exports.paymentMiddleware = paymentMiddleware;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { H as HTTPAdapter, R as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CUi55YSx.cjs';
|
|
3
|
+
import 'viem';
|
|
4
|
+
|
|
5
|
+
declare class ExpressAdapter implements HTTPAdapter {
|
|
6
|
+
private readonly req;
|
|
7
|
+
private readonly res;
|
|
8
|
+
private readonly next;
|
|
9
|
+
constructor(req: Request, res: Response, next: NextFunction);
|
|
10
|
+
getHeader(name: string): string | undefined;
|
|
11
|
+
getMethod(): string;
|
|
12
|
+
getPath(): string;
|
|
13
|
+
getUrl(): string;
|
|
14
|
+
runHandler(): Promise<RunHandlerResult>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare function paymentMiddleware(config: PaymentMiddlewareConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
export { ExpressAdapter, paymentMiddleware };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { H as HTTPAdapter, R as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CUi55YSx.js';
|
|
3
|
+
import 'viem';
|
|
4
|
+
|
|
5
|
+
declare class ExpressAdapter implements HTTPAdapter {
|
|
6
|
+
private readonly req;
|
|
7
|
+
private readonly res;
|
|
8
|
+
private readonly next;
|
|
9
|
+
constructor(req: Request, res: Response, next: NextFunction);
|
|
10
|
+
getHeader(name: string): string | undefined;
|
|
11
|
+
getMethod(): string;
|
|
12
|
+
getPath(): string;
|
|
13
|
+
getUrl(): string;
|
|
14
|
+
runHandler(): Promise<RunHandlerResult>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare function paymentMiddleware(config: PaymentMiddlewareConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
export { ExpressAdapter, paymentMiddleware };
|