@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.
- package/dist/ACTPClient.d.ts +200 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +266 -2
- package/dist/ACTPClient.js.map +1 -1
- package/dist/abi/ACTPKernel.json +16 -0
- package/dist/adapters/AdapterRegistry.d.ts +140 -0
- package/dist/adapters/AdapterRegistry.d.ts.map +1 -0
- package/dist/adapters/AdapterRegistry.js +166 -0
- package/dist/adapters/AdapterRegistry.js.map +1 -0
- package/dist/adapters/AdapterRouter.d.ts +165 -0
- package/dist/adapters/AdapterRouter.d.ts.map +1 -0
- package/dist/adapters/AdapterRouter.js +350 -0
- package/dist/adapters/AdapterRouter.js.map +1 -0
- package/dist/adapters/BaseAdapter.d.ts +17 -0
- package/dist/adapters/BaseAdapter.d.ts.map +1 -1
- package/dist/adapters/BaseAdapter.js +21 -0
- package/dist/adapters/BaseAdapter.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +72 -3
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +170 -2
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/IAdapter.d.ts +230 -0
- package/dist/adapters/IAdapter.d.ts.map +1 -0
- package/dist/adapters/IAdapter.js +44 -0
- package/dist/adapters/IAdapter.js.map +1 -0
- package/dist/adapters/StandardAdapter.d.ts +70 -1
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +184 -0
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/adapters/X402Adapter.d.ts +208 -0
- package/dist/adapters/X402Adapter.d.ts.map +1 -0
- package/dist/adapters/X402Adapter.js +423 -0
- package/dist/adapters/X402Adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +19 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +184 -4
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/config/networks.js +3 -3
- package/dist/config/networks.js.map +1 -1
- package/dist/erc8004/ERC8004Bridge.d.ts +155 -0
- package/dist/erc8004/ERC8004Bridge.d.ts.map +1 -0
- package/dist/erc8004/ERC8004Bridge.js +325 -0
- package/dist/erc8004/ERC8004Bridge.js.map +1 -0
- package/dist/erc8004/ReputationReporter.d.ts +223 -0
- package/dist/erc8004/ReputationReporter.d.ts.map +1 -0
- package/dist/erc8004/ReputationReporter.js +266 -0
- package/dist/erc8004/ReputationReporter.js.map +1 -0
- package/dist/erc8004/index.d.ts +36 -0
- package/dist/erc8004/index.d.ts.map +1 -0
- package/dist/erc8004/index.js +46 -0
- package/dist/erc8004/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/protocol/ACTPKernel.d.ts +1 -1
- package/dist/protocol/ACTPKernel.d.ts.map +1 -1
- package/dist/protocol/ACTPKernel.js +16 -7
- package/dist/protocol/ACTPKernel.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +2 -0
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/runtime/IACTPRuntime.d.ts +6 -0
- package/dist/runtime/IACTPRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.d.ts +12 -0
- package/dist/runtime/MockRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.js +41 -0
- package/dist/runtime/MockRuntime.js.map +1 -1
- package/dist/runtime/types/MockState.d.ts +6 -0
- package/dist/runtime/types/MockState.d.ts.map +1 -1
- package/dist/runtime/types/MockState.js.map +1 -1
- package/dist/types/adapter.d.ts +359 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +115 -0
- package/dist/types/adapter.js.map +1 -0
- package/dist/types/erc8004.d.ts +184 -0
- package/dist/types/erc8004.d.ts.map +1 -0
- package/dist/types/erc8004.js +132 -0
- package/dist/types/erc8004.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/transaction.d.ts +12 -0
- package/dist/types/transaction.d.ts.map +1 -1
- package/dist/types/x402.d.ts +162 -0
- package/dist/types/x402.d.ts.map +1 -0
- package/dist/types/x402.js +162 -0
- package/dist/types/x402.js.map +1 -0
- package/package.json +3 -2
- package/src/ACTPClient.ts +318 -2
- package/src/abi/ACTPKernel.json +16 -0
- package/src/adapters/AdapterRegistry.ts +173 -0
- package/src/adapters/AdapterRouter.ts +417 -0
- package/src/adapters/BaseAdapter.ts +25 -0
- package/src/adapters/BasicAdapter.ts +199 -3
- package/src/adapters/IAdapter.ts +292 -0
- package/src/adapters/StandardAdapter.ts +220 -1
- package/src/adapters/X402Adapter.ts +653 -0
- package/src/adapters/index.ts +27 -0
- package/src/cli/commands/init.ts +208 -3
- package/src/config/networks.ts +3 -3
- package/src/erc8004/ERC8004Bridge.ts +461 -0
- package/src/erc8004/ReputationReporter.ts +472 -0
- package/src/erc8004/index.ts +61 -0
- package/src/index.ts +43 -0
- package/src/protocol/ACTPKernel.ts +26 -7
- package/src/runtime/BlockchainRuntime.ts +2 -0
- package/src/runtime/IACTPRuntime.ts +6 -0
- package/src/runtime/MockRuntime.ts +42 -0
- package/src/runtime/types/MockState.ts +7 -0
- package/src/types/adapter.ts +296 -0
- package/src/types/erc8004.ts +293 -0
- package/src/types/index.ts +3 -0
- package/src/types/transaction.ts +12 -0
- 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.
|
|
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
|
|
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
|
}
|