@psf/bch-js 4.20.24 → 4.20.28
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/LICENSE.md +2 -1
- package/package.json +9 -9
- package/src/address.js +3 -3
- package/src/crypto.js +3 -3
- package/src/electrumx.js +2 -2
- package/src/price.js +5 -5
- package/src/transaction.js +228 -121
- package/src/utxo.js +4 -0
- package/test/integration/{util.js → chains/bchn/util.js} +3 -2
- package/test/unit/fixtures/transaction-mock.js +268 -52
- package/test/unit/transaction-unit.js +222 -52
package/LICENSE.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Copyright 2021
|
|
1
|
+
Copyright 2021 Permissionless Software Foundation contributors
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
|
5
5
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software
|
|
6
6
|
|
|
7
7
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
8
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@psf/bch-js",
|
|
3
|
-
"version": "4.20.
|
|
3
|
+
"version": "4.20.28",
|
|
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": [
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
|
19
19
|
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
|
20
20
|
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
|
|
21
|
-
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/",
|
|
21
|
+
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
|
22
|
+
"test:integration:decatur:abc": "export RESTURL=http://192.168.0.38:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
|
22
23
|
"test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
|
|
23
24
|
"test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
|
|
24
25
|
"test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
|
|
@@ -68,20 +69,19 @@
|
|
|
68
69
|
"assert": "2.0.0",
|
|
69
70
|
"chai": "4.1.2",
|
|
70
71
|
"coveralls": "3.0.2",
|
|
71
|
-
"eslint": "
|
|
72
|
-
"eslint-config-prettier": "
|
|
73
|
-
"eslint-config-standard": "
|
|
74
|
-
"eslint-plugin-node": "
|
|
75
|
-
"eslint-plugin-prettier": "3.1
|
|
72
|
+
"eslint": "7.17.0",
|
|
73
|
+
"eslint-config-prettier": "7.1.0",
|
|
74
|
+
"eslint-config-standard": "16.0.3",
|
|
75
|
+
"eslint-plugin-node": "11.1.0",
|
|
76
|
+
"eslint-plugin-prettier": "3.3.1",
|
|
76
77
|
"eslint-plugin-standard": "4.0.0",
|
|
77
78
|
"husky": "^4.3.8",
|
|
78
79
|
"lodash.clonedeep": "4.5.0",
|
|
79
80
|
"mocha": "9.1.3",
|
|
80
81
|
"node-mocks-http": "1.7.0",
|
|
81
82
|
"nyc": "15.1.0",
|
|
82
|
-
"prettier": "1.18.2",
|
|
83
83
|
"semantic-release": "18.0.0",
|
|
84
|
-
"sinon": "
|
|
84
|
+
"sinon": "9.2.2",
|
|
85
85
|
"standard": "^16.0.4"
|
|
86
86
|
},
|
|
87
87
|
"apidoc": {
|
package/src/address.js
CHANGED
|
@@ -223,7 +223,7 @@ class Address {
|
|
|
223
223
|
* @api Address.hash160ToCash() hash160ToCash()
|
|
224
224
|
* @apiName hash160ToCash
|
|
225
225
|
* @apiGroup Address
|
|
226
|
-
* @apiDescription Convert hash160 to cash address.
|
|
226
|
+
* @apiDescription Convert hash160 to cash address. Accepts either hexadecimal or buffer.
|
|
227
227
|
*
|
|
228
228
|
* @apiExample Example usage:
|
|
229
229
|
* bchjs.Address.hash160ToCash("573d93b475be4f1925f3b74ed951201b0147eac1")
|
|
@@ -794,13 +794,13 @@ class Address {
|
|
|
794
794
|
* @apiDescription Detect an addess from an OutputScript..
|
|
795
795
|
*
|
|
796
796
|
* @apiExample Example usage:
|
|
797
|
-
* const
|
|
797
|
+
* const scriptBuffer = bchjs.Script.encode([
|
|
798
798
|
* Buffer.from("BOX", "ascii"),
|
|
799
799
|
* bchjs.Script.opcodes.OP_CAT,
|
|
800
800
|
* Buffer.from("BITBOX", "ascii"),
|
|
801
801
|
* bchjs.Script.opcodes.OP_EQUAL
|
|
802
802
|
* ]);
|
|
803
|
-
* const p2sh_hash160 = bchjs.Crypto.hash160(
|
|
803
|
+
* const p2sh_hash160 = bchjs.Crypto.hash160(scriptBuffer);
|
|
804
804
|
* const scriptPubKey = bchjs.Script.scriptHash.output.encode(p2sh_hash160);
|
|
805
805
|
*
|
|
806
806
|
* // mainnet address from output script
|
package/src/crypto.js
CHANGED
|
@@ -22,7 +22,7 @@ class Crypto {
|
|
|
22
22
|
* // buffer from hex
|
|
23
23
|
* let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex')
|
|
24
24
|
* bchjs.Crypto.sha256(buffer)
|
|
25
|
-
* // <Buffer
|
|
25
|
+
* // <Buffer 97 8c 09 dd 46 09 1d 19 22 fa 01 e9 f4 a9 75 b9 1a 37 1f 26 ba 83 99 de 27 d5 38 01 15 21 21 de>
|
|
26
26
|
*
|
|
27
27
|
* */
|
|
28
28
|
// Translate address from any address format into a specific format.
|
|
@@ -60,7 +60,7 @@ class Crypto {
|
|
|
60
60
|
* @api Crypto.hash256() hash256()
|
|
61
61
|
* @apiName hash256
|
|
62
62
|
* @apiGroup Crypto
|
|
63
|
-
* @apiDescription Utility for creating double sha256 hash digests of data.
|
|
63
|
+
* @apiDescription Utility for creating double sha256 hash digests of buffer encoded data.
|
|
64
64
|
*
|
|
65
65
|
* @apiExample Example usage:
|
|
66
66
|
* // buffer from hex
|
|
@@ -86,7 +86,7 @@ class Crypto {
|
|
|
86
86
|
* @api Crypto.hash160() hash160()
|
|
87
87
|
* @apiName hash160
|
|
88
88
|
* @apiGroup Crypto
|
|
89
|
-
* @apiDescription Utility for creating ripemd160(sha256()) hash digests of data.
|
|
89
|
+
* @apiDescription Utility for creating ripemd160(sha256()) hash digests of buffer encoded data.
|
|
90
90
|
*
|
|
91
91
|
* @apiExample Example usage:
|
|
92
92
|
* // buffer from hex
|
package/src/electrumx.js
CHANGED
|
@@ -142,7 +142,7 @@ class ElectrumX {
|
|
|
142
142
|
* (async () => {
|
|
143
143
|
* try {
|
|
144
144
|
* let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf');
|
|
145
|
-
* console.log(
|
|
145
|
+
* console.log(balance);
|
|
146
146
|
* } catch(error) {
|
|
147
147
|
* console.error(error)
|
|
148
148
|
* }
|
|
@@ -159,7 +159,7 @@ class ElectrumX {
|
|
|
159
159
|
* (async () => {
|
|
160
160
|
* try {
|
|
161
161
|
* let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']);
|
|
162
|
-
* console.log(
|
|
162
|
+
* console.log(balance);
|
|
163
163
|
* } catch(error) {
|
|
164
164
|
* console.error(error)
|
|
165
165
|
* }
|
package/src/price.js
CHANGED
|
@@ -58,7 +58,7 @@ class Price {
|
|
|
58
58
|
* let current = await bchjs.Price.getUsd();
|
|
59
59
|
* console.log(current);
|
|
60
60
|
* } catch(err) {
|
|
61
|
-
* console.
|
|
61
|
+
* console.error(err)
|
|
62
62
|
* }
|
|
63
63
|
*})()
|
|
64
64
|
*
|
|
@@ -94,7 +94,7 @@ class Price {
|
|
|
94
94
|
* let current = await bchjs.Price.rates();
|
|
95
95
|
* console.log(current);
|
|
96
96
|
* } catch(err) {
|
|
97
|
-
* console.
|
|
97
|
+
* console.error(err)
|
|
98
98
|
* }
|
|
99
99
|
*})()
|
|
100
100
|
*
|
|
@@ -137,7 +137,7 @@ class Price {
|
|
|
137
137
|
* let current = await bchjs.Price.getBchaUsd();
|
|
138
138
|
* console.log(current);
|
|
139
139
|
* } catch(err) {
|
|
140
|
-
* console.
|
|
140
|
+
* console.error(err)
|
|
141
141
|
* }
|
|
142
142
|
*})()
|
|
143
143
|
*
|
|
@@ -175,7 +175,7 @@ class Price {
|
|
|
175
175
|
* let current = await bchjs.Price.getXecUsd();
|
|
176
176
|
* console.log(current);
|
|
177
177
|
* } catch(err) {
|
|
178
|
-
* console.
|
|
178
|
+
* console.error(err)
|
|
179
179
|
* }
|
|
180
180
|
*})()
|
|
181
181
|
*
|
|
@@ -210,7 +210,7 @@ class Price {
|
|
|
210
210
|
* let current = await bchjs.Price.getBchUsd();
|
|
211
211
|
* console.log(current);
|
|
212
212
|
* } catch(err) {
|
|
213
|
-
* console.
|
|
213
|
+
* console.error(err)
|
|
214
214
|
* }
|
|
215
215
|
*})()
|
|
216
216
|
*
|
package/src/transaction.js
CHANGED
|
@@ -225,8 +225,8 @@ class Transaction {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
|
-
* @api Transaction.
|
|
229
|
-
* @apiName
|
|
228
|
+
* @api Transaction.get3() get3()
|
|
229
|
+
* @apiName get3
|
|
230
230
|
* @apiGroup Transaction
|
|
231
231
|
* @apiDescription
|
|
232
232
|
* Returns an object of transaction data, including addresses for input UTXOs.
|
|
@@ -248,7 +248,7 @@ class Transaction {
|
|
|
248
248
|
* }
|
|
249
249
|
* })()
|
|
250
250
|
*/
|
|
251
|
-
async
|
|
251
|
+
async get3 (txid) {
|
|
252
252
|
try {
|
|
253
253
|
if (typeof txid !== 'string') {
|
|
254
254
|
throw new Error(
|
|
@@ -256,6 +256,7 @@ class Transaction {
|
|
|
256
256
|
)
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
// Get TX data
|
|
259
260
|
const txDetails = await this.rawTransaction.getTxData(txid)
|
|
260
261
|
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
|
261
262
|
|
|
@@ -264,35 +265,61 @@ class Transaction {
|
|
|
264
265
|
txDetails.blockhash
|
|
265
266
|
)
|
|
266
267
|
txDetails.blockheight = blockHeader.height
|
|
268
|
+
// console.log(`blockHeader: ${JSON.stringify(blockHeader, null, 2)}`)
|
|
269
|
+
|
|
270
|
+
// Set default as not an SLP tx
|
|
271
|
+
txDetails.isSlpTx = false
|
|
272
|
+
|
|
273
|
+
// Get Token Data
|
|
274
|
+
const txTokenData = await this.getTokenInfo(txid)
|
|
275
|
+
// console.log(`txTokenData: ${JSON.stringify(txTokenData, null, 2)}`)
|
|
276
|
+
|
|
277
|
+
// If not a token, return the tx data. Processing is complete.
|
|
278
|
+
if (!txTokenData) return txDetails
|
|
279
|
+
|
|
280
|
+
// Mark TX as an SLP tx. This does not mean it's valid, it just means
|
|
281
|
+
// the OP_RETURN passes a basic check.
|
|
282
|
+
txDetails.isSlpTx = true
|
|
283
|
+
|
|
284
|
+
// Get Genesis data
|
|
285
|
+
const genesisData = await this.getTokenInfo(txTokenData.tokenId)
|
|
286
|
+
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
|
287
|
+
|
|
288
|
+
// Add token information to the tx details object.
|
|
289
|
+
txDetails.tokenTxType = txTokenData.txType
|
|
290
|
+
txDetails.tokenId = txTokenData.tokenId
|
|
291
|
+
txDetails.tokenTicker = genesisData.ticker
|
|
292
|
+
txDetails.tokenName = genesisData.name
|
|
293
|
+
txDetails.tokenDecimals = genesisData.decimals
|
|
294
|
+
txDetails.tokenUri = genesisData.documentUri
|
|
295
|
+
txDetails.tokenDocHash = genesisData.documentHash
|
|
296
|
+
// console.log(`txDetails before processing input and outputs: ${JSON.stringify(txDetails, null, 2)}`)
|
|
297
|
+
|
|
298
|
+
// Process TX Outputs
|
|
299
|
+
// Add the token quantity to each output.
|
|
300
|
+
// 'i' starts at 1, because vout[0] is the OP_RETURN
|
|
301
|
+
for (let i = 0; i < txDetails.vout.length; i++) {
|
|
302
|
+
const thisVout = txDetails.vout[i]
|
|
303
|
+
if (txTokenData.txType === 'SEND') {
|
|
304
|
+
// console.log(
|
|
305
|
+
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
|
|
306
|
+
// )
|
|
307
|
+
|
|
308
|
+
// First output is OP_RETURN, so tokenQty is null.
|
|
309
|
+
if (i === 0) {
|
|
310
|
+
thisVout.tokenQty = null
|
|
311
|
+
thisVout.tokenQtyStr = null
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
267
314
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
315
|
+
// Non SLP outputs.
|
|
316
|
+
if (i > txTokenData.amounts.length) {
|
|
317
|
+
thisVout.tokenQty = null
|
|
318
|
+
thisVout.tokenQtyStr = null
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
291
321
|
|
|
292
|
-
|
|
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)
|
|
322
|
+
const rawQty = txTokenData.amounts[i - 1]
|
|
296
323
|
|
|
297
324
|
// Calculate the real quantity using a BigNumber, then convert it to a
|
|
298
325
|
// floating point number.
|
|
@@ -302,110 +329,179 @@ class Transaction {
|
|
|
302
329
|
realQty = realQty.toString()
|
|
303
330
|
// realQty = parseFloat(realQty)
|
|
304
331
|
|
|
305
|
-
txDetails.vout[i
|
|
306
|
-
txDetails.vout[i
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
txDetails.vout[i].tokenQtyStr = realQty
|
|
333
|
+
txDetails.vout[i].tokenQty = parseFloat(realQty)
|
|
334
|
+
|
|
335
|
+
// console.log(
|
|
336
|
+
// `thisVout ${i}: ${JSON.stringify(txDetails.vout[i], null, 2)}`
|
|
337
|
+
// )
|
|
338
|
+
} else if (
|
|
339
|
+
txTokenData.txType === 'GENESIS' ||
|
|
340
|
+
txTokenData.txType === 'MINT'
|
|
341
|
+
) {
|
|
342
|
+
// console.log(
|
|
343
|
+
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
|
|
344
|
+
// )
|
|
345
|
+
|
|
346
|
+
let tokenQty = 0 // Default value
|
|
347
|
+
|
|
348
|
+
// Only vout[1] of a Genesis or Mint transaction represents the tokens.
|
|
349
|
+
// Any other outputs in that transaction are normal BCH UTXOs.
|
|
350
|
+
if (i === 1) {
|
|
351
|
+
tokenQty = txTokenData.qty
|
|
352
|
+
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
|
353
|
+
|
|
354
|
+
// Calculate the real quantity using a BigNumber, then convert it to a
|
|
355
|
+
// floating point number.
|
|
356
|
+
let realQty = new BigNumber(tokenQty).dividedBy(
|
|
357
|
+
10 ** parseInt(txDetails.tokenDecimals)
|
|
358
|
+
)
|
|
359
|
+
realQty = realQty.toString()
|
|
360
|
+
// realQty = parseFloat(realQty)
|
|
361
|
+
|
|
362
|
+
thisVout.tokenQtyStr = realQty
|
|
363
|
+
thisVout.tokenQty = parseFloat(realQty)
|
|
364
|
+
// console.log(`thisVout[${i}]: ${JSON.stringify(thisVout, null, 2)}`)
|
|
365
|
+
} else if (i === txTokenData.mintBatonVout) {
|
|
366
|
+
// Optional Mint baton
|
|
367
|
+
thisVout.tokenQtyStr = '0'
|
|
368
|
+
thisVout.tokenQty = 0
|
|
369
|
+
thisVout.isMintBaton = true
|
|
370
|
+
} else {
|
|
371
|
+
thisVout.tokenQtyStr = '0'
|
|
372
|
+
thisVout.tokenQty = 0
|
|
315
373
|
}
|
|
374
|
+
} else {
|
|
375
|
+
throw new Error('Unknown SLP TX type for TX')
|
|
316
376
|
}
|
|
377
|
+
}
|
|
317
378
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
}
|
|
379
|
+
// Process TX inputs
|
|
380
|
+
for (let i = 0; i < txDetails.vin.length; i++) {
|
|
381
|
+
const thisVin = txDetails.vin[i]
|
|
382
|
+
// console.log(`thisVin[${i}]: ${JSON.stringify(thisVin, null, 2)}`)
|
|
383
|
+
|
|
384
|
+
const vinTokenData = await this.getTokenInfo(thisVin.txid)
|
|
385
|
+
// console.log(
|
|
386
|
+
// `vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
|
|
387
|
+
// )
|
|
388
|
+
|
|
389
|
+
// Corner case: Ensure the token ID is the same.
|
|
390
|
+
const vinTokenIdIsTheSame = vinTokenData.tokenId === txDetails.tokenId
|
|
391
|
+
|
|
392
|
+
// If the input is not a token input, or if the tokenID is not the same,
|
|
393
|
+
// then mark the token output as null.
|
|
394
|
+
if (!vinTokenData || !vinTokenIdIsTheSame) {
|
|
395
|
+
thisVin.tokenQty = 0
|
|
396
|
+
thisVin.tokenQtyStr = '0'
|
|
397
|
+
thisVin.tokenId = null
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
355
400
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
361
|
-
console.log(inTokenData)
|
|
362
|
-
throw new Error('Unexpected code path')
|
|
363
|
-
}
|
|
401
|
+
if (vinTokenData.txType === 'SEND') {
|
|
402
|
+
// console.log(
|
|
403
|
+
// `SEND vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
|
|
404
|
+
// )
|
|
364
405
|
|
|
365
|
-
|
|
366
|
-
|
|
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)
|
|
406
|
+
const tokenQty = vinTokenData.amounts[thisVin.vout - 1]
|
|
407
|
+
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
|
373
408
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
409
|
+
// Calculate the real quantity using a BigNumber, then convert it to a
|
|
410
|
+
// floating point number.
|
|
411
|
+
let realQty = new BigNumber(tokenQty).dividedBy(
|
|
412
|
+
10 ** parseInt(txDetails.tokenDecimals)
|
|
413
|
+
)
|
|
414
|
+
realQty = realQty.toString()
|
|
415
|
+
// realQty = parseFloat(realQty)
|
|
377
416
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
417
|
+
thisVin.tokenQtyStr = realQty
|
|
418
|
+
thisVin.tokenQty = parseFloat(realQty)
|
|
419
|
+
thisVin.tokenId = vinTokenData.tokenId
|
|
420
|
+
} else if (vinTokenData.txType === 'MINT') {
|
|
421
|
+
// console.log(
|
|
422
|
+
// `MINT vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
|
|
423
|
+
// )
|
|
424
|
+
|
|
425
|
+
let tokenQty = 0 // Default value
|
|
426
|
+
|
|
427
|
+
// Only vout[1] of a Genesis transaction represents the tokens.
|
|
428
|
+
// Any other outputs in that transaction are normal BCH UTXOs.
|
|
429
|
+
if (thisVin.vout === 1) {
|
|
430
|
+
tokenQty = vinTokenData.qty
|
|
431
|
+
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
|
432
|
+
|
|
433
|
+
// Calculate the real quantity using a BigNumber, then convert it to a
|
|
434
|
+
// floating point number.
|
|
435
|
+
let realQty = new BigNumber(tokenQty).dividedBy(
|
|
436
|
+
10 ** parseInt(txDetails.tokenDecimals)
|
|
437
|
+
)
|
|
438
|
+
realQty = realQty.toString()
|
|
439
|
+
// realQty = parseFloat(realQty)
|
|
440
|
+
|
|
441
|
+
thisVin.tokenQtyStr = realQty
|
|
442
|
+
thisVin.tokenQty = parseFloat(realQty)
|
|
443
|
+
thisVin.tokenId = vinTokenData.tokenId
|
|
444
|
+
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
|
|
445
|
+
// Optional Mint baton
|
|
446
|
+
thisVin.tokenQtyStr = '0'
|
|
447
|
+
thisVin.tokenQty = 0
|
|
448
|
+
thisVin.tokenId = vinTokenData.tokenId
|
|
449
|
+
thisVin.isMintBaton = true
|
|
450
|
+
} else {
|
|
451
|
+
thisVin.tokenQtyStr = '0'
|
|
452
|
+
thisVin.tokenQty = 0
|
|
453
|
+
thisVin.tokenId = null
|
|
391
454
|
}
|
|
455
|
+
} else if (vinTokenData.txType === 'GENESIS') {
|
|
456
|
+
// console.log(
|
|
457
|
+
// `GENESIS vinTokenData ${i}: ${JSON.stringify(
|
|
458
|
+
// vinTokenData,
|
|
459
|
+
// null,
|
|
460
|
+
// 2
|
|
461
|
+
// )}`
|
|
462
|
+
// )
|
|
463
|
+
|
|
464
|
+
let tokenQty = 0 // Default value
|
|
465
|
+
|
|
466
|
+
// Only vout[1] of a Genesis transaction represents the tokens.
|
|
467
|
+
// Any other outputs in that transaction are normal BCH UTXOs.
|
|
468
|
+
if (thisVin.vout === 1) {
|
|
469
|
+
tokenQty = vinTokenData.qty
|
|
470
|
+
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
|
471
|
+
|
|
472
|
+
// Calculate the real quantity using a BigNumber, then convert it to a
|
|
473
|
+
// floating point number.
|
|
474
|
+
let realQty = new BigNumber(tokenQty).dividedBy(
|
|
475
|
+
10 ** parseInt(txDetails.tokenDecimals)
|
|
476
|
+
)
|
|
477
|
+
realQty = realQty.toString()
|
|
478
|
+
// realQty = parseFloat(realQty)
|
|
479
|
+
|
|
480
|
+
thisVin.tokenQtyStr = realQty
|
|
481
|
+
thisVin.tokenQty = parseFloat(realQty)
|
|
482
|
+
thisVin.tokenId = vinTokenData.tokenId
|
|
483
|
+
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
|
|
484
|
+
// Optional Mint baton
|
|
485
|
+
thisVin.tokenQtyStr = '0'
|
|
486
|
+
thisVin.tokenQty = 0
|
|
487
|
+
thisVin.tokenId = vinTokenData.tokenId
|
|
488
|
+
thisVin.isMintBaton = true
|
|
489
|
+
} else {
|
|
490
|
+
thisVin.tokenQtyStr = '0'
|
|
491
|
+
thisVin.tokenQty = 0
|
|
492
|
+
thisVin.tokenId = null
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
console.log(
|
|
496
|
+
`Unknown vinTokenData: ${JSON.stringify(vinTokenData, null, 2)}`
|
|
497
|
+
)
|
|
498
|
+
throw new Error('Unknown token type in input')
|
|
392
499
|
}
|
|
393
|
-
} catch (err) {
|
|
394
|
-
// console.log('Error: ', err)
|
|
395
|
-
|
|
396
|
-
// This case handles rate limit errors.
|
|
397
|
-
if (err.response && err.response.data && err.response.data.error) {
|
|
398
|
-
throw new Error(err.response.data.error)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// If decoding the op_return fails, then it's not an SLP transaction,
|
|
402
|
-
// and the non-hyrated TX details can be returned.
|
|
403
|
-
return txDetails
|
|
404
500
|
}
|
|
405
501
|
|
|
406
502
|
return txDetails
|
|
407
503
|
} catch (err) {
|
|
408
|
-
|
|
504
|
+
console.error('Error in get3()')
|
|
409
505
|
|
|
410
506
|
// This case handles rate limit errors.
|
|
411
507
|
if (err.response && err.response.data && err.response.data.error) {
|
|
@@ -416,6 +512,17 @@ class Transaction {
|
|
|
416
512
|
throw err
|
|
417
513
|
}
|
|
418
514
|
}
|
|
515
|
+
|
|
516
|
+
// A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx.
|
|
517
|
+
// Returns the token data if the txid is an SLP tx.
|
|
518
|
+
async getTokenInfo (txid) {
|
|
519
|
+
try {
|
|
520
|
+
const tokenData = await this.slpUtils.decodeOpReturn(txid)
|
|
521
|
+
return tokenData
|
|
522
|
+
} catch (err) {
|
|
523
|
+
return false
|
|
524
|
+
}
|
|
525
|
+
}
|
|
419
526
|
}
|
|
420
527
|
|
|
421
528
|
module.exports = Transaction
|
package/src/utxo.js
CHANGED
|
@@ -183,6 +183,10 @@ class UTXO {
|
|
|
183
183
|
*/
|
|
184
184
|
async get (address, useWhitelist = false) {
|
|
185
185
|
try {
|
|
186
|
+
if (!address) {
|
|
187
|
+
throw new Error('Address must be an array or a string')
|
|
188
|
+
}
|
|
189
|
+
|
|
186
190
|
// Convert address to an array if it is a string.
|
|
187
191
|
if (typeof address === 'string') address = [address]
|
|
188
192
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const chai = require('chai')
|
|
7
7
|
const assert = chai.assert
|
|
8
|
-
const BCHJS = require('
|
|
8
|
+
const BCHJS = require('../../../../src/bch-js')
|
|
9
9
|
const bchjs = new BCHJS()
|
|
10
10
|
|
|
11
11
|
// Inspect utility used for debugging.
|
|
@@ -81,7 +81,8 @@ describe('#util', () => {
|
|
|
81
81
|
|
|
82
82
|
it('should throw error on array size rate limit', async () => {
|
|
83
83
|
try {
|
|
84
|
-
const dataMock =
|
|
84
|
+
const dataMock =
|
|
85
|
+
'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z'
|
|
85
86
|
const data = []
|
|
86
87
|
for (let i = 0; i < 25; i++) data.push(dataMock)
|
|
87
88
|
|