x402-bch-axios 2.0.0 → 2.1.1

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)
@@ -162,33 +165,57 @@ export async function createPaymentHeader (
162
165
  }
163
166
 
164
167
  async function sendPayment (signer, paymentRequirements, bchServerConfig = {}) {
165
- const { apiType, bchServerURL } = bchServerConfig
166
- // Support both v1 (minAmountRequired) and v2 (amount) field names
167
- const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
168
- const paymentAmountSats = signer.paymentAmountSats || amountRequired
168
+ try {
169
+ const { apiType, bchServerURL } = bchServerConfig
170
+ // Support both v1 (minAmountRequired) and v2 (amount) field names
171
+ const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
172
+ const paymentAmountSats = signer.paymentAmountSats || amountRequired
173
+
174
+ const bchWallet = new dependencies.BCHWallet(signer.wif, {
175
+ interface: apiType,
176
+ restURL: bchServerURL
177
+ })
178
+ // console.log(`sendPayment() - interface: ${apiType}, restURL: ${bchServerURL}, wif: ${signer.wif}, payTo: ${paymentRequirements.payTo}, paymentAmountSats: ${paymentAmountSats}`)
179
+ console.log(`Sending ${paymentAmountSats} for x402 API payment to ${paymentRequirements.payTo}`)
180
+ await bchWallet.initialize()
181
+
182
+ const retryQueue = new dependencies.RetryQueue()
183
+ const receivers = [
184
+ {
185
+ address: paymentRequirements.payTo,
186
+ amountSat: paymentAmountSats
187
+ }
188
+ ]
169
189
 
170
- const bchWallet = new dependencies.BCHWallet(signer.wif, {
171
- interface: apiType,
172
- restURL: bchServerURL
173
- })
174
- // console.log(`sendPayment() - interface: ${apiType}, restURL: ${bchServerURL}, wif: ${signer.wif}, payTo: ${paymentRequirements.payTo}, paymentAmountSats: ${paymentAmountSats}`)
175
- console.log(`Sending ${paymentAmountSats} for x402 API payment to ${paymentRequirements.payTo}`)
176
- await bchWallet.initialize()
177
-
178
- const retryQueue = new dependencies.RetryQueue()
179
- const receivers = [
180
- {
181
- address: paymentRequirements.payTo,
182
- amountSat: paymentAmountSats
190
+ // Wrap send function to detect "Insufficient balance" errors
191
+ const sendWithRetry = async (receivers) => {
192
+ try {
193
+ return await bchWallet.send(receivers)
194
+ } catch (error) {
195
+ // Check if error message contains "Insufficient balance"
196
+ if (error.message && error.message.includes('Insufficient balance')) {
197
+ return null
198
+ }
199
+
200
+ // Re-throw other errors normally (will be retried)
201
+ throw error
202
+ }
183
203
  }
184
- ]
185
204
 
186
- const txid = await retryQueue.addToQueue(bchWallet.send.bind(bchWallet), receivers)
205
+ const txid = await retryQueue.addToQueue(sendWithRetry, receivers)
187
206
 
188
- return {
189
- txid,
190
- vout: 0,
191
- satsSent: paymentAmountSats
207
+ if (txid === null) {
208
+ throw new Error('Insufficient balance')
209
+ }
210
+
211
+ return {
212
+ txid,
213
+ vout: 0,
214
+ satsSent: paymentAmountSats
215
+ }
216
+ } catch (err) {
217
+ console.error('Error in x402-bch-axios/sendPayment(): ', err.message)
218
+ throw err
192
219
  }
193
220
  }
194
221
 
@@ -232,10 +259,17 @@ export function withPaymentInterceptor (
232
259
  return Promise.reject(new Error('Missing axios request configuration'))
233
260
  }
234
261
 
262
+ // Prevent infinite loops
235
263
  if (originalConfig.__is402Retry) {
236
264
  return Promise.reject(error)
237
265
  }
238
266
 
267
+ // If this is a "check my tab" retry that failed, fall back to UTXO generation
268
+ if (originalConfig.__is402CheckMyTab) {
269
+ // Clear the flag and proceed with UTXO generation
270
+ originalConfig.__is402CheckMyTab = false
271
+ }
272
+
239
273
  // Parse payment requirements - v2 can come from header, v1 from body
240
274
  let paymentRequired = null
241
275
  let x402Version = 1
@@ -277,6 +311,45 @@ export function withPaymentInterceptor (
277
311
  // Convert to number for calculations (v2 uses strings, v1 uses numbers)
278
312
  const cost = Number(paymentRequirements.amount || paymentRequirements.minAmountRequired)
279
313
 
314
+ // Try "check my tab" mode first if no UTXO is tracked
315
+ if (!currentUtxo.txid && !originalConfig.__is402CheckMyTab) {
316
+ // Attempt "check my tab" mode
317
+ const checkMyTabHeader = await createPaymentHeader(
318
+ signer,
319
+ paymentRequirements,
320
+ x402Version || 2,
321
+ '*', // txid = "*" for check my tab mode
322
+ null, // vout = null for check my tab mode
323
+ resource,
324
+ extensions
325
+ )
326
+
327
+ originalConfig.__is402CheckMyTab = true
328
+ originalConfig.__is402Retry = true
329
+ originalConfig.headers['PAYMENT-SIGNATURE'] = checkMyTabHeader
330
+ originalConfig.headers['Access-Control-Expose-Headers'] = 'PAYMENT-RESPONSE'
331
+
332
+ try {
333
+ const checkMyTabResponse = await axiosInstance.request(originalConfig)
334
+ // "Check my tab" succeeded - return response and continue using check my tab mode
335
+ // Don't update currentUtxo since we're using check my tab mode
336
+ return checkMyTabResponse
337
+ } catch (checkMyTabError) {
338
+ // "Check my tab" failed - check if it's a 402 error
339
+ if (checkMyTabError.response && checkMyTabError.response.status === 402) {
340
+ // 402 error from "check my tab" - fall back to UTXO generation
341
+ // Reset flags and continue with standard flow
342
+ originalConfig.__is402CheckMyTab = false
343
+ originalConfig.__is402Retry = false
344
+ // Continue to UTXO generation logic below
345
+ } else {
346
+ // Non-402 error (network error, etc.) - reject it
347
+ return Promise.reject(checkMyTabError)
348
+ }
349
+ }
350
+ }
351
+
352
+ // Standard mode: use existing UTXO or generate new one
280
353
  let txid = null
281
354
  let vout = null
282
355
  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.1",
4
4
  "description": "Axios wrapper for x402 payment protocol with Bitcoin Cash (BCH) support.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "repository": "x402-bch/x402-bch-axios",
27
27
  "dependencies": {
28
28
  "@chris.troutner/retry-queue": "1.0.11",
29
- "minimal-slp-wallet": "7.0.2"
29
+ "minimal-slp-wallet": "7.0.5"
30
30
  },
31
31
  "devDependencies": {
32
32
  "c8": "10.1.3",
@@ -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
 
@@ -464,4 +506,343 @@ describe('#index.js', () => {
464
506
  assert.isTrue(sendPaymentStub.calledOnce)
465
507
  })
466
508
  })
509
+
510
+ describe('#sendPayment', () => {
511
+ function createSignerStub () {
512
+ return {
513
+ wif: 'test-wif',
514
+ paymentAmountSats: 2000,
515
+ address: 'bitcoincash:qptest'
516
+ }
517
+ }
518
+
519
+ function createPaymentRequirementsStub () {
520
+ return {
521
+ payTo: 'bitcoincash:qprecv',
522
+ amount: '1500',
523
+ scheme: 'utxo',
524
+ network: 'bip122:000000000000000000651ef99cb9fcbe'
525
+ }
526
+ }
527
+
528
+ it('should successfully send payment and return txid, vout, and satsSent', async () => {
529
+ const signer = createSignerStub()
530
+ const paymentRequirements = createPaymentRequirementsStub()
531
+ const bchServerConfig = {
532
+ apiType: 'rest-api',
533
+ bchServerURL: 'https://api.example.com'
534
+ }
535
+
536
+ const mockBchWallet = {
537
+ initialize: sandbox.stub().resolves(),
538
+ send: sandbox.stub().resolves('tx123')
539
+ }
540
+
541
+ const mockRetryQueue = {
542
+ addToQueue: sandbox.stub().resolves('tx123')
543
+ }
544
+
545
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
546
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
547
+
548
+ __setDependencies({
549
+ BCHWallet: BCHWalletStub,
550
+ RetryQueue: RetryQueueStub
551
+ })
552
+
553
+ const result = await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
554
+
555
+ assert.deepEqual(result, {
556
+ txid: 'tx123',
557
+ vout: 0,
558
+ satsSent: 2000
559
+ })
560
+
561
+ assert.isTrue(BCHWalletStub.calledOnce)
562
+ assert.deepEqual(BCHWalletStub.firstCall.args[0], 'test-wif')
563
+ assert.deepEqual(BCHWalletStub.firstCall.args[1], {
564
+ interface: 'rest-api',
565
+ restURL: 'https://api.example.com'
566
+ })
567
+ assert.isTrue(mockBchWallet.initialize.calledOnce)
568
+ assert.isTrue(RetryQueueStub.calledOnce)
569
+ assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
570
+
571
+ // Verify sendWithRetry was called with receivers
572
+ const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
573
+ const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
574
+ assert.isFunction(sendWithRetry)
575
+ assert.deepEqual(receivers, [{
576
+ address: 'bitcoincash:qprecv',
577
+ amountSat: 2000
578
+ }])
579
+
580
+ __resetDependencies()
581
+ })
582
+
583
+ it('should throw "Insufficient balance" error when sendWithRetry returns null', async () => {
584
+ const signer = createSignerStub()
585
+ const paymentRequirements = createPaymentRequirementsStub()
586
+ const bchServerConfig = {}
587
+
588
+ const mockBchWallet = {
589
+ initialize: sandbox.stub().resolves(),
590
+ send: sandbox.stub().rejects(new Error('Insufficient balance'))
591
+ }
592
+
593
+ const mockRetryQueue = {
594
+ addToQueue: sandbox.stub().resolves(null) // sendWithRetry returns null for insufficient balance
595
+ }
596
+
597
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
598
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
599
+
600
+ __setDependencies({
601
+ BCHWallet: BCHWalletStub,
602
+ RetryQueue: RetryQueueStub
603
+ })
604
+
605
+ try {
606
+ await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
607
+ assert.fail('Expected "Insufficient balance" error to be thrown')
608
+ } catch (err) {
609
+ assert.equal(err.message, 'Insufficient balance')
610
+ }
611
+
612
+ // Verify sendWithRetry was called and handled the error
613
+ assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
614
+ const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
615
+
616
+ // Test sendWithRetry directly to verify it returns null for insufficient balance
617
+ try {
618
+ const result = await sendWithRetry([{ address: 'test', amountSat: 1000 }])
619
+ assert.strictEqual(result, null)
620
+ } catch (err) {
621
+ assert.fail('sendWithRetry should return null, not throw')
622
+ }
623
+
624
+ __resetDependencies()
625
+ })
626
+
627
+ it('should handle "Insufficient balance" error in sendWithRetry wrapper', async () => {
628
+ const signer = createSignerStub()
629
+ const paymentRequirements = createPaymentRequirementsStub()
630
+
631
+ const insufficientBalanceError = new Error('Insufficient balance')
632
+ const mockBchWallet = {
633
+ initialize: sandbox.stub().resolves(),
634
+ send: sandbox.stub().rejects(insufficientBalanceError)
635
+ }
636
+
637
+ const mockRetryQueue = {
638
+ addToQueue: sandbox.stub().callsFake(async (fn, args) => {
639
+ // Simulate what RetryQueue does - call the function
640
+ return await fn(args)
641
+ })
642
+ }
643
+
644
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
645
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
646
+
647
+ __setDependencies({
648
+ BCHWallet: BCHWalletStub,
649
+ RetryQueue: RetryQueueStub
650
+ })
651
+
652
+ try {
653
+ await __internals.sendPayment(signer, paymentRequirements)
654
+ assert.fail('Expected "Insufficient balance" error to be thrown')
655
+ } catch (err) {
656
+ assert.equal(err.message, 'Insufficient balance')
657
+ }
658
+
659
+ // Verify that sendWithRetry was called and returned null
660
+ assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
661
+ const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
662
+ const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
663
+
664
+ // Call sendWithRetry directly to verify it returns null
665
+ const result = await sendWithRetry(receivers)
666
+ assert.strictEqual(result, null)
667
+
668
+ __resetDependencies()
669
+ })
670
+
671
+ it('should re-throw other errors from sendWithRetry for retry queue to handle', async () => {
672
+ const networkError = new Error('Network timeout')
673
+ const mockBchWallet = {
674
+ initialize: sandbox.stub().resolves(),
675
+ send: sandbox.stub().rejects(networkError)
676
+ }
677
+
678
+ // RetryQueue should eventually succeed after retries
679
+ const mockRetryQueue = {
680
+ addToQueue: sandbox.stub().resolves('tx456')
681
+ }
682
+
683
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
684
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
685
+
686
+ __setDependencies({
687
+ BCHWallet: BCHWalletStub,
688
+ RetryQueue: RetryQueueStub
689
+ })
690
+
691
+ // Test sendWithRetry directly to verify it re-throws non-insufficient-balance errors
692
+ const receivers = [{ address: 'test', amountSat: 1000 }]
693
+
694
+ // Extract sendWithRetry by simulating what happens in sendPayment
695
+ const sendWithRetry = async (receivers) => {
696
+ try {
697
+ return await mockBchWallet.send(receivers)
698
+ } catch (error) {
699
+ if (error.message && error.message.includes('Insufficient balance')) {
700
+ return null
701
+ }
702
+ throw error
703
+ }
704
+ }
705
+
706
+ // Verify that sendWithRetry re-throws non-insufficient-balance errors
707
+ try {
708
+ await sendWithRetry(receivers)
709
+ assert.fail('Expected network error to be thrown')
710
+ } catch (err) {
711
+ assert.equal(err.message, 'Network timeout')
712
+ }
713
+
714
+ // Verify that send was called
715
+ assert.isTrue(mockBchWallet.send.calledOnce)
716
+
717
+ __resetDependencies()
718
+ })
719
+
720
+ it('should use paymentAmountSats from signer when available', async () => {
721
+ const signer = createSignerStub()
722
+ signer.paymentAmountSats = 5000
723
+ const paymentRequirements = createPaymentRequirementsStub()
724
+
725
+ const mockBchWallet = {
726
+ initialize: sandbox.stub().resolves(),
727
+ send: sandbox.stub().resolves('tx789')
728
+ }
729
+
730
+ const mockRetryQueue = {
731
+ addToQueue: sandbox.stub().resolves('tx789')
732
+ }
733
+
734
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
735
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
736
+
737
+ __setDependencies({
738
+ BCHWallet: BCHWalletStub,
739
+ RetryQueue: RetryQueueStub
740
+ })
741
+
742
+ await __internals.sendPayment(signer, paymentRequirements)
743
+
744
+ const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
745
+ assert.equal(receivers[0].amountSat, 5000)
746
+
747
+ __resetDependencies()
748
+ })
749
+
750
+ it('should use amountRequired from paymentRequirements when signer.paymentAmountSats is not set', async () => {
751
+ const signer = createSignerStub()
752
+ delete signer.paymentAmountSats
753
+ const paymentRequirements = createPaymentRequirementsStub()
754
+ paymentRequirements.amount = '3000'
755
+
756
+ const mockBchWallet = {
757
+ initialize: sandbox.stub().resolves(),
758
+ send: sandbox.stub().resolves('tx999')
759
+ }
760
+
761
+ const mockRetryQueue = {
762
+ addToQueue: sandbox.stub().resolves('tx999')
763
+ }
764
+
765
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
766
+ const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
767
+
768
+ __setDependencies({
769
+ BCHWallet: BCHWalletStub,
770
+ RetryQueue: RetryQueueStub
771
+ })
772
+
773
+ await __internals.sendPayment(signer, paymentRequirements)
774
+
775
+ const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
776
+ assert.equal(receivers[0].amountSat, '3000')
777
+
778
+ __resetDependencies()
779
+ })
780
+
781
+ it('should only match "Insufficient balance" error message (case-sensitive)', async () => {
782
+ const mockBchWallet = {
783
+ initialize: sandbox.stub().resolves(),
784
+ send: sandbox.stub().rejects(new Error('INSUFFICIENT BALANCE'))
785
+ }
786
+
787
+ // Test sendWithRetry directly
788
+ const sendWithRetry = async (receivers) => {
789
+ try {
790
+ return await mockBchWallet.send(receivers)
791
+ } catch (error) {
792
+ if (error.message && error.message.includes('Insufficient balance')) {
793
+ return null
794
+ }
795
+ throw error
796
+ }
797
+ }
798
+
799
+ const receivers = [{ address: 'test', amountSat: 1000 }]
800
+
801
+ // This should NOT return null because the error message doesn't match (case-sensitive check)
802
+ // The includes() method is case-sensitive, so 'INSUFFICIENT BALANCE' won't match 'Insufficient balance'
803
+ try {
804
+ const result = await sendWithRetry(receivers)
805
+ // If it returns null, that's unexpected
806
+ if (result === null) {
807
+ assert.fail('Should not return null for case-mismatched error message')
808
+ }
809
+ } catch (err) {
810
+ // Expected - error should be re-thrown because it doesn't match
811
+ assert.equal(err.message, 'INSUFFICIENT BALANCE')
812
+ }
813
+
814
+ __resetDependencies()
815
+ })
816
+
817
+ it('should propagate errors from wallet initialization', async () => {
818
+ const signer = createSignerStub()
819
+ const paymentRequirements = createPaymentRequirementsStub()
820
+
821
+ const initError = new Error('Failed to initialize wallet')
822
+ const mockBchWallet = {
823
+ initialize: sandbox.stub().rejects(initError),
824
+ send: sandbox.stub()
825
+ }
826
+
827
+ const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
828
+ const RetryQueueStub = sandbox.stub()
829
+
830
+ __setDependencies({
831
+ BCHWallet: BCHWalletStub,
832
+ RetryQueue: RetryQueueStub
833
+ })
834
+
835
+ try {
836
+ await __internals.sendPayment(signer, paymentRequirements)
837
+ assert.fail('Expected initialization error to be thrown')
838
+ } catch (err) {
839
+ assert.equal(err.message, 'Failed to initialize wallet')
840
+ }
841
+
842
+ assert.isTrue(mockBchWallet.initialize.calledOnce)
843
+ assert.isTrue(RetryQueueStub.notCalled)
844
+
845
+ __resetDependencies()
846
+ })
847
+ })
467
848
  })