@elizaos/plugin-x402 2.0.0-alpha.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.
@@ -0,0 +1,1974 @@
1
+ // actions/check-payment-history.ts
2
+ import { logger } from "@elizaos/core";
3
+
4
+ // utils.ts
5
+ var ONE_DAY_MS = 24 * 60 * 60 * 1000;
6
+ function formatUsd(baseUnits) {
7
+ const dollars = Number(baseUnits) / 1e6;
8
+ return `$${dollars.toFixed(2)}`;
9
+ }
10
+ function truncateAddress(address) {
11
+ if (address.length <= 10)
12
+ return address;
13
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
14
+ }
15
+ function usdToBaseUnits(usd) {
16
+ return BigInt(Math.round(usd * 1e6));
17
+ }
18
+
19
+ // actions/check-payment-history.ts
20
+ var checkPaymentHistoryAction = {
21
+ name: "CHECK_PAYMENT_HISTORY",
22
+ description: "Check x402 payment history including spending summary and recent transactions. Use when asked about payment activity, spending, or earnings.",
23
+ similes: [
24
+ "check payments",
25
+ "payment history",
26
+ "spending summary",
27
+ "how much have I spent",
28
+ "payment transactions",
29
+ "show payments"
30
+ ],
31
+ parameters: [
32
+ {
33
+ name: "limit",
34
+ description: "Maximum number of recent transactions to show (default: 10)",
35
+ required: false,
36
+ schema: { type: "number" }
37
+ }
38
+ ],
39
+ validate: async (runtime) => {
40
+ const service = runtime.getService("x402_payment");
41
+ return !!service && service.isActive();
42
+ },
43
+ handler: async (runtime, _message, _state, options, callback) => {
44
+ const service = runtime.getService("x402_payment");
45
+ if (!service || !service.isActive()) {
46
+ logger.warn("[x402] CHECK_PAYMENT_HISTORY: Service not available or inactive");
47
+ if (callback) {
48
+ await callback({
49
+ text: "Payment tracking is not active. The x402 payment service is not configured.",
50
+ actions: []
51
+ });
52
+ }
53
+ return { success: false, error: "x402 service not available" };
54
+ }
55
+ const params = options?.parameters;
56
+ const limit = typeof params?.limit === "number" ? params.limit : 10;
57
+ try {
58
+ const summary = await service.getSummary(ONE_DAY_MS);
59
+ const recentTxns = await service.getRecentTransactions(limit);
60
+ const lines = [];
61
+ const walletAddress = service.getWalletAddress();
62
+ const network = service.getNetwork();
63
+ lines.push(`**Payment Summary** (${network})`);
64
+ lines.push(`Wallet: ${walletAddress ? truncateAddress(walletAddress) : "N/A"}`);
65
+ lines.push("");
66
+ lines.push("**Last 24 Hours:**");
67
+ lines.push(`- Spent: ${formatUsd(summary.totalSpent)} (${summary.outgoingCount} transactions)`);
68
+ lines.push(`- Earned: ${formatUsd(summary.totalEarned)} (${summary.incomingCount} transactions)`);
69
+ const net = summary.totalEarned - summary.totalSpent;
70
+ const netDisplay = net < 0n ? `-${formatUsd(-net)}` : `+${formatUsd(net)}`;
71
+ lines.push(`- Net: ${netDisplay}`);
72
+ lines.push("");
73
+ lines.push(`Circuit Breaker: ${service.getCircuitBreakerState()}`);
74
+ lines.push("");
75
+ if (recentTxns.length > 0) {
76
+ lines.push(`**Recent Transactions** (last ${recentTxns.length}):`);
77
+ for (const txn of recentTxns) {
78
+ const direction = txn.direction === "outgoing" ? "SENT" : "RECV";
79
+ const counterpartyDisplay = truncateAddress(txn.counterparty);
80
+ const time = new Date(txn.createdAt).toLocaleString();
81
+ lines.push(`- [${direction}] ${formatUsd(txn.amount)} ${txn.direction === "outgoing" ? "to" : "from"} ${counterpartyDisplay} — ${txn.resource || "N/A"} (${time}) [${txn.status}]`);
82
+ }
83
+ } else {
84
+ lines.push("No recent transactions.");
85
+ }
86
+ const responseText = lines.join(`
87
+ `);
88
+ if (callback) {
89
+ await callback({
90
+ text: responseText,
91
+ actions: []
92
+ });
93
+ }
94
+ return {
95
+ success: true,
96
+ text: responseText,
97
+ data: {
98
+ totalSpent: formatUsd(summary.totalSpent),
99
+ totalEarned: formatUsd(summary.totalEarned),
100
+ outgoingCount: summary.outgoingCount,
101
+ incomingCount: summary.incomingCount,
102
+ recentTransactionCount: recentTxns.length
103
+ }
104
+ };
105
+ } catch (err) {
106
+ const errorMessage = err instanceof Error ? err.message : String(err);
107
+ logger.error(`[x402] CHECK_PAYMENT_HISTORY: Failed: ${errorMessage}`);
108
+ if (callback) {
109
+ await callback({
110
+ text: `Failed to retrieve payment history: ${errorMessage}`,
111
+ actions: []
112
+ });
113
+ }
114
+ return { success: false, error: errorMessage };
115
+ }
116
+ },
117
+ examples: [
118
+ [
119
+ {
120
+ name: "user",
121
+ content: {
122
+ text: "How much have you spent today?"
123
+ }
124
+ },
125
+ {
126
+ name: "assistant",
127
+ content: {
128
+ text: "Let me check my payment history for today.",
129
+ actions: ["CHECK_PAYMENT_HISTORY"]
130
+ }
131
+ }
132
+ ],
133
+ [
134
+ {
135
+ name: "user",
136
+ content: {
137
+ text: "Show me your recent payment transactions"
138
+ }
139
+ },
140
+ {
141
+ name: "assistant",
142
+ content: {
143
+ text: "Here are my recent x402 payment transactions.",
144
+ actions: ["CHECK_PAYMENT_HISTORY"]
145
+ }
146
+ }
147
+ ]
148
+ ]
149
+ };
150
+
151
+ // actions/set-payment-policy.ts
152
+ import { logger as logger2 } from "@elizaos/core";
153
+ var setPaymentPolicyAction = {
154
+ name: "SET_PAYMENT_POLICY",
155
+ description: "Manage payment policies for the x402 payment service. Set per-transaction limits, daily spending limits, or block/allow specific recipient addresses.",
156
+ similes: [
157
+ "set payment policy",
158
+ "update payment limits",
159
+ "change spending limit",
160
+ "block recipient",
161
+ "allow recipient",
162
+ "set max payment",
163
+ "set daily limit",
164
+ "payment policy"
165
+ ],
166
+ parameters: [
167
+ {
168
+ name: "maxPerPaymentUsd",
169
+ description: "Maximum amount in USD for a single outgoing payment (e.g. 5.0 for $5.00)",
170
+ required: false,
171
+ schema: { type: "number" }
172
+ },
173
+ {
174
+ name: "maxDailyUsd",
175
+ description: "Maximum total USD spend per day (e.g. 50.0 for $50.00)",
176
+ required: false,
177
+ schema: { type: "number" }
178
+ },
179
+ {
180
+ name: "blockRecipient",
181
+ description: "Ethereum address to add to the blocklist (payments to this address will be rejected)",
182
+ required: false,
183
+ schema: { type: "string" }
184
+ },
185
+ {
186
+ name: "allowRecipient",
187
+ description: "Ethereum address to add to the allowlist (when allowlist is non-empty, only listed addresses can receive payments)",
188
+ required: false,
189
+ schema: { type: "string" }
190
+ }
191
+ ],
192
+ validate: async (runtime) => {
193
+ const service = runtime.getService("x402_payment");
194
+ return !!service && service.isActive();
195
+ },
196
+ handler: async (runtime, _message, _state, options, callback) => {
197
+ const service = runtime.getService("x402_payment");
198
+ if (!service || !service.isActive()) {
199
+ logger2.warn("[x402] SET_PAYMENT_POLICY: Service not available or inactive");
200
+ if (callback) {
201
+ await callback({
202
+ text: "I'm unable to update payment policies right now. The x402 payment service is not configured or is inactive.",
203
+ actions: []
204
+ });
205
+ }
206
+ return { success: false, error: "x402 service not available" };
207
+ }
208
+ const rawParams = options?.parameters;
209
+ const maxPerPaymentUsd = rawParams?.maxPerPaymentUsd;
210
+ const maxDailyUsd = rawParams?.maxDailyUsd;
211
+ const blockRecipient = rawParams?.blockRecipient;
212
+ const allowRecipient = rawParams?.allowRecipient;
213
+ if (maxPerPaymentUsd === undefined && maxDailyUsd === undefined && !blockRecipient && !allowRecipient) {
214
+ if (callback) {
215
+ await callback({
216
+ text: "Please specify at least one policy change: maxPerPaymentUsd, maxDailyUsd, blockRecipient, or allowRecipient.",
217
+ actions: []
218
+ });
219
+ }
220
+ return { success: false, error: "No policy parameters provided" };
221
+ }
222
+ const changes = [];
223
+ try {
224
+ if (maxPerPaymentUsd !== undefined) {
225
+ if (maxPerPaymentUsd <= 0) {
226
+ if (callback) {
227
+ await callback({
228
+ text: "maxPerPaymentUsd must be a positive number.",
229
+ actions: []
230
+ });
231
+ }
232
+ return { success: false, error: "Invalid maxPerPaymentUsd" };
233
+ }
234
+ service.updatePolicy({
235
+ outgoing: {
236
+ maxPerTransaction: usdToBaseUnits(maxPerPaymentUsd)
237
+ }
238
+ });
239
+ changes.push(`Max per-payment limit set to $${maxPerPaymentUsd.toFixed(2)}`);
240
+ logger2.info(`[x402] SET_PAYMENT_POLICY: maxPerTransaction set to $${maxPerPaymentUsd}`);
241
+ }
242
+ if (maxDailyUsd !== undefined) {
243
+ if (maxDailyUsd <= 0) {
244
+ if (callback) {
245
+ await callback({
246
+ text: "maxDailyUsd must be a positive number.",
247
+ actions: []
248
+ });
249
+ }
250
+ return { success: false, error: "Invalid maxDailyUsd" };
251
+ }
252
+ service.updatePolicy({
253
+ outgoing: {
254
+ maxTotal: usdToBaseUnits(maxDailyUsd)
255
+ }
256
+ });
257
+ changes.push(`Daily spending limit set to $${maxDailyUsd.toFixed(2)}`);
258
+ logger2.info(`[x402] SET_PAYMENT_POLICY: maxTotal (daily) set to $${maxDailyUsd}`);
259
+ }
260
+ if (blockRecipient) {
261
+ service.updatePolicy({
262
+ outgoing: {
263
+ blockedRecipients: [blockRecipient]
264
+ }
265
+ });
266
+ changes.push(`Blocked recipient: ${blockRecipient}`);
267
+ logger2.info(`[x402] SET_PAYMENT_POLICY: blocked recipient ${blockRecipient}`);
268
+ }
269
+ if (allowRecipient) {
270
+ service.updatePolicy({
271
+ outgoing: {
272
+ allowedRecipients: [allowRecipient]
273
+ }
274
+ });
275
+ changes.push(`Added to allowlist: ${allowRecipient}`);
276
+ logger2.info(`[x402] SET_PAYMENT_POLICY: allowed recipient ${allowRecipient}`);
277
+ }
278
+ const summary = changes.join(`
279
+ - `);
280
+ if (callback) {
281
+ await callback({
282
+ text: `Payment policy updated:
283
+ - ${summary}`,
284
+ actions: []
285
+ });
286
+ }
287
+ return {
288
+ success: true,
289
+ text: `Payment policy updated: ${changes.join("; ")}`,
290
+ data: {
291
+ maxPerPaymentUsd: maxPerPaymentUsd ?? null,
292
+ maxDailyUsd: maxDailyUsd ?? null,
293
+ blockRecipient: blockRecipient ?? null,
294
+ allowRecipient: allowRecipient ?? null
295
+ }
296
+ };
297
+ } catch (err) {
298
+ const errorMessage = err instanceof Error ? err.message : String(err);
299
+ logger2.error(`[x402] SET_PAYMENT_POLICY: Failed to update policy: ${errorMessage}`);
300
+ if (callback) {
301
+ await callback({
302
+ text: `Failed to update payment policy: ${errorMessage}`,
303
+ actions: []
304
+ });
305
+ }
306
+ return { success: false, error: errorMessage };
307
+ }
308
+ },
309
+ examples: [
310
+ [
311
+ {
312
+ name: "user",
313
+ content: {
314
+ text: "Set the maximum payment per transaction to $5"
315
+ }
316
+ },
317
+ {
318
+ name: "assistant",
319
+ content: {
320
+ text: "I'll set the per-transaction limit to $5.00.",
321
+ actions: ["SET_PAYMENT_POLICY"]
322
+ }
323
+ }
324
+ ],
325
+ [
326
+ {
327
+ name: "user",
328
+ content: {
329
+ text: "Limit my daily spending to $50 and block payments to 0xDEAD...BEEF"
330
+ }
331
+ },
332
+ {
333
+ name: "assistant",
334
+ content: {
335
+ text: "I'll set the daily limit to $50 and block that address.",
336
+ actions: ["SET_PAYMENT_POLICY"]
337
+ }
338
+ }
339
+ ],
340
+ [
341
+ {
342
+ name: "user",
343
+ content: {
344
+ text: "Allow payments only to 0x1234567890abcdef1234567890abcdef12345678"
345
+ }
346
+ },
347
+ {
348
+ name: "assistant",
349
+ content: {
350
+ text: "Adding that address to the payment allowlist.",
351
+ actions: ["SET_PAYMENT_POLICY"]
352
+ }
353
+ }
354
+ ]
355
+ ]
356
+ };
357
+
358
+ // actions/pay-for-service.ts
359
+ import { logger as logger3 } from "@elizaos/core";
360
+ var payForServiceAction = {
361
+ name: "PAY_FOR_SERVICE",
362
+ description: "Make a request to an x402-protected URL, automatically paying if required. Use when you need to access a paid API or service that uses the x402 payment protocol.",
363
+ similes: [
364
+ "pay for service",
365
+ "x402 payment",
366
+ "make a paid request",
367
+ "access paid endpoint",
368
+ "pay and fetch"
369
+ ],
370
+ parameters: [
371
+ {
372
+ name: "url",
373
+ description: "The URL of the x402-protected service to access",
374
+ required: true,
375
+ schema: { type: "string" }
376
+ },
377
+ {
378
+ name: "method",
379
+ description: "HTTP method (GET, POST, etc.). Defaults to GET.",
380
+ required: false,
381
+ schema: {
382
+ type: "string",
383
+ enumValues: ["GET", "POST", "PUT", "DELETE"]
384
+ }
385
+ },
386
+ {
387
+ name: "body",
388
+ description: "Optional request body as a JSON string (for POST/PUT)",
389
+ required: false,
390
+ schema: { type: "string" }
391
+ }
392
+ ],
393
+ validate: async (runtime) => {
394
+ const service = runtime.getService("x402_payment");
395
+ return !!service && service.canMakePayments();
396
+ },
397
+ handler: async (runtime, message, _state, options, callback) => {
398
+ const service = runtime.getService("x402_payment");
399
+ if (!service || !service.canMakePayments()) {
400
+ logger3.warn("[x402] PAY_FOR_SERVICE: Service not available or inactive");
401
+ if (callback) {
402
+ await callback({
403
+ text: "I'm unable to make payments right now. The x402 payment service is not configured or is inactive.",
404
+ actions: []
405
+ });
406
+ }
407
+ return { success: false, error: "x402 service not available" };
408
+ }
409
+ const params = options?.parameters;
410
+ const url = params?.url ?? extractUrlFromMessage(message);
411
+ const method = params?.method ?? "GET";
412
+ const body = params?.body;
413
+ if (!url) {
414
+ logger3.warn("[x402] PAY_FOR_SERVICE: No URL provided");
415
+ if (callback) {
416
+ await callback({
417
+ text: "I need a URL to make the paid request. Please provide the URL of the service you want me to access.",
418
+ actions: []
419
+ });
420
+ }
421
+ return { success: false, error: "No URL provided" };
422
+ }
423
+ logger3.info(`[x402] PAY_FOR_SERVICE: Requesting ${method} ${url}`);
424
+ const payFetch = service.getFetchWithPayment();
425
+ try {
426
+ const init = { method };
427
+ if (body && (method === "POST" || method === "PUT")) {
428
+ init.body = body;
429
+ init.headers = { "Content-Type": "application/json" };
430
+ }
431
+ const response = await payFetch(url, init);
432
+ const contentType = response.headers.get("content-type") ?? "";
433
+ let responseText;
434
+ if (contentType.includes("application/json")) {
435
+ const json = await response.json();
436
+ responseText = JSON.stringify(json, null, 2);
437
+ } else {
438
+ responseText = await response.text();
439
+ }
440
+ const maxLen = 2000;
441
+ const truncated = responseText.length > maxLen ? `${responseText.slice(0, maxLen)}...
442
+ [Truncated - ${responseText.length} total characters]` : responseText;
443
+ if (response.ok) {
444
+ logger3.info(`[x402] PAY_FOR_SERVICE: Success (${response.status}) from ${url}`);
445
+ if (callback) {
446
+ await callback({
447
+ text: `Successfully accessed ${url} (HTTP ${response.status}):
448
+
449
+ ${truncated}`,
450
+ actions: []
451
+ });
452
+ }
453
+ return {
454
+ success: true,
455
+ text: `Payment and request successful for ${url}`,
456
+ data: {
457
+ status: response.status,
458
+ url,
459
+ responsePreview: truncated
460
+ }
461
+ };
462
+ } else {
463
+ logger3.warn(`[x402] PAY_FOR_SERVICE: Request returned ${response.status} from ${url}`);
464
+ if (callback) {
465
+ await callback({
466
+ text: `Request to ${url} returned HTTP ${response.status}:
467
+
468
+ ${truncated}`,
469
+ actions: []
470
+ });
471
+ }
472
+ return {
473
+ success: false,
474
+ error: `HTTP ${response.status}`,
475
+ data: {
476
+ status: response.status,
477
+ url,
478
+ responsePreview: truncated
479
+ }
480
+ };
481
+ }
482
+ } catch (err) {
483
+ const errorMessage = err instanceof Error ? err.message : String(err);
484
+ logger3.error(`[x402] PAY_FOR_SERVICE: Request failed: ${errorMessage}`);
485
+ if (callback) {
486
+ await callback({
487
+ text: `Failed to access ${url}: ${errorMessage}`,
488
+ actions: []
489
+ });
490
+ }
491
+ return { success: false, error: errorMessage };
492
+ }
493
+ },
494
+ examples: [
495
+ [
496
+ {
497
+ name: "user",
498
+ content: {
499
+ text: "Can you fetch the data from https://api.example.com/premium/data? It's a paid API."
500
+ }
501
+ },
502
+ {
503
+ name: "assistant",
504
+ content: {
505
+ text: "I'll access that paid API for you now.",
506
+ actions: ["PAY_FOR_SERVICE"]
507
+ }
508
+ }
509
+ ],
510
+ [
511
+ {
512
+ name: "user",
513
+ content: {
514
+ text: "Please make a paid request to https://weather.paid-api.com/forecast"
515
+ }
516
+ },
517
+ {
518
+ name: "assistant",
519
+ content: {
520
+ text: "Making a paid request to the weather API.",
521
+ actions: ["PAY_FOR_SERVICE"]
522
+ }
523
+ }
524
+ ]
525
+ ]
526
+ };
527
+ function extractUrlFromMessage(message) {
528
+ const text = typeof message.content === "string" ? message.content : message.content?.text;
529
+ if (!text)
530
+ return;
531
+ const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/g;
532
+ const matches = text.match(urlRegex);
533
+ return matches?.[0];
534
+ }
535
+
536
+ // providers/payment-balance.ts
537
+ var paymentBalanceProvider = {
538
+ name: "x402_payment_status",
539
+ description: "Current x402 payment status including wallet, spending, and earning summary",
540
+ get: async (runtime, _message, _state) => {
541
+ const service = runtime.getService("x402_payment");
542
+ if (!service || !service.isActive()) {
543
+ return {
544
+ text: `[Payment Status]
545
+ Payments: Inactive (no wallet configured)`,
546
+ values: {
547
+ x402Active: false
548
+ }
549
+ };
550
+ }
551
+ const walletAddress = service.getWalletAddress();
552
+ const network = service.getNetwork();
553
+ const summary = await service.getSummary(ONE_DAY_MS);
554
+ const netAmount = summary.totalEarned - summary.totalSpent;
555
+ const netDisplay = netAmount < 0n ? `-${formatUsd(-netAmount)}` : `+${formatUsd(netAmount)}`;
556
+ const statusLines = [
557
+ "[Payment Status]",
558
+ `Wallet: ${walletAddress ? truncateAddress(walletAddress) : "N/A"} (${network})`,
559
+ `24h Spent: ${formatUsd(summary.totalSpent)} (${summary.outgoingCount} txns)`,
560
+ `24h Earned: ${formatUsd(summary.totalEarned)} (${summary.incomingCount} txns)`,
561
+ `Net: ${netDisplay}`,
562
+ `Circuit Breaker: ${service.getCircuitBreakerState()}`
563
+ ];
564
+ return {
565
+ text: statusLines.join(`
566
+ `),
567
+ values: {
568
+ x402Active: true,
569
+ x402Wallet: walletAddress ?? "",
570
+ x402Network: network,
571
+ x402TotalSpent: formatUsd(summary.totalSpent),
572
+ x402TotalEarned: formatUsd(summary.totalEarned),
573
+ x402OutgoingCount: summary.outgoingCount,
574
+ x402IncomingCount: summary.incomingCount
575
+ }
576
+ };
577
+ }
578
+ };
579
+
580
+ // networks.ts
581
+ var NETWORK_REGISTRY = {
582
+ base: {
583
+ caip2: "eip155:8453",
584
+ chainId: 8453,
585
+ name: "Base",
586
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
587
+ usdcDomainName: "USDC",
588
+ usdcPermitVersion: "2"
589
+ },
590
+ "base-sepolia": {
591
+ caip2: "eip155:84532",
592
+ chainId: 84532,
593
+ name: "Base Sepolia",
594
+ usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
595
+ usdcDomainName: "USDC",
596
+ usdcPermitVersion: "2"
597
+ },
598
+ ethereum: {
599
+ caip2: "eip155:1",
600
+ chainId: 1,
601
+ name: "Ethereum",
602
+ usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
603
+ usdcDomainName: "USD Coin",
604
+ usdcPermitVersion: "2"
605
+ },
606
+ sepolia: {
607
+ caip2: "eip155:11155111",
608
+ chainId: 11155111,
609
+ name: "Sepolia",
610
+ usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
611
+ usdcDomainName: "USDC",
612
+ usdcPermitVersion: "2"
613
+ }
614
+ };
615
+ function resolveNetwork(key) {
616
+ const info = NETWORK_REGISTRY[key];
617
+ if (!info) {
618
+ const supported = Object.keys(NETWORK_REGISTRY).join(", ");
619
+ throw new Error(`Unknown network "${key}". Supported: ${supported}`);
620
+ }
621
+ return info;
622
+ }
623
+ function networkKeyFromCaip2(caip2) {
624
+ for (const [key, info] of Object.entries(NETWORK_REGISTRY)) {
625
+ if (info.caip2 === caip2) {
626
+ return key;
627
+ }
628
+ }
629
+ return;
630
+ }
631
+
632
+ // routes/agent-card.ts
633
+ async function handleAgentCard(req, res, runtime) {
634
+ const service = runtime.getService("x402_payment");
635
+ if (!service || !service.isActive()) {
636
+ res.status(503).json({ error: "x402 service not active" });
637
+ return;
638
+ }
639
+ const walletAddress = service.getWalletAddress() ?? "";
640
+ const networkKey = service.getNetwork();
641
+ const facilitatorUrl = service.getFacilitatorUrl();
642
+ let caip2Network;
643
+ try {
644
+ const networkInfo = resolveNetwork(networkKey);
645
+ caip2Network = networkInfo.caip2;
646
+ } catch {
647
+ caip2Network = networkKey;
648
+ }
649
+ const character = runtime.character;
650
+ const agentName = character?.name ?? "ElizaOS Agent";
651
+ const agentDescription = (Array.isArray(character?.bio) ? character.bio[0] : character?.bio) ?? "An ElizaOS agent with x402 payment capabilities";
652
+ const configuredUrl = String(runtime.getSetting("X402_AGENT_URL") ?? "");
653
+ let agentUrl = configuredUrl;
654
+ if (!agentUrl && req.headers) {
655
+ const host = (Array.isArray(req.headers.host) ? req.headers.host[0] : req.headers.host) ?? "";
656
+ const proto = (Array.isArray(req.headers["x-forwarded-proto"]) ? req.headers["x-forwarded-proto"][0] : req.headers["x-forwarded-proto"]) ?? "https";
657
+ if (host) {
658
+ agentUrl = `${proto}://${host}`;
659
+ }
660
+ }
661
+ const skills = [];
662
+ const plugins = runtime.plugins ?? [];
663
+ for (const plugin of plugins) {
664
+ if (!plugin.routes)
665
+ continue;
666
+ for (const route of plugin.routes) {
667
+ const x402Config = "x402" in route ? route.x402 : undefined;
668
+ if (x402Config) {
669
+ skills.push({
670
+ name: ("name" in route ? route.name : undefined) ?? route.path,
671
+ description: x402Config.description ?? `Paid endpoint: ${route.path}`,
672
+ path: route.path,
673
+ price: x402Config.price,
674
+ network: x402Config.network
675
+ });
676
+ }
677
+ }
678
+ }
679
+ const card = {
680
+ protocolVersion: "1.0",
681
+ name: agentName,
682
+ description: agentDescription,
683
+ url: agentUrl,
684
+ capabilities: {
685
+ x402Payments: true
686
+ },
687
+ payments: [
688
+ {
689
+ method: "x402",
690
+ payee: walletAddress,
691
+ network: caip2Network,
692
+ facilitatorUrl
693
+ }
694
+ ],
695
+ skills
696
+ };
697
+ res.status(200).json(card);
698
+ }
699
+ var agentCardRoute = {
700
+ type: "GET",
701
+ path: "/.well-known/agent-card.json",
702
+ name: "x402-agent-card",
703
+ public: true,
704
+ handler: handleAgentCard
705
+ };
706
+
707
+ // services/x402-service.ts
708
+ import { Service } from "@elizaos/core";
709
+ import { logger as logger4 } from "@elizaos/core";
710
+
711
+ // client/signer.ts
712
+ import { privateKeyToAccount } from "viem/accounts";
713
+ var PERMIT_TYPES = {
714
+ Permit: [
715
+ { name: "owner", type: "address" },
716
+ { name: "spender", type: "address" },
717
+ { name: "value", type: "uint256" },
718
+ { name: "nonce", type: "uint256" },
719
+ { name: "deadline", type: "uint256" }
720
+ ]
721
+ };
722
+
723
+ class EvmPaymentSigner {
724
+ account;
725
+ network;
726
+ constructor(privateKey, network) {
727
+ const key = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
728
+ this.account = privateKeyToAccount(key);
729
+ this.network = network;
730
+ }
731
+ get address() {
732
+ return this.account.address;
733
+ }
734
+ get networkId() {
735
+ return resolveNetwork(this.network).caip2;
736
+ }
737
+ async signPermit(params) {
738
+ const networkInfo = resolveNetwork(this.network);
739
+ const domain = {
740
+ name: networkInfo.usdcDomainName,
741
+ version: networkInfo.usdcPermitVersion,
742
+ chainId: BigInt(networkInfo.chainId),
743
+ verifyingContract: networkInfo.usdcAddress
744
+ };
745
+ const message = {
746
+ owner: this.account.address,
747
+ spender: params.spender,
748
+ value: params.value,
749
+ nonce: params.nonce,
750
+ deadline: params.deadline
751
+ };
752
+ const signature = await this.account.signTypedData({
753
+ domain,
754
+ types: PERMIT_TYPES,
755
+ primaryType: "Permit",
756
+ message
757
+ });
758
+ const raw = signature.slice(2);
759
+ const r = `0x${raw.slice(0, 64)}`;
760
+ const s = `0x${raw.slice(64, 128)}`;
761
+ const v = parseInt(raw.slice(128, 130), 16);
762
+ return { v, r, s };
763
+ }
764
+ async buildPaymentHeader(requirement) {
765
+ const networkInfo = resolveNetwork(this.network);
766
+ const amount = BigInt(requirement.maxAmountRequired);
767
+ const tokenName = requirement.extra?.name ?? networkInfo.usdcDomainName;
768
+ const tokenVersion = requirement.extra?.version ?? networkInfo.usdcPermitVersion;
769
+ const nonce = BigInt(requirement.extra?.nonce ?? "0");
770
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + requirement.maxTimeoutSeconds);
771
+ const spender = requirement.payTo;
772
+ const domain = {
773
+ name: tokenName,
774
+ version: tokenVersion,
775
+ chainId: BigInt(networkInfo.chainId),
776
+ verifyingContract: requirement.asset
777
+ };
778
+ const message = {
779
+ owner: this.account.address,
780
+ spender,
781
+ value: amount,
782
+ nonce,
783
+ deadline
784
+ };
785
+ const signature = await this.account.signTypedData({
786
+ domain,
787
+ types: PERMIT_TYPES,
788
+ primaryType: "Permit",
789
+ message
790
+ });
791
+ const payload = {
792
+ x402Version: 2,
793
+ accepted: {
794
+ scheme: "upto",
795
+ network: requirement.network,
796
+ asset: requirement.asset,
797
+ amount: requirement.maxAmountRequired,
798
+ payTo: requirement.payTo
799
+ },
800
+ payload: {
801
+ authorization: {
802
+ from: this.account.address,
803
+ to: requirement.payTo,
804
+ value: requirement.maxAmountRequired,
805
+ validBefore: deadline.toString(),
806
+ nonce: nonce.toString()
807
+ },
808
+ signature
809
+ }
810
+ };
811
+ const jsonString = JSON.stringify(payload);
812
+ return Buffer.from(jsonString).toString("base64");
813
+ }
814
+ }
815
+
816
+ // client/fetch-with-payment.ts
817
+ function generateId() {
818
+ const timestamp = Date.now().toString(36);
819
+ const random = Math.random().toString(36).substring(2, 10);
820
+ return `x402_${timestamp}_${random}`;
821
+ }
822
+ function parsePaymentRequired(response) {
823
+ const headerValue = response.headers.get("payment-required") ?? response.headers.get("x-402") ?? response.headers.get("x-payment-required");
824
+ if (!headerValue) {
825
+ return null;
826
+ }
827
+ try {
828
+ const decoded = Buffer.from(headerValue, "base64").toString("utf-8");
829
+ return JSON.parse(decoded);
830
+ } catch {
831
+ try {
832
+ return JSON.parse(headerValue);
833
+ } catch {
834
+ return null;
835
+ }
836
+ }
837
+ }
838
+ function selectPaymentOption(accepts, signerNetworkId) {
839
+ const matching = accepts.find((a) => a.network === signerNetworkId);
840
+ return matching ?? null;
841
+ }
842
+ function createFetchWithPayment(options) {
843
+ const { signer, policyEngine, circuitBreaker, storage, logger: logger4 } = options;
844
+ return async function fetchWithPayment(input, init) {
845
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
846
+ logger4.debug(`[x402] Making request to ${url}`);
847
+ const initialResponse = await fetch(input, init);
848
+ if (initialResponse.status !== 402) {
849
+ return initialResponse;
850
+ }
851
+ logger4.info(`[x402] Received 402 Payment Required from ${url}`);
852
+ const paymentRequired = parsePaymentRequired(initialResponse);
853
+ if (!paymentRequired) {
854
+ logger4.error("[x402] Could not parse payment requirement header from 402 response");
855
+ return initialResponse;
856
+ }
857
+ if (!paymentRequired.accepts || paymentRequired.accepts.length === 0) {
858
+ logger4.error("[x402] No payment options in 402 response");
859
+ return initialResponse;
860
+ }
861
+ const requirement = selectPaymentOption(paymentRequired.accepts, signer.networkId);
862
+ if (!requirement) {
863
+ logger4.error("[x402] No compatible payment option found");
864
+ return initialResponse;
865
+ }
866
+ const amount = BigInt(requirement.maxAmountRequired);
867
+ logger4.info(`[x402] Payment required: ${amount} to ${requirement.payTo} on ${requirement.network}`);
868
+ const policyResult = await policyEngine.evaluateOutgoing({
869
+ amount,
870
+ recipient: requirement.payTo,
871
+ resource: url
872
+ });
873
+ if (!policyResult.allowed) {
874
+ logger4.warn(`[x402] Payment blocked by policy: ${policyResult.reason}`);
875
+ return initialResponse;
876
+ }
877
+ const breakerResult = circuitBreaker.check(amount);
878
+ if (!breakerResult.allowed) {
879
+ logger4.warn(`[x402] Payment blocked by circuit breaker: ${breakerResult.reason}`);
880
+ return initialResponse;
881
+ }
882
+ let paymentHeader;
883
+ try {
884
+ paymentHeader = await signer.buildPaymentHeader(requirement);
885
+ } catch (err) {
886
+ const message = err instanceof Error ? err.message : String(err);
887
+ logger4.error(`[x402] Failed to sign payment: ${message}`);
888
+ circuitBreaker.recordFailure();
889
+ return initialResponse;
890
+ }
891
+ logger4.info("[x402] Retrying request with X-PAYMENT header");
892
+ const retryHeaders = new Headers(init?.headers);
893
+ retryHeaders.set("X-PAYMENT", paymentHeader);
894
+ const retryInit = {
895
+ ...init,
896
+ headers: retryHeaders
897
+ };
898
+ let retryResponse;
899
+ try {
900
+ retryResponse = await fetch(input, retryInit);
901
+ } catch (err) {
902
+ const message = err instanceof Error ? err.message : String(err);
903
+ logger4.error(`[x402] Retry request failed: ${message}`);
904
+ circuitBreaker.recordFailure();
905
+ return initialResponse;
906
+ }
907
+ const sessionId = retryResponse.headers.get("x-upto-session-id") ?? retryResponse.headers.get("X-PAYMENT-RESPONSE") ?? "";
908
+ const record = {
909
+ id: generateId(),
910
+ direction: "outgoing",
911
+ counterparty: requirement.payTo,
912
+ amount,
913
+ network: requirement.network,
914
+ txHash: sessionId,
915
+ resource: url,
916
+ status: retryResponse.ok ? "confirmed" : "failed",
917
+ createdAt: new Date().toISOString(),
918
+ metadata: {
919
+ scheme: requirement.scheme,
920
+ description: requirement.description
921
+ }
922
+ };
923
+ try {
924
+ await storage.recordPayment(record);
925
+ } catch (err) {
926
+ const message = err instanceof Error ? err.message : String(err);
927
+ logger4.error(`[x402] Failed to record payment: ${message}`);
928
+ }
929
+ if (retryResponse.ok) {
930
+ circuitBreaker.recordSuccess(amount);
931
+ logger4.info(`[x402] Payment successful: ${amount} to ${requirement.payTo}`);
932
+ } else {
933
+ circuitBreaker.recordFailure();
934
+ logger4.warn(`[x402] Payment request returned ${retryResponse.status} after payment`);
935
+ }
936
+ return retryResponse;
937
+ };
938
+ }
939
+
940
+ // policy/circuit-breaker.ts
941
+ var DEFAULT_CONFIG = {
942
+ maxPaymentsPerMinute: 50,
943
+ anomalyMultiplier: 10,
944
+ cooldownMs: 60000,
945
+ recentWindowSize: 20
946
+ };
947
+
948
+ class CircuitBreaker {
949
+ state = "closed";
950
+ config;
951
+ recentTimestamps = [];
952
+ recentAmounts = [];
953
+ trippedAt = 0;
954
+ lastTripReason = "";
955
+ constructor(config) {
956
+ this.config = { ...DEFAULT_CONFIG, ...config };
957
+ }
958
+ check(amount) {
959
+ const now = Date.now();
960
+ if (this.state === "open") {
961
+ if (now - this.trippedAt >= this.config.cooldownMs) {
962
+ this.state = "half-open";
963
+ } else {
964
+ return {
965
+ allowed: false,
966
+ reason: `Circuit breaker is OPEN: ${this.lastTripReason}. Resets in ${Math.ceil((this.config.cooldownMs - (now - this.trippedAt)) / 1000)}s`
967
+ };
968
+ }
969
+ }
970
+ const oneMinuteAgo = now - 60000;
971
+ this.recentTimestamps = this.recentTimestamps.filter((t) => t > oneMinuteAgo);
972
+ if (this.recentTimestamps.length >= this.config.maxPaymentsPerMinute) {
973
+ this.trip(`Rate exceeded: ${this.recentTimestamps.length} payments in the last minute`);
974
+ return { allowed: false, reason: this.lastTripReason };
975
+ }
976
+ if (this.recentAmounts.length >= 3) {
977
+ const sum = this.recentAmounts.reduce((a, b) => a + b, 0n);
978
+ const avg = sum / BigInt(this.recentAmounts.length);
979
+ if (avg > 0n && amount > avg * BigInt(this.config.anomalyMultiplier)) {
980
+ this.trip(`Anomaly detected: payment of ${amount} is >${this.config.anomalyMultiplier}x the average of ${avg}`);
981
+ return { allowed: false, reason: this.lastTripReason };
982
+ }
983
+ }
984
+ return { allowed: true, reason: "" };
985
+ }
986
+ recordSuccess(amount) {
987
+ const now = Date.now();
988
+ this.recentTimestamps.push(now);
989
+ this.recentAmounts.push(amount);
990
+ if (this.recentAmounts.length > this.config.recentWindowSize) {
991
+ this.recentAmounts.shift();
992
+ }
993
+ if (this.state === "half-open") {
994
+ this.state = "closed";
995
+ this.lastTripReason = "";
996
+ }
997
+ }
998
+ recordFailure() {
999
+ if (this.state === "half-open") {
1000
+ this.trip("Probe payment failed in half-open state");
1001
+ }
1002
+ }
1003
+ getState() {
1004
+ return this.state;
1005
+ }
1006
+ getTripReason() {
1007
+ return this.lastTripReason;
1008
+ }
1009
+ reset() {
1010
+ this.state = "closed";
1011
+ this.lastTripReason = "";
1012
+ this.trippedAt = 0;
1013
+ }
1014
+ trip(reason) {
1015
+ this.state = "open";
1016
+ this.trippedAt = Date.now();
1017
+ this.lastTripReason = reason;
1018
+ }
1019
+ }
1020
+
1021
+ // policy/engine.ts
1022
+ var ALLOW = { allowed: true, reason: "" };
1023
+ function deny(reason) {
1024
+ return { allowed: false, reason };
1025
+ }
1026
+
1027
+ class PolicyEngine {
1028
+ policy;
1029
+ storage;
1030
+ constructor(policy, storage) {
1031
+ this.policy = policy;
1032
+ this.storage = storage;
1033
+ }
1034
+ updatePolicy(partial) {
1035
+ if (partial.outgoing) {
1036
+ this.policy.outgoing = { ...this.policy.outgoing, ...partial.outgoing };
1037
+ }
1038
+ if (partial.incoming) {
1039
+ this.policy.incoming = { ...this.policy.incoming, ...partial.incoming };
1040
+ }
1041
+ }
1042
+ getPolicy() {
1043
+ return {
1044
+ outgoing: { ...this.policy.outgoing },
1045
+ incoming: { ...this.policy.incoming }
1046
+ };
1047
+ }
1048
+ async evaluateOutgoing(request) {
1049
+ const limits = this.policy.outgoing;
1050
+ if (request.amount > limits.maxPerTransaction) {
1051
+ return deny(`Amount ${request.amount} exceeds per-transaction limit of ${limits.maxPerTransaction}`);
1052
+ }
1053
+ if (limits.blockedRecipients.length > 0) {
1054
+ const normalized = request.recipient.toLowerCase();
1055
+ if (limits.blockedRecipients.some((addr) => addr.toLowerCase() === normalized)) {
1056
+ return deny(`Recipient ${request.recipient} is blocked`);
1057
+ }
1058
+ }
1059
+ if (limits.allowedRecipients.length > 0) {
1060
+ const normalized = request.recipient.toLowerCase();
1061
+ if (!limits.allowedRecipients.some((addr) => addr.toLowerCase() === normalized)) {
1062
+ return deny(`Recipient ${request.recipient} is not in the allow list`);
1063
+ }
1064
+ }
1065
+ const currentTotal = await this.storage.getTotal("outgoing", limits.windowMs);
1066
+ if (currentTotal + request.amount > limits.maxTotal) {
1067
+ return deny(`Total spend would be ${currentTotal + request.amount}, exceeding window limit of ${limits.maxTotal}`);
1068
+ }
1069
+ const currentCount = await this.storage.getCount("outgoing", limits.windowMs);
1070
+ if (currentCount >= limits.maxTransactions) {
1071
+ return deny(`Transaction count ${currentCount} has reached the limit of ${limits.maxTransactions}`);
1072
+ }
1073
+ return ALLOW;
1074
+ }
1075
+ async evaluateIncoming(request) {
1076
+ const limits = this.policy.incoming;
1077
+ if (request.amount < limits.minPerTransaction) {
1078
+ return deny(`Amount ${request.amount} is below minimum of ${limits.minPerTransaction}`);
1079
+ }
1080
+ if (limits.blockedSenders.length > 0) {
1081
+ const normalized = request.sender.toLowerCase();
1082
+ if (limits.blockedSenders.some((addr) => addr.toLowerCase() === normalized)) {
1083
+ return deny(`Sender ${request.sender} is blocked`);
1084
+ }
1085
+ }
1086
+ if (limits.allowedSenders.length > 0) {
1087
+ const normalized = request.sender.toLowerCase();
1088
+ if (!limits.allowedSenders.some((addr) => addr.toLowerCase() === normalized)) {
1089
+ return deny(`Sender ${request.sender} is not in the allow list`);
1090
+ }
1091
+ }
1092
+ return ALLOW;
1093
+ }
1094
+ }
1095
+
1096
+ // storage/memory.ts
1097
+ class MemoryPaymentStorage {
1098
+ records = [];
1099
+ async recordPayment(record) {
1100
+ this.records.push({
1101
+ ...record,
1102
+ metadata: { ...record.metadata }
1103
+ });
1104
+ }
1105
+ async getTotal(direction, windowMs, scope) {
1106
+ const cutoff = windowMs ? new Date(Date.now() - windowMs).toISOString() : undefined;
1107
+ let total = 0n;
1108
+ for (const r of this.records) {
1109
+ if (r.direction !== direction)
1110
+ continue;
1111
+ if (cutoff && r.createdAt < cutoff)
1112
+ continue;
1113
+ if (scope && r.counterparty !== scope)
1114
+ continue;
1115
+ if (r.status === "failed" || r.status === "refunded")
1116
+ continue;
1117
+ total += r.amount;
1118
+ }
1119
+ return total;
1120
+ }
1121
+ async getRecords(filters) {
1122
+ let result = [...this.records];
1123
+ if (filters) {
1124
+ if (filters.direction) {
1125
+ result = result.filter((r) => r.direction === filters.direction);
1126
+ }
1127
+ if (filters.counterparty) {
1128
+ result = result.filter((r) => r.counterparty.toLowerCase() === filters.counterparty.toLowerCase());
1129
+ }
1130
+ if (filters.status) {
1131
+ result = result.filter((r) => r.status === filters.status);
1132
+ }
1133
+ if (filters.network) {
1134
+ result = result.filter((r) => r.network === filters.network);
1135
+ }
1136
+ if (filters.since) {
1137
+ result = result.filter((r) => r.createdAt >= filters.since);
1138
+ }
1139
+ if (filters.until) {
1140
+ result = result.filter((r) => r.createdAt <= filters.until);
1141
+ }
1142
+ }
1143
+ result.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1144
+ const offset = filters?.offset ?? 0;
1145
+ const limit = filters?.limit ?? result.length;
1146
+ return result.slice(offset, offset + limit);
1147
+ }
1148
+ async getCount(direction, windowMs) {
1149
+ const cutoff = windowMs ? new Date(Date.now() - windowMs).toISOString() : undefined;
1150
+ let count = 0;
1151
+ for (const r of this.records) {
1152
+ if (r.direction !== direction)
1153
+ continue;
1154
+ if (cutoff && r.createdAt < cutoff)
1155
+ continue;
1156
+ if (r.status === "failed" || r.status === "refunded")
1157
+ continue;
1158
+ count++;
1159
+ }
1160
+ return count;
1161
+ }
1162
+ async clear() {
1163
+ this.records = [];
1164
+ }
1165
+ }
1166
+
1167
+ // storage/sqlite.ts
1168
+ import Database from "better-sqlite3";
1169
+
1170
+ class SqlitePaymentStorage {
1171
+ db;
1172
+ constructor(dbPath) {
1173
+ this.db = new Database(dbPath);
1174
+ this.db.pragma("journal_mode = WAL");
1175
+ this.db.pragma("foreign_keys = ON");
1176
+ this.initialize();
1177
+ }
1178
+ initialize() {
1179
+ this.db.exec(`
1180
+ CREATE TABLE IF NOT EXISTS x402_payments (
1181
+ id TEXT PRIMARY KEY,
1182
+ direction TEXT NOT NULL CHECK(direction IN ('outgoing', 'incoming')),
1183
+ counterparty TEXT NOT NULL,
1184
+ amount TEXT NOT NULL,
1185
+ network TEXT NOT NULL,
1186
+ tx_hash TEXT NOT NULL DEFAULT '',
1187
+ resource TEXT NOT NULL DEFAULT '',
1188
+ status TEXT NOT NULL DEFAULT 'pending',
1189
+ created_at TEXT NOT NULL,
1190
+ metadata TEXT NOT NULL DEFAULT '{}'
1191
+ );
1192
+
1193
+ CREATE INDEX IF NOT EXISTS idx_x402_direction ON x402_payments(direction);
1194
+ CREATE INDEX IF NOT EXISTS idx_x402_created_at ON x402_payments(created_at);
1195
+ CREATE INDEX IF NOT EXISTS idx_x402_counterparty ON x402_payments(counterparty);
1196
+ CREATE INDEX IF NOT EXISTS idx_x402_status ON x402_payments(status);
1197
+ `);
1198
+ }
1199
+ async recordPayment(record) {
1200
+ const stmt = this.db.prepare(`
1201
+ INSERT INTO x402_payments (id, direction, counterparty, amount, network, tx_hash, resource, status, created_at, metadata)
1202
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1203
+ `);
1204
+ stmt.run(record.id, record.direction, record.counterparty, record.amount.toString(), record.network, record.txHash, record.resource, record.status, record.createdAt, JSON.stringify(record.metadata));
1205
+ }
1206
+ async getTotal(direction, windowMs, scope) {
1207
+ let sql = "SELECT amount FROM x402_payments WHERE direction = ? AND status NOT IN ('failed', 'refunded')";
1208
+ const params = [direction];
1209
+ if (windowMs !== undefined) {
1210
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
1211
+ sql += " AND created_at >= ?";
1212
+ params.push(cutoff);
1213
+ }
1214
+ if (scope) {
1215
+ sql += " AND counterparty = ?";
1216
+ params.push(scope);
1217
+ }
1218
+ const rows = this.db.prepare(sql).all(...params);
1219
+ let total = 0n;
1220
+ for (const row of rows) {
1221
+ total += BigInt(row.amount);
1222
+ }
1223
+ return total;
1224
+ }
1225
+ async getRecords(filters) {
1226
+ let sql = "SELECT * FROM x402_payments WHERE 1=1";
1227
+ const params = [];
1228
+ if (filters) {
1229
+ if (filters.direction) {
1230
+ sql += " AND direction = ?";
1231
+ params.push(filters.direction);
1232
+ }
1233
+ if (filters.counterparty) {
1234
+ sql += " AND LOWER(counterparty) = LOWER(?)";
1235
+ params.push(filters.counterparty);
1236
+ }
1237
+ if (filters.status) {
1238
+ sql += " AND status = ?";
1239
+ params.push(filters.status);
1240
+ }
1241
+ if (filters.network) {
1242
+ sql += " AND network = ?";
1243
+ params.push(filters.network);
1244
+ }
1245
+ if (filters.since) {
1246
+ sql += " AND created_at >= ?";
1247
+ params.push(filters.since);
1248
+ }
1249
+ if (filters.until) {
1250
+ sql += " AND created_at <= ?";
1251
+ params.push(filters.until);
1252
+ }
1253
+ }
1254
+ sql += " ORDER BY created_at DESC";
1255
+ if (filters?.limit !== undefined) {
1256
+ sql += " LIMIT ?";
1257
+ params.push(filters.limit);
1258
+ }
1259
+ if (filters?.offset !== undefined) {
1260
+ sql += " OFFSET ?";
1261
+ params.push(filters.offset);
1262
+ }
1263
+ const rows = this.db.prepare(sql).all(...params);
1264
+ return rows.map((row) => this.rowToRecord(row));
1265
+ }
1266
+ async getCount(direction, windowMs) {
1267
+ let sql = "SELECT COUNT(*) as cnt FROM x402_payments WHERE direction = ? AND status NOT IN ('failed', 'refunded')";
1268
+ const params = [direction];
1269
+ if (windowMs !== undefined) {
1270
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
1271
+ sql += " AND created_at >= ?";
1272
+ params.push(cutoff);
1273
+ }
1274
+ const row = this.db.prepare(sql).get(...params);
1275
+ return row.cnt;
1276
+ }
1277
+ async clear() {
1278
+ this.db.exec("DELETE FROM x402_payments");
1279
+ }
1280
+ close() {
1281
+ this.db.close();
1282
+ }
1283
+ rowToRecord(row) {
1284
+ let metadata = {};
1285
+ try {
1286
+ metadata = JSON.parse(row.metadata);
1287
+ } catch {}
1288
+ return {
1289
+ id: row.id,
1290
+ direction: row.direction,
1291
+ counterparty: row.counterparty,
1292
+ amount: BigInt(row.amount),
1293
+ network: row.network,
1294
+ txHash: row.tx_hash,
1295
+ resource: row.resource,
1296
+ status: row.status,
1297
+ createdAt: row.created_at,
1298
+ metadata
1299
+ };
1300
+ }
1301
+ }
1302
+
1303
+ // services/x402-service.ts
1304
+ var DEFAULT_FACILITATOR_URL = "https://facilitator.daydreams.systems";
1305
+ var DEFAULTS = {
1306
+ network: "base",
1307
+ maxPaymentUsd: 1,
1308
+ maxTotalUsd: 10
1309
+ };
1310
+
1311
+ class X402Service extends Service {
1312
+ static serviceType = "x402_payment";
1313
+ capabilityDescription = "x402 HTTP payment protocol - send and receive crypto payments";
1314
+ signer = null;
1315
+ policyEngine = null;
1316
+ circuitBreaker;
1317
+ storage;
1318
+ serviceConfig;
1319
+ fetchWithPayment = null;
1320
+ constructor(runtime) {
1321
+ super(runtime);
1322
+ this.circuitBreaker = new CircuitBreaker;
1323
+ this.storage = new MemoryPaymentStorage;
1324
+ this.serviceConfig = {
1325
+ privateKey: "",
1326
+ network: DEFAULTS.network,
1327
+ payTo: "",
1328
+ facilitatorUrl: DEFAULT_FACILITATOR_URL,
1329
+ maxPaymentUsd: DEFAULTS.maxPaymentUsd,
1330
+ maxTotalUsd: DEFAULTS.maxTotalUsd,
1331
+ enabled: false
1332
+ };
1333
+ }
1334
+ static async start(runtime) {
1335
+ const service = new X402Service(runtime);
1336
+ await service.initialize(runtime);
1337
+ return service;
1338
+ }
1339
+ async initialize(runtime) {
1340
+ this.runtime = runtime;
1341
+ const privateKey = String(runtime.getSetting("X402_PRIVATE_KEY") ?? "");
1342
+ const network = String(runtime.getSetting("X402_NETWORK") ?? DEFAULTS.network);
1343
+ const payTo = String(runtime.getSetting("X402_PAY_TO") ?? "");
1344
+ const facilitatorUrl = String(runtime.getSetting("X402_FACILITATOR_URL") ?? DEFAULT_FACILITATOR_URL);
1345
+ const maxPaymentUsdRaw = runtime.getSetting("X402_MAX_PAYMENT_USD");
1346
+ const maxPaymentUsd = maxPaymentUsdRaw !== null ? parseFloat(String(maxPaymentUsdRaw)) : DEFAULTS.maxPaymentUsd;
1347
+ const maxTotalUsdRaw = runtime.getSetting("X402_MAX_TOTAL_USD");
1348
+ const maxTotalUsd = maxTotalUsdRaw !== null ? parseFloat(String(maxTotalUsdRaw)) : DEFAULTS.maxTotalUsd;
1349
+ const enabledSetting = runtime.getSetting("X402_ENABLED");
1350
+ const enabled = String(enabledSetting) !== "false" && privateKey.length > 0;
1351
+ this.serviceConfig = {
1352
+ privateKey,
1353
+ network,
1354
+ payTo,
1355
+ facilitatorUrl,
1356
+ maxPaymentUsd: isNaN(maxPaymentUsd) ? DEFAULTS.maxPaymentUsd : maxPaymentUsd,
1357
+ maxTotalUsd: isNaN(maxTotalUsd) ? DEFAULTS.maxTotalUsd : maxTotalUsd,
1358
+ enabled
1359
+ };
1360
+ if (!enabled) {
1361
+ logger4.info("[x402] Service inactive — no private key configured or explicitly disabled");
1362
+ return;
1363
+ }
1364
+ try {
1365
+ resolveNetwork(network);
1366
+ } catch (err) {
1367
+ const message = err instanceof Error ? err.message : String(err);
1368
+ logger4.error(`[x402] Invalid network configuration: ${message}`);
1369
+ this.serviceConfig.enabled = false;
1370
+ return;
1371
+ }
1372
+ try {
1373
+ this.signer = new EvmPaymentSigner(privateKey, network);
1374
+ logger4.info(`[x402] Wallet initialized: ${this.signer.address} on ${network}`);
1375
+ } catch (err) {
1376
+ const message = err instanceof Error ? err.message : String(err);
1377
+ logger4.error(`[x402] Failed to initialize signer: ${message}`);
1378
+ this.serviceConfig.enabled = false;
1379
+ return;
1380
+ }
1381
+ this.serviceConfig.privateKey = "";
1382
+ const dbPath = String(runtime.getSetting("X402_DB_PATH") ?? "");
1383
+ if (dbPath) {
1384
+ try {
1385
+ this.storage = new SqlitePaymentStorage(dbPath);
1386
+ logger4.info(`[x402] Using SQLite storage at ${dbPath}`);
1387
+ } catch (err) {
1388
+ const message = err instanceof Error ? err.message : String(err);
1389
+ logger4.warn(`[x402] Failed to initialize SQLite storage: ${message}. Falling back to memory storage.`);
1390
+ this.storage = new MemoryPaymentStorage;
1391
+ }
1392
+ } else {
1393
+ logger4.info("[x402] Using in-memory storage (set X402_DB_PATH for persistence)");
1394
+ }
1395
+ const policy = {
1396
+ outgoing: {
1397
+ maxPerTransaction: usdToBaseUnits(this.serviceConfig.maxPaymentUsd),
1398
+ maxTotal: usdToBaseUnits(this.serviceConfig.maxTotalUsd),
1399
+ windowMs: ONE_DAY_MS,
1400
+ maxTransactions: 1000,
1401
+ allowedRecipients: [],
1402
+ blockedRecipients: []
1403
+ },
1404
+ incoming: {
1405
+ minPerTransaction: 0n,
1406
+ allowedSenders: [],
1407
+ blockedSenders: []
1408
+ }
1409
+ };
1410
+ this.policyEngine = new PolicyEngine(policy, this.storage);
1411
+ this.circuitBreaker = new CircuitBreaker;
1412
+ this.fetchWithPayment = createFetchWithPayment({
1413
+ signer: this.signer,
1414
+ policyEngine: this.policyEngine,
1415
+ circuitBreaker: this.circuitBreaker,
1416
+ storage: this.storage,
1417
+ logger: logger4
1418
+ });
1419
+ logger4.info(`[x402] Service active — max per-txn: $${this.serviceConfig.maxPaymentUsd}, max daily: $${this.serviceConfig.maxTotalUsd}`);
1420
+ }
1421
+ async stop() {
1422
+ logger4.info("[x402] Service stopping");
1423
+ this.signer = null;
1424
+ this.fetchWithPayment = null;
1425
+ }
1426
+ getFetchWithPayment() {
1427
+ if (!this.fetchWithPayment) {
1428
+ return (input, init) => fetch(input, init);
1429
+ }
1430
+ return this.fetchWithPayment;
1431
+ }
1432
+ async getSummary(windowMs = ONE_DAY_MS) {
1433
+ const [totalSpent, totalEarned, outgoingCount, incomingCount] = await Promise.all([
1434
+ this.storage.getTotal("outgoing", windowMs),
1435
+ this.storage.getTotal("incoming", windowMs),
1436
+ this.storage.getCount("outgoing", windowMs),
1437
+ this.storage.getCount("incoming", windowMs)
1438
+ ]);
1439
+ return {
1440
+ totalSpent,
1441
+ totalEarned,
1442
+ outgoingCount,
1443
+ incomingCount,
1444
+ windowMs
1445
+ };
1446
+ }
1447
+ async getRecentTransactions(limit = 20) {
1448
+ return this.storage.getRecords({ limit });
1449
+ }
1450
+ isActive() {
1451
+ return this.serviceConfig.enabled && this.signer !== null;
1452
+ }
1453
+ canMakePayments() {
1454
+ return this.isActive() && this.fetchWithPayment !== null;
1455
+ }
1456
+ updatePolicy(policy) {
1457
+ if (this.policyEngine) {
1458
+ this.policyEngine.updatePolicy(policy);
1459
+ logger4.info("[x402] Payment policy updated");
1460
+ }
1461
+ }
1462
+ getWalletAddress() {
1463
+ return this.signer?.address ?? null;
1464
+ }
1465
+ getNetwork() {
1466
+ return this.serviceConfig.network;
1467
+ }
1468
+ getFacilitatorUrl() {
1469
+ return this.serviceConfig.facilitatorUrl;
1470
+ }
1471
+ getPayToAddress() {
1472
+ return this.serviceConfig.payTo;
1473
+ }
1474
+ getStorage() {
1475
+ return this.storage;
1476
+ }
1477
+ getCircuitBreakerState() {
1478
+ return this.circuitBreaker.getState();
1479
+ }
1480
+ resetCircuitBreaker() {
1481
+ this.circuitBreaker.reset();
1482
+ logger4.info("[x402] Circuit breaker reset");
1483
+ }
1484
+ }
1485
+ // middleware/facilitator-client.ts
1486
+ function buildFacilitatorRequestBody(paymentProof, requirement) {
1487
+ let paymentPayload;
1488
+ try {
1489
+ const decoded = Buffer.from(paymentProof, "base64").toString("utf-8");
1490
+ paymentPayload = JSON.parse(decoded);
1491
+ } catch {
1492
+ try {
1493
+ paymentPayload = JSON.parse(paymentProof);
1494
+ } catch {
1495
+ throw new Error("Payment proof is neither valid base64-encoded JSON nor direct JSON");
1496
+ }
1497
+ }
1498
+ const paymentRequirements = {
1499
+ scheme: requirement.scheme,
1500
+ network: requirement.network,
1501
+ asset: requirement.asset,
1502
+ amount: requirement.maxAmountRequired,
1503
+ payTo: requirement.payTo,
1504
+ maxTimeoutSeconds: requirement.maxTimeoutSeconds,
1505
+ extra: requirement.extra
1506
+ };
1507
+ return JSON.stringify({ paymentPayload, paymentRequirements });
1508
+ }
1509
+ async function verifyPaymentWithFacilitator(paymentProof, facilitatorUrl, requirement) {
1510
+ const url = new URL("/verify", facilitatorUrl);
1511
+ try {
1512
+ const body = buildFacilitatorRequestBody(paymentProof, requirement);
1513
+ const response = await fetch(url.toString(), {
1514
+ method: "POST",
1515
+ headers: {
1516
+ "Content-Type": "application/json"
1517
+ },
1518
+ body
1519
+ });
1520
+ if (!response.ok) {
1521
+ const errorText = await response.text().catch(() => "unknown error");
1522
+ return {
1523
+ valid: false,
1524
+ reason: `Facilitator returned ${response.status}: ${errorText}`
1525
+ };
1526
+ }
1527
+ const result = await response.json();
1528
+ return {
1529
+ valid: result.isValid === true,
1530
+ payer: result.payer,
1531
+ reason: result.invalidReason
1532
+ };
1533
+ } catch (err) {
1534
+ const message = err instanceof Error ? err.message : String(err);
1535
+ return {
1536
+ valid: false,
1537
+ reason: `Facilitator request failed: ${message}`
1538
+ };
1539
+ }
1540
+ }
1541
+ async function settlePaymentWithFacilitator(paymentProof, facilitatorUrl, requirement) {
1542
+ const url = new URL("/settle", facilitatorUrl);
1543
+ try {
1544
+ const body = buildFacilitatorRequestBody(paymentProof, requirement);
1545
+ const response = await fetch(url.toString(), {
1546
+ method: "POST",
1547
+ headers: {
1548
+ "Content-Type": "application/json"
1549
+ },
1550
+ body
1551
+ });
1552
+ if (!response.ok) {
1553
+ const errorText = await response.text().catch(() => "unknown error");
1554
+ return {
1555
+ success: false,
1556
+ reason: `Facilitator returned ${response.status}: ${errorText}`
1557
+ };
1558
+ }
1559
+ const result = await response.json();
1560
+ return {
1561
+ success: result.success === true,
1562
+ txHash: result.transaction,
1563
+ reason: result.errorReason
1564
+ };
1565
+ } catch (err) {
1566
+ const message = err instanceof Error ? err.message : String(err);
1567
+ return {
1568
+ success: false,
1569
+ reason: `Facilitator request failed: ${message}`
1570
+ };
1571
+ }
1572
+ }
1573
+
1574
+ // middleware/paywall.ts
1575
+ function createPaywallMiddleware(config, storage, onStorageError) {
1576
+ const networkInfo = resolveNetwork(config.network);
1577
+ const requirementTemplate = {
1578
+ scheme: "upto",
1579
+ network: networkInfo.caip2,
1580
+ maxAmountRequired: config.amount.toString(),
1581
+ resource: "",
1582
+ description: config.description,
1583
+ mimeType: config.mimeType,
1584
+ payTo: config.payTo,
1585
+ maxTimeoutSeconds: config.maxTimeoutSeconds,
1586
+ asset: networkInfo.usdcAddress,
1587
+ extra: {
1588
+ name: networkInfo.usdcDomainName,
1589
+ version: networkInfo.usdcPermitVersion
1590
+ }
1591
+ };
1592
+ return async (req, res, next) => {
1593
+ const paymentHeader = getHeader(req, "x-payment");
1594
+ if (!paymentHeader) {
1595
+ const resource = req.url ?? "/";
1596
+ const requirement2 = { ...requirementTemplate, resource };
1597
+ const paymentRequired = {
1598
+ x402Version: 2,
1599
+ accepts: [requirement2]
1600
+ };
1601
+ const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
1602
+ res.setHeader("Payment-Required", encoded).setHeader("x-402", encoded).status(402).json({
1603
+ error: "Payment Required",
1604
+ message: config.description,
1605
+ x402Version: 2
1606
+ });
1607
+ return;
1608
+ }
1609
+ const requirement = {
1610
+ ...requirementTemplate,
1611
+ resource: req.url ?? "/"
1612
+ };
1613
+ const verifyResult = await verifyPaymentWithFacilitator(paymentHeader, config.facilitatorUrl, requirement);
1614
+ if (!verifyResult.valid) {
1615
+ res.status(402).json({
1616
+ error: "Payment Invalid",
1617
+ reason: verifyResult.reason ?? "Payment verification failed"
1618
+ });
1619
+ return;
1620
+ }
1621
+ const settleResult = await settlePaymentWithFacilitator(paymentHeader, config.facilitatorUrl, requirement);
1622
+ if (storage && verifyResult.payer) {
1623
+ const id = `x402_in_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
1624
+ await storage.recordPayment({
1625
+ id,
1626
+ direction: "incoming",
1627
+ counterparty: verifyResult.payer,
1628
+ amount: config.amount,
1629
+ network: networkInfo.caip2,
1630
+ txHash: settleResult.txHash ?? "",
1631
+ resource: req.url ?? "/",
1632
+ status: settleResult.success ? "confirmed" : "pending",
1633
+ createdAt: new Date().toISOString(),
1634
+ metadata: {}
1635
+ }).catch((err) => {
1636
+ if (onStorageError) {
1637
+ onStorageError(err);
1638
+ }
1639
+ });
1640
+ }
1641
+ if (settleResult.txHash) {
1642
+ res.setHeader("x-upto-session-id", settleResult.txHash);
1643
+ res.setHeader("X-PAYMENT-RESPONSE", settleResult.txHash);
1644
+ }
1645
+ next();
1646
+ };
1647
+ }
1648
+ function getHeader(req, name) {
1649
+ const headers = req.headers;
1650
+ const lowerName = name.toLowerCase();
1651
+ for (const [key, value] of Object.entries(headers)) {
1652
+ if (key.toLowerCase() === lowerName) {
1653
+ if (Array.isArray(value)) {
1654
+ return value[0];
1655
+ }
1656
+ return value;
1657
+ }
1658
+ }
1659
+ return;
1660
+ }
1661
+ // storage/postgres.ts
1662
+ import pg from "pg";
1663
+ var { Pool } = pg;
1664
+
1665
+ class PostgresPaymentStorage {
1666
+ pool;
1667
+ agentId;
1668
+ initialized;
1669
+ constructor(connectionString, agentId) {
1670
+ this.pool = new Pool({ connectionString });
1671
+ this.agentId = agentId;
1672
+ this.initialized = this.initialize();
1673
+ }
1674
+ async initialize() {
1675
+ const client = await this.pool.connect();
1676
+ try {
1677
+ await client.query(`
1678
+ CREATE TABLE IF NOT EXISTS x402_payments (
1679
+ id TEXT PRIMARY KEY,
1680
+ agent_id TEXT NOT NULL,
1681
+ direction VARCHAR NOT NULL CHECK(direction IN ('outgoing', 'incoming')),
1682
+ counterparty TEXT NOT NULL,
1683
+ amount TEXT NOT NULL,
1684
+ network TEXT NOT NULL,
1685
+ tx_hash TEXT NOT NULL DEFAULT '',
1686
+ resource TEXT NOT NULL DEFAULT '',
1687
+ status TEXT NOT NULL DEFAULT 'pending',
1688
+ created_at TEXT NOT NULL,
1689
+ metadata JSONB NOT NULL DEFAULT '{}'
1690
+ );
1691
+ CREATE INDEX IF NOT EXISTS idx_x402_agent ON x402_payments(agent_id);
1692
+ CREATE INDEX IF NOT EXISTS idx_x402_direction ON x402_payments(agent_id, direction, created_at);
1693
+ `);
1694
+ } finally {
1695
+ client.release();
1696
+ }
1697
+ }
1698
+ async ready() {
1699
+ await this.initialized;
1700
+ }
1701
+ async recordPayment(record) {
1702
+ await this.ready();
1703
+ await this.pool.query(`INSERT INTO x402_payments (id, agent_id, direction, counterparty, amount, network, tx_hash, resource, status, created_at, metadata)
1704
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
1705
+ record.id,
1706
+ this.agentId,
1707
+ record.direction,
1708
+ record.counterparty,
1709
+ record.amount.toString(),
1710
+ record.network,
1711
+ record.txHash,
1712
+ record.resource,
1713
+ record.status,
1714
+ record.createdAt,
1715
+ JSON.stringify(record.metadata)
1716
+ ]);
1717
+ }
1718
+ async getTotal(direction, windowMs, scope) {
1719
+ await this.ready();
1720
+ let sql = "SELECT amount FROM x402_payments WHERE agent_id = $1 AND direction = $2 AND status NOT IN ('failed', 'refunded')";
1721
+ const params = [this.agentId, direction];
1722
+ let paramIdx = 3;
1723
+ if (windowMs !== undefined) {
1724
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
1725
+ sql += ` AND created_at >= $${paramIdx}`;
1726
+ params.push(cutoff);
1727
+ paramIdx++;
1728
+ }
1729
+ if (scope) {
1730
+ sql += ` AND counterparty = $${paramIdx}`;
1731
+ params.push(scope);
1732
+ paramIdx++;
1733
+ }
1734
+ const result = await this.pool.query(sql, params);
1735
+ let total = 0n;
1736
+ for (const row of result.rows) {
1737
+ total += BigInt(row.amount);
1738
+ }
1739
+ return total;
1740
+ }
1741
+ async getRecords(filters) {
1742
+ await this.ready();
1743
+ let sql = "SELECT * FROM x402_payments WHERE agent_id = $1";
1744
+ const params = [this.agentId];
1745
+ let paramIdx = 2;
1746
+ if (filters) {
1747
+ if (filters.direction) {
1748
+ sql += ` AND direction = $${paramIdx}`;
1749
+ params.push(filters.direction);
1750
+ paramIdx++;
1751
+ }
1752
+ if (filters.counterparty) {
1753
+ sql += ` AND LOWER(counterparty) = LOWER($${paramIdx})`;
1754
+ params.push(filters.counterparty);
1755
+ paramIdx++;
1756
+ }
1757
+ if (filters.status) {
1758
+ sql += ` AND status = $${paramIdx}`;
1759
+ params.push(filters.status);
1760
+ paramIdx++;
1761
+ }
1762
+ if (filters.network) {
1763
+ sql += ` AND network = $${paramIdx}`;
1764
+ params.push(filters.network);
1765
+ paramIdx++;
1766
+ }
1767
+ if (filters.since) {
1768
+ sql += ` AND created_at >= $${paramIdx}`;
1769
+ params.push(filters.since);
1770
+ paramIdx++;
1771
+ }
1772
+ if (filters.until) {
1773
+ sql += ` AND created_at <= $${paramIdx}`;
1774
+ params.push(filters.until);
1775
+ paramIdx++;
1776
+ }
1777
+ }
1778
+ sql += " ORDER BY created_at DESC";
1779
+ if (filters?.limit !== undefined) {
1780
+ sql += ` LIMIT $${paramIdx}`;
1781
+ params.push(filters.limit);
1782
+ paramIdx++;
1783
+ }
1784
+ if (filters?.offset !== undefined) {
1785
+ sql += ` OFFSET $${paramIdx}`;
1786
+ params.push(filters.offset);
1787
+ paramIdx++;
1788
+ }
1789
+ const result = await this.pool.query(sql, params);
1790
+ return result.rows.map((row) => this.rowToRecord(row));
1791
+ }
1792
+ async getCount(direction, windowMs) {
1793
+ await this.ready();
1794
+ let sql = "SELECT COUNT(*) as cnt FROM x402_payments WHERE agent_id = $1 AND direction = $2 AND status NOT IN ('failed', 'refunded')";
1795
+ const params = [this.agentId, direction];
1796
+ let paramIdx = 3;
1797
+ if (windowMs !== undefined) {
1798
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
1799
+ sql += ` AND created_at >= $${paramIdx}`;
1800
+ params.push(cutoff);
1801
+ paramIdx++;
1802
+ }
1803
+ const result = await this.pool.query(sql, params);
1804
+ return parseInt(result.rows[0].cnt, 10);
1805
+ }
1806
+ async clear() {
1807
+ await this.ready();
1808
+ await this.pool.query("DELETE FROM x402_payments WHERE agent_id = $1", [
1809
+ this.agentId
1810
+ ]);
1811
+ }
1812
+ async close() {
1813
+ await this.pool.end();
1814
+ }
1815
+ rowToRecord(row) {
1816
+ let metadata = {};
1817
+ try {
1818
+ metadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
1819
+ } catch {}
1820
+ return {
1821
+ id: row.id,
1822
+ direction: row.direction,
1823
+ counterparty: row.counterparty,
1824
+ amount: BigInt(row.amount),
1825
+ network: row.network,
1826
+ txHash: row.tx_hash,
1827
+ resource: row.resource,
1828
+ status: row.status,
1829
+ createdAt: row.created_at,
1830
+ metadata
1831
+ };
1832
+ }
1833
+ }
1834
+
1835
+ // index.ts
1836
+ async function handleSummary(_req, res, runtime) {
1837
+ const service = runtime.getService("x402_payment");
1838
+ if (!service || !service.isActive()) {
1839
+ res.status(503).json({ error: "x402 service not active" });
1840
+ return;
1841
+ }
1842
+ const summary = await service.getSummary();
1843
+ res.status(200).json({
1844
+ wallet: service.getWalletAddress() ?? "",
1845
+ network: service.getNetwork(),
1846
+ totalSpent: formatUsd(summary.totalSpent),
1847
+ totalSpentRaw: summary.totalSpent.toString(),
1848
+ totalEarned: formatUsd(summary.totalEarned),
1849
+ totalEarnedRaw: summary.totalEarned.toString(),
1850
+ outgoingCount: summary.outgoingCount,
1851
+ incomingCount: summary.incomingCount,
1852
+ windowMs: summary.windowMs,
1853
+ circuitBreaker: service.getCircuitBreakerState()
1854
+ });
1855
+ }
1856
+ async function handleHistory(req, res, runtime) {
1857
+ const service = runtime.getService("x402_payment");
1858
+ if (!service || !service.isActive()) {
1859
+ res.status(503).json({ error: "x402 service not active" });
1860
+ return;
1861
+ }
1862
+ const limitStr = (Array.isArray(req.query?.limit) ? req.query.limit[0] : req.query?.limit) ?? "20";
1863
+ const limit = Math.min(Math.max(parseInt(limitStr, 10) || 20, 1), 100);
1864
+ const transactions = await service.getRecentTransactions(limit);
1865
+ const serialized = transactions.map((txn) => ({
1866
+ id: txn.id,
1867
+ direction: txn.direction,
1868
+ counterparty: txn.counterparty,
1869
+ amount: txn.amount.toString(),
1870
+ amountUsd: formatUsd(txn.amount),
1871
+ network: txn.network,
1872
+ txHash: txn.txHash,
1873
+ resource: txn.resource,
1874
+ status: txn.status,
1875
+ createdAt: txn.createdAt,
1876
+ metadata: txn.metadata
1877
+ }));
1878
+ res.status(200).json({ transactions: serialized, count: serialized.length });
1879
+ }
1880
+ async function handleExport(_req, res, runtime) {
1881
+ const service = runtime.getService("x402_payment");
1882
+ if (!service || !service.isActive()) {
1883
+ res.status(503).json({ error: "x402 service not active" });
1884
+ return;
1885
+ }
1886
+ const transactions = await service.getRecentTransactions(1e4);
1887
+ const csvHeader = "id,direction,counterparty,amount_base_units,amount_usd,network,tx_hash,resource,status,created_at";
1888
+ const csvRows = transactions.map((txn) => `${txn.id},${txn.direction},${txn.counterparty},${txn.amount.toString()},${formatUsd(txn.amount)},${txn.network},${txn.txHash},${escapeCSV(txn.resource)},${txn.status},${txn.createdAt}`);
1889
+ const csv = [csvHeader, ...csvRows].join(`
1890
+ `);
1891
+ if (res.setHeader) {
1892
+ res.setHeader("Content-Type", "text/csv");
1893
+ res.setHeader("Content-Disposition", `attachment; filename="x402-payments-${new Date().toISOString().slice(0, 10)}.csv"`);
1894
+ }
1895
+ res.send(csv);
1896
+ }
1897
+ function escapeCSV(value) {
1898
+ if (value.includes(",") || value.includes('"') || value.includes(`
1899
+ `)) {
1900
+ return `"${value.replace(/"/g, '""')}"`;
1901
+ }
1902
+ return value;
1903
+ }
1904
+ var x402Plugin = {
1905
+ name: "x402",
1906
+ description: "x402 HTTP payment protocol - send and receive crypto payments (USDC on EVM chains)",
1907
+ config: {
1908
+ X402_PRIVATE_KEY: null,
1909
+ X402_NETWORK: null,
1910
+ X402_PAY_TO: null,
1911
+ X402_FACILITATOR_URL: null,
1912
+ X402_MAX_PAYMENT_USD: null,
1913
+ X402_MAX_TOTAL_USD: null,
1914
+ X402_ENABLED: null
1915
+ },
1916
+ init: async (_config, _runtime) => {},
1917
+ services: [X402Service],
1918
+ actions: [payForServiceAction, checkPaymentHistoryAction, setPaymentPolicyAction],
1919
+ providers: [paymentBalanceProvider],
1920
+ routes: [
1921
+ agentCardRoute,
1922
+ {
1923
+ type: "GET",
1924
+ path: "/x402/summary",
1925
+ name: "x402-summary",
1926
+ public: false,
1927
+ handler: handleSummary
1928
+ },
1929
+ {
1930
+ type: "GET",
1931
+ path: "/x402/history",
1932
+ name: "x402-history",
1933
+ public: false,
1934
+ handler: handleHistory
1935
+ },
1936
+ {
1937
+ type: "GET",
1938
+ path: "/x402/export",
1939
+ name: "x402-export",
1940
+ public: false,
1941
+ handler: handleExport
1942
+ }
1943
+ ]
1944
+ };
1945
+ var typescript_default = x402Plugin;
1946
+ export {
1947
+ x402Plugin,
1948
+ verifyPaymentWithFacilitator,
1949
+ usdToBaseUnits,
1950
+ truncateAddress,
1951
+ settlePaymentWithFacilitator,
1952
+ setPaymentPolicyAction,
1953
+ resolveNetwork,
1954
+ paymentBalanceProvider,
1955
+ payForServiceAction,
1956
+ networkKeyFromCaip2,
1957
+ formatUsd,
1958
+ typescript_default as default,
1959
+ createPaywallMiddleware,
1960
+ createFetchWithPayment,
1961
+ checkPaymentHistoryAction,
1962
+ agentCardRoute,
1963
+ X402Service,
1964
+ SqlitePaymentStorage,
1965
+ PostgresPaymentStorage,
1966
+ PolicyEngine,
1967
+ ONE_DAY_MS,
1968
+ NETWORK_REGISTRY,
1969
+ MemoryPaymentStorage,
1970
+ EvmPaymentSigner,
1971
+ CircuitBreaker
1972
+ };
1973
+
1974
+ //# debugId=8D689045A014B14A64756E2164756E21