@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 +2 -0
- package/package.json +44 -44
- package/src/transaction.js +208 -1
- package/test/integration/transaction-integration.js +21 -0
- package/test/unit/transaction-unit.js +270 -0
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@psf/bch-js",
|
|
3
|
-
"version": "4.20.
|
|
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": "
|
|
40
|
-
"@psf/bip32-utils": "
|
|
41
|
-
"@psf/bitcoincash-ops": "
|
|
42
|
-
"@psf/bitcoincashjs-lib": "
|
|
43
|
-
"@psf/coininfo": "
|
|
44
|
-
"@uppy/core": "
|
|
45
|
-
"@uppy/tus": "
|
|
46
|
-
"axios": "^0.21.
|
|
47
|
-
"bc-bip68": "
|
|
48
|
-
"bchaddrjs-slp": "
|
|
49
|
-
"bigi": "
|
|
50
|
-
"bignumber.js": "
|
|
51
|
-
"bip-schnorr": "
|
|
52
|
-
"bip38": "
|
|
53
|
-
"bip39": "
|
|
54
|
-
"bip66": "
|
|
55
|
-
"bitcoinjs-message": "
|
|
56
|
-
"bs58": "
|
|
57
|
-
"ecashaddrjs": "
|
|
58
|
-
"ini": "
|
|
59
|
-
"randombytes": "
|
|
60
|
-
"safe-buffer": "
|
|
61
|
-
"satoshi-bitcoin": "
|
|
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": "
|
|
64
|
+
"wif": "2.0.6"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"apidoc": "
|
|
68
|
-
"assert": "
|
|
69
|
-
"chai": "
|
|
70
|
-
"coveralls": "
|
|
71
|
-
"eslint": "
|
|
72
|
-
"eslint-config-prettier": "
|
|
73
|
-
"eslint-config-standard": "
|
|
74
|
-
"eslint-plugin-node": "
|
|
75
|
-
"eslint-plugin-prettier": "
|
|
76
|
-
"eslint-plugin-standard": "
|
|
77
|
-
"husky": "^4.3.
|
|
78
|
-
"lodash.clonedeep": "
|
|
79
|
-
"mocha": "
|
|
80
|
-
"node-mocks-http": "
|
|
81
|
-
"nyc": "
|
|
82
|
-
"prettier": "
|
|
83
|
-
"semantic-release": "
|
|
84
|
-
"sinon": "
|
|
85
|
-
"standard": "^16.0.
|
|
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",
|
package/src/transaction.js
CHANGED
|
@@ -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
|
|
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
|
})
|