@psf/bch-js 5.2.0 → 5.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
5
5
  "author": "Chris Troutner <chris.troutner@gmail.com>",
6
6
  "contributors": [
@@ -18,7 +18,7 @@
18
18
  "test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
19
19
  "test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
20
20
  "test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
21
- "test:integration:decatur:bchn": "export RESTURL=http://192.168.2.139:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
21
+ "test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
22
22
  "test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
23
23
  "test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
24
24
  "test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
package/src/electrumx.js CHANGED
@@ -638,13 +638,24 @@ class ElectrumX {
638
638
  // Sort confirmed Transactions by the block height
639
639
  sortConfTxs (txs, sortingOrder = 'DESCENDING') {
640
640
  try {
641
+ // console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`)
642
+
641
643
  // Filter out unconfirmed transactions, with a height of 0 or less.
642
644
  txs = txs.filter(elem => elem.height > 0)
643
645
 
644
646
  if (sortingOrder === 'DESCENDING') {
645
- return txs.sort((a, b) => b.height - a.height)
647
+ // console.log('Sorting in descending order')
648
+ return txs.sort((a, b) => {
649
+ // console.log(`descending b.height: ${b.height}, a.height: ${a.height}`)
650
+ return b.height - a.height
651
+ })
646
652
  }
647
- return txs.sort((a, b) => a.height - b.height)
653
+
654
+ // console.log('Sorting in ascending order')
655
+ return txs.sort((a, b) => {
656
+ // console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`)
657
+ return a.height - b.height
658
+ })
648
659
  } catch (err) {
649
660
  console.log('Error in util.js/sortConfTxs()')
650
661
  throw err
@@ -685,6 +696,8 @@ class ElectrumX {
685
696
  // Substitute zero-conf txs with the current block-height + 1
686
697
  async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
687
698
  try {
699
+ // console.log(`sortingOrder: ${sortingOrder}`)
700
+
688
701
  // Calculate the height of the next block
689
702
  const nextBlock = (await this.blockchain.getBlockCount()) + 1
690
703
 
@@ -132,7 +132,8 @@ class PsfSlpIndexer {
132
132
  */
133
133
  async balance (address) {
134
134
  try {
135
- console.log('balance() address: ', address)
135
+ // console.log('balance() address: ', address)
136
+
136
137
  // Handle single address.
137
138
  if (typeof address === 'string') {
138
139
  const response = await axios.post(
package/src/util.js CHANGED
@@ -150,6 +150,56 @@ class Util {
150
150
  }
151
151
  }
152
152
 
153
+ /**
154
+ * @api Util.chunk100() chunk100()
155
+ * @apiName chunk100
156
+ * @apiGroup Util
157
+ * @apiDescription chunk up an array into multiple arrays of 100 elements each.
158
+ * Input: arrayToSlice - a one-dimensional array of elements.
159
+ * Returns a two-dimensional array. An array of 100-element arrays.
160
+ *
161
+ * @apiExample Example usage:
162
+ * (async () => {
163
+ * try {
164
+ * const bigArray = [0,1,2,3,4,5,6,7,8,9,10,...,148, 149, 150]
165
+ *
166
+ * const chunked = bchjs.Util.chunk20(bigArray)
167
+ * console.log(chunked)
168
+ * } catch(error) {
169
+ * console.error(error)
170
+ * }
171
+ * })()
172
+ *
173
+ * // returns
174
+ * [
175
+ * [0,1,2,3,4,5,6,7,8,9,10,11,...,98,99],
176
+ * [100,101,102,...,148,149,150]
177
+ * ]
178
+ */
179
+ chunk100 (arrayToSlice) {
180
+ try {
181
+ // Validate inputs
182
+ if (!Array.isArray(arrayToSlice)) {
183
+ throw new Error('input must be an array')
184
+ }
185
+
186
+ let offset = 0
187
+ const result = []
188
+
189
+ // Loop over the array and slice off chunks of 100 elements.
190
+ while (offset < arrayToSlice.length) {
191
+ const chunk = arrayToSlice.slice(offset, offset + 100)
192
+ result.push(chunk)
193
+ offset = offset + 100
194
+ }
195
+
196
+ return result
197
+ } catch (err) {
198
+ console.error('Error in chunk100()')
199
+ throw err
200
+ }
201
+ }
202
+
153
203
  /**
154
204
  * @api Util.sleep() sleep()
155
205
  * @apiName sleep
package/src/utxo.js CHANGED
@@ -9,6 +9,7 @@
9
9
  const Electrumx = require('./electrumx')
10
10
  const Slp = require('./slp/slp')
11
11
  const PsfSlpIndexer = require('./psf-slp-indexer')
12
+ const BigNumber = require('bignumber.js')
12
13
 
13
14
  class UTXO {
14
15
  constructor (config = {}) {
@@ -16,6 +17,7 @@ class UTXO {
16
17
  this.electrumx = new Electrumx(config)
17
18
  this.slp = new Slp(config)
18
19
  this.psfSlpIndexer = new PsfSlpIndexer(config)
20
+ this.BigNumber = BigNumber
19
21
  }
20
22
 
21
23
  /**
@@ -309,10 +311,6 @@ class UTXO {
309
311
  * - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
310
312
  * - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
311
313
  *
312
- * Note: You can pass in an optional second Boolean argument. The default
313
- * `false` will use the normal waterfall validation method. Set to `true`,
314
- * SLP UTXOs will be validated with the whitelist filtered SLPDB. This will
315
- * result is many more UTXOs in the `nullUtxos` array.
316
314
  *
317
315
  * @apiExample Example usage:
318
316
  * (async () => {
@@ -359,10 +357,22 @@ class UTXO {
359
357
  // console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
360
358
  const utxos = utxoData.utxos
361
359
 
360
+ let slpUtxos = []
361
+
362
362
  // Get SLP UTXOs from the psf-slp-indexer
363
- const slpUtxoData = await this.psfSlpIndexer.balance(addr)
364
- // console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
365
- const slpUtxos = slpUtxoData.balance.utxos
363
+ try {
364
+ const slpUtxoData = await this.psfSlpIndexer.balance(addr)
365
+ // console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
366
+
367
+ slpUtxos = slpUtxoData.balance.utxos
368
+ } catch (err) {
369
+ // console.log('err: ', err)
370
+
371
+ // Exit quietly if address has no SLP UTXOs. Otherwise, throw the error.
372
+ if (err.error && !err.error.includes('Key not found in database')) {
373
+ throw err
374
+ }
375
+ }
366
376
 
367
377
  // Loop through the Fulcrum UTXOs.
368
378
  for (let i = 0; i < utxos.length; i++) {
@@ -399,10 +409,15 @@ class UTXO {
399
409
  }
400
410
  }
401
411
 
402
- const bchUtxos = utxos.filter(x => x.isSlp === false)
403
- const type1TokenUtxos = utxos.filter(
412
+ // Get token UTXOs
413
+ let type1TokenUtxos = utxos.filter(
404
414
  x => x.isSlp === true && x.type === 'token'
405
415
  )
416
+
417
+ // Hydrate the UTXOs with additional token data.
418
+ type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
419
+
420
+ const bchUtxos = utxos.filter(x => x.isSlp === false)
406
421
  const type1BatonUtxos = utxos.filter(
407
422
  x => x.isSlp === true && x.type === 'baton'
408
423
  )
@@ -430,6 +445,60 @@ class UTXO {
430
445
  }
431
446
  }
432
447
 
448
+ // Hydrate an array of token UTXOs with token information.
449
+ // Returns an array of token UTXOs with additional data.
450
+ async hydrateTokenData (utxoAry) {
451
+ try {
452
+ // console.log('utxoAry: ', utxoAry)
453
+
454
+ // Create a list of token IDs without duplicates.
455
+ let tokenIds = utxoAry.map(x => x.tokenId)
456
+
457
+ // Remove duplicates. https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array
458
+ tokenIds = [...new Set(tokenIds)]
459
+ // console.log('tokenIds: ', tokenIds)
460
+
461
+ // Get Genesis data for each tokenId
462
+ const genesisData = []
463
+ for (let i = 0; i < tokenIds.length; i++) {
464
+ const thisTokenId = tokenIds[i]
465
+ const thisTokenData = await this.psfSlpIndexer.tokenStats(thisTokenId)
466
+ // console.log('thisTokenData: ', thisTokenData)
467
+
468
+ genesisData.push(thisTokenData)
469
+ }
470
+ // console.log('genesisData: ', genesisData)
471
+
472
+ // Hydrate each token UTXO with data from the genesis transaction.
473
+ for (let i = 0; i < utxoAry.length; i++) {
474
+ const thisUtxo = utxoAry[i]
475
+
476
+ // Get the genesis data for this token.
477
+ const genData = genesisData.filter(
478
+ x => x.tokenData.tokenId === thisUtxo.tokenId
479
+ )
480
+ // console.log('genData: ', genData)
481
+
482
+ thisUtxo.ticker = genData[0].tokenData.ticker
483
+ thisUtxo.name = genData[0].tokenData.name
484
+ thisUtxo.documentUri = genData[0].tokenData.documentUri
485
+ thisUtxo.documentHash = genData[0].tokenData.documentHash
486
+ thisUtxo.decimals = genData[0].tokenData.decimals
487
+
488
+ // Calculate the real token quantity
489
+ const qty = new BigNumber(thisUtxo.qty).dividedBy(
490
+ 10 ** parseInt(thisUtxo.decimals)
491
+ )
492
+ thisUtxo.qtyStr = qty.toString()
493
+ }
494
+
495
+ return utxoAry
496
+ } catch (err) {
497
+ console.log('Error in hydrateTokenData()')
498
+ throw err
499
+ }
500
+ }
501
+
433
502
  /**
434
503
  * @api Utxo.findBiggestUtxo() findBiggestUtxo()
435
504
  * @apiName findBiggestUtxo
@@ -2,10 +2,10 @@
2
2
  Integration tests for the utxo.js library.
3
3
  */
4
4
 
5
- const assert = require('chai').assert
5
+ // const assert = require('chai').assert
6
6
 
7
- const BCHJS = require('../../../../src/bch-js')
8
- const bchjs = new BCHJS()
7
+ // const BCHJS = require('../../../../src/bch-js')
8
+ // const bchjs = new BCHJS()
9
9
 
10
10
  describe('#UTXO', () => {
11
11
  beforeEach(async () => {
@@ -14,6 +14,7 @@ describe('#UTXO', () => {
14
14
  if (process.env.IS_USING_FREE_TIER) await sleep(1500)
15
15
  })
16
16
 
17
+ /*
17
18
  describe('#get', () => {
18
19
  it('should get hydrated and filtered UTXOs for an address', async () => {
19
20
  // const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
@@ -31,6 +32,7 @@ describe('#UTXO', () => {
31
32
  assert.isArray(result[0].nullUtxos)
32
33
  })
33
34
  })
35
+ */
34
36
  })
35
37
 
36
38
  function sleep (ms) {
@@ -0,0 +1,28 @@
1
+ /*
2
+ Integration tests for the transaction.js library.
3
+ */
4
+
5
+ const assert = require('chai').assert
6
+ const BCHJS = require('../../../../src/bch-js')
7
+ const bchjs = new BCHJS()
8
+
9
+ describe('#Transaction', () => {
10
+ beforeEach(async () => {
11
+ if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
12
+ })
13
+
14
+ describe('#get', () => {
15
+ it('should get a tx details for a non-SLP TX with an OP_RETURN', async () => {
16
+ const txid =
17
+ '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
18
+
19
+ const result = await bchjs.Transaction.get(txid)
20
+ // console.log('result: ', result)
21
+
22
+ assert.property(result.txData, 'txid')
23
+ assert.property(result.txData, 'vin')
24
+ assert.property(result.txData, 'vout')
25
+ assert.equal(result.txData.isValidSlp, false)
26
+ })
27
+ })
28
+ })
@@ -13,84 +13,170 @@ describe('#UTXO', () => {
13
13
 
14
14
  if (process.env.IS_USING_FREE_TIER) await sleep(3000)
15
15
  })
16
- /*
17
- describe('#get', () => {
18
- it('should get hydrated and filtered UTXOs for an address', async () => {
19
- // const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
20
- const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
21
-
22
- const result = await bchjs.Utxo.get(addr)
23
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
24
16
 
25
- assert.isArray(result)
26
- assert.property(result[0], 'address')
27
- assert.property(result[0], 'bchUtxos')
28
- assert.property(result[0], 'nullUtxos')
29
- assert.property(result[0], 'slpUtxos')
30
- assert.isArray(result[0].bchUtxos)
31
- assert.isArray(result[0].nullUtxos)
17
+ if (process.env.TESTSLP) {
18
+ describe('#getOld', () => {
19
+ it('should get hydrated and filtered UTXOs for an address', async () => {
20
+ // const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
21
+ const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
22
+
23
+ const result = await bchjs.Utxo.getOld(addr)
24
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
25
+
26
+ assert.isArray(result)
27
+ assert.property(result[0], 'address')
28
+ assert.property(result[0], 'bchUtxos')
29
+ assert.property(result[0], 'nullUtxos')
30
+ assert.property(result[0], 'slpUtxos')
31
+ assert.isArray(result[0].bchUtxos)
32
+ assert.isArray(result[0].nullUtxos)
33
+ })
34
+
35
+ it('should handle an array of addresses', async () => {
36
+ const addr = [
37
+ 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9',
38
+ 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
39
+ ]
40
+
41
+ const result = await bchjs.Utxo.getOld(addr)
42
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
43
+
44
+ assert.isArray(result)
45
+ assert.property(result[0], 'address')
46
+ assert.property(result[0], 'bchUtxos')
47
+ assert.property(result[0], 'nullUtxos')
48
+ assert.property(result[0], 'slpUtxos')
49
+ assert.isArray(result[0].bchUtxos)
50
+ assert.isArray(result[0].nullUtxos)
51
+ })
52
+
53
+ it('should handle NFTs and minting batons', async () => {
54
+ const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
55
+
56
+ const result = await bchjs.Utxo.getOld(addr)
57
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
58
+
59
+ assert.isArray(result)
60
+ assert.property(result[0], 'address')
61
+ assert.property(result[0], 'bchUtxos')
62
+ assert.property(result[0], 'nullUtxos')
63
+ assert.property(result[0], 'slpUtxos')
64
+ assert.isArray(result[0].bchUtxos)
65
+ assert.isArray(result[0].nullUtxos)
66
+
67
+ assert.isArray(result[0].slpUtxos.type1.mintBatons)
68
+ assert.isArray(result[0].slpUtxos.type1.tokens)
69
+ assert.isArray(result[0].slpUtxos.nft.groupMintBatons)
70
+ assert.isArray(result[0].slpUtxos.nft.groupTokens)
71
+ assert.isArray(result[0].slpUtxos.nft.tokens)
72
+ })
73
+
74
+ it('should use the whitelist when flag is set', async () => {
75
+ const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
76
+ const useWhitelist = true
77
+
78
+ const result = await bchjs.Utxo.getOld(addr, useWhitelist)
79
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
80
+
81
+ assert.isArray(result)
82
+ assert.property(result[0], 'address')
83
+ assert.property(result[0], 'bchUtxos')
84
+ assert.property(result[0], 'nullUtxos')
85
+ assert.property(result[0], 'slpUtxos')
86
+ assert.isArray(result[0].bchUtxos)
87
+ assert.isArray(result[0].nullUtxos)
88
+
89
+ // Most token UTXOs should end up in the nullUtxos array.
90
+ assert.isAbove(result[0].bchUtxos.length, 0)
91
+ assert.isAbove(result[0].nullUtxos.length, 1)
92
+ })
32
93
  })
33
94
 
34
- it('should handle an array of addresses', async () => {
35
- const addr = [
36
- 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9',
37
- 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
38
- ]
95
+ describe('#findBiggestUtxo', () => {
96
+ it('should sort UTXOs from Electrumx', async () => {
97
+ const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
39
98
 
40
- const result = await bchjs.Utxo.get(addr)
41
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
99
+ const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
100
+ // console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
42
101
 
43
- assert.isArray(result)
44
- assert.property(result[0], 'address')
45
- assert.property(result[0], 'bchUtxos')
46
- assert.property(result[0], 'nullUtxos')
47
- assert.property(result[0], 'slpUtxos')
48
- assert.isArray(result[0].bchUtxos)
49
- assert.isArray(result[0].nullUtxos)
50
- })
102
+ const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
103
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
51
104
 
52
- it('should handle NFTs and minting batons', async () => {
53
- const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
105
+ assert.property(result, 'satoshis')
106
+ assert.equal(result.satoshis, 800)
107
+ })
54
108
 
55
- const result = await bchjs.Utxo.get(addr)
56
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
109
+ it('should sort UTXOs from Utxos.get()', async () => {
110
+ const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
57
111
 
58
- assert.isArray(result)
59
- assert.property(result[0], 'address')
60
- assert.property(result[0], 'bchUtxos')
61
- assert.property(result[0], 'nullUtxos')
62
- assert.property(result[0], 'slpUtxos')
63
- assert.isArray(result[0].bchUtxos)
64
- assert.isArray(result[0].nullUtxos)
65
-
66
- assert.isArray(result[0].slpUtxos.type1.mintBatons)
67
- assert.isArray(result[0].slpUtxos.type1.tokens)
68
- assert.isArray(result[0].slpUtxos.nft.groupMintBatons)
69
- assert.isArray(result[0].slpUtxos.nft.groupTokens)
70
- assert.isArray(result[0].slpUtxos.nft.tokens)
71
- })
112
+ const utxos = await bchjs.Utxo.getOld(addr)
113
+ // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
72
114
 
73
- it('should use the whitelist when flag is set', async () => {
74
- const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
75
- const useWhitelist = true
115
+ const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
116
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
76
117
 
77
- const result = await bchjs.Utxo.get(addr, useWhitelist)
78
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
118
+ assert.property(result, 'satoshis')
119
+ assert.equal(result.satoshis, 800)
120
+ })
121
+ })
122
+ }
123
+
124
+ describe('#hydrateTokenData', () => {
125
+ it('should hydrate token UTXOs', async () => {
126
+ const utxos = [
127
+ {
128
+ txid:
129
+ '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
130
+ vout: 1,
131
+ type: 'token',
132
+ qty: '10000000',
133
+ tokenId:
134
+ 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
135
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
136
+ },
137
+ {
138
+ txid:
139
+ '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
140
+ vout: 1,
141
+ type: 'token',
142
+ qty: '100000000',
143
+ tokenId:
144
+ 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
145
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
146
+ },
147
+ {
148
+ txid:
149
+ '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
150
+ vout: 5,
151
+ type: 'token',
152
+ qty: '4764',
153
+ tokenId:
154
+ 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
155
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
156
+ },
157
+ {
158
+ txid:
159
+ '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
160
+ vout: 5,
161
+ type: 'token',
162
+ qty: '238',
163
+ tokenId:
164
+ 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
165
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
166
+ }
167
+ ]
79
168
 
80
- assert.isArray(result)
81
- assert.property(result[0], 'address')
82
- assert.property(result[0], 'bchUtxos')
83
- assert.property(result[0], 'nullUtxos')
84
- assert.property(result[0], 'slpUtxos')
85
- assert.isArray(result[0].bchUtxos)
86
- assert.isArray(result[0].nullUtxos)
87
-
88
- // Most token UTXOs should end up in the nullUtxos array.
89
- assert.isAbove(result[0].bchUtxos.length, 0)
90
- assert.isAbove(result[0].nullUtxos.length, 1)
169
+ const result = await bchjs.Utxo.hydrateTokenData(utxos)
170
+ // console.log('result: ', result)
171
+
172
+ assert.property(result[0], 'ticker')
173
+ assert.property(result[0], 'name')
174
+ assert.property(result[0], 'qtyStr')
175
+ assert.property(result[0], 'documentUri')
176
+ assert.property(result[0], 'documentHash')
91
177
  })
92
178
  })
93
- */
179
+
94
180
  describe('#get', () => {
95
181
  it('should hydrate address with BCH and SLP UTXOs', async () => {
96
182
  const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
@@ -122,36 +208,17 @@ describe('#UTXO', () => {
122
208
  // Assert that minting batons are correctly identified.
123
209
  assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0)
124
210
  })
125
- })
126
- /*
127
- describe('#findBiggestUtxo', () => {
128
- it('should sort UTXOs from Electrumx', async () => {
129
- const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
130
211
 
131
- const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
132
- // console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
133
-
134
- const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
135
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
212
+ it('should return UTXOs for address with no SLP tokens', async () => {
213
+ const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
136
214
 
137
- assert.property(result, 'satoshis')
138
- assert.equal(result.satoshis, 800)
139
- })
140
-
141
- it('should sort UTXOs from Utxos.get()', async () => {
142
- const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
143
-
144
- const utxos = await bchjs.Utxo.get(addr)
145
- // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
146
-
147
- const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
215
+ const result = await bchjs.Utxo.get(addr)
148
216
  // console.log(`result: ${JSON.stringify(result, null, 2)}`)
149
217
 
150
- assert.property(result, 'satoshis')
151
- assert.equal(result.satoshis, 800)
218
+ assert.isAbove(result.bchUtxos.length, 0)
219
+ assert.equal(result.slpUtxos.type1.tokens.length, 0)
152
220
  })
153
221
  })
154
- */
155
222
  })
156
223
 
157
224
  function sleep (ms) {
@@ -328,6 +328,19 @@ const fulcrumUtxos01 = {
328
328
  ]
329
329
  }
330
330
 
331
+ const fulcrumUtxos02 = {
332
+ success: true,
333
+ utxos: [
334
+ {
335
+ height: 674513,
336
+ tx_hash:
337
+ '705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
338
+ tx_pos: 3,
339
+ value: 38134
340
+ }
341
+ ]
342
+ }
343
+
331
344
  const psfSlpIndexerUtxos01 = {
332
345
  balance: {
333
346
  utxos: [
@@ -368,6 +381,101 @@ const psfSlpIndexerUtxos01 = {
368
381
  }
369
382
  }
370
383
 
384
+ const tokenUtxos01 = [
385
+ {
386
+ txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
387
+ vout: 1,
388
+ type: 'token',
389
+ qty: '10000000',
390
+ tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
391
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
392
+ },
393
+ {
394
+ txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
395
+ vout: 1,
396
+ type: 'token',
397
+ qty: '100000000',
398
+ tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
399
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
400
+ },
401
+ {
402
+ txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
403
+ vout: 5,
404
+ type: 'token',
405
+ qty: '4764',
406
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
407
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
408
+ },
409
+ {
410
+ txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
411
+ vout: 5,
412
+ type: 'token',
413
+ qty: '238',
414
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
415
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
416
+ }
417
+ ]
418
+
419
+ const genesisData01 = {
420
+ tokenData: {
421
+ type: 1,
422
+ ticker: 'sleven',
423
+ name: 'sleven',
424
+ tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
425
+ documentUri: 'sleven',
426
+ documentHash: '',
427
+ decimals: 7,
428
+ mintBatonIsActive: false,
429
+ tokensInCirculationBN: '770059999999',
430
+ tokensInCirculationStr: '770059999999',
431
+ blockCreated: 555483,
432
+ totalBurned: '7711234568',
433
+ totalMinted: '777771234567'
434
+ }
435
+ }
436
+
437
+ const genesisData02 = {
438
+ tokenData: {
439
+ type: 1,
440
+ ticker: 'NAKAMOTO',
441
+ name: 'NAKAMOTO',
442
+ tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
443
+ documentUri: '',
444
+ documentHash: '',
445
+ decimals: 8,
446
+ mintBatonIsActive: false,
447
+ tokensInCirculationBN: '2099260968799900',
448
+ tokensInCirculationStr: '2099260968799900',
449
+ blockCreated: 555671,
450
+ totalBurned: '739031200100',
451
+ totalMinted: '2100000000000000'
452
+ }
453
+ }
454
+
455
+ const genesisData03 = {
456
+ tokenData: {
457
+ type: 1,
458
+ ticker: 'AUDC',
459
+ name: 'AUD Coin',
460
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
461
+ documentUri: 'audcoino@gmail.com',
462
+ documentHash: '',
463
+ decimals: 6,
464
+ mintBatonIsActive: false,
465
+ tokensInCirculationBN: '974791786216512742',
466
+ tokensInCirculationStr: '974791786216512742',
467
+ blockCreated: 603311,
468
+ totalBurned: '1025208213783487258',
469
+ totalMinted: '2000000000000000000'
470
+ }
471
+ }
472
+
473
+ const noUtxoErr = {
474
+ success: false,
475
+ error:
476
+ 'Key not found in database [bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7]'
477
+ }
478
+
371
479
  module.exports = {
372
480
  mockUtxoData,
373
481
  mockHydratedUtxos,
@@ -375,5 +483,11 @@ module.exports = {
375
483
  mockEveryUtxoType,
376
484
  electrumxUtxos,
377
485
  fulcrumUtxos01,
378
- psfSlpIndexerUtxos01
486
+ fulcrumUtxos02,
487
+ psfSlpIndexerUtxos01,
488
+ tokenUtxos01,
489
+ genesisData01,
490
+ genesisData02,
491
+ genesisData03,
492
+ noUtxoErr
379
493
  }
@@ -189,6 +189,45 @@ describe('#utxo', () => {
189
189
  })
190
190
  })
191
191
 
192
+ describe('#hydrateTokenData', () => {
193
+ it('should hydrate token UTXOs', async () => {
194
+ // Mock dependencies
195
+ sandbox
196
+ .stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
197
+ .onCall(0)
198
+ .resolves(mockData.genesisData01)
199
+ .onCall(1)
200
+ .resolves(mockData.genesisData02)
201
+ .onCall(2)
202
+ .resolves(mockData.genesisData03)
203
+
204
+ const result = await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
205
+ // console.log('result: ', result)
206
+
207
+ assert.equal(result.length, 4)
208
+ assert.property(result[0], 'qtyStr')
209
+ assert.property(result[0], 'ticker')
210
+ assert.property(result[0], 'name')
211
+ assert.property(result[0], 'documentUri')
212
+ assert.property(result[0], 'documentHash')
213
+ })
214
+
215
+ it('should should catch and throw errors', async () => {
216
+ try {
217
+ // Force error
218
+ sandbox
219
+ .stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
220
+ .rejects(new Error('test error'))
221
+
222
+ await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
223
+
224
+ assert.fail('Unexpected code path')
225
+ } catch (err) {
226
+ assert.equal(err.message, 'test error')
227
+ }
228
+ })
229
+ })
230
+
192
231
  describe('#get', () => {
193
232
  it('should throw an error if input is not a string', async () => {
194
233
  try {
@@ -210,6 +249,8 @@ describe('#utxo', () => {
210
249
  sandbox
211
250
  .stub(bchjs.Utxo.psfSlpIndexer, 'balance')
212
251
  .resolves(mockData.psfSlpIndexerUtxos01)
252
+ // Mock function to return the same input. Good enough for this test.
253
+ sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x)
213
254
 
214
255
  const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
215
256
 
@@ -231,5 +272,30 @@ describe('#utxo', () => {
231
272
  assert.equal(result.slpUtxos.type1.mintBatons.length, 1)
232
273
  assert.equal(result.nullUtxos.length, 0)
233
274
  })
275
+
276
+ it('should handle an address with no SLP UTXOs', async () => {
277
+ // mock dependencies
278
+ sandbox
279
+ .stub(bchjs.Utxo.electrumx, 'utxo')
280
+ .resolves(mockData.fulcrumUtxos02)
281
+
282
+ // Force psf-slp-indexer to return no UTXOs
283
+ sandbox
284
+ .stub(bchjs.Utxo.psfSlpIndexer, 'balance')
285
+ .rejects(mockData.noUtxoErr)
286
+
287
+ // Mock function to return the same input. Good enough for this test.
288
+ sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => [])
289
+
290
+ const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
291
+
292
+ const result = await bchjs.Utxo.get(addr)
293
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
294
+
295
+ assert.equal(result.bchUtxos.length, 1)
296
+ assert.equal(result.slpUtxos.type1.tokens.length, 0)
297
+ assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
298
+ assert.equal(result.nullUtxos.length, 0)
299
+ })
234
300
  })
235
301
  })