x402-bch-axios 2.1.0 → 2.2.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 +173 -24
- package/package.json +2 -2
- package/test/unit/index-unit.js +683 -0
package/index.js
CHANGED
|
@@ -164,34 +164,183 @@ export async function createPaymentHeader (
|
|
|
164
164
|
return JSON.stringify(paymentHeader)
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
// Send the payment using bch.fullstack.cash. In this case, we can use bch-js to execute
|
|
168
|
+
// the payment in a more optimized way.
|
|
169
|
+
// Assumption: A UTXO that is equal to or larger than paymentAmountSats is not a SLP token UTXO.
|
|
170
|
+
async function sendPaymentFullstack (signer, paymentRequirements, bchServerConfig = {}) {
|
|
171
|
+
try {
|
|
172
|
+
const { apiType, bchServerURL, bearerToken } = bchServerConfig
|
|
173
|
+
|
|
174
|
+
// Private key in WIF format.
|
|
175
|
+
const wif = signer.wif
|
|
176
|
+
const payToAddr = paymentRequirements.payTo
|
|
177
|
+
|
|
178
|
+
// Support both v1 (minAmountRequired) and v2 (amount) field names
|
|
179
|
+
const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
|
|
180
|
+
const paymentAmountSats = signer.paymentAmountSats || amountRequired
|
|
181
|
+
|
|
182
|
+
// Get bch-js
|
|
183
|
+
const bchWallet = new dependencies.BCHWallet(signer.wif, {
|
|
184
|
+
interface: apiType,
|
|
185
|
+
restURL: bchServerURL,
|
|
186
|
+
bearerToken
|
|
187
|
+
})
|
|
188
|
+
await bchWallet.walletInfoPromise
|
|
189
|
+
const bchjs = bchWallet.bchjs
|
|
190
|
+
|
|
191
|
+
// Generate the bitcoincash: address from the private key.
|
|
192
|
+
const ecPair = bchjs.ECPair.fromWIF(wif)
|
|
193
|
+
const payFromAddr = bchjs.ECPair.toCashAddress(ecPair)
|
|
194
|
+
// console.log(`payFromAddr: ${payFromAddr}`)
|
|
195
|
+
|
|
196
|
+
// console.log('bchjs.restURL: ', bchjs.restURL)
|
|
197
|
+
|
|
198
|
+
// Get the UTXOs controlled by the private key.
|
|
199
|
+
let utxos = []
|
|
200
|
+
let utxoData
|
|
201
|
+
try {
|
|
202
|
+
utxoData = await bchjs.Electrumx.utxo(payFromAddr)
|
|
203
|
+
} catch (err) {
|
|
204
|
+
throw new Error(`Error retrieving UTXOs for address ${payFromAddr}: ${err.message}`)
|
|
205
|
+
}
|
|
206
|
+
if (utxoData.utxos && Array.isArray(utxoData.utxos)) {
|
|
207
|
+
utxos = utxoData.utxos
|
|
208
|
+
}
|
|
172
209
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
210
|
+
// Filter out UTXOs that are equal to or greater than the paymentAmountSats
|
|
211
|
+
utxos = utxos.filter(utxo => utxo.value >= paymentAmountSats)
|
|
212
|
+
// console.log(`UTXOs available for payment: ${JSON.stringify(utxos, null, 2)}`)
|
|
213
|
+
|
|
214
|
+
// Choose the first UTXO that is big enough to pay for the transaction.
|
|
215
|
+
const utxo = utxos[0]
|
|
216
|
+
|
|
217
|
+
// instance of transaction builder
|
|
218
|
+
const transactionBuilder = new bchjs.TransactionBuilder()
|
|
219
|
+
|
|
220
|
+
// Essential variables of a transaction.
|
|
221
|
+
const satoshisToSend = paymentAmountSats
|
|
222
|
+
const originalAmount = utxo.value
|
|
223
|
+
const vout = utxo.tx_pos
|
|
224
|
+
const txid = utxo.tx_hash
|
|
225
|
+
|
|
226
|
+
// add input with txid and index of vout
|
|
227
|
+
transactionBuilder.addInput(txid, vout)
|
|
228
|
+
|
|
229
|
+
// get byte count to calculate fee. paying 1.2 sat/byte
|
|
230
|
+
const byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 })
|
|
231
|
+
// console.log(`Transaction byte count: ${byteCount}`)
|
|
232
|
+
const satoshisPerByte = 1.2
|
|
233
|
+
const txFee = Math.floor(satoshisPerByte * byteCount)
|
|
234
|
+
// console.log(`Transaction fee: ${txFee}`)
|
|
235
|
+
|
|
236
|
+
// amount to send back to the sending address.
|
|
237
|
+
// It's the original amount - 1 sat/byte for tx size
|
|
238
|
+
const remainder = originalAmount - satoshisToSend - txFee
|
|
239
|
+
|
|
240
|
+
if (remainder < 0) {
|
|
241
|
+
throw new Error('Not enough BCH to complete transaction!')
|
|
186
242
|
}
|
|
187
|
-
]
|
|
188
243
|
|
|
189
|
-
|
|
244
|
+
// add output w/ address and amount to send
|
|
245
|
+
transactionBuilder.addOutput(payToAddr, satoshisToSend)
|
|
246
|
+
transactionBuilder.addOutput(payFromAddr, remainder)
|
|
247
|
+
|
|
248
|
+
// Sign the transaction with the HD node.
|
|
249
|
+
let redeemScript
|
|
250
|
+
transactionBuilder.sign(
|
|
251
|
+
0,
|
|
252
|
+
ecPair,
|
|
253
|
+
redeemScript,
|
|
254
|
+
transactionBuilder.hashTypes.SIGHASH_ALL,
|
|
255
|
+
originalAmount
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// build tx
|
|
259
|
+
const tx = transactionBuilder.build()
|
|
260
|
+
// output rawhex
|
|
261
|
+
const hex = tx.toHex()
|
|
262
|
+
// console.log(`TX hex: ${hex}`);
|
|
263
|
+
console.log(' ')
|
|
264
|
+
|
|
265
|
+
// Broadcast transation to the network
|
|
266
|
+
const txid2 = await bchjs.RawTransactions.sendRawTransaction([hex])
|
|
267
|
+
// console.log(`Transaction ID: ${txid2}`)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
txid: txid2,
|
|
271
|
+
vout: 0,
|
|
272
|
+
satsSent: paymentAmountSats
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.error('Error in x402-bch-axios/sendPaymentFullstack(): ', err)
|
|
276
|
+
throw err
|
|
277
|
+
}
|
|
278
|
+
}
|
|
190
279
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
280
|
+
async function sendPaymentGeneric (signer, paymentRequirements, bchServerConfig = {}) {
|
|
281
|
+
try {
|
|
282
|
+
const { apiType, bchServerURL, bearerToken } = bchServerConfig
|
|
283
|
+
// Support both v1 (minAmountRequired) and v2 (amount) field names
|
|
284
|
+
const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
|
|
285
|
+
const paymentAmountSats = signer.paymentAmountSats || amountRequired
|
|
286
|
+
|
|
287
|
+
const bchWallet = new dependencies.BCHWallet(signer.wif, {
|
|
288
|
+
interface: apiType,
|
|
289
|
+
restURL: bchServerURL,
|
|
290
|
+
bearerToken
|
|
291
|
+
})
|
|
292
|
+
// console.log(`sendPayment() - interface: ${apiType}, restURL: ${bchServerURL}, wif: ${signer.wif}, payTo: ${paymentRequirements.payTo}, paymentAmountSats: ${paymentAmountSats}`)
|
|
293
|
+
console.log(`Sending ${paymentAmountSats} for x402 API payment to ${paymentRequirements.payTo}`)
|
|
294
|
+
await bchWallet.initialize()
|
|
295
|
+
|
|
296
|
+
const retryQueue = new dependencies.RetryQueue()
|
|
297
|
+
const receivers = [
|
|
298
|
+
{
|
|
299
|
+
address: paymentRequirements.payTo,
|
|
300
|
+
amountSat: paymentAmountSats
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
// Wrap send function to detect "Insufficient balance" errors
|
|
305
|
+
const sendWithRetry = async (receivers) => {
|
|
306
|
+
try {
|
|
307
|
+
return await bchWallet.send(receivers)
|
|
308
|
+
} catch (error) {
|
|
309
|
+
// Check if error message contains "Insufficient balance"
|
|
310
|
+
if (error.message && error.message.includes('Insufficient balance')) {
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Re-throw other errors normally (will be retried)
|
|
315
|
+
throw error
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const txid = await retryQueue.addToQueue(sendWithRetry, receivers)
|
|
320
|
+
|
|
321
|
+
if (txid === null) {
|
|
322
|
+
throw new Error('Insufficient balance')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
txid,
|
|
327
|
+
vout: 0,
|
|
328
|
+
satsSent: paymentAmountSats
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
console.error('Error in x402-bch-axios/sendPayment(): ', err.message)
|
|
332
|
+
throw err
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Route the payment to the appropriate function based on the BCH server URL.
|
|
337
|
+
async function sendPayment (signer, paymentRequirements, bchServerConfig = {}) {
|
|
338
|
+
const { bchServerURL } = bchServerConfig
|
|
339
|
+
if (bchServerURL.includes('bch.fullstack.cash')) {
|
|
340
|
+
// If the BCH server URL is a Fullstack server, use an optimized payment function.
|
|
341
|
+
return sendPaymentFullstack(signer, paymentRequirements, bchServerConfig)
|
|
342
|
+
} else {
|
|
343
|
+
return sendPaymentGeneric(signer, paymentRequirements, bchServerConfig)
|
|
195
344
|
}
|
|
196
345
|
}
|
|
197
346
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-bch-axios",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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.
|
|
29
|
+
"minimal-slp-wallet": "7.0.5"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"c8": "10.1.3",
|
package/test/unit/index-unit.js
CHANGED
|
@@ -506,4 +506,687 @@ describe('#index.js', () => {
|
|
|
506
506
|
assert.isTrue(sendPaymentStub.calledOnce)
|
|
507
507
|
})
|
|
508
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 route to sendPaymentGeneric when URL is not fullstack', 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
|
+
bearerToken: undefined
|
|
567
|
+
})
|
|
568
|
+
assert.isTrue(mockBchWallet.initialize.calledOnce)
|
|
569
|
+
assert.isTrue(RetryQueueStub.calledOnce)
|
|
570
|
+
assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
|
|
571
|
+
|
|
572
|
+
// Verify sendWithRetry was called with receivers
|
|
573
|
+
const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
|
|
574
|
+
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
575
|
+
assert.isFunction(sendWithRetry)
|
|
576
|
+
assert.deepEqual(receivers, [{
|
|
577
|
+
address: 'bitcoincash:qprecv',
|
|
578
|
+
amountSat: 2000
|
|
579
|
+
}])
|
|
580
|
+
|
|
581
|
+
__resetDependencies()
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('should route to sendPaymentFullstack when URL contains bch.fullstack.cash', async () => {
|
|
585
|
+
const signer = createSignerStub()
|
|
586
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
587
|
+
const bchServerConfig = {
|
|
588
|
+
apiType: 'rest-api',
|
|
589
|
+
bchServerURL: 'https://bch.fullstack.cash/v5/'
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const mockEcPair = { ecpair: true }
|
|
593
|
+
const mockUtxos = [{
|
|
594
|
+
tx_hash: 'utxo-txid',
|
|
595
|
+
tx_pos: 1,
|
|
596
|
+
value: 5000
|
|
597
|
+
}]
|
|
598
|
+
|
|
599
|
+
const mockTransactionBuilder = {
|
|
600
|
+
addInput: sandbox.stub(),
|
|
601
|
+
addOutput: sandbox.stub(),
|
|
602
|
+
sign: sandbox.stub(),
|
|
603
|
+
build: sandbox.stub().returns({
|
|
604
|
+
toHex: sandbox.stub().returns('raw-hex')
|
|
605
|
+
}),
|
|
606
|
+
hashTypes: {
|
|
607
|
+
SIGHASH_ALL: 1
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const mockBchjs = {
|
|
612
|
+
ECPair: {
|
|
613
|
+
fromWIF: sandbox.stub().returns(mockEcPair),
|
|
614
|
+
toCashAddress: sandbox.stub().returns('bitcoincash:qptest')
|
|
615
|
+
},
|
|
616
|
+
Electrumx: {
|
|
617
|
+
utxo: sandbox.stub().resolves({ utxos: mockUtxos })
|
|
618
|
+
},
|
|
619
|
+
TransactionBuilder: sandbox.stub().returns(mockTransactionBuilder),
|
|
620
|
+
BitcoinCash: {
|
|
621
|
+
getByteCount: sandbox.stub().returns(250)
|
|
622
|
+
},
|
|
623
|
+
RawTransactions: {
|
|
624
|
+
sendRawTransaction: sandbox.stub().resolves('tx123')
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const mockBchWallet = {
|
|
629
|
+
walletInfoPromise: Promise.resolve(),
|
|
630
|
+
bchjs: mockBchjs
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
634
|
+
|
|
635
|
+
__setDependencies({
|
|
636
|
+
BCHWallet: BCHWalletStub
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
const result = await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
640
|
+
|
|
641
|
+
assert.deepEqual(result, {
|
|
642
|
+
txid: 'tx123',
|
|
643
|
+
vout: 0,
|
|
644
|
+
satsSent: 2000
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
assert.isTrue(BCHWalletStub.calledOnce)
|
|
648
|
+
assert.deepEqual(BCHWalletStub.firstCall.args[0], 'test-wif')
|
|
649
|
+
assert.deepEqual(BCHWalletStub.firstCall.args[1], {
|
|
650
|
+
interface: 'rest-api',
|
|
651
|
+
restURL: 'https://bch.fullstack.cash/v5/',
|
|
652
|
+
bearerToken: undefined
|
|
653
|
+
})
|
|
654
|
+
assert.isTrue(mockBchjs.ECPair.fromWIF.calledOnce)
|
|
655
|
+
assert.isTrue(mockBchjs.Electrumx.utxo.calledOnce)
|
|
656
|
+
assert.isTrue(mockBchjs.TransactionBuilder.calledOnce)
|
|
657
|
+
assert.isTrue(mockTransactionBuilder.addInput.calledOnce)
|
|
658
|
+
assert.isTrue(mockTransactionBuilder.addOutput.calledTwice)
|
|
659
|
+
assert.isTrue(mockTransactionBuilder.sign.calledOnce)
|
|
660
|
+
assert.isTrue(mockBchjs.RawTransactions.sendRawTransaction.calledOnce)
|
|
661
|
+
assert.deepEqual(mockBchjs.RawTransactions.sendRawTransaction.firstCall.args[0], ['raw-hex'])
|
|
662
|
+
|
|
663
|
+
__resetDependencies()
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
it('should throw "Insufficient balance" error when sendWithRetry returns null', async () => {
|
|
667
|
+
const signer = createSignerStub()
|
|
668
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
669
|
+
const bchServerConfig = {
|
|
670
|
+
bchServerURL: 'https://api.example.com'
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const mockBchWallet = {
|
|
674
|
+
initialize: sandbox.stub().resolves(),
|
|
675
|
+
send: sandbox.stub().rejects(new Error('Insufficient balance'))
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const mockRetryQueue = {
|
|
679
|
+
addToQueue: sandbox.stub().resolves(null) // sendWithRetry returns null for insufficient balance
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
683
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
684
|
+
|
|
685
|
+
__setDependencies({
|
|
686
|
+
BCHWallet: BCHWalletStub,
|
|
687
|
+
RetryQueue: RetryQueueStub
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
692
|
+
assert.fail('Expected "Insufficient balance" error to be thrown')
|
|
693
|
+
} catch (err) {
|
|
694
|
+
assert.equal(err.message, 'Insufficient balance')
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Verify sendWithRetry was called and handled the error
|
|
698
|
+
assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
|
|
699
|
+
const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
|
|
700
|
+
|
|
701
|
+
// Test sendWithRetry directly to verify it returns null for insufficient balance
|
|
702
|
+
try {
|
|
703
|
+
const result = await sendWithRetry([{ address: 'test', amountSat: 1000 }])
|
|
704
|
+
assert.strictEqual(result, null)
|
|
705
|
+
} catch (err) {
|
|
706
|
+
assert.fail('sendWithRetry should return null, not throw')
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
__resetDependencies()
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('should handle "Insufficient balance" error in sendWithRetry wrapper', async () => {
|
|
713
|
+
const signer = createSignerStub()
|
|
714
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
715
|
+
const bchServerConfig = {
|
|
716
|
+
bchServerURL: 'https://api.example.com'
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const insufficientBalanceError = new Error('Insufficient balance')
|
|
720
|
+
const mockBchWallet = {
|
|
721
|
+
initialize: sandbox.stub().resolves(),
|
|
722
|
+
send: sandbox.stub().rejects(insufficientBalanceError)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const mockRetryQueue = {
|
|
726
|
+
addToQueue: sandbox.stub().callsFake(async (fn, args) => {
|
|
727
|
+
// Simulate what RetryQueue does - call the function
|
|
728
|
+
return await fn(args)
|
|
729
|
+
})
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
733
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
734
|
+
|
|
735
|
+
__setDependencies({
|
|
736
|
+
BCHWallet: BCHWalletStub,
|
|
737
|
+
RetryQueue: RetryQueueStub
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
742
|
+
assert.fail('Expected "Insufficient balance" error to be thrown')
|
|
743
|
+
} catch (err) {
|
|
744
|
+
assert.equal(err.message, 'Insufficient balance')
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Verify that sendWithRetry was called and returned null
|
|
748
|
+
assert.isTrue(mockRetryQueue.addToQueue.calledOnce)
|
|
749
|
+
const sendWithRetry = mockRetryQueue.addToQueue.firstCall.args[0]
|
|
750
|
+
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
751
|
+
|
|
752
|
+
// Call sendWithRetry directly to verify it returns null
|
|
753
|
+
const result = await sendWithRetry(receivers)
|
|
754
|
+
assert.strictEqual(result, null)
|
|
755
|
+
|
|
756
|
+
__resetDependencies()
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
it('should re-throw other errors from sendWithRetry for retry queue to handle', async () => {
|
|
760
|
+
const networkError = new Error('Network timeout')
|
|
761
|
+
const mockBchWallet = {
|
|
762
|
+
initialize: sandbox.stub().resolves(),
|
|
763
|
+
send: sandbox.stub().rejects(networkError)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// RetryQueue should eventually succeed after retries
|
|
767
|
+
const mockRetryQueue = {
|
|
768
|
+
addToQueue: sandbox.stub().resolves('tx456')
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
772
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
773
|
+
|
|
774
|
+
__setDependencies({
|
|
775
|
+
BCHWallet: BCHWalletStub,
|
|
776
|
+
RetryQueue: RetryQueueStub
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
// Test sendWithRetry directly to verify it re-throws non-insufficient-balance errors
|
|
780
|
+
const receivers = [{ address: 'test', amountSat: 1000 }]
|
|
781
|
+
|
|
782
|
+
// Extract sendWithRetry by simulating what happens in sendPayment
|
|
783
|
+
const sendWithRetry = async (receivers) => {
|
|
784
|
+
try {
|
|
785
|
+
return await mockBchWallet.send(receivers)
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (error.message && error.message.includes('Insufficient balance')) {
|
|
788
|
+
return null
|
|
789
|
+
}
|
|
790
|
+
throw error
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Verify that sendWithRetry re-throws non-insufficient-balance errors
|
|
795
|
+
try {
|
|
796
|
+
await sendWithRetry(receivers)
|
|
797
|
+
assert.fail('Expected network error to be thrown')
|
|
798
|
+
} catch (err) {
|
|
799
|
+
assert.equal(err.message, 'Network timeout')
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Verify that send was called
|
|
803
|
+
assert.isTrue(mockBchWallet.send.calledOnce)
|
|
804
|
+
|
|
805
|
+
__resetDependencies()
|
|
806
|
+
})
|
|
807
|
+
|
|
808
|
+
it('should use paymentAmountSats from signer when available', async () => {
|
|
809
|
+
const signer = createSignerStub()
|
|
810
|
+
signer.paymentAmountSats = 5000
|
|
811
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
812
|
+
const bchServerConfig = {
|
|
813
|
+
bchServerURL: 'https://api.example.com'
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const mockBchWallet = {
|
|
817
|
+
initialize: sandbox.stub().resolves(),
|
|
818
|
+
send: sandbox.stub().resolves('tx789')
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const mockRetryQueue = {
|
|
822
|
+
addToQueue: sandbox.stub().resolves('tx789')
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
826
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
827
|
+
|
|
828
|
+
__setDependencies({
|
|
829
|
+
BCHWallet: BCHWalletStub,
|
|
830
|
+
RetryQueue: RetryQueueStub
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
834
|
+
|
|
835
|
+
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
836
|
+
assert.equal(receivers[0].amountSat, 5000)
|
|
837
|
+
|
|
838
|
+
__resetDependencies()
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
it('should use amountRequired from paymentRequirements when signer.paymentAmountSats is not set', async () => {
|
|
842
|
+
const signer = createSignerStub()
|
|
843
|
+
delete signer.paymentAmountSats
|
|
844
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
845
|
+
paymentRequirements.amount = '3000'
|
|
846
|
+
const bchServerConfig = {
|
|
847
|
+
bchServerURL: 'https://api.example.com'
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const mockBchWallet = {
|
|
851
|
+
initialize: sandbox.stub().resolves(),
|
|
852
|
+
send: sandbox.stub().resolves('tx999')
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const mockRetryQueue = {
|
|
856
|
+
addToQueue: sandbox.stub().resolves('tx999')
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
860
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
861
|
+
|
|
862
|
+
__setDependencies({
|
|
863
|
+
BCHWallet: BCHWalletStub,
|
|
864
|
+
RetryQueue: RetryQueueStub
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
868
|
+
|
|
869
|
+
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
870
|
+
assert.equal(receivers[0].amountSat, '3000')
|
|
871
|
+
|
|
872
|
+
__resetDependencies()
|
|
873
|
+
})
|
|
874
|
+
|
|
875
|
+
it('should only match "Insufficient balance" error message (case-sensitive)', async () => {
|
|
876
|
+
const mockBchWallet = {
|
|
877
|
+
initialize: sandbox.stub().resolves(),
|
|
878
|
+
send: sandbox.stub().rejects(new Error('INSUFFICIENT BALANCE'))
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Test sendWithRetry directly
|
|
882
|
+
const sendWithRetry = async (receivers) => {
|
|
883
|
+
try {
|
|
884
|
+
return await mockBchWallet.send(receivers)
|
|
885
|
+
} catch (error) {
|
|
886
|
+
if (error.message && error.message.includes('Insufficient balance')) {
|
|
887
|
+
return null
|
|
888
|
+
}
|
|
889
|
+
throw error
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const receivers = [{ address: 'test', amountSat: 1000 }]
|
|
894
|
+
|
|
895
|
+
// This should NOT return null because the error message doesn't match (case-sensitive check)
|
|
896
|
+
// The includes() method is case-sensitive, so 'INSUFFICIENT BALANCE' won't match 'Insufficient balance'
|
|
897
|
+
try {
|
|
898
|
+
const result = await sendWithRetry(receivers)
|
|
899
|
+
// If it returns null, that's unexpected
|
|
900
|
+
if (result === null) {
|
|
901
|
+
assert.fail('Should not return null for case-mismatched error message')
|
|
902
|
+
}
|
|
903
|
+
} catch (err) {
|
|
904
|
+
// Expected - error should be re-thrown because it doesn't match
|
|
905
|
+
assert.equal(err.message, 'INSUFFICIENT BALANCE')
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
__resetDependencies()
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
it('should propagate errors from wallet initialization', async () => {
|
|
912
|
+
const signer = createSignerStub()
|
|
913
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
914
|
+
const bchServerConfig = {
|
|
915
|
+
bchServerURL: 'https://api.example.com'
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const initError = new Error('Failed to initialize wallet')
|
|
919
|
+
const mockBchWallet = {
|
|
920
|
+
initialize: sandbox.stub().rejects(initError),
|
|
921
|
+
send: sandbox.stub()
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
925
|
+
const RetryQueueStub = sandbox.stub()
|
|
926
|
+
|
|
927
|
+
__setDependencies({
|
|
928
|
+
BCHWallet: BCHWalletStub,
|
|
929
|
+
RetryQueue: RetryQueueStub
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
try {
|
|
933
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
934
|
+
assert.fail('Expected initialization error to be thrown')
|
|
935
|
+
} catch (err) {
|
|
936
|
+
assert.equal(err.message, 'Failed to initialize wallet')
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
assert.isTrue(mockBchWallet.initialize.calledOnce)
|
|
940
|
+
assert.isTrue(RetryQueueStub.notCalled)
|
|
941
|
+
|
|
942
|
+
__resetDependencies()
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
it('should support v1 minAmountRequired field in sendPaymentGeneric', async () => {
|
|
946
|
+
const signer = createSignerStub()
|
|
947
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
948
|
+
delete paymentRequirements.amount
|
|
949
|
+
paymentRequirements.minAmountRequired = 2500
|
|
950
|
+
const bchServerConfig = {
|
|
951
|
+
bchServerURL: 'https://api.example.com'
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const mockBchWallet = {
|
|
955
|
+
initialize: sandbox.stub().resolves(),
|
|
956
|
+
send: sandbox.stub().resolves('tx-v1')
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const mockRetryQueue = {
|
|
960
|
+
addToQueue: sandbox.stub().resolves('tx-v1')
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
964
|
+
const RetryQueueStub = sandbox.stub().returns(mockRetryQueue)
|
|
965
|
+
|
|
966
|
+
__setDependencies({
|
|
967
|
+
BCHWallet: BCHWalletStub,
|
|
968
|
+
RetryQueue: RetryQueueStub
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
972
|
+
|
|
973
|
+
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
974
|
+
// Should use paymentAmountSats from signer (2000) when available, not minAmountRequired
|
|
975
|
+
assert.equal(receivers[0].amountSat, 2000)
|
|
976
|
+
|
|
977
|
+
__resetDependencies()
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
it('should handle UTXO retrieval errors in sendPaymentFullstack', async () => {
|
|
981
|
+
const signer = createSignerStub()
|
|
982
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
983
|
+
const bchServerConfig = {
|
|
984
|
+
apiType: 'rest-api',
|
|
985
|
+
bchServerURL: 'https://bch.fullstack.cash/v5/'
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const mockEcPair = { ecpair: true }
|
|
989
|
+
const mockBchjs = {
|
|
990
|
+
ECPair: {
|
|
991
|
+
fromWIF: sandbox.stub().returns(mockEcPair),
|
|
992
|
+
toCashAddress: sandbox.stub().returns('bitcoincash:qptest')
|
|
993
|
+
},
|
|
994
|
+
Electrumx: {
|
|
995
|
+
utxo: sandbox.stub().rejects(new Error('Network error'))
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const mockBchWallet = {
|
|
1000
|
+
walletInfoPromise: Promise.resolve(),
|
|
1001
|
+
bchjs: mockBchjs
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
1005
|
+
|
|
1006
|
+
__setDependencies({
|
|
1007
|
+
BCHWallet: BCHWalletStub
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
try {
|
|
1011
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
1012
|
+
assert.fail('Expected error to be thrown')
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
assert.match(err.message, /Error retrieving UTXOs/)
|
|
1015
|
+
assert.match(err.message, /Network error/)
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
__resetDependencies()
|
|
1019
|
+
})
|
|
1020
|
+
|
|
1021
|
+
it('should throw error when insufficient balance in sendPaymentFullstack', async () => {
|
|
1022
|
+
const signer = createSignerStub()
|
|
1023
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
1024
|
+
const bchServerConfig = {
|
|
1025
|
+
apiType: 'rest-api',
|
|
1026
|
+
bchServerURL: 'https://bch.fullstack.cash/v5/'
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const mockEcPair = { ecpair: true }
|
|
1030
|
+
const mockUtxos = [{
|
|
1031
|
+
tx_hash: 'utxo-txid',
|
|
1032
|
+
tx_pos: 1,
|
|
1033
|
+
value: 1000 // Less than paymentAmountSats (2000)
|
|
1034
|
+
}]
|
|
1035
|
+
|
|
1036
|
+
const mockBchjs = {
|
|
1037
|
+
ECPair: {
|
|
1038
|
+
fromWIF: sandbox.stub().returns(mockEcPair),
|
|
1039
|
+
toCashAddress: sandbox.stub().returns('bitcoincash:qptest')
|
|
1040
|
+
},
|
|
1041
|
+
Electrumx: {
|
|
1042
|
+
utxo: sandbox.stub().resolves({ utxos: mockUtxos })
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const mockBchWallet = {
|
|
1047
|
+
walletInfoPromise: Promise.resolve(),
|
|
1048
|
+
bchjs: mockBchjs
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
1052
|
+
|
|
1053
|
+
__setDependencies({
|
|
1054
|
+
BCHWallet: BCHWalletStub
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
try {
|
|
1058
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
1059
|
+
assert.fail('Expected error to be thrown')
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
// Should fail because utxos[0] is undefined after filtering
|
|
1062
|
+
assert.isDefined(err)
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
__resetDependencies()
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
it('should handle transaction building errors in sendPaymentFullstack', async () => {
|
|
1069
|
+
const signer = createSignerStub()
|
|
1070
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
1071
|
+
const bchServerConfig = {
|
|
1072
|
+
apiType: 'rest-api',
|
|
1073
|
+
bchServerURL: 'https://bch.fullstack.cash/v5/'
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const mockEcPair = { ecpair: true }
|
|
1077
|
+
const mockUtxos = [{
|
|
1078
|
+
tx_hash: 'utxo-txid',
|
|
1079
|
+
tx_pos: 1,
|
|
1080
|
+
value: 5000
|
|
1081
|
+
}]
|
|
1082
|
+
|
|
1083
|
+
const mockTransactionBuilder = {
|
|
1084
|
+
addInput: sandbox.stub(),
|
|
1085
|
+
addOutput: sandbox.stub(),
|
|
1086
|
+
sign: sandbox.stub(),
|
|
1087
|
+
build: sandbox.stub().throws(new Error('Build failed')),
|
|
1088
|
+
hashTypes: {
|
|
1089
|
+
SIGHASH_ALL: 1
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const mockBchjs = {
|
|
1094
|
+
ECPair: {
|
|
1095
|
+
fromWIF: sandbox.stub().returns(mockEcPair),
|
|
1096
|
+
toCashAddress: sandbox.stub().returns('bitcoincash:qptest')
|
|
1097
|
+
},
|
|
1098
|
+
Electrumx: {
|
|
1099
|
+
utxo: sandbox.stub().resolves({ utxos: mockUtxos })
|
|
1100
|
+
},
|
|
1101
|
+
TransactionBuilder: sandbox.stub().returns(mockTransactionBuilder),
|
|
1102
|
+
BitcoinCash: {
|
|
1103
|
+
getByteCount: sandbox.stub().returns(250)
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const mockBchWallet = {
|
|
1108
|
+
walletInfoPromise: Promise.resolve(),
|
|
1109
|
+
bchjs: mockBchjs
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
1113
|
+
|
|
1114
|
+
__setDependencies({
|
|
1115
|
+
BCHWallet: BCHWalletStub
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
try {
|
|
1119
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
1120
|
+
assert.fail('Expected error to be thrown')
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
assert.equal(err.message, 'Build failed')
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
__resetDependencies()
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
it('should throw error when remainder is negative in sendPaymentFullstack', async () => {
|
|
1129
|
+
const signer = createSignerStub()
|
|
1130
|
+
signer.paymentAmountSats = 10000 // Large amount
|
|
1131
|
+
const paymentRequirements = createPaymentRequirementsStub()
|
|
1132
|
+
const bchServerConfig = {
|
|
1133
|
+
apiType: 'rest-api',
|
|
1134
|
+
bchServerURL: 'https://bch.fullstack.cash/v5/'
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const mockEcPair = { ecpair: true }
|
|
1138
|
+
// UTXO value must be >= paymentAmountSats (10000) to pass filter
|
|
1139
|
+
// But remainder = value - paymentAmountSats - txFee must be negative
|
|
1140
|
+
// With txFee = 1.2 * 250 = 300, we need value < 10300
|
|
1141
|
+
// So use value = 10200: remainder = 10200 - 10000 - 300 = -100 < 0
|
|
1142
|
+
const mockUtxos = [{
|
|
1143
|
+
tx_hash: 'utxo-txid',
|
|
1144
|
+
tx_pos: 1,
|
|
1145
|
+
value: 10200 // >= paymentAmountSats but insufficient after fee
|
|
1146
|
+
}]
|
|
1147
|
+
|
|
1148
|
+
const mockTransactionBuilder = {
|
|
1149
|
+
addInput: sandbox.stub(),
|
|
1150
|
+
addOutput: sandbox.stub(),
|
|
1151
|
+
sign: sandbox.stub(),
|
|
1152
|
+
hashTypes: {
|
|
1153
|
+
SIGHASH_ALL: 1
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const mockBchjs = {
|
|
1158
|
+
ECPair: {
|
|
1159
|
+
fromWIF: sandbox.stub().returns(mockEcPair),
|
|
1160
|
+
toCashAddress: sandbox.stub().returns('bitcoincash:qptest')
|
|
1161
|
+
},
|
|
1162
|
+
Electrumx: {
|
|
1163
|
+
utxo: sandbox.stub().resolves({ utxos: mockUtxos })
|
|
1164
|
+
},
|
|
1165
|
+
TransactionBuilder: sandbox.stub().returns(mockTransactionBuilder),
|
|
1166
|
+
BitcoinCash: {
|
|
1167
|
+
getByteCount: sandbox.stub().returns(250)
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const mockBchWallet = {
|
|
1172
|
+
walletInfoPromise: Promise.resolve(),
|
|
1173
|
+
bchjs: mockBchjs
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const BCHWalletStub = sandbox.stub().returns(mockBchWallet)
|
|
1177
|
+
|
|
1178
|
+
__setDependencies({
|
|
1179
|
+
BCHWallet: BCHWalletStub
|
|
1180
|
+
})
|
|
1181
|
+
|
|
1182
|
+
try {
|
|
1183
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
1184
|
+
assert.fail('Expected error to be thrown')
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
assert.equal(err.message, 'Not enough BCH to complete transaction!')
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
__resetDependencies()
|
|
1190
|
+
})
|
|
1191
|
+
})
|
|
509
1192
|
})
|