@psf/bch-js 4.20.19 → 4.20.23

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/README.md CHANGED
@@ -118,3 +118,5 @@ Copies of this repository are also published on [IPFS](https://ipfs.io).
118
118
  ## License
119
119
 
120
120
  [MIT](LICENSE.md)
121
+
122
+ test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "4.20.19",
3
+ "version": "4.20.23",
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": [
@@ -36,53 +36,53 @@
36
36
  "url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
37
37
  },
38
38
  "dependencies": {
39
- "@psf/bip21": "^2.0.1",
40
- "@psf/bip32-utils": "^1.0.0",
41
- "@psf/bitcoincash-ops": "^2.0.0",
42
- "@psf/bitcoincashjs-lib": "^4.0.2",
43
- "@psf/coininfo": "^4.0.0",
44
- "@uppy/core": "^1.10.4",
45
- "@uppy/tus": "^1.5.12",
46
- "axios": "^0.21.1",
47
- "bc-bip68": "^1.0.5",
48
- "bchaddrjs-slp": "^0.2.5",
49
- "bigi": "^1.4.2",
50
- "bignumber.js": "^9.0.0",
51
- "bip-schnorr": "^0.3.0",
52
- "bip38": "^2.0.2",
53
- "bip39": "^3.0.2",
54
- "bip66": "^1.1.5",
55
- "bitcoinjs-message": "^2.0.0",
56
- "bs58": "^4.0.1",
57
- "ecashaddrjs": "^1.0.7",
58
- "ini": "^1.3.8",
59
- "randombytes": "^2.0.6",
60
- "safe-buffer": "^5.1.2",
61
- "satoshi-bitcoin": "^1.0.4",
39
+ "@psf/bip21": "2.0.1",
40
+ "@psf/bip32-utils": "1.0.1",
41
+ "@psf/bitcoincash-ops": "2.0.0",
42
+ "@psf/bitcoincashjs-lib": "4.0.2",
43
+ "@psf/coininfo": "4.0.0",
44
+ "@uppy/core": "1.10.4",
45
+ "@uppy/tus": "1.5.12",
46
+ "axios": "^0.21.4",
47
+ "bc-bip68": "1.0.5",
48
+ "bchaddrjs-slp": "0.2.5",
49
+ "bigi": "1.4.2",
50
+ "bignumber.js": "9.0.0",
51
+ "bip-schnorr": "0.3.0",
52
+ "bip38": "2.0.2",
53
+ "bip39": "3.0.2",
54
+ "bip66": "1.1.5",
55
+ "bitcoinjs-message": "2.0.0",
56
+ "bs58": "4.0.1",
57
+ "ecashaddrjs": "1.0.7",
58
+ "ini": "1.3.8",
59
+ "randombytes": "2.0.6",
60
+ "safe-buffer": "5.1.2",
61
+ "satoshi-bitcoin": "1.0.4",
62
62
  "slp-mdm": "0.0.6",
63
63
  "slp-parser": "0.0.4",
64
- "wif": "^2.0.6"
64
+ "wif": "2.0.6"
65
65
  },
66
66
  "devDependencies": {
67
- "apidoc": "^0.17.7",
68
- "assert": "^2.0.0",
69
- "chai": "^4.1.2",
70
- "coveralls": "^3.0.2",
71
- "eslint": "5.16.0",
72
- "eslint-config-prettier": "^6.0.0",
73
- "eslint-config-standard": "^14.1.0",
74
- "eslint-plugin-node": "^9.1.0",
75
- "eslint-plugin-prettier": "^3.1.0",
76
- "eslint-plugin-standard": "^4.0.0",
77
- "husky": "^4.3.6",
78
- "lodash.clonedeep": "^4.5.0",
79
- "mocha": "^9.1.3",
80
- "node-mocks-http": "^1.7.0",
81
- "nyc": "^15.1.0",
82
- "prettier": "^1.18.2",
83
- "semantic-release": "^17.3.9",
84
- "sinon": "^7.3.2",
85
- "standard": "^16.0.3"
67
+ "apidoc": "0.17.7",
68
+ "assert": "2.0.0",
69
+ "chai": "4.1.2",
70
+ "coveralls": "3.0.2",
71
+ "eslint": "^8.1.0",
72
+ "eslint-config-prettier": "6.0.0",
73
+ "eslint-config-standard": "14.1.0",
74
+ "eslint-plugin-node": "9.1.0",
75
+ "eslint-plugin-prettier": "3.1.0",
76
+ "eslint-plugin-standard": "4.0.0",
77
+ "husky": "^4.3.8",
78
+ "lodash.clonedeep": "4.5.0",
79
+ "mocha": "9.1.3",
80
+ "node-mocks-http": "1.7.0",
81
+ "nyc": "15.1.0",
82
+ "prettier": "1.18.2",
83
+ "semantic-release": "18.0.0",
84
+ "sinon": "7.3.2",
85
+ "standard": "^16.0.4"
86
86
  },
87
87
  "apidoc": {
88
88
  "title": "bch-js",
@@ -2,15 +2,18 @@
2
2
  High-level functions for working with Transactions
3
3
  */
4
4
 
5
+ const BigNumber = require('bignumber.js')
6
+
5
7
  const RawTransaction = require('./raw-transactions')
6
8
  const SlpUtils = require('./slp/utils')
7
- const BigNumber = require('bignumber.js')
9
+ const Blockchain = require('./blockchain')
8
10
 
9
11
  class Transaction {
10
12
  constructor (config) {
11
13
  // Encapsulate dependencies
12
14
  this.slpUtils = new SlpUtils(config)
13
15
  this.rawTransaction = new RawTransaction(config)
16
+ this.blockchain = new Blockchain(config)
14
17
  }
15
18
 
16
19
  /**
@@ -35,6 +38,10 @@ class Transaction {
35
38
  * }
36
39
  * })()
37
40
  */
41
+
42
+ // CT 10/31/21: TODO: this function should be refactored to use get2(), but
43
+ // add waterfall validation of the TX and its inputs.
44
+
38
45
  async get (txid) {
39
46
  try {
40
47
  if (typeof txid !== 'string') {
@@ -102,6 +109,7 @@ class Transaction {
102
109
  // Loop through each input and retrieve the token data.
103
110
  for (let i = 0; i < txDetails.vin.length; i++) {
104
111
  const thisVin = txDetails.vin[i]
112
+ // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
105
113
 
106
114
  try {
107
115
  // If decodeOpReturn() throws an error, then this input is not
@@ -168,6 +176,9 @@ class Transaction {
168
176
  thisVin.tokenQtyStr = realQty
169
177
  thisVin.tokenQty = parseFloat(realQty)
170
178
  // txDetails.vin[i].tokenQty = tokenQty
179
+
180
+ // Add token ID to input
181
+ thisVin.tokenId = inTokenData.tokenId
171
182
  } else {
172
183
  thisVin.tokenQty = null
173
184
  }
@@ -183,6 +194,202 @@ class Transaction {
183
194
  // Finally, validate the SLP TX.
184
195
  // this.slpUtils.waterfallValidateTxid(txid, usrObj)
185
196
  txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid)
197
+
198
+ // TODO: Convert the block hash to a block height. Add block height
199
+ // value to the transaction.
200
+ } catch (err) {
201
+ // console.log('Error: ', err)
202
+
203
+ // This case handles rate limit errors.
204
+ if (err.response && err.response.data && err.response.data.error) {
205
+ throw new Error(err.response.data.error)
206
+ }
207
+
208
+ // If decoding the op_return fails, then it's not an SLP transaction,
209
+ // and the non-hyrated TX details can be returned.
210
+ return txDetails
211
+ }
212
+
213
+ return txDetails
214
+ } catch (err) {
215
+ // console.error('Error in transactions.js/get(): ', err)
216
+
217
+ // This case handles rate limit errors.
218
+ if (err.response && err.response.data && err.response.data.error) {
219
+ throw new Error(err.response.data.error)
220
+ }
221
+
222
+ if (err.error) throw new Error(err.error)
223
+ throw err
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @api Transaction.get2() get2()
229
+ * @apiName get2
230
+ * @apiGroup Transaction
231
+ * @apiDescription
232
+ * Returns an object of transaction data, including addresses for input UTXOs.
233
+ * If it is a SLP token transaction, the token information for inputs and
234
+ * outputs will also be included.
235
+ *
236
+ * This is an API heavy call. This function will only work with a single txid.
237
+ * It does not yet support an array of TXIDs.
238
+ *
239
+ * This is the same as get(), except it omits DAG validation of the TXID.
240
+ *
241
+ * @apiExample Example usage:
242
+ * (async () => {
243
+ * try {
244
+ * let txData = await bchjs.Transaction.get2("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
245
+ * console.log(txData);
246
+ * } catch(error) {
247
+ * console.error(error)
248
+ * }
249
+ * })()
250
+ */
251
+ async get2 (txid) {
252
+ try {
253
+ if (typeof txid !== 'string') {
254
+ throw new Error(
255
+ 'Input to Transaction.get() must be a string containing a TXID.'
256
+ )
257
+ }
258
+
259
+ const txDetails = await this.rawTransaction.getTxData(txid)
260
+ // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
261
+
262
+ // Get the block height the transaction was mined in.
263
+ const blockHeader = await this.blockchain.getBlockHeader(
264
+ txDetails.blockhash
265
+ )
266
+ txDetails.blockheight = blockHeader.height
267
+
268
+ // First get the token information for the output. If that fails, then
269
+ // this is not an SLP transaction, and this method can return false.
270
+ let outTokenData
271
+ try {
272
+ outTokenData = await this.slpUtils.decodeOpReturn(txid)
273
+ // console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`)
274
+
275
+ // Get Genesis data for this token.
276
+ const genesisData = await this.slpUtils.decodeOpReturn(
277
+ outTokenData.tokenId
278
+ // decodeOpReturnCache
279
+ // usrObj // pass user data when making an internal call.
280
+ )
281
+ // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
282
+
283
+ // Add token information to the tx details object.
284
+ txDetails.tokenTxType = outTokenData.txType
285
+ txDetails.tokenId = outTokenData.tokenId
286
+ txDetails.tokenTicker = genesisData.ticker
287
+ txDetails.tokenName = genesisData.name
288
+ txDetails.tokenDecimals = genesisData.decimals
289
+ txDetails.tokenUri = genesisData.documentUri
290
+ txDetails.tokenDocHash = genesisData.documentHash
291
+
292
+ // Add the token quantity to each output.
293
+ for (let i = 0; i < outTokenData.amounts.length; i++) {
294
+ const rawQty = outTokenData.amounts[i]
295
+ // const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals)
296
+
297
+ // Calculate the real quantity using a BigNumber, then convert it to a
298
+ // floating point number.
299
+ let realQty = new BigNumber(rawQty).dividedBy(
300
+ 10 ** parseInt(txDetails.tokenDecimals)
301
+ )
302
+ realQty = realQty.toString()
303
+ // realQty = parseFloat(realQty)
304
+
305
+ txDetails.vout[i + 1].tokenQtyStr = realQty
306
+ txDetails.vout[i + 1].tokenQty = parseFloat(realQty)
307
+ }
308
+
309
+ // Add tokenQty = null to any outputs that don't have a value.
310
+ for (let i = 0; i < txDetails.vout.length; i++) {
311
+ const thisVout = txDetails.vout[i]
312
+
313
+ if (!thisVout.tokenQty && thisVout.tokenQty !== 0) {
314
+ thisVout.tokenQty = null
315
+ }
316
+ }
317
+
318
+ // Loop through each input and retrieve the token data.
319
+ for (let i = 0; i < txDetails.vin.length; i++) {
320
+ const thisVin = txDetails.vin[i]
321
+ // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
322
+
323
+ try {
324
+ // If decodeOpReturn() throws an error, then this input is not
325
+ // from an SLP transaction and can be ignored.
326
+ const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid)
327
+ // console.log(
328
+ // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}`
329
+ // )
330
+
331
+ let tokenQty = 0
332
+
333
+ if (inTokenData.txType === 'SEND') {
334
+ // Get the appropriate vout token amount. This may throw an error,
335
+ // which means this Vin is not actually a token UTXO, it was just
336
+ // associated with a previous token TX.
337
+ tokenQty = inTokenData.amounts[thisVin.vout - 1]
338
+ // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
339
+
340
+ //
341
+ } else if (inTokenData.txType === 'GENESIS') {
342
+ // Only vout[1] of a Genesis transaction represents the tokens.
343
+ // Any other outputs in that transaction are normal BCH UTXOs.
344
+ if (thisVin.vout === 1) {
345
+ tokenQty = inTokenData.qty
346
+ // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
347
+ }
348
+ } else if (inTokenData.txType === 'MINT') {
349
+ // vout=1 (second output) recieves the newly minted tokens.
350
+ if (thisVin.vout === 1) {
351
+ tokenQty = inTokenData.qty
352
+ } else {
353
+ tokenQty = null
354
+ }
355
+
356
+ //
357
+ } else {
358
+ console.log(
359
+ 'Unexpected code path in Transaction.get2(). What is the txType?'
360
+ )
361
+ console.log(inTokenData)
362
+ throw new Error('Unexpected code path')
363
+ }
364
+
365
+ if (tokenQty) {
366
+ // Calculate the real quantity using a BigNumber, then convert it to a
367
+ // floating point number.
368
+ let realQty = new BigNumber(tokenQty).dividedBy(
369
+ 10 ** parseInt(txDetails.tokenDecimals)
370
+ )
371
+ realQty = realQty.toString()
372
+ // realQty = parseFloat(realQty)
373
+
374
+ thisVin.tokenQtyStr = realQty
375
+ thisVin.tokenQty = parseFloat(realQty)
376
+ // txDetails.vin[i].tokenQty = tokenQty
377
+
378
+ // Add token ID to input
379
+ thisVin.tokenId = inTokenData.tokenId
380
+ } else {
381
+ thisVin.tokenQty = null
382
+ thisVin.tokenQtyStr = null
383
+ }
384
+ } catch (err) {
385
+ // If decodeOpReturn() throws an error, then this input is not
386
+ // from an SLP transaction and can be ignored.
387
+ // thisVin.tokenQty = null
388
+ thisVin.tokenQty = null
389
+ thisVin.tokenQtyStr = null
390
+ continue
391
+ }
392
+ }
186
393
  } catch (err) {
187
394
  // console.log('Error: ', err)
188
395
 
@@ -56,5 +56,26 @@ describe('#Transaction', () => {
56
56
  assert.property(result, 'isValidSLPTx')
57
57
  assert.equal(result.isValidSLPTx, true)
58
58
  })
59
+
60
+ // it('should get problematic transaction', async () => {
61
+ // const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
62
+ //
63
+ // const result = await bchjs.Transaction.get(txid)
64
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
65
+ // })
66
+
67
+ // TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
68
+ // an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
69
+ // that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
70
+ // but for separate tokens.
71
+ it('should get problematic transaction', async () => {
72
+ const txid = 'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
73
+
74
+ const result = await bchjs.Transaction.get(txid)
75
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
76
+
77
+ // The token ID should equal the txid for this Vin.
78
+ assert.equal(result.vin[2].txid, result.vin[2].tokenId)
79
+ })
59
80
  })
60
81
  })
@@ -265,4 +265,274 @@ describe('#TransactionLib', () => {
265
265
  assert.equal(result.vin[2].tokenQty, 99000000)
266
266
  })
267
267
  })
268
+
269
+ describe('#get2', () => {
270
+ it('should throw an error if txid is not specified', async () => {
271
+ try {
272
+ await bchjs.Transaction.get2()
273
+
274
+ assert.fail('Unexpected code path!')
275
+ } catch (err) {
276
+ assert.include(
277
+ err.message,
278
+ 'Input to Transaction.get() must be a string containing a TXID.'
279
+ )
280
+ }
281
+ })
282
+
283
+ it('should get details about a non-SLP transaction', async () => {
284
+ const txid =
285
+ '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
286
+
287
+ // Mock dependencies
288
+ sandbox
289
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
290
+ .resolves(mockData.nonSlpTxDetails)
291
+ sandbox
292
+ .stub(bchjs.Transaction.blockchain, 'getBlockHeader')
293
+ .resolves({ height: 602405 })
294
+
295
+ const result = await bchjs.Transaction.get2(txid)
296
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
297
+
298
+ // Assert that there are stanardized properties.
299
+ assert.property(result, 'txid')
300
+ assert.property(result, 'vin')
301
+ assert.property(result, 'vout')
302
+ assert.property(result.vout[0], 'value')
303
+ assert.property(result.vout[0].scriptPubKey, 'addresses')
304
+
305
+ // Assert that added properties exist.
306
+ assert.property(result.vin[0], 'address')
307
+ assert.property(result.vin[0], 'value')
308
+ assert.property(result, 'isValidSLPTx')
309
+ assert.equal(result.isValidSLPTx, false)
310
+
311
+ // Assert blockheight is added
312
+ assert.equal(result.blockheight, 602405)
313
+ })
314
+
315
+ it('should get details about a SLP transaction', async () => {
316
+ // Mock dependencies
317
+ sandbox
318
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
319
+ .resolves(mockData.slpTxDetails)
320
+ sandbox
321
+ .stub(bchjs.Transaction.blockchain, 'getBlockHeader')
322
+ .resolves({ height: 603424 })
323
+ sandbox
324
+ .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
325
+ .onCall(0)
326
+ .resolves(mockData.mockOpReturnData01)
327
+ .onCall(1)
328
+ .resolves(mockData.mockOpReturnData02)
329
+ .onCall(2)
330
+ .resolves(mockData.mockOpReturnData03)
331
+ .onCall(3)
332
+ .rejects(new Error('No OP_RETURN'))
333
+
334
+ const txid =
335
+ '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
336
+
337
+ const result = await bchjs.Transaction.get2(txid)
338
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
339
+
340
+ // Assert that there are stanardized properties.
341
+ assert.property(result, 'txid')
342
+ assert.property(result, 'vin')
343
+ assert.property(result, 'vout')
344
+ assert.property(result.vout[0], 'value')
345
+ assert.property(result.vout[1].scriptPubKey, 'addresses')
346
+
347
+ // Assert that added properties exist.
348
+ assert.property(result.vout[0], 'tokenQty')
349
+ assert.equal(result.vout[0].tokenQty, null)
350
+ assert.property(result.vin[0], 'address')
351
+ assert.property(result.vin[0], 'value')
352
+ assert.property(result.vin[0], 'tokenQty')
353
+
354
+ // Assert that tokenIds and tokenQty are included in inputs.
355
+ assert.property(result.vin[0], 'tokenId')
356
+ assert.equal(result.vin[0].tokenQtyStr, '998834')
357
+
358
+ // Assert blockheight is added
359
+ assert.equal(result.blockheight, 603424)
360
+ })
361
+
362
+ it('should catch and throw error on network error', async () => {
363
+ try {
364
+ const txid =
365
+ '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
366
+
367
+ // Force an error
368
+ sandbox
369
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
370
+ .rejects(new Error('test error'))
371
+
372
+ await bchjs.Transaction.get(txid)
373
+
374
+ assert.fail('Unexpected code path')
375
+ } catch (err) {
376
+ assert.include(err.message, 'test error')
377
+ }
378
+ })
379
+
380
+ // This test case was created in response to a bug. When the input TX
381
+ // was a Genesis SLP transaction, the inputs of the transaction were not
382
+ // being hydrated properly.
383
+ it('should get input details when input is a genesis tx', async () => {
384
+ // Mock dependencies
385
+ sandbox
386
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
387
+ .resolves(mockData.genesisTestInputTx)
388
+ sandbox
389
+ .stub(bchjs.Transaction.blockchain, 'getBlockHeader')
390
+ .resolves({ height: 543409 })
391
+ sandbox
392
+ .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
393
+ .onCall(0)
394
+ .resolves(mockData.genesisTestOpReturnData01)
395
+ .onCall(1)
396
+ .resolves(mockData.genesisTestOpReturnData02)
397
+ .onCall(2)
398
+ .resolves(mockData.genesisTestOpReturnData02)
399
+ .onCall(3)
400
+ .resolves(mockData.genesisTestOpReturnData02)
401
+
402
+ const txid =
403
+ '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
404
+
405
+ const result = await bchjs.Transaction.get2(txid)
406
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
407
+
408
+ // Assert that there are stanardized properties.
409
+ assert.property(result, 'txid')
410
+ assert.property(result, 'vin')
411
+ assert.property(result, 'vout')
412
+ assert.property(result.vout[0], 'value')
413
+ assert.property(result.vout[1].scriptPubKey, 'addresses')
414
+
415
+ // Assert that added properties exist.
416
+ assert.property(result.vout[0], 'tokenQty')
417
+ assert.equal(result.vout[0].tokenQty, null)
418
+ assert.property(result.vin[0], 'address')
419
+ assert.property(result.vin[0], 'value')
420
+ assert.property(result.vin[0], 'tokenQty')
421
+
422
+ // Assert inputs values unique to a Genesis input have the proper values.
423
+ assert.equal(result.vin[0].tokenQty, 10000000)
424
+ assert.equal(result.vin[1].tokenQty, null)
425
+
426
+ // Assert that tokenIds and tokenQty are included in inputs.
427
+ assert.property(result.vin[0], 'tokenId')
428
+ assert.equal(result.vin[0].tokenQtyStr, '10000000')
429
+ assert.equal(result.vin[1].tokenQty, null)
430
+ assert.equal(result.vin[1].tokenQtyStr, null)
431
+
432
+ // Assert blockheight is added
433
+ assert.equal(result.blockheight, 543409)
434
+ })
435
+
436
+ it('should get input details when input is a mint tx', async () => {
437
+ // Mock dependencies
438
+ sandbox
439
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
440
+ .resolves(mockData.mintTestInputTx)
441
+ sandbox
442
+ .stub(bchjs.Transaction.blockchain, 'getBlockHeader')
443
+ .resolves({ height: 543614 })
444
+ sandbox
445
+ .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
446
+ .onCall(0)
447
+ .resolves(mockData.mintTestOpReturnData01)
448
+ .onCall(1)
449
+ .resolves(mockData.mintTestOpReturnData02)
450
+ .onCall(2)
451
+ .resolves(mockData.mintTestOpReturnData02)
452
+ .onCall(3)
453
+ .resolves(mockData.mintTestOpReturnData03)
454
+ .onCall(4)
455
+ .resolves(mockData.mintTestOpReturnData03)
456
+
457
+ const txid =
458
+ '4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
459
+
460
+ const result = await bchjs.Transaction.get2(txid)
461
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
462
+
463
+ // Assert that there are stanardized properties.
464
+ assert.property(result, 'txid')
465
+ assert.property(result, 'vin')
466
+ assert.property(result, 'vout')
467
+ assert.property(result.vout[0], 'value')
468
+ assert.property(result.vout[1].scriptPubKey, 'addresses')
469
+
470
+ // Assert that added properties exist.
471
+ assert.property(result.vout[0], 'tokenQty')
472
+ assert.equal(result.vout[0].tokenQty, null)
473
+ assert.property(result.vin[0], 'address')
474
+ assert.property(result.vin[0], 'value')
475
+ assert.property(result.vin[0], 'tokenQty')
476
+
477
+ // Assert inputs values unique to a Mint input have the proper values.
478
+ assert.equal(result.vin[0].tokenQty, 43545.34534)
479
+ assert.equal(result.vin[1].tokenQty, 2.34123)
480
+ assert.equal(result.vin[2].tokenQty, null)
481
+
482
+ // Assert blockheight is added
483
+ assert.equal(result.blockheight, 543614)
484
+ })
485
+
486
+ // This test case was generated from the problematic transaction that
487
+ // used inputs in a 'non-standard' way.
488
+ it('should correctly assign quantities to mixed mint inputs', async () => {
489
+ // Mock dependencies
490
+ sandbox
491
+ .stub(bchjs.Transaction.rawTransaction, 'getTxData')
492
+ .resolves(mockData.sendTestInputTx01)
493
+ sandbox
494
+ .stub(bchjs.Transaction.blockchain, 'getBlockHeader')
495
+ .resolves({ height: 543957 })
496
+ sandbox
497
+ .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
498
+ .onCall(0)
499
+ .resolves(mockData.sendTestOpReturnData01)
500
+ .onCall(1)
501
+ .resolves(mockData.sendTestOpReturnData02)
502
+ .onCall(2)
503
+ .resolves(mockData.sendTestOpReturnData03)
504
+ .onCall(3)
505
+ .resolves(mockData.sendTestOpReturnData03)
506
+ .onCall(4)
507
+ .resolves(mockData.sendTestOpReturnData04)
508
+
509
+ const txid =
510
+ '6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
511
+
512
+ const result = await bchjs.Transaction.get2(txid)
513
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
514
+
515
+ // Assert that there are stanardized properties.
516
+ assert.property(result, 'txid')
517
+ assert.property(result, 'vin')
518
+ assert.property(result, 'vout')
519
+ assert.property(result.vout[0], 'value')
520
+ assert.property(result.vout[1].scriptPubKey, 'addresses')
521
+
522
+ // Assert that added properties exist.
523
+ assert.property(result.vout[0], 'tokenQty')
524
+ assert.equal(result.vout[0].tokenQty, null)
525
+ assert.property(result.vin[0], 'address')
526
+ assert.property(result.vin[0], 'value')
527
+ assert.property(result.vin[0], 'tokenQty')
528
+
529
+ // Assert inputs values unique to a Mint input have the proper values.
530
+ assert.equal(result.vin[0].tokenQty, 100000000)
531
+ assert.equal(result.vin[1].tokenQty, null)
532
+ assert.equal(result.vin[2].tokenQty, 99000000)
533
+
534
+ // Assert blockheight is added
535
+ assert.equal(result.blockheight, 543957)
536
+ })
537
+ })
268
538
  })