@psf/bch-js 4.21.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "4.21.0",
3
+ "version": "5.2.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,11 +14,11 @@
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.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
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
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/",
package/src/bch-js.js CHANGED
@@ -36,8 +36,8 @@ const DSProof = require('./dsproof')
36
36
  const Ecash = require('./ecash')
37
37
 
38
38
  // Indexers
39
- const Ninsight = require('./ninsight')
40
39
  const Electrumx = require('./electrumx')
40
+ const PsfSlpIndexer = require('./psf-slp-indexer')
41
41
 
42
42
  class BCHJS {
43
43
  constructor (config) {
@@ -82,9 +82,6 @@ class BCHJS {
82
82
 
83
83
  // console.log(`apiToken: ${this.apiToken}`)
84
84
 
85
- // Bitcoin.com Ninsight indexer
86
- this.Ninsight = new Ninsight(config)
87
-
88
85
  // ElectrumX indexer
89
86
  this.Electrumx = new Electrumx(libConfig)
90
87
 
@@ -120,6 +117,8 @@ class BCHJS {
120
117
 
121
118
  this.DSProof = new DSProof(libConfig)
122
119
  this.eCash = new Ecash()
120
+
121
+ this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
123
122
  }
124
123
  }
125
124
 
@@ -0,0 +1,370 @@
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
+ const SlpUtils = require('./slp/utils')
15
+
16
+ // let _this
17
+
18
+ class PsfSlpIndexer {
19
+ constructor (config = {}) {
20
+ this.restURL = config.restURL
21
+ this.apiToken = config.apiToken
22
+ this.authToken = config.authToken
23
+
24
+ if (this.authToken) {
25
+ // Add Basic Authentication token to the authorization header.
26
+ this.axiosOptions = {
27
+ headers: {
28
+ authorization: this.authToken
29
+ }
30
+ }
31
+ } else {
32
+ // Add JWT token to the authorization header.
33
+ this.axiosOptions = {
34
+ headers: {
35
+ authorization: `Token ${this.apiToken}`
36
+ }
37
+ }
38
+ }
39
+
40
+ // Encapsulate dependencies
41
+ this.rawTransaction = new RawTransaction(config)
42
+ this.slpUtils = new SlpUtils(config)
43
+
44
+ // _this = this
45
+ }
46
+
47
+ /**
48
+ * @api PsfSlpIndexer.status() status()
49
+ * @apiName Status
50
+ * @apiGroup PSF SLP
51
+ * @apiDescription Return status from psf slp indexer.
52
+ *
53
+ * @apiExample Example usage:
54
+ * (async () => {
55
+ * try {
56
+ * let status = await bchjs.PsfSlpIndexer.status();
57
+ * console.log(status);
58
+ * } catch(error) {
59
+ * console.error(error)
60
+ * }
61
+ * })()
62
+ *
63
+ * {
64
+ * "status": {
65
+ * "startBlockHeight": 543376,
66
+ * "syncedBlockHeight": 723249,
67
+ * "chainBlockHeight": 722679
68
+ * }
69
+ * }
70
+ *
71
+ */
72
+ async status () {
73
+ try {
74
+ const response = await axios.get(
75
+ `${this.restURL}psf/slp/status`,
76
+ this.axiosOptions
77
+ )
78
+ return response.data
79
+ } catch (error) {
80
+ if (error.response && error.response.data) throw error.response.data
81
+ else throw error
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @api PsfSlpIndexer.balance() balance()
87
+ * @apiName SLP Balance
88
+ * @apiGroup PSF SLP
89
+ * @apiDescription Return slp balance for a single address.
90
+ *
91
+ * @apiExample Example usage:
92
+ * (async () => {
93
+ * try {
94
+ * let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
95
+ * console.log(balance);
96
+ * } catch(error) {
97
+ * console.error(error)
98
+ * }
99
+ * })()
100
+ *
101
+ * {
102
+ * balance: {
103
+ * utxos: [
104
+ * {
105
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
106
+ * vout: 1,
107
+ * type: 'token',
108
+ * qty: '1800',
109
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
110
+ * address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
111
+ * }
112
+ * ],
113
+ * txs: [
114
+ * {
115
+ * txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
116
+ * height: 717796
117
+ * },
118
+ * {
119
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
120
+ * height: 717832
121
+ * }
122
+ * ],
123
+ * balances: [
124
+ * {
125
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
126
+ * qty: '1800'
127
+ * }
128
+ * ]
129
+ * }
130
+ * }
131
+ *
132
+ */
133
+ async balance (address) {
134
+ try {
135
+ console.log('balance() address: ', address)
136
+ // Handle single address.
137
+ if (typeof address === 'string') {
138
+ const response = await axios.post(
139
+ `${this.restURL}psf/slp/address`,
140
+ { address },
141
+ this.axiosOptions
142
+ )
143
+ return response.data
144
+ }
145
+ throw new Error('Input address must be a string.')
146
+ } catch (error) {
147
+ if (error.response && error.response.data) throw error.response.data
148
+ else throw error
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @api PsfSlpIndexer.tokenStats() tokenStats()
154
+ * @apiName Token Stats
155
+ * @apiGroup PSF SLP
156
+ * @apiDescription Return list stats for a single slp token.
157
+ *
158
+ * @apiExample Example usage:
159
+ * (async () => {
160
+ * try {
161
+ * let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
162
+ * console.log(tokenStats);
163
+ * } catch(error) {
164
+ * console.error(error)
165
+ * }
166
+ * })()
167
+ *
168
+ * {
169
+ * tokenData: {
170
+ * type: 1,
171
+ * ticker: 'TROUT',
172
+ * name: "Trout's test token",
173
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
174
+ * documentUri: 'troutsblog.com',
175
+ * documentHash: '',
176
+ * decimals: 2,
177
+ * mintBatonIsActive: true,
178
+ * tokensInCirculationBN: '100098953386',
179
+ * tokensInCirculationStr: '100098953386',
180
+ * blockCreated: 622414,
181
+ * totalBurned: '1046614',
182
+ * totalMinted: '100100000000'
183
+ * txs: [
184
+ * {
185
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
186
+ * height: 622414,
187
+ * type: 'GENESIS',
188
+ * qty: '100000000000'
189
+ * }
190
+ * ]
191
+ * }
192
+ * }
193
+ *
194
+ */
195
+
196
+ async tokenStats (tokenId) {
197
+ try {
198
+ // Handle single address.
199
+ if (typeof tokenId === 'string') {
200
+ const response = await axios.post(
201
+ `${this.restURL}psf/slp/token`,
202
+ { tokenId },
203
+ this.axiosOptions
204
+ )
205
+ return response.data
206
+ }
207
+ throw new Error('Input tokenId must be a string.')
208
+ } catch (error) {
209
+ if (error.response && error.response.data) throw error.response.data
210
+ else throw error
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @api PsfSlpIndexer.tx() tx()
216
+ * @apiName SLP Transaction Data
217
+ * @apiGroup PSF SLP
218
+ * @apiDescription Return slp transaction data.
219
+ *
220
+ * @apiExample Example usage:
221
+ * (async () => {
222
+ * try {
223
+ * let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
224
+ * console.log(txData);
225
+ * } catch(error) {
226
+ * console.error(error)
227
+ * }
228
+ * })()
229
+ *
230
+ * {
231
+ * txData: {
232
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
233
+ * hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
234
+ * version: 2,
235
+ * size: 339,
236
+ * locktime: 0,
237
+ * vin: [
238
+ * {
239
+ * txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
240
+ * vout: 0,
241
+ * scriptSig: {
242
+ * asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
243
+ * hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
244
+ * },
245
+ * sequence: 4294967295,
246
+ * address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
247
+ * value: 0.00051303,
248
+ * tokenQty: 0,
249
+ * tokenQtyStr: '0',
250
+ * tokenId: null
251
+ * }
252
+ * ],
253
+ * vout: [
254
+ * {
255
+ * value: 0,
256
+ * n: 0,
257
+ * scriptPubKey: {
258
+ * asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
259
+ * hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
260
+ * type: 'nulldata'
261
+ * },
262
+ * tokenQtyStr: '0',
263
+ * tokenQty: 0
264
+ * }
265
+ * ],
266
+ * hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
267
+ * blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
268
+ * confirmations: 97398,
269
+ * time: 1581773131,
270
+ * blocktime: 1581773131,
271
+ * blockheight: 622414,
272
+ * isSlpTx: true,
273
+ * tokenTxType: 'GENESIS',
274
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
275
+ * tokenType: 1,
276
+ * tokenTicker: 'TROUT',
277
+ * tokenName: "Trout's test token",
278
+ * tokenDecimals: 2,
279
+ * tokenUri: 'troutsblog.com',
280
+ * tokenDocHash: '',
281
+ * isValidSlp: true
282
+ * }
283
+ * }
284
+ *
285
+ */
286
+ async tx (txid) {
287
+ try {
288
+ // Handle single address.
289
+ if (typeof txid === 'string') {
290
+ const response = await axios.post(
291
+ `${this.restURL}psf/slp/txid`,
292
+ { txid },
293
+ this.axiosOptions
294
+ )
295
+ return response.data
296
+ }
297
+ throw new Error('Input txid must be a string.')
298
+ } catch (error) {
299
+ // console.log('error: ', error)
300
+
301
+ // Case: txid is not stored in the psf-slp-indexer tx database.
302
+ // Response: If it's not in the database, then it can be assumed the TX
303
+ // is not a token TX?
304
+ if (
305
+ error.response &&
306
+ error.response.data &&
307
+ error.response.data.error &&
308
+ error.response.data.error.includes('Key not found in database')
309
+ ) {
310
+ // console.log(
311
+ // 'TX not found in psf-slp-indexer. Retrieving from full node.'
312
+ // )
313
+
314
+ // Check if this txid belongs to a blacklisted token.
315
+ const isInBlacklist = await this.checkBlacklist(txid)
316
+
317
+ // Get the TX Details from the full node.
318
+ const txDetails = await this.rawTransaction.getTxData(txid)
319
+ // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
320
+
321
+ if (isInBlacklist) {
322
+ txDetails.isValidSlp = null
323
+ } else {
324
+ txDetails.isValidSlp = false
325
+ }
326
+
327
+ const outObj = {
328
+ txData: txDetails
329
+ }
330
+
331
+ return outObj
332
+ } else throw error
333
+ }
334
+ }
335
+
336
+ // Check if the txid has an OP_RETURN containing a tokenID that is in the
337
+ // blacklist. In that case, the isValidSlp property should be marked as
338
+ // null, and not false.
339
+ async checkBlacklist (txid) {
340
+ try {
341
+ // TODO: Add endpoint to psf-slp-indexer to retrieve current blacklist.
342
+ // This should be done once at startup, and not each time this function
343
+ // is called.
344
+ const blacklist = [
345
+ 'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9'
346
+ ]
347
+
348
+ const outTokenData = await this.slpUtils.decodeOpReturn(txid)
349
+ // console.log('outTokenData: ', outTokenData)
350
+
351
+ // Loop through each token in the blacklist.
352
+ for (let i = 0; i < blacklist.length; i++) {
353
+ // If a match is found, return true.
354
+ if (outTokenData.tokenId === blacklist[i]) {
355
+ return true
356
+ }
357
+ }
358
+
359
+ // By default, return false.
360
+ return false
361
+ } catch (err) {
362
+ // console.log(err)
363
+
364
+ // Exit quietly.
365
+ return false
366
+ }
367
+ }
368
+ }
369
+
370
+ module.exports = PsfSlpIndexer
package/src/slp/utils.js CHANGED
@@ -11,7 +11,7 @@ const Util = require('../util')
11
11
  let _this
12
12
 
13
13
  class Utils {
14
- constructor (config) {
14
+ constructor (config = {}) {
15
15
  this.restURL = config.restURL
16
16
  this.apiToken = config.apiToken
17
17
  this.slpParser = slpParser
@@ -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(
package/src/utxo.js CHANGED
@@ -8,19 +8,21 @@
8
8
  // Local libraries
9
9
  const Electrumx = require('./electrumx')
10
10
  const Slp = require('./slp/slp')
11
+ const PsfSlpIndexer = require('./psf-slp-indexer')
11
12
 
12
13
  class UTXO {
13
- constructor (config) {
14
+ constructor (config = {}) {
14
15
  // Encapsulate dependencies for easier mocking.
15
16
  this.electrumx = new Electrumx(config)
16
17
  this.slp = new Slp(config)
18
+ this.psfSlpIndexer = new PsfSlpIndexer(config)
17
19
  }
18
20
 
19
21
  /**
20
- * @api Utxo.get() get()
21
- * @apiName get
22
+ * @api Utxo.getOld() getOld()
23
+ * @apiName getOld
22
24
  * @apiGroup UTXO
23
- * @apiDescription Get UTXOs for an address
25
+ * @apiDescription Get UTXOs for an address (from SLPDB)
24
26
  *
25
27
  * Given an address, this function will return an object with thre following
26
28
  * properties:
@@ -45,7 +47,7 @@ class UTXO {
45
47
  * @apiExample Example usage:
46
48
  * (async () => {
47
49
  * try {
48
- * let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
50
+ * let utxos = await bchjs.Utxo.getOld('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
49
51
  * console.log(utxos);
50
52
  * } catch(error) {
51
53
  * console.error(error)
@@ -181,7 +183,7 @@ class UTXO {
181
183
  *
182
184
  *
183
185
  */
184
- async get (address, useWhitelist = false) {
186
+ async getOld (address, useWhitelist = false) {
185
187
  try {
186
188
  if (!address) {
187
189
  throw new Error('Address must be an array or a string')
@@ -286,6 +288,148 @@ class UTXO {
286
288
  }
287
289
  }
288
290
 
291
+ /**
292
+ * @api Utxo.get() get()
293
+ * @apiName get
294
+ * @apiGroup UTXO
295
+ * @apiDescription Get UTXOs for an address (from psf-slp-indexer)
296
+ *
297
+ * Given an address, this function will return an object with thre following
298
+ * properties:
299
+ * - address: "" - the address these UTXOs are associated with
300
+ * - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
301
+ * - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
302
+ * not spent, to be safe.
303
+ * - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
304
+ * - type1: {}
305
+ * - tokens: [] - SLP token Type 1 tokens.
306
+ * - mintBatons: [] - SLP token Type 1 mint batons.
307
+ * - nft: {}
308
+ * - tokens: [] - NFT tokens
309
+ * - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
310
+ * - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
311
+ *
312
+ * Note: You can pass in an optional second Boolean argument. The default
313
+ * `false` will use the normal waterfall validation method. Set to `true`,
314
+ * SLP UTXOs will be validated with the whitelist filtered SLPDB. This will
315
+ * result is many more UTXOs in the `nullUtxos` array.
316
+ *
317
+ * @apiExample Example usage:
318
+ * (async () => {
319
+ * try {
320
+ * let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
321
+ * console.log(utxos);
322
+ * } catch(error) {
323
+ * console.error(error)
324
+ * }
325
+ * })()
326
+ *
327
+ * // returns
328
+ * [
329
+ * {
330
+ * "address": "bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv",
331
+ * "bchUtxos": [
332
+ * {
333
+ * "height": 674513,
334
+ * "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
335
+ * "tx_pos": 3,
336
+ * "value": 38134,
337
+ * "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
338
+ * "vout": 3,
339
+ * "isValid": false
340
+ * }
341
+ * ],
342
+ */
343
+ // This version of get() uses the psf-slp-indexer. It will replace the older
344
+ // get() function that uses SLPDB.
345
+ // TODO: NFT UTXOs are identified as non-token UTXOs, which will cause a wallet
346
+ // to burn them. The psf-slp-indexer needs to be updated to mark these UTXOs.
347
+ async get (address) {
348
+ try {
349
+ // Convert address to an array if it is a string.
350
+ if (typeof address !== 'string') {
351
+ throw new Error('address input must be a string')
352
+ }
353
+
354
+ // Ensure the address is a BCH address.
355
+ const addr = this.slp.Address.toCashAddress(address)
356
+
357
+ // Get the UTXOs associated with the address.
358
+ const utxoData = await this.electrumx.utxo(addr)
359
+ // console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
360
+ const utxos = utxoData.utxos
361
+
362
+ // Get SLP UTXOs from the psf-slp-indexer
363
+ const slpUtxoData = await this.psfSlpIndexer.balance(addr)
364
+ // console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
365
+ const slpUtxos = slpUtxoData.balance.utxos
366
+
367
+ // Loop through the Fulcrum UTXOs.
368
+ for (let i = 0; i < utxos.length; i++) {
369
+ const thisUtxo = utxos[i]
370
+
371
+ // Loop through the UTXOs from psf-slp-indexer.
372
+ for (let j = 0; j < slpUtxos.length; j++) {
373
+ const thisSlpUtxo = slpUtxos[j]
374
+
375
+ // If the non-hydrated UTXO matches the SLP UTXO, then combine the data
376
+ // and mark the UTXO as an SLP token.
377
+ if (
378
+ thisUtxo.tx_hash === thisSlpUtxo.txid &&
379
+ thisUtxo.tx_pos === thisSlpUtxo.vout
380
+ ) {
381
+ thisUtxo.txid = thisUtxo.tx_hash
382
+ thisUtxo.vout = thisUtxo.tx_pos
383
+ thisUtxo.isSlp = true
384
+ thisUtxo.type = thisSlpUtxo.type
385
+ thisUtxo.qty = thisSlpUtxo.qty
386
+ thisUtxo.tokenId = thisSlpUtxo.tokenId
387
+ thisUtxo.address = thisSlpUtxo.address
388
+
389
+ break
390
+ }
391
+ }
392
+
393
+ // If there was no match, then this is a normal BCH UTXO. Mark it as such.
394
+ if (!thisUtxo.isSlp) {
395
+ thisUtxo.txid = thisUtxo.tx_hash
396
+ thisUtxo.vout = thisUtxo.tx_pos
397
+ thisUtxo.isSlp = false
398
+ thisUtxo.address = addr
399
+ }
400
+ }
401
+
402
+ const bchUtxos = utxos.filter(x => x.isSlp === false)
403
+ const type1TokenUtxos = utxos.filter(
404
+ x => x.isSlp === true && x.type === 'token'
405
+ )
406
+ const type1BatonUtxos = utxos.filter(
407
+ x => x.isSlp === true && x.type === 'baton'
408
+ )
409
+ const nullUtxos = utxos.filter(x => x.isSlp === null)
410
+
411
+ const outObj = {
412
+ address: addr,
413
+ bchUtxos,
414
+ slpUtxos: {
415
+ type1: {
416
+ tokens: type1TokenUtxos,
417
+ mintBatons: type1BatonUtxos
418
+ },
419
+ nft: {} // Allocated for future support of NFT spec.
420
+ },
421
+ nullUtxos
422
+ }
423
+
424
+ return outObj
425
+ } catch (err) {
426
+ // console.error('Error in bchjs.utxo.get2(): ', err)
427
+
428
+ if (err.error) throw new Error(err.error)
429
+ throw err
430
+ }
431
+ }
432
+
289
433
  /**
290
434
  * @api Utxo.findBiggestUtxo() findBiggestUtxo()
291
435
  * @apiName findBiggestUtxo