@4mica/x402 1.2.4 → 2.0.0-alpha.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +67 -82
  3. package/dist/client/scheme.d.ts +40 -4
  4. package/dist/client/scheme.js +96 -38
  5. package/dist/domain.d.ts +12 -0
  6. package/dist/domain.js +30 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/server/express/adapter.d.ts +2 -2
  9. package/dist/server/express/index.d.ts +13 -46
  10. package/dist/server/express/index.js +32 -71
  11. package/dist/server/facilitator.d.ts +2 -24
  12. package/dist/server/facilitator.js +0 -36
  13. package/dist/server/index.d.ts +2 -4
  14. package/dist/server/index.js +1 -2
  15. package/dist/server/scheme.d.ts +54 -10
  16. package/dist/server/scheme.js +118 -56
  17. package/dist/types.d.ts +36 -12
  18. package/package.json +33 -32
  19. package/.eslintrc.cjs +0 -29
  20. package/.prettierignore +0 -3
  21. package/.prettierrc +0 -6
  22. package/demo/.env.example +0 -8
  23. package/demo/README.md +0 -125
  24. package/demo/package.json +0 -26
  25. package/demo/src/client.ts +0 -54
  26. package/demo/src/deposit.ts +0 -37
  27. package/demo/src/server.ts +0 -81
  28. package/demo/tsconfig.json +0 -8
  29. package/demo/yarn.lock +0 -925
  30. package/eslint.config.mjs +0 -22
  31. package/src/client/index.ts +0 -1
  32. package/src/client/scheme.ts +0 -111
  33. package/src/index.ts +0 -9
  34. package/src/server/express/adapter.ts +0 -100
  35. package/src/server/express/index.ts +0 -499
  36. package/src/server/facilitator.ts +0 -206
  37. package/src/server/index.ts +0 -10
  38. package/src/server/scheme.ts +0 -229
  39. package/src/types.ts +0 -24
  40. package/tests/client-scheme.test.ts +0 -99
  41. package/tests/facilitator.test.ts +0 -174
  42. package/tsconfig.build.json +0 -5
  43. package/tsconfig.json +0 -17
  44. package/vitest.config.ts +0 -12
@@ -1,499 +0,0 @@
1
- import {
2
- HTTPRequestContext,
3
- PaywallConfig,
4
- PaywallProvider,
5
- x402HTTPResourceServer,
6
- x402ResourceServer,
7
- RoutesConfig,
8
- FacilitatorClient,
9
- } from '@x402/core/server'
10
- import { SchemeNetworkServer, Network } from '@x402/core/types'
11
- import { NextFunction, Request, Response } from 'express'
12
- import { ExpressAdapter } from './adapter.js'
13
- import { FourMicaEvmScheme, SUPPORTED_NETWORKS } from '../scheme.js'
14
- import { FourMicaFacilitatorClient } from '../facilitator.js'
15
-
16
- /**
17
- * Configuration for payment tab handling
18
- */
19
- interface TabConfig {
20
- /**
21
- * The full URL endpoint for opening payment tabs. This URL is injected into
22
- * paymentRequirements.extra and clients use it to open a payment tab.
23
- * When a request matches this endpoint's path, the middleware will parse
24
- * the request body and call the 4mica facilitator to open a tab.
25
- *
26
- * @example "https://api.example.com/x402/tab"
27
- */
28
- advertisedEndpoint: string
29
-
30
- /**
31
- * The lifetime of the payment tab in seconds. Defines how long the tab
32
- * remains valid before expiring.
33
- *
34
- * @example 3600 // 1 hour
35
- */
36
- ttlSeconds?: number
37
- }
38
-
39
- interface ResourceServerInternals {
40
- register: (network: Network, server: SchemeNetworkServer) => unknown
41
- hasExtension: (extension: string) => boolean
42
- registerExtension: (extension: unknown) => unknown
43
- }
44
-
45
- interface HTTPServerInternals {
46
- ResourceServer: ResourceServerInternals
47
- routesConfig: RoutesConfig
48
- }
49
-
50
- function getHTTPServerInternals(httpServer: x402HTTPResourceServer): HTTPServerInternals {
51
- return httpServer as unknown as HTTPServerInternals
52
- }
53
-
54
- function registerNetworkServers(httpServer: x402HTTPResourceServer, tabEndpoint: string) {
55
- const schemeServer = new FourMicaEvmScheme(tabEndpoint)
56
- const server = getHTTPServerInternals(httpServer)
57
- SUPPORTED_NETWORKS.forEach((network) => {
58
- server.ResourceServer.register(network, schemeServer)
59
- })
60
- }
61
-
62
- function checkIfBazaarNeeded(routes: RoutesConfig): boolean {
63
- if ('accepts' in routes) {
64
- return !!(routes.extensions && 'bazaar' in routes.extensions)
65
- }
66
-
67
- return Object.values(routes).some((routeConfig) => {
68
- return !!(routeConfig.extensions && 'bazaar' in routeConfig.extensions)
69
- })
70
- }
71
-
72
- interface OpenTabHttpError {
73
- status: number
74
- response: unknown
75
- }
76
-
77
- function isOpenTabHttpError(error: unknown): error is OpenTabHttpError {
78
- if (typeof error !== 'object' || error === null) {
79
- return false
80
- }
81
-
82
- const candidate = error as { status?: unknown; response?: unknown }
83
- return typeof candidate.status === 'number' && 'response' in candidate
84
- }
85
-
86
- /**
87
- * Configuration for registering a payment scheme with a specific network
88
- */
89
- export interface SchemeRegistration {
90
- /**
91
- * The network identifier (e.g., 'eip155:84532', 'solana:mainnet')
92
- */
93
- network: Network
94
-
95
- /**
96
- * The scheme server implementation for this network
97
- */
98
- server: SchemeNetworkServer
99
- }
100
-
101
- /**
102
- * Express payment middleware for x402 protocol (direct HTTP server instance).
103
- *
104
- * Use this when you need to configure HTTP-level hooks.
105
- *
106
- * @param httpServer - Pre-configured x402HTTPResourceServer instance
107
- * @param tabConfig - Configuration for payment tab handling (endpoint URL and TTL)
108
- * @param paywallConfig - Optional configuration for the built-in paywall UI
109
- * @param paywall - Optional custom paywall provider (overrides default)
110
- * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
111
- * @returns Express middleware handler
112
- *
113
- * @example
114
- * ```typescript
115
- * import { paymentMiddlewareFromHTTPServer, x402ResourceServer, x402HTTPResourceServer } from "@x402/express";
116
- *
117
- * const resourceServer = new x402ResourceServer(facilitatorClient)
118
- * .register(NETWORK, new ExactEvmScheme())
119
- *
120
- * const httpServer = new x402HTTPResourceServer(resourceServer, routes)
121
- * .onProtectedRequest(requestHook);
122
- *
123
- * app.use(paymentMiddlewareFromHTTPServer(
124
- * httpServer,
125
- * { advertisedEndpoint: "https://api.example.com/x402/tab" },
126
- * )); * ```
127
- */
128
- export function paymentMiddlewareFromHTTPServer(
129
- httpServer: x402HTTPResourceServer,
130
- tabConfig: TabConfig,
131
- paywallConfig?: PaywallConfig,
132
- paywall?: PaywallProvider,
133
- syncFacilitatorOnStart: boolean = true
134
- ) {
135
- const facilitatorClient = new FourMicaFacilitatorClient()
136
-
137
- registerNetworkServers(httpServer, tabConfig.advertisedEndpoint)
138
-
139
- // Register custom paywall provider if provided
140
- if (paywall) {
141
- httpServer.registerPaywallProvider(paywall)
142
- }
143
-
144
- // Store initialization promise (not the result)
145
- // httpServer.initialize() fetches facilitator support and validates routes
146
- let initPromise: Promise<void> | null = syncFacilitatorOnStart ? httpServer.initialize() : null
147
-
148
- // Dynamically register bazaar extension if routes declare it and not already registered
149
- // Skip if pre-registered (e.g., in serverless environments where static imports are used)
150
- let bazaarPromise: Promise<void> | null = null
151
- const httpServerInternals = getHTTPServerInternals(httpServer)
152
- if (
153
- checkIfBazaarNeeded(httpServerInternals.routesConfig) &&
154
- !httpServerInternals.ResourceServer.hasExtension('bazaar')
155
- ) {
156
- bazaarPromise = import('@x402/extensions/bazaar')
157
- .then(({ bazaarResourceServerExtension }) => {
158
- httpServerInternals.ResourceServer.registerExtension(bazaarResourceServerExtension)
159
- })
160
- .catch((err) => {
161
- console.error('Failed to load bazaar extension:', err)
162
- })
163
- }
164
-
165
- return async (req: Request, res: Response, next: NextFunction) => {
166
- // Check if this request is for the tab opening endpoint
167
- try {
168
- const advertisedUrl = new URL(tabConfig.advertisedEndpoint)
169
- if (req.path === advertisedUrl.pathname) {
170
- // Parse the request body
171
- const { userAddress, paymentRequirements, x402Version } = req.body
172
-
173
- try {
174
- // Call the facilitator to open the tab
175
- const openTabResponse = await facilitatorClient.openTab(
176
- userAddress,
177
- paymentRequirements,
178
- tabConfig.ttlSeconds,
179
- x402Version
180
- )
181
-
182
- // Return the response
183
- return res.json(openTabResponse)
184
- } catch (error) {
185
- if (isOpenTabHttpError(error)) {
186
- return res.status(error.status).json(error.response)
187
- }
188
- console.error('Failed to open tab:', error)
189
- return res.status(500).json({
190
- error: 'Failed to open tab',
191
- details: error instanceof Error ? error.message : 'Unknown error',
192
- })
193
- }
194
- }
195
- } catch (urlError) {
196
- console.error('Invalid advertisedEndpoint URL:', urlError)
197
- }
198
-
199
- // Create adapter and context
200
- const adapter = new ExpressAdapter(req)
201
- const context: HTTPRequestContext = {
202
- adapter,
203
- path: req.path,
204
- method: req.method,
205
- paymentHeader: adapter.getHeader('payment-signature') || adapter.getHeader('x-payment'),
206
- }
207
-
208
- // Check if route requires payment before initializing facilitator
209
- if (!httpServer.requiresPayment(context)) {
210
- return next()
211
- }
212
-
213
- // Only initialize when processing a protected route
214
- if (initPromise) {
215
- await initPromise
216
- initPromise = null // Clear after first await
217
- }
218
-
219
- // Await bazaar extension loading if needed
220
- if (bazaarPromise) {
221
- await bazaarPromise
222
- bazaarPromise = null
223
- }
224
-
225
- // Process payment requirement check
226
- const result = await httpServer.processHTTPRequest(context, paywallConfig)
227
-
228
- // Handle the different result types
229
- switch (result.type) {
230
- case 'no-payment-required':
231
- // No payment needed, proceed directly to the route handler
232
- return next()
233
-
234
- case 'payment-error': {
235
- // Payment required but not provided or invalid
236
- const { response } = result
237
- res.status(response.status)
238
- Object.entries(response.headers).forEach(([key, value]) => {
239
- res.setHeader(key, value)
240
- })
241
- if (response.isHtml) {
242
- res.send(response.body)
243
- } else {
244
- res.json(response.body || {})
245
- }
246
- return
247
- }
248
-
249
- case 'payment-verified': {
250
- // Payment is valid, need to wrap response for settlement
251
- const { paymentPayload, paymentRequirements } = result
252
-
253
- // Intercept and buffer all core methods that can commit response to client
254
- const originalWriteHead = res.writeHead.bind(res)
255
- const originalWrite = res.write.bind(res)
256
- const originalEnd = res.end.bind(res)
257
- const originalFlushHeaders = res.flushHeaders.bind(res)
258
-
259
- type BufferedCall =
260
- | ['writeHead', Parameters<typeof originalWriteHead>]
261
- | ['write', Parameters<typeof originalWrite>]
262
- | ['end', Parameters<typeof originalEnd>]
263
- | ['flushHeaders', []]
264
- let bufferedCalls: BufferedCall[] = []
265
- let settled = false
266
-
267
- // Create a promise that resolves when the handler finishes and calls res.end()
268
- let endCalled: () => void
269
- const endPromise = new Promise<void>((resolve) => {
270
- endCalled = resolve
271
- })
272
-
273
- res.writeHead = function (...args: Parameters<typeof originalWriteHead>) {
274
- if (!settled) {
275
- bufferedCalls.push(['writeHead', args])
276
- return res
277
- }
278
- return originalWriteHead(...args)
279
- } as typeof originalWriteHead
280
-
281
- res.write = function (...args: Parameters<typeof originalWrite>) {
282
- if (!settled) {
283
- bufferedCalls.push(['write', args])
284
- return true
285
- }
286
- return originalWrite(...args)
287
- } as typeof originalWrite
288
-
289
- res.end = function (...args: Parameters<typeof originalEnd>) {
290
- if (!settled) {
291
- bufferedCalls.push(['end', args])
292
- // Signal that the handler has finished
293
- endCalled()
294
- return res
295
- }
296
- return originalEnd(...args)
297
- } as typeof originalEnd
298
-
299
- res.flushHeaders = function () {
300
- if (!settled) {
301
- bufferedCalls.push(['flushHeaders', []])
302
- return
303
- }
304
- return originalFlushHeaders()
305
- }
306
-
307
- // Proceed to the next middleware or route handler
308
- next()
309
-
310
- // Wait for the handler to actually call res.end() before checking status
311
- await endPromise
312
-
313
- // If the response from the protected route is >= 400, do not settle payment
314
- if (res.statusCode >= 400) {
315
- settled = true
316
- res.writeHead = originalWriteHead
317
- res.write = originalWrite
318
- res.end = originalEnd
319
- res.flushHeaders = originalFlushHeaders
320
- // Replay all buffered calls in order
321
- for (const [method, args] of bufferedCalls) {
322
- if (method === 'writeHead')
323
- originalWriteHead(...(args as Parameters<typeof originalWriteHead>))
324
- else if (method === 'write')
325
- originalWrite(...(args as Parameters<typeof originalWrite>))
326
- else if (method === 'end') originalEnd(...(args as Parameters<typeof originalEnd>))
327
- else if (method === 'flushHeaders') originalFlushHeaders()
328
- }
329
- bufferedCalls = []
330
- return
331
- }
332
-
333
- try {
334
- const settleResult = await httpServer.processSettlement(
335
- paymentPayload,
336
- paymentRequirements
337
- )
338
-
339
- // If settlement fails, return an error and do not send the buffered response
340
- if (!settleResult.success) {
341
- bufferedCalls = []
342
- res.status(402).json({
343
- error: 'Settlement failed',
344
- details: settleResult.errorReason,
345
- })
346
- return
347
- }
348
-
349
- // Settlement succeeded - add headers to response
350
- Object.entries(settleResult.headers).forEach(([key, value]) => {
351
- res.setHeader(key, value)
352
- })
353
- } catch (error) {
354
- console.error(error)
355
- // If settlement fails, don't send the buffered response
356
- bufferedCalls = []
357
- res.status(402).json({
358
- error: 'Settlement failed',
359
- details: error instanceof Error ? error.message : 'Unknown error',
360
- })
361
- return
362
- } finally {
363
- settled = true
364
- res.writeHead = originalWriteHead
365
- res.write = originalWrite
366
- res.end = originalEnd
367
- res.flushHeaders = originalFlushHeaders
368
-
369
- // Replay all buffered calls in order
370
- for (const [method, args] of bufferedCalls) {
371
- if (method === 'writeHead')
372
- originalWriteHead(...(args as Parameters<typeof originalWriteHead>))
373
- else if (method === 'write')
374
- originalWrite(...(args as Parameters<typeof originalWrite>))
375
- else if (method === 'end') originalEnd(...(args as Parameters<typeof originalEnd>))
376
- else if (method === 'flushHeaders') originalFlushHeaders()
377
- }
378
- bufferedCalls = []
379
- }
380
- return
381
- }
382
- }
383
- }
384
- }
385
-
386
- /**
387
- * Express payment middleware for x402 protocol (direct server instance).
388
- *
389
- * Use this when you want to pass a pre-configured x402ResourceServer instance.
390
- * This provides more flexibility for testing, custom configuration, and reusing
391
- * server instances across multiple middlewares.
392
- *
393
- * @param routes - Route configurations for protected endpoints
394
- * @param server - Pre-configured x402ResourceServer instance
395
- * @param tabConfig - Configuration for payment tab handling (endpoint URL and TTL)
396
- * @param paywallConfig - Optional configuration for the built-in paywall UI
397
- * @param paywall - Optional custom paywall provider (overrides default)
398
- * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
399
- * @returns Express middleware handler
400
- *
401
- * @example
402
- * ```typescript
403
- * import { paymentMiddleware } from "@x402/express";
404
- *
405
- * const server = new x402ResourceServer(myFacilitatorClient)
406
- * .register(NETWORK, new ExactEvmScheme());
407
- *
408
- * app.use(paymentMiddleware(
409
- * routes,
410
- * server,
411
- * { advertisedEndpoint: "https://api.example.com/x402/tab" },
412
- * ));
413
- * ```
414
- */
415
- export function paymentMiddleware(
416
- routes: RoutesConfig,
417
- server: x402ResourceServer,
418
- tabConfig: TabConfig,
419
- paywallConfig?: PaywallConfig,
420
- paywall?: PaywallProvider,
421
- syncFacilitatorOnStart: boolean = true
422
- ) {
423
- // Create the x402 HTTP server instance with the resource server
424
- const httpServer = new x402HTTPResourceServer(server, routes)
425
-
426
- return paymentMiddlewareFromHTTPServer(
427
- httpServer,
428
- tabConfig,
429
- paywallConfig,
430
- paywall,
431
- syncFacilitatorOnStart
432
- )
433
- }
434
-
435
- /**
436
- * Express payment middleware for x402 protocol (configuration-based).
437
- *
438
- * Use this when you want to quickly set up middleware with simple configuration.
439
- * This function creates and configures the x402ResourceServer internally.
440
- *
441
- * @param routes - Route configurations for protected endpoints
442
- * @param tabConfig - Configuration for payment tab handling
443
- * @param facilitatorClients - Optional facilitator client(s) for payment processing
444
- * @param schemes - Optional array of scheme registrations for server-side payment processing
445
- * @param paywallConfig - Optional configuration for the built-in paywall UI
446
- * @param paywall - Optional custom paywall provider (overrides default)
447
- * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
448
- * @returns Express middleware handler
449
- *
450
- * @example
451
- * ```typescript
452
- * import { paymentMiddlewareFromConfig } from "@x402/express";
453
- *
454
- * app.use(paymentMiddlewareFromConfig(
455
- * routes,
456
- * { advertisedEndpoint: "https://api.example.com/x402/tab" },
457
- * ));
458
- * ```
459
- */
460
- export function paymentMiddlewareFromConfig(
461
- routes: RoutesConfig,
462
- tabConfig: TabConfig,
463
- facilitatorClients?: FacilitatorClient | FacilitatorClient[],
464
- schemes?: SchemeRegistration[],
465
- paywallConfig?: PaywallConfig,
466
- paywall?: PaywallProvider,
467
- syncFacilitatorOnStart: boolean = true
468
- ) {
469
- const facilitators = facilitatorClients
470
- ? Array.isArray(facilitatorClients)
471
- ? facilitatorClients
472
- : [facilitatorClients]
473
- : []
474
-
475
- if (!facilitators.some((c) => c instanceof FourMicaFacilitatorClient)) {
476
- facilitators.push(new FourMicaFacilitatorClient())
477
- }
478
-
479
- const ResourceServer = new x402ResourceServer(facilitators)
480
-
481
- if (schemes) {
482
- schemes.forEach(({ network, server: schemeServer }) => {
483
- ResourceServer.register(network, schemeServer)
484
- })
485
- }
486
-
487
- // Use the direct paymentMiddleware with the configured server
488
- // Note: paymentMiddleware handles dynamic bazaar registration
489
- return paymentMiddleware(
490
- routes,
491
- ResourceServer,
492
- tabConfig,
493
- paywallConfig,
494
- paywall,
495
- syncFacilitatorOnStart
496
- )
497
- }
498
-
499
- export { ExpressAdapter } from './adapter.js'
@@ -1,206 +0,0 @@
1
- import { FacilitatorConfig, HTTPFacilitatorClient } from '@x402/core/server'
2
- import { Network, PaymentPayload, PaymentRequirements, SettleResponse } from '@x402/core/types'
3
-
4
- const DEFAULT_FACILITATOR_URL = 'https://x402.4mica.xyz'
5
-
6
- export interface OpenTabRequest {
7
- userAddress: string
8
- recipientAddress: string
9
- network?: Network
10
- erc20Token?: string
11
- ttlSeconds?: number
12
- }
13
-
14
- export interface OpenTabResponse {
15
- tabId: string
16
- userAddress: string
17
- recipientAddress: string
18
- assetAddress: string
19
- startTimestamp: number
20
- ttlSeconds: number
21
- nextReqId: string
22
- }
23
-
24
- export interface CertificateResponse {
25
- claims: string
26
- signature: string
27
- }
28
-
29
- export type FourMicaSettleResponse = SettleResponse & {
30
- certificate?: CertificateResponse
31
- txHash?: string
32
- networkId?: string
33
- error?: string
34
- }
35
-
36
- export class OpenTabError extends Error {
37
- constructor(
38
- public readonly status: number,
39
- public readonly response: OpenTabResponse
40
- ) {
41
- super(`OpenTab failed with status ${status}`)
42
- this.name = 'OpenTabError'
43
- }
44
- }
45
-
46
- export class FourMicaFacilitatorClient extends HTTPFacilitatorClient {
47
- constructor(config?: FacilitatorConfig) {
48
- super({ ...config, url: config?.url ?? DEFAULT_FACILITATOR_URL })
49
- }
50
-
51
- async openTab(
52
- userAddress: string,
53
- paymentRequirements: PaymentRequirements,
54
- ttlSeconds?: number,
55
- guaranteeVersion?: number
56
- ): Promise<OpenTabResponse> {
57
- let headers: Record<string, string> = {
58
- 'Content-Type': 'application/json',
59
- }
60
-
61
- const authHeaders = await this.createAuthHeaders('tabs')
62
- headers = { ...headers, ...authHeaders.headers }
63
-
64
- const response = await fetch(`${this.url}/tabs`, {
65
- method: 'POST',
66
- headers,
67
- body: JSON.stringify(
68
- this.safeJson({
69
- userAddress,
70
- recipientAddress: paymentRequirements.payTo,
71
- network: paymentRequirements.network,
72
- erc20Token: paymentRequirements.asset,
73
- ttlSeconds,
74
- guaranteeVersion: guaranteeVersion ?? 1,
75
- })
76
- ),
77
- })
78
-
79
- const data = await response.json()
80
-
81
- if (typeof data === 'object' && data !== null && 'tabId' in data) {
82
- const openTabResponse = data as OpenTabResponse
83
- if (!response.ok) {
84
- throw new OpenTabError(response.status, openTabResponse)
85
- }
86
- return openTabResponse
87
- }
88
-
89
- throw new Error(`Facilitator openTab failed (${response.status}): ${JSON.stringify(data)}`)
90
- }
91
-
92
- async settle(
93
- paymentPayload: PaymentPayload,
94
- paymentRequirements: PaymentRequirements
95
- ): Promise<FourMicaSettleResponse> {
96
- let headers: Record<string, string> = {
97
- 'Content-Type': 'application/json',
98
- }
99
-
100
- const authHeaders = await this.createAuthHeaders('settle')
101
- headers = { ...headers, ...authHeaders.headers }
102
-
103
- const response = await fetch(`${this.url}/settle`, {
104
- method: 'POST',
105
- headers,
106
- body: JSON.stringify(
107
- this.safeJson({
108
- x402Version: paymentPayload.x402Version,
109
- paymentPayload,
110
- paymentRequirements,
111
- })
112
- ),
113
- })
114
-
115
- const data = (await response.json()) as Record<string, unknown>
116
- const normalized = normalizeSettleResponse(data, paymentRequirements)
117
-
118
- if (!response.ok || !normalized.success) {
119
- throw new Error(
120
- `Facilitator settle failed (${response.status}): ${normalized.errorReason ?? normalized.error ?? 'unknown error'}`
121
- )
122
- }
123
-
124
- return normalized
125
- }
126
-
127
- /**
128
- * Helper to convert objects to JSON-safe format.
129
- * Handles BigInt and other non-JSON types.
130
- *
131
- * @param obj - The object to convert
132
- * @returns The JSON-safe representation of the object
133
- */
134
- private safeJson<T>(obj: T): T {
135
- return JSON.parse(
136
- JSON.stringify(obj, (_, value) => (typeof value === 'bigint' ? value.toString() : value))
137
- )
138
- }
139
- }
140
-
141
- function normalizeSettleResponse(
142
- payload: Record<string, unknown>,
143
- requirements: PaymentRequirements
144
- ): FourMicaSettleResponse {
145
- const transaction = String(
146
- payload.transaction ??
147
- payload.transactionHash ??
148
- payload.txHash ??
149
- payload.tx_hash ??
150
- payload.hash ??
151
- ''
152
- )
153
- const network = String(
154
- payload.network ?? payload.networkId ?? payload.network_id ?? requirements.network
155
- ) as Network
156
- const errorReason =
157
- typeof payload.errorReason === 'string'
158
- ? payload.errorReason
159
- : typeof payload.error_reason === 'string'
160
- ? payload.error_reason
161
- : typeof payload.error === 'string'
162
- ? payload.error
163
- : typeof payload.message === 'string'
164
- ? payload.message
165
- : undefined
166
-
167
- const certificate =
168
- payload.certificate &&
169
- typeof payload.certificate === 'object' &&
170
- typeof (payload.certificate as Record<string, unknown>).claims === 'string' &&
171
- typeof (payload.certificate as Record<string, unknown>).signature === 'string'
172
- ? {
173
- claims: (payload.certificate as Record<string, string>).claims,
174
- signature: (payload.certificate as Record<string, string>).signature,
175
- }
176
- : undefined
177
-
178
- return {
179
- success: Boolean(payload.success ?? errorReason === undefined),
180
- errorReason,
181
- payer:
182
- typeof payload.payer === 'string'
183
- ? payload.payer
184
- : typeof payload.userAddress === 'string'
185
- ? payload.userAddress
186
- : typeof payload.user_address === 'string'
187
- ? payload.user_address
188
- : undefined,
189
- transaction,
190
- network,
191
- certificate,
192
- txHash:
193
- typeof payload.txHash === 'string'
194
- ? payload.txHash
195
- : typeof payload.tx_hash === 'string'
196
- ? payload.tx_hash
197
- : transaction || undefined,
198
- networkId:
199
- typeof payload.networkId === 'string'
200
- ? payload.networkId
201
- : typeof payload.network_id === 'string'
202
- ? payload.network_id
203
- : network,
204
- error: typeof payload.error === 'string' ? payload.error : undefined,
205
- }
206
- }