@psf/bch-js 5.4.0 → 6.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/src/slp/utils.js CHANGED
@@ -3,7 +3,6 @@
3
3
  // Public npm libraries
4
4
  const axios = require('axios')
5
5
  const slpParser = require('slp-parser')
6
- const BigNumber = require('bignumber.js')
7
6
 
8
7
  // Local libraries
9
8
  const Util = require('../util')
@@ -41,863 +40,6 @@ class Utils {
41
40
  this.util = new Util(config)
42
41
  }
43
42
 
44
- /**
45
- * @api SLP.Utils.list() list()
46
- * @apiName list
47
- * @apiGroup SLP Utils
48
- * @apiDescription List all tokens or list single token by id.
49
- *
50
- * @apiExample Example usage:
51
- *
52
- * // List all tokens
53
- *
54
- * (async () => {
55
- * try {
56
- * let list = await bchjs.SLP.Utils.list();
57
- * console.log(list);
58
- * } catch (error) {
59
- * console.error(error);
60
- * }
61
- * })();
62
- *
63
- * // returns
64
- * [ { decimals: 5,
65
- * timestamp: '2019-04-20 05:03',
66
- * versionType: 1,
67
- * documentUri: 'developer.bitcoin.com',
68
- * symbol: 'MYSTERY',
69
- * name: 'Mystery',
70
- * containsBaton: true,
71
- * id:
72
- * '10528f22fc20422f7c1075a87ed7270c0a17bc17ea79c6e2f426c6cc14bb25f2',
73
- * documentHash:
74
- * '1010101010101010101010101010101010101010101010101010101010101010',
75
- * initialTokenQty: 500,
76
- * blockCreated: 579041,
77
- * blockLastActiveSend: null,
78
- * blockLastActiveMint: null,
79
- * txnsSinceGenesis: 1,
80
- * validAddresses: 1,
81
- * totalMinted: 500,
82
- * totalBurned: 0,
83
- * circulatingSupply: 500,
84
- * mintingBatonStatus: 'ALIVE' },
85
- * { decimals: 8,
86
- * timestamp: '2019-04-20 04:54',
87
- * versionType: 1,
88
- * documentUri: 'developer.bitcoin.com',
89
- * symbol: 'ENIGMA',
90
- * name: 'Enigma',
91
- * containsBaton: true,
92
- * id:
93
- * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
94
- * documentHash:
95
- * '1010101010101010101010101010101010101010101010101010101010101010',
96
- * initialTokenQty: 1234,
97
- * blockCreated: 579040,
98
- * blockLastActiveSend: null,
99
- * blockLastActiveMint: 579040,
100
- * txnsSinceGenesis: 2,
101
- * validAddresses: 2,
102
- * totalMinted: 1334,
103
- * totalBurned: 0,
104
- * circulatingSupply: 1334,
105
- * mintingBatonStatus: 'ALIVE' }
106
- * ]
107
- *
108
- * // List single token
109
- *
110
- * (async () => {
111
- * try {
112
- * let list = await bchjs.SLP.Utils.list(
113
- * "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313"
114
- * );
115
- * console.log(list);
116
- * } catch (error) {
117
- * console.error(error);
118
- * }
119
- * })();
120
- *
121
- * // returns
122
- * { decimals: 8,
123
- * timestamp: '2019-04-20 04:54',
124
- * versionType: 1,
125
- * documentUri: 'developer.bitcoin.com',
126
- * symbol: 'ENIGMA',
127
- * name: 'Enigma',
128
- * containsBaton: true,
129
- * id:
130
- * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
131
- * documentHash:
132
- * '1010101010101010101010101010101010101010101010101010101010101010',
133
- * initialTokenQty: 1234,
134
- * blockCreated: 579040,
135
- * blockLastActiveSend: null,
136
- * blockLastActiveMint: 579040,
137
- * txnsSinceGenesis: 2,
138
- * validAddresses: 2,
139
- * totalMinted: 1334,
140
- * totalBurned: 0,
141
- * circulatingSupply: 1334,
142
- * mintingBatonStatus: 'ALIVE' }
143
- *
144
- * // List multiple tokens by tokenIds
145
- *
146
- * (async () => {
147
- * try {
148
- * let list = await bchjs.SLP.Utils.list([
149
- * "fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f",
150
- * "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0"
151
- * ]);
152
- * console.log(list);
153
- * } catch (error) {
154
- * console.error(error);
155
- * }
156
- * })();
157
- *
158
- * // returns
159
- * [ { decimals: 0,
160
- * timestamp: '2019-02-18 14:47',
161
- * versionType: 1,
162
- * documentUri: 'https://simpleledger.cash',
163
- * symbol: 'SLP',
164
- * name: 'Official SLP Token',
165
- * containsBaton: true,
166
- * id:
167
- * 'fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f',
168
- * documentHash: null,
169
- * initialTokenQty: 18446744073709552000,
170
- * blockCreated: 570305,
171
- * blockLastActiveSend: 580275,
172
- * blockLastActiveMint: 575914,
173
- * txnsSinceGenesis: 4537,
174
- * validAddresses: 164,
175
- * totalMinted: 19414628793626410000,
176
- * totalBurned: 18446568350267302000,
177
- * circulatingSupply: 968060443359109600,
178
- * mintingBatonStatus: 'ALIVE' },
179
- * { decimals: 8,
180
- * timestamp: '2019-02-14 03:11',
181
- * versionType: 1,
182
- * documentUri: 'psfoundation.cash',
183
- * symbol: 'PSF',
184
- * name: 'Permissionless Software Foundation',
185
- * containsBaton: true,
186
- * id:
187
- * '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
188
- * documentHash: null,
189
- * initialTokenQty: 19882.09163133,
190
- * blockCreated: 569658,
191
- * blockLastActiveSend: 580153,
192
- * blockLastActiveMint: null,
193
- * txnsSinceGenesis: 51,
194
- * validAddresses: 9,
195
- * totalMinted: 19882.09163133,
196
- * totalBurned: 0.0534241,
197
- * circulatingSupply: 19882.03820723,
198
- * mintingBatonStatus: 'ALIVE' } ]
199
- */
200
- async list (id) {
201
- let path
202
- let method
203
-
204
- if (!id) {
205
- method = 'get'
206
- path = `${this.restURL}slp/list`
207
- } else if (typeof id === 'string') {
208
- method = 'get'
209
- path = `${this.restURL}slp/list/${id}`
210
- } else if (typeof id === 'object') {
211
- method = 'post'
212
- path = `${this.restURL}slp/list`
213
- }
214
-
215
- // console.log(`path: ${path}`)
216
-
217
- try {
218
- let response
219
- if (method === 'get') {
220
- response = await _this.axios.get(path, this.axiosOptions)
221
- } else {
222
- response = await _this.axios.post(
223
- path,
224
- {
225
- tokenIds: id
226
- },
227
- this.axiosOptions
228
- )
229
- }
230
- return response.data
231
- } catch (error) {
232
- if (error.response && error.response.data) throw error.response.data
233
- throw error
234
- }
235
- }
236
-
237
- /**
238
- * @api SLP.Utils.balancesForAddress() balancesForAddress()
239
- * @apiName balancesForAddress
240
- * @apiGroup SLP Utils
241
- * @apiDescription Return all balances for an address or array of addresses.
242
- *
243
- * @apiExample Example usage:
244
- *
245
- * (async () => {
246
- * try {
247
- * let balances = await bchjs.SLP.Utils.balancesForAddress('simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk');
248
- * console.log(balances);
249
- * } catch (error) {
250
- * console.error(error);
251
- * }
252
- * })();
253
- *
254
- * // returns
255
- * // [ { tokenId:
256
- * // '968ff0cc4c93864001e03e9524e351250b94ec56150fa4897f65b0b6477d44d4',
257
- * // balance: '9980',
258
- * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
259
- * // decimalCount: 9 },
260
- * // { tokenId:
261
- * // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
262
- * // balance: '617',
263
- * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
264
- * // decimalCount: 8 },
265
- * // { tokenId:
266
- * // 'b96304d12f1bbc2196df582516410e55a9b34e13c7b4585bf5c1770af30d034f',
267
- * // balance: '1',
268
- * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
269
- * // decimalCount: 0 },
270
- * // { tokenId:
271
- * // 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
272
- * // balance: '776',
273
- * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
274
- * // decimalCount: 7 } ]
275
- *
276
- * // balances for Cash Address
277
- * (async () => {
278
- * try {
279
- * let balances = await bchjs.SLP.Utils.balancesForAddress('bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y');
280
- * console.log(balances);
281
- * } catch (error) {
282
- * console.error(error);
283
- * }
284
- * })();
285
- *
286
- * // returns
287
- * // [ { tokenId:
288
- * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
289
- * // balance: '507',
290
- * // decimalCount: 2 } ]
291
- *
292
- * // balances for Legacy Address
293
- * (async () => {
294
- * try {
295
- * let balances = await bchjs.SLP.Utils.balancesForAddress('1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS');
296
- * console.log(balances);
297
- * } catch (error) {
298
- * console.error(error);
299
- * }
300
- * })();
301
- *
302
- * // returns
303
- * // [ { tokenId:
304
- * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
305
- * // balance: '507',
306
- * // decimalCount: 2 } ]
307
- *
308
- * Note: Balances for multiple addresses can be retrieves by passing in an
309
- * array of addresses.
310
- */
311
- // Retrieve token balances for a given address.
312
- async balancesForAddress (address) {
313
- try {
314
- // Single address.
315
- if (typeof address === 'string') {
316
- const path = `${this.restURL}slp/balancesForAddress/${address}`
317
-
318
- const response = await _this.axios.get(path, this.axiosOptions)
319
- return response.data
320
-
321
- // Array of addresses.
322
- } else if (Array.isArray(address)) {
323
- const path = `${this.restURL}slp/balancesForAddress`
324
-
325
- // Dev note: must use axios.post for unit test stubbing.
326
- const response = await _this.axios.post(
327
- path,
328
- {
329
- addresses: address
330
- },
331
- this.axiosOptions
332
- )
333
-
334
- return response.data
335
- }
336
-
337
- throw new Error('Input address must be a string or array of strings.')
338
- } catch (error) {
339
- if (error.response && error.response.data) throw error.response.data
340
- throw error
341
- }
342
- }
343
-
344
- /**
345
- * @api SLP.Utils.balancesForToken() balancesForToken()
346
- * @apiName balancesForToken
347
- * @apiGroup SLP Utils
348
- * @apiDescription List all balances for tokenId.
349
- *
350
- * @apiExample Example usage:
351
- *
352
- * (async () => {
353
- * try {
354
- * let balances = await bchjs.SLP.Utils.balancesForToken(
355
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
356
- * )
357
- * console.log(balances)
358
- * } catch (error) {
359
- * console.error(error)
360
- * }
361
- * })()
362
- *
363
- * // returns
364
- * [
365
- * {
366
- * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
367
- * tokenBalance: 20,
368
- * slpAddress: 'simpleledger:qp4g0q97tq53pasnxk2rs570c6573qvylunsf5gy9e'
369
- * },
370
- * {
371
- * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
372
- * tokenBalance: 335.55,
373
- * slpAddress: 'simpleledger:qqcraw7q0ys3kg4z6f2zd267fhg2093c5c0spfk03f'
374
- * }
375
- * ]
376
- *
377
- */
378
- // Retrieve token balances for a given tokenId.
379
- async balancesForToken (tokenId) {
380
- try {
381
- const path = `${this.restURL}slp/balancesForToken/${tokenId}`
382
-
383
- const response = await _this.axios.get(path, this.axiosOptions)
384
- return response.data
385
- } catch (error) {
386
- if (error.response && error.response.data) throw error.response.data
387
- throw error
388
- }
389
- }
390
-
391
- /**
392
- * @api SLP.Utils.validateTxid() validateTxid()
393
- * @apiName validateTxid
394
- * @apiGroup SLP Utils
395
- * @apiDescription Validate that txid is an SLP transaction.
396
- *
397
- * @apiExample Example usage:
398
- *
399
- * // validate single SLP txid
400
- * (async () => {
401
- * try {
402
- * let validated = await bchjs.SLP.Utils.validateTxid(
403
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
404
- * );
405
- * console.log(validated);
406
- * } catch (error) {
407
- * console.error(error);
408
- * }
409
- * })();
410
- *
411
- * // returns
412
- * [ { txid:
413
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
414
- * valid: true } ]
415
- *
416
- * // validate multiple SLP txids
417
- * (async () => {
418
- * try {
419
- * let validated = await bchjs.SLP.Utils.validateTxid([
420
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
421
- * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476"
422
- * ]);
423
- * console.log(validated);
424
- * } catch (error) {
425
- * console.error(error);
426
- * }
427
- * })();
428
- *
429
- * // returns
430
- * [ { txid:
431
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
432
- * valid: true },
433
- * { txid:
434
- * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
435
- * valid: true } ]
436
- */
437
- // This function has two responses. If SLPDB is working correctly, the output
438
- // will be like the examples above. If SLPDB has fallen behind real-time
439
- // processing, it will return this output:
440
- // [ null ]
441
- async validateTxid (txid, usrObj = null) {
442
- const path = `${this.restURL}slp/validateTxid`
443
-
444
- // console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
445
- // console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`)
446
-
447
- // Handle a single TXID or an array of TXIDs.
448
- let txids
449
- if (typeof txid === 'string') txids = [txid]
450
- else txids = txid
451
-
452
- try {
453
- // console.log('validateTxid() this.axiosOptions: ', this.axiosOptions)
454
- const response = await _this.axios.post(
455
- path,
456
- {
457
- txids: txids,
458
- usrObj // pass user data when making an internal call.
459
- },
460
- this.axiosOptions
461
- )
462
- // console.log(
463
- // `validateTxid response.data: ${JSON.stringify(response.data, null, 2)}`
464
- // )
465
-
466
- const validatedTxids = response.data
467
-
468
- return validatedTxids
469
- } catch (error) {
470
- if (error.response && error.response.data) throw error.response.data
471
- throw error
472
- }
473
- }
474
-
475
- /**
476
- * @api SLP.Utils.validateTxid2() validateTxid2()
477
- * @apiName validateTxid2
478
- * @apiGroup SLP Utils
479
- * @apiDescription Validate that txid is an SLP transaction.
480
- *
481
- * This second validatoin version uses the slp-validate slp library. It is
482
- * much slower and less efficient than SLPDB and is prone to time-outs for
483
- * tokens with large DAGs. However, it operates independently of SLPDB and
484
- * is a great second validation option, particularly when SLPDB returns 'null'
485
- * values.
486
- *
487
- * Due to the inefficiency of this call, only a single TXID can be input at a
488
- * time. This call will throw an error if the input is an array.
489
- *
490
- * @apiExample Example usage:
491
- *
492
- * // validate single SLP txid
493
- * (async () => {
494
- * try {
495
- * let validated = await bchjs.SLP.Utils.validateTxid2(
496
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
497
- * );
498
- * console.log(validated);
499
- * } catch (error) {
500
- * console.error(error);
501
- * }
502
- * })();
503
- *
504
- * // returns
505
- * [ { txid:
506
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
507
- * valid: true } ]
508
- */
509
- async validateTxid2 (txid) {
510
- try {
511
- // console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
512
-
513
- if (
514
- !txid ||
515
- txid === '' ||
516
- typeof txid !== 'string' ||
517
- txid.length !== 64
518
- ) {
519
- throw new Error('txid must be 64 character string.')
520
- }
521
-
522
- const path = `${this.restURL}slp/validateTxid2/${txid}`
523
-
524
- // console.log('validateTxid2() this.axiosOptions: ', this.axiosOptions)
525
- const response = await _this.axios.get(path, this.axiosOptions)
526
- return response.data
527
- } catch (error) {
528
- if (error.response && error.response.data) throw error.response.data
529
-
530
- if (error.error && error.error.indexOf('Network error') > -1) {
531
- throw new Error('slp-validate timed out')
532
- }
533
-
534
- throw error
535
- }
536
- }
537
-
538
- /**
539
- * @api SLP.Utils.whitelist() whitelist()
540
- * @apiName whitelist
541
- * @apiGroup SLP Utils
542
- * @apiDescription Get SLP tokens in whitelist
543
- * Retrieves a list of the SLP tokens that in the whitelist. Tokens in the
544
- * whitelist can be validated with the validateTxid3() function. validateTxid3()
545
- * will still work when the SLP network is under stress.
546
- *
547
- * @apiExample Example usage:
548
- *
549
- * // validate single SLP txid
550
- * (async () => {
551
- * try {
552
- * let list = await bchjs.SLP.Utils.whitelit();
553
- * console.log(list);
554
- * } catch (error) {
555
- * console.error(error);
556
- * }
557
- * })();
558
- *
559
- * // returns
560
- * [
561
- * {
562
- * name: 'USDH',
563
- * tokenId:
564
- * 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479'
565
- * },
566
- * {
567
- * name: 'SPICE',
568
- * tokenId:
569
- * '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
570
- * },
571
- * {
572
- * name: 'PSF',
573
- * tokenId:
574
- * '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
575
- * },
576
- * {
577
- * name: 'TROUT',
578
- * tokenId:
579
- * 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
580
- * },
581
- * {
582
- * name: 'PSFTEST',
583
- * tokenId:
584
- * 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0'
585
- * }
586
- * ]
587
- */
588
- async getWhitelist () {
589
- try {
590
- const path = `${this.restURL}slp/whitelist`
591
-
592
- // Retrieve the whitelist from the REST API if we haven't gotten it yet.
593
- if (this.whitelist.length === 0) {
594
- const response = await _this.axios.get(path, this.axiosOptions)
595
- // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
596
-
597
- this.whitelist = response.data
598
- }
599
-
600
- return this.whitelist
601
- } catch (error) {
602
- if (error.response && error.response.data) throw error.response.data
603
- throw error
604
- }
605
- }
606
-
607
- /**
608
- * @api SLP.Utils.validateTxid3() validateTxid3()
609
- * @apiName validateTxid3
610
- * @apiGroup SLP Utils
611
- * @apiDescription
612
- * Validate that txid is an SLP transaction using the SLPDB whitelist server.
613
- * Same exact functionality as the validateTxid() function, but this function
614
- * calls the whitelist SLPDB. It will only validate SLP tokens that are in the
615
- * whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist()
616
- * function.
617
- *
618
- * @apiExample Example usage:
619
- *
620
- * // validate single SLP txid
621
- * (async () => {
622
- * try {
623
- * let validated = await bchjs.SLP.Utils.validateTxid3(
624
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
625
- * );
626
- * console.log(validated);
627
- * } catch (error) {
628
- * console.error(error);
629
- * }
630
- * })();
631
- *
632
- * // returns
633
- * [ { txid:
634
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
635
- * valid: true } ]
636
- *
637
- * // validate multiple SLP txids
638
- * (async () => {
639
- * try {
640
- * let validated = await bchjs.SLP.Utils.validateTxid3([
641
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
642
- * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476"
643
- * ]);
644
- * console.log(validated);
645
- * } catch (error) {
646
- * console.error(error);
647
- * }
648
- * })();
649
- *
650
- * // returns
651
- * [ { txid:
652
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
653
- * valid: true },
654
- * { txid:
655
- * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
656
- * valid: true } ]
657
- */
658
- async validateTxid3 (txid, usrObj = null) {
659
- const path = `${this.restURL}slp/validateTxid3`
660
-
661
- // console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
662
- // console.log(`path: ${JSON.stringify(path, null, 2)}`)
663
- // console.log('validateTxid3 usrObj: ', usrObj)
664
-
665
- // Handle a single TXID or an array of TXIDs.
666
- let txids
667
- if (typeof txid === 'string') txids = [txid]
668
- else txids = txid
669
-
670
- try {
671
- // console.log('validateTxid3() this.axiosOptions: ', this.axiosOptions)
672
- const response = await _this.axios.post(
673
- path,
674
- {
675
- txids: txids,
676
- usrObj // pass user data when making an internal call.
677
- },
678
- this.axiosOptions
679
- )
680
- // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
681
-
682
- const validatedTxids = response.data
683
-
684
- return validatedTxids
685
- } catch (error) {
686
- // console.log('validateTxid3 error: ', error)
687
-
688
- // This case handles rate limit errors.
689
- if (error.response && error.response.data && error.response.data.error) {
690
- throw new Error(error.response.data.error)
691
- }
692
-
693
- // Not sure if this can be safely deprecated?
694
- if (error.response && error.response.data) throw error.response.data
695
- throw error
696
- }
697
- }
698
-
699
- /**
700
- * @api SLP.Utils.tokenStats() tokenStats()
701
- * @apiName tokenStats
702
- * @apiGroup SLP Utils
703
- * @apiDescription Stats for token by tokenId.
704
- *
705
- * @apiExample Example usage:
706
- *
707
- * (async () => {
708
- * try {
709
- * let stats = await bchjs.SLP.Utils.tokenStats(
710
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
711
- * )
712
- * console.log(stats)
713
- * } catch (error) {
714
- * console.error(error)
715
- * }
716
- * })()
717
- *
718
- * // returns
719
- * { tokenId:
720
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
721
- * documentUri: '',
722
- * symbol: 'NAKAMOTO',
723
- * name: 'NAKAMOTO',
724
- * decimals: 8,
725
- * txnsSinceGenesis: 367,
726
- * validUtxos: 248,
727
- * validAddresses: 195,
728
- * circulatingSupply: 20995990,
729
- * totalBurned: 4010,
730
- * totalMinted: 21000000,
731
- * satoshisLockedUp: 135408
732
- * }
733
- */
734
- async tokenStats (tokenId) {
735
- try {
736
- const path = `${this.restURL}slp/tokenStats/${tokenId}`
737
-
738
- const response = await _this.axios.get(path, this.axiosOptions)
739
-
740
- return response.data
741
- } catch (error) {
742
- if (error.response && error.response.data) throw error.response.data
743
- throw error
744
- }
745
- }
746
-
747
- /**
748
- * @api SLP.Utils.transactions() transactions()
749
- * @apiName transactions
750
- * @apiGroup SLP Utils
751
- * @apiDescription SLP Transactions by tokenId and address.
752
- *
753
- * @apiExample Example usage:
754
- *
755
- * (async () => {
756
- * try {
757
- * let transactions = await bchjs.SLP.Utils.transactions(
758
- * "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
759
- * "qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqlsy4gusz"
760
- * )
761
- * console.log(transactions)
762
- * } catch (error) {
763
- * console.error(error)
764
- * }
765
- * })()
766
- *
767
- * // returns
768
- * [
769
- * {
770
- * "txid": "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f",
771
- * "tokenDetails": {
772
- * "valid": true,
773
- * "detail": {
774
- * "decimals": null,
775
- * "tokenIdHex": "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
776
- * "timestamp": null,
777
- * "transactionType": "SEND",
778
- * "versionType": 1,
779
- * "documentUri": null,
780
- * "documentSha256Hex": null,
781
- * "symbol": null,
782
- * "name": null,
783
- * "batonVout": null,
784
- * "containsBaton": null,
785
- * "genesisOrMintQuantity": null,
786
- * "sendOutputs": [
787
- * {
788
- * "$numberDecimal": "0"
789
- * },
790
- * {
791
- * "$numberDecimal": "25"
792
- * },
793
- * {
794
- * "$numberDecimal": "77"
795
- * }
796
- * ]
797
- * },
798
- * "invalidReason": null,
799
- * "schema_version": 30
800
- * }
801
- * }
802
- * ]
803
- */
804
- // Retrieve token transactions for a given tokenId and address.
805
- async transactions (tokenId, address) {
806
- try {
807
- const path = `${this.restURL}slp/transactions/${tokenId}/${address}`
808
-
809
- const response = await _this.axios.get(path, this.axiosOptions)
810
-
811
- return response.data
812
- } catch (error) {
813
- if (error.response && error.response.data) throw error.response.data
814
- throw error
815
- }
816
- }
817
-
818
- /**
819
- * @api SLP.Utils.burnTotal() burnTotal()
820
- * @apiName burnTotal
821
- * @apiGroup SLP Utils
822
- * @apiDescription List input, output and burn total for slp transaction.
823
- *
824
- * @apiExample Example usage:
825
- *
826
- * (async () => {
827
- * try {
828
- * const burnTotal = await bchjs.SLP.Utils.burnTotal(
829
- * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
830
- * )
831
- * console.log(burnTotal)
832
- * } catch (error) {
833
- * console.error(error)
834
- * }
835
- * })()
836
- *
837
- * // returns
838
- * {
839
- * transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de',
840
- * inputTotal: 100000100,
841
- * outputTotal: 100000000,
842
- * burnTotal: 100
843
- * }
844
- */
845
- async burnTotal (transactionId) {
846
- try {
847
- const path = `${this.restURL}slp/burnTotal/${transactionId}`
848
-
849
- const response = await _this.axios.get(path, this.axiosOptions)
850
-
851
- return response.data
852
- } catch (error) {
853
- if (error.response && error.response.data) throw error.response.data
854
- throw error
855
- }
856
- }
857
-
858
- /**
859
- * @api SLP.Utils.txDetails() txDetails()
860
- * @apiName txDetails
861
- * @apiGroup SLP Utils
862
- * @apiDescription Transaction details on a token transfer.
863
- * There is no bulk method for this endpoint. Can only get one tx at a time.
864
- *
865
- * @apiExample Example usage:
866
- *
867
- * (async () => {
868
- * try {
869
- * const details = await bchjs.SLP.Utils.txDetails(
870
- * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
871
- * )
872
- * console.log(details)
873
- * } catch (error) {
874
- * console.error(error)
875
- * }
876
- * })()
877
- *
878
- */
879
- async txDetails (txid) {
880
- try {
881
- if (
882
- !txid ||
883
- txid === '' ||
884
- typeof txid !== 'string' ||
885
- txid.length !== 64
886
- ) {
887
- throw new Error('txid string must be included.')
888
- }
889
-
890
- // console.log(`this.restURL: ${this.restURL}`)
891
- const path = `${this.restURL}slp/txDetails/${txid}`
892
-
893
- const response = await _this.axios.get(path, this.axiosOptions)
894
- return response.data
895
- } catch (error) {
896
- if (error.response && error.response.data) throw error.response.data
897
- throw error
898
- }
899
- }
900
-
901
43
  /**
902
44
  * @api SLP.Utils.decodeOpReturn() decodeOpReturn()
903
45
  * @apiName decodeOpReturn
@@ -1040,910 +182,6 @@ class Utils {
1040
182
  throw error
1041
183
  }
1042
184
  }
1043
-
1044
- /**
1045
- * @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails()
1046
- * @apiName tokenUtxoDetails
1047
- * @apiGroup SLP Utils
1048
- * @apiDescription Hydrate a UTXO with SLP token metadata.
1049
- *
1050
- * Expects an array of UTXO objects as input. Returns an array of equal size.
1051
- * Returns UTXO data hydrated with token information.
1052
- *
1053
- * - If the UTXO does not belong to a SLP transaction, it will return an
1054
- * `isValid` property set to `false`.
1055
- *
1056
- * - If the UTXO is part of an SLP transaction, it will return the UTXO object
1057
- * with additional SLP information attached. An `isValid` property will be
1058
- * included.
1059
- * - If the `isValid` property is `true`, the UTXO is a valid SLP UTXO.
1060
- * - If the `isValid` property is `null`, then SLPDB has not yet processed
1061
- * that txid and validity has not been confirmed, or a 429 rate-limit error
1062
- * was enountered during the processing of the request.
1063
- *
1064
- * An optional second input object, `usrObj`, allows the user to inject an
1065
- * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
1066
- * a number, the call will delay by that number of milliseconds between
1067
- * processing UTXOs.
1068
- *
1069
- * This is an API-heavy call. If you get a lot of `null` values, then slow down
1070
- * the calls by using the usrObj.utxoDelay property, or request info on fewer
1071
- * UTXOs at a
1072
- * time. `null` indicates that the UTXO can *not* be safely spent, because
1073
- * a judgement as to weather it is a token UTXO has not been made. Spending it
1074
- * could burn tokens. It's safest to ignore UTXOs with a value of `null`.
1075
- *
1076
- *
1077
- * @apiExample Example usage:
1078
- *
1079
- * (async () => {
1080
- * try {
1081
- * const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
1082
- *
1083
- * // Delay 100mS between processing UTXOs, to prevent rate-limit errors.
1084
- * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos, { utxoDelay: 100 })
1085
- *
1086
- * console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
1087
- * } catch (error) {
1088
- * console.error(error)
1089
- * }
1090
- * })()
1091
- *
1092
- * // returns
1093
- * {
1094
- * "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
1095
- * "vout": 1,
1096
- * "amount": 0.00000546,
1097
- * "satoshis": 546,
1098
- * "height": 596089,
1099
- * "confirmations": 748,
1100
- * "utxoType": "token",
1101
- * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
1102
- * "tokenTicker": "TOK-CH",
1103
- * "tokenName": "TokyoCash",
1104
- * "tokenDocumentUrl": "",
1105
- * "tokenDocumentHash": "",
1106
- * "decimals": 8,
1107
- * "tokenQty": 2,
1108
- * "isValid": true,
1109
- * "tokenType": 1
1110
- * }
1111
- */
1112
- async tokenUtxoDetails (utxos, usrObj = null) {
1113
- try {
1114
- // Throw error if input is not an array.
1115
- if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
1116
-
1117
- // console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`)
1118
-
1119
- // Loop through each element in the array and validate the input before
1120
- // further processing.
1121
- for (let i = 0; i < utxos.length; i++) {
1122
- const utxo = utxos[i]
1123
-
1124
- // Ensure the UTXO has a txid or tx_hash property.
1125
- if (!utxo.txid) {
1126
- // If Electrumx, convert the tx_hash property to txid.
1127
- if (utxo.tx_hash) {
1128
- utxo.txid = utxo.tx_hash
1129
- } else {
1130
- // If there is neither a txid or tx_hash property, throw an error.
1131
- throw new Error(
1132
- `utxo ${i} does not have a txid or tx_hash property.`
1133
- )
1134
- }
1135
- }
1136
-
1137
- // Ensure the UTXO has a vout or tx_pos property.
1138
- if (!Number.isInteger(utxo.vout)) {
1139
- if (Number.isInteger(utxo.tx_pos)) {
1140
- utxo.vout = utxo.tx_pos
1141
- } else {
1142
- throw new Error(
1143
- `utxo ${i} does not have a vout or tx_pos property.`
1144
- )
1145
- }
1146
- }
1147
- }
1148
-
1149
- // Hydrate each UTXO with data from SLP OP_REUTRNs.
1150
- const outAry = await this._hydrateUtxo(utxos, usrObj)
1151
- // console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
1152
-
1153
- // *After* each UTXO has been hydrated with SLP data,
1154
- // validate the TXID with SLPDB.
1155
- for (let i = 0; i < outAry.length; i++) {
1156
- const utxo = outAry[i]
1157
-
1158
- // *After* the UTXO has been hydrated with SLP data,
1159
- // validate the TXID with SLPDB.
1160
- if (utxo.tokenType) {
1161
- // Only execute this code-path if the current UTXO has a 'tokenType'
1162
- // property. i.e. it has been successfully hydrated with SLP
1163
- // information.
1164
-
1165
- // Validate using a 'waterfall' of validators.
1166
- utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
1167
- // console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
1168
- }
1169
- }
1170
-
1171
- return outAry
1172
- } catch (error) {
1173
- // console.log('Error in tokenUtxoDetails()')
1174
- if (error.response && error.response.data) throw error.response.data
1175
- throw error
1176
- }
1177
- }
1178
-
1179
- /**
1180
- * @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
1181
- * @apiName tokenUtxoDetailsWL
1182
- * @apiGroup SLP Utils
1183
- * @apiDescription
1184
- *
1185
- * Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
1186
- * validate UTXOs. This will result in a lot of `isValid: null` values,
1187
- * but much more performant handling of SLP tokens. Some wallet apps prefer
1188
- * the scaling performance over the breadth of supported tokens.
1189
- *
1190
- * An optional second input object, `usrObj`, allows the user to inject an
1191
- * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
1192
- * a number, the call will delay by that number of milliseconds between
1193
- * processing UTXOs.
1194
- *
1195
- * This is an API-heavy call. If you get a lot of `null` values, then slow down
1196
- * the calls by using the usrObj.utxoDelay property, or request info on fewer
1197
- * UTXOs at a
1198
- * time. `null` indicates that the UTXO can *not* be safely spent, because
1199
- * a judgement as to weather it is a token UTXO has not been made. Spending it
1200
- * could burn tokens. It's safest to ignore UTXOs with a value of `null`.
1201
- *
1202
- */
1203
- async tokenUtxoDetailsWL (utxos, usrObj = null) {
1204
- try {
1205
- // Throw error if input is not an array.
1206
- if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
1207
-
1208
- // Loop through each element in the array and validate the input before
1209
- // further processing.
1210
- for (let i = 0; i < utxos.length; i++) {
1211
- const utxo = utxos[i]
1212
-
1213
- // Ensure the UTXO has a txid or tx_hash property.
1214
- if (!utxo.txid) {
1215
- // If Electrumx, convert the tx_hash property to txid.
1216
- if (utxo.tx_hash) {
1217
- utxo.txid = utxo.tx_hash
1218
- } else {
1219
- // If there is neither a txid or tx_hash property, throw an error.
1220
- throw new Error(
1221
- `utxo ${i} does not have a txid or tx_hash property.`
1222
- )
1223
- }
1224
- }
1225
-
1226
- // Ensure the UTXO has a vout or tx_pos property.
1227
- if (!Number.isInteger(utxo.vout)) {
1228
- if (Number.isInteger(utxo.tx_pos)) {
1229
- utxo.vout = utxo.tx_pos
1230
- } else {
1231
- throw new Error(
1232
- `utxo ${i} does not have a vout or tx_pos property.`
1233
- )
1234
- }
1235
- }
1236
- }
1237
-
1238
- // Hydrate each UTXO with data from SLP OP_REUTRNs.
1239
- const outAry = await this._hydrateUtxo(utxos, usrObj)
1240
- // console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
1241
-
1242
- // *After* each UTXO has been hydrated with SLP data,
1243
- // validate the TXID with SLPDB.
1244
- for (let i = 0; i < outAry.length; i++) {
1245
- const utxo = outAry[i]
1246
-
1247
- // *After* the UTXO has been hydrated with SLP data,
1248
- // validate the TXID with SLPDB.
1249
- if (utxo.tokenType) {
1250
- // Only execute this block if the current UTXO has a 'tokenType'
1251
- // property. i.e. it has been successfully hydrated with SLP
1252
- // information.
1253
-
1254
- // Validate against the whitelist SLPDB.
1255
- const whitelistResult = await this.validateTxid3(utxo.txid, usrObj)
1256
- // console.log(
1257
- // `whitelist-SLPDB for ${txid}: ${JSON.stringify(
1258
- // whitelistResult,
1259
- // null,
1260
- // 2
1261
- // )}`
1262
- // )
1263
-
1264
- let isValid = null
1265
-
1266
- // Safely retrieve the returned value.
1267
- if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
1268
-
1269
- utxo.isValid = isValid
1270
- }
1271
- }
1272
-
1273
- return outAry
1274
- } catch (error) {
1275
- if (error.response && error.response.data) throw error.response.data
1276
- throw error
1277
- }
1278
- }
1279
-
1280
- // This is a private function that is called by tokenUtxoDetails().
1281
- // It loops through an array of UTXOs and tries to hydrate them with SLP
1282
- // token information from the OP_RETURN data.
1283
- //
1284
- // This function makes several calls to decodeOpReturn() to retrieve SLP
1285
- // token data. If that call throws an error due to hitting rate limits, this
1286
- // function will not throw an error. Instead, it will mark the `isValid`
1287
- // property as `null`
1288
- //
1289
- // Exception to the above: It *will* throw an error if decodeOpReturn() throws
1290
- // an error while trying to get the Genesis transaction for a Send or Mint
1291
- // transaction. However, that is a rare occurence since the cache of
1292
- // decodeOpReturn() will minimize API calls for this case. This behavior
1293
- // could be changed, but right now it's a corner case of a corner case.
1294
- //
1295
- // If the usrObj has a utxoDelay property, then it will delay the loop for
1296
- // each UTXO by that many milliseconds.
1297
- async _hydrateUtxo (utxos, usrObj = null) {
1298
- try {
1299
- const decodeOpReturnCache = {}
1300
-
1301
- // console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
1302
-
1303
- // Output Array
1304
- const outAry = []
1305
-
1306
- // Loop through each utxo
1307
- for (let i = 0; i < utxos.length; i++) {
1308
- const utxo = utxos[i]
1309
-
1310
- // If the user passes in a delay, then wait.
1311
- if (usrObj && usrObj.utxoDelay && !isNaN(Number(usrObj.utxoDelay))) {
1312
- const delayMs = Number(usrObj.utxoDelay)
1313
- await this.util.sleep(delayMs)
1314
- }
1315
-
1316
- // Get raw transaction data from the full node and attempt to decode
1317
- // the OP_RETURN data.
1318
- // If there is no OP_RETURN, mark the UTXO as false.
1319
- let slpData = false
1320
- try {
1321
- slpData = await this.decodeOpReturn(
1322
- utxo.txid,
1323
- decodeOpReturnCache,
1324
- usrObj // pass user data when making an internal call.
1325
- )
1326
- // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
1327
- } catch (err) {
1328
- // console.log(
1329
- // `error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `,
1330
- // err
1331
- // )
1332
-
1333
- // An error will be thrown if the txid is not SLP.
1334
- // If error is for some other reason, like a 429 error, mark utxo as 'null'
1335
- // to display the unknown state.
1336
- if (
1337
- !err.message ||
1338
- (err.message.indexOf('scriptpubkey not op_return') === -1 &&
1339
- err.message.indexOf('lokad id') === -1 &&
1340
- err.message.indexOf('trailing data') === -1)
1341
- ) {
1342
- // console.log(
1343
- // "unknown error from decodeOpReturn(). Marking as 'null'",
1344
- // err
1345
- // )
1346
-
1347
- utxo.isValid = null
1348
- outAry.push(utxo)
1349
-
1350
- // If error is thrown because there is no OP_RETURN, then it's not
1351
- // an SLP UTXO.
1352
- // Mark as false and continue the loop.
1353
- } else {
1354
- // console.log('marking as invalid')
1355
- utxo.isValid = false
1356
- outAry.push(utxo)
1357
- }
1358
-
1359
- // Halt the execution of the loop and increase to the next index.
1360
- continue
1361
- }
1362
- // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
1363
-
1364
- const txType = slpData.txType.toLowerCase()
1365
-
1366
- // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
1367
-
1368
- // If there is an OP_RETURN, attempt to decode it.
1369
- // Handle Genesis SLP transactions.
1370
- if (txType === 'genesis') {
1371
- if (
1372
- utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
1373
- utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
1374
- ) {
1375
- // Can safely be marked as false.
1376
- utxo.isValid = false
1377
- outAry[i] = utxo
1378
- } else {
1379
- // If this is a valid SLP UTXO, then return the decoded OP_RETURN data.
1380
- // Minting Baton
1381
- if (utxo.vout === slpData.mintBatonVout) {
1382
- utxo.utxoType = 'minting-baton'
1383
- } else {
1384
- // Tokens
1385
-
1386
- utxo.utxoType = 'token'
1387
- utxo.tokenQty = new BigNumber(slpData.qty)
1388
- .div(Math.pow(10, slpData.decimals))
1389
- .toString()
1390
- }
1391
-
1392
- utxo.tokenId = utxo.txid
1393
- utxo.tokenTicker = slpData.ticker
1394
- utxo.tokenName = slpData.name
1395
- utxo.tokenDocumentUrl = slpData.documentUri
1396
- utxo.tokenDocumentHash = slpData.documentHash
1397
- utxo.decimals = slpData.decimals
1398
- utxo.tokenType = slpData.tokenType
1399
-
1400
- // Initial value is null until UTXO can be validated and confirmed
1401
- // to be valid (true) or not (false).
1402
- utxo.isValid = null
1403
-
1404
- outAry[i] = utxo
1405
- }
1406
- }
1407
-
1408
- // Handle Mint SLP transactions.
1409
- if (txType === 'mint') {
1410
- if (
1411
- utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
1412
- utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
1413
- ) {
1414
- // Can safely be marked as false.
1415
- utxo.isValid = false
1416
-
1417
- outAry[i] = utxo
1418
- } else {
1419
- // If UTXO passes validation, then return formatted token data.
1420
-
1421
- const genesisData = await this.decodeOpReturn(
1422
- slpData.tokenId,
1423
- decodeOpReturnCache,
1424
- usrObj // pass user data when making an internal call.
1425
- )
1426
- // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
1427
-
1428
- // Minting Baton
1429
- if (utxo.vout === slpData.mintBatonVout) {
1430
- utxo.utxoType = 'minting-baton'
1431
- } else {
1432
- // Tokens
1433
-
1434
- utxo.utxoType = 'token'
1435
- utxo.tokenQty = new BigNumber(slpData.qty)
1436
- .div(Math.pow(10, genesisData.decimals))
1437
- .toString()
1438
- }
1439
-
1440
- // Hydrate the UTXO object with information about the SLP token.
1441
- utxo.transactionType = 'mint'
1442
- utxo.tokenId = slpData.tokenId
1443
- utxo.tokenType = slpData.tokenType
1444
-
1445
- utxo.tokenTicker = genesisData.ticker
1446
- utxo.tokenName = genesisData.name
1447
- utxo.tokenDocumentUrl = genesisData.documentUri
1448
- utxo.tokenDocumentHash = genesisData.documentHash
1449
- utxo.decimals = genesisData.decimals
1450
-
1451
- utxo.mintBatonVout = slpData.mintBatonVout
1452
-
1453
- // Initial value is null until UTXO can be validated and confirmed
1454
- // to be valid (true) or not (false).
1455
- utxo.isValid = null
1456
-
1457
- outAry[i] = utxo
1458
- }
1459
- }
1460
-
1461
- // Handle Send SLP transactions.
1462
- if (txType === 'send') {
1463
- // Filter out any vouts that match.
1464
- // const voutMatch = slpData.spendData.filter(x => utxo.vout === x.vout)
1465
- // console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`)
1466
-
1467
- // Figure out what token quantity is represented by this utxo.
1468
- const tokenQty = slpData.amounts[utxo.vout - 1]
1469
- // console.log('tokenQty: ', tokenQty)
1470
-
1471
- if (!tokenQty) {
1472
- utxo.isValid = false
1473
-
1474
- outAry[i] = utxo
1475
- } else {
1476
- // If UTXO passes validation, then return formatted token data.
1477
-
1478
- const genesisData = await this.decodeOpReturn(
1479
- slpData.tokenId,
1480
- decodeOpReturnCache,
1481
- usrObj // pass user data when making an internal call.
1482
- )
1483
- // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
1484
-
1485
- // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
1486
-
1487
- // Hydrate the UTXO object with information about the SLP token.
1488
- utxo.utxoType = 'token'
1489
- utxo.transactionType = 'send'
1490
- utxo.tokenId = slpData.tokenId
1491
- utxo.tokenTicker = genesisData.ticker
1492
- utxo.tokenName = genesisData.name
1493
- utxo.tokenDocumentUrl = genesisData.documentUri
1494
- utxo.tokenDocumentHash = genesisData.documentHash
1495
- utxo.decimals = genesisData.decimals
1496
- utxo.tokenType = slpData.tokenType
1497
-
1498
- // Initial value is null until UTXO can be validated and confirmed
1499
- // to be valid (true) or not (false).
1500
- utxo.isValid = null
1501
-
1502
- // Calculate the real token quantity.
1503
-
1504
- const tokenQtyBig = new BigNumber(tokenQty).div(
1505
- Math.pow(10, genesisData.decimals)
1506
- )
1507
- // console.log(`tokenQtyBig`, tokenQtyBig.toString())
1508
- utxo.tokenQty = tokenQtyBig.toString()
1509
-
1510
- // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
1511
-
1512
- outAry[i] = utxo
1513
- }
1514
- }
1515
- }
1516
-
1517
- return outAry
1518
- } catch (error) {
1519
- // console.log('_hydrateUtxo error: ', error)
1520
- throw error
1521
- }
1522
- }
1523
-
1524
- /**
1525
- * @api SLP.Utils.waterfallValidateTxid() waterfallValidateTxid()
1526
- * @apiName waterfallValidateTxid
1527
- * @apiGroup SLP Utils
1528
- * @apiDescription Use multiple validators to validate an SLP TXID.
1529
- *
1530
- * This function aggregates all the available SLP token validation sources.
1531
- * It starts with the fastest, most-efficient source first, and continues
1532
- * to other validation sources until the txid is validated (true or false).
1533
- * If the txid goes through all sources and can't be validated, it will
1534
- * return null.
1535
- *
1536
- * Validation sources from most efficient to least efficient:
1537
- * - SLPDB with whitelist filter
1538
- * - SLPDB general purpose
1539
- * - slp-api
1540
- *
1541
- * Currently only supports a single txid at a time.
1542
- *
1543
- * @apiExample Example usage:
1544
- *
1545
- * // validate single SLP txid
1546
- * (async () => {
1547
- * try {
1548
- * let validated = await bchjs.SLP.Utils.waterfallValidateTxid(
1549
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
1550
- * );
1551
- * console.log(validated);
1552
- * } catch (error) {
1553
- * console.error(error);
1554
- * }
1555
- * })();
1556
- *
1557
- * // returns
1558
- * true
1559
- */
1560
- async waterfallValidateTxid (txid, usrObj = null) {
1561
- try {
1562
- // console.log('txid: ', txid)
1563
-
1564
- const cachedTxValidation = {}
1565
-
1566
- // If the value has been cached, use the cached version first.
1567
- let isValid = cachedTxValidation[txid]
1568
- if (!isValid && isValid !== false) {
1569
- isValid = null
1570
- } else {
1571
- return isValid
1572
- }
1573
-
1574
- // There are two possible responses from SLPDB. If SLPDB is functioning
1575
- // correctly, then validateTxid() will return this:
1576
- // isValid: [
1577
- // {
1578
- // "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
1579
- // "valid": true
1580
- // }
1581
- // ]
1582
- //
1583
- // If SLPDB has fallen behind real-time processing, it will return this:
1584
- // isValid: [
1585
- // null
1586
- // ]
1587
- //
1588
- // Note: validateTxid3() has the same output as validateTxid().
1589
- // validateTxid2() uses slp-validate, which has a different output format.
1590
-
1591
- // Validate against the whitelist SLPDB first.
1592
- const whitelistResult = await this.validateTxid3(txid, usrObj)
1593
- // console.log(
1594
- // `whitelist-SLPDB for ${txid}: ${JSON.stringify(
1595
- // whitelistResult,
1596
- // null,
1597
- // 2
1598
- // )}`
1599
- // )
1600
-
1601
- // Safely retrieve the returned value.
1602
- if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
1603
-
1604
- // Exit if isValid is not null.
1605
- if (isValid !== null) {
1606
- // Save to the cache.
1607
- cachedTxValidation[txid] = isValid
1608
-
1609
- return isValid
1610
- }
1611
-
1612
- // Try the general SLPDB, if the whitelist returned null.
1613
- const generalResult = await this.validateTxid(txid, usrObj)
1614
- // console.log(
1615
- // `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
1616
- // )
1617
-
1618
- // Safely retrieve the returned value.
1619
- if (generalResult[0] !== null) isValid = generalResult[0].valid
1620
-
1621
- // Exit if isValid is not null.
1622
- if (isValid !== null) {
1623
- // Save to the cache.
1624
- cachedTxValidation[txid] = isValid
1625
-
1626
- return isValid
1627
- }
1628
-
1629
- // If still null, as a last resort, check it against slp-validate
1630
- let slpValidateResult = null
1631
- try {
1632
- slpValidateResult = await this.validateTxid2(txid)
1633
- } catch (err) {
1634
- /* exit quietly */
1635
- }
1636
- // console.log(
1637
- // `slpValidateResult: ${JSON.stringify(slpValidateResult, null, 2)}`
1638
- // )
1639
-
1640
- // Exit if isValid is not null.
1641
- if (slpValidateResult !== null) {
1642
- isValid = slpValidateResult.isValid
1643
-
1644
- // Save to the cache.
1645
- cachedTxValidation[txid] = isValid
1646
-
1647
- return isValid
1648
- }
1649
-
1650
- // If isValid is still null, return that value, signaling that the txid
1651
- // could not be validated.
1652
- return isValid
1653
- } catch (error) {
1654
- // This case handles rate limit errors.
1655
- if (error.response && error.response.data && error.response.data.error) {
1656
- throw new Error(error.response.data.error)
1657
- }
1658
-
1659
- // console.log('Error in waterfallValidateTxid()')
1660
- if (error.response && error.response.data) throw error.response.data
1661
- throw error
1662
- }
1663
- }
1664
-
1665
- /**
1666
- * @api SLP.Utils.hydrateUtxos() hydrateUtxos()
1667
- * @apiName hydrateUtxos
1668
- * @apiGroup SLP Utils
1669
- * @apiDescription Hydrate a UTXO with SLP token metadata.
1670
- *
1671
- * The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting,
1672
- * which greatly increases the speed, since fewer API calls need to be made.
1673
- * However, internal API calls are still counted against your rate limits.
1674
- *
1675
- * This function expects an array of UTXO objects as input. It returns an
1676
- * array of equal size. The UTXO data hydrated with token information.
1677
- * - If the UTXO does not belong to a SLP transaction, it will return an
1678
- * `isValid` property set to `false`.
1679
- * - If the UTXO is part of an SLP transaction, it will return the UTXO object
1680
- * with additional SLP information attached. An `isValid` property will be
1681
- * included.
1682
- * - If `isValid` is `true`, the UTXO is a valid SLP UTXO.
1683
- * - If `isValid` is `null`, then SLPDB has not yet processed that txid
1684
- * and validity has not been confirmed,
1685
- * or a 429 rate-limit error was enountered during the processing of the
1686
- * request.
1687
- *
1688
- * An optional second input object, `usrObj`, allows the user to inject an
1689
- * artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
1690
- * a number, the call will delay by that number of milliseconds between
1691
- * processing UTXOs.
1692
- *
1693
- * This is an API-heavy call. If you get a lot of `null` values, then slow down
1694
- * the calls by using the usrObj.utxoDelay property, or request info on fewer
1695
- * UTXOs at a
1696
- * time. `null` indicates that the UTXO can *not* be safely spent, because
1697
- * a judgement as to weather it is a token UTXO has not been made. Spending it
1698
- * could burn tokens. It's safest to ignore UTXOs with a value of `null`.
1699
- *
1700
- * @apiExample Example usage:
1701
- *
1702
- * (async () => {
1703
- * try {
1704
- * const utxos = await bchjs.Electrumx.utxo([
1705
- * "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg",
1706
- * "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89",
1707
- * "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
1708
- * ])
1709
- *
1710
- * // Wait 100mS between processing UTXOs, to prevent rate limit errors.
1711
- * const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos, { utxoDelay: 100 })
1712
- *
1713
- * console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
1714
- * } catch (error) {
1715
- * console.error(error)
1716
- * }
1717
- * })()
1718
- *
1719
- * // returns
1720
- * {
1721
- * "slpUtxos": [
1722
- * {
1723
- * "utxos": [
1724
- * {
1725
- * "height": 654522,
1726
- * "tx_hash": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
1727
- * "tx_pos": 0,
1728
- * "value": 6000,
1729
- * "satoshis": 6000,
1730
- * "txid": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
1731
- * "vout": 0,
1732
- * "isValid": false
1733
- * }
1734
- * ],
1735
- * "address": "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg"
1736
- * },
1737
- * {
1738
- * "utxos": [
1739
- * {
1740
- * "height": 654522,
1741
- * "tx_hash": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
1742
- * "tx_pos": 1,
1743
- * "value": 546,
1744
- * "satoshis": 546,
1745
- * "txid": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
1746
- * "vout": 1,
1747
- * "utxoType": "token",
1748
- * "transactionType": "send",
1749
- * "tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
1750
- * "tokenTicker": "TROUT",
1751
- * "tokenName": "Trout's test token",
1752
- * "tokenDocumentUrl": "troutsblog.com",
1753
- * "tokenDocumentHash": "",
1754
- * "decimals": 2,
1755
- * "tokenType": 1,
1756
- * "tokenQty": 10,
1757
- * "isValid": true
1758
- * }
1759
- * ],
1760
- * "address": "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89"
1761
- * },
1762
- * {
1763
- * "utxos": [
1764
- * {
1765
- * "height": 654522,
1766
- * "tx_hash": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
1767
- * "tx_pos": 0,
1768
- * "value": 6999,
1769
- * "satoshis": 6999,
1770
- * "txid": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
1771
- * "vout": 0,
1772
- * "isValid": false
1773
- * },
1774
- * {
1775
- * "height": 654522,
1776
- * "tx_hash": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
1777
- * "tx_pos": 1,
1778
- * "value": 546,
1779
- * "satoshis": 546,
1780
- * "txid": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
1781
- * "vout": 1,
1782
- * "utxoType": "token",
1783
- * "transactionType": "send",
1784
- * "tokenId": "6201f3efe486c577433622817b99645e1d473cd3882378f9a0efc128ab839a82",
1785
- * "tokenTicker": "VALENTINE",
1786
- * "tokenName": "Valentine day token",
1787
- * "tokenDocumentUrl": "fullstack.cash",
1788
- * "tokenDocumentHash": "",
1789
- * "decimals": 2,
1790
- * "tokenType": 1,
1791
- * "tokenQty": 5,
1792
- * "isValid": true
1793
- * }
1794
- * ],
1795
- * "address": "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
1796
- * }
1797
- * ]
1798
- * }
1799
- *
1800
- * (async () => {
1801
- * try {
1802
- * const utxos = [
1803
- * {
1804
- * utxos: [
1805
- * {
1806
- * txid: "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
1807
- * vout: 3,
1808
- * value: "6816",
1809
- * height: 606848,
1810
- * confirmations: 13,
1811
- * satoshis: 6816
1812
- * }
1813
- * ]
1814
- * }
1815
- * ]
1816
- *
1817
- * const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos)
1818
- *
1819
- * console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
1820
- * } catch (error) {
1821
- * console.error(error)
1822
- * }
1823
- * })()
1824
- *
1825
- * // returns
1826
- * {
1827
- * "slpUtxos": [
1828
- * {
1829
- * "utxos": [
1830
- * {
1831
- * "txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
1832
- * "vout": 3,
1833
- * "value": "6816",
1834
- * "height": 606848,
1835
- * "confirmations": 13,
1836
- * "satoshis": 6816,
1837
- * "isValid": false
1838
- * }
1839
- * ]
1840
- * }
1841
- * ]
1842
- */
1843
- // Same as tokenUtxoDetails(), but reduces API calls by having bch-api server
1844
- // do the heavy lifting.
1845
- async hydrateUtxos (utxos, usrObj) {
1846
- try {
1847
- // Throw error if input is not an array.
1848
- if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
1849
-
1850
- const response = await _this.axios.post(
1851
- `${this.restURL}slp/hydrateUtxos`,
1852
- {
1853
- utxos: utxos,
1854
- usrObj
1855
- },
1856
- this.axiosOptions
1857
- )
1858
-
1859
- return response.data
1860
- } catch (error) {
1861
- console.log('Error in hydrateUtxos(): ', error)
1862
-
1863
- if (error.response && error.response.data) {
1864
- throw new Error(JSON.stringify(error.response.data, null, 2))
1865
- }
1866
- throw error
1867
- }
1868
- }
1869
-
1870
- /**
1871
- * @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL()
1872
- * @apiName hydrateUtxosWL
1873
- * @apiGroup SLP Utils
1874
- * @apiDescription
1875
- * This call is exactly the same as `hydrateUtxos()`. This version hydrate a
1876
- * UTXO with SLP token metadata, but only uses the whitelist SLPDB for
1877
- * validation.
1878
- *
1879
- * Whitelist SLPDBs will return `isValid: null` for any token not in the
1880
- * 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable
1881
- * to operate.
1882
- *
1883
- */
1884
- // Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server
1885
- // do the heavy lifting.
1886
- async hydrateUtxosWL (utxos) {
1887
- try {
1888
- // Throw error if input is not an array.
1889
- if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
1890
-
1891
- const response = await _this.axios.post(
1892
- `${this.restURL}slp/hydrateUtxosWL`,
1893
- {
1894
- utxos: utxos
1895
- },
1896
- this.axiosOptions
1897
- )
1898
-
1899
- return response.data
1900
- } catch (error) {
1901
- if (error.response && error.response.data) throw error.response.data
1902
- else throw error
1903
- }
1904
- }
1905
-
1906
- /**
1907
- * @api SLP.Utils.getStatus() getStatus()
1908
- * @apiName getStatus
1909
- * @apiGroup SLP Utils
1910
- * @apiDescription Get the status and health of the SLPDB connected to bch-api.
1911
- *
1912
- * @apiExample Example usage:
1913
- *
1914
- * // Get the current blockheight of the SLPDB indexer.
1915
- * (async () => {
1916
- * try {
1917
- * let validated = await bchjs.SLP.Utils.validateTxid(
1918
- * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
1919
- * );
1920
- * console.log(validated);
1921
- * } catch (error) {
1922
- * console.error(error);
1923
- * }
1924
- * })();
1925
- *
1926
- * // returns
1927
- * [ { txid:
1928
- * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
1929
- * valid: true } ]
1930
- *
1931
- */
1932
- async getStatus (txid) {
1933
- const path = `${this.restURL}slp/status`
1934
-
1935
- try {
1936
- const response = await _this.axios.get(path, this.axiosOptions)
1937
- // console.log(
1938
- // `getStatus response.data: ${JSON.stringify(response.data, null, 2)}`
1939
- // )
1940
-
1941
- return response.data
1942
- } catch (error) {
1943
- if (error.response && error.response.data) throw error.response.data
1944
- throw error
1945
- }
1946
- }
1947
185
  }
1948
186
 
1949
187
  module.exports = Utils