@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,417 @@
1
+ /**
2
+ * AdapterRouter - Intelligent adapter selection with guard-rails.
3
+ *
4
+ * The router selects the best adapter for each payment based on:
5
+ * - Explicit adapter preference
6
+ * - Required capabilities (escrow, disputes, identity)
7
+ * - Recipient type (address vs HTTP endpoint)
8
+ * - Adapter priority
9
+ *
10
+ * SECURITY: All parameters are validated before selection.
11
+ * SECURITY: All adapters must enforce explicit release (no auto-settle).
12
+ *
13
+ * @module adapters/AdapterRouter
14
+ */
15
+
16
+ import { AdapterRegistry } from './AdapterRegistry';
17
+ import { IAdapter } from './IAdapter';
18
+ import {
19
+ UnifiedPayParams,
20
+ UnifiedPayParamsSchema,
21
+ safeValidatePayParams,
22
+ } from '../types/adapter';
23
+ import { ValidationError } from './BaseAdapter';
24
+ import { ERC8004Bridge } from '../erc8004/ERC8004Bridge';
25
+
26
+ /**
27
+ * Result of adapter selection with potential ERC-8004 resolution.
28
+ */
29
+ export interface AdapterSelectionResult {
30
+ /** Selected adapter */
31
+ adapter: IAdapter;
32
+
33
+ /** Resolved payment parameters (with wallet instead of agentId) */
34
+ resolvedParams: UnifiedPayParams;
35
+
36
+ /** Whether an ERC-8004 agent ID was resolved */
37
+ wasAgentIdResolved: boolean;
38
+ }
39
+
40
+ /**
41
+ * AdapterRouter - Intelligent adapter selection with guard-rails.
42
+ *
43
+ * Selection logic (in order):
44
+ * 1. Validate params (throws if invalid)
45
+ * 2. Explicit adapter requested -> use it
46
+ * 3. Escrow/dispute required -> StandardAdapter
47
+ * 4. HTTP endpoint + no escrow -> X402Adapter (when available)
48
+ * 5. ERC-8004 identity -> ERC8004Adapter (when available)
49
+ * 6. Default -> First adapter that can handle (by priority)
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const registry = new AdapterRegistry();
54
+ * registry.register(basicAdapter);
55
+ * registry.register(standardAdapter);
56
+ *
57
+ * const router = new AdapterRouter(registry);
58
+ *
59
+ * // Auto-select best adapter
60
+ * const adapter = router.select({ to: '0x...', amount: '100' });
61
+ *
62
+ * // Explicit adapter request
63
+ * const adapter = router.select({
64
+ * to: '0x...',
65
+ * amount: '100',
66
+ * metadata: { preferredAdapter: 'standard' }
67
+ * });
68
+ * ```
69
+ */
70
+ export class AdapterRouter {
71
+ private erc8004Bridge?: ERC8004Bridge;
72
+
73
+ /**
74
+ * Creates a new AdapterRouter instance.
75
+ *
76
+ * @param registry - AdapterRegistry containing available adapters
77
+ * @param erc8004Bridge - Optional ERC-8004 bridge for agent ID resolution
78
+ */
79
+ constructor(
80
+ private registry: AdapterRegistry,
81
+ erc8004Bridge?: ERC8004Bridge
82
+ ) {
83
+ this.erc8004Bridge = erc8004Bridge;
84
+ }
85
+
86
+ /**
87
+ * Set the ERC-8004 bridge for agent ID resolution.
88
+ *
89
+ * @param bridge - ERC8004Bridge instance
90
+ */
91
+ setERC8004Bridge(bridge: ERC8004Bridge): void {
92
+ this.erc8004Bridge = bridge;
93
+ }
94
+
95
+ /**
96
+ * Select the best adapter for the given payment parameters.
97
+ *
98
+ * @param params - Unified payment parameters
99
+ * @returns The selected adapter
100
+ * @throws {ValidationError} If params are invalid
101
+ * @throws {Error} If no suitable adapter found
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const adapter = router.select({
106
+ * to: '0xProvider...',
107
+ * amount: '100',
108
+ * });
109
+ * const result = await adapter.pay(params);
110
+ * ```
111
+ */
112
+ select(params: UnifiedPayParams): IAdapter {
113
+ // GUARD-RAIL: Validate all params first
114
+ this.validateParams(params);
115
+
116
+ const metadata = params.metadata || {};
117
+
118
+ // 1. Explicit adapter requested
119
+ if (metadata.preferredAdapter) {
120
+ const adapter = this.registry.get(metadata.preferredAdapter);
121
+ if (!adapter) {
122
+ throw new Error(
123
+ `Preferred adapter '${metadata.preferredAdapter}' not found. ` +
124
+ `Available adapters: ${this.registry.getIds().join(', ') || 'none'}`
125
+ );
126
+ }
127
+ // Verify adapter can handle these params
128
+ adapter.validate(params);
129
+ return adapter;
130
+ }
131
+
132
+ // 2. Escrow/dispute required -> STRICT enforcement
133
+ if (metadata.requiresEscrow || metadata.requiresDispute) {
134
+ // First try standard adapter
135
+ const standard = this.registry.get('standard');
136
+ if (standard && standard.canHandle(params)) {
137
+ return standard;
138
+ }
139
+
140
+ // Find any adapter that meets the capability requirements
141
+ const compatibleAdapters = this.registry.getByPriority().filter((adapter) => {
142
+ const meta = adapter.metadata;
143
+ const meetsEscrow = !metadata.requiresEscrow || meta.usesEscrow;
144
+ const meetsDispute = !metadata.requiresDispute || meta.supportsDisputes;
145
+ return meetsEscrow && meetsDispute && adapter.canHandle(params);
146
+ });
147
+
148
+ if (compatibleAdapters.length > 0) {
149
+ return compatibleAdapters[0];
150
+ }
151
+
152
+ // STRICT: No compatible adapter found - throw error instead of falling through
153
+ const requirements = [];
154
+ if (metadata.requiresEscrow) requirements.push('escrow');
155
+ if (metadata.requiresDispute) requirements.push('dispute resolution');
156
+ throw new Error(
157
+ `No adapter found that supports required capabilities: ${requirements.join(', ')}. ` +
158
+ `Available adapters: ${this.registry.getIds().join(', ') || 'none'}`
159
+ );
160
+ }
161
+
162
+ // 3. HTTP endpoint -> x402 (when registered)
163
+ // NOTE: X402Adapter is ATOMIC (usesEscrow: false) - instant payment, no escrow.
164
+ // Route HTTPS endpoints to x402 for fire-and-forget API payments.
165
+ // If caller needs escrow protection, they should use ACTP with address, not URL.
166
+ if (this.isHttpEndpoint(params.to)) {
167
+ const x402 = this.registry.get('x402');
168
+ if (x402 && x402.canHandle(params)) {
169
+ return x402;
170
+ }
171
+ // HTTP endpoints without x402 adapter will fall through
172
+ // and likely fail with basic/standard adapters
173
+ }
174
+
175
+ // 4. ERC-8004 identity -> erc8004 (when registered)
176
+ if (metadata.identity?.type === 'erc8004') {
177
+ const erc8004 = this.registry.get('erc8004');
178
+ if (erc8004 && erc8004.canHandle(params)) {
179
+ return erc8004;
180
+ }
181
+ }
182
+
183
+ // 5. Find first adapter that can handle it (by priority)
184
+ for (const adapter of this.registry.getByPriority()) {
185
+ if (adapter.canHandle(params)) {
186
+ return adapter;
187
+ }
188
+ }
189
+
190
+ // 6. Default to basic as last resort
191
+ const basic = this.registry.get('basic');
192
+ if (basic) {
193
+ // Try to use basic even if canHandle returned false
194
+ // Let it fail with a proper error message during pay()
195
+ return basic;
196
+ }
197
+
198
+ throw new Error(
199
+ `No suitable adapter found for params. ` +
200
+ `Available adapters: ${this.registry.getIds().join(', ') || 'none'}`
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Validate payment parameters with Zod schema.
206
+ *
207
+ * GUARD-RAIL: Performs strict validation before any adapter selection.
208
+ *
209
+ * @param params - Parameters to validate
210
+ * @throws {ValidationError} If params are invalid
211
+ */
212
+ private validateParams(params: UnifiedPayParams): void {
213
+ const result = safeValidatePayParams(params);
214
+
215
+ if (!result.success) {
216
+ const issues = result.error.issues
217
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
218
+ .join('; ');
219
+ throw new ValidationError(`Invalid payment params: ${issues}`);
220
+ }
221
+
222
+ // Additional security checks
223
+
224
+ // Check for path traversal attempts in 'to' field
225
+ if (typeof params.to === 'string') {
226
+ if (params.to.includes('..')) {
227
+ throw new ValidationError(
228
+ 'Invalid recipient: path traversal characters not allowed'
229
+ );
230
+ }
231
+
232
+ // Check for script injection attempts
233
+ if (params.to.includes('<') || params.to.includes('>')) {
234
+ throw new ValidationError(
235
+ 'Invalid recipient: HTML/script characters not allowed'
236
+ );
237
+ }
238
+
239
+ // Check for null bytes
240
+ if (params.to.includes('\0')) {
241
+ throw new ValidationError(
242
+ 'Invalid recipient: null bytes not allowed'
243
+ );
244
+ }
245
+ }
246
+
247
+ // Validate description if provided
248
+ if (params.description) {
249
+ if (params.description.length > 1000) {
250
+ throw new ValidationError(
251
+ 'Description too long: maximum 1000 characters'
252
+ );
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Check if a string is an HTTP/HTTPS endpoint.
259
+ *
260
+ * @param to - Recipient string to check
261
+ * @returns True if it's an HTTP endpoint
262
+ */
263
+ private isHttpEndpoint(to: string): boolean {
264
+ try {
265
+ const url = new URL(to);
266
+ return url.protocol === 'http:' || url.protocol === 'https:';
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Get all adapters that can handle the given params.
274
+ *
275
+ * Useful for debugging or letting users choose from multiple options.
276
+ *
277
+ * @param params - Payment parameters
278
+ * @returns Array of adapters that can handle params
279
+ */
280
+ getCompatibleAdapters(params: UnifiedPayParams): IAdapter[] {
281
+ this.validateParams(params);
282
+ return this.registry.getAll().filter((adapter) => {
283
+ try {
284
+ return adapter.canHandle(params);
285
+ } catch {
286
+ return false;
287
+ }
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Check if any adapter can handle the given params.
293
+ *
294
+ * @param params - Payment parameters
295
+ * @returns True if at least one adapter can handle
296
+ */
297
+ canHandle(params: UnifiedPayParams): boolean {
298
+ try {
299
+ this.validateParams(params);
300
+ return this.getCompatibleAdapters(params).length > 0;
301
+ } catch {
302
+ return false;
303
+ }
304
+ }
305
+
306
+ // ==========================================================================
307
+ // ERC-8004 Agent ID Resolution
308
+ // ==========================================================================
309
+
310
+ /**
311
+ * Select adapter AND resolve ERC-8004 agent IDs.
312
+ *
313
+ * This is the recommended method for payment flows. It:
314
+ * 1. Checks if `to` is an ERC-8004 agent ID (numeric string)
315
+ * 2. If so, resolves it to a wallet address via ERC8004Bridge
316
+ * 3. Stores the original agentId in erc8004AgentId field
317
+ * 4. Selects the appropriate adapter
318
+ *
319
+ * @param params - Unified payment parameters
320
+ * @returns Selection result with resolved params
321
+ * @throws {ValidationError} If params invalid or agent not found
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const { adapter, resolvedParams } = await router.selectAndResolve({
326
+ * to: '12345', // ERC-8004 agent ID
327
+ * amount: '100',
328
+ * });
329
+ *
330
+ * // resolvedParams.to is now the wallet address
331
+ * // resolvedParams.erc8004AgentId is '12345'
332
+ * const result = await adapter.pay(resolvedParams);
333
+ * ```
334
+ */
335
+ async selectAndResolve(params: UnifiedPayParams): Promise<AdapterSelectionResult> {
336
+ // Check if 'to' is an ERC-8004 agent ID
337
+ if (this.isERC8004AgentId(params.to)) {
338
+ if (!this.erc8004Bridge) {
339
+ throw new ValidationError(
340
+ `Cannot resolve ERC-8004 agent ID '${params.to}': ` +
341
+ 'ERC-8004 resolution requires testnet or mainnet mode. ' +
342
+ 'Use a wallet address (0x...) in mock mode, or switch to testnet/mainnet.'
343
+ );
344
+ }
345
+
346
+ try {
347
+ // Resolve agent ID to wallet address
348
+ const wallet = await this.erc8004Bridge.getAgentWallet(params.to);
349
+
350
+ // Create resolved params with wallet and stored agentId
351
+ const resolvedParams: UnifiedPayParams = {
352
+ ...params,
353
+ to: wallet,
354
+ erc8004AgentId: params.to,
355
+ };
356
+
357
+ // Select adapter for resolved params
358
+ const adapter = this.select(resolvedParams);
359
+
360
+ return {
361
+ adapter,
362
+ resolvedParams,
363
+ wasAgentIdResolved: true,
364
+ };
365
+ } catch (error) {
366
+ // Re-throw with clearer message
367
+ const errorMsg = error instanceof Error ? error.message : String(error);
368
+ throw new ValidationError(
369
+ `Failed to resolve ERC-8004 agent '${params.to}': ${errorMsg}`
370
+ );
371
+ }
372
+ }
373
+
374
+ // Not an agent ID - proceed normally
375
+ const adapter = this.select(params);
376
+ return {
377
+ adapter,
378
+ resolvedParams: params,
379
+ wasAgentIdResolved: false,
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Check if a string looks like an ERC-8004 agent ID.
385
+ *
386
+ * Agent IDs are numeric strings (uint256) that are:
387
+ * - NOT Ethereum addresses (0x-prefixed)
388
+ * - NOT URLs (http/https)
389
+ * - Valid as BigInt in range [0, 2^256)
390
+ *
391
+ * @param to - Recipient string to check
392
+ * @returns True if it looks like an agent ID
393
+ */
394
+ isERC8004AgentId(to: string): boolean {
395
+ if (!to || typeof to !== 'string') {
396
+ return false;
397
+ }
398
+
399
+ // Not an Ethereum address
400
+ if (to.startsWith('0x')) {
401
+ return false;
402
+ }
403
+
404
+ // Not a URL
405
+ if (to.includes('://') || to.startsWith('http')) {
406
+ return false;
407
+ }
408
+
409
+ // Must be a valid uint256
410
+ try {
411
+ const bn = BigInt(to);
412
+ return bn >= 0n && bn < 2n ** 256n;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+ }
@@ -470,4 +470,29 @@ export abstract class BaseAdapter {
470
470
  const formattedDecimal = roundedCents.toString().padStart(2, '0');
471
471
  return `${wholePart}.${formattedDecimal} USDC`;
472
472
  }
473
+
474
+ /**
475
+ * Encode dispute window as ABI-encoded proof for DELIVERED transition.
476
+ *
477
+ * This helper centralizes proof encoding to prevent drift between
478
+ * adapters and ensures consistency with on-chain expectations.
479
+ *
480
+ * @param disputeWindowSeconds - Dispute window in seconds
481
+ * @returns ABI-encoded bytes32 proof
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * // Encode 2-hour dispute window
486
+ * const proof = this.encodeDisputeWindowProof(7200);
487
+ * await runtime.transitionState(txId, 'DELIVERED', proof);
488
+ * ```
489
+ */
490
+ protected encodeDisputeWindowProof(disputeWindowSeconds: number): string {
491
+ // Lazy import to avoid circular dependency issues
492
+ const { ethers } = require('ethers');
493
+ return ethers.AbiCoder.defaultAbiCoder().encode(
494
+ ['uint256'],
495
+ [disputeWindowSeconds]
496
+ );
497
+ }
473
498
  }
@@ -16,6 +16,12 @@
16
16
  import { BaseAdapter, ValidationError } from './BaseAdapter';
17
17
  import { IACTPRuntime } from '../runtime/IACTPRuntime';
18
18
  import { EASHelper } from '../protocol/EASHelper';
19
+ import { IAdapter, TransactionStatus } from './IAdapter';
20
+ import {
21
+ AdapterMetadata,
22
+ UnifiedPayParams,
23
+ UnifiedPayResult,
24
+ } from '../types/adapter';
19
25
 
20
26
  /**
21
27
  * Parameters for creating a simple payment.
@@ -70,6 +76,8 @@ export interface BasicPayResult {
70
76
  *
71
77
  * All complexity is hidden behind smart defaults.
72
78
  *
79
+ * Implements IAdapter for router integration.
80
+ *
73
81
  * @example
74
82
  * ```typescript
75
83
  * const client = await ACTPClient.create({ mode: 'mock' });
@@ -89,7 +97,19 @@ export interface BasicPayResult {
89
97
  * }
90
98
  * ```
91
99
  */
92
- export class BasicAdapter extends BaseAdapter {
100
+ export class BasicAdapter extends BaseAdapter implements IAdapter {
101
+ /**
102
+ * Adapter metadata for router selection.
103
+ */
104
+ public readonly metadata: AdapterMetadata = {
105
+ id: 'basic',
106
+ name: 'Basic Adapter',
107
+ usesEscrow: true,
108
+ supportsDisputes: true,
109
+ requiresIdentity: false,
110
+ settlementMode: 'timed', // Auto-release after dispute window
111
+ priority: 50, // Default priority
112
+ };
93
113
  /**
94
114
  * Creates a new BasicAdapter instance.
95
115
  *
@@ -123,19 +143,20 @@ export class BasicAdapter extends BaseAdapter {
123
143
  * - Cannot pay yourself
124
144
  *
125
145
  * @param params - Payment parameters
146
+ * @param agentId - Optional ERC-8004 agent ID (for reputation reporting)
126
147
  * @returns User-friendly payment result
127
148
  * @throws {ValidationError} If inputs are invalid
128
149
  *
129
150
  * @example
130
151
  * ```typescript
131
- * const result = await adapter.pay({
152
+ * const result = await adapter.payBasic({
132
153
  * to: '0xProvider123',
133
154
  * amount: '100.50',
134
155
  * deadline: '+7d', // Optional: 7 days from now
135
156
  * });
136
157
  * ```
137
158
  */
138
- async pay(params: BasicPayParams): Promise<BasicPayResult> {
159
+ async payBasic(params: BasicPayParams, agentId?: string): Promise<BasicPayResult> {
139
160
  // Validate and parse inputs
140
161
  const provider = this.validateAddress(params.to, 'to');
141
162
  const amount = this.parseAmount(params.amount);
@@ -173,6 +194,7 @@ export class BasicAdapter extends BaseAdapter {
173
194
  amount: amount.toString(),
174
195
  deadline,
175
196
  disputeWindow,
197
+ agentId, // ERC-8004 agent ID for reputation reporting
176
198
  });
177
199
 
178
200
  // Link escrow (auto-transitions to COMMITTED)
@@ -239,4 +261,178 @@ export class BasicAdapter extends BaseAdapter {
239
261
  canDispute: tx.state === 'DELIVERED' && tx.completedAt !== null && tx.completedAt + tx.disputeWindow > now,
240
262
  };
241
263
  }
264
+
265
+ // ==========================================================================
266
+ // IAdapter Implementation
267
+ // ==========================================================================
268
+
269
+ /**
270
+ * Execute payment through this adapter.
271
+ *
272
+ * This is the IAdapter-compatible pay() method that returns UnifiedPayResult.
273
+ * For the legacy BasicPayResult API, use payBasic().
274
+ *
275
+ * @param params - Unified payment parameters
276
+ * @returns Promise resolving to unified payment result
277
+ */
278
+ async pay(params: UnifiedPayParams): Promise<UnifiedPayResult> {
279
+ // Validate using IAdapter validate()
280
+ this.validate(params);
281
+
282
+ // Map to BasicPayParams
283
+ const basicParams: BasicPayParams = {
284
+ to: params.to,
285
+ amount: params.amount,
286
+ deadline: params.deadline,
287
+ disputeWindow: params.disputeWindow,
288
+ };
289
+
290
+ // Call existing payBasic() with optional agentId
291
+ const result = await this.payBasic(basicParams, params.erc8004AgentId);
292
+
293
+ // Map to UnifiedPayResult
294
+ return {
295
+ txId: result.txId,
296
+ escrowId: result.txId, // In ACTP, escrowId === txId
297
+ adapter: this.metadata.id,
298
+ state: 'COMMITTED',
299
+ success: true,
300
+ amount: result.amount,
301
+ releaseRequired: true, // ACTP requires explicit release()
302
+ provider: result.provider,
303
+ requester: result.requester,
304
+ deadline: result.deadline,
305
+ erc8004AgentId: params.erc8004AgentId,
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Check if this adapter can handle the given parameters.
311
+ *
312
+ * BasicAdapter can handle any Ethereum address recipient.
313
+ *
314
+ * @param params - Payment parameters to check
315
+ * @returns True if params have a valid Ethereum address
316
+ */
317
+ canHandle(params: UnifiedPayParams): boolean {
318
+ // BasicAdapter handles Ethereum addresses only
319
+ if (typeof params.to !== 'string') {
320
+ return false;
321
+ }
322
+
323
+ // Check if it's an Ethereum address (0x-prefixed hex)
324
+ return /^0x[a-fA-F0-9]{40}$/.test(params.to);
325
+ }
326
+
327
+ /**
328
+ * Validate parameters before execution.
329
+ *
330
+ * @param params - Parameters to validate
331
+ * @throws {ValidationError} If params are invalid
332
+ */
333
+ validate(params: UnifiedPayParams): void {
334
+ // Validate address
335
+ this.validateAddress(params.to, 'to');
336
+
337
+ // Validate amount (will throw if invalid)
338
+ this.parseAmount(params.amount);
339
+
340
+ // Validate deadline if provided
341
+ if (params.deadline !== undefined) {
342
+ this.parseDeadline(params.deadline);
343
+ }
344
+
345
+ // Validate dispute window if provided
346
+ if (params.disputeWindow !== undefined) {
347
+ this.validateDisputeWindow(params.disputeWindow);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Get transaction status by ID.
353
+ *
354
+ * Returns TransactionStatus with action hints.
355
+ *
356
+ * @param txId - Transaction ID
357
+ * @returns Promise resolving to transaction status
358
+ */
359
+ async getStatus(txId: string): Promise<TransactionStatus> {
360
+ const tx = await this.runtime.getTransaction(txId);
361
+
362
+ if (!tx) {
363
+ throw new Error(`Transaction ${txId} not found`);
364
+ }
365
+
366
+ const now = this.runtime.time.now();
367
+ const disputeWindowEnds = tx.completedAt
368
+ ? tx.completedAt + tx.disputeWindow
369
+ : undefined;
370
+
371
+ return {
372
+ state: tx.state as TransactionStatus['state'],
373
+ canStartWork: tx.state === 'COMMITTED',
374
+ canDeliver: tx.state === 'IN_PROGRESS',
375
+ canRelease:
376
+ tx.state === 'DELIVERED' &&
377
+ disputeWindowEnds !== undefined &&
378
+ now >= disputeWindowEnds,
379
+ canDispute:
380
+ tx.state === 'DELIVERED' &&
381
+ disputeWindowEnds !== undefined &&
382
+ now < disputeWindowEnds,
383
+ amount: this.formatAmount(tx.amount),
384
+ deadline: new Date(tx.deadline * 1000).toISOString(),
385
+ disputeWindowEnds: disputeWindowEnds
386
+ ? new Date(disputeWindowEnds * 1000).toISOString()
387
+ : undefined,
388
+ provider: tx.provider,
389
+ requester: tx.requester,
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Transition to IN_PROGRESS state (provider starts work).
395
+ *
396
+ * @param txId - Transaction ID
397
+ */
398
+ async startWork(txId: string): Promise<void> {
399
+ await this.runtime.transitionState(txId, 'IN_PROGRESS');
400
+ }
401
+
402
+ /**
403
+ * Transition to DELIVERED state (provider completes work).
404
+ *
405
+ * When no proof is provided, fetches the transaction's actual disputeWindow
406
+ * and encodes it as proof. This ensures consistency with the dispute window
407
+ * specified at transaction creation time.
408
+ *
409
+ * @param txId - Transaction ID
410
+ * @param proof - Optional delivery proof (ABI-encoded dispute window).
411
+ * If not provided, uses transaction's disputeWindow.
412
+ */
413
+ async deliver(txId: string, proof?: string): Promise<void> {
414
+ let deliveryProof = proof;
415
+
416
+ if (!deliveryProof) {
417
+ // Fetch transaction to get its actual disputeWindow
418
+ const tx = await this.runtime.getTransaction(txId);
419
+ if (!tx) {
420
+ throw new Error(`Transaction ${txId} not found`);
421
+ }
422
+ // Use transaction's disputeWindow, not a default
423
+ deliveryProof = this.encodeDisputeWindowProof(tx.disputeWindow);
424
+ }
425
+
426
+ await this.runtime.transitionState(txId, 'DELIVERED', deliveryProof);
427
+ }
428
+
429
+ /**
430
+ * Release escrow funds (EXPLICIT settlement).
431
+ *
432
+ * @param escrowId - Escrow ID (usually same as txId)
433
+ * @param attestationUID - Optional attestation UID for verification
434
+ */
435
+ async release(escrowId: string, attestationUID?: string): Promise<void> {
436
+ await this.runtime.releaseEscrow(escrowId, attestationUID);
437
+ }
242
438
  }