@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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  // Global npm libraries
6
- const BigNumber = require('bignumber.js')
6
+ // const BigNumber = require('bignumber.js')
7
7
 
8
8
  // Local libraries
9
9
  const RawTransaction = require('./raw-transactions')
@@ -25,503 +25,6 @@ class Transaction {
25
25
  return await this.psfSlpIndexer.tx(txid)
26
26
  }
27
27
 
28
- /**
29
- * @api Transaction.get() get()
30
- * @apiName get
31
- * @apiGroup Transaction
32
- * @apiDescription
33
- * Returns an object of transaction data, including addresses for input UTXOs.
34
- * If it is a SLP token transaction, the token information for inputs and
35
- * outputs will also be included.
36
- *
37
- * This is an API heavy call. This function will only work with a single txid.
38
- * It does not yet support an array of TXIDs.
39
- *
40
- * @apiExample Example usage:
41
- * (async () => {
42
- * try {
43
- * let txData = await bchjs.Transaction.get("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
44
- * console.log(txData);
45
- * } catch(error) {
46
- * console.error(error)
47
- * }
48
- * })()
49
- */
50
-
51
- // CT 10/31/21: TODO: this function should be refactored to use get2(), but
52
- // add waterfall validation of the TX and its inputs.
53
-
54
- async getOld (txid) {
55
- try {
56
- if (typeof txid !== 'string') {
57
- throw new Error(
58
- 'Input to Transaction.get() must be a string containing a TXID.'
59
- )
60
- }
61
-
62
- const txDetails = await this.rawTransaction.getTxData(txid)
63
- // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
64
-
65
- // Setup default SLP properties.
66
- txDetails.isValidSLPTx = false
67
-
68
- // First get the token information for the output. If that fails, then
69
- // this is not an SLP transaction, and this method can return false.
70
- let outTokenData
71
- try {
72
- outTokenData = await this.slpUtils.decodeOpReturn(txid)
73
- // console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`)
74
-
75
- // Get Genesis data for this token.
76
- const genesisData = await this.slpUtils.decodeOpReturn(
77
- outTokenData.tokenId
78
- // decodeOpReturnCache
79
- // usrObj // pass user data when making an internal call.
80
- )
81
- // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
82
-
83
- // Add token information to the tx details object.
84
- txDetails.tokenTxType = outTokenData.txType
85
- txDetails.tokenId = outTokenData.tokenId
86
- txDetails.tokenTicker = genesisData.ticker
87
- txDetails.tokenName = genesisData.name
88
- txDetails.tokenDecimals = genesisData.decimals
89
- txDetails.tokenUri = genesisData.documentUri
90
- txDetails.tokenDocHash = genesisData.documentHash
91
-
92
- // Add the token quantity to each output.
93
- for (let i = 0; i < outTokenData.amounts.length; i++) {
94
- const rawQty = outTokenData.amounts[i]
95
- // const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals)
96
-
97
- // Calculate the real quantity using a BigNumber, then convert it to a
98
- // floating point number.
99
- let realQty = new BigNumber(rawQty).dividedBy(
100
- 10 ** parseInt(txDetails.tokenDecimals)
101
- )
102
- realQty = realQty.toString()
103
- // realQty = parseFloat(realQty)
104
-
105
- txDetails.vout[i + 1].tokenQtyStr = realQty
106
- txDetails.vout[i + 1].tokenQty = parseFloat(realQty)
107
- }
108
-
109
- // Add tokenQty = null to any outputs that don't have a value.
110
- for (let i = 0; i < txDetails.vout.length; i++) {
111
- const thisVout = txDetails.vout[i]
112
-
113
- if (!thisVout.tokenQty && thisVout.tokenQty !== 0) {
114
- thisVout.tokenQty = null
115
- }
116
- }
117
-
118
- // Loop through each input and retrieve the token data.
119
- for (let i = 0; i < txDetails.vin.length; i++) {
120
- const thisVin = txDetails.vin[i]
121
- // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
122
-
123
- try {
124
- // If decodeOpReturn() throws an error, then this input is not
125
- // from an SLP transaction and can be ignored.
126
- const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid)
127
- // console.log(
128
- // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}`
129
- // )
130
-
131
- let tokenQty = 0
132
-
133
- // Validate the input. Mark qty as null if not valid.
134
- const vinIsValid = await this.slpUtils.waterfallValidateTxid(
135
- thisVin.txid
136
- )
137
-
138
- if (!vinIsValid) {
139
- // If the input is not a valid, then set qty as null.
140
- tokenQty = null
141
- } else if (inTokenData.txType === 'SEND') {
142
- // Get the appropriate vout token amount. This may throw an error,
143
- // which means this Vin is not actually a token UTXO, it was just
144
- // associated with a previous token TX.
145
- tokenQty = inTokenData.amounts[thisVin.vout - 1]
146
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
147
-
148
- //
149
- } else if (inTokenData.txType === 'GENESIS') {
150
- // Only vout[1] of a Genesis transaction represents the tokens.
151
- // Any other outputs in that transaction are normal BCH UTXOs.
152
- if (thisVin.vout === 1) {
153
- tokenQty = inTokenData.qty
154
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
155
- }
156
- } else if (inTokenData.txType === 'MINT') {
157
- // vout=1 (second output) recieves the newly minted tokens.
158
- if (thisVin.vout === 1) {
159
- tokenQty = inTokenData.qty
160
- } else {
161
- tokenQty = null
162
- }
163
-
164
- //
165
- } else {
166
- console.log(
167
- 'Unexpected code path in Transaction.get(). What is the txType?'
168
- )
169
- console.log(inTokenData)
170
- throw new Error('Unexpected code path')
171
- }
172
-
173
- if (tokenQty) {
174
- // const realQty =
175
- // Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
176
-
177
- // Calculate the real quantity using a BigNumber, then convert it to a
178
- // floating point number.
179
- let realQty = new BigNumber(tokenQty).dividedBy(
180
- 10 ** parseInt(txDetails.tokenDecimals)
181
- )
182
- realQty = realQty.toString()
183
- // realQty = parseFloat(realQty)
184
-
185
- thisVin.tokenQtyStr = realQty
186
- thisVin.tokenQty = parseFloat(realQty)
187
- // txDetails.vin[i].tokenQty = tokenQty
188
-
189
- // Add token ID to input
190
- thisVin.tokenId = inTokenData.tokenId
191
- } else {
192
- thisVin.tokenQty = null
193
- }
194
- } catch (err) {
195
- // If decodeOpReturn() throws an error, then this input is not
196
- // from an SLP transaction and can be ignored.
197
- // thisVin.tokenQty = null
198
- thisVin.tokenQty = null
199
- continue
200
- }
201
- }
202
-
203
- // Finally, validate the SLP TX.
204
- // this.slpUtils.waterfallValidateTxid(txid, usrObj)
205
- txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid)
206
-
207
- // TODO: Convert the block hash to a block height. Add block height
208
- // value to the transaction.
209
- } catch (err) {
210
- // console.log('Error: ', err)
211
-
212
- // This case handles rate limit errors.
213
- if (err.response && err.response.data && err.response.data.error) {
214
- throw new Error(err.response.data.error)
215
- }
216
-
217
- // If decoding the op_return fails, then it's not an SLP transaction,
218
- // and the non-hyrated TX details can be returned.
219
- return txDetails
220
- }
221
-
222
- return txDetails
223
- } catch (err) {
224
- // console.error('Error in transactions.js/get(): ', err)
225
-
226
- // This case handles rate limit errors.
227
- if (err.response && err.response.data && err.response.data.error) {
228
- throw new Error(err.response.data.error)
229
- }
230
-
231
- if (err.error) throw new Error(err.error)
232
- throw err
233
- }
234
- }
235
-
236
- /**
237
- * @api Transaction.get3() get3()
238
- * @apiName get3
239
- * @apiGroup Transaction
240
- * @apiDescription
241
- * Returns an object of transaction data, including addresses for input UTXOs.
242
- * If it is a SLP token transaction, the token information for inputs and
243
- * outputs will also be included.
244
- *
245
- * This is an API heavy call. This function will only work with a single txid.
246
- * It does not yet support an array of TXIDs.
247
- *
248
- * This is the same as get(), except it omits DAG validation of the TXID.
249
- *
250
- * @apiExample Example usage:
251
- * (async () => {
252
- * try {
253
- * let txData = await bchjs.Transaction.get2("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
254
- * console.log(txData);
255
- * } catch(error) {
256
- * console.error(error)
257
- * }
258
- * })()
259
- */
260
- async get3 (txid) {
261
- try {
262
- if (typeof txid !== 'string') {
263
- throw new Error(
264
- 'Input to Transaction.get() must be a string containing a TXID.'
265
- )
266
- }
267
-
268
- // Get TX data
269
- const txDetails = await this.rawTransaction.getTxData(txid)
270
- // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
271
-
272
- // Get the block height the transaction was mined in.
273
- const blockHeader = await this.blockchain.getBlockHeader(
274
- txDetails.blockhash
275
- )
276
- txDetails.blockheight = blockHeader.height
277
- // console.log(`blockHeader: ${JSON.stringify(blockHeader, null, 2)}`)
278
-
279
- // Set default as not an SLP tx
280
- txDetails.isSlpTx = false
281
-
282
- // Get Token Data
283
- const txTokenData = await this.getTokenInfo(txid)
284
- // console.log(`txTokenData: ${JSON.stringify(txTokenData, null, 2)}`)
285
-
286
- // If not a token, return the tx data. Processing is complete.
287
- if (!txTokenData) return txDetails
288
-
289
- // Mark TX as an SLP tx. This does not mean it's valid, it just means
290
- // the OP_RETURN passes a basic check.
291
- txDetails.isSlpTx = true
292
-
293
- // Get Genesis data
294
- const genesisData = await this.getTokenInfo(txTokenData.tokenId)
295
- // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
296
-
297
- // Add token information to the tx details object.
298
- txDetails.tokenTxType = txTokenData.txType
299
- txDetails.tokenId = txTokenData.tokenId
300
- txDetails.tokenTicker = genesisData.ticker
301
- txDetails.tokenName = genesisData.name
302
- txDetails.tokenDecimals = genesisData.decimals
303
- txDetails.tokenUri = genesisData.documentUri
304
- txDetails.tokenDocHash = genesisData.documentHash
305
- // console.log(`txDetails before processing input and outputs: ${JSON.stringify(txDetails, null, 2)}`)
306
-
307
- // Process TX Outputs
308
- // Add the token quantity to each output.
309
- // 'i' starts at 1, because vout[0] is the OP_RETURN
310
- for (let i = 0; i < txDetails.vout.length; i++) {
311
- const thisVout = txDetails.vout[i]
312
- if (txTokenData.txType === 'SEND') {
313
- // console.log(
314
- // `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
315
- // )
316
-
317
- // First output is OP_RETURN, so tokenQty is null.
318
- if (i === 0) {
319
- thisVout.tokenQty = null
320
- thisVout.tokenQtyStr = null
321
- continue
322
- }
323
-
324
- // Non SLP outputs.
325
- if (i > txTokenData.amounts.length) {
326
- thisVout.tokenQty = null
327
- thisVout.tokenQtyStr = null
328
- continue
329
- }
330
-
331
- const rawQty = txTokenData.amounts[i - 1]
332
-
333
- // Calculate the real quantity using a BigNumber, then convert it to a
334
- // floating point number.
335
- let realQty = new BigNumber(rawQty).dividedBy(
336
- 10 ** parseInt(txDetails.tokenDecimals)
337
- )
338
- realQty = realQty.toString()
339
- // realQty = parseFloat(realQty)
340
-
341
- txDetails.vout[i].tokenQtyStr = realQty
342
- txDetails.vout[i].tokenQty = parseFloat(realQty)
343
-
344
- // console.log(
345
- // `thisVout ${i}: ${JSON.stringify(txDetails.vout[i], null, 2)}`
346
- // )
347
- } else if (
348
- txTokenData.txType === 'GENESIS' ||
349
- txTokenData.txType === 'MINT'
350
- ) {
351
- // console.log(
352
- // `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
353
- // )
354
-
355
- let tokenQty = 0 // Default value
356
-
357
- // Only vout[1] of a Genesis or Mint transaction represents the tokens.
358
- // Any other outputs in that transaction are normal BCH UTXOs.
359
- if (i === 1) {
360
- tokenQty = txTokenData.qty
361
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
362
-
363
- // Calculate the real quantity using a BigNumber, then convert it to a
364
- // floating point number.
365
- let realQty = new BigNumber(tokenQty).dividedBy(
366
- 10 ** parseInt(txDetails.tokenDecimals)
367
- )
368
- realQty = realQty.toString()
369
- // realQty = parseFloat(realQty)
370
-
371
- thisVout.tokenQtyStr = realQty
372
- thisVout.tokenQty = parseFloat(realQty)
373
- // console.log(`thisVout[${i}]: ${JSON.stringify(thisVout, null, 2)}`)
374
- } else if (i === txTokenData.mintBatonVout) {
375
- // Optional Mint baton
376
- thisVout.tokenQtyStr = '0'
377
- thisVout.tokenQty = 0
378
- thisVout.isMintBaton = true
379
- } else {
380
- thisVout.tokenQtyStr = '0'
381
- thisVout.tokenQty = 0
382
- }
383
- } else {
384
- throw new Error('Unknown SLP TX type for TX')
385
- }
386
- }
387
-
388
- // Process TX inputs
389
- for (let i = 0; i < txDetails.vin.length; i++) {
390
- const thisVin = txDetails.vin[i]
391
- // console.log(`thisVin[${i}]: ${JSON.stringify(thisVin, null, 2)}`)
392
-
393
- const vinTokenData = await this.getTokenInfo(thisVin.txid)
394
- // console.log(
395
- // `vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
396
- // )
397
-
398
- // Corner case: Ensure the token ID is the same.
399
- const vinTokenIdIsTheSame = vinTokenData.tokenId === txDetails.tokenId
400
-
401
- // If the input is not a token input, or if the tokenID is not the same,
402
- // then mark the token output as null.
403
- if (!vinTokenData || !vinTokenIdIsTheSame) {
404
- thisVin.tokenQty = 0
405
- thisVin.tokenQtyStr = '0'
406
- thisVin.tokenId = null
407
- continue
408
- }
409
-
410
- if (vinTokenData.txType === 'SEND') {
411
- // console.log(
412
- // `SEND vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
413
- // )
414
-
415
- const tokenQty = vinTokenData.amounts[thisVin.vout - 1]
416
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
417
-
418
- // Calculate the real quantity using a BigNumber, then convert it to a
419
- // floating point number.
420
- let realQty = new BigNumber(tokenQty).dividedBy(
421
- 10 ** parseInt(txDetails.tokenDecimals)
422
- )
423
- realQty = realQty.toString()
424
- // realQty = parseFloat(realQty)
425
-
426
- thisVin.tokenQtyStr = realQty
427
- thisVin.tokenQty = parseFloat(realQty)
428
- thisVin.tokenId = vinTokenData.tokenId
429
- } else if (vinTokenData.txType === 'MINT') {
430
- // console.log(
431
- // `MINT vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
432
- // )
433
-
434
- let tokenQty = 0 // Default value
435
-
436
- // Only vout[1] of a Genesis transaction represents the tokens.
437
- // Any other outputs in that transaction are normal BCH UTXOs.
438
- if (thisVin.vout === 1) {
439
- tokenQty = vinTokenData.qty
440
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
441
-
442
- // Calculate the real quantity using a BigNumber, then convert it to a
443
- // floating point number.
444
- let realQty = new BigNumber(tokenQty).dividedBy(
445
- 10 ** parseInt(txDetails.tokenDecimals)
446
- )
447
- realQty = realQty.toString()
448
- // realQty = parseFloat(realQty)
449
-
450
- thisVin.tokenQtyStr = realQty
451
- thisVin.tokenQty = parseFloat(realQty)
452
- thisVin.tokenId = vinTokenData.tokenId
453
- } else if (thisVin.vout === vinTokenData.mintBatonVout) {
454
- // Optional Mint baton
455
- thisVin.tokenQtyStr = '0'
456
- thisVin.tokenQty = 0
457
- thisVin.tokenId = vinTokenData.tokenId
458
- thisVin.isMintBaton = true
459
- } else {
460
- thisVin.tokenQtyStr = '0'
461
- thisVin.tokenQty = 0
462
- thisVin.tokenId = null
463
- }
464
- } else if (vinTokenData.txType === 'GENESIS') {
465
- // console.log(
466
- // `GENESIS vinTokenData ${i}: ${JSON.stringify(
467
- // vinTokenData,
468
- // null,
469
- // 2
470
- // )}`
471
- // )
472
-
473
- let tokenQty = 0 // Default value
474
-
475
- // Only vout[1] of a Genesis transaction represents the tokens.
476
- // Any other outputs in that transaction are normal BCH UTXOs.
477
- if (thisVin.vout === 1) {
478
- tokenQty = vinTokenData.qty
479
- // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
480
-
481
- // Calculate the real quantity using a BigNumber, then convert it to a
482
- // floating point number.
483
- let realQty = new BigNumber(tokenQty).dividedBy(
484
- 10 ** parseInt(txDetails.tokenDecimals)
485
- )
486
- realQty = realQty.toString()
487
- // realQty = parseFloat(realQty)
488
-
489
- thisVin.tokenQtyStr = realQty
490
- thisVin.tokenQty = parseFloat(realQty)
491
- thisVin.tokenId = vinTokenData.tokenId
492
- } else if (thisVin.vout === vinTokenData.mintBatonVout) {
493
- // Optional Mint baton
494
- thisVin.tokenQtyStr = '0'
495
- thisVin.tokenQty = 0
496
- thisVin.tokenId = vinTokenData.tokenId
497
- thisVin.isMintBaton = true
498
- } else {
499
- thisVin.tokenQtyStr = '0'
500
- thisVin.tokenQty = 0
501
- thisVin.tokenId = null
502
- }
503
- } else {
504
- console.log(
505
- `Unknown vinTokenData: ${JSON.stringify(vinTokenData, null, 2)}`
506
- )
507
- throw new Error('Unknown token type in input')
508
- }
509
- }
510
-
511
- return txDetails
512
- } catch (err) {
513
- console.error('Error in get3()')
514
-
515
- // This case handles rate limit errors.
516
- if (err.response && err.response.data && err.response.data.error) {
517
- throw new Error(err.response.data.error)
518
- }
519
-
520
- if (err.error) throw new Error(err.error)
521
- throw err
522
- }
523
- }
524
-
525
28
  // A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx.
526
29
  // Returns the token data if the txid is an SLP tx.
527
30
  async getTokenInfo (txid) {