@bankofai/x402-extensions 2.6.0-beta.0
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/README.md +792 -0
- package/dist/cjs/bazaar/index.d.ts +3 -0
- package/dist/cjs/bazaar/index.js +629 -0
- package/dist/cjs/bazaar/index.js.map +1 -0
- package/dist/cjs/index-DPJQYYGe.d.ts +662 -0
- package/dist/cjs/index.d.ts +360 -0
- package/dist/cjs/index.js +1880 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/payment-identifier/index.d.ts +345 -0
- package/dist/cjs/payment-identifier/index.js +285 -0
- package/dist/cjs/payment-identifier/index.js.map +1 -0
- package/dist/cjs/sign-in-with-x/index.d.ts +1091 -0
- package/dist/cjs/sign-in-with-x/index.js +845 -0
- package/dist/cjs/sign-in-with-x/index.js.map +1 -0
- package/dist/esm/bazaar/index.d.mts +3 -0
- package/dist/esm/bazaar/index.mjs +33 -0
- package/dist/esm/bazaar/index.mjs.map +1 -0
- package/dist/esm/chunk-MTWK6ERV.mjs +777 -0
- package/dist/esm/chunk-MTWK6ERV.mjs.map +1 -0
- package/dist/esm/chunk-RERA4OZZ.mjs +233 -0
- package/dist/esm/chunk-RERA4OZZ.mjs.map +1 -0
- package/dist/esm/chunk-UHTNEDIJ.mjs +580 -0
- package/dist/esm/chunk-UHTNEDIJ.mjs.map +1 -0
- package/dist/esm/index-DPJQYYGe.d.mts +662 -0
- package/dist/esm/index.d.mts +360 -0
- package/dist/esm/index.mjs +323 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/payment-identifier/index.d.mts +345 -0
- package/dist/esm/payment-identifier/index.mjs +39 -0
- package/dist/esm/payment-identifier/index.mjs.map +1 -0
- package/dist/esm/sign-in-with-x/index.d.mts +1091 -0
- package/dist/esm/sign-in-with-x/index.mjs +71 -0
- package/dist/esm/sign-in-with-x/index.mjs.map +1 -0
- package/package.json +99 -0
package/README.md
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
# @x402/extensions
|
|
2
|
+
|
|
3
|
+
x402 Payment Protocol Extensions. This package provides optional extensions that enhance the x402 payment protocol with additional functionality.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install @x402/extensions
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Extensions are optional features that can be added to x402 payment flows. They follow a standardized `{ info, schema }` structure and are included in `PaymentRequired.extensions` and `PaymentPayload.extensions`.
|
|
14
|
+
|
|
15
|
+
This package includes:
|
|
16
|
+
- **Bazaar Discovery**: Automatic cataloging and indexing of x402-enabled resources
|
|
17
|
+
- **Sign-In-With-X (SIWx)**: CAIP-122 wallet authentication for accessing previously purchased resources
|
|
18
|
+
|
|
19
|
+
## Bazaar Discovery Extension
|
|
20
|
+
|
|
21
|
+
The Bazaar Discovery Extension enables facilitators to automatically catalog and index x402-enabled resources by following server-declared discovery instructions. This allows users to discover paid APIs and services through facilitator catalogs.
|
|
22
|
+
|
|
23
|
+
### How It Works
|
|
24
|
+
|
|
25
|
+
1. **Servers** declare discovery metadata when configuring their payment endpoints
|
|
26
|
+
2. The HTTP method is automatically inferred from the route definition (e.g., `"GET /weather"`)
|
|
27
|
+
3. **Facilitators** extract this metadata from payment requests
|
|
28
|
+
4. **Users** can browse and discover available paid resources through facilitator catalogs
|
|
29
|
+
|
|
30
|
+
### For Resource Servers
|
|
31
|
+
|
|
32
|
+
Declare endpoint discovery metadata in your payment middleware configuration. This helps facilitators understand how to call your endpoints and what they return.
|
|
33
|
+
|
|
34
|
+
> **Note:** The HTTP method is automatically inferred from the route key (e.g., `"GET /weather"` → GET method). You don't need to specify it in `declareDiscoveryExtension`.
|
|
35
|
+
|
|
36
|
+
#### Basic Example: GET Endpoint with Query Parameters
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
40
|
+
|
|
41
|
+
const resources = {
|
|
42
|
+
"GET /weather": {
|
|
43
|
+
accepts: {
|
|
44
|
+
scheme: "exact",
|
|
45
|
+
price: "$0.001",
|
|
46
|
+
network: "eip155:84532",
|
|
47
|
+
payTo: "0xYourAddress"
|
|
48
|
+
},
|
|
49
|
+
extensions: {
|
|
50
|
+
...declareDiscoveryExtension({
|
|
51
|
+
input: { city: "San Francisco" },
|
|
52
|
+
inputSchema: {
|
|
53
|
+
properties: {
|
|
54
|
+
city: { type: "string" },
|
|
55
|
+
units: { type: "string", enum: ["celsius", "fahrenheit"] }
|
|
56
|
+
},
|
|
57
|
+
required: ["city"]
|
|
58
|
+
},
|
|
59
|
+
output: {
|
|
60
|
+
example: {
|
|
61
|
+
city: "San Francisco",
|
|
62
|
+
weather: "foggy",
|
|
63
|
+
temperature: 15,
|
|
64
|
+
humidity: 85
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Example: POST Endpoint with JSON Body
|
|
74
|
+
|
|
75
|
+
For POST, PUT, and PATCH endpoints, specify `bodyType` to indicate the request body format:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
79
|
+
|
|
80
|
+
const resources = {
|
|
81
|
+
"POST /api/translate": {
|
|
82
|
+
accepts: {
|
|
83
|
+
scheme: "exact",
|
|
84
|
+
price: "$0.01",
|
|
85
|
+
network: "eip155:84532",
|
|
86
|
+
payTo: "0xYourAddress"
|
|
87
|
+
},
|
|
88
|
+
extensions: {
|
|
89
|
+
...declareDiscoveryExtension({
|
|
90
|
+
input: {
|
|
91
|
+
text: "Hello, world!",
|
|
92
|
+
targetLanguage: "es"
|
|
93
|
+
},
|
|
94
|
+
inputSchema: {
|
|
95
|
+
properties: {
|
|
96
|
+
text: { type: "string" },
|
|
97
|
+
targetLanguage: { type: "string", pattern: "^[a-z]{2}$" }
|
|
98
|
+
},
|
|
99
|
+
required: ["text", "targetLanguage"]
|
|
100
|
+
},
|
|
101
|
+
bodyType: "json",
|
|
102
|
+
output: {
|
|
103
|
+
example: {
|
|
104
|
+
translatedText: "¡Hola, mundo!",
|
|
105
|
+
sourceLanguage: "en",
|
|
106
|
+
targetLanguage: "es"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Example: PUT Endpoint with Form Data
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const resources = {
|
|
119
|
+
"PUT /api/user/profile": {
|
|
120
|
+
accepts: {
|
|
121
|
+
scheme: "exact",
|
|
122
|
+
price: "$0.05",
|
|
123
|
+
network: "eip155:84532",
|
|
124
|
+
payTo: "0xYourAddress"
|
|
125
|
+
},
|
|
126
|
+
extensions: {
|
|
127
|
+
...declareDiscoveryExtension({
|
|
128
|
+
input: {
|
|
129
|
+
name: "John Doe",
|
|
130
|
+
email: "john@example.com",
|
|
131
|
+
bio: "Software developer"
|
|
132
|
+
},
|
|
133
|
+
inputSchema: {
|
|
134
|
+
properties: {
|
|
135
|
+
name: { type: "string", minLength: 1 },
|
|
136
|
+
email: { type: "string", format: "email" },
|
|
137
|
+
bio: { type: "string", maxLength: 500 }
|
|
138
|
+
},
|
|
139
|
+
required: ["name", "email"]
|
|
140
|
+
},
|
|
141
|
+
bodyType: "form-data",
|
|
142
|
+
output: {
|
|
143
|
+
example: {
|
|
144
|
+
success: true,
|
|
145
|
+
userId: "123",
|
|
146
|
+
updatedAt: "2024-01-01T00:00:00Z"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Example: DELETE Endpoint
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const resources = {
|
|
159
|
+
"DELETE /api/data/:id": {
|
|
160
|
+
accepts: {
|
|
161
|
+
scheme: "exact",
|
|
162
|
+
price: "$0.001",
|
|
163
|
+
network: "eip155:84532",
|
|
164
|
+
payTo: "0xYourAddress"
|
|
165
|
+
},
|
|
166
|
+
extensions: {
|
|
167
|
+
...declareDiscoveryExtension({
|
|
168
|
+
input: { id: "123" },
|
|
169
|
+
inputSchema: {
|
|
170
|
+
properties: {
|
|
171
|
+
id: { type: "string" }
|
|
172
|
+
},
|
|
173
|
+
required: ["id"]
|
|
174
|
+
},
|
|
175
|
+
output: {
|
|
176
|
+
example: {
|
|
177
|
+
success: true,
|
|
178
|
+
deletedId: "123"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Example: MCP Tool
|
|
188
|
+
|
|
189
|
+
For MCP (Model Context Protocol) tools, use the `toolName` field instead of `bodyType`/`input`. The HTTP method is not relevant -- MCP tools are invoked by name.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
193
|
+
|
|
194
|
+
const resources = {
|
|
195
|
+
"POST /mcp": {
|
|
196
|
+
accepts: {
|
|
197
|
+
scheme: "exact",
|
|
198
|
+
price: "$0.01",
|
|
199
|
+
network: "eip155:84532",
|
|
200
|
+
payTo: "0xYourAddress"
|
|
201
|
+
},
|
|
202
|
+
extensions: {
|
|
203
|
+
...declareDiscoveryExtension({
|
|
204
|
+
toolName: "financial_analysis",
|
|
205
|
+
description: "Analyze financial data for a given ticker",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
ticker: { type: "string", description: "Stock ticker symbol" },
|
|
210
|
+
analysis_type: {
|
|
211
|
+
type: "string",
|
|
212
|
+
enum: ["fundamental", "technical", "sentiment"],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
required: ["ticker"],
|
|
216
|
+
},
|
|
217
|
+
example: { ticker: "AAPL", analysis_type: "fundamental" },
|
|
218
|
+
output: {
|
|
219
|
+
example: {
|
|
220
|
+
pe_ratio: 28.5,
|
|
221
|
+
recommendation: "hold",
|
|
222
|
+
confidence: 0.85
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
You can optionally specify `transport` to indicate the MCP transport type (`"streamable-http"` or `"sse"`). When omitted, `streamable-http` is assumed per the MCP spec.
|
|
232
|
+
|
|
233
|
+
#### Using with Next.js Middleware
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { paymentProxy, x402ResourceServer } from "@x402/next";
|
|
237
|
+
import { HTTPFacilitatorClient } from "@x402/core/http";
|
|
238
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
239
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
240
|
+
|
|
241
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: "https://facilitator.x402.org" });
|
|
242
|
+
const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
243
|
+
.register("eip155:84532", new ExactEvmScheme());
|
|
244
|
+
|
|
245
|
+
export const proxy = paymentProxy(
|
|
246
|
+
{
|
|
247
|
+
"/api/weather": {
|
|
248
|
+
accepts: {
|
|
249
|
+
scheme: "exact",
|
|
250
|
+
price: "$0.001",
|
|
251
|
+
network: "eip155:84532",
|
|
252
|
+
payTo: "0xYourAddress",
|
|
253
|
+
},
|
|
254
|
+
extensions: {
|
|
255
|
+
...declareDiscoveryExtension({
|
|
256
|
+
input: { city: "San Francisco" },
|
|
257
|
+
inputSchema: {
|
|
258
|
+
properties: { city: { type: "string" } },
|
|
259
|
+
required: ["city"],
|
|
260
|
+
},
|
|
261
|
+
output: {
|
|
262
|
+
example: { city: "San Francisco", weather: "foggy" }
|
|
263
|
+
},
|
|
264
|
+
}),
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
resourceServer,
|
|
269
|
+
);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### For Facilitators
|
|
273
|
+
|
|
274
|
+
Extract discovery information from incoming payment requests to catalog resources in the Bazaar.
|
|
275
|
+
|
|
276
|
+
#### Basic Usage
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { extractDiscoveryInfo } from "@x402/extensions/bazaar";
|
|
280
|
+
import type { PaymentPayload, PaymentRequirements } from "@x402/core/types";
|
|
281
|
+
|
|
282
|
+
async function handlePayment(
|
|
283
|
+
paymentPayload: PaymentPayload,
|
|
284
|
+
paymentRequirements: PaymentRequirements
|
|
285
|
+
) {
|
|
286
|
+
// Extract discovery info from the payment
|
|
287
|
+
const discovered = extractDiscoveryInfo(paymentPayload, paymentRequirements);
|
|
288
|
+
|
|
289
|
+
if (discovered) {
|
|
290
|
+
// discovered contains:
|
|
291
|
+
// {
|
|
292
|
+
// resourceUrl: "https://api.example.com/weather",
|
|
293
|
+
// method: "GET",
|
|
294
|
+
// x402Version: 2,
|
|
295
|
+
// discoveryInfo: {
|
|
296
|
+
// input: { type: "http", method: "GET", queryParams: { city: "..." } },
|
|
297
|
+
// output: { type: "json", example: { ... } }
|
|
298
|
+
// }
|
|
299
|
+
// }
|
|
300
|
+
|
|
301
|
+
// Catalog the resource in your Bazaar
|
|
302
|
+
await catalogResource({
|
|
303
|
+
url: discovered.resourceUrl,
|
|
304
|
+
method: discovered.method,
|
|
305
|
+
inputSchema: discovered.discoveryInfo.input,
|
|
306
|
+
outputExample: discovered.discoveryInfo.output?.example,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### Validating Discovery Extensions
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { validateDiscoveryExtension, extractDiscoveryInfo } from "@x402/extensions/bazaar";
|
|
316
|
+
|
|
317
|
+
function processPayment(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements) {
|
|
318
|
+
const discovered = extractDiscoveryInfo(paymentPayload, paymentRequirements);
|
|
319
|
+
|
|
320
|
+
if (discovered && paymentPayload.extensions?.bazaar) {
|
|
321
|
+
// Validate the extension schema
|
|
322
|
+
const validation = validateDiscoveryExtension(paymentPayload.extensions.bazaar);
|
|
323
|
+
|
|
324
|
+
if (!validation.valid) {
|
|
325
|
+
console.warn("Invalid discovery extension:", validation.errors);
|
|
326
|
+
// Handle invalid extension (log, reject, etc.)
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Extension is valid, proceed with cataloging
|
|
331
|
+
catalogResource(discovered);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Using with Server Extension Helper
|
|
337
|
+
|
|
338
|
+
The `bazaarResourceServerExtension` automatically enriches discovery extensions with HTTP method information from the request context:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { bazaarResourceServerExtension } from "@x402/extensions/bazaar";
|
|
342
|
+
import { x402ResourceServer } from "@x402/core/server";
|
|
343
|
+
|
|
344
|
+
// The extension helper automatically extracts discovery info
|
|
345
|
+
const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
346
|
+
.register("eip155:84532", new ExactEvmScheme())
|
|
347
|
+
.registerExtension(bazaarResourceServerExtension);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Bazaar API Reference
|
|
351
|
+
|
|
352
|
+
#### `declareDiscoveryExtension(config)`
|
|
353
|
+
|
|
354
|
+
Creates a discovery extension object for resource servers. Accepts either an HTTP endpoint config or an MCP tool config.
|
|
355
|
+
|
|
356
|
+
**HTTP Parameters:**
|
|
357
|
+
- `config.input` (optional): Example input values (query params for GET/HEAD/DELETE, body for POST/PUT/PATCH)
|
|
358
|
+
- `config.inputSchema` (optional): JSON Schema for input validation
|
|
359
|
+
- `config.bodyType` (required for body methods): For POST/PUT/PATCH, specify `"json"`, `"form-data"`, or `"text"`. This is how TypeScript discriminates between query methods (GET/HEAD/DELETE) and body methods.
|
|
360
|
+
- `config.output` (optional): Output specification
|
|
361
|
+
- `output.example`: Example output data
|
|
362
|
+
- `output.schema`: JSON Schema for output validation
|
|
363
|
+
|
|
364
|
+
> **Note:** The HTTP method is NOT passed to this function. It is automatically inferred from the route key (e.g., `"GET /weather"`) or enriched by `bazaarResourceServerExtension` at runtime.
|
|
365
|
+
|
|
366
|
+
**MCP Parameters:**
|
|
367
|
+
- `config.toolName` (required): MCP tool name — the presence of this field identifies the config as MCP
|
|
368
|
+
- `config.description` (optional): Human-readable tool description
|
|
369
|
+
- `config.inputSchema` (required): JSON Schema for tool arguments
|
|
370
|
+
- `config.example` (optional): Example tool arguments
|
|
371
|
+
- `config.transport` (optional): MCP transport type (`"streamable-http"` or `"sse"`). Defaults to `streamable-http` per the MCP spec when omitted.
|
|
372
|
+
- `config.output` (optional): Output specification
|
|
373
|
+
- `output.example`: Example output data
|
|
374
|
+
- `output.schema`: JSON Schema for output validation
|
|
375
|
+
|
|
376
|
+
**Returns:** An object with a `bazaar` key containing the discovery extension.
|
|
377
|
+
|
|
378
|
+
**Examples:**
|
|
379
|
+
```typescript
|
|
380
|
+
// HTTP endpoint
|
|
381
|
+
const httpExtension = declareDiscoveryExtension({
|
|
382
|
+
input: { query: "search term" },
|
|
383
|
+
inputSchema: {
|
|
384
|
+
properties: { query: { type: "string" } },
|
|
385
|
+
required: ["query"]
|
|
386
|
+
},
|
|
387
|
+
output: {
|
|
388
|
+
example: { results: [] }
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// MCP tool
|
|
393
|
+
const mcpExtension = declareDiscoveryExtension({
|
|
394
|
+
toolName: "search",
|
|
395
|
+
description: "Search for documents",
|
|
396
|
+
inputSchema: {
|
|
397
|
+
type: "object",
|
|
398
|
+
properties: { query: { type: "string" } },
|
|
399
|
+
required: ["query"]
|
|
400
|
+
},
|
|
401
|
+
output: {
|
|
402
|
+
example: { results: [] }
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
// Both return: { bazaar: { info: {...}, schema: {...} } }
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### `extractDiscoveryInfo(paymentPayload, paymentRequirements, validate?)`
|
|
409
|
+
|
|
410
|
+
Extracts discovery information from a payment request (for facilitators).
|
|
411
|
+
|
|
412
|
+
**Parameters:**
|
|
413
|
+
- `paymentPayload`: The payment payload from the client
|
|
414
|
+
- `paymentRequirements`: The payment requirements from the server
|
|
415
|
+
- `validate` (optional): Whether to validate the extension (default: `true`)
|
|
416
|
+
|
|
417
|
+
**Returns:** `DiscoveredResource` object or `null` if not found.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
interface DiscoveredHTTPResource {
|
|
421
|
+
resourceUrl: string;
|
|
422
|
+
method: string; // e.g. "GET", "POST"
|
|
423
|
+
x402Version: number;
|
|
424
|
+
discoveryInfo: DiscoveryInfo;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
interface DiscoveredMCPResource {
|
|
428
|
+
resourceUrl: string;
|
|
429
|
+
toolName: string; // MCP tool name
|
|
430
|
+
x402Version: number;
|
|
431
|
+
discoveryInfo: DiscoveryInfo;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
type DiscoveredResource = DiscoveredHTTPResource | DiscoveredMCPResource;
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `validateDiscoveryExtension(extension)`
|
|
438
|
+
|
|
439
|
+
Validates a discovery extension's info against its schema.
|
|
440
|
+
|
|
441
|
+
**Returns:** `{ valid: boolean, errors?: string[] }`
|
|
442
|
+
|
|
443
|
+
#### `validateAndExtract(extension)`
|
|
444
|
+
|
|
445
|
+
Validates and extracts discovery info in one step.
|
|
446
|
+
|
|
447
|
+
**Returns:** `{ valid: boolean, info?: DiscoveryInfo, errors?: string[] }`
|
|
448
|
+
|
|
449
|
+
#### `bazaarResourceServerExtension`
|
|
450
|
+
|
|
451
|
+
A server extension that automatically enriches HTTP discovery extensions with method information from the request context. MCP extensions are passed through unchanged.
|
|
452
|
+
|
|
453
|
+
**Usage:**
|
|
454
|
+
```typescript
|
|
455
|
+
import { bazaarResourceServerExtension } from "@x402/extensions/bazaar";
|
|
456
|
+
|
|
457
|
+
const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
458
|
+
.registerExtension(bazaarResourceServerExtension);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### `BAZAAR`
|
|
462
|
+
|
|
463
|
+
The extension identifier constant (`"bazaar"`).
|
|
464
|
+
|
|
465
|
+
## Sign-In-With-X Extension
|
|
466
|
+
|
|
467
|
+
The Sign-In-With-X extension implements [CAIP-122](https://chainagnostic.org/CAIPs/caip-122) for chain-agnostic wallet authentication. It allows clients to prove control of a wallet that previously paid for a resource, enabling access without repurchase.
|
|
468
|
+
|
|
469
|
+
### How It Works
|
|
470
|
+
|
|
471
|
+
1. Server returns 402 with `sign-in-with-x` extension containing challenge parameters
|
|
472
|
+
2. Client signs the CAIP-122 message with their wallet
|
|
473
|
+
3. Client sends signed proof in `SIGN-IN-WITH-X` header
|
|
474
|
+
4. Server verifies signature and grants access if wallet has previous payment
|
|
475
|
+
|
|
476
|
+
### Server Usage
|
|
477
|
+
|
|
478
|
+
#### Recommended: Hooks (Automatic)
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import {
|
|
482
|
+
declareSIWxExtension,
|
|
483
|
+
siwxResourceServerExtension,
|
|
484
|
+
createSIWxSettleHook,
|
|
485
|
+
createSIWxRequestHook,
|
|
486
|
+
InMemorySIWxStorage,
|
|
487
|
+
} from '@x402/extensions/sign-in-with-x';
|
|
488
|
+
|
|
489
|
+
// Storage for tracking paid addresses
|
|
490
|
+
const storage = new InMemorySIWxStorage();
|
|
491
|
+
|
|
492
|
+
// 1. Register extension for time-based field refreshment
|
|
493
|
+
const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
494
|
+
.register(NETWORK, new ExactEvmScheme())
|
|
495
|
+
.registerExtension(siwxResourceServerExtension) // Refreshes nonce/timestamps per request
|
|
496
|
+
.onAfterSettle(createSIWxSettleHook({ storage })); // Records payments
|
|
497
|
+
|
|
498
|
+
// 2. Declare SIWX support in routes (network/domain/uri derived automatically)
|
|
499
|
+
const routes = {
|
|
500
|
+
"GET /data": {
|
|
501
|
+
accepts: [{scheme: "exact", price: "$0.01", network: "eip155:8453", payTo}],
|
|
502
|
+
extensions: declareSIWxExtension({
|
|
503
|
+
statement: 'Sign in to access your purchased content',
|
|
504
|
+
}),
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// 3. Verify incoming SIWX proofs
|
|
509
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, routes)
|
|
510
|
+
.onProtectedRequest(createSIWxRequestHook({ storage })); // Grants access if paid
|
|
511
|
+
|
|
512
|
+
// Optional: Enable smart wallet support (EIP-1271/EIP-6492)
|
|
513
|
+
import { createPublicClient, http } from 'viem';
|
|
514
|
+
import { base } from 'viem/chains';
|
|
515
|
+
|
|
516
|
+
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
517
|
+
const httpServerWithSmartWallets = new x402HTTPResourceServer(resourceServer, routes)
|
|
518
|
+
.onProtectedRequest(createSIWxRequestHook({
|
|
519
|
+
storage,
|
|
520
|
+
verifyOptions: { evmVerifier: publicClient.verifyMessage },
|
|
521
|
+
}));
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The hooks automatically:
|
|
525
|
+
- **siwxResourceServerExtension**: Derives `network` from `accepts`, `domain`/`uri` from request URL, refreshes `nonce`/`issuedAt`/`expirationTime` per request
|
|
526
|
+
- **createSIWxSettleHook**: Records payment when settlement succeeds
|
|
527
|
+
- **createSIWxRequestHook**: Validates and verifies SIWX proofs, grants access if wallet has paid
|
|
528
|
+
|
|
529
|
+
#### Manual Usage (Advanced)
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import {
|
|
533
|
+
declareSIWxExtension,
|
|
534
|
+
parseSIWxHeader,
|
|
535
|
+
validateSIWxMessage,
|
|
536
|
+
verifySIWxSignature,
|
|
537
|
+
SIGN_IN_WITH_X,
|
|
538
|
+
} from '@x402/extensions/sign-in-with-x';
|
|
539
|
+
|
|
540
|
+
// 1. Declare in PaymentRequired response
|
|
541
|
+
const extensions = {
|
|
542
|
+
[SIGN_IN_WITH_X]: declareSIWxExtension({
|
|
543
|
+
domain: 'api.example.com',
|
|
544
|
+
resourceUri: 'https://api.example.com/data',
|
|
545
|
+
network: 'eip155:8453',
|
|
546
|
+
statement: 'Sign in to access your purchased content',
|
|
547
|
+
}),
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// 2. Verify incoming proof
|
|
551
|
+
async function handleRequest(request: Request) {
|
|
552
|
+
const header = request.headers.get('SIGN-IN-WITH-X');
|
|
553
|
+
if (!header) return; // No auth provided
|
|
554
|
+
|
|
555
|
+
// Parse the header
|
|
556
|
+
const payload = parseSIWxHeader(header);
|
|
557
|
+
|
|
558
|
+
// Validate message fields (expiry, nonce, domain, etc.)
|
|
559
|
+
const validation = await validateSIWxMessage(
|
|
560
|
+
payload,
|
|
561
|
+
'https://api.example.com/data'
|
|
562
|
+
);
|
|
563
|
+
if (!validation.valid) {
|
|
564
|
+
return { error: validation.error };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Verify signature and recover address
|
|
568
|
+
const verification = await verifySIWxSignature(payload);
|
|
569
|
+
if (!verification.valid) {
|
|
570
|
+
return { error: verification.error };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// verification.address is the verified wallet
|
|
574
|
+
// Check if this wallet has paid before
|
|
575
|
+
const hasPaid = await checkPaymentHistory(verification.address);
|
|
576
|
+
if (hasPaid) {
|
|
577
|
+
// Grant access without payment
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Client Usage
|
|
583
|
+
|
|
584
|
+
#### Recommended: Client Hook (Automatic)
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { createSIWxClientHook } from '@x402/extensions/sign-in-with-x';
|
|
588
|
+
import { x402HTTPClient } from '@x402/fetch';
|
|
589
|
+
|
|
590
|
+
// Configure client with SIWX hook - automatically tries SIWX auth before payment
|
|
591
|
+
const httpClient = new x402HTTPClient(client)
|
|
592
|
+
.onPaymentRequired(createSIWxClientHook(signer));
|
|
593
|
+
|
|
594
|
+
// Requests automatically use SIWX auth when server supports it
|
|
595
|
+
const response = await httpClient.fetch(url);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The client hook automatically:
|
|
599
|
+
- Detects SIWX support in 402 responses
|
|
600
|
+
- Matches your wallet's chain with server's `supportedChains`
|
|
601
|
+
- Signs and sends the authentication proof
|
|
602
|
+
- Falls back to payment if SIWX auth fails
|
|
603
|
+
|
|
604
|
+
#### Manual Usage (Advanced)
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import {
|
|
608
|
+
createSIWxPayload,
|
|
609
|
+
encodeSIWxHeader,
|
|
610
|
+
} from '@x402/extensions/sign-in-with-x';
|
|
611
|
+
|
|
612
|
+
// 1. Get extension and network from 402 response
|
|
613
|
+
const paymentRequired = await response.json();
|
|
614
|
+
const extension = paymentRequired.extensions['sign-in-with-x'];
|
|
615
|
+
const paymentNetwork = paymentRequired.accepts[0].network; // e.g., "eip155:8453"
|
|
616
|
+
|
|
617
|
+
// 2. Find matching chain in supportedChains
|
|
618
|
+
const matchingChain = extension.supportedChains.find(
|
|
619
|
+
chain => chain.chainId === paymentNetwork
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
if (!matchingChain) {
|
|
623
|
+
// Payment network not supported for SIWX
|
|
624
|
+
throw new Error('Chain not supported');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 3. Build complete info with selected chain
|
|
628
|
+
const completeInfo = {
|
|
629
|
+
...extension.info,
|
|
630
|
+
chainId: matchingChain.chainId,
|
|
631
|
+
type: matchingChain.type,
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// 4. Create signed payload
|
|
635
|
+
const payload = await createSIWxPayload(completeInfo, signer);
|
|
636
|
+
|
|
637
|
+
// 5. Encode and send
|
|
638
|
+
const header = encodeSIWxHeader(payload);
|
|
639
|
+
const response = await fetch(url, {
|
|
640
|
+
headers: { 'SIGN-IN-WITH-X': header }
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### SIWx API Reference
|
|
645
|
+
|
|
646
|
+
#### `declareSIWxExtension(options?)`
|
|
647
|
+
|
|
648
|
+
Creates the extension object for servers to include in PaymentRequired. Most fields are derived automatically from request context when using `siwxResourceServerExtension`.
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
declareSIWxExtension({
|
|
652
|
+
// All fields optional - derived from context if omitted
|
|
653
|
+
domain?: string; // Server domain (derived from request URL)
|
|
654
|
+
resourceUri?: string; // Full resource URI (derived from request URL)
|
|
655
|
+
network?: string | string[]; // CAIP-2 network(s) (derived from accepts[].network)
|
|
656
|
+
statement?: string; // Human-readable purpose
|
|
657
|
+
version?: string; // CAIP-122 version (default: "1")
|
|
658
|
+
expirationSeconds?: number; // Challenge TTL in seconds
|
|
659
|
+
})
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Automatic derivation:** When using `siwxResourceServerExtension`, omitted fields are derived:
|
|
663
|
+
- `network` → from `accepts[].network` in route config
|
|
664
|
+
- `resourceUri` → from request URL
|
|
665
|
+
- `domain` → parsed from resourceUri
|
|
666
|
+
|
|
667
|
+
**Multi-chain support:** When `network` is an array (or multiple networks in `accepts`), `supportedChains` will contain one entry per network.
|
|
668
|
+
|
|
669
|
+
#### `parseSIWxHeader(header)`
|
|
670
|
+
|
|
671
|
+
Parses a base64-encoded SIGN-IN-WITH-X header into a payload object.
|
|
672
|
+
|
|
673
|
+
#### `validateSIWxMessage(payload, resourceUri, options?)`
|
|
674
|
+
|
|
675
|
+
Validates message fields (expiry, domain binding, nonce, etc.).
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
validateSIWxMessage(payload, resourceUri, {
|
|
679
|
+
maxAge?: number; // Max age for issuedAt (default: 5 min)
|
|
680
|
+
checkNonce?: (nonce) => boolean; // Custom nonce validation
|
|
681
|
+
})
|
|
682
|
+
// Returns: { valid: boolean; error?: string }
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
#### `verifySIWxSignature(payload, options?)`
|
|
686
|
+
|
|
687
|
+
Verifies the cryptographic signature and recovers the signer address.
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
verifySIWxSignature(payload, {
|
|
691
|
+
evmVerifier?: EVMMessageVerifier; // For smart wallet support
|
|
692
|
+
})
|
|
693
|
+
// Returns: { valid: boolean; address?: string; error?: string }
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Smart Wallet Support (EIP-1271 / EIP-6492):**
|
|
697
|
+
|
|
698
|
+
By default, only EOA (Externally Owned Account) signatures are verified. To support smart contract wallets (like Coinbase Smart Wallet, Safe, etc.), pass `publicClient.verifyMessage` from viem:
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
import { createPublicClient, http } from 'viem';
|
|
702
|
+
import { base } from 'viem/chains';
|
|
703
|
+
|
|
704
|
+
const publicClient = createPublicClient({
|
|
705
|
+
chain: base,
|
|
706
|
+
transport: http()
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// In your request hook
|
|
710
|
+
const result = await verifySIWxSignature(payload, {
|
|
711
|
+
evmVerifier: publicClient.verifyMessage,
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
This enables:
|
|
716
|
+
- **EIP-1271**: Verification of deployed smart contract wallets
|
|
717
|
+
- **EIP-6492**: Verification of counterfactual (not-yet-deployed) wallets
|
|
718
|
+
|
|
719
|
+
Note: Smart wallet verification requires RPC calls, while EOA verification is purely local.
|
|
720
|
+
|
|
721
|
+
#### `createSIWxPayload(serverInfo, signer)`
|
|
722
|
+
|
|
723
|
+
Client helper that creates and signs a complete payload.
|
|
724
|
+
|
|
725
|
+
#### `encodeSIWxHeader(payload)`
|
|
726
|
+
|
|
727
|
+
Encodes a payload as base64 for the SIGN-IN-WITH-X header.
|
|
728
|
+
|
|
729
|
+
#### `SIGN_IN_WITH_X`
|
|
730
|
+
|
|
731
|
+
Extension identifier constant (`"sign-in-with-x"`).
|
|
732
|
+
|
|
733
|
+
### Supported Signature Schemes
|
|
734
|
+
|
|
735
|
+
| Scheme | Description |
|
|
736
|
+
|--------|-------------|
|
|
737
|
+
| `eip191` | personal_sign (default for EVM EOAs) |
|
|
738
|
+
| `eip1271` | Smart contract wallet verification |
|
|
739
|
+
| `eip6492` | Counterfactual smart wallet verification |
|
|
740
|
+
| `siws` | Sign-In-With-Solana |
|
|
741
|
+
|
|
742
|
+
## Troubleshooting
|
|
743
|
+
|
|
744
|
+
### Bazaar Extension Not Being Extracted
|
|
745
|
+
|
|
746
|
+
**Problem:** `extractDiscoveryInfo` returns `null`.
|
|
747
|
+
|
|
748
|
+
**Solutions:**
|
|
749
|
+
- Ensure the server has declared the extension using `declareDiscoveryExtension`
|
|
750
|
+
- Check that `paymentPayload.extensions.bazaar` exists
|
|
751
|
+
- Verify you're using x402 v2 (v1 uses a different format in `outputSchema`)
|
|
752
|
+
|
|
753
|
+
### Bazaar Schema Validation Fails
|
|
754
|
+
|
|
755
|
+
**Problem:** `validateDiscoveryExtension` returns `valid: false`.
|
|
756
|
+
|
|
757
|
+
**Solutions:**
|
|
758
|
+
- Ensure `inputSchema` matches the structure of `input`
|
|
759
|
+
- Check that required fields are marked in `inputSchema.required`
|
|
760
|
+
- Verify JSON Schema syntax is correct
|
|
761
|
+
|
|
762
|
+
### SIWx Signature Verification Fails
|
|
763
|
+
|
|
764
|
+
**Problem:** `verifySIWxSignature` returns `valid: false`.
|
|
765
|
+
|
|
766
|
+
**Solutions:**
|
|
767
|
+
- Ensure the message was signed with the correct wallet
|
|
768
|
+
- Check that the signature scheme matches the wallet type
|
|
769
|
+
- For smart wallets, enable `checkSmartWallet` option with a provider
|
|
770
|
+
|
|
771
|
+
### SIWx Message Validation Fails
|
|
772
|
+
|
|
773
|
+
**Problem:** `validateSIWxMessage` returns `valid: false`.
|
|
774
|
+
|
|
775
|
+
**Solutions:**
|
|
776
|
+
- Check that `issuedAt` is recent (within `maxAge`, default 5 minutes)
|
|
777
|
+
- Verify `expirationTime` hasn't passed
|
|
778
|
+
- Ensure `domain` matches the server's domain
|
|
779
|
+
- Confirm `uri` matches the resource URI
|
|
780
|
+
|
|
781
|
+
## Related Resources
|
|
782
|
+
|
|
783
|
+
- [x402 Core Package](../core/README.md) - Core x402 protocol implementation
|
|
784
|
+
- [CAIP-122 Specification](https://chainagnostic.org/CAIPs/caip-122) - Sign-In-With-X standard
|
|
785
|
+
|
|
786
|
+
## Version Support
|
|
787
|
+
|
|
788
|
+
This package supports both x402 v1 and v2:
|
|
789
|
+
- **v2**: Extensions are in `PaymentPayload.extensions` and `PaymentRequired.extensions`
|
|
790
|
+
- **v1**: Discovery info is in `PaymentRequirements.outputSchema` (automatically converted)
|
|
791
|
+
|
|
792
|
+
The `extractDiscoveryInfo` function automatically handles both versions.
|