@elizaos/plugin-x402 2.0.0-alpha.5 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +57 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30914 -1844
- package/dist/index.js.map +114 -21
- package/dist/payment-config.d.ts +256 -0
- package/dist/payment-config.d.ts.map +1 -0
- package/dist/payment-wrapper.d.ts +42 -0
- package/dist/payment-wrapper.d.ts.map +1 -0
- package/dist/startup-validator.d.ts +28 -0
- package/dist/startup-validator.d.ts.map +1 -0
- package/dist/types.d.ts +158 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/x402-facilitator-binding.d.ts +9 -0
- package/dist/x402-facilitator-binding.d.ts.map +1 -0
- package/dist/x402-replay-durable.d.ts +30 -0
- package/dist/x402-replay-durable.d.ts.map +1 -0
- package/dist/x402-replay-guard.d.ts +28 -0
- package/dist/x402-replay-guard.d.ts.map +1 -0
- package/dist/x402-replay-keys.d.ts +21 -0
- package/dist/x402-replay-keys.d.ts.map +1 -0
- package/dist/x402-resolve.d.ts +6 -0
- package/dist/x402-resolve.d.ts.map +1 -0
- package/dist/x402-standard-payment.d.ts +130 -0
- package/dist/x402-standard-payment.d.ts.map +1 -0
- package/dist/x402-types.d.ts +130 -0
- package/dist/x402-types.d.ts.map +1 -0
- package/package.json +43 -94
- package/src/index.ts +113 -0
- package/src/payment-config.ts +737 -0
- package/src/payment-wrapper.ts +1991 -0
- package/src/startup-validator.ts +349 -0
- package/src/types.ts +177 -0
- package/src/x402-facilitator-binding.ts +104 -0
- package/src/x402-replay-durable.ts +320 -0
- package/src/x402-replay-guard.ts +165 -0
- package/src/x402-replay-keys.ts +151 -0
- package/src/x402-resolve.ts +43 -0
- package/src/x402-standard-payment.ts +519 -0
- package/src/x402-types.ts +376 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402scan Validation Schema Types
|
|
3
|
+
* Stricter schema required for listing on x402scan
|
|
4
|
+
* Allows UI-based resource invocation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Field definition for input/output schema
|
|
9
|
+
*/
|
|
10
|
+
export type FieldDef = {
|
|
11
|
+
type?: string;
|
|
12
|
+
required?: boolean | string[];
|
|
13
|
+
description?: string;
|
|
14
|
+
enum?: string[];
|
|
15
|
+
properties?: Record<string, FieldDef>; // for nested objects
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* JSON Schema type for API output
|
|
20
|
+
*/
|
|
21
|
+
export type OutputSchemaType = {
|
|
22
|
+
type?: "object" | "array" | "string" | "number" | "boolean" | "null";
|
|
23
|
+
description?: string;
|
|
24
|
+
properties?: Record<string, FieldDef>;
|
|
25
|
+
items?: FieldDef;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Output schema describing input and output expectations for the paid endpoint
|
|
30
|
+
*/
|
|
31
|
+
export type OutputSchema = {
|
|
32
|
+
input: {
|
|
33
|
+
type: "http";
|
|
34
|
+
method: "GET" | "POST";
|
|
35
|
+
bodyType?: "json" | "form-data" | "multipart-form-data" | "text" | "binary";
|
|
36
|
+
pathParams?: Record<string, FieldDef>;
|
|
37
|
+
queryParams?: Record<string, FieldDef>;
|
|
38
|
+
bodyFields?: Record<string, FieldDef>;
|
|
39
|
+
headerFields?: Record<string, FieldDef>;
|
|
40
|
+
};
|
|
41
|
+
output?: OutputSchemaType;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Valid x402scan network types (as per their API specification)
|
|
46
|
+
*/
|
|
47
|
+
export type X402ScanNetwork =
|
|
48
|
+
| "base-sepolia"
|
|
49
|
+
| "base"
|
|
50
|
+
| "avalanche-fuji"
|
|
51
|
+
| "avalanche"
|
|
52
|
+
| "iotex"
|
|
53
|
+
| "solana-devnet"
|
|
54
|
+
| "solana"
|
|
55
|
+
| "sei"
|
|
56
|
+
| "sei-testnet"
|
|
57
|
+
| "polygon"
|
|
58
|
+
| "polygon-amoy"
|
|
59
|
+
| "bsc"
|
|
60
|
+
| "bsc-testnet"
|
|
61
|
+
| "peaq";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* EIP-712 domain information for EVM chains
|
|
65
|
+
*/
|
|
66
|
+
export type EIP712DomainInfo = {
|
|
67
|
+
name: string;
|
|
68
|
+
version: string;
|
|
69
|
+
chainId: number;
|
|
70
|
+
verifyingContract: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extra metadata for payment configuration
|
|
75
|
+
*/
|
|
76
|
+
export type PaymentExtraMetadata = {
|
|
77
|
+
priceInCents: number;
|
|
78
|
+
priceUSD: string;
|
|
79
|
+
symbol: string;
|
|
80
|
+
paymentConfig: string;
|
|
81
|
+
expiresIn: number;
|
|
82
|
+
name?: string;
|
|
83
|
+
version?: string;
|
|
84
|
+
eip712Domain?: EIP712DomainInfo;
|
|
85
|
+
[key: string]: string | number | EIP712DomainInfo | undefined;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Accepts object defining payment terms for a resource
|
|
90
|
+
*/
|
|
91
|
+
export type Accepts = {
|
|
92
|
+
scheme: "exact";
|
|
93
|
+
network: X402ScanNetwork;
|
|
94
|
+
maxAmountRequired: string;
|
|
95
|
+
resource: string; // Must be a full URL (https://...)
|
|
96
|
+
description: string;
|
|
97
|
+
mimeType: string;
|
|
98
|
+
payTo: string; // Wallet address - must be valid for the network
|
|
99
|
+
maxTimeoutSeconds: number;
|
|
100
|
+
asset: string;
|
|
101
|
+
|
|
102
|
+
// Optional schema describing the input and output expectations
|
|
103
|
+
outputSchema?: OutputSchema;
|
|
104
|
+
|
|
105
|
+
// Optional additional custom data
|
|
106
|
+
extra?: PaymentExtraMetadata;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* X402 Response structure
|
|
111
|
+
*/
|
|
112
|
+
export type X402Response = {
|
|
113
|
+
x402Version: number;
|
|
114
|
+
error?: string;
|
|
115
|
+
accepts?: Array<Accepts>;
|
|
116
|
+
payer?: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validation result type
|
|
121
|
+
*/
|
|
122
|
+
export type ValidationResult = {
|
|
123
|
+
valid: boolean;
|
|
124
|
+
errors: string[];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Valid x402scan networks
|
|
129
|
+
*/
|
|
130
|
+
const VALID_NETWORKS: X402ScanNetwork[] = [
|
|
131
|
+
"base-sepolia",
|
|
132
|
+
"base",
|
|
133
|
+
"avalanche-fuji",
|
|
134
|
+
"avalanche",
|
|
135
|
+
"iotex",
|
|
136
|
+
"solana-devnet",
|
|
137
|
+
"solana",
|
|
138
|
+
"sei",
|
|
139
|
+
"sei-testnet",
|
|
140
|
+
"polygon",
|
|
141
|
+
"polygon-amoy",
|
|
142
|
+
"bsc",
|
|
143
|
+
"bsc-testnet",
|
|
144
|
+
"peaq",
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validate URL format
|
|
149
|
+
*/
|
|
150
|
+
function isValidUrl(url: string): boolean {
|
|
151
|
+
try {
|
|
152
|
+
const parsed = new URL(url);
|
|
153
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate wallet address format based on network
|
|
161
|
+
*/
|
|
162
|
+
function isValidWalletAddress(
|
|
163
|
+
address: string,
|
|
164
|
+
network: X402ScanNetwork,
|
|
165
|
+
): boolean {
|
|
166
|
+
if (!address || typeof address !== "string") return false;
|
|
167
|
+
|
|
168
|
+
// Solana addresses are base58 encoded, typically 32-44 characters
|
|
169
|
+
if (network.includes("solana")) {
|
|
170
|
+
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// EVM-compatible chains use 0x addresses
|
|
174
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Validate that an Accepts object conforms to the x402scan schema
|
|
179
|
+
*/
|
|
180
|
+
export function validateAccepts(accepts: Partial<Accepts>): ValidationResult {
|
|
181
|
+
const errors: string[] = [];
|
|
182
|
+
|
|
183
|
+
// Required fields
|
|
184
|
+
if (accepts.scheme !== "exact") {
|
|
185
|
+
errors.push('scheme must be "exact"');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (
|
|
189
|
+
!accepts.network ||
|
|
190
|
+
!VALID_NETWORKS.includes(accepts.network as X402ScanNetwork)
|
|
191
|
+
) {
|
|
192
|
+
errors.push(`network must be one of: ${VALID_NETWORKS.join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
!accepts.maxAmountRequired ||
|
|
197
|
+
typeof accepts.maxAmountRequired !== "string"
|
|
198
|
+
) {
|
|
199
|
+
errors.push("maxAmountRequired is required and must be a string");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!accepts.resource || typeof accepts.resource !== "string") {
|
|
203
|
+
errors.push("resource is required and must be a string (full URL)");
|
|
204
|
+
} else if (!isValidUrl(accepts.resource)) {
|
|
205
|
+
errors.push(
|
|
206
|
+
"resource must be a valid URL (must start with http:// or https://)",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!accepts.description || typeof accepts.description !== "string") {
|
|
211
|
+
errors.push("description is required and must be a string");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!accepts.mimeType || typeof accepts.mimeType !== "string") {
|
|
215
|
+
errors.push(
|
|
216
|
+
'mimeType is required and must be a string (e.g., "application/json")',
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!accepts.payTo || typeof accepts.payTo !== "string") {
|
|
221
|
+
errors.push("payTo is required and must be a string (wallet address)");
|
|
222
|
+
} else if (
|
|
223
|
+
accepts.network &&
|
|
224
|
+
!isValidWalletAddress(accepts.payTo, accepts.network as X402ScanNetwork)
|
|
225
|
+
) {
|
|
226
|
+
errors.push(
|
|
227
|
+
`payTo must be a valid wallet address for network ${accepts.network}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (
|
|
232
|
+
!accepts.maxTimeoutSeconds ||
|
|
233
|
+
typeof accepts.maxTimeoutSeconds !== "number"
|
|
234
|
+
) {
|
|
235
|
+
errors.push("maxTimeoutSeconds is required and must be a number");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!accepts.asset || typeof accepts.asset !== "string") {
|
|
239
|
+
errors.push('asset is required and must be a string (e.g., "USDC", "ETH")');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Validate outputSchema if present
|
|
243
|
+
if (accepts.outputSchema) {
|
|
244
|
+
const schema = accepts.outputSchema;
|
|
245
|
+
|
|
246
|
+
if (schema.input.type !== "http") {
|
|
247
|
+
errors.push('outputSchema.input.type must be "http"');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
!schema.input.method ||
|
|
252
|
+
!["GET", "POST"].includes(schema.input.method)
|
|
253
|
+
) {
|
|
254
|
+
errors.push('outputSchema.input.method must be "GET" or "POST"');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (schema.input.bodyType) {
|
|
258
|
+
const validBodyTypes = [
|
|
259
|
+
"json",
|
|
260
|
+
"form-data",
|
|
261
|
+
"multipart-form-data",
|
|
262
|
+
"text",
|
|
263
|
+
"binary",
|
|
264
|
+
];
|
|
265
|
+
if (!validBodyTypes.includes(schema.input.bodyType)) {
|
|
266
|
+
errors.push(
|
|
267
|
+
`outputSchema.input.bodyType must be one of: ${validBodyTypes.join(", ")}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
valid: errors.length === 0,
|
|
275
|
+
errors,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Validate that an X402Response conforms to the x402scan schema
|
|
281
|
+
*/
|
|
282
|
+
export function validateX402Response(
|
|
283
|
+
response: Partial<X402Response>,
|
|
284
|
+
): ValidationResult {
|
|
285
|
+
const errors: string[] = [];
|
|
286
|
+
|
|
287
|
+
// x402Version is required
|
|
288
|
+
if (typeof response.x402Version !== "number") {
|
|
289
|
+
errors.push("x402Version is required and must be a number");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// If accepts is provided, validate each entry
|
|
293
|
+
if (response.accepts) {
|
|
294
|
+
if (!Array.isArray(response.accepts)) {
|
|
295
|
+
errors.push("accepts must be an array");
|
|
296
|
+
} else {
|
|
297
|
+
response.accepts.forEach((accepts, index) => {
|
|
298
|
+
const validation = validateAccepts(accepts);
|
|
299
|
+
if (!validation.valid) {
|
|
300
|
+
errors.push(`accepts[${index}]: ${validation.errors.join(", ")}`);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
valid: errors.length === 0,
|
|
308
|
+
errors,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Create a validated Accepts object with sensible defaults
|
|
314
|
+
*/
|
|
315
|
+
export function createAccepts(params: {
|
|
316
|
+
network: X402ScanNetwork;
|
|
317
|
+
maxAmountRequired: string;
|
|
318
|
+
resource: string;
|
|
319
|
+
description: string;
|
|
320
|
+
payTo: string;
|
|
321
|
+
asset: string;
|
|
322
|
+
mimeType?: string;
|
|
323
|
+
maxTimeoutSeconds?: number;
|
|
324
|
+
outputSchema?: OutputSchema;
|
|
325
|
+
extra?: PaymentExtraMetadata;
|
|
326
|
+
}): Accepts {
|
|
327
|
+
const accepts: Accepts = {
|
|
328
|
+
scheme: "exact",
|
|
329
|
+
network: params.network,
|
|
330
|
+
maxAmountRequired: params.maxAmountRequired,
|
|
331
|
+
resource: params.resource,
|
|
332
|
+
description: params.description,
|
|
333
|
+
mimeType: params.mimeType || "application/json",
|
|
334
|
+
payTo: params.payTo,
|
|
335
|
+
maxTimeoutSeconds: params.maxTimeoutSeconds || 300, // 5 minutes default
|
|
336
|
+
asset: params.asset,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (params.outputSchema) {
|
|
340
|
+
accepts.outputSchema = params.outputSchema;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (params.extra) {
|
|
344
|
+
accepts.extra = params.extra;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Validate before returning
|
|
348
|
+
const validation = validateAccepts(accepts);
|
|
349
|
+
if (!validation.valid) {
|
|
350
|
+
throw new Error(`Invalid Accepts object: ${validation.errors.join(", ")}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return accepts;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create a validated X402Response
|
|
358
|
+
*/
|
|
359
|
+
export function createX402Response(params: {
|
|
360
|
+
accepts?: Accepts[];
|
|
361
|
+
error?: string;
|
|
362
|
+
payer?: string;
|
|
363
|
+
}): X402Response {
|
|
364
|
+
const response: X402Response = {
|
|
365
|
+
x402Version: 1,
|
|
366
|
+
...params,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Validate before returning
|
|
370
|
+
const validation = validateX402Response(response);
|
|
371
|
+
if (!validation.valid) {
|
|
372
|
+
throw new Error(`Invalid X402Response: ${validation.errors.join(", ")}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return response;
|
|
376
|
+
}
|