@4mica/x402 0.1.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/.eslintrc.cjs +29 -0
- package/.prettierignore +3 -0
- package/.prettierrc +6 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/demo/.env.example +8 -0
- package/demo/README.md +125 -0
- package/demo/package.json +26 -0
- package/demo/src/client.ts +54 -0
- package/demo/src/deposit.ts +39 -0
- package/demo/src/server.ts +74 -0
- package/demo/tsconfig.json +8 -0
- package/demo/yarn.lock +925 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/scheme.d.ts +11 -0
- package/dist/client/scheme.js +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/express/adapter.d.ts +71 -0
- package/dist/server/express/adapter.js +90 -0
- package/dist/server/express/index.d.ts +122 -0
- package/dist/server/express/index.js +340 -0
- package/dist/server/facilitator.d.ts +35 -0
- package/dist/server/facilitator.js +52 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +4 -0
- package/dist/server/scheme.d.ts +93 -0
- package/dist/server/scheme.js +179 -0
- package/eslint.config.mjs +22 -0
- package/package.json +79 -0
- package/src/client/index.ts +1 -0
- package/src/client/scheme.ts +95 -0
- package/src/index.ts +7 -0
- package/src/server/express/adapter.ts +100 -0
- package/src/server/express/index.ts +466 -0
- package/src/server/facilitator.ts +90 -0
- package/src/server/index.ts +10 -0
- package/src/server/scheme.ts +223 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { x402HTTPResourceServer, x402ResourceServer, } from '@x402/core/server';
|
|
2
|
+
import { ExpressAdapter } from './adapter.js';
|
|
3
|
+
import { FourMicaEvmScheme, SUPPORTED_NETWORKS } from '../scheme.js';
|
|
4
|
+
import { FourMicaFacilitatorClient } from '../facilitator.js';
|
|
5
|
+
function registerNetworkServers(httpServer, tabEndpoint) {
|
|
6
|
+
const schemeServer = new FourMicaEvmScheme(tabEndpoint);
|
|
7
|
+
SUPPORTED_NETWORKS.forEach((network) => {
|
|
8
|
+
;
|
|
9
|
+
httpServer.ResourceServer.register(network, schemeServer);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function checkIfBazaarNeeded(routes) {
|
|
13
|
+
if ('accepts' in routes) {
|
|
14
|
+
return !!(routes.extensions && 'bazaar' in routes.extensions);
|
|
15
|
+
}
|
|
16
|
+
return Object.values(routes).some((routeConfig) => {
|
|
17
|
+
return !!(routeConfig.extensions && 'bazaar' in routeConfig.extensions);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Express payment middleware for x402 protocol (direct HTTP server instance).
|
|
22
|
+
*
|
|
23
|
+
* Use this when you need to configure HTTP-level hooks.
|
|
24
|
+
*
|
|
25
|
+
* @param httpServer - Pre-configured x402HTTPResourceServer instance
|
|
26
|
+
* @param tabConfig - Configuration for payment tab handling (endpoint URL and TTL)
|
|
27
|
+
* @param paywallConfig - Optional configuration for the built-in paywall UI
|
|
28
|
+
* @param paywall - Optional custom paywall provider (overrides default)
|
|
29
|
+
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
|
|
30
|
+
* @returns Express middleware handler
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { paymentMiddlewareFromHTTPServer, x402ResourceServer, x402HTTPResourceServer } from "@x402/express";
|
|
35
|
+
*
|
|
36
|
+
* const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
37
|
+
* .register(NETWORK, new ExactEvmScheme())
|
|
38
|
+
*
|
|
39
|
+
* const httpServer = new x402HTTPResourceServer(resourceServer, routes)
|
|
40
|
+
* .onProtectedRequest(requestHook);
|
|
41
|
+
*
|
|
42
|
+
* app.use(paymentMiddlewareFromHTTPServer(
|
|
43
|
+
* httpServer,
|
|
44
|
+
* { advertisedEndpoint: "https://api.example.com/x402/tab" },
|
|
45
|
+
* )); * ```
|
|
46
|
+
*/
|
|
47
|
+
export function paymentMiddlewareFromHTTPServer(httpServer, tabConfig, paywallConfig, paywall, syncFacilitatorOnStart = true) {
|
|
48
|
+
const facilitatorClient = new FourMicaFacilitatorClient();
|
|
49
|
+
registerNetworkServers(httpServer, tabConfig.advertisedEndpoint);
|
|
50
|
+
// Register custom paywall provider if provided
|
|
51
|
+
if (paywall) {
|
|
52
|
+
httpServer.registerPaywallProvider(paywall);
|
|
53
|
+
}
|
|
54
|
+
// Store initialization promise (not the result)
|
|
55
|
+
// httpServer.initialize() fetches facilitator support and validates routes
|
|
56
|
+
let initPromise = syncFacilitatorOnStart ? httpServer.initialize() : null;
|
|
57
|
+
// Dynamically register bazaar extension if routes declare it and not already registered
|
|
58
|
+
// Skip if pre-registered (e.g., in serverless environments where static imports are used)
|
|
59
|
+
let bazaarPromise = null;
|
|
60
|
+
if (checkIfBazaarNeeded(httpServer.routesConfig) &&
|
|
61
|
+
!httpServer.ResourceServer.hasExtension('bazaar')) {
|
|
62
|
+
bazaarPromise = import('@x402/extensions/bazaar')
|
|
63
|
+
.then(({ bazaarResourceServerExtension }) => {
|
|
64
|
+
;
|
|
65
|
+
httpServer.ResourceServer.registerExtension(bazaarResourceServerExtension);
|
|
66
|
+
})
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
console.error('Failed to load bazaar extension:', err);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return async (req, res, next) => {
|
|
72
|
+
// Check if this request is for the tab opening endpoint
|
|
73
|
+
try {
|
|
74
|
+
const advertisedUrl = new URL(tabConfig.advertisedEndpoint);
|
|
75
|
+
if (req.path === advertisedUrl.pathname) {
|
|
76
|
+
// Parse the request body
|
|
77
|
+
const { userAddress, paymentRequirements } = req.body;
|
|
78
|
+
try {
|
|
79
|
+
// Call the facilitator to open the tab
|
|
80
|
+
const openTabResponse = await facilitatorClient.openTab(userAddress, paymentRequirements, tabConfig.ttlSeconds);
|
|
81
|
+
// Return the response
|
|
82
|
+
return res.json(openTabResponse);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof Error && 'status' in error) {
|
|
86
|
+
const openTabError = error;
|
|
87
|
+
return res.status(openTabError.status).json(openTabError.response);
|
|
88
|
+
}
|
|
89
|
+
console.error('Failed to open tab:', error);
|
|
90
|
+
return res.status(500).json({
|
|
91
|
+
error: 'Failed to open tab',
|
|
92
|
+
details: error instanceof Error ? error.message : 'Unknown error',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (urlError) {
|
|
98
|
+
console.error('Invalid advertisedEndpoint URL:', urlError);
|
|
99
|
+
}
|
|
100
|
+
// Create adapter and context
|
|
101
|
+
const adapter = new ExpressAdapter(req);
|
|
102
|
+
const context = {
|
|
103
|
+
adapter,
|
|
104
|
+
path: req.path,
|
|
105
|
+
method: req.method,
|
|
106
|
+
paymentHeader: adapter.getHeader('payment-signature') || adapter.getHeader('x-payment'),
|
|
107
|
+
};
|
|
108
|
+
// Check if route requires payment before initializing facilitator
|
|
109
|
+
if (!httpServer.requiresPayment(context)) {
|
|
110
|
+
return next();
|
|
111
|
+
}
|
|
112
|
+
// Only initialize when processing a protected route
|
|
113
|
+
if (initPromise) {
|
|
114
|
+
await initPromise;
|
|
115
|
+
initPromise = null; // Clear after first await
|
|
116
|
+
}
|
|
117
|
+
// Await bazaar extension loading if needed
|
|
118
|
+
if (bazaarPromise) {
|
|
119
|
+
await bazaarPromise;
|
|
120
|
+
bazaarPromise = null;
|
|
121
|
+
}
|
|
122
|
+
// Process payment requirement check
|
|
123
|
+
const result = await httpServer.processHTTPRequest(context, paywallConfig);
|
|
124
|
+
// Handle the different result types
|
|
125
|
+
switch (result.type) {
|
|
126
|
+
case 'no-payment-required':
|
|
127
|
+
// No payment needed, proceed directly to the route handler
|
|
128
|
+
return next();
|
|
129
|
+
case 'payment-error':
|
|
130
|
+
// Payment required but not provided or invalid
|
|
131
|
+
const { response } = result;
|
|
132
|
+
res.status(response.status);
|
|
133
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
134
|
+
res.setHeader(key, value);
|
|
135
|
+
});
|
|
136
|
+
if (response.isHtml) {
|
|
137
|
+
res.send(response.body);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
res.json(response.body || {});
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
case 'payment-verified':
|
|
144
|
+
// Payment is valid, need to wrap response for settlement
|
|
145
|
+
const { paymentPayload, paymentRequirements } = result;
|
|
146
|
+
// Intercept and buffer all core methods that can commit response to client
|
|
147
|
+
const originalWriteHead = res.writeHead.bind(res);
|
|
148
|
+
const originalWrite = res.write.bind(res);
|
|
149
|
+
const originalEnd = res.end.bind(res);
|
|
150
|
+
const originalFlushHeaders = res.flushHeaders.bind(res);
|
|
151
|
+
let bufferedCalls = [];
|
|
152
|
+
let settled = false;
|
|
153
|
+
// Create a promise that resolves when the handler finishes and calls res.end()
|
|
154
|
+
let endCalled;
|
|
155
|
+
const endPromise = new Promise((resolve) => {
|
|
156
|
+
endCalled = resolve;
|
|
157
|
+
});
|
|
158
|
+
res.writeHead = function (...args) {
|
|
159
|
+
if (!settled) {
|
|
160
|
+
bufferedCalls.push(['writeHead', args]);
|
|
161
|
+
return res;
|
|
162
|
+
}
|
|
163
|
+
return originalWriteHead(...args);
|
|
164
|
+
};
|
|
165
|
+
res.write = function (...args) {
|
|
166
|
+
if (!settled) {
|
|
167
|
+
bufferedCalls.push(['write', args]);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return originalWrite(...args);
|
|
171
|
+
};
|
|
172
|
+
res.end = function (...args) {
|
|
173
|
+
if (!settled) {
|
|
174
|
+
bufferedCalls.push(['end', args]);
|
|
175
|
+
// Signal that the handler has finished
|
|
176
|
+
endCalled();
|
|
177
|
+
return res;
|
|
178
|
+
}
|
|
179
|
+
return originalEnd(...args);
|
|
180
|
+
};
|
|
181
|
+
res.flushHeaders = function () {
|
|
182
|
+
if (!settled) {
|
|
183
|
+
bufferedCalls.push(['flushHeaders', []]);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
return originalFlushHeaders();
|
|
187
|
+
};
|
|
188
|
+
// Proceed to the next middleware or route handler
|
|
189
|
+
next();
|
|
190
|
+
// Wait for the handler to actually call res.end() before checking status
|
|
191
|
+
await endPromise;
|
|
192
|
+
// If the response from the protected route is >= 400, do not settle payment
|
|
193
|
+
if (res.statusCode >= 400) {
|
|
194
|
+
settled = true;
|
|
195
|
+
res.writeHead = originalWriteHead;
|
|
196
|
+
res.write = originalWrite;
|
|
197
|
+
res.end = originalEnd;
|
|
198
|
+
res.flushHeaders = originalFlushHeaders;
|
|
199
|
+
// Replay all buffered calls in order
|
|
200
|
+
for (const [method, args] of bufferedCalls) {
|
|
201
|
+
if (method === 'writeHead')
|
|
202
|
+
originalWriteHead(...args);
|
|
203
|
+
else if (method === 'write')
|
|
204
|
+
originalWrite(...args);
|
|
205
|
+
else if (method === 'end')
|
|
206
|
+
originalEnd(...args);
|
|
207
|
+
else if (method === 'flushHeaders')
|
|
208
|
+
originalFlushHeaders();
|
|
209
|
+
}
|
|
210
|
+
bufferedCalls = [];
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const settleResult = await httpServer.processSettlement(paymentPayload, paymentRequirements);
|
|
215
|
+
// If settlement fails, return an error and do not send the buffered response
|
|
216
|
+
if (!settleResult.success) {
|
|
217
|
+
bufferedCalls = [];
|
|
218
|
+
res.status(402).json({
|
|
219
|
+
error: 'Settlement failed',
|
|
220
|
+
details: settleResult.errorReason,
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Settlement succeeded - add headers to response
|
|
225
|
+
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
226
|
+
res.setHeader(key, value);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error(error);
|
|
231
|
+
// If settlement fails, don't send the buffered response
|
|
232
|
+
bufferedCalls = [];
|
|
233
|
+
res.status(402).json({
|
|
234
|
+
error: 'Settlement failed',
|
|
235
|
+
details: error instanceof Error ? error.message : 'Unknown error',
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
settled = true;
|
|
241
|
+
res.writeHead = originalWriteHead;
|
|
242
|
+
res.write = originalWrite;
|
|
243
|
+
res.end = originalEnd;
|
|
244
|
+
res.flushHeaders = originalFlushHeaders;
|
|
245
|
+
// Replay all buffered calls in order
|
|
246
|
+
for (const [method, args] of bufferedCalls) {
|
|
247
|
+
if (method === 'writeHead')
|
|
248
|
+
originalWriteHead(...args);
|
|
249
|
+
else if (method === 'write')
|
|
250
|
+
originalWrite(...args);
|
|
251
|
+
else if (method === 'end')
|
|
252
|
+
originalEnd(...args);
|
|
253
|
+
else if (method === 'flushHeaders')
|
|
254
|
+
originalFlushHeaders();
|
|
255
|
+
}
|
|
256
|
+
bufferedCalls = [];
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Express payment middleware for x402 protocol (direct server instance).
|
|
264
|
+
*
|
|
265
|
+
* Use this when you want to pass a pre-configured x402ResourceServer instance.
|
|
266
|
+
* This provides more flexibility for testing, custom configuration, and reusing
|
|
267
|
+
* server instances across multiple middlewares.
|
|
268
|
+
*
|
|
269
|
+
* @param routes - Route configurations for protected endpoints
|
|
270
|
+
* @param server - Pre-configured x402ResourceServer instance
|
|
271
|
+
* @param tabConfig - Configuration for payment tab handling (endpoint URL and TTL)
|
|
272
|
+
* @param paywallConfig - Optional configuration for the built-in paywall UI
|
|
273
|
+
* @param paywall - Optional custom paywall provider (overrides default)
|
|
274
|
+
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
|
|
275
|
+
* @returns Express middleware handler
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```typescript
|
|
279
|
+
* import { paymentMiddleware } from "@x402/express";
|
|
280
|
+
*
|
|
281
|
+
* const server = new x402ResourceServer(myFacilitatorClient)
|
|
282
|
+
* .register(NETWORK, new ExactEvmScheme());
|
|
283
|
+
*
|
|
284
|
+
* app.use(paymentMiddleware(
|
|
285
|
+
* routes,
|
|
286
|
+
* server,
|
|
287
|
+
* { advertisedEndpoint: "https://api.example.com/x402/tab" },
|
|
288
|
+
* ));
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
export function paymentMiddleware(routes, server, tabConfig, paywallConfig, paywall, syncFacilitatorOnStart = true) {
|
|
292
|
+
// Create the x402 HTTP server instance with the resource server
|
|
293
|
+
const httpServer = new x402HTTPResourceServer(server, routes);
|
|
294
|
+
return paymentMiddlewareFromHTTPServer(httpServer, tabConfig, paywallConfig, paywall, syncFacilitatorOnStart);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Express payment middleware for x402 protocol (configuration-based).
|
|
298
|
+
*
|
|
299
|
+
* Use this when you want to quickly set up middleware with simple configuration.
|
|
300
|
+
* This function creates and configures the x402ResourceServer internally.
|
|
301
|
+
*
|
|
302
|
+
* @param routes - Route configurations for protected endpoints
|
|
303
|
+
* @param tabConfig - Configuration for payment tab handling
|
|
304
|
+
* @param facilitatorClients - Optional facilitator client(s) for payment processing
|
|
305
|
+
* @param schemes - Optional array of scheme registrations for server-side payment processing
|
|
306
|
+
* @param paywallConfig - Optional configuration for the built-in paywall UI
|
|
307
|
+
* @param paywall - Optional custom paywall provider (overrides default)
|
|
308
|
+
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
|
|
309
|
+
* @returns Express middleware handler
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* import { paymentMiddlewareFromConfig } from "@x402/express";
|
|
314
|
+
*
|
|
315
|
+
* app.use(paymentMiddlewareFromConfig(
|
|
316
|
+
* routes,
|
|
317
|
+
* { advertisedEndpoint: "https://api.example.com/x402/tab" },
|
|
318
|
+
* ));
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export function paymentMiddlewareFromConfig(routes, tabConfig, facilitatorClients, schemes, paywallConfig, paywall, syncFacilitatorOnStart = true) {
|
|
322
|
+
const facilitators = facilitatorClients
|
|
323
|
+
? Array.isArray(facilitatorClients)
|
|
324
|
+
? facilitatorClients
|
|
325
|
+
: [facilitatorClients]
|
|
326
|
+
: [];
|
|
327
|
+
if (!facilitators.some((c) => c instanceof FourMicaFacilitatorClient)) {
|
|
328
|
+
facilitators.push(new FourMicaFacilitatorClient());
|
|
329
|
+
}
|
|
330
|
+
const ResourceServer = new x402ResourceServer(facilitators);
|
|
331
|
+
if (schemes) {
|
|
332
|
+
schemes.forEach(({ network, server: schemeServer }) => {
|
|
333
|
+
ResourceServer.register(network, schemeServer);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Use the direct paymentMiddleware with the configured server
|
|
337
|
+
// Note: paymentMiddleware handles dynamic bazaar registration
|
|
338
|
+
return paymentMiddleware(routes, ResourceServer, tabConfig, paywallConfig, paywall, syncFacilitatorOnStart);
|
|
339
|
+
}
|
|
340
|
+
export { ExpressAdapter } from './adapter.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FacilitatorConfig, HTTPFacilitatorClient } from '@x402/core/server';
|
|
2
|
+
import { Network, PaymentRequirements } from '@x402/core/types';
|
|
3
|
+
export interface OpenTabRequest {
|
|
4
|
+
userAddress: string;
|
|
5
|
+
recipientAddress: string;
|
|
6
|
+
network?: Network;
|
|
7
|
+
erc20Token?: string;
|
|
8
|
+
ttlSeconds?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface OpenTabResponse {
|
|
11
|
+
tabId: string;
|
|
12
|
+
userAddress: string;
|
|
13
|
+
recipientAddress: string;
|
|
14
|
+
assetAddress: string;
|
|
15
|
+
startTimestamp: number;
|
|
16
|
+
ttlSeconds: number;
|
|
17
|
+
nextReqId: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class OpenTabError extends Error {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly response: OpenTabResponse;
|
|
22
|
+
constructor(status: number, response: OpenTabResponse);
|
|
23
|
+
}
|
|
24
|
+
export declare class FourMicaFacilitatorClient extends HTTPFacilitatorClient {
|
|
25
|
+
constructor(config?: FacilitatorConfig);
|
|
26
|
+
openTab(userAddress: string, paymentRequirements: PaymentRequirements, ttlSeconds?: number): Promise<OpenTabResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* Helper to convert objects to JSON-safe format.
|
|
29
|
+
* Handles BigInt and other non-JSON types.
|
|
30
|
+
*
|
|
31
|
+
* @param obj - The object to convert
|
|
32
|
+
* @returns The JSON-safe representation of the object
|
|
33
|
+
*/
|
|
34
|
+
private safeJson;
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HTTPFacilitatorClient } from '@x402/core/server';
|
|
2
|
+
const DEFAULT_FACILITATOR_URL = 'https://x402.4mica.xyz';
|
|
3
|
+
export class OpenTabError extends Error {
|
|
4
|
+
constructor(status, response) {
|
|
5
|
+
super(`OpenTab failed with status ${status}`);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.response = response;
|
|
8
|
+
this.name = 'OpenTabError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class FourMicaFacilitatorClient extends HTTPFacilitatorClient {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super({ ...config, url: config?.url ?? DEFAULT_FACILITATOR_URL });
|
|
14
|
+
}
|
|
15
|
+
async openTab(userAddress, paymentRequirements, ttlSeconds) {
|
|
16
|
+
let headers = {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
const authHeaders = await this.createAuthHeaders('tabs');
|
|
20
|
+
headers = { ...headers, ...authHeaders.headers };
|
|
21
|
+
const response = await fetch(`${this.url}/tabs`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers,
|
|
24
|
+
body: JSON.stringify(this.safeJson({
|
|
25
|
+
userAddress,
|
|
26
|
+
recipientAddress: paymentRequirements.payTo,
|
|
27
|
+
network: paymentRequirements.network,
|
|
28
|
+
erc20Token: paymentRequirements.asset,
|
|
29
|
+
ttlSeconds,
|
|
30
|
+
})),
|
|
31
|
+
});
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
if (typeof data === 'object' && data !== null && 'tabId' in data) {
|
|
34
|
+
const openTabResponse = data;
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new OpenTabError(response.status, openTabResponse);
|
|
37
|
+
}
|
|
38
|
+
return openTabResponse;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Facilitator openTab failed (${response.status}): ${JSON.stringify(data)}`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Helper to convert objects to JSON-safe format.
|
|
44
|
+
* Handles BigInt and other non-JSON types.
|
|
45
|
+
*
|
|
46
|
+
* @param obj - The object to convert
|
|
47
|
+
* @returns The JSON-safe representation of the object
|
|
48
|
+
*/
|
|
49
|
+
safeJson(obj) {
|
|
50
|
+
return JSON.parse(JSON.stringify(obj, (_, value) => (typeof value === 'bigint' ? value.toString() : value)));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './facilitator.js';
|
|
2
|
+
export * from './scheme.js';
|
|
3
|
+
export { x402ResourceServer, x402HTTPResourceServer } from '@x402/core/server';
|
|
4
|
+
export type { PaywallProvider, PaywallConfig } from '@x402/core/server';
|
|
5
|
+
export { RouteConfigurationError } from '@x402/core/server';
|
|
6
|
+
export type { RouteValidationError } from '@x402/core/server';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { AssetAmount, Network, PaymentRequirements, Price, SchemeNetworkServer, MoneyParser } from '@x402/core/types';
|
|
2
|
+
export declare const SUPPORTED_NETWORKS: Network[];
|
|
3
|
+
/**
|
|
4
|
+
* EVM server implementation for the 4mica payment scheme.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FourMicaEvmScheme implements SchemeNetworkServer {
|
|
7
|
+
readonly advertisedTabEndpoint: string;
|
|
8
|
+
readonly scheme = "4mica-credit";
|
|
9
|
+
private moneyParsers;
|
|
10
|
+
constructor(advertisedTabEndpoint: string);
|
|
11
|
+
/**
|
|
12
|
+
* Register a custom money parser in the parser chain.
|
|
13
|
+
* Multiple parsers can be registered - they will be tried in registration order.
|
|
14
|
+
* Each parser receives a decimal amount (e.g., 1.50 for $1.50).
|
|
15
|
+
* If a parser returns null, the next parser in the chain will be tried.
|
|
16
|
+
* The default parser is always the final fallback.
|
|
17
|
+
*
|
|
18
|
+
* @param parser - Custom function to convert amount to AssetAmount (or null to skip)
|
|
19
|
+
* @returns The server instance for chaining
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* evmServer.registerMoneyParser(async (amount, network) => {
|
|
23
|
+
* // Custom conversion logic
|
|
24
|
+
* if (amount > 100) {
|
|
25
|
+
* // Use different token for large amounts
|
|
26
|
+
* return { amount: (amount * 1e18).toString(), asset: "0xCustomToken" };
|
|
27
|
+
* }
|
|
28
|
+
* return null; // Use next parser
|
|
29
|
+
* });
|
|
30
|
+
*/
|
|
31
|
+
registerMoneyParser(parser: MoneyParser): FourMicaEvmScheme;
|
|
32
|
+
/**
|
|
33
|
+
* Parses a price into an asset amount.
|
|
34
|
+
* If price is already an AssetAmount, returns it directly.
|
|
35
|
+
* If price is Money (string | number), parses to decimal and tries custom parsers.
|
|
36
|
+
* Falls back to default conversion if all custom parsers return null.
|
|
37
|
+
*
|
|
38
|
+
* @param price - The price to parse
|
|
39
|
+
* @param network - The network to use
|
|
40
|
+
* @returns Promise that resolves to the parsed asset amount
|
|
41
|
+
*/
|
|
42
|
+
parsePrice(price: Price, network: Network): Promise<AssetAmount>;
|
|
43
|
+
/**
|
|
44
|
+
* Build payment requirements for this scheme/network combination
|
|
45
|
+
*
|
|
46
|
+
* @param paymentRequirements - The base payment requirements
|
|
47
|
+
* @param supportedKind - The supported kind from facilitator (unused)
|
|
48
|
+
* @param supportedKind.x402Version - The x402 version
|
|
49
|
+
* @param supportedKind.scheme - The logical payment scheme
|
|
50
|
+
* @param supportedKind.network - The network identifier in CAIP-2 format
|
|
51
|
+
* @param supportedKind.extra - Optional extra metadata regarding scheme/network implementation details
|
|
52
|
+
* @param extensionKeys - Extension keys supported by the facilitator (unused)
|
|
53
|
+
* @returns Payment requirements ready to be sent to clients
|
|
54
|
+
*/
|
|
55
|
+
enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
|
|
56
|
+
x402Version: number;
|
|
57
|
+
scheme: string;
|
|
58
|
+
network: Network;
|
|
59
|
+
extra?: Record<string, unknown>;
|
|
60
|
+
}, extensionKeys: string[]): Promise<PaymentRequirements>;
|
|
61
|
+
/**
|
|
62
|
+
* Parse Money (string | number) to a decimal number.
|
|
63
|
+
* Handles formats like "$1.50", "1.50", 1.50, etc.
|
|
64
|
+
*
|
|
65
|
+
* @param money - The money value to parse
|
|
66
|
+
* @returns Decimal number
|
|
67
|
+
*/
|
|
68
|
+
private parseMoneyToDecimal;
|
|
69
|
+
/**
|
|
70
|
+
* Default money conversion implementation.
|
|
71
|
+
* Converts decimal amount to the default stablecoin on the specified network.
|
|
72
|
+
*
|
|
73
|
+
* @param amount - The decimal amount (e.g., 1.50)
|
|
74
|
+
* @param network - The network to use
|
|
75
|
+
* @returns The parsed asset amount in the default stablecoin
|
|
76
|
+
*/
|
|
77
|
+
private defaultMoneyConversion;
|
|
78
|
+
/**
|
|
79
|
+
* Convert decimal amount to token units (e.g., 0.10 -> 100000 for 6-decimal tokens)
|
|
80
|
+
*
|
|
81
|
+
* @param decimalAmount - The decimal amount to convert
|
|
82
|
+
* @param decimals - The number of decimals for the token
|
|
83
|
+
* @returns The token amount as a string
|
|
84
|
+
*/
|
|
85
|
+
private convertToTokenAmount;
|
|
86
|
+
/**
|
|
87
|
+
* Get the default asset info for a network (typically USDC)
|
|
88
|
+
*
|
|
89
|
+
* @param network - The network to get asset info for
|
|
90
|
+
* @returns The asset information including address, name, version, and decimals
|
|
91
|
+
*/
|
|
92
|
+
private getDefaultAsset;
|
|
93
|
+
}
|