x402-bch-axios 1.1.2 → 2.0.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 CHANGED
@@ -4,6 +4,10 @@ JavaScript helpers for handling HTTP 402 responses against Bitcoin Cash powered
4
4
  x402 endpoints. This package ports the `withPaymentInterceptor` experience from
5
5
  the TypeScript `x402-axios` client and adapts it for BCH UTXO payments.
6
6
 
7
+ **Version 2.0+**: This library now supports x402-bch protocol v2, which includes
8
+ CAIP-2 network identifiers, updated header names, and restructured payment payloads.
9
+ Backward compatibility with v1 responses is maintained.
10
+
7
11
  ## Installation
8
12
 
9
13
  ```bash
@@ -50,11 +54,84 @@ console.log(response.data)
50
54
  - waits for a 402 response,
51
55
  - selects the BCH `utxo` payment requirement (or uses your selector),
52
56
  - funds or reuses a tracked UTXO,
53
- - replays the request with the `X-PAYMENT` header.
57
+ - replays the request with the `PAYMENT-SIGNATURE` header (v2) or `X-PAYMENT` header (v1).
54
58
  - `selectPaymentRequirements(accepts)` — utility for filtering BCH
55
- requirements.
56
- - `createPaymentHeader(...)` — exposed for advanced integrations that need
57
- direct x402 payload handling.
59
+ requirements. Supports both v1 (`bch`) and v2 CAIP-2 (`bip122:*`) network formats.
60
+ - `createPaymentHeader(signer, paymentRequirements, x402Version, txid, vout, resource?, extensions?)` — exposed for advanced integrations that need
61
+ direct x402 payload handling. Returns v2 format by default.
62
+
63
+ ## Protocol Version 2 Changes
64
+
65
+ This library supports x402-bch protocol v2 with the following changes:
66
+
67
+ ### Header Names
68
+ - **v2**: `PAYMENT-SIGNATURE` (replaces `X-PAYMENT`)
69
+ - **v2**: `PAYMENT-RESPONSE` (replaces `X-PAYMENT-RESPONSE`)
70
+
71
+ ### Network Identifiers
72
+ - **v1**: `bch` (simple string)
73
+ - **v2**: `bip122:000000000000000000651ef99cb9fcbe` (CAIP-2 format for BCH mainnet)
74
+
75
+ The library automatically detects and supports both formats.
76
+
77
+ ### Payment Payload Structure
78
+
79
+ **v2 Format:**
80
+ ```json
81
+ {
82
+ "x402Version": 2,
83
+ "resource": {
84
+ "url": "http://localhost:4021/weather",
85
+ "description": "Access to weather data",
86
+ "mimeType": "application/json"
87
+ },
88
+ "accepted": {
89
+ "scheme": "utxo",
90
+ "network": "bip122:000000000000000000651ef99cb9fcbe",
91
+ "amount": "1000",
92
+ "asset": "0x0000000000000000000000000000000000000001",
93
+ "payTo": "bitcoincash:...",
94
+ "maxTimeoutSeconds": 60,
95
+ "extra": {}
96
+ },
97
+ "payload": {
98
+ "signature": "...",
99
+ "authorization": {
100
+ "from": "bitcoincash:...",
101
+ "to": "bitcoincash:...",
102
+ "value": "1000",
103
+ "txid": "...",
104
+ "vout": 0,
105
+ "amount": "2000"
106
+ }
107
+ },
108
+ "extensions": {}
109
+ }
110
+ ```
111
+
112
+ **Key differences from v1:**
113
+ - Removed top-level `scheme` and `network` fields
114
+ - Added `accepted` field containing the selected PaymentRequirements
115
+ - Added optional `resource` and `extensions` fields
116
+ - Field name change: `minAmountRequired` → `amount` (library supports both for compatibility)
117
+
118
+ ### Response Parsing
119
+
120
+ The library supports both v1 and v2 response formats:
121
+ - **v2**: Parses from `PAYMENT-REQUIRED` header (base64-encoded JSON)
122
+ - **v1**: Falls back to response body format
123
+
124
+ ## Migration from v1
125
+
126
+ If you're upgrading from v1, the library maintains backward compatibility:
127
+ - v1 responses are automatically detected and handled
128
+ - v1 field names (`minAmountRequired`, `bch` network) are supported
129
+ - No code changes required for basic usage
130
+
131
+ However, for full v2 support:
132
+ - Update your server to send v2 responses
133
+ - Use CAIP-2 network identifiers in payment requirements
134
+ - Expect `PAYMENT-SIGNATURE` and `PAYMENT-RESPONSE` headers
58
135
 
59
136
  ## Licence
60
137
 
package/index.js CHANGED
@@ -62,15 +62,34 @@ export function createSigner (privateKeyWIF, paymentAmountSats) {
62
62
 
63
63
  export const createBCHSigner = createSigner
64
64
 
65
+ /**
66
+ * Normalizes network identifier to support both v1 and v2 formats.
67
+ * v1 uses 'bch', v2 uses CAIP-2 format 'bip122:000000000000000000651ef99cb9fcbe'
68
+ *
69
+ * @param {string} network - Network identifier
70
+ * @returns {boolean} True if the network is BCH (mainnet)
71
+ */
72
+ function isBCHNetwork (network) {
73
+ if (!network) return false
74
+ // v1 format
75
+ if (network === 'bch') return true
76
+ // v2 CAIP-2 format for BCH mainnet
77
+ if (network === 'bip122:000000000000000000651ef99cb9fcbe') return true
78
+ // v2 CAIP-2 format pattern matching (bip122:*)
79
+ if (network.startsWith('bip122:')) return true
80
+ return false
81
+ }
82
+
65
83
  /**
66
84
  * Selects BCH `utxo` payment requirements from a 402 accepts array.
85
+ * Supports both v1 ('bch') and v2 (CAIP-2 'bip122:*') network formats.
67
86
  *
68
87
  * @param {Array} accepts - Array of payment requirements objects
69
88
  * @returns {Object} First BCH `utxo` payment requirement
70
89
  */
71
90
  export function selectPaymentRequirements (accepts = []) {
72
91
  const bchRequirements = accepts.filter(req => {
73
- return req?.network === 'bch' && req?.scheme === 'utxo'
92
+ return isBCHNetwork(req?.network) && req?.scheme === 'utxo'
74
93
  })
75
94
 
76
95
  if (bchRequirements.length === 0) {
@@ -81,26 +100,33 @@ export function selectPaymentRequirements (accepts = []) {
81
100
  }
82
101
 
83
102
  /**
84
- * Builds the X-PAYMENT header payload for BCH transfers.
103
+ * Builds the PAYMENT-SIGNATURE header payload for BCH transfers.
85
104
  *
86
105
  * @param {ReturnType<typeof createSigner>} signer
87
106
  * @param {Object} paymentRequirements
88
107
  * @param {number} x402Version
89
108
  * @param {string|null} txid
90
109
  * @param {number|null} vout
110
+ * @param {Object|null} resource - Optional ResourceInfo object
111
+ * @param {Object|null} extensions - Optional extensions object
91
112
  * @returns {Promise<string>}
92
113
  */
93
114
  export async function createPaymentHeader (
94
115
  signer,
95
116
  paymentRequirements,
96
- x402Version = 1,
117
+ x402Version = 2,
97
118
  txid = null,
98
- vout = null
119
+ vout = null,
120
+ resource = null,
121
+ extensions = null
99
122
  ) {
123
+ // Support both v1 (minAmountRequired) and v2 (amount) field names
124
+ const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
125
+
100
126
  const authorization = {
101
127
  from: signer.address,
102
128
  to: paymentRequirements.payTo,
103
- value: paymentRequirements.minAmountRequired,
129
+ value: amountRequired,
104
130
  txid,
105
131
  vout,
106
132
  amount: signer.paymentAmountSats
@@ -109,14 +135,27 @@ export async function createPaymentHeader (
109
135
  const messageToSign = JSON.stringify(authorization)
110
136
  const signature = signer.signMessage(messageToSign)
111
137
 
138
+ // Build accepted PaymentRequirements object
139
+ const accepted = {
140
+ scheme: paymentRequirements.scheme || 'utxo',
141
+ network: paymentRequirements.network || 'bip122:000000000000000000651ef99cb9fcbe',
142
+ amount: amountRequired,
143
+ asset: paymentRequirements.asset,
144
+ payTo: paymentRequirements.payTo,
145
+ maxTimeoutSeconds: paymentRequirements.maxTimeoutSeconds,
146
+ extra: paymentRequirements.extra || {}
147
+ }
148
+
149
+ // Build v2 PaymentPayload structure
112
150
  const paymentHeader = {
113
151
  x402Version,
114
- scheme: paymentRequirements.scheme || 'utxo',
115
- network: paymentRequirements.network || 'bch',
152
+ ...(resource && { resource }),
153
+ accepted,
116
154
  payload: {
117
155
  signature,
118
156
  authorization
119
- }
157
+ },
158
+ ...(extensions && { extensions })
120
159
  }
121
160
 
122
161
  return JSON.stringify(paymentHeader)
@@ -124,7 +163,9 @@ export async function createPaymentHeader (
124
163
 
125
164
  async function sendPayment (signer, paymentRequirements, bchServerConfig = {}) {
126
165
  const { apiType, bchServerURL } = bchServerConfig
127
- const paymentAmountSats = signer.paymentAmountSats || paymentRequirements.minAmountRequired
166
+ // Support both v1 (minAmountRequired) and v2 (amount) field names
167
+ const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
168
+ const paymentAmountSats = signer.paymentAmountSats || amountRequired
128
169
 
129
170
  const bchWallet = new dependencies.BCHWallet(signer.wif, {
130
171
  interface: apiType,
@@ -195,13 +236,46 @@ export function withPaymentInterceptor (
195
236
  return Promise.reject(error)
196
237
  }
197
238
 
198
- const { x402Version, accepts } = error.response.data || {}
239
+ // Parse payment requirements - v2 can come from header, v1 from body
240
+ let paymentRequired = null
241
+ let x402Version = 1
242
+ let accepts = []
243
+ let resource = null
244
+ let extensions = null
245
+
246
+ // Try v2 PAYMENT-REQUIRED header first (base64-encoded)
247
+ const paymentRequiredHeader = error.response.headers['payment-required'] ||
248
+ error.response.headers['PAYMENT-REQUIRED']
249
+ if (paymentRequiredHeader) {
250
+ try {
251
+ const decoded = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8')
252
+ paymentRequired = JSON.parse(decoded)
253
+ x402Version = paymentRequired.x402Version || 2
254
+ accepts = paymentRequired.accepts || []
255
+ resource = paymentRequired.resource
256
+ extensions = paymentRequired.extensions
257
+ } catch (parseError) {
258
+ // If header parsing fails, fall back to body
259
+ }
260
+ }
261
+
262
+ // Fall back to body format (v1 or v2)
263
+ if (!paymentRequired) {
264
+ const body = error.response.data || {}
265
+ x402Version = body.x402Version || 1
266
+ accepts = body.accepts || []
267
+ resource = body.resource
268
+ extensions = body.extensions
269
+ }
270
+
199
271
  if (!accepts || !Array.isArray(accepts) || accepts.length === 0) {
200
272
  return Promise.reject(new Error('No payment requirements found in 402 response'))
201
273
  }
202
274
 
203
275
  const paymentRequirements = paymentRequirementsSelector(accepts)
204
- const cost = paymentRequirements.minAmountRequired
276
+ // Support both v1 (minAmountRequired) and v2 (amount) field names
277
+ // Convert to number for calculations (v2 uses strings, v1 uses numbers)
278
+ const cost = Number(paymentRequirements.amount || paymentRequirements.minAmountRequired)
205
279
 
206
280
  let txid = null
207
281
  let vout = null
@@ -229,14 +303,16 @@ export function withPaymentInterceptor (
229
303
  const paymentHeader = await createPaymentHeader(
230
304
  signer,
231
305
  paymentRequirements,
232
- x402Version || 1,
306
+ x402Version || 2,
233
307
  txid,
234
- vout
308
+ vout,
309
+ resource,
310
+ extensions
235
311
  )
236
312
 
237
313
  originalConfig.__is402Retry = true
238
- originalConfig.headers['X-PAYMENT'] = paymentHeader
239
- originalConfig.headers['Access-Control-Expose-Headers'] = 'X-PAYMENT-RESPONSE'
314
+ originalConfig.headers['PAYMENT-SIGNATURE'] = paymentHeader
315
+ originalConfig.headers['Access-Control-Expose-Headers'] = 'PAYMENT-RESPONSE'
240
316
 
241
317
  const secondResponse = await axiosInstance.request(originalConfig)
242
318
  return secondResponse
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-bch-axios",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Axios wrapper for x402 payment protocol with Bitcoin Cash (BCH) support.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -73,7 +73,7 @@ describe('#index.js', () => {
73
73
  })
74
74
 
75
75
  describe('#selectPaymentRequirements', () => {
76
- it('should select the first BCH utxo requirement', () => {
76
+ it('should select the first BCH utxo requirement (v1 format)', () => {
77
77
  const accepts = [
78
78
  { network: 'eth', scheme: 'account' },
79
79
  { network: 'bch', scheme: 'utxo', payTo: 'addr1' },
@@ -84,6 +84,17 @@ describe('#index.js', () => {
84
84
  assert.deepEqual(req, { network: 'bch', scheme: 'utxo', payTo: 'addr1' })
85
85
  })
86
86
 
87
+ it('should select the first BCH utxo requirement (v2 CAIP-2 format)', () => {
88
+ const accepts = [
89
+ { network: 'eip155:84532', scheme: 'exact' },
90
+ { network: 'bip122:000000000000000000651ef99cb9fcbe', scheme: 'utxo', payTo: 'addr1' },
91
+ { network: 'bch', scheme: 'utxo', payTo: 'addr2' }
92
+ ]
93
+
94
+ const req = selectPaymentRequirements(accepts)
95
+ assert.deepEqual(req, { network: 'bip122:000000000000000000651ef99cb9fcbe', scheme: 'utxo', payTo: 'addr1' })
96
+ })
97
+
87
98
  it('should throw if no BCH utxo requirement exists', () => {
88
99
  assert.throws(
89
100
  () => selectPaymentRequirements([{ network: 'btc', scheme: 'utxo' }]),
@@ -93,7 +104,7 @@ describe('#index.js', () => {
93
104
  })
94
105
 
95
106
  describe('#createPaymentHeader', () => {
96
- it('should build a valid payment header payload', async () => {
107
+ it('should build a valid v2 payment header payload', async () => {
97
108
  const signer = {
98
109
  address: 'bitcoincash:qptest',
99
110
  paymentAmountSats: 2000,
@@ -102,9 +113,18 @@ describe('#index.js', () => {
102
113
 
103
114
  const paymentRequirements = {
104
115
  payTo: 'bitcoincash:qprecv',
105
- minAmountRequired: 1500,
116
+ amount: '1500',
106
117
  scheme: 'utxo',
107
- network: 'bch'
118
+ network: 'bip122:000000000000000000651ef99cb9fcbe',
119
+ asset: '0x0000000000000000000000000000000000000001',
120
+ maxTimeoutSeconds: 60,
121
+ extra: {}
122
+ }
123
+
124
+ const resource = {
125
+ url: 'http://localhost:4021/weather',
126
+ description: 'Access to weather data',
127
+ mimeType: 'application/json'
108
128
  }
109
129
 
110
130
  const header = await createPaymentHeader(
@@ -112,28 +132,65 @@ describe('#index.js', () => {
112
132
  paymentRequirements,
113
133
  2,
114
134
  'tx123',
115
- 0
135
+ 0,
136
+ resource
116
137
  )
117
138
 
118
139
  const parsed = JSON.parse(header)
119
- assert.deepEqual(parsed, {
120
- x402Version: 2,
140
+ assert.equal(parsed.x402Version, 2)
141
+ assert.deepEqual(parsed.resource, resource)
142
+ assert.deepEqual(parsed.accepted, {
121
143
  scheme: 'utxo',
122
- network: 'bch',
123
- payload: {
124
- signature: 'mock-signature',
125
- authorization: {
126
- from: 'bitcoincash:qptest',
127
- to: 'bitcoincash:qprecv',
128
- value: 1500,
129
- txid: 'tx123',
130
- vout: 0,
131
- amount: 2000
132
- }
144
+ network: 'bip122:000000000000000000651ef99cb9fcbe',
145
+ amount: '1500',
146
+ asset: '0x0000000000000000000000000000000000000001',
147
+ payTo: 'bitcoincash:qprecv',
148
+ maxTimeoutSeconds: 60,
149
+ extra: {}
150
+ })
151
+ assert.deepEqual(parsed.payload, {
152
+ signature: 'mock-signature',
153
+ authorization: {
154
+ from: 'bitcoincash:qptest',
155
+ to: 'bitcoincash:qprecv',
156
+ value: '1500',
157
+ txid: 'tx123',
158
+ vout: 0,
159
+ amount: 2000
133
160
  }
134
161
  })
162
+ // v2 should not have top-level scheme/network
163
+ assert.isUndefined(parsed.scheme)
164
+ assert.isUndefined(parsed.network)
135
165
  assert.isTrue(signer.signMessage.calledOnce)
136
166
  })
167
+
168
+ it('should support v1 minAmountRequired field for backward compatibility', async () => {
169
+ const signer = {
170
+ address: 'bitcoincash:qptest',
171
+ paymentAmountSats: 2000,
172
+ signMessage: sandbox.stub().returns('mock-signature')
173
+ }
174
+
175
+ const paymentRequirements = {
176
+ payTo: 'bitcoincash:qprecv',
177
+ minAmountRequired: 1500, // v1 field name
178
+ scheme: 'utxo',
179
+ network: 'bch'
180
+ }
181
+
182
+ const header = await createPaymentHeader(
183
+ signer,
184
+ paymentRequirements,
185
+ 2,
186
+ 'tx123',
187
+ 0
188
+ )
189
+
190
+ const parsed = JSON.parse(header)
191
+ assert.equal(parsed.accepted.amount, 1500)
192
+ assert.equal(parsed.payload.authorization.value, 1500)
193
+ })
137
194
  })
138
195
 
139
196
  describe('#withPaymentInterceptor', () => {
@@ -157,19 +214,31 @@ describe('#index.js', () => {
157
214
  }
158
215
 
159
216
  const basePaymentRequirements = {
160
- network: 'bch',
217
+ network: 'bip122:000000000000000000651ef99cb9fcbe',
161
218
  scheme: 'utxo',
162
219
  payTo: 'bitcoincash:qprecv',
163
- minAmountRequired: 1500
220
+ amount: '1500',
221
+ asset: '0x0000000000000000000000000000000000000001',
222
+ maxTimeoutSeconds: 60,
223
+ extra: {}
224
+ }
225
+
226
+ const baseResource = {
227
+ url: 'http://localhost:4021/weather',
228
+ description: 'Access to weather data',
229
+ mimeType: 'application/json'
164
230
  }
165
231
 
166
232
  function create402Error (overrides = {}) {
167
233
  const defaultError = {
168
234
  response: {
169
235
  status: 402,
236
+ headers: {},
170
237
  data: {
171
- x402Version: 1,
172
- accepts: [cloneDeep(basePaymentRequirements)]
238
+ x402Version: 2,
239
+ resource: cloneDeep(baseResource),
240
+ accepts: [cloneDeep(basePaymentRequirements)],
241
+ extensions: {}
173
242
  }
174
243
  },
175
244
  config: {
@@ -223,14 +292,16 @@ describe('#index.js', () => {
223
292
 
224
293
  const [, errorHandler] = axiosInstance.interceptors.response.use.firstCall.args
225
294
  const error = create402Error({
226
- response: { status: 402, data: { accepts: [] } }
295
+ response: { status: 402, headers: {}, data: { accepts: [] } }
227
296
  })
228
297
 
229
298
  try {
230
299
  await errorHandler(error)
231
300
  assert.fail('Expected rejection')
232
301
  } catch (err) {
233
- assert.match(err.message, /No payment requirements/)
302
+ // Should reject with "No payment requirements found in 402 response"
303
+ // or "No BCH payment requirements found in 402 response" from selector
304
+ assert.match(err.message, /No.*payment requirements/)
234
305
  }
235
306
  })
236
307
 
@@ -259,14 +330,16 @@ describe('#index.js', () => {
259
330
 
260
331
  const updatedConfig = axiosInstance.request.firstCall.args[0]
261
332
  assert.isTrue(updatedConfig.__is402Retry)
262
- assert.property(updatedConfig.headers, 'X-PAYMENT')
333
+ assert.property(updatedConfig.headers, 'PAYMENT-SIGNATURE')
263
334
  assert.propertyVal(
264
335
  updatedConfig.headers,
265
336
  'Access-Control-Expose-Headers',
266
- 'X-PAYMENT-RESPONSE'
337
+ 'PAYMENT-RESPONSE'
267
338
  )
268
339
 
269
- const headerPayload = JSON.parse(updatedConfig.headers['X-PAYMENT'])
340
+ const headerPayload = JSON.parse(updatedConfig.headers['PAYMENT-SIGNATURE'])
341
+ assert.equal(headerPayload.x402Version, 2)
342
+ assert.deepEqual(headerPayload.accepted, basePaymentRequirements)
270
343
  assert.equal(headerPayload.payload.authorization.txid, 'tx123')
271
344
  assert.equal(__internals.currentUtxo.txid, 'tx123')
272
345
  assert.equal(__internals.currentUtxo.satsLeft, 500)
@@ -298,5 +371,97 @@ describe('#index.js', () => {
298
371
  assert.equal(__internals.currentUtxo.txid, 'cached')
299
372
  assert.equal(__internals.currentUtxo.satsLeft, 500)
300
373
  })
374
+
375
+ it('should parse v2 response from PAYMENT-REQUIRED header', async () => {
376
+ const axiosInstance = createAxiosInstance()
377
+ const signer = createSignerStub()
378
+ signer.signMessage.returns('signed')
379
+
380
+ const sendPaymentStub = sandbox
381
+ .stub()
382
+ .resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
383
+ __internals.sendPayment = sendPaymentStub
384
+
385
+ axiosInstance.request.resolves({ data: 'ok' })
386
+
387
+ withPaymentInterceptor(axiosInstance, signer)
388
+
389
+ const [, errorHandler] = axiosInstance.interceptors.response.use.firstCall.args
390
+
391
+ // Create v2 response with PAYMENT-REQUIRED header
392
+ const paymentRequired = {
393
+ x402Version: 2,
394
+ resource: baseResource,
395
+ accepts: [cloneDeep(basePaymentRequirements)],
396
+ extensions: {}
397
+ }
398
+ const headerValue = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')
399
+
400
+ const error = {
401
+ response: {
402
+ status: 402,
403
+ headers: {
404
+ 'payment-required': headerValue
405
+ },
406
+ data: {}
407
+ },
408
+ config: {
409
+ headers: {}
410
+ }
411
+ }
412
+
413
+ const response = await errorHandler(error)
414
+
415
+ assert.deepEqual(response, { data: 'ok' })
416
+ assert.isTrue(sendPaymentStub.calledOnce)
417
+ assert.isTrue(axiosInstance.request.calledOnce)
418
+
419
+ const updatedConfig = axiosInstance.request.firstCall.args[0]
420
+ const headerPayload = JSON.parse(updatedConfig.headers['PAYMENT-SIGNATURE'])
421
+ assert.equal(headerPayload.x402Version, 2)
422
+ assert.deepEqual(headerPayload.resource, baseResource)
423
+ })
424
+
425
+ it('should support v1 response format for backward compatibility', async () => {
426
+ const axiosInstance = createAxiosInstance()
427
+ const signer = createSignerStub()
428
+ signer.signMessage.returns('signed')
429
+
430
+ const sendPaymentStub = sandbox
431
+ .stub()
432
+ .resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
433
+ __internals.sendPayment = sendPaymentStub
434
+
435
+ axiosInstance.request.resolves({ data: 'ok' })
436
+
437
+ withPaymentInterceptor(axiosInstance, signer)
438
+
439
+ const [, errorHandler] = axiosInstance.interceptors.response.use.firstCall.args
440
+
441
+ // Create v1 response format
442
+ const error = {
443
+ response: {
444
+ status: 402,
445
+ headers: {},
446
+ data: {
447
+ x402Version: 1,
448
+ accepts: [{
449
+ network: 'bch',
450
+ scheme: 'utxo',
451
+ payTo: 'bitcoincash:qprecv',
452
+ minAmountRequired: 1500
453
+ }]
454
+ }
455
+ },
456
+ config: {
457
+ headers: {}
458
+ }
459
+ }
460
+
461
+ const response = await errorHandler(error)
462
+
463
+ assert.deepEqual(response, { data: 'ok' })
464
+ assert.isTrue(sendPaymentStub.calledOnce)
465
+ })
301
466
  })
302
467
  })