x402-bch-axios 2.0.0 → 2.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/index.js CHANGED
@@ -123,13 +123,16 @@ export async function createPaymentHeader (
123
123
  // Support both v1 (minAmountRequired) and v2 (amount) field names
124
124
  const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
125
125
 
126
+ // Check if this is "check my tab" mode (txid === "*")
127
+ const isCheckMyTabMode = txid === '*'
128
+
126
129
  const authorization = {
127
130
  from: signer.address,
128
131
  to: paymentRequirements.payTo,
129
132
  value: amountRequired,
130
- txid,
131
- vout,
132
- amount: signer.paymentAmountSats
133
+ txid: isCheckMyTabMode ? '*' : txid,
134
+ vout: isCheckMyTabMode ? null : vout,
135
+ amount: isCheckMyTabMode ? null : signer.paymentAmountSats
133
136
  }
134
137
 
135
138
  const messageToSign = JSON.stringify(authorization)
@@ -232,10 +235,17 @@ export function withPaymentInterceptor (
232
235
  return Promise.reject(new Error('Missing axios request configuration'))
233
236
  }
234
237
 
238
+ // Prevent infinite loops
235
239
  if (originalConfig.__is402Retry) {
236
240
  return Promise.reject(error)
237
241
  }
238
242
 
243
+ // If this is a "check my tab" retry that failed, fall back to UTXO generation
244
+ if (originalConfig.__is402CheckMyTab) {
245
+ // Clear the flag and proceed with UTXO generation
246
+ originalConfig.__is402CheckMyTab = false
247
+ }
248
+
239
249
  // Parse payment requirements - v2 can come from header, v1 from body
240
250
  let paymentRequired = null
241
251
  let x402Version = 1
@@ -277,6 +287,45 @@ export function withPaymentInterceptor (
277
287
  // Convert to number for calculations (v2 uses strings, v1 uses numbers)
278
288
  const cost = Number(paymentRequirements.amount || paymentRequirements.minAmountRequired)
279
289
 
290
+ // Try "check my tab" mode first if no UTXO is tracked
291
+ if (!currentUtxo.txid && !originalConfig.__is402CheckMyTab) {
292
+ // Attempt "check my tab" mode
293
+ const checkMyTabHeader = await createPaymentHeader(
294
+ signer,
295
+ paymentRequirements,
296
+ x402Version || 2,
297
+ '*', // txid = "*" for check my tab mode
298
+ null, // vout = null for check my tab mode
299
+ resource,
300
+ extensions
301
+ )
302
+
303
+ originalConfig.__is402CheckMyTab = true
304
+ originalConfig.__is402Retry = true
305
+ originalConfig.headers['PAYMENT-SIGNATURE'] = checkMyTabHeader
306
+ originalConfig.headers['Access-Control-Expose-Headers'] = 'PAYMENT-RESPONSE'
307
+
308
+ try {
309
+ const checkMyTabResponse = await axiosInstance.request(originalConfig)
310
+ // "Check my tab" succeeded - return response and continue using check my tab mode
311
+ // Don't update currentUtxo since we're using check my tab mode
312
+ return checkMyTabResponse
313
+ } catch (checkMyTabError) {
314
+ // "Check my tab" failed - check if it's a 402 error
315
+ if (checkMyTabError.response && checkMyTabError.response.status === 402) {
316
+ // 402 error from "check my tab" - fall back to UTXO generation
317
+ // Reset flags and continue with standard flow
318
+ originalConfig.__is402CheckMyTab = false
319
+ originalConfig.__is402Retry = false
320
+ // Continue to UTXO generation logic below
321
+ } else {
322
+ // Non-402 error (network error, etc.) - reject it
323
+ return Promise.reject(checkMyTabError)
324
+ }
325
+ }
326
+ }
327
+
328
+ // Standard mode: use existing UTXO or generate new one
280
329
  let txid = null
281
330
  let vout = null
282
331
  let satsLeft = null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-bch-axios",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Axios wrapper for x402 payment protocol with Bitcoin Cash (BCH) support.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -315,7 +315,12 @@ describe('#index.js', () => {
315
315
  .resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
316
316
  __internals.sendPayment = sendPaymentStub
317
317
 
318
- axiosInstance.request.resolves({ data: 'ok' })
318
+ // First call (check my tab) returns 402, second call (with UTXO) succeeds
319
+ axiosInstance.request
320
+ .onFirstCall()
321
+ .rejects(create402Error())
322
+ .onSecondCall()
323
+ .resolves({ data: 'ok' })
319
324
 
320
325
  withPaymentInterceptor(axiosInstance, signer)
321
326
 
@@ -326,9 +331,10 @@ describe('#index.js', () => {
326
331
 
327
332
  assert.deepEqual(response, { data: 'ok' })
328
333
  assert.isTrue(sendPaymentStub.calledOnce)
329
- assert.isTrue(axiosInstance.request.calledOnce)
334
+ assert.isTrue(axiosInstance.request.calledTwice)
330
335
 
331
- const updatedConfig = axiosInstance.request.firstCall.args[0]
336
+ // Check the second call (with UTXO) - first call was check my tab
337
+ const updatedConfig = axiosInstance.request.secondCall.args[0]
332
338
  assert.isTrue(updatedConfig.__is402Retry)
333
339
  assert.property(updatedConfig.headers, 'PAYMENT-SIGNATURE')
334
340
  assert.propertyVal(
@@ -382,7 +388,22 @@ describe('#index.js', () => {
382
388
  .resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
383
389
  __internals.sendPayment = sendPaymentStub
384
390
 
385
- axiosInstance.request.resolves({ data: 'ok' })
391
+ // First call (check my tab) returns 402, second call (with UTXO) succeeds
392
+ axiosInstance.request
393
+ .onFirstCall()
394
+ .rejects({
395
+ response: {
396
+ status: 402,
397
+ headers: {},
398
+ data: {
399
+ x402Version: 2,
400
+ accepts: [cloneDeep(basePaymentRequirements)]
401
+ }
402
+ },
403
+ config: { headers: {} }
404
+ })
405
+ .onSecondCall()
406
+ .resolves({ data: 'ok' })
386
407
 
387
408
  withPaymentInterceptor(axiosInstance, signer)
388
409
 
@@ -414,9 +435,10 @@ describe('#index.js', () => {
414
435
 
415
436
  assert.deepEqual(response, { data: 'ok' })
416
437
  assert.isTrue(sendPaymentStub.calledOnce)
417
- assert.isTrue(axiosInstance.request.calledOnce)
438
+ assert.isTrue(axiosInstance.request.calledTwice)
418
439
 
419
- const updatedConfig = axiosInstance.request.firstCall.args[0]
440
+ // Check the second call (with UTXO) - first call was check my tab
441
+ const updatedConfig = axiosInstance.request.secondCall.args[0]
420
442
  const headerPayload = JSON.parse(updatedConfig.headers['PAYMENT-SIGNATURE'])
421
443
  assert.equal(headerPayload.x402Version, 2)
422
444
  assert.deepEqual(headerPayload.resource, baseResource)
@@ -432,7 +454,27 @@ describe('#index.js', () => {
432
454
  .resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
433
455
  __internals.sendPayment = sendPaymentStub
434
456
 
435
- axiosInstance.request.resolves({ data: 'ok' })
457
+ // First call (check my tab) returns 402, second call (with UTXO) succeeds
458
+ axiosInstance.request
459
+ .onFirstCall()
460
+ .rejects({
461
+ response: {
462
+ status: 402,
463
+ headers: {},
464
+ data: {
465
+ x402Version: 1,
466
+ accepts: [{
467
+ network: 'bch',
468
+ scheme: 'utxo',
469
+ payTo: 'bitcoincash:qprecv',
470
+ minAmountRequired: 1500
471
+ }]
472
+ }
473
+ },
474
+ config: { headers: {} }
475
+ })
476
+ .onSecondCall()
477
+ .resolves({ data: 'ok' })
436
478
 
437
479
  withPaymentInterceptor(axiosInstance, signer)
438
480