@apitoll/seller-sdk 0.1.0-beta.1
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 +131 -0
- package/dist/analytics.d.ts +62 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +157 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analytics.test.d.ts +2 -0
- package/dist/analytics.test.d.ts.map +1 -0
- package/dist/analytics.test.js +222 -0
- package/dist/analytics.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware-express.d.ts +43 -0
- package/dist/middleware-express.d.ts.map +1 -0
- package/dist/middleware-express.js +239 -0
- package/dist/middleware-express.js.map +1 -0
- package/dist/middleware-hono.d.ts +30 -0
- package/dist/middleware-hono.d.ts.map +1 -0
- package/dist/middleware-hono.js +192 -0
- package/dist/middleware-hono.js.map +1 -0
- package/dist/payment.d.ts +42 -0
- package/dist/payment.d.ts.map +1 -0
- package/dist/payment.js +130 -0
- package/dist/payment.js.map +1 -0
- package/dist/payment.test.d.ts +2 -0
- package/dist/payment.test.d.ts.map +1 -0
- package/dist/payment.test.js +221 -0
- package/dist/payment.test.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { type SellerConfig, type EndpointConfig, type FeeBreakdown } from "@apitoll/shared";
|
|
3
|
+
export interface PaymentMiddlewareOptions extends SellerConfig {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Express middleware that adds x402 payment requirements to protected endpoints.
|
|
7
|
+
* Now supports platform fee splitting — a percentage of each payment goes to the platform.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { paymentMiddleware } from "@apitoll/seller-sdk";
|
|
12
|
+
*
|
|
13
|
+
* app.use(paymentMiddleware({
|
|
14
|
+
* walletAddress: "0xYourWallet...",
|
|
15
|
+
* endpoints: {
|
|
16
|
+
* "GET /api/data": {
|
|
17
|
+
* price: "0.005",
|
|
18
|
+
* chains: ["base", "solana"],
|
|
19
|
+
* description: "Premium data feed",
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* platformFee: {
|
|
23
|
+
* feeBps: 300, // 3%
|
|
24
|
+
* platformWalletBase: "0xPlatformWallet...",
|
|
25
|
+
* },
|
|
26
|
+
* }));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function paymentMiddleware(options: PaymentMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Helper to access the payment receipt from a request.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getPaymentReceipt(req: Request): any;
|
|
34
|
+
/**
|
|
35
|
+
* Helper to access full x402 context from a request (includes fee info).
|
|
36
|
+
*/
|
|
37
|
+
export declare function getX402Context(req: Request): {
|
|
38
|
+
receipt: ReturnType<typeof getPaymentReceipt>;
|
|
39
|
+
feeBreakdown?: FeeBreakdown;
|
|
40
|
+
endpoint: string;
|
|
41
|
+
config: EndpointConfig;
|
|
42
|
+
} | null;
|
|
43
|
+
//# sourceMappingURL=middleware-express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-express.d.ts","sourceRoot":"","sources":["../src/middleware-express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EAGnB,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAUzB,MAAM,WAAW,wBAAyB,SAAQ,YAAY;CAAG;AAEjE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,IAoH/D,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CAwIjB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,OAE7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG;IAC5C,OAAO,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,IAAI,CAEP"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { DEFAULT_CHAIN_CONFIGS, SECURITY_HEADERS, } from "@apitoll/shared";
|
|
2
|
+
import { buildPaymentRequirements, encodePaymentRequired, verifyPayment, findEndpointConfig, getEndpointFeeBreakdown, } from "./payment";
|
|
3
|
+
import { AnalyticsReporter } from "./analytics";
|
|
4
|
+
/**
|
|
5
|
+
* Express middleware that adds x402 payment requirements to protected endpoints.
|
|
6
|
+
* Now supports platform fee splitting — a percentage of each payment goes to the platform.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { paymentMiddleware } from "@apitoll/seller-sdk";
|
|
11
|
+
*
|
|
12
|
+
* app.use(paymentMiddleware({
|
|
13
|
+
* walletAddress: "0xYourWallet...",
|
|
14
|
+
* endpoints: {
|
|
15
|
+
* "GET /api/data": {
|
|
16
|
+
* price: "0.005",
|
|
17
|
+
* chains: ["base", "solana"],
|
|
18
|
+
* description: "Premium data feed",
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* platformFee: {
|
|
22
|
+
* feeBps: 300, // 3%
|
|
23
|
+
* platformWalletBase: "0xPlatformWallet...",
|
|
24
|
+
* },
|
|
25
|
+
* }));
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function paymentMiddleware(options) {
|
|
29
|
+
const { walletAddress, endpoints, chainConfigs: customChainConfigs, facilitatorUrl, webhookUrl, platformApiKey, platformFee, } = options;
|
|
30
|
+
// Merge custom chain configs with defaults
|
|
31
|
+
const chainConfigs = {
|
|
32
|
+
base: { ...DEFAULT_CHAIN_CONFIGS.base, ...customChainConfigs?.base },
|
|
33
|
+
solana: { ...DEFAULT_CHAIN_CONFIGS.solana, ...customChainConfigs?.solana },
|
|
34
|
+
};
|
|
35
|
+
// Initialize analytics reporter
|
|
36
|
+
const reporter = new AnalyticsReporter({
|
|
37
|
+
apiKey: platformApiKey,
|
|
38
|
+
webhookUrl,
|
|
39
|
+
verbose: process.env.NODE_ENV !== "production",
|
|
40
|
+
});
|
|
41
|
+
// Rate limiting: Redis-backed distributed rate limiting with circuit breaker
|
|
42
|
+
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
|
|
43
|
+
const RATE_LIMIT_MAX = 120; // max requests per window per IP
|
|
44
|
+
// Circuit breaker state for Redis failures
|
|
45
|
+
let redisFailureCount = 0;
|
|
46
|
+
let circuitOpen = false;
|
|
47
|
+
let circuitOpenedAt = 0;
|
|
48
|
+
const CIRCUIT_FAILURE_THRESHOLD = 5; // Open circuit after 5 consecutive failures
|
|
49
|
+
const CIRCUIT_RESET_MS = 30_000; // Try again after 30 seconds
|
|
50
|
+
// Redis is optional — if not available, falls through to in-memory rate limiting
|
|
51
|
+
let redis = null;
|
|
52
|
+
try {
|
|
53
|
+
const Redis = require('redis');
|
|
54
|
+
redis = Redis.createClient({
|
|
55
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
56
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
57
|
+
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
58
|
+
});
|
|
59
|
+
redis.on('error', (err) => {
|
|
60
|
+
console.warn('Redis connection error, using in-memory rate limiting:', err.message);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Redis not installed — start with circuit open to use in-memory fallback
|
|
65
|
+
circuitOpen = true;
|
|
66
|
+
circuitOpenedAt = Date.now();
|
|
67
|
+
}
|
|
68
|
+
// In-memory fallback rate limiter (used when Redis circuit is open)
|
|
69
|
+
const fallbackRateLimitMap = new Map();
|
|
70
|
+
function checkFallbackRateLimit(key, limit, windowMs) {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const timestamps = fallbackRateLimitMap.get(key) || [];
|
|
73
|
+
const recent = timestamps.filter((t) => now - t < windowMs);
|
|
74
|
+
recent.push(now);
|
|
75
|
+
fallbackRateLimitMap.set(key, recent);
|
|
76
|
+
// Periodic cleanup to prevent memory leaks
|
|
77
|
+
if (fallbackRateLimitMap.size > 10_000) {
|
|
78
|
+
for (const [k, v] of fallbackRateLimitMap) {
|
|
79
|
+
if (v.every((t) => now - t > windowMs)) {
|
|
80
|
+
fallbackRateLimitMap.delete(k);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return recent.length <= limit;
|
|
85
|
+
}
|
|
86
|
+
async function checkRateLimit(key, limit) {
|
|
87
|
+
// If circuit is open, check if enough time has passed to try again
|
|
88
|
+
if (circuitOpen) {
|
|
89
|
+
if (Date.now() - circuitOpenedAt > CIRCUIT_RESET_MS) {
|
|
90
|
+
// Half-open: try Redis again
|
|
91
|
+
circuitOpen = false;
|
|
92
|
+
redisFailureCount = 0;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Circuit still open — use in-memory fallback
|
|
96
|
+
return checkFallbackRateLimit(key, limit, RATE_LIMIT_WINDOW_MS);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
if (!redis)
|
|
101
|
+
throw new Error('Redis not available');
|
|
102
|
+
const count = await redis.incr(key);
|
|
103
|
+
if (count === 1) {
|
|
104
|
+
await redis.expire(key, Math.ceil(RATE_LIMIT_WINDOW_MS / 1000));
|
|
105
|
+
}
|
|
106
|
+
// Success — reset failure counter
|
|
107
|
+
redisFailureCount = 0;
|
|
108
|
+
return count <= limit;
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
console.error('Rate limit Redis error:', e);
|
|
112
|
+
redisFailureCount++;
|
|
113
|
+
if (redisFailureCount >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
114
|
+
circuitOpen = true;
|
|
115
|
+
circuitOpenedAt = Date.now();
|
|
116
|
+
console.warn(`⚠️ Redis circuit breaker OPEN after ${redisFailureCount} failures. ` +
|
|
117
|
+
`Falling back to in-memory rate limiting for ${CIRCUIT_RESET_MS / 1000}s.`);
|
|
118
|
+
}
|
|
119
|
+
// Fallback to in-memory rate limiting (never fail open)
|
|
120
|
+
return checkFallbackRateLimit(key, limit, RATE_LIMIT_WINDOW_MS);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return async function x402PaymentMiddleware(req, res, next) {
|
|
124
|
+
const startTime = Date.now();
|
|
125
|
+
// Apply security headers
|
|
126
|
+
for (const [header, value] of Object.entries(SECURITY_HEADERS)) {
|
|
127
|
+
res.setHeader(header, value);
|
|
128
|
+
}
|
|
129
|
+
// Rate limiting by IP (Redis-backed, distributed)
|
|
130
|
+
const clientIp = req.ip || req.socket.remoteAddress || "unknown";
|
|
131
|
+
const key = `ratelimit:${clientIp}:${Math.floor(Date.now() / 60000)}`;
|
|
132
|
+
const allowed = await checkRateLimit(key, RATE_LIMIT_MAX);
|
|
133
|
+
if (!allowed) {
|
|
134
|
+
res.status(429);
|
|
135
|
+
res.json({
|
|
136
|
+
error: "Too Many Requests",
|
|
137
|
+
message: "Rate limit exceeded. Try again later.",
|
|
138
|
+
retryAfter: Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Check if this route requires payment
|
|
143
|
+
const match = findEndpointConfig(req.method, req.path, endpoints);
|
|
144
|
+
if (!match) {
|
|
145
|
+
// Not a paid endpoint — pass through
|
|
146
|
+
return next();
|
|
147
|
+
}
|
|
148
|
+
const { pattern, config } = match;
|
|
149
|
+
// Validate Content-Type for POST/PUT/PATCH
|
|
150
|
+
if (["POST", "PUT", "PATCH"].includes(req.method)) {
|
|
151
|
+
const contentType = req.headers["content-type"];
|
|
152
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
153
|
+
res.status(415);
|
|
154
|
+
res.json({
|
|
155
|
+
error: "Unsupported Media Type",
|
|
156
|
+
message: "Content-Type must be application/json",
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Check for X-PAYMENT header (client already paid)
|
|
162
|
+
const paymentHeader = req.headers["x-payment"];
|
|
163
|
+
if (!paymentHeader) {
|
|
164
|
+
// No payment provided — return 402 with payment requirements
|
|
165
|
+
const requirements = buildPaymentRequirements(config, walletAddress, chainConfigs, platformFee);
|
|
166
|
+
const feeBreakdown = getEndpointFeeBreakdown(config, platformFee);
|
|
167
|
+
res.status(402);
|
|
168
|
+
res.setHeader("PAYMENT-REQUIRED", encodePaymentRequired(requirements));
|
|
169
|
+
res.setHeader("Content-Type", "application/json");
|
|
170
|
+
res.json({
|
|
171
|
+
error: "Payment Required",
|
|
172
|
+
paymentRequirements: requirements,
|
|
173
|
+
description: config.description,
|
|
174
|
+
feeBreakdown: platformFee ? feeBreakdown : undefined,
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Payment provided — verify it
|
|
179
|
+
const requirements = buildPaymentRequirements(config, walletAddress, chainConfigs, platformFee);
|
|
180
|
+
const resolvedFacilitatorUrl = facilitatorUrl || chainConfigs.base.facilitatorUrl;
|
|
181
|
+
const verification = await verifyPayment({
|
|
182
|
+
paymentHeader,
|
|
183
|
+
requirements,
|
|
184
|
+
facilitatorUrl: resolvedFacilitatorUrl,
|
|
185
|
+
}, platformFee);
|
|
186
|
+
if (!verification.valid) {
|
|
187
|
+
await reporter.reportRejection(pattern, req.method, verification.error || "unknown");
|
|
188
|
+
res.status(402);
|
|
189
|
+
res.json({
|
|
190
|
+
error: "Payment Invalid",
|
|
191
|
+
message: verification.error,
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Payment verified — attach receipt + fee info to request and continue
|
|
196
|
+
req.paymentReceipt = verification.receipt;
|
|
197
|
+
req.x402 = {
|
|
198
|
+
receipt: verification.receipt,
|
|
199
|
+
feeBreakdown: verification.feeBreakdown,
|
|
200
|
+
endpoint: pattern,
|
|
201
|
+
config,
|
|
202
|
+
};
|
|
203
|
+
// Intercept response to report analytics (including fee data)
|
|
204
|
+
const originalEnd = res.end.bind(res);
|
|
205
|
+
res.end = function (chunk, encoding, cb) {
|
|
206
|
+
const latencyMs = Date.now() - startTime;
|
|
207
|
+
if (verification.receipt) {
|
|
208
|
+
reporter.report({
|
|
209
|
+
endpoint: pattern,
|
|
210
|
+
method: req.method,
|
|
211
|
+
receipt: verification.receipt,
|
|
212
|
+
responseStatus: res.statusCode,
|
|
213
|
+
latencyMs,
|
|
214
|
+
feeBreakdown: verification.feeBreakdown,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (typeof encoding === 'function') {
|
|
218
|
+
return originalEnd(chunk, encoding);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
return originalEnd(chunk, encoding, cb);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
next();
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Helper to access the payment receipt from a request.
|
|
229
|
+
*/
|
|
230
|
+
export function getPaymentReceipt(req) {
|
|
231
|
+
return req.paymentReceipt || null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Helper to access full x402 context from a request (includes fee info).
|
|
235
|
+
*/
|
|
236
|
+
export function getX402Context(req) {
|
|
237
|
+
return req.x402 || null;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=middleware-express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-express.js","sourceRoot":"","sources":["../src/middleware-express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import { type SellerConfig } from "@apitoll/shared";
|
|
3
|
+
/**
|
|
4
|
+
* Hono middleware for x402 payments with platform fee support.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { Hono } from "hono";
|
|
9
|
+
* import { paymentMiddleware } from "@apitoll/seller-sdk/hono";
|
|
10
|
+
*
|
|
11
|
+
* const app = new Hono();
|
|
12
|
+
*
|
|
13
|
+
* app.use("*", paymentMiddleware({
|
|
14
|
+
* walletAddress: "0xYourWallet...",
|
|
15
|
+
* endpoints: {
|
|
16
|
+
* "GET /api/data": {
|
|
17
|
+
* price: "0.005",
|
|
18
|
+
* chains: ["base", "solana"],
|
|
19
|
+
* description: "Premium data feed",
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* platformFee: {
|
|
23
|
+
* feeBps: 300, // 3%
|
|
24
|
+
* platformWalletBase: "0xPlatformWallet...",
|
|
25
|
+
* },
|
|
26
|
+
* }));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function paymentMiddleware(options: SellerConfig): MiddlewareHandler;
|
|
30
|
+
//# sourceMappingURL=middleware-hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-hono.d.ts","sourceRoot":"","sources":["../src/middleware-hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,EACL,KAAK,YAAY,EAKlB,MAAM,iBAAiB,CAAC;AAUzB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,iBAAiB,CAsN1E"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { DEFAULT_CHAIN_CONFIGS, SECURITY_HEADERS, } from "@apitoll/shared";
|
|
2
|
+
import { buildPaymentRequirements, encodePaymentRequired, verifyPayment, findEndpointConfig, getEndpointFeeBreakdown, } from "./payment";
|
|
3
|
+
import { AnalyticsReporter } from "./analytics";
|
|
4
|
+
/**
|
|
5
|
+
* Hono middleware for x402 payments with platform fee support.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Hono } from "hono";
|
|
10
|
+
* import { paymentMiddleware } from "@apitoll/seller-sdk/hono";
|
|
11
|
+
*
|
|
12
|
+
* const app = new Hono();
|
|
13
|
+
*
|
|
14
|
+
* app.use("*", paymentMiddleware({
|
|
15
|
+
* walletAddress: "0xYourWallet...",
|
|
16
|
+
* endpoints: {
|
|
17
|
+
* "GET /api/data": {
|
|
18
|
+
* price: "0.005",
|
|
19
|
+
* chains: ["base", "solana"],
|
|
20
|
+
* description: "Premium data feed",
|
|
21
|
+
* },
|
|
22
|
+
* },
|
|
23
|
+
* platformFee: {
|
|
24
|
+
* feeBps: 300, // 3%
|
|
25
|
+
* platformWalletBase: "0xPlatformWallet...",
|
|
26
|
+
* },
|
|
27
|
+
* }));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function paymentMiddleware(options) {
|
|
31
|
+
const { walletAddress, endpoints, chainConfigs: customChainConfigs, facilitatorUrl, webhookUrl, platformApiKey, platformFee, } = options;
|
|
32
|
+
const chainConfigs = {
|
|
33
|
+
base: { ...DEFAULT_CHAIN_CONFIGS.base, ...customChainConfigs?.base },
|
|
34
|
+
solana: { ...DEFAULT_CHAIN_CONFIGS.solana, ...customChainConfigs?.solana },
|
|
35
|
+
};
|
|
36
|
+
const reporter = new AnalyticsReporter({
|
|
37
|
+
apiKey: platformApiKey,
|
|
38
|
+
webhookUrl,
|
|
39
|
+
verbose: process.env.NODE_ENV !== "production",
|
|
40
|
+
});
|
|
41
|
+
// Rate limiting: Redis-backed with circuit breaker fallback
|
|
42
|
+
const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
43
|
+
const RATE_LIMIT_MAX = 120;
|
|
44
|
+
let redis = null;
|
|
45
|
+
try {
|
|
46
|
+
const Redis = require('redis');
|
|
47
|
+
redis = Redis.createClient({
|
|
48
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
49
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
50
|
+
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
console.warn('Redis not available for Hono middleware, using in-memory rate limiting');
|
|
55
|
+
}
|
|
56
|
+
// Circuit breaker state
|
|
57
|
+
let redisFailureCount = 0;
|
|
58
|
+
let circuitOpen = false;
|
|
59
|
+
let circuitOpenedAt = 0;
|
|
60
|
+
const CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
61
|
+
const CIRCUIT_RESET_MS = 30_000;
|
|
62
|
+
// In-memory fallback
|
|
63
|
+
const fallbackRateLimitMap = new Map();
|
|
64
|
+
function checkFallbackRateLimit(key, limit, windowMs) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const timestamps = fallbackRateLimitMap.get(key) || [];
|
|
67
|
+
const recent = timestamps.filter((t) => now - t < windowMs);
|
|
68
|
+
recent.push(now);
|
|
69
|
+
fallbackRateLimitMap.set(key, recent);
|
|
70
|
+
if (fallbackRateLimitMap.size > 10_000) {
|
|
71
|
+
for (const [k, v] of fallbackRateLimitMap) {
|
|
72
|
+
if (v.every((t) => now - t > windowMs)) {
|
|
73
|
+
fallbackRateLimitMap.delete(k);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return recent.length <= limit;
|
|
78
|
+
}
|
|
79
|
+
async function checkRateLimit(key, limit) {
|
|
80
|
+
if (!redis) {
|
|
81
|
+
return checkFallbackRateLimit(key, limit, RATE_LIMIT_WINDOW_MS);
|
|
82
|
+
}
|
|
83
|
+
if (circuitOpen) {
|
|
84
|
+
if (Date.now() - circuitOpenedAt > CIRCUIT_RESET_MS) {
|
|
85
|
+
circuitOpen = false;
|
|
86
|
+
redisFailureCount = 0;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return checkFallbackRateLimit(key, limit, RATE_LIMIT_WINDOW_MS);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const count = await redis.incr(key);
|
|
94
|
+
if (count === 1) {
|
|
95
|
+
await redis.expire(key, Math.ceil(RATE_LIMIT_WINDOW_MS / 1000));
|
|
96
|
+
}
|
|
97
|
+
redisFailureCount = 0;
|
|
98
|
+
return count <= limit;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
console.error('Rate limit Redis error (Hono):', e);
|
|
102
|
+
redisFailureCount++;
|
|
103
|
+
if (redisFailureCount >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
104
|
+
circuitOpen = true;
|
|
105
|
+
circuitOpenedAt = Date.now();
|
|
106
|
+
console.warn(`⚠️ Redis circuit breaker OPEN (Hono). Falling back to in-memory rate limiting.`);
|
|
107
|
+
}
|
|
108
|
+
return checkFallbackRateLimit(key, limit, RATE_LIMIT_WINDOW_MS);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return async (c, next) => {
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
const url = new URL(c.req.url);
|
|
114
|
+
const method = c.req.method;
|
|
115
|
+
const path = url.pathname;
|
|
116
|
+
// Apply security headers
|
|
117
|
+
for (const [header, value] of Object.entries(SECURITY_HEADERS)) {
|
|
118
|
+
c.header(header, value);
|
|
119
|
+
}
|
|
120
|
+
// Rate limiting by IP (Redis-backed with circuit breaker)
|
|
121
|
+
const clientIp = c.req.header("x-forwarded-for") || "unknown";
|
|
122
|
+
const key = `ratelimit:${clientIp}:${Math.floor(Date.now() / 60000)}`;
|
|
123
|
+
const allowed = await checkRateLimit(key, RATE_LIMIT_MAX);
|
|
124
|
+
if (!allowed) {
|
|
125
|
+
return c.json({
|
|
126
|
+
error: "Too Many Requests",
|
|
127
|
+
message: "Rate limit exceeded. Try again later.",
|
|
128
|
+
retryAfter: Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
|
|
129
|
+
}, 429);
|
|
130
|
+
}
|
|
131
|
+
// Check if this route requires payment
|
|
132
|
+
const match = findEndpointConfig(method, path, endpoints);
|
|
133
|
+
if (!match) {
|
|
134
|
+
return next();
|
|
135
|
+
}
|
|
136
|
+
const { pattern, config } = match;
|
|
137
|
+
// Validate Content-Type for mutation methods
|
|
138
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
139
|
+
const contentType = c.req.header("content-type");
|
|
140
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
141
|
+
return c.json({ error: "Unsupported Media Type", message: "Content-Type must be application/json" }, 415);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const paymentHeader = c.req.header("x-payment");
|
|
145
|
+
if (!paymentHeader) {
|
|
146
|
+
const requirements = buildPaymentRequirements(config, walletAddress, chainConfigs, platformFee);
|
|
147
|
+
const feeBreakdown = getEndpointFeeBreakdown(config, platformFee);
|
|
148
|
+
return c.json({
|
|
149
|
+
error: "Payment Required",
|
|
150
|
+
paymentRequirements: requirements,
|
|
151
|
+
description: config.description,
|
|
152
|
+
feeBreakdown: platformFee ? feeBreakdown : undefined,
|
|
153
|
+
}, 402, {
|
|
154
|
+
"PAYMENT-REQUIRED": encodePaymentRequired(requirements),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Verify payment
|
|
158
|
+
const requirements = buildPaymentRequirements(config, walletAddress, chainConfigs, platformFee);
|
|
159
|
+
const resolvedFacilitatorUrl = facilitatorUrl || chainConfigs.base.facilitatorUrl;
|
|
160
|
+
const verification = await verifyPayment({
|
|
161
|
+
paymentHeader,
|
|
162
|
+
requirements,
|
|
163
|
+
facilitatorUrl: resolvedFacilitatorUrl,
|
|
164
|
+
}, platformFee);
|
|
165
|
+
if (!verification.valid) {
|
|
166
|
+
await reporter.reportRejection(pattern, method, verification.error || "unknown");
|
|
167
|
+
return c.json({ error: "Payment Invalid", message: verification.error }, 402);
|
|
168
|
+
}
|
|
169
|
+
// Attach receipt + fee info to context
|
|
170
|
+
c.set("paymentReceipt", verification.receipt);
|
|
171
|
+
c.set("x402", {
|
|
172
|
+
receipt: verification.receipt,
|
|
173
|
+
feeBreakdown: verification.feeBreakdown,
|
|
174
|
+
endpoint: pattern,
|
|
175
|
+
config,
|
|
176
|
+
});
|
|
177
|
+
await next();
|
|
178
|
+
// Report after response (including fee data)
|
|
179
|
+
const latencyMs = Date.now() - startTime;
|
|
180
|
+
if (verification.receipt) {
|
|
181
|
+
reporter.report({
|
|
182
|
+
endpoint: pattern,
|
|
183
|
+
method,
|
|
184
|
+
receipt: verification.receipt,
|
|
185
|
+
responseStatus: c.res.status,
|
|
186
|
+
latencyMs,
|
|
187
|
+
feeBreakdown: verification.feeBreakdown,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=middleware-hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-hono.js","sourceRoot":"","sources":["../src/middleware-hono.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SupportedChain, type PaymentRequirement, type PaymentReceipt, type EndpointConfig, type ChainConfig, type PlatformFeeConfig, type FeeBreakdown } from "@apitoll/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Build the PaymentRequired response body for a given endpoint config.
|
|
4
|
+
* Returns an array of PaymentRequirement objects (one per supported chain).
|
|
5
|
+
* When platform fees are configured, the response includes fee metadata.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildPaymentRequirements(endpoint: EndpointConfig, sellerWallet: string, chainConfigs: Record<SupportedChain, ChainConfig>, platformFee?: PlatformFeeConfig): PaymentRequirement[];
|
|
8
|
+
/**
|
|
9
|
+
* Get the fee breakdown for an endpoint price.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getEndpointFeeBreakdown(endpoint: EndpointConfig, platformFee?: PlatformFeeConfig): FeeBreakdown;
|
|
12
|
+
/**
|
|
13
|
+
* Build the base64-encoded PAYMENT-REQUIRED header value.
|
|
14
|
+
*/
|
|
15
|
+
export declare function encodePaymentRequired(requirements: PaymentRequirement[]): string;
|
|
16
|
+
export interface VerifyPaymentOptions {
|
|
17
|
+
/** The X-PAYMENT header value from the client */
|
|
18
|
+
paymentHeader: string;
|
|
19
|
+
/** The payment requirements that were sent in the 402 response */
|
|
20
|
+
requirements: PaymentRequirement[];
|
|
21
|
+
/** Facilitator URL for verification */
|
|
22
|
+
facilitatorUrl: string;
|
|
23
|
+
}
|
|
24
|
+
export interface VerificationResult {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
receipt?: PaymentReceipt;
|
|
27
|
+
feeBreakdown?: FeeBreakdown;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Verify a payment by calling the x402 facilitator.
|
|
32
|
+
* The facilitator checks on-chain that the payment is valid and settles it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function verifyPayment(options: VerifyPaymentOptions, platformFee?: PlatformFeeConfig): Promise<VerificationResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Find the matching endpoint config for a given request.
|
|
37
|
+
*/
|
|
38
|
+
export declare function findEndpointConfig(method: string, path: string, endpoints: Record<string, EndpointConfig>): {
|
|
39
|
+
pattern: string;
|
|
40
|
+
config: EndpointConfig;
|
|
41
|
+
} | null;
|
|
42
|
+
//# sourceMappingURL=payment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.d.ts","sourceRoot":"","sources":["../src/payment.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAQlB,MAAM,iBAAiB,CAAC;AAIzB;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,EACjD,WAAW,CAAC,EAAE,iBAAiB,GAC9B,kBAAkB,EAAE,CA+BtB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,cAAc,EACxB,WAAW,CAAC,EAAE,iBAAiB,GAC9B,YAAY,CAEd;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAEhF;AAID,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,uCAAuC;IACvC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,CAAC,EAAE,iBAAiB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAuE7B;AAaD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAAG,IAAI,CAOpD"}
|