@psf/bch-js 4.20.28 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "4.20.28",
3
+ "version": "5.1.0",
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": [
@@ -14,12 +14,12 @@
14
14
  "test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
15
15
  "test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
16
16
  "test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
17
- "test:integration:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
17
+ "test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
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/ && 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/",
21
+ "test:integration:decatur:bchn": "export RESTURL=http://192.168.2.139:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
22
+ "test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
23
23
  "test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
24
24
  "test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
25
25
  "test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
package/src/address.js CHANGED
@@ -186,6 +186,21 @@ class Address {
186
186
  return cashAddress.split(':')[1]
187
187
  }
188
188
 
189
+ /**
190
+ * @api Address.toHash160() toHash160()
191
+ * @apiName toHash160
192
+ * @apiGroup Address
193
+ * @apiDescription Converts any address format to hash160
194
+ *
195
+ * @apiExample Example usage:
196
+ * // cash address mainnet p2pkh
197
+ * bchjs.Address.toHash160("bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh")
198
+ * // 573d93b475be4f1925f3b74ed951201b0147eac1
199
+ *
200
+ * // cash address mainnet p2sh
201
+ * bchjs.Address.toHash160("bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug")
202
+ * // 7dc85da64d1d93ef01ef62e0221c02f512e3942f
203
+ */
189
204
  // Converts any address format to hash160
190
205
  toHash160 (address) {
191
206
  const legacyAddress = this.toLegacyAddress(address)
package/src/bch-js.js CHANGED
@@ -33,10 +33,12 @@ const Encryption = require('./encryption')
33
33
  const Utxo = require('./utxo')
34
34
  const Transaction = require('./transaction')
35
35
  const DSProof = require('./dsproof')
36
+ const Ecash = require('./ecash')
36
37
 
37
38
  // Indexers
38
39
  const Ninsight = require('./ninsight')
39
40
  const Electrumx = require('./electrumx')
41
+ const PsfSlpIndexer = require('./psf-slp-indexer')
40
42
 
41
43
  class BCHJS {
42
44
  constructor (config) {
@@ -118,6 +120,9 @@ class BCHJS {
118
120
  this.Transaction = new Transaction(libConfig)
119
121
 
120
122
  this.DSProof = new DSProof(libConfig)
123
+ this.eCash = new Ecash()
124
+
125
+ this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
121
126
  }
122
127
  }
123
128
 
package/src/ecash.js ADDED
@@ -0,0 +1,47 @@
1
+ /*
2
+ Utility library for converting units with eCash.
3
+ */
4
+
5
+ class eCash {
6
+ /**
7
+ * @api eCash.toSatoshi() toSatoshi()
8
+ * @apiName toSatoshi
9
+ * @apiGroup eCash
10
+ * @apiDescription
11
+ * Convert XEC units into satoshi units
12
+ *
13
+ * @apiExample Example usage:
14
+ * // convert 10,704.35 XEC to satoshis:
15
+ * bchjs.eCash.toSatoshi(10704.35)
16
+ * // 1070435
17
+ */
18
+ toSatoshi (xec) {
19
+ if (typeof xec !== 'number') {
20
+ throw new Error('input must be a floating number representing XEC')
21
+ }
22
+
23
+ return Math.floor(xec * 100)
24
+ }
25
+
26
+ /**
27
+ * @api eCash.toXec() toXec()
28
+ * @apiName toXec
29
+ * @apiGroup eCash
30
+ * @apiDescription
31
+ * Convert satoshi units to XEC units
32
+ *
33
+ * @apiExample Example usage:
34
+ * // convert 1,070,435 satoshis to XEC:
35
+ * bchjs.eCash.toSatoshi(1070435)
36
+ * // 10704.35
37
+ */
38
+ toXec (sats) {
39
+ if (typeof sats !== 'number') {
40
+ throw new Error('input must be a floating number representing satoshis')
41
+ }
42
+
43
+ return sats / 100
44
+ }
45
+ }
46
+
47
+ module.exports = eCash
@@ -0,0 +1,332 @@
1
+ /*
2
+ This library interacts with the PSF slp indexer REST API endpoints operated
3
+ by FullStack.cash
4
+
5
+ TODO:
6
+ - detect TXs from tokens in the blacklist.
7
+ */
8
+
9
+ // Public npm libraries
10
+ const axios = require('axios')
11
+
12
+ // Local libraries
13
+ const RawTransaction = require('./raw-transactions')
14
+
15
+ // let _this
16
+
17
+ class PsfSlpIndexer {
18
+ constructor (config) {
19
+ this.restURL = config.restURL
20
+ this.apiToken = config.apiToken
21
+ this.authToken = config.authToken
22
+
23
+ if (this.authToken) {
24
+ // Add Basic Authentication token to the authorization header.
25
+ this.axiosOptions = {
26
+ headers: {
27
+ authorization: this.authToken
28
+ }
29
+ }
30
+ } else {
31
+ // Add JWT token to the authorization header.
32
+ this.axiosOptions = {
33
+ headers: {
34
+ authorization: `Token ${this.apiToken}`
35
+ }
36
+ }
37
+ }
38
+
39
+ // Encapsulate dependencies
40
+ this.rawTransaction = new RawTransaction(config)
41
+
42
+ // _this = this
43
+ }
44
+
45
+ /**
46
+ * @api PsfSlpIndexer.status() status()
47
+ * @apiName Status
48
+ * @apiGroup PSF SLP
49
+ * @apiDescription Return status from psf slp indexer.
50
+ *
51
+ * @apiExample Example usage:
52
+ * (async () => {
53
+ * try {
54
+ * let status = await bchjs.PsfSlpIndexer.status();
55
+ * console.log(status);
56
+ * } catch(error) {
57
+ * console.error(error)
58
+ * }
59
+ * })()
60
+ *
61
+ * {
62
+ * "status": {
63
+ * "startBlockHeight": 543376,
64
+ * "syncedBlockHeight": 723249,
65
+ * "chainBlockHeight": 722679
66
+ * }
67
+ * }
68
+ *
69
+ */
70
+ async status () {
71
+ try {
72
+ const response = await axios.get(
73
+ `${this.restURL}psf/slp/status`,
74
+ this.axiosOptions
75
+ )
76
+ return response.data
77
+ } catch (error) {
78
+ if (error.response && error.response.data) throw error.response.data
79
+ else throw error
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @api PsfSlpIndexer.balance() balance()
85
+ * @apiName SLP Balance
86
+ * @apiGroup PSF SLP
87
+ * @apiDescription Return slp balance for a single address.
88
+ *
89
+ * @apiExample Example usage:
90
+ * (async () => {
91
+ * try {
92
+ * let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
93
+ * console.log(balance);
94
+ * } catch(error) {
95
+ * console.error(error)
96
+ * }
97
+ * })()
98
+ *
99
+ * {
100
+ * balance: {
101
+ * utxos: [
102
+ * {
103
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
104
+ * vout: 1,
105
+ * type: 'token',
106
+ * qty: '1800',
107
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
108
+ * address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
109
+ * }
110
+ * ],
111
+ * txs: [
112
+ * {
113
+ * txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
114
+ * height: 717796
115
+ * },
116
+ * {
117
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
118
+ * height: 717832
119
+ * }
120
+ * ],
121
+ * balances: [
122
+ * {
123
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
124
+ * qty: '1800'
125
+ * }
126
+ * ]
127
+ * }
128
+ * }
129
+ *
130
+ */
131
+ async balance (address) {
132
+ try {
133
+ // Handle single address.
134
+ if (typeof address === 'string') {
135
+ const response = await axios.post(
136
+ `${this.restURL}psf/slp/address`,
137
+ { address },
138
+ this.axiosOptions
139
+ )
140
+ return response.data
141
+ }
142
+ throw new Error('Input address must be a string.')
143
+ } catch (error) {
144
+ if (error.response && error.response.data) throw error.response.data
145
+ else throw error
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @api PsfSlpIndexer.tokenStats() tokenStats()
151
+ * @apiName Token Stats
152
+ * @apiGroup PSF SLP
153
+ * @apiDescription Return list stats for a single slp token.
154
+ *
155
+ * @apiExample Example usage:
156
+ * (async () => {
157
+ * try {
158
+ * let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
159
+ * console.log(tokenStats);
160
+ * } catch(error) {
161
+ * console.error(error)
162
+ * }
163
+ * })()
164
+ *
165
+ * {
166
+ * tokenData: {
167
+ * type: 1,
168
+ * ticker: 'TROUT',
169
+ * name: "Trout's test token",
170
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
171
+ * documentUri: 'troutsblog.com',
172
+ * documentHash: '',
173
+ * decimals: 2,
174
+ * mintBatonIsActive: true,
175
+ * tokensInCirculationBN: '100098953386',
176
+ * tokensInCirculationStr: '100098953386',
177
+ * blockCreated: 622414,
178
+ * totalBurned: '1046614',
179
+ * totalMinted: '100100000000'
180
+ * txs: [
181
+ * {
182
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
183
+ * height: 622414,
184
+ * type: 'GENESIS',
185
+ * qty: '100000000000'
186
+ * }
187
+ * ]
188
+ * }
189
+ * }
190
+ *
191
+ */
192
+
193
+ async tokenStats (tokenId) {
194
+ try {
195
+ // Handle single address.
196
+ if (typeof tokenId === 'string') {
197
+ const response = await axios.post(
198
+ `${this.restURL}psf/slp/token`,
199
+ { tokenId },
200
+ this.axiosOptions
201
+ )
202
+ return response.data
203
+ }
204
+ throw new Error('Input tokenId must be a string.')
205
+ } catch (error) {
206
+ if (error.response && error.response.data) throw error.response.data
207
+ else throw error
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @api PsfSlpIndexer.tx() tx()
213
+ * @apiName SLP Transaction Data
214
+ * @apiGroup PSF SLP
215
+ * @apiDescription Return slp transaction data.
216
+ *
217
+ * @apiExample Example usage:
218
+ * (async () => {
219
+ * try {
220
+ * let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
221
+ * console.log(txData);
222
+ * } catch(error) {
223
+ * console.error(error)
224
+ * }
225
+ * })()
226
+ *
227
+ * {
228
+ * txData: {
229
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
230
+ * hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
231
+ * version: 2,
232
+ * size: 339,
233
+ * locktime: 0,
234
+ * vin: [
235
+ * {
236
+ * txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
237
+ * vout: 0,
238
+ * scriptSig: {
239
+ * asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
240
+ * hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
241
+ * },
242
+ * sequence: 4294967295,
243
+ * address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
244
+ * value: 0.00051303,
245
+ * tokenQty: 0,
246
+ * tokenQtyStr: '0',
247
+ * tokenId: null
248
+ * }
249
+ * ],
250
+ * vout: [
251
+ * {
252
+ * value: 0,
253
+ * n: 0,
254
+ * scriptPubKey: {
255
+ * asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
256
+ * hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
257
+ * type: 'nulldata'
258
+ * },
259
+ * tokenQtyStr: '0',
260
+ * tokenQty: 0
261
+ * }
262
+ * ],
263
+ * hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
264
+ * blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
265
+ * confirmations: 97398,
266
+ * time: 1581773131,
267
+ * blocktime: 1581773131,
268
+ * blockheight: 622414,
269
+ * isSlpTx: true,
270
+ * tokenTxType: 'GENESIS',
271
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
272
+ * tokenType: 1,
273
+ * tokenTicker: 'TROUT',
274
+ * tokenName: "Trout's test token",
275
+ * tokenDecimals: 2,
276
+ * tokenUri: 'troutsblog.com',
277
+ * tokenDocHash: '',
278
+ * isValidSlp: true
279
+ * }
280
+ * }
281
+ *
282
+ */
283
+ async tx (txid) {
284
+ try {
285
+ // Handle single address.
286
+ if (typeof txid === 'string') {
287
+ const response = await axios.post(
288
+ `${this.restURL}psf/slp/txid`,
289
+ { txid },
290
+ this.axiosOptions
291
+ )
292
+ return response.data
293
+ }
294
+ throw new Error('Input txid must be a string.')
295
+ } catch (error) {
296
+ // console.log('error: ', error)
297
+
298
+ // Case: txid is not stored in the psf-slp-indexer tx database.
299
+ // Response: If it's not in the database, then it can be assumed the TX
300
+ // is not a token TX?
301
+ if (
302
+ error.response &&
303
+ error.response.data &&
304
+ error.response.data.error &&
305
+ error.response.data.error.includes('Key not found in database')
306
+ ) {
307
+ // console.log(
308
+ // 'TX not found in psf-slp-indexer. Retrieving from full node.'
309
+ // )
310
+
311
+ // Get the TX Details from the full node.
312
+ const txDetails = await this.rawTransaction.getTxData(txid)
313
+ // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
314
+
315
+ // TODO: Run the TX through decodeOpReturn() to see if it has a tokenID
316
+ // that is in the blacklist. If it is, then set isValidSlp = null to
317
+ // signal that it's state can not be determined.
318
+
319
+ // Assumption: transaction is not a valid SLP UTXO.
320
+ txDetails.isValidSlp = false
321
+
322
+ const outObj = {
323
+ txData: txDetails
324
+ }
325
+
326
+ return outObj
327
+ } else throw error
328
+ }
329
+ }
330
+ }
331
+
332
+ module.exports = PsfSlpIndexer
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
36
36
  * bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
37
37
  * // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
38
38
  *
39
- * // tesnet legacy
39
+ * // testnet legacy
40
40
  * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
41
41
  * // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
42
42
  *
@@ -2,18 +2,27 @@
2
2
  High-level functions for working with Transactions
3
3
  */
4
4
 
5
+ // Global npm libraries
5
6
  const BigNumber = require('bignumber.js')
6
7
 
8
+ // Local libraries
7
9
  const RawTransaction = require('./raw-transactions')
8
10
  const SlpUtils = require('./slp/utils')
9
11
  const Blockchain = require('./blockchain')
12
+ const PsfSlpIndexer = require('./psf-slp-indexer')
10
13
 
11
14
  class Transaction {
12
- constructor (config) {
15
+ constructor (config = {}) {
13
16
  // Encapsulate dependencies
14
17
  this.slpUtils = new SlpUtils(config)
15
18
  this.rawTransaction = new RawTransaction(config)
16
19
  this.blockchain = new Blockchain(config)
20
+ this.psfSlpIndexer = new PsfSlpIndexer(config)
21
+ }
22
+
23
+ // Proxy the call to the psf-slp-indexer.
24
+ async get (txid) {
25
+ return await this.psfSlpIndexer.tx(txid)
17
26
  }
18
27
 
19
28
  /**
@@ -42,7 +51,7 @@ class Transaction {
42
51
  // CT 10/31/21: TODO: this function should be refactored to use get2(), but
43
52
  // add waterfall validation of the TX and its inputs.
44
53
 
45
- async get (txid) {
54
+ async getOld (txid) {
46
55
  try {
47
56
  if (typeof txid !== 'string') {
48
57
  throw new Error(
@@ -313,6 +313,14 @@ describe('#blockchain', () => {
313
313
  assert.isString(result)
314
314
  })
315
315
  })
316
+
317
+ describe('#getBlockchainInfo', () => {
318
+ it('should get info about the blockchain', async () => {
319
+ const result = await bchjs.Blockchain.getBlockchainInfo()
320
+
321
+ console.log(`blockchain info: ${JSON.stringify(result, null, 2)}`)
322
+ })
323
+ })
316
324
  })
317
325
 
318
326
  function sleep (ms) {
@@ -0,0 +1,105 @@
1
+ /*
2
+ Integration tests for the psf-slp-indexer.js library
3
+ */
4
+
5
+ const assert = require('chai').assert
6
+
7
+ const BCHJS = require('../../../../src/bch-js')
8
+ let bchjs
9
+
10
+ describe('#psf-slp-indexer', () => {
11
+ beforeEach(async () => {
12
+ // Introduce a delay so that the BVT doesn't trip the rate limits.
13
+ if (process.env.IS_USING_FREE_TIER) await sleep(3000)
14
+
15
+ bchjs = new BCHJS()
16
+ })
17
+
18
+ describe('#status', () => {
19
+ it('should return the status of the indexer.', async () => {
20
+ const result = await bchjs.PsfSlpIndexer.status()
21
+ // console.log('result: ', result)
22
+
23
+ assert.property(result.status, 'startBlockHeight')
24
+ assert.property(result.status, 'syncedBlockHeight')
25
+ assert.property(result.status, 'chainBlockHeight')
26
+ })
27
+ })
28
+
29
+ describe('#balance', () => {
30
+ it('should get balance data for an address.', async () => {
31
+ const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
32
+
33
+ const result = await bchjs.PsfSlpIndexer.balance(addr)
34
+ // console.log('result: ', result)
35
+
36
+ assert.property(result.balance, 'utxos')
37
+ assert.property(result.balance, 'txs')
38
+ assert.property(result.balance, 'balances')
39
+ })
40
+ })
41
+
42
+ describe('#tokenStats', () => {
43
+ it('should get stats on a token', async () => {
44
+ const tokenId =
45
+ '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
46
+
47
+ const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
48
+ // console.log('result: ', result)
49
+
50
+ assert.property(result.tokenData, 'documentUri')
51
+ assert.property(result.tokenData, 'txs')
52
+ assert.property(result.tokenData, 'totalBurned')
53
+ })
54
+ })
55
+
56
+ describe('#tx', () => {
57
+ it('should get hydrated tx data for an SLP transaction', async () => {
58
+ const txid =
59
+ '83361c34cac2ea7f9ca287fca57a96cc0763719f0cdf4850f9696c1e68eb635c'
60
+
61
+ const result = await bchjs.PsfSlpIndexer.tx(txid)
62
+ // console.log('result: ', result)
63
+
64
+ assert.property(result.txData, 'vin')
65
+ assert.property(result.txData, 'vout')
66
+ assert.property(result.txData, 'isValidSlp')
67
+ assert.equal(result.txData.isValidSlp, true)
68
+ })
69
+
70
+ it('should handle non-SLP transaction', async () => {
71
+ const txid =
72
+ '03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
73
+
74
+ const result = await bchjs.PsfSlpIndexer.tx(txid)
75
+ // console.log('result: ', result)
76
+
77
+ assert.property(result.txData, 'vin')
78
+ assert.property(result.txData, 'vout')
79
+ assert.property(result.txData, 'isValidSlp')
80
+ assert.equal(result.txData.isValidSlp, false)
81
+ })
82
+
83
+ // FlexUSD transactions
84
+ // Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means
85
+ // the wallet will burn them. There is a TODO in the code. This test will
86
+ // need to be changed when it is done.
87
+ it('should handle a blacklisted token', async () => {
88
+ const txid =
89
+ '302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
90
+
91
+ const result = await bchjs.PsfSlpIndexer.tx(txid)
92
+ // console.log('result: ', result)
93
+
94
+ assert.property(result.txData, 'vin')
95
+ assert.property(result.txData, 'vout')
96
+ assert.property(result.txData, 'isValidSlp')
97
+ assert.equal(result.txData.isValidSlp, false)
98
+ })
99
+ })
100
+ })
101
+
102
+ // Promise-based sleep function
103
+ function sleep (ms) {
104
+ return new Promise(resolve => setTimeout(resolve, ms))
105
+ }