@psf/bch-js 5.2.0 → 5.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
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": [
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
  /**
@@ -399,10 +401,15 @@ class UTXO {
399
401
  }
400
402
  }
401
403
 
402
- const bchUtxos = utxos.filter(x => x.isSlp === false)
403
- const type1TokenUtxos = utxos.filter(
404
+ // Get token UTXOs
405
+ let type1TokenUtxos = utxos.filter(
404
406
  x => x.isSlp === true && x.type === 'token'
405
407
  )
408
+
409
+ // Hydrate the UTXOs with additional token data.
410
+ type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
411
+
412
+ const bchUtxos = utxos.filter(x => x.isSlp === false)
406
413
  const type1BatonUtxos = utxos.filter(
407
414
  x => x.isSlp === true && x.type === 'baton'
408
415
  )
@@ -430,6 +437,60 @@ class UTXO {
430
437
  }
431
438
  }
432
439
 
440
+ // Hydrate an array of token UTXOs with token information.
441
+ // Returns an array of token UTXOs with additional data.
442
+ async hydrateTokenData (utxoAry) {
443
+ try {
444
+ // console.log('utxoAry: ', utxoAry)
445
+
446
+ // Create a list of token IDs without duplicates.
447
+ let tokenIds = utxoAry.map(x => x.tokenId)
448
+
449
+ // Remove duplicates. https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array
450
+ tokenIds = [...new Set(tokenIds)]
451
+ // console.log('tokenIds: ', tokenIds)
452
+
453
+ // Get Genesis data for each tokenId
454
+ const genesisData = []
455
+ for (let i = 0; i < tokenIds.length; i++) {
456
+ const thisTokenId = tokenIds[i]
457
+ const thisTokenData = await this.psfSlpIndexer.tokenStats(thisTokenId)
458
+ // console.log('thisTokenData: ', thisTokenData)
459
+
460
+ genesisData.push(thisTokenData)
461
+ }
462
+ // console.log('genesisData: ', genesisData)
463
+
464
+ // Hydrate each token UTXO with data from the genesis transaction.
465
+ for (let i = 0; i < utxoAry.length; i++) {
466
+ const thisUtxo = utxoAry[i]
467
+
468
+ // Get the genesis data for this token.
469
+ const genData = genesisData.filter(
470
+ x => x.tokenData.tokenId === thisUtxo.tokenId
471
+ )
472
+ // console.log('genData: ', genData)
473
+
474
+ thisUtxo.ticker = genData[0].tokenData.ticker
475
+ thisUtxo.name = genData[0].tokenData.name
476
+ thisUtxo.documentUri = genData[0].tokenData.documentUri
477
+ thisUtxo.documentHash = genData[0].tokenData.documentHash
478
+ thisUtxo.decimals = genData[0].tokenData.decimals
479
+
480
+ // Calculate the real token quantity
481
+ const qty = new BigNumber(thisUtxo.qty).dividedBy(
482
+ 10 ** parseInt(thisUtxo.decimals)
483
+ )
484
+ thisUtxo.qtyStr = qty.toString()
485
+ }
486
+
487
+ return utxoAry
488
+ } catch (err) {
489
+ console.log('Error in hydrateTokenData()')
490
+ throw err
491
+ }
492
+ }
493
+
433
494
  /**
434
495
  * @api Utxo.findBiggestUtxo() findBiggestUtxo()
435
496
  * @apiName findBiggestUtxo
@@ -91,6 +91,57 @@ describe('#UTXO', () => {
91
91
  })
92
92
  })
93
93
  */
94
+
95
+ describe('#hydrateTokenData', () => {
96
+ it('should hydrate token UTXOs', async () => {
97
+ const utxos = [
98
+ {
99
+ txid:
100
+ '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
101
+ vout: 1,
102
+ type: 'token',
103
+ qty: '10000000',
104
+ tokenId:
105
+ 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
106
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
107
+ },
108
+ {
109
+ txid:
110
+ '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
111
+ vout: 1,
112
+ type: 'token',
113
+ qty: '100000000',
114
+ tokenId:
115
+ 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
116
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
117
+ },
118
+ {
119
+ txid:
120
+ '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
121
+ vout: 5,
122
+ type: 'token',
123
+ qty: '4764',
124
+ tokenId:
125
+ 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
126
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
127
+ },
128
+ {
129
+ txid:
130
+ '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
131
+ vout: 5,
132
+ type: 'token',
133
+ qty: '238',
134
+ tokenId:
135
+ 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
136
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
137
+ }
138
+ ]
139
+
140
+ const result = await bchjs.Utxo.hydrateTokenData(utxos)
141
+ console.log('result: ', result)
142
+ })
143
+ })
144
+
94
145
  describe('#get', () => {
95
146
  it('should hydrate address with BCH and SLP UTXOs', async () => {
96
147
  const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
@@ -117,12 +168,13 @@ describe('#UTXO', () => {
117
168
  const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
118
169
 
119
170
  const result = await bchjs.Utxo.get(addr)
120
- // console.log(`result: ${JSON.stringify(result, null, 2)}`)
171
+ console.log(`result: ${JSON.stringify(result, null, 2)}`)
121
172
 
122
173
  // Assert that minting batons are correctly identified.
123
174
  assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0)
124
175
  })
125
176
  })
177
+
126
178
  /*
127
179
  describe('#findBiggestUtxo', () => {
128
180
  it('should sort UTXOs from Electrumx', async () => {
@@ -368,6 +368,95 @@ const psfSlpIndexerUtxos01 = {
368
368
  }
369
369
  }
370
370
 
371
+ const tokenUtxos01 = [
372
+ {
373
+ txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
374
+ vout: 1,
375
+ type: 'token',
376
+ qty: '10000000',
377
+ tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
378
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
379
+ },
380
+ {
381
+ txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
382
+ vout: 1,
383
+ type: 'token',
384
+ qty: '100000000',
385
+ tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
386
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
387
+ },
388
+ {
389
+ txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
390
+ vout: 5,
391
+ type: 'token',
392
+ qty: '4764',
393
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
394
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
395
+ },
396
+ {
397
+ txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
398
+ vout: 5,
399
+ type: 'token',
400
+ qty: '238',
401
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
402
+ address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
403
+ }
404
+ ]
405
+
406
+ const genesisData01 = {
407
+ tokenData: {
408
+ type: 1,
409
+ ticker: 'sleven',
410
+ name: 'sleven',
411
+ tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
412
+ documentUri: 'sleven',
413
+ documentHash: '',
414
+ decimals: 7,
415
+ mintBatonIsActive: false,
416
+ tokensInCirculationBN: '770059999999',
417
+ tokensInCirculationStr: '770059999999',
418
+ blockCreated: 555483,
419
+ totalBurned: '7711234568',
420
+ totalMinted: '777771234567'
421
+ }
422
+ }
423
+
424
+ const genesisData02 = {
425
+ tokenData: {
426
+ type: 1,
427
+ ticker: 'NAKAMOTO',
428
+ name: 'NAKAMOTO',
429
+ tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
430
+ documentUri: '',
431
+ documentHash: '',
432
+ decimals: 8,
433
+ mintBatonIsActive: false,
434
+ tokensInCirculationBN: '2099260968799900',
435
+ tokensInCirculationStr: '2099260968799900',
436
+ blockCreated: 555671,
437
+ totalBurned: '739031200100',
438
+ totalMinted: '2100000000000000'
439
+ }
440
+ }
441
+
442
+ const genesisData03 = {
443
+ tokenData: {
444
+ type: 1,
445
+ ticker: 'AUDC',
446
+ name: 'AUD Coin',
447
+ tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
448
+ documentUri: 'audcoino@gmail.com',
449
+ documentHash: '',
450
+ decimals: 6,
451
+ mintBatonIsActive: false,
452
+ tokensInCirculationBN: '974791786216512742',
453
+ tokensInCirculationStr: '974791786216512742',
454
+ blockCreated: 603311,
455
+ totalBurned: '1025208213783487258',
456
+ totalMinted: '2000000000000000000'
457
+ }
458
+ }
459
+
371
460
  module.exports = {
372
461
  mockUtxoData,
373
462
  mockHydratedUtxos,
@@ -375,5 +464,9 @@ module.exports = {
375
464
  mockEveryUtxoType,
376
465
  electrumxUtxos,
377
466
  fulcrumUtxos01,
378
- psfSlpIndexerUtxos01
467
+ psfSlpIndexerUtxos01,
468
+ tokenUtxos01,
469
+ genesisData01,
470
+ genesisData02,
471
+ genesisData03
379
472
  }
@@ -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