pay-lobster 1.0.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/README.md +401 -0
- package/README.md.bak +401 -0
- package/dist/agent.d.ts +132 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +224 -0
- package/dist/agent.js.map +1 -0
- package/dist/analytics.d.ts +120 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +345 -0
- package/dist/analytics.js.map +1 -0
- package/dist/approvals.d.ts +168 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +406 -0
- package/dist/approvals.js.map +1 -0
- package/dist/circle-client.d.ts +152 -0
- package/dist/circle-client.d.ts.map +1 -0
- package/dist/circle-client.js +266 -0
- package/dist/circle-client.js.map +1 -0
- package/dist/commission.d.ts +191 -0
- package/dist/commission.d.ts.map +1 -0
- package/dist/commission.js +475 -0
- package/dist/commission.js.map +1 -0
- package/dist/condition-builder.d.ts +98 -0
- package/dist/condition-builder.d.ts.map +1 -0
- package/dist/condition-builder.js +193 -0
- package/dist/condition-builder.js.map +1 -0
- package/dist/contacts.d.ts +179 -0
- package/dist/contacts.d.ts.map +1 -0
- package/dist/contacts.js +445 -0
- package/dist/contacts.js.map +1 -0
- package/dist/easy.d.ts +22 -0
- package/dist/easy.d.ts.map +1 -0
- package/dist/easy.js +40 -0
- package/dist/easy.js.map +1 -0
- package/dist/erc8004/constants.d.ts +152 -0
- package/dist/erc8004/constants.d.ts.map +1 -0
- package/dist/erc8004/constants.js +114 -0
- package/dist/erc8004/constants.js.map +1 -0
- package/dist/erc8004/discovery.d.ts +84 -0
- package/dist/erc8004/discovery.d.ts.map +1 -0
- package/dist/erc8004/discovery.js +217 -0
- package/dist/erc8004/discovery.js.map +1 -0
- package/dist/erc8004/identity.d.ts +91 -0
- package/dist/erc8004/identity.d.ts.map +1 -0
- package/dist/erc8004/identity.js +250 -0
- package/dist/erc8004/identity.js.map +1 -0
- package/dist/erc8004/index.d.ts +147 -0
- package/dist/erc8004/index.d.ts.map +1 -0
- package/dist/erc8004/index.js +225 -0
- package/dist/erc8004/index.js.map +1 -0
- package/dist/erc8004/reputation.d.ts +133 -0
- package/dist/erc8004/reputation.d.ts.map +1 -0
- package/dist/erc8004/reputation.js +277 -0
- package/dist/erc8004/reputation.js.map +1 -0
- package/dist/escrow-templates.d.ts +38 -0
- package/dist/escrow-templates.d.ts.map +1 -0
- package/dist/escrow-templates.js +419 -0
- package/dist/escrow-templates.js.map +1 -0
- package/dist/escrow.d.ts +320 -0
- package/dist/escrow.d.ts.map +1 -0
- package/dist/escrow.js +854 -0
- package/dist/escrow.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/invoices.d.ts +212 -0
- package/dist/invoices.d.ts.map +1 -0
- package/dist/invoices.js +393 -0
- package/dist/invoices.js.map +1 -0
- package/dist/notifications.d.ts +141 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +350 -0
- package/dist/notifications.js.map +1 -0
- package/dist/tips.d.ts +171 -0
- package/dist/tips.d.ts.map +1 -0
- package/dist/tips.js +390 -0
- package/dist/tips.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/x402-client.d.ts +127 -0
- package/dist/x402-client.d.ts.map +1 -0
- package/dist/x402-client.js +350 -0
- package/dist/x402-client.js.map +1 -0
- package/dist/x402-server.d.ts +133 -0
- package/dist/x402-server.d.ts.map +1 -0
- package/dist/x402-server.js +330 -0
- package/dist/x402-server.js.map +1 -0
- package/lib/agent.ts +273 -0
- package/lib/analytics.ts +474 -0
- package/lib/analytics.ts.bak +474 -0
- package/lib/approvals.ts +585 -0
- package/lib/approvals.ts.bak +585 -0
- package/lib/circle-client.ts +376 -0
- package/lib/circle-client.ts.bak +376 -0
- package/lib/commission.ts +680 -0
- package/lib/commission.ts.bak +680 -0
- package/lib/condition-builder.ts +223 -0
- package/lib/condition-builder.ts.bak +223 -0
- package/lib/contacts.ts +615 -0
- package/lib/contacts.ts.bak +615 -0
- package/lib/easy.ts +46 -0
- package/lib/easy.ts.bak +352 -0
- package/lib/erc8004/constants.ts +175 -0
- package/lib/erc8004/discovery.ts +299 -0
- package/lib/erc8004/identity.ts +327 -0
- package/lib/erc8004/index.ts +285 -0
- package/lib/erc8004/reputation.ts +368 -0
- package/lib/escrow-templates.ts +462 -0
- package/lib/escrow.ts +1216 -0
- package/lib/index.ts +13 -0
- package/lib/invoices.ts +588 -0
- package/lib/notifications.ts +484 -0
- package/lib/tips.ts +570 -0
- package/lib/types.ts +108 -0
- package/lib/x402-client.ts +471 -0
- package/lib/x402-server.ts +462 -0
- package/package.json +58 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 Payment Server Middleware
|
|
3
|
+
*
|
|
4
|
+
* Express middleware for HTTP 402 Payment Required.
|
|
5
|
+
* Generates payment challenges and verifies payments via Coinbase facilitator.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Request, Response, NextFunction } from 'express';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
// x402 Configuration
|
|
12
|
+
export interface X402ServerConfig {
|
|
13
|
+
facilitatorUrl?: string; // Coinbase x402 facilitator
|
|
14
|
+
network: string; // e.g., 'ETH-SEPOLIA'
|
|
15
|
+
receiverAddress: string; // Your wallet address
|
|
16
|
+
acceptedAssets?: string[]; // Default: ['USDC']
|
|
17
|
+
challengeExpiry?: number; // Seconds (default: 300 = 5 min)
|
|
18
|
+
|
|
19
|
+
// Custom verification function (optional)
|
|
20
|
+
verifyPayment?: (signature: string, amount: string) => Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Payment Challenge Response
|
|
24
|
+
export interface PaymentChallenge {
|
|
25
|
+
'x-payment-required': {
|
|
26
|
+
version: '1';
|
|
27
|
+
network: string;
|
|
28
|
+
receiver: string;
|
|
29
|
+
asset: string;
|
|
30
|
+
amount: string;
|
|
31
|
+
description: string;
|
|
32
|
+
expires: number;
|
|
33
|
+
nonce: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Global config (can be set once for all middleware)
|
|
38
|
+
let globalConfig: X402ServerConfig | null = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configure x402 server globally
|
|
42
|
+
*/
|
|
43
|
+
export function configureX402Server(config: X402ServerConfig): void {
|
|
44
|
+
globalConfig = {
|
|
45
|
+
facilitatorUrl: config.facilitatorUrl || 'https://x402.coinbase.com',
|
|
46
|
+
acceptedAssets: config.acceptedAssets || ['USDC'],
|
|
47
|
+
challengeExpiry: config.challengeExpiry || 300,
|
|
48
|
+
...config,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get effective config (merge global + local)
|
|
54
|
+
*/
|
|
55
|
+
function getConfig(localConfig?: Partial<X402ServerConfig>): X402ServerConfig {
|
|
56
|
+
if (!globalConfig && !localConfig) {
|
|
57
|
+
throw new Error('x402 server not configured. Call configureX402Server() first.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
facilitatorUrl: localConfig?.facilitatorUrl || globalConfig?.facilitatorUrl || 'https://x402.coinbase.com',
|
|
62
|
+
network: localConfig?.network || globalConfig?.network || 'ETH-SEPOLIA',
|
|
63
|
+
receiverAddress: localConfig?.receiverAddress || globalConfig?.receiverAddress || '',
|
|
64
|
+
acceptedAssets: localConfig?.acceptedAssets || globalConfig?.acceptedAssets || ['USDC'],
|
|
65
|
+
challengeExpiry: localConfig?.challengeExpiry || globalConfig?.challengeExpiry || 300,
|
|
66
|
+
verifyPayment: localConfig?.verifyPayment || globalConfig?.verifyPayment,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create payment challenge
|
|
72
|
+
*/
|
|
73
|
+
export function createPaymentChallenge(
|
|
74
|
+
amount: string,
|
|
75
|
+
description: string,
|
|
76
|
+
config?: Partial<X402ServerConfig>
|
|
77
|
+
): PaymentChallenge {
|
|
78
|
+
const effectiveConfig = getConfig(config);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
'x-payment-required': {
|
|
82
|
+
version: '1',
|
|
83
|
+
network: effectiveConfig.network,
|
|
84
|
+
receiver: effectiveConfig.receiverAddress,
|
|
85
|
+
asset: 'USDC',
|
|
86
|
+
amount,
|
|
87
|
+
description,
|
|
88
|
+
expires: Math.floor(Date.now() / 1000) + (effectiveConfig.challengeExpiry || 300),
|
|
89
|
+
nonce: crypto.randomUUID(),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Paywall middleware - require payment to access endpoint
|
|
96
|
+
*/
|
|
97
|
+
export function paywall(
|
|
98
|
+
price: string,
|
|
99
|
+
description: string,
|
|
100
|
+
config?: Partial<X402ServerConfig>
|
|
101
|
+
) {
|
|
102
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
103
|
+
const paymentSignature = req.headers['x-payment-signature'];
|
|
104
|
+
|
|
105
|
+
if (!paymentSignature) {
|
|
106
|
+
// No payment provided - return 402 with challenge
|
|
107
|
+
const challenge = createPaymentChallenge(price, description, config);
|
|
108
|
+
|
|
109
|
+
res.status(402).json({
|
|
110
|
+
error: 'Payment Required',
|
|
111
|
+
message: `This endpoint requires ${price} USDC`,
|
|
112
|
+
...challenge,
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Verify payment
|
|
118
|
+
try {
|
|
119
|
+
const effectiveConfig = getConfig(config);
|
|
120
|
+
const verified = await verifyPayment(
|
|
121
|
+
paymentSignature as string,
|
|
122
|
+
price,
|
|
123
|
+
effectiveConfig
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (verified) {
|
|
127
|
+
// Payment verified - proceed
|
|
128
|
+
(req as any).paymentVerified = true;
|
|
129
|
+
(req as any).paymentAmount = price;
|
|
130
|
+
(req as any).paymentSignature = paymentSignature;
|
|
131
|
+
next();
|
|
132
|
+
} else {
|
|
133
|
+
res.status(402).json({
|
|
134
|
+
error: 'Payment Invalid',
|
|
135
|
+
message: 'Payment signature could not be verified',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Payment verification error:', error);
|
|
140
|
+
res.status(500).json({
|
|
141
|
+
error: 'Payment Verification Failed',
|
|
142
|
+
message: (error as Error).message,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Dynamic paywall - determine price at runtime
|
|
150
|
+
*/
|
|
151
|
+
export function dynamicPaywall(
|
|
152
|
+
priceFn: (req: Request) => string | Promise<string>,
|
|
153
|
+
descriptionFn: (req: Request) => string | Promise<string>,
|
|
154
|
+
config?: Partial<X402ServerConfig>
|
|
155
|
+
) {
|
|
156
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
157
|
+
try {
|
|
158
|
+
// Determine price and description
|
|
159
|
+
const price = await Promise.resolve(priceFn(req));
|
|
160
|
+
const description = await Promise.resolve(descriptionFn(req));
|
|
161
|
+
|
|
162
|
+
// Use standard paywall
|
|
163
|
+
const middleware = paywall(price, description, config);
|
|
164
|
+
await middleware(req, res, next);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
res.status(500).json({
|
|
167
|
+
error: 'Paywall Configuration Error',
|
|
168
|
+
message: (error as Error).message,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Verify payment with Coinbase x402 facilitator
|
|
176
|
+
*/
|
|
177
|
+
async function verifyPayment(
|
|
178
|
+
signature: string,
|
|
179
|
+
expectedAmount: string,
|
|
180
|
+
config: X402ServerConfig
|
|
181
|
+
): Promise<boolean> {
|
|
182
|
+
// Use custom verification if provided
|
|
183
|
+
if (config.verifyPayment) {
|
|
184
|
+
return config.verifyPayment(signature, expectedAmount);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Default: verify with Coinbase facilitator
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(`${config.facilitatorUrl}/verify`, {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
headers: {
|
|
192
|
+
'Content-Type': 'application/json',
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
signature,
|
|
196
|
+
expectedAmount,
|
|
197
|
+
network: config.network,
|
|
198
|
+
receiver: config.receiverAddress,
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = await response.json();
|
|
207
|
+
return result.valid === true;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('Facilitator verification error:', error);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Mock verification for testing (accepts any signature)
|
|
216
|
+
*/
|
|
217
|
+
export function mockVerification(): (signature: string, amount: string) => Promise<boolean> {
|
|
218
|
+
return async (signature: string, amount: string) => {
|
|
219
|
+
// In testing, accept any non-empty signature
|
|
220
|
+
return signature.length > 0;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Simple signature verification (hash-based)
|
|
226
|
+
* Use only for testing - NOT secure for production
|
|
227
|
+
*/
|
|
228
|
+
export function simpleVerification(secret: string): (signature: string, amount: string) => Promise<boolean> {
|
|
229
|
+
return async (signature: string, amount: string) => {
|
|
230
|
+
// Verify signature matches hash of secret + amount
|
|
231
|
+
const expectedSig = crypto.createHash('sha256')
|
|
232
|
+
.update(secret + amount)
|
|
233
|
+
.digest('hex');
|
|
234
|
+
|
|
235
|
+
return signature === expectedSig;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Pricing helper - combine multiple tiers
|
|
241
|
+
*/
|
|
242
|
+
export class PricingTier {
|
|
243
|
+
private tiers: Map<string, string> = new Map();
|
|
244
|
+
|
|
245
|
+
add(key: string, price: string): this {
|
|
246
|
+
this.tiers.set(key, price);
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
get(key: string): string | undefined {
|
|
251
|
+
return this.tiers.get(key);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
middleware(
|
|
255
|
+
keyFn: (req: Request) => string,
|
|
256
|
+
descriptionFn: (req: Request) => string,
|
|
257
|
+
config?: Partial<X402ServerConfig>
|
|
258
|
+
) {
|
|
259
|
+
return dynamicPaywall(
|
|
260
|
+
(req) => {
|
|
261
|
+
const key = keyFn(req);
|
|
262
|
+
const price = this.tiers.get(key);
|
|
263
|
+
if (!price) {
|
|
264
|
+
throw new Error(`No price configured for tier: ${key}`);
|
|
265
|
+
}
|
|
266
|
+
return price;
|
|
267
|
+
},
|
|
268
|
+
descriptionFn,
|
|
269
|
+
config
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Usage-based pricing
|
|
276
|
+
*/
|
|
277
|
+
export interface UsageConfig {
|
|
278
|
+
basePrice: string; // Base price per request
|
|
279
|
+
perUnit: string; // Price per unit
|
|
280
|
+
unit: string; // Unit name (e.g., 'cpu-second', 'MB', 'query')
|
|
281
|
+
calculate: (req: Request) => number | Promise<number>; // Calculate units
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function usagePaywall(
|
|
285
|
+
usage: UsageConfig,
|
|
286
|
+
config?: Partial<X402ServerConfig>
|
|
287
|
+
) {
|
|
288
|
+
return dynamicPaywall(
|
|
289
|
+
async (req) => {
|
|
290
|
+
const units = await Promise.resolve(usage.calculate(req));
|
|
291
|
+
const base = parseFloat(usage.basePrice);
|
|
292
|
+
const perUnit = parseFloat(usage.perUnit);
|
|
293
|
+
const total = base + (units * perUnit);
|
|
294
|
+
return total.toFixed(2);
|
|
295
|
+
},
|
|
296
|
+
(req) => `Usage-based pricing: base + ${usage.perUnit} per ${usage.unit}`,
|
|
297
|
+
config
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Subscription paywall (check for valid subscription)
|
|
303
|
+
*/
|
|
304
|
+
export interface SubscriptionConfig {
|
|
305
|
+
getSubscription: (req: Request) => Promise<{
|
|
306
|
+
active: boolean;
|
|
307
|
+
expiresAt: string;
|
|
308
|
+
} | null>;
|
|
309
|
+
price: string;
|
|
310
|
+
period: 'monthly' | 'yearly';
|
|
311
|
+
renewalUrl?: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function subscriptionPaywall(
|
|
315
|
+
subscription: SubscriptionConfig,
|
|
316
|
+
config?: Partial<X402ServerConfig>
|
|
317
|
+
) {
|
|
318
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
319
|
+
// Check for active subscription
|
|
320
|
+
const sub = await subscription.getSubscription(req);
|
|
321
|
+
|
|
322
|
+
if (sub && sub.active && new Date(sub.expiresAt) > new Date()) {
|
|
323
|
+
// Active subscription - allow access
|
|
324
|
+
(req as any).subscription = sub;
|
|
325
|
+
next();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// No active subscription - require payment
|
|
330
|
+
const description = `${subscription.period} subscription - ${subscription.price} USDC`;
|
|
331
|
+
const challenge = createPaymentChallenge(subscription.price, description, config);
|
|
332
|
+
|
|
333
|
+
res.status(402).json({
|
|
334
|
+
error: 'Subscription Required',
|
|
335
|
+
message: `This endpoint requires an active ${subscription.period} subscription`,
|
|
336
|
+
subscription: {
|
|
337
|
+
price: subscription.price,
|
|
338
|
+
period: subscription.period,
|
|
339
|
+
renewalUrl: subscription.renewalUrl,
|
|
340
|
+
},
|
|
341
|
+
...challenge,
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Rate-limited paywall (free tier + paid tier)
|
|
348
|
+
*/
|
|
349
|
+
export interface RateLimitConfig {
|
|
350
|
+
free: {
|
|
351
|
+
limit: number; // Free requests per window
|
|
352
|
+
windowMs: number; // Window in milliseconds
|
|
353
|
+
};
|
|
354
|
+
paid: {
|
|
355
|
+
price: string; // Price per additional request
|
|
356
|
+
description: string;
|
|
357
|
+
};
|
|
358
|
+
getUserId: (req: Request) => string;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Simple in-memory rate limiter
|
|
362
|
+
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
|
|
363
|
+
|
|
364
|
+
export function rateLimitedPaywall(
|
|
365
|
+
rateLimitConfig: RateLimitConfig,
|
|
366
|
+
config?: Partial<X402ServerConfig>
|
|
367
|
+
) {
|
|
368
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
369
|
+
const userId = rateLimitConfig.getUserId(req);
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
|
|
372
|
+
// Get or create usage record
|
|
373
|
+
let usage = rateLimitStore.get(userId);
|
|
374
|
+
|
|
375
|
+
if (!usage || usage.resetAt < now) {
|
|
376
|
+
// New window
|
|
377
|
+
usage = {
|
|
378
|
+
count: 0,
|
|
379
|
+
resetAt: now + rateLimitConfig.free.windowMs,
|
|
380
|
+
};
|
|
381
|
+
rateLimitStore.set(userId, usage);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check if within free tier
|
|
385
|
+
if (usage.count < rateLimitConfig.free.limit) {
|
|
386
|
+
usage.count++;
|
|
387
|
+
next();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Exceeded free tier - require payment
|
|
392
|
+
const paymentSignature = req.headers['x-payment-signature'];
|
|
393
|
+
|
|
394
|
+
if (!paymentSignature) {
|
|
395
|
+
const challenge = createPaymentChallenge(
|
|
396
|
+
rateLimitConfig.paid.price,
|
|
397
|
+
rateLimitConfig.paid.description,
|
|
398
|
+
config
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
res.status(402).json({
|
|
402
|
+
error: 'Rate Limit Exceeded',
|
|
403
|
+
message: `Free tier limit reached (${rateLimitConfig.free.limit} requests per window)`,
|
|
404
|
+
rateTier: 'paid',
|
|
405
|
+
...challenge,
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Verify payment for additional request
|
|
411
|
+
const effectiveConfig = getConfig(config);
|
|
412
|
+
const verified = await verifyPayment(
|
|
413
|
+
paymentSignature as string,
|
|
414
|
+
rateLimitConfig.paid.price,
|
|
415
|
+
effectiveConfig
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
if (verified) {
|
|
419
|
+
(req as any).paymentVerified = true;
|
|
420
|
+
(req as any).paidRequest = true;
|
|
421
|
+
next();
|
|
422
|
+
} else {
|
|
423
|
+
res.status(402).json({
|
|
424
|
+
error: 'Payment Invalid',
|
|
425
|
+
message: 'Payment signature could not be verified',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Express router with common x402 endpoints
|
|
433
|
+
*/
|
|
434
|
+
export function createX402Router() {
|
|
435
|
+
// This would return an Express router with health/pricing endpoints
|
|
436
|
+
// Implemented in the specific application
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Export pricing helpers
|
|
441
|
+
export const PRICING = {
|
|
442
|
+
micro: '0.01',
|
|
443
|
+
small: '0.05',
|
|
444
|
+
medium: '0.10',
|
|
445
|
+
large: '0.25',
|
|
446
|
+
premium: '0.50',
|
|
447
|
+
enterprise: '1.00',
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export default {
|
|
451
|
+
configureX402Server,
|
|
452
|
+
createPaymentChallenge,
|
|
453
|
+
paywall,
|
|
454
|
+
dynamicPaywall,
|
|
455
|
+
usagePaywall,
|
|
456
|
+
subscriptionPaywall,
|
|
457
|
+
rateLimitedPaywall,
|
|
458
|
+
mockVerification,
|
|
459
|
+
simpleVerification,
|
|
460
|
+
PricingTier,
|
|
461
|
+
PRICING,
|
|
462
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pay-lobster",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Payment infrastructure for AI agents - The Stripe for autonomous agents. Send, receive, and escrow USDC on Base.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pay-lobster": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"cli": "npx tsx lib/index.ts",
|
|
14
|
+
"test": "echo \"Tests coming soon\" && exit 0"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"lib",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@circle-fin/developer-controlled-wallets": "^2.0.0",
|
|
24
|
+
"ethers": "^6.9.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"tsx": "^4.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"usdc",
|
|
33
|
+
"payments",
|
|
34
|
+
"ai-agents",
|
|
35
|
+
"crypto",
|
|
36
|
+
"base",
|
|
37
|
+
"circle",
|
|
38
|
+
"blockchain",
|
|
39
|
+
"escrow",
|
|
40
|
+
"stablecoin",
|
|
41
|
+
"web3",
|
|
42
|
+
"openclaw",
|
|
43
|
+
"agent-commerce"
|
|
44
|
+
],
|
|
45
|
+
"author": "Jakub Adamowicz <realtorjakub@gmail.com>",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/itsGustav/Lobster-Pay.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/itsGustav/Lobster-Pay/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://paylobster.com",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|