@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,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
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -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';
|