x402-bch-axios 2.1.1 → 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 +128 -3
- package/package.json +1 -1
- package/test/unit/index-unit.js +351 -7
package/index.js
CHANGED
|
@@ -164,16 +164,130 @@ export async function createPaymentHeader (
|
|
|
164
164
|
return JSON.stringify(paymentHeader)
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
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 = {}) {
|
|
168
171
|
try {
|
|
169
|
-
const { apiType, bchServerURL } = bchServerConfig
|
|
172
|
+
const { apiType, bchServerURL, bearerToken } = bchServerConfig
|
|
173
|
+
|
|
174
|
+
// Private key in WIF format.
|
|
175
|
+
const wif = signer.wif
|
|
176
|
+
const payToAddr = paymentRequirements.payTo
|
|
177
|
+
|
|
170
178
|
// Support both v1 (minAmountRequired) and v2 (amount) field names
|
|
171
179
|
const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
|
|
172
180
|
const paymentAmountSats = signer.paymentAmountSats || amountRequired
|
|
173
181
|
|
|
182
|
+
// Get bch-js
|
|
174
183
|
const bchWallet = new dependencies.BCHWallet(signer.wif, {
|
|
175
184
|
interface: apiType,
|
|
176
|
-
restURL: bchServerURL
|
|
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
|
+
}
|
|
209
|
+
|
|
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!')
|
|
242
|
+
}
|
|
243
|
+
|
|
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
|
+
}
|
|
279
|
+
|
|
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
|
|
177
291
|
})
|
|
178
292
|
// console.log(`sendPayment() - interface: ${apiType}, restURL: ${bchServerURL}, wif: ${signer.wif}, payTo: ${paymentRequirements.payTo}, paymentAmountSats: ${paymentAmountSats}`)
|
|
179
293
|
console.log(`Sending ${paymentAmountSats} for x402 API payment to ${paymentRequirements.payTo}`)
|
|
@@ -219,6 +333,17 @@ async function sendPayment (signer, paymentRequirements, bchServerConfig = {}) {
|
|
|
219
333
|
}
|
|
220
334
|
}
|
|
221
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)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
222
347
|
/**
|
|
223
348
|
* Adds a payment interceptor to an axios instance.
|
|
224
349
|
*
|
package/package.json
CHANGED
package/test/unit/index-unit.js
CHANGED
|
@@ -525,7 +525,7 @@ describe('#index.js', () => {
|
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
it('should
|
|
528
|
+
it('should route to sendPaymentGeneric when URL is not fullstack', async () => {
|
|
529
529
|
const signer = createSignerStub()
|
|
530
530
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
531
531
|
const bchServerConfig = {
|
|
@@ -562,7 +562,8 @@ describe('#index.js', () => {
|
|
|
562
562
|
assert.deepEqual(BCHWalletStub.firstCall.args[0], 'test-wif')
|
|
563
563
|
assert.deepEqual(BCHWalletStub.firstCall.args[1], {
|
|
564
564
|
interface: 'rest-api',
|
|
565
|
-
restURL: 'https://api.example.com'
|
|
565
|
+
restURL: 'https://api.example.com',
|
|
566
|
+
bearerToken: undefined
|
|
566
567
|
})
|
|
567
568
|
assert.isTrue(mockBchWallet.initialize.calledOnce)
|
|
568
569
|
assert.isTrue(RetryQueueStub.calledOnce)
|
|
@@ -580,10 +581,94 @@ describe('#index.js', () => {
|
|
|
580
581
|
__resetDependencies()
|
|
581
582
|
})
|
|
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
|
+
|
|
583
666
|
it('should throw "Insufficient balance" error when sendWithRetry returns null', async () => {
|
|
584
667
|
const signer = createSignerStub()
|
|
585
668
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
586
|
-
const bchServerConfig = {
|
|
669
|
+
const bchServerConfig = {
|
|
670
|
+
bchServerURL: 'https://api.example.com'
|
|
671
|
+
}
|
|
587
672
|
|
|
588
673
|
const mockBchWallet = {
|
|
589
674
|
initialize: sandbox.stub().resolves(),
|
|
@@ -627,6 +712,9 @@ describe('#index.js', () => {
|
|
|
627
712
|
it('should handle "Insufficient balance" error in sendWithRetry wrapper', async () => {
|
|
628
713
|
const signer = createSignerStub()
|
|
629
714
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
715
|
+
const bchServerConfig = {
|
|
716
|
+
bchServerURL: 'https://api.example.com'
|
|
717
|
+
}
|
|
630
718
|
|
|
631
719
|
const insufficientBalanceError = new Error('Insufficient balance')
|
|
632
720
|
const mockBchWallet = {
|
|
@@ -650,7 +738,7 @@ describe('#index.js', () => {
|
|
|
650
738
|
})
|
|
651
739
|
|
|
652
740
|
try {
|
|
653
|
-
await __internals.sendPayment(signer, paymentRequirements)
|
|
741
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
654
742
|
assert.fail('Expected "Insufficient balance" error to be thrown')
|
|
655
743
|
} catch (err) {
|
|
656
744
|
assert.equal(err.message, 'Insufficient balance')
|
|
@@ -721,6 +809,9 @@ describe('#index.js', () => {
|
|
|
721
809
|
const signer = createSignerStub()
|
|
722
810
|
signer.paymentAmountSats = 5000
|
|
723
811
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
812
|
+
const bchServerConfig = {
|
|
813
|
+
bchServerURL: 'https://api.example.com'
|
|
814
|
+
}
|
|
724
815
|
|
|
725
816
|
const mockBchWallet = {
|
|
726
817
|
initialize: sandbox.stub().resolves(),
|
|
@@ -739,7 +830,7 @@ describe('#index.js', () => {
|
|
|
739
830
|
RetryQueue: RetryQueueStub
|
|
740
831
|
})
|
|
741
832
|
|
|
742
|
-
await __internals.sendPayment(signer, paymentRequirements)
|
|
833
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
743
834
|
|
|
744
835
|
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
745
836
|
assert.equal(receivers[0].amountSat, 5000)
|
|
@@ -752,6 +843,9 @@ describe('#index.js', () => {
|
|
|
752
843
|
delete signer.paymentAmountSats
|
|
753
844
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
754
845
|
paymentRequirements.amount = '3000'
|
|
846
|
+
const bchServerConfig = {
|
|
847
|
+
bchServerURL: 'https://api.example.com'
|
|
848
|
+
}
|
|
755
849
|
|
|
756
850
|
const mockBchWallet = {
|
|
757
851
|
initialize: sandbox.stub().resolves(),
|
|
@@ -770,7 +864,7 @@ describe('#index.js', () => {
|
|
|
770
864
|
RetryQueue: RetryQueueStub
|
|
771
865
|
})
|
|
772
866
|
|
|
773
|
-
await __internals.sendPayment(signer, paymentRequirements)
|
|
867
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
774
868
|
|
|
775
869
|
const receivers = mockRetryQueue.addToQueue.firstCall.args[1]
|
|
776
870
|
assert.equal(receivers[0].amountSat, '3000')
|
|
@@ -817,6 +911,9 @@ describe('#index.js', () => {
|
|
|
817
911
|
it('should propagate errors from wallet initialization', async () => {
|
|
818
912
|
const signer = createSignerStub()
|
|
819
913
|
const paymentRequirements = createPaymentRequirementsStub()
|
|
914
|
+
const bchServerConfig = {
|
|
915
|
+
bchServerURL: 'https://api.example.com'
|
|
916
|
+
}
|
|
820
917
|
|
|
821
918
|
const initError = new Error('Failed to initialize wallet')
|
|
822
919
|
const mockBchWallet = {
|
|
@@ -833,7 +930,7 @@ describe('#index.js', () => {
|
|
|
833
930
|
})
|
|
834
931
|
|
|
835
932
|
try {
|
|
836
|
-
await __internals.sendPayment(signer, paymentRequirements)
|
|
933
|
+
await __internals.sendPayment(signer, paymentRequirements, bchServerConfig)
|
|
837
934
|
assert.fail('Expected initialization error to be thrown')
|
|
838
935
|
} catch (err) {
|
|
839
936
|
assert.equal(err.message, 'Failed to initialize wallet')
|
|
@@ -844,5 +941,252 @@ describe('#index.js', () => {
|
|
|
844
941
|
|
|
845
942
|
__resetDependencies()
|
|
846
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
|
+
})
|
|
847
1191
|
})
|
|
848
1192
|
})
|