@agirails/sdk 2.2.0 → 2.2.2

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.
Files changed (120) hide show
  1. package/dist/ACTPClient.d.ts +200 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +266 -2
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/abi/ACTPKernel.json +16 -0
  6. package/dist/adapters/AdapterRegistry.d.ts +140 -0
  7. package/dist/adapters/AdapterRegistry.d.ts.map +1 -0
  8. package/dist/adapters/AdapterRegistry.js +166 -0
  9. package/dist/adapters/AdapterRegistry.js.map +1 -0
  10. package/dist/adapters/AdapterRouter.d.ts +165 -0
  11. package/dist/adapters/AdapterRouter.d.ts.map +1 -0
  12. package/dist/adapters/AdapterRouter.js +350 -0
  13. package/dist/adapters/AdapterRouter.js.map +1 -0
  14. package/dist/adapters/BaseAdapter.d.ts +17 -0
  15. package/dist/adapters/BaseAdapter.d.ts.map +1 -1
  16. package/dist/adapters/BaseAdapter.js +21 -0
  17. package/dist/adapters/BaseAdapter.js.map +1 -1
  18. package/dist/adapters/BasicAdapter.d.ts +72 -3
  19. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  20. package/dist/adapters/BasicAdapter.js +170 -2
  21. package/dist/adapters/BasicAdapter.js.map +1 -1
  22. package/dist/adapters/IAdapter.d.ts +230 -0
  23. package/dist/adapters/IAdapter.d.ts.map +1 -0
  24. package/dist/adapters/IAdapter.js +44 -0
  25. package/dist/adapters/IAdapter.js.map +1 -0
  26. package/dist/adapters/StandardAdapter.d.ts +70 -1
  27. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  28. package/dist/adapters/StandardAdapter.js +184 -0
  29. package/dist/adapters/StandardAdapter.js.map +1 -1
  30. package/dist/adapters/X402Adapter.d.ts +208 -0
  31. package/dist/adapters/X402Adapter.d.ts.map +1 -0
  32. package/dist/adapters/X402Adapter.js +423 -0
  33. package/dist/adapters/X402Adapter.js.map +1 -0
  34. package/dist/adapters/index.d.ts +8 -0
  35. package/dist/adapters/index.d.ts.map +1 -1
  36. package/dist/adapters/index.js +19 -1
  37. package/dist/adapters/index.js.map +1 -1
  38. package/dist/cli/commands/init.d.ts +4 -0
  39. package/dist/cli/commands/init.d.ts.map +1 -1
  40. package/dist/cli/commands/init.js +184 -4
  41. package/dist/cli/commands/init.js.map +1 -1
  42. package/dist/config/networks.js +3 -3
  43. package/dist/config/networks.js.map +1 -1
  44. package/dist/erc8004/ERC8004Bridge.d.ts +155 -0
  45. package/dist/erc8004/ERC8004Bridge.d.ts.map +1 -0
  46. package/dist/erc8004/ERC8004Bridge.js +325 -0
  47. package/dist/erc8004/ERC8004Bridge.js.map +1 -0
  48. package/dist/erc8004/ReputationReporter.d.ts +223 -0
  49. package/dist/erc8004/ReputationReporter.d.ts.map +1 -0
  50. package/dist/erc8004/ReputationReporter.js +266 -0
  51. package/dist/erc8004/ReputationReporter.js.map +1 -0
  52. package/dist/erc8004/index.d.ts +36 -0
  53. package/dist/erc8004/index.d.ts.map +1 -0
  54. package/dist/erc8004/index.js +46 -0
  55. package/dist/erc8004/index.js.map +1 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +21 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/protocol/ACTPKernel.d.ts +1 -1
  61. package/dist/protocol/ACTPKernel.d.ts.map +1 -1
  62. package/dist/protocol/ACTPKernel.js +16 -7
  63. package/dist/protocol/ACTPKernel.js.map +1 -1
  64. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  65. package/dist/runtime/BlockchainRuntime.js +2 -0
  66. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  67. package/dist/runtime/IACTPRuntime.d.ts +6 -0
  68. package/dist/runtime/IACTPRuntime.d.ts.map +1 -1
  69. package/dist/runtime/MockRuntime.d.ts +12 -0
  70. package/dist/runtime/MockRuntime.d.ts.map +1 -1
  71. package/dist/runtime/MockRuntime.js +41 -0
  72. package/dist/runtime/MockRuntime.js.map +1 -1
  73. package/dist/runtime/types/MockState.d.ts +6 -0
  74. package/dist/runtime/types/MockState.d.ts.map +1 -1
  75. package/dist/runtime/types/MockState.js.map +1 -1
  76. package/dist/types/adapter.d.ts +359 -0
  77. package/dist/types/adapter.d.ts.map +1 -0
  78. package/dist/types/adapter.js +115 -0
  79. package/dist/types/adapter.js.map +1 -0
  80. package/dist/types/erc8004.d.ts +184 -0
  81. package/dist/types/erc8004.d.ts.map +1 -0
  82. package/dist/types/erc8004.js +132 -0
  83. package/dist/types/erc8004.js.map +1 -0
  84. package/dist/types/index.d.ts +3 -0
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +3 -0
  87. package/dist/types/index.js.map +1 -1
  88. package/dist/types/transaction.d.ts +12 -0
  89. package/dist/types/transaction.d.ts.map +1 -1
  90. package/dist/types/x402.d.ts +162 -0
  91. package/dist/types/x402.d.ts.map +1 -0
  92. package/dist/types/x402.js +162 -0
  93. package/dist/types/x402.js.map +1 -0
  94. package/package.json +3 -2
  95. package/src/ACTPClient.ts +318 -2
  96. package/src/abi/ACTPKernel.json +16 -0
  97. package/src/adapters/AdapterRegistry.ts +173 -0
  98. package/src/adapters/AdapterRouter.ts +417 -0
  99. package/src/adapters/BaseAdapter.ts +25 -0
  100. package/src/adapters/BasicAdapter.ts +199 -3
  101. package/src/adapters/IAdapter.ts +292 -0
  102. package/src/adapters/StandardAdapter.ts +220 -1
  103. package/src/adapters/X402Adapter.ts +653 -0
  104. package/src/adapters/index.ts +27 -0
  105. package/src/cli/commands/init.ts +208 -3
  106. package/src/config/networks.ts +3 -3
  107. package/src/erc8004/ERC8004Bridge.ts +461 -0
  108. package/src/erc8004/ReputationReporter.ts +472 -0
  109. package/src/erc8004/index.ts +61 -0
  110. package/src/index.ts +43 -0
  111. package/src/protocol/ACTPKernel.ts +26 -7
  112. package/src/runtime/BlockchainRuntime.ts +2 -0
  113. package/src/runtime/IACTPRuntime.ts +6 -0
  114. package/src/runtime/MockRuntime.ts +42 -0
  115. package/src/runtime/types/MockState.ts +7 -0
  116. package/src/types/adapter.ts +296 -0
  117. package/src/types/erc8004.ts +293 -0
  118. package/src/types/index.ts +3 -0
  119. package/src/types/transaction.ts +12 -0
  120. package/src/types/x402.ts +219 -0
@@ -0,0 +1,653 @@
1
+ /**
2
+ * X402Adapter - HTTP 402 Payment Required Protocol (Atomic Payments)
3
+ *
4
+ * Implements the x402 protocol for atomic, instant API payments.
5
+ * NO escrow, NO state machine, NO disputes - just pay and receive.
6
+ *
7
+ * This is fundamentally different from ACTP:
8
+ * - ACTP: escrow → state machine → disputes → explicit release
9
+ * - x402: atomic payment → instant settlement → done
10
+ *
11
+ * Use x402 for:
12
+ * - Simple API calls (pay-per-request)
13
+ * - Instant delivery (response IS the delivery)
14
+ * - Low-value, high-frequency transactions
15
+ *
16
+ * Use ACTP for:
17
+ * - Complex services requiring verification
18
+ * - High-value transactions needing dispute protection
19
+ * - Multi-step deliveries
20
+ *
21
+ * @module adapters/X402Adapter
22
+ */
23
+
24
+ import { BaseAdapter, ValidationError } from './BaseAdapter';
25
+ import { IAdapter, TransactionStatus, AdapterTransactionState } from './IAdapter';
26
+ import {
27
+ AdapterMetadata,
28
+ UnifiedPayParams,
29
+ UnifiedPayResult,
30
+ } from '../types/adapter';
31
+ import {
32
+ X402PaymentHeaders,
33
+ X402_HEADERS,
34
+ X402_PROOF_HEADERS,
35
+ X402Error,
36
+ X402ErrorCode,
37
+ X402Network,
38
+ isValidX402Network,
39
+ } from '../types/x402';
40
+
41
+ // ============================================================================
42
+ // Types
43
+ // ============================================================================
44
+
45
+ /** Type for fetch function (cross-platform compatible) */
46
+ export type FetchFunction = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
47
+
48
+ /**
49
+ * Transfer function for atomic payments.
50
+ *
51
+ * @param to - Recipient address
52
+ * @param amount - Amount in USDC wei (string)
53
+ * @returns Transaction hash as proof
54
+ */
55
+ export type TransferFunction = (to: string, amount: string) => Promise<string>;
56
+
57
+ /** Supported HTTP methods for x402 requests */
58
+ export type X402HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
59
+
60
+ /**
61
+ * Extended payment parameters for x402 with full HTTP support.
62
+ */
63
+ export interface X402PayParams extends UnifiedPayParams {
64
+ /** HTTP method (default: GET) */
65
+ method?: X402HttpMethod;
66
+
67
+ /** Custom request headers */
68
+ headers?: Record<string, string>;
69
+
70
+ /** Request body (string or object, will be JSON.stringify'd if object) */
71
+ body?: string | Record<string, unknown>;
72
+
73
+ /** Content-Type header (default: application/json for POST/PUT/PATCH with body) */
74
+ contentType?: string;
75
+ }
76
+
77
+ /**
78
+ * Configuration options for X402Adapter.
79
+ */
80
+ export interface X402AdapterConfig {
81
+ /** Expected network for validation (must match server's X-Payment-Network) */
82
+ expectedNetwork: X402Network;
83
+
84
+ /** Transfer function for atomic payments (required) */
85
+ transferFn: TransferFunction;
86
+
87
+ /** Request timeout in milliseconds (default: 30000) */
88
+ requestTimeout?: number;
89
+
90
+ /** Custom fetch function for testing (default: global fetch) */
91
+ fetchFn?: FetchFunction;
92
+
93
+ /** Default headers to include in all requests */
94
+ defaultHeaders?: Record<string, string>;
95
+ }
96
+
97
+ /**
98
+ * Atomic payment record (for getStatus lookups).
99
+ */
100
+ interface AtomicPaymentRecord {
101
+ txHash: string;
102
+ provider: string;
103
+ requester: string;
104
+ amount: string;
105
+ timestamp: number;
106
+ endpoint: string;
107
+ }
108
+
109
+ // ============================================================================
110
+ // X402Adapter Implementation
111
+ // ============================================================================
112
+
113
+ /**
114
+ * X402Adapter - Atomic HTTP payment protocol.
115
+ *
116
+ * Key characteristics:
117
+ * - usesEscrow: false (direct payment)
118
+ * - supportsDisputes: false (atomic = final)
119
+ * - settlementMode: 'atomic' (instant)
120
+ * - releaseRequired: false (no escrow to release)
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const adapter = new X402Adapter(requesterAddress, {
125
+ * expectedNetwork: 'base-sepolia',
126
+ * transferFn: async (to, amount) => {
127
+ * const tx = await usdcContract.transfer(to, amount);
128
+ * return tx.hash;
129
+ * },
130
+ * });
131
+ *
132
+ * const result = await adapter.pay({
133
+ * to: 'https://api.provider.com/service',
134
+ * amount: '10', // Hint only, actual amount from 402
135
+ * });
136
+ *
137
+ * // That's it! No release() needed.
138
+ * console.log(result.response?.status); // 200
139
+ * console.log(result.releaseRequired); // false
140
+ * ```
141
+ */
142
+ export class X402Adapter extends BaseAdapter implements IAdapter {
143
+ /**
144
+ * Adapter metadata - atomic, no escrow.
145
+ */
146
+ public readonly metadata: AdapterMetadata = {
147
+ id: 'x402',
148
+ name: 'X402 Atomic Payment Adapter',
149
+ usesEscrow: false,
150
+ supportsDisputes: false,
151
+ requiresIdentity: false,
152
+ settlementMode: 'atomic',
153
+ priority: 70,
154
+ };
155
+
156
+ private readonly timeout: number;
157
+ private readonly fetchFn: FetchFunction;
158
+ private readonly defaultHeaders: Record<string, string>;
159
+ private readonly transferFn: TransferFunction;
160
+
161
+ /** Local cache of payments for status lookups */
162
+ private readonly payments = new Map<string, AtomicPaymentRecord>();
163
+
164
+ /**
165
+ * Creates a new X402Adapter instance.
166
+ *
167
+ * @param requesterAddress - The requester's Ethereum address
168
+ * @param config - X402-specific configuration
169
+ */
170
+ constructor(
171
+ requesterAddress: string,
172
+ private config: X402AdapterConfig
173
+ ) {
174
+ super(requesterAddress);
175
+ this.timeout = config.requestTimeout ?? 30000;
176
+ this.fetchFn = config.fetchFn ?? fetch;
177
+ this.defaultHeaders = config.defaultHeaders ?? {};
178
+ this.transferFn = config.transferFn;
179
+ }
180
+
181
+ // ==========================================================================
182
+ // IAdapter Implementation
183
+ // ==========================================================================
184
+
185
+ /**
186
+ * Check if this adapter can handle the given parameters.
187
+ *
188
+ * X402Adapter handles HTTPS URLs only (security requirement).
189
+ */
190
+ canHandle(params: UnifiedPayParams): boolean {
191
+ if (typeof params.to !== 'string') {
192
+ return false;
193
+ }
194
+
195
+ try {
196
+ const url = new URL(params.to);
197
+ return url.protocol === 'https:';
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Validate parameters before execution.
205
+ */
206
+ validate(params: UnifiedPayParams): void {
207
+ if (!this.canHandle(params)) {
208
+ throw new ValidationError(
209
+ `X402 requires HTTPS URL, got: "${params.to}". ` +
210
+ `HTTP endpoints are not supported for security reasons.`
211
+ );
212
+ }
213
+
214
+ const url = new URL(params.to);
215
+
216
+ if (url.username || url.password) {
217
+ throw new ValidationError(
218
+ 'URL cannot contain embedded credentials (username:password).'
219
+ );
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Execute atomic x402 payment flow with full HTTP support.
225
+ *
226
+ * 1. Request endpoint → get 402
227
+ * 2. Parse payment headers
228
+ * 3. Execute atomic USDC transfer
229
+ * 4. Retry with tx hash as proof (same method/headers/body)
230
+ * 5. Return response (settlement complete!)
231
+ *
232
+ * @param params - Payment parameters with optional HTTP method, headers, body
233
+ */
234
+ async pay(params: UnifiedPayParams | X402PayParams): Promise<UnifiedPayResult> {
235
+ this.validate(params);
236
+
237
+ const endpoint = params.to;
238
+ const x402Params = params as X402PayParams;
239
+
240
+ // Extract HTTP options
241
+ const method: X402HttpMethod = x402Params.method ?? 'GET';
242
+ const requestHeaders = x402Params.headers ?? {};
243
+ const requestBody = this.serializeBody(x402Params.body, x402Params.contentType);
244
+ const contentType = x402Params.contentType ??
245
+ (x402Params.body && method !== 'GET' ? 'application/json' : undefined);
246
+
247
+ // Step 1: Initial request
248
+ const initialResponse = await this.makeRequest(
249
+ endpoint,
250
+ method,
251
+ requestHeaders,
252
+ requestBody,
253
+ contentType
254
+ );
255
+
256
+ // Step 2: Check response status
257
+ if (initialResponse.status !== 402) {
258
+ if (initialResponse.ok) {
259
+ return this.createFreeServiceResult(params, initialResponse);
260
+ }
261
+ throw new X402Error(
262
+ `Expected 402 Payment Required, got ${initialResponse.status}`,
263
+ X402ErrorCode.NOT_402_RESPONSE,
264
+ initialResponse
265
+ );
266
+ }
267
+
268
+ // Step 3: Parse payment headers
269
+ const paymentHeaders = this.parsePaymentHeaders(initialResponse);
270
+
271
+ // Step 4: Validate network
272
+ if (paymentHeaders.network !== this.config.expectedNetwork) {
273
+ throw new X402Error(
274
+ `Network mismatch: expected ${this.config.expectedNetwork}, got ${paymentHeaders.network}`,
275
+ X402ErrorCode.NETWORK_MISMATCH,
276
+ initialResponse
277
+ );
278
+ }
279
+
280
+ // Step 5: Validate deadline
281
+ const now = Math.floor(Date.now() / 1000);
282
+ if (paymentHeaders.deadline <= now) {
283
+ throw new X402Error(
284
+ `Payment deadline has passed: ${new Date(paymentHeaders.deadline * 1000).toISOString()}`,
285
+ X402ErrorCode.DEADLINE_PASSED,
286
+ initialResponse
287
+ );
288
+ }
289
+
290
+ // Step 6: ATOMIC PAYMENT - direct transfer, no escrow
291
+ const txHash = await this.executeAtomicPayment(paymentHeaders);
292
+
293
+ // Step 7: Retry with proof (same method/headers/body + payment proof)
294
+ const serviceResponse = await this.retryWithProof(
295
+ endpoint,
296
+ txHash,
297
+ method,
298
+ requestHeaders,
299
+ requestBody,
300
+ contentType
301
+ );
302
+
303
+ // Step 8: Cache payment record for status lookups
304
+ this.payments.set(txHash, {
305
+ txHash,
306
+ provider: paymentHeaders.paymentAddress.toLowerCase(),
307
+ requester: this.requesterAddress.toLowerCase(),
308
+ amount: paymentHeaders.amount,
309
+ timestamp: now,
310
+ endpoint,
311
+ });
312
+
313
+ // Step 9: Return result - DONE! No release needed.
314
+ return {
315
+ txId: txHash,
316
+ escrowId: null, // No escrow!
317
+ adapter: this.metadata.id,
318
+ state: 'COMMITTED', // Atomic = immediately settled
319
+ success: true,
320
+ amount: this.formatAmount(paymentHeaders.amount),
321
+ response: serviceResponse,
322
+ releaseRequired: false, // KEY DIFFERENCE from ACTP
323
+ provider: paymentHeaders.paymentAddress.toLowerCase(),
324
+ requester: this.requesterAddress.toLowerCase(),
325
+ deadline: new Date(paymentHeaders.deadline * 1000).toISOString(),
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Serialize request body to string.
331
+ */
332
+ private serializeBody(
333
+ body: string | Record<string, unknown> | undefined,
334
+ _contentType?: string
335
+ ): string | undefined {
336
+ if (body === undefined) return undefined;
337
+ if (typeof body === 'string') return body;
338
+ return JSON.stringify(body);
339
+ }
340
+
341
+ /**
342
+ * Get payment status by transaction hash.
343
+ *
344
+ * For atomic payments, status is simple:
345
+ * - If tx exists → SETTLED (atomic = instant settlement)
346
+ */
347
+ async getStatus(txId: string): Promise<TransactionStatus> {
348
+ const record = this.payments.get(txId);
349
+
350
+ if (!record) {
351
+ throw new Error(`Payment ${txId} not found. X402 payments are atomic and stateless.`);
352
+ }
353
+
354
+ return {
355
+ state: 'SETTLED' as AdapterTransactionState,
356
+ canStartWork: false,
357
+ canDeliver: false,
358
+ canRelease: false,
359
+ canDispute: false,
360
+ amount: this.formatAmount(record.amount),
361
+ provider: record.provider,
362
+ requester: record.requester,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Not applicable for atomic payments.
368
+ * @throws {Error} Always - x402 has no lifecycle
369
+ */
370
+ async startWork(_txId: string): Promise<void> {
371
+ throw new Error(
372
+ 'X402 is atomic - no lifecycle methods. ' +
373
+ 'Payment and delivery happen atomically. Use ACTP for stateful transactions.'
374
+ );
375
+ }
376
+
377
+ /**
378
+ * Not applicable for atomic payments.
379
+ * @throws {Error} Always - x402 has no lifecycle
380
+ */
381
+ async deliver(_txId: string, _proof?: string): Promise<void> {
382
+ throw new Error(
383
+ 'X402 is atomic - no lifecycle methods. ' +
384
+ 'The HTTP response IS the delivery. Use ACTP for stateful transactions.'
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Not applicable for atomic payments.
390
+ * @throws {Error} Always - x402 has no escrow
391
+ */
392
+ async release(_escrowId: string, _attestationUID?: string): Promise<void> {
393
+ throw new Error(
394
+ 'X402 is atomic - no escrow to release. ' +
395
+ 'Payment settled instantly. Use ACTP for escrow-based transactions.'
396
+ );
397
+ }
398
+
399
+ // ==========================================================================
400
+ // Private Helpers
401
+ // ==========================================================================
402
+
403
+ /**
404
+ * Make an HTTP request with full options support.
405
+ *
406
+ * @param url - Request URL
407
+ * @param method - HTTP method
408
+ * @param customHeaders - Custom headers from request params
409
+ * @param body - Request body (optional)
410
+ * @param contentType - Content-Type header (optional)
411
+ * @param proofHeaders - Payment proof headers for retry (optional)
412
+ */
413
+ private async makeRequest(
414
+ url: string,
415
+ method: X402HttpMethod,
416
+ customHeaders: Record<string, string> = {},
417
+ body?: string,
418
+ contentType?: string,
419
+ proofHeaders?: Record<string, string>
420
+ ): Promise<Response> {
421
+ const controller = new AbortController();
422
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
423
+
424
+ try {
425
+ // Build headers: defaults + custom + content-type + proof
426
+ const headers = new Headers(this.defaultHeaders);
427
+
428
+ // Add custom headers from request
429
+ for (const [key, value] of Object.entries(customHeaders)) {
430
+ headers.set(key, value);
431
+ }
432
+
433
+ // Add content-type if provided
434
+ if (contentType) {
435
+ headers.set('Content-Type', contentType);
436
+ }
437
+
438
+ // Add proof headers for retry
439
+ if (proofHeaders) {
440
+ for (const [key, value] of Object.entries(proofHeaders)) {
441
+ headers.set(key, value);
442
+ }
443
+ }
444
+
445
+ const init: RequestInit = {
446
+ method,
447
+ headers,
448
+ signal: controller.signal,
449
+ };
450
+
451
+ // Add body for non-GET requests
452
+ if (body && method !== 'GET') {
453
+ init.body = body;
454
+ }
455
+
456
+ return await this.fetchFn(url, init);
457
+ } finally {
458
+ clearTimeout(timeoutId);
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Parse X-Payment-* headers from 402 response.
464
+ */
465
+ private parsePaymentHeaders(response: Response): X402PaymentHeaders {
466
+ const h = response.headers;
467
+
468
+ const requiredHeader = h.get(X402_HEADERS.REQUIRED);
469
+ if (requiredHeader?.toLowerCase() !== 'true') {
470
+ throw new X402Error(
471
+ `Missing or invalid ${X402_HEADERS.REQUIRED} header`,
472
+ X402ErrorCode.MISSING_HEADERS,
473
+ response
474
+ );
475
+ }
476
+
477
+ const address = h.get(X402_HEADERS.ADDRESS);
478
+ const amount = h.get(X402_HEADERS.AMOUNT);
479
+ const network = h.get(X402_HEADERS.NETWORK);
480
+ const token = h.get(X402_HEADERS.TOKEN);
481
+ const deadline = h.get(X402_HEADERS.DEADLINE);
482
+
483
+ if (!address) {
484
+ throw new X402Error(`Missing ${X402_HEADERS.ADDRESS}`, X402ErrorCode.MISSING_HEADERS, response);
485
+ }
486
+ if (!amount) {
487
+ throw new X402Error(`Missing ${X402_HEADERS.AMOUNT}`, X402ErrorCode.MISSING_HEADERS, response);
488
+ }
489
+ if (!network) {
490
+ throw new X402Error(`Missing ${X402_HEADERS.NETWORK}`, X402ErrorCode.MISSING_HEADERS, response);
491
+ }
492
+ if (!token) {
493
+ throw new X402Error(`Missing ${X402_HEADERS.TOKEN}`, X402ErrorCode.MISSING_HEADERS, response);
494
+ }
495
+ if (!deadline) {
496
+ throw new X402Error(`Missing ${X402_HEADERS.DEADLINE}`, X402ErrorCode.MISSING_HEADERS, response);
497
+ }
498
+
499
+ // Validate address
500
+ const validatedAddress = this.validatePaymentAddress(address, response);
501
+
502
+ // Validate amount
503
+ if (!/^\d+$/.test(amount)) {
504
+ throw new X402Error(
505
+ `Invalid ${X402_HEADERS.AMOUNT}: "${amount}"`,
506
+ X402ErrorCode.INVALID_AMOUNT,
507
+ response
508
+ );
509
+ }
510
+
511
+ // Validate network
512
+ if (!isValidX402Network(network)) {
513
+ throw new X402Error(
514
+ `Invalid ${X402_HEADERS.NETWORK}: "${network}"`,
515
+ X402ErrorCode.INVALID_NETWORK,
516
+ response
517
+ );
518
+ }
519
+
520
+ // Validate token
521
+ if (token.toUpperCase() !== 'USDC') {
522
+ throw new X402Error(
523
+ `Unsupported token: "${token}". Only USDC supported.`,
524
+ X402ErrorCode.MISSING_HEADERS,
525
+ response
526
+ );
527
+ }
528
+
529
+ const deadlineNum = parseInt(deadline, 10);
530
+ if (isNaN(deadlineNum) || deadlineNum <= 0) {
531
+ throw new X402Error(
532
+ `Invalid ${X402_HEADERS.DEADLINE}: "${deadline}"`,
533
+ X402ErrorCode.MISSING_HEADERS,
534
+ response
535
+ );
536
+ }
537
+
538
+ return {
539
+ required: true,
540
+ paymentAddress: validatedAddress,
541
+ amount,
542
+ network,
543
+ token: 'USDC',
544
+ deadline: deadlineNum,
545
+ serviceId: h.get(X402_HEADERS.SERVICE_ID) ?? undefined,
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Validate payment address from header.
551
+ */
552
+ private validatePaymentAddress(address: string, response: Response): string {
553
+ try {
554
+ return this.validateAddress(address, X402_HEADERS.ADDRESS);
555
+ } catch {
556
+ throw new X402Error(
557
+ `Invalid ${X402_HEADERS.ADDRESS}: "${address}"`,
558
+ X402ErrorCode.INVALID_ADDRESS,
559
+ response
560
+ );
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Execute atomic payment - direct transfer to provider.
566
+ *
567
+ * This is the key difference from ACTP:
568
+ * - No escrow
569
+ * - No state machine
570
+ * - Just transfer and done
571
+ */
572
+ private async executeAtomicPayment(headers: X402PaymentHeaders): Promise<string> {
573
+ try {
574
+ const txHash = await this.transferFn(
575
+ headers.paymentAddress,
576
+ headers.amount
577
+ );
578
+ return txHash;
579
+ } catch (error) {
580
+ throw new X402Error(
581
+ `Atomic payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
582
+ X402ErrorCode.PAYMENT_FAILED
583
+ );
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Retry request with payment proof (tx hash).
589
+ * Uses the same HTTP method, headers, and body as the original request.
590
+ *
591
+ * @param endpoint - Request URL
592
+ * @param txHash - Payment transaction hash as proof
593
+ * @param method - Original HTTP method
594
+ * @param customHeaders - Original custom headers
595
+ * @param body - Original request body
596
+ * @param contentType - Original content-type
597
+ */
598
+ private async retryWithProof(
599
+ endpoint: string,
600
+ txHash: string,
601
+ method: X402HttpMethod = 'GET',
602
+ customHeaders: Record<string, string> = {},
603
+ body?: string,
604
+ contentType?: string
605
+ ): Promise<Response> {
606
+ // Add payment proof headers
607
+ const proofHeaders: Record<string, string> = {
608
+ [X402_PROOF_HEADERS.TX_ID]: txHash,
609
+ // No escrow ID for atomic payments
610
+ };
611
+
612
+ const response = await this.makeRequest(
613
+ endpoint,
614
+ method,
615
+ customHeaders,
616
+ body,
617
+ contentType,
618
+ proofHeaders
619
+ );
620
+
621
+ if (!response.ok) {
622
+ throw new X402Error(
623
+ `Retry failed: ${response.status} ${response.statusText}`,
624
+ X402ErrorCode.RETRY_FAILED,
625
+ response
626
+ );
627
+ }
628
+
629
+ return response;
630
+ }
631
+
632
+ /**
633
+ * Create result for free services (200 on initial request).
634
+ */
635
+ private createFreeServiceResult(
636
+ params: UnifiedPayParams,
637
+ response: Response
638
+ ): UnifiedPayResult {
639
+ return {
640
+ txId: '0x' + '0'.repeat(64),
641
+ escrowId: null,
642
+ adapter: this.metadata.id,
643
+ state: 'COMMITTED',
644
+ success: true,
645
+ amount: '0.00 USDC',
646
+ response,
647
+ releaseRequired: false,
648
+ provider: '0x' + '0'.repeat(40),
649
+ requester: this.requesterAddress.toLowerCase(),
650
+ deadline: new Date(Date.now() + 86400000).toISOString(),
651
+ };
652
+ }
653
+ }
@@ -5,6 +5,9 @@
5
5
  * - BaseAdapter: Abstract base with shared utilities
6
6
  * - BasicAdapter: High-level, opinionated API
7
7
  * - StandardAdapter: Balanced control API
8
+ * - AdapterRegistry: Central registry for adapter management
9
+ * - AdapterRouter: Intelligent adapter selection with guard-rails
10
+ * - IAdapter: Common interface for all adapters
8
11
  *
9
12
  * @module adapters
10
13
  */
@@ -20,6 +23,30 @@ export {
20
23
  } from './BaseAdapter';
21
24
  export { BasicAdapter, BasicPayParams, BasicPayResult } from './BasicAdapter';
22
25
  export { StandardAdapter, StandardTransactionParams } from './StandardAdapter';
26
+ export { X402Adapter, X402AdapterConfig, FetchFunction } from './X402Adapter';
27
+ export { AdapterRegistry } from './AdapterRegistry';
28
+ export { AdapterRouter, AdapterSelectionResult } from './AdapterRouter';
29
+ export {
30
+ IAdapter,
31
+ TransactionStatus,
32
+ AdapterTransactionState,
33
+ isAdapter,
34
+ } from './IAdapter';
23
35
 
24
36
  // Re-export runtime interface for convenience
25
37
  export { IACTPRuntime, CreateTransactionParams } from '../runtime/IACTPRuntime';
38
+
39
+ // Re-export adapter types for convenience
40
+ export {
41
+ AdapterMetadata,
42
+ PaymentMetadata,
43
+ PaymentIdentity,
44
+ UnifiedPayParams,
45
+ UnifiedPayResult,
46
+ InitialTransactionState,
47
+ AdapterMetadataSchema,
48
+ PaymentMetadataSchema,
49
+ UnifiedPayParamsSchema,
50
+ validatePayParams,
51
+ safeValidatePayParams,
52
+ } from '../types/adapter';