@psf/bch-js 5.4.1 → 6.2.1
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/.on-save.json +8 -0
- package/LICENSE.md +1 -2
- package/package.json +6 -6
- package/src/slp/utils.js +0 -1762
- package/src/transaction.js +9 -467
- package/src/utxo.js +0 -270
- package/test/integration/slp.js +0 -470
- package/test/unit/slp-utils.js +0 -2105
- package/test/unit/transaction-unit.js +4 -688
- package/test/unit/utxo-unit.js +0 -131
package/src/slp/utils.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// Public npm libraries
|
|
4
4
|
const axios = require('axios')
|
|
5
5
|
const slpParser = require('slp-parser')
|
|
6
|
-
const BigNumber = require('bignumber.js')
|
|
7
6
|
|
|
8
7
|
// Local libraries
|
|
9
8
|
const Util = require('../util')
|
|
@@ -41,863 +40,6 @@ class Utils {
|
|
|
41
40
|
this.util = new Util(config)
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
/**
|
|
45
|
-
* @api SLP.Utils.list() list()
|
|
46
|
-
* @apiName list
|
|
47
|
-
* @apiGroup SLP Utils
|
|
48
|
-
* @apiDescription List all tokens or list single token by id.
|
|
49
|
-
*
|
|
50
|
-
* @apiExample Example usage:
|
|
51
|
-
*
|
|
52
|
-
* // List all tokens
|
|
53
|
-
*
|
|
54
|
-
* (async () => {
|
|
55
|
-
* try {
|
|
56
|
-
* let list = await bchjs.SLP.Utils.list();
|
|
57
|
-
* console.log(list);
|
|
58
|
-
* } catch (error) {
|
|
59
|
-
* console.error(error);
|
|
60
|
-
* }
|
|
61
|
-
* })();
|
|
62
|
-
*
|
|
63
|
-
* // returns
|
|
64
|
-
* [ { decimals: 5,
|
|
65
|
-
* timestamp: '2019-04-20 05:03',
|
|
66
|
-
* versionType: 1,
|
|
67
|
-
* documentUri: 'developer.bitcoin.com',
|
|
68
|
-
* symbol: 'MYSTERY',
|
|
69
|
-
* name: 'Mystery',
|
|
70
|
-
* containsBaton: true,
|
|
71
|
-
* id:
|
|
72
|
-
* '10528f22fc20422f7c1075a87ed7270c0a17bc17ea79c6e2f426c6cc14bb25f2',
|
|
73
|
-
* documentHash:
|
|
74
|
-
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
75
|
-
* initialTokenQty: 500,
|
|
76
|
-
* blockCreated: 579041,
|
|
77
|
-
* blockLastActiveSend: null,
|
|
78
|
-
* blockLastActiveMint: null,
|
|
79
|
-
* txnsSinceGenesis: 1,
|
|
80
|
-
* validAddresses: 1,
|
|
81
|
-
* totalMinted: 500,
|
|
82
|
-
* totalBurned: 0,
|
|
83
|
-
* circulatingSupply: 500,
|
|
84
|
-
* mintingBatonStatus: 'ALIVE' },
|
|
85
|
-
* { decimals: 8,
|
|
86
|
-
* timestamp: '2019-04-20 04:54',
|
|
87
|
-
* versionType: 1,
|
|
88
|
-
* documentUri: 'developer.bitcoin.com',
|
|
89
|
-
* symbol: 'ENIGMA',
|
|
90
|
-
* name: 'Enigma',
|
|
91
|
-
* containsBaton: true,
|
|
92
|
-
* id:
|
|
93
|
-
* '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
|
|
94
|
-
* documentHash:
|
|
95
|
-
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
96
|
-
* initialTokenQty: 1234,
|
|
97
|
-
* blockCreated: 579040,
|
|
98
|
-
* blockLastActiveSend: null,
|
|
99
|
-
* blockLastActiveMint: 579040,
|
|
100
|
-
* txnsSinceGenesis: 2,
|
|
101
|
-
* validAddresses: 2,
|
|
102
|
-
* totalMinted: 1334,
|
|
103
|
-
* totalBurned: 0,
|
|
104
|
-
* circulatingSupply: 1334,
|
|
105
|
-
* mintingBatonStatus: 'ALIVE' }
|
|
106
|
-
* ]
|
|
107
|
-
*
|
|
108
|
-
* // List single token
|
|
109
|
-
*
|
|
110
|
-
* (async () => {
|
|
111
|
-
* try {
|
|
112
|
-
* let list = await bchjs.SLP.Utils.list(
|
|
113
|
-
* "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313"
|
|
114
|
-
* );
|
|
115
|
-
* console.log(list);
|
|
116
|
-
* } catch (error) {
|
|
117
|
-
* console.error(error);
|
|
118
|
-
* }
|
|
119
|
-
* })();
|
|
120
|
-
*
|
|
121
|
-
* // returns
|
|
122
|
-
* { decimals: 8,
|
|
123
|
-
* timestamp: '2019-04-20 04:54',
|
|
124
|
-
* versionType: 1,
|
|
125
|
-
* documentUri: 'developer.bitcoin.com',
|
|
126
|
-
* symbol: 'ENIGMA',
|
|
127
|
-
* name: 'Enigma',
|
|
128
|
-
* containsBaton: true,
|
|
129
|
-
* id:
|
|
130
|
-
* '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
|
|
131
|
-
* documentHash:
|
|
132
|
-
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
133
|
-
* initialTokenQty: 1234,
|
|
134
|
-
* blockCreated: 579040,
|
|
135
|
-
* blockLastActiveSend: null,
|
|
136
|
-
* blockLastActiveMint: 579040,
|
|
137
|
-
* txnsSinceGenesis: 2,
|
|
138
|
-
* validAddresses: 2,
|
|
139
|
-
* totalMinted: 1334,
|
|
140
|
-
* totalBurned: 0,
|
|
141
|
-
* circulatingSupply: 1334,
|
|
142
|
-
* mintingBatonStatus: 'ALIVE' }
|
|
143
|
-
*
|
|
144
|
-
* // List multiple tokens by tokenIds
|
|
145
|
-
*
|
|
146
|
-
* (async () => {
|
|
147
|
-
* try {
|
|
148
|
-
* let list = await bchjs.SLP.Utils.list([
|
|
149
|
-
* "fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f",
|
|
150
|
-
* "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0"
|
|
151
|
-
* ]);
|
|
152
|
-
* console.log(list);
|
|
153
|
-
* } catch (error) {
|
|
154
|
-
* console.error(error);
|
|
155
|
-
* }
|
|
156
|
-
* })();
|
|
157
|
-
*
|
|
158
|
-
* // returns
|
|
159
|
-
* [ { decimals: 0,
|
|
160
|
-
* timestamp: '2019-02-18 14:47',
|
|
161
|
-
* versionType: 1,
|
|
162
|
-
* documentUri: 'https://simpleledger.cash',
|
|
163
|
-
* symbol: 'SLP',
|
|
164
|
-
* name: 'Official SLP Token',
|
|
165
|
-
* containsBaton: true,
|
|
166
|
-
* id:
|
|
167
|
-
* 'fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f',
|
|
168
|
-
* documentHash: null,
|
|
169
|
-
* initialTokenQty: 18446744073709552000,
|
|
170
|
-
* blockCreated: 570305,
|
|
171
|
-
* blockLastActiveSend: 580275,
|
|
172
|
-
* blockLastActiveMint: 575914,
|
|
173
|
-
* txnsSinceGenesis: 4537,
|
|
174
|
-
* validAddresses: 164,
|
|
175
|
-
* totalMinted: 19414628793626410000,
|
|
176
|
-
* totalBurned: 18446568350267302000,
|
|
177
|
-
* circulatingSupply: 968060443359109600,
|
|
178
|
-
* mintingBatonStatus: 'ALIVE' },
|
|
179
|
-
* { decimals: 8,
|
|
180
|
-
* timestamp: '2019-02-14 03:11',
|
|
181
|
-
* versionType: 1,
|
|
182
|
-
* documentUri: 'psfoundation.cash',
|
|
183
|
-
* symbol: 'PSF',
|
|
184
|
-
* name: 'Permissionless Software Foundation',
|
|
185
|
-
* containsBaton: true,
|
|
186
|
-
* id:
|
|
187
|
-
* '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
|
188
|
-
* documentHash: null,
|
|
189
|
-
* initialTokenQty: 19882.09163133,
|
|
190
|
-
* blockCreated: 569658,
|
|
191
|
-
* blockLastActiveSend: 580153,
|
|
192
|
-
* blockLastActiveMint: null,
|
|
193
|
-
* txnsSinceGenesis: 51,
|
|
194
|
-
* validAddresses: 9,
|
|
195
|
-
* totalMinted: 19882.09163133,
|
|
196
|
-
* totalBurned: 0.0534241,
|
|
197
|
-
* circulatingSupply: 19882.03820723,
|
|
198
|
-
* mintingBatonStatus: 'ALIVE' } ]
|
|
199
|
-
*/
|
|
200
|
-
async list (id) {
|
|
201
|
-
let path
|
|
202
|
-
let method
|
|
203
|
-
|
|
204
|
-
if (!id) {
|
|
205
|
-
method = 'get'
|
|
206
|
-
path = `${this.restURL}slp/list`
|
|
207
|
-
} else if (typeof id === 'string') {
|
|
208
|
-
method = 'get'
|
|
209
|
-
path = `${this.restURL}slp/list/${id}`
|
|
210
|
-
} else if (typeof id === 'object') {
|
|
211
|
-
method = 'post'
|
|
212
|
-
path = `${this.restURL}slp/list`
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// console.log(`path: ${path}`)
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
let response
|
|
219
|
-
if (method === 'get') {
|
|
220
|
-
response = await _this.axios.get(path, this.axiosOptions)
|
|
221
|
-
} else {
|
|
222
|
-
response = await _this.axios.post(
|
|
223
|
-
path,
|
|
224
|
-
{
|
|
225
|
-
tokenIds: id
|
|
226
|
-
},
|
|
227
|
-
this.axiosOptions
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
return response.data
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error.response && error.response.data) throw error.response.data
|
|
233
|
-
throw error
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @api SLP.Utils.balancesForAddress() balancesForAddress()
|
|
239
|
-
* @apiName balancesForAddress
|
|
240
|
-
* @apiGroup SLP Utils
|
|
241
|
-
* @apiDescription Return all balances for an address or array of addresses.
|
|
242
|
-
*
|
|
243
|
-
* @apiExample Example usage:
|
|
244
|
-
*
|
|
245
|
-
* (async () => {
|
|
246
|
-
* try {
|
|
247
|
-
* let balances = await bchjs.SLP.Utils.balancesForAddress('simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk');
|
|
248
|
-
* console.log(balances);
|
|
249
|
-
* } catch (error) {
|
|
250
|
-
* console.error(error);
|
|
251
|
-
* }
|
|
252
|
-
* })();
|
|
253
|
-
*
|
|
254
|
-
* // returns
|
|
255
|
-
* // [ { tokenId:
|
|
256
|
-
* // '968ff0cc4c93864001e03e9524e351250b94ec56150fa4897f65b0b6477d44d4',
|
|
257
|
-
* // balance: '9980',
|
|
258
|
-
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
259
|
-
* // decimalCount: 9 },
|
|
260
|
-
* // { tokenId:
|
|
261
|
-
* // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
262
|
-
* // balance: '617',
|
|
263
|
-
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
264
|
-
* // decimalCount: 8 },
|
|
265
|
-
* // { tokenId:
|
|
266
|
-
* // 'b96304d12f1bbc2196df582516410e55a9b34e13c7b4585bf5c1770af30d034f',
|
|
267
|
-
* // balance: '1',
|
|
268
|
-
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
269
|
-
* // decimalCount: 0 },
|
|
270
|
-
* // { tokenId:
|
|
271
|
-
* // 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
|
272
|
-
* // balance: '776',
|
|
273
|
-
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
274
|
-
* // decimalCount: 7 } ]
|
|
275
|
-
*
|
|
276
|
-
* // balances for Cash Address
|
|
277
|
-
* (async () => {
|
|
278
|
-
* try {
|
|
279
|
-
* let balances = await bchjs.SLP.Utils.balancesForAddress('bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y');
|
|
280
|
-
* console.log(balances);
|
|
281
|
-
* } catch (error) {
|
|
282
|
-
* console.error(error);
|
|
283
|
-
* }
|
|
284
|
-
* })();
|
|
285
|
-
*
|
|
286
|
-
* // returns
|
|
287
|
-
* // [ { tokenId:
|
|
288
|
-
* // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
|
|
289
|
-
* // balance: '507',
|
|
290
|
-
* // decimalCount: 2 } ]
|
|
291
|
-
*
|
|
292
|
-
* // balances for Legacy Address
|
|
293
|
-
* (async () => {
|
|
294
|
-
* try {
|
|
295
|
-
* let balances = await bchjs.SLP.Utils.balancesForAddress('1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS');
|
|
296
|
-
* console.log(balances);
|
|
297
|
-
* } catch (error) {
|
|
298
|
-
* console.error(error);
|
|
299
|
-
* }
|
|
300
|
-
* })();
|
|
301
|
-
*
|
|
302
|
-
* // returns
|
|
303
|
-
* // [ { tokenId:
|
|
304
|
-
* // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
|
|
305
|
-
* // balance: '507',
|
|
306
|
-
* // decimalCount: 2 } ]
|
|
307
|
-
*
|
|
308
|
-
* Note: Balances for multiple addresses can be retrieves by passing in an
|
|
309
|
-
* array of addresses.
|
|
310
|
-
*/
|
|
311
|
-
// Retrieve token balances for a given address.
|
|
312
|
-
async balancesForAddress (address) {
|
|
313
|
-
try {
|
|
314
|
-
// Single address.
|
|
315
|
-
if (typeof address === 'string') {
|
|
316
|
-
const path = `${this.restURL}slp/balancesForAddress/${address}`
|
|
317
|
-
|
|
318
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
319
|
-
return response.data
|
|
320
|
-
|
|
321
|
-
// Array of addresses.
|
|
322
|
-
} else if (Array.isArray(address)) {
|
|
323
|
-
const path = `${this.restURL}slp/balancesForAddress`
|
|
324
|
-
|
|
325
|
-
// Dev note: must use axios.post for unit test stubbing.
|
|
326
|
-
const response = await _this.axios.post(
|
|
327
|
-
path,
|
|
328
|
-
{
|
|
329
|
-
addresses: address
|
|
330
|
-
},
|
|
331
|
-
this.axiosOptions
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
return response.data
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
throw new Error('Input address must be a string or array of strings.')
|
|
338
|
-
} catch (error) {
|
|
339
|
-
if (error.response && error.response.data) throw error.response.data
|
|
340
|
-
throw error
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* @api SLP.Utils.balancesForToken() balancesForToken()
|
|
346
|
-
* @apiName balancesForToken
|
|
347
|
-
* @apiGroup SLP Utils
|
|
348
|
-
* @apiDescription List all balances for tokenId.
|
|
349
|
-
*
|
|
350
|
-
* @apiExample Example usage:
|
|
351
|
-
*
|
|
352
|
-
* (async () => {
|
|
353
|
-
* try {
|
|
354
|
-
* let balances = await bchjs.SLP.Utils.balancesForToken(
|
|
355
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
356
|
-
* )
|
|
357
|
-
* console.log(balances)
|
|
358
|
-
* } catch (error) {
|
|
359
|
-
* console.error(error)
|
|
360
|
-
* }
|
|
361
|
-
* })()
|
|
362
|
-
*
|
|
363
|
-
* // returns
|
|
364
|
-
* [
|
|
365
|
-
* {
|
|
366
|
-
* tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
367
|
-
* tokenBalance: 20,
|
|
368
|
-
* slpAddress: 'simpleledger:qp4g0q97tq53pasnxk2rs570c6573qvylunsf5gy9e'
|
|
369
|
-
* },
|
|
370
|
-
* {
|
|
371
|
-
* tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
372
|
-
* tokenBalance: 335.55,
|
|
373
|
-
* slpAddress: 'simpleledger:qqcraw7q0ys3kg4z6f2zd267fhg2093c5c0spfk03f'
|
|
374
|
-
* }
|
|
375
|
-
* ]
|
|
376
|
-
*
|
|
377
|
-
*/
|
|
378
|
-
// Retrieve token balances for a given tokenId.
|
|
379
|
-
async balancesForToken (tokenId) {
|
|
380
|
-
try {
|
|
381
|
-
const path = `${this.restURL}slp/balancesForToken/${tokenId}`
|
|
382
|
-
|
|
383
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
384
|
-
return response.data
|
|
385
|
-
} catch (error) {
|
|
386
|
-
if (error.response && error.response.data) throw error.response.data
|
|
387
|
-
throw error
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* @api SLP.Utils.validateTxid() validateTxid()
|
|
393
|
-
* @apiName validateTxid
|
|
394
|
-
* @apiGroup SLP Utils
|
|
395
|
-
* @apiDescription Validate that txid is an SLP transaction.
|
|
396
|
-
*
|
|
397
|
-
* @apiExample Example usage:
|
|
398
|
-
*
|
|
399
|
-
* // validate single SLP txid
|
|
400
|
-
* (async () => {
|
|
401
|
-
* try {
|
|
402
|
-
* let validated = await bchjs.SLP.Utils.validateTxid(
|
|
403
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
404
|
-
* );
|
|
405
|
-
* console.log(validated);
|
|
406
|
-
* } catch (error) {
|
|
407
|
-
* console.error(error);
|
|
408
|
-
* }
|
|
409
|
-
* })();
|
|
410
|
-
*
|
|
411
|
-
* // returns
|
|
412
|
-
* [ { txid:
|
|
413
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
414
|
-
* valid: true } ]
|
|
415
|
-
*
|
|
416
|
-
* // validate multiple SLP txids
|
|
417
|
-
* (async () => {
|
|
418
|
-
* try {
|
|
419
|
-
* let validated = await bchjs.SLP.Utils.validateTxid([
|
|
420
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
421
|
-
* "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476"
|
|
422
|
-
* ]);
|
|
423
|
-
* console.log(validated);
|
|
424
|
-
* } catch (error) {
|
|
425
|
-
* console.error(error);
|
|
426
|
-
* }
|
|
427
|
-
* })();
|
|
428
|
-
*
|
|
429
|
-
* // returns
|
|
430
|
-
* [ { txid:
|
|
431
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
432
|
-
* valid: true },
|
|
433
|
-
* { txid:
|
|
434
|
-
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
|
|
435
|
-
* valid: true } ]
|
|
436
|
-
*/
|
|
437
|
-
// This function has two responses. If SLPDB is working correctly, the output
|
|
438
|
-
// will be like the examples above. If SLPDB has fallen behind real-time
|
|
439
|
-
// processing, it will return this output:
|
|
440
|
-
// [ null ]
|
|
441
|
-
async validateTxid (txid, usrObj = null) {
|
|
442
|
-
const path = `${this.restURL}slp/validateTxid`
|
|
443
|
-
|
|
444
|
-
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
|
445
|
-
// console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
|
446
|
-
|
|
447
|
-
// Handle a single TXID or an array of TXIDs.
|
|
448
|
-
let txids
|
|
449
|
-
if (typeof txid === 'string') txids = [txid]
|
|
450
|
-
else txids = txid
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
// console.log('validateTxid() this.axiosOptions: ', this.axiosOptions)
|
|
454
|
-
const response = await _this.axios.post(
|
|
455
|
-
path,
|
|
456
|
-
{
|
|
457
|
-
txids: txids,
|
|
458
|
-
usrObj // pass user data when making an internal call.
|
|
459
|
-
},
|
|
460
|
-
this.axiosOptions
|
|
461
|
-
)
|
|
462
|
-
// console.log(
|
|
463
|
-
// `validateTxid response.data: ${JSON.stringify(response.data, null, 2)}`
|
|
464
|
-
// )
|
|
465
|
-
|
|
466
|
-
const validatedTxids = response.data
|
|
467
|
-
|
|
468
|
-
return validatedTxids
|
|
469
|
-
} catch (error) {
|
|
470
|
-
if (error.response && error.response.data) throw error.response.data
|
|
471
|
-
throw error
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* @api SLP.Utils.validateTxid2() validateTxid2()
|
|
477
|
-
* @apiName validateTxid2
|
|
478
|
-
* @apiGroup SLP Utils
|
|
479
|
-
* @apiDescription Validate that txid is an SLP transaction.
|
|
480
|
-
*
|
|
481
|
-
* This second validatoin version uses the slp-validate slp library. It is
|
|
482
|
-
* much slower and less efficient than SLPDB and is prone to time-outs for
|
|
483
|
-
* tokens with large DAGs. However, it operates independently of SLPDB and
|
|
484
|
-
* is a great second validation option, particularly when SLPDB returns 'null'
|
|
485
|
-
* values.
|
|
486
|
-
*
|
|
487
|
-
* Due to the inefficiency of this call, only a single TXID can be input at a
|
|
488
|
-
* time. This call will throw an error if the input is an array.
|
|
489
|
-
*
|
|
490
|
-
* @apiExample Example usage:
|
|
491
|
-
*
|
|
492
|
-
* // validate single SLP txid
|
|
493
|
-
* (async () => {
|
|
494
|
-
* try {
|
|
495
|
-
* let validated = await bchjs.SLP.Utils.validateTxid2(
|
|
496
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
497
|
-
* );
|
|
498
|
-
* console.log(validated);
|
|
499
|
-
* } catch (error) {
|
|
500
|
-
* console.error(error);
|
|
501
|
-
* }
|
|
502
|
-
* })();
|
|
503
|
-
*
|
|
504
|
-
* // returns
|
|
505
|
-
* [ { txid:
|
|
506
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
507
|
-
* valid: true } ]
|
|
508
|
-
*/
|
|
509
|
-
async validateTxid2 (txid) {
|
|
510
|
-
try {
|
|
511
|
-
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
|
512
|
-
|
|
513
|
-
if (
|
|
514
|
-
!txid ||
|
|
515
|
-
txid === '' ||
|
|
516
|
-
typeof txid !== 'string' ||
|
|
517
|
-
txid.length !== 64
|
|
518
|
-
) {
|
|
519
|
-
throw new Error('txid must be 64 character string.')
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const path = `${this.restURL}slp/validateTxid2/${txid}`
|
|
523
|
-
|
|
524
|
-
// console.log('validateTxid2() this.axiosOptions: ', this.axiosOptions)
|
|
525
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
526
|
-
return response.data
|
|
527
|
-
} catch (error) {
|
|
528
|
-
if (error.response && error.response.data) throw error.response.data
|
|
529
|
-
|
|
530
|
-
if (error.error && error.error.indexOf('Network error') > -1) {
|
|
531
|
-
throw new Error('slp-validate timed out')
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
throw error
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* @api SLP.Utils.whitelist() whitelist()
|
|
540
|
-
* @apiName whitelist
|
|
541
|
-
* @apiGroup SLP Utils
|
|
542
|
-
* @apiDescription Get SLP tokens in whitelist
|
|
543
|
-
* Retrieves a list of the SLP tokens that in the whitelist. Tokens in the
|
|
544
|
-
* whitelist can be validated with the validateTxid3() function. validateTxid3()
|
|
545
|
-
* will still work when the SLP network is under stress.
|
|
546
|
-
*
|
|
547
|
-
* @apiExample Example usage:
|
|
548
|
-
*
|
|
549
|
-
* // validate single SLP txid
|
|
550
|
-
* (async () => {
|
|
551
|
-
* try {
|
|
552
|
-
* let list = await bchjs.SLP.Utils.whitelit();
|
|
553
|
-
* console.log(list);
|
|
554
|
-
* } catch (error) {
|
|
555
|
-
* console.error(error);
|
|
556
|
-
* }
|
|
557
|
-
* })();
|
|
558
|
-
*
|
|
559
|
-
* // returns
|
|
560
|
-
* [
|
|
561
|
-
* {
|
|
562
|
-
* name: 'USDH',
|
|
563
|
-
* tokenId:
|
|
564
|
-
* 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479'
|
|
565
|
-
* },
|
|
566
|
-
* {
|
|
567
|
-
* name: 'SPICE',
|
|
568
|
-
* tokenId:
|
|
569
|
-
* '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
|
570
|
-
* },
|
|
571
|
-
* {
|
|
572
|
-
* name: 'PSF',
|
|
573
|
-
* tokenId:
|
|
574
|
-
* '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
|
|
575
|
-
* },
|
|
576
|
-
* {
|
|
577
|
-
* name: 'TROUT',
|
|
578
|
-
* tokenId:
|
|
579
|
-
* 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
|
|
580
|
-
* },
|
|
581
|
-
* {
|
|
582
|
-
* name: 'PSFTEST',
|
|
583
|
-
* tokenId:
|
|
584
|
-
* 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0'
|
|
585
|
-
* }
|
|
586
|
-
* ]
|
|
587
|
-
*/
|
|
588
|
-
async getWhitelist () {
|
|
589
|
-
try {
|
|
590
|
-
const path = `${this.restURL}slp/whitelist`
|
|
591
|
-
|
|
592
|
-
// Retrieve the whitelist from the REST API if we haven't gotten it yet.
|
|
593
|
-
if (this.whitelist.length === 0) {
|
|
594
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
595
|
-
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
|
596
|
-
|
|
597
|
-
this.whitelist = response.data
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return this.whitelist
|
|
601
|
-
} catch (error) {
|
|
602
|
-
if (error.response && error.response.data) throw error.response.data
|
|
603
|
-
throw error
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* @api SLP.Utils.validateTxid3() validateTxid3()
|
|
609
|
-
* @apiName validateTxid3
|
|
610
|
-
* @apiGroup SLP Utils
|
|
611
|
-
* @apiDescription
|
|
612
|
-
* Validate that txid is an SLP transaction using the SLPDB whitelist server.
|
|
613
|
-
* Same exact functionality as the validateTxid() function, but this function
|
|
614
|
-
* calls the whitelist SLPDB. It will only validate SLP tokens that are in the
|
|
615
|
-
* whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist()
|
|
616
|
-
* function.
|
|
617
|
-
*
|
|
618
|
-
* @apiExample Example usage:
|
|
619
|
-
*
|
|
620
|
-
* // validate single SLP txid
|
|
621
|
-
* (async () => {
|
|
622
|
-
* try {
|
|
623
|
-
* let validated = await bchjs.SLP.Utils.validateTxid3(
|
|
624
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
625
|
-
* );
|
|
626
|
-
* console.log(validated);
|
|
627
|
-
* } catch (error) {
|
|
628
|
-
* console.error(error);
|
|
629
|
-
* }
|
|
630
|
-
* })();
|
|
631
|
-
*
|
|
632
|
-
* // returns
|
|
633
|
-
* [ { txid:
|
|
634
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
635
|
-
* valid: true } ]
|
|
636
|
-
*
|
|
637
|
-
* // validate multiple SLP txids
|
|
638
|
-
* (async () => {
|
|
639
|
-
* try {
|
|
640
|
-
* let validated = await bchjs.SLP.Utils.validateTxid3([
|
|
641
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
642
|
-
* "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476"
|
|
643
|
-
* ]);
|
|
644
|
-
* console.log(validated);
|
|
645
|
-
* } catch (error) {
|
|
646
|
-
* console.error(error);
|
|
647
|
-
* }
|
|
648
|
-
* })();
|
|
649
|
-
*
|
|
650
|
-
* // returns
|
|
651
|
-
* [ { txid:
|
|
652
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
653
|
-
* valid: true },
|
|
654
|
-
* { txid:
|
|
655
|
-
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
|
|
656
|
-
* valid: true } ]
|
|
657
|
-
*/
|
|
658
|
-
async validateTxid3 (txid, usrObj = null) {
|
|
659
|
-
const path = `${this.restURL}slp/validateTxid3`
|
|
660
|
-
|
|
661
|
-
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
|
662
|
-
// console.log(`path: ${JSON.stringify(path, null, 2)}`)
|
|
663
|
-
// console.log('validateTxid3 usrObj: ', usrObj)
|
|
664
|
-
|
|
665
|
-
// Handle a single TXID or an array of TXIDs.
|
|
666
|
-
let txids
|
|
667
|
-
if (typeof txid === 'string') txids = [txid]
|
|
668
|
-
else txids = txid
|
|
669
|
-
|
|
670
|
-
try {
|
|
671
|
-
// console.log('validateTxid3() this.axiosOptions: ', this.axiosOptions)
|
|
672
|
-
const response = await _this.axios.post(
|
|
673
|
-
path,
|
|
674
|
-
{
|
|
675
|
-
txids: txids,
|
|
676
|
-
usrObj // pass user data when making an internal call.
|
|
677
|
-
},
|
|
678
|
-
this.axiosOptions
|
|
679
|
-
)
|
|
680
|
-
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
|
681
|
-
|
|
682
|
-
const validatedTxids = response.data
|
|
683
|
-
|
|
684
|
-
return validatedTxids
|
|
685
|
-
} catch (error) {
|
|
686
|
-
// console.log('validateTxid3 error: ', error)
|
|
687
|
-
|
|
688
|
-
// This case handles rate limit errors.
|
|
689
|
-
if (error.response && error.response.data && error.response.data.error) {
|
|
690
|
-
throw new Error(error.response.data.error)
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// Not sure if this can be safely deprecated?
|
|
694
|
-
if (error.response && error.response.data) throw error.response.data
|
|
695
|
-
throw error
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* @api SLP.Utils.tokenStats() tokenStats()
|
|
701
|
-
* @apiName tokenStats
|
|
702
|
-
* @apiGroup SLP Utils
|
|
703
|
-
* @apiDescription Stats for token by tokenId.
|
|
704
|
-
*
|
|
705
|
-
* @apiExample Example usage:
|
|
706
|
-
*
|
|
707
|
-
* (async () => {
|
|
708
|
-
* try {
|
|
709
|
-
* let stats = await bchjs.SLP.Utils.tokenStats(
|
|
710
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
711
|
-
* )
|
|
712
|
-
* console.log(stats)
|
|
713
|
-
* } catch (error) {
|
|
714
|
-
* console.error(error)
|
|
715
|
-
* }
|
|
716
|
-
* })()
|
|
717
|
-
*
|
|
718
|
-
* // returns
|
|
719
|
-
* { tokenId:
|
|
720
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
721
|
-
* documentUri: '',
|
|
722
|
-
* symbol: 'NAKAMOTO',
|
|
723
|
-
* name: 'NAKAMOTO',
|
|
724
|
-
* decimals: 8,
|
|
725
|
-
* txnsSinceGenesis: 367,
|
|
726
|
-
* validUtxos: 248,
|
|
727
|
-
* validAddresses: 195,
|
|
728
|
-
* circulatingSupply: 20995990,
|
|
729
|
-
* totalBurned: 4010,
|
|
730
|
-
* totalMinted: 21000000,
|
|
731
|
-
* satoshisLockedUp: 135408
|
|
732
|
-
* }
|
|
733
|
-
*/
|
|
734
|
-
async tokenStats (tokenId) {
|
|
735
|
-
try {
|
|
736
|
-
const path = `${this.restURL}slp/tokenStats/${tokenId}`
|
|
737
|
-
|
|
738
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
739
|
-
|
|
740
|
-
return response.data
|
|
741
|
-
} catch (error) {
|
|
742
|
-
if (error.response && error.response.data) throw error.response.data
|
|
743
|
-
throw error
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* @api SLP.Utils.transactions() transactions()
|
|
749
|
-
* @apiName transactions
|
|
750
|
-
* @apiGroup SLP Utils
|
|
751
|
-
* @apiDescription SLP Transactions by tokenId and address.
|
|
752
|
-
*
|
|
753
|
-
* @apiExample Example usage:
|
|
754
|
-
*
|
|
755
|
-
* (async () => {
|
|
756
|
-
* try {
|
|
757
|
-
* let transactions = await bchjs.SLP.Utils.transactions(
|
|
758
|
-
* "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
|
|
759
|
-
* "qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqlsy4gusz"
|
|
760
|
-
* )
|
|
761
|
-
* console.log(transactions)
|
|
762
|
-
* } catch (error) {
|
|
763
|
-
* console.error(error)
|
|
764
|
-
* }
|
|
765
|
-
* })()
|
|
766
|
-
*
|
|
767
|
-
* // returns
|
|
768
|
-
* [
|
|
769
|
-
* {
|
|
770
|
-
* "txid": "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f",
|
|
771
|
-
* "tokenDetails": {
|
|
772
|
-
* "valid": true,
|
|
773
|
-
* "detail": {
|
|
774
|
-
* "decimals": null,
|
|
775
|
-
* "tokenIdHex": "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
|
|
776
|
-
* "timestamp": null,
|
|
777
|
-
* "transactionType": "SEND",
|
|
778
|
-
* "versionType": 1,
|
|
779
|
-
* "documentUri": null,
|
|
780
|
-
* "documentSha256Hex": null,
|
|
781
|
-
* "symbol": null,
|
|
782
|
-
* "name": null,
|
|
783
|
-
* "batonVout": null,
|
|
784
|
-
* "containsBaton": null,
|
|
785
|
-
* "genesisOrMintQuantity": null,
|
|
786
|
-
* "sendOutputs": [
|
|
787
|
-
* {
|
|
788
|
-
* "$numberDecimal": "0"
|
|
789
|
-
* },
|
|
790
|
-
* {
|
|
791
|
-
* "$numberDecimal": "25"
|
|
792
|
-
* },
|
|
793
|
-
* {
|
|
794
|
-
* "$numberDecimal": "77"
|
|
795
|
-
* }
|
|
796
|
-
* ]
|
|
797
|
-
* },
|
|
798
|
-
* "invalidReason": null,
|
|
799
|
-
* "schema_version": 30
|
|
800
|
-
* }
|
|
801
|
-
* }
|
|
802
|
-
* ]
|
|
803
|
-
*/
|
|
804
|
-
// Retrieve token transactions for a given tokenId and address.
|
|
805
|
-
async transactions (tokenId, address) {
|
|
806
|
-
try {
|
|
807
|
-
const path = `${this.restURL}slp/transactions/${tokenId}/${address}`
|
|
808
|
-
|
|
809
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
810
|
-
|
|
811
|
-
return response.data
|
|
812
|
-
} catch (error) {
|
|
813
|
-
if (error.response && error.response.data) throw error.response.data
|
|
814
|
-
throw error
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* @api SLP.Utils.burnTotal() burnTotal()
|
|
820
|
-
* @apiName burnTotal
|
|
821
|
-
* @apiGroup SLP Utils
|
|
822
|
-
* @apiDescription List input, output and burn total for slp transaction.
|
|
823
|
-
*
|
|
824
|
-
* @apiExample Example usage:
|
|
825
|
-
*
|
|
826
|
-
* (async () => {
|
|
827
|
-
* try {
|
|
828
|
-
* const burnTotal = await bchjs.SLP.Utils.burnTotal(
|
|
829
|
-
* "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
|
|
830
|
-
* )
|
|
831
|
-
* console.log(burnTotal)
|
|
832
|
-
* } catch (error) {
|
|
833
|
-
* console.error(error)
|
|
834
|
-
* }
|
|
835
|
-
* })()
|
|
836
|
-
*
|
|
837
|
-
* // returns
|
|
838
|
-
* {
|
|
839
|
-
* transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de',
|
|
840
|
-
* inputTotal: 100000100,
|
|
841
|
-
* outputTotal: 100000000,
|
|
842
|
-
* burnTotal: 100
|
|
843
|
-
* }
|
|
844
|
-
*/
|
|
845
|
-
async burnTotal (transactionId) {
|
|
846
|
-
try {
|
|
847
|
-
const path = `${this.restURL}slp/burnTotal/${transactionId}`
|
|
848
|
-
|
|
849
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
850
|
-
|
|
851
|
-
return response.data
|
|
852
|
-
} catch (error) {
|
|
853
|
-
if (error.response && error.response.data) throw error.response.data
|
|
854
|
-
throw error
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/**
|
|
859
|
-
* @api SLP.Utils.txDetails() txDetails()
|
|
860
|
-
* @apiName txDetails
|
|
861
|
-
* @apiGroup SLP Utils
|
|
862
|
-
* @apiDescription Transaction details on a token transfer.
|
|
863
|
-
* There is no bulk method for this endpoint. Can only get one tx at a time.
|
|
864
|
-
*
|
|
865
|
-
* @apiExample Example usage:
|
|
866
|
-
*
|
|
867
|
-
* (async () => {
|
|
868
|
-
* try {
|
|
869
|
-
* const details = await bchjs.SLP.Utils.txDetails(
|
|
870
|
-
* "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
|
|
871
|
-
* )
|
|
872
|
-
* console.log(details)
|
|
873
|
-
* } catch (error) {
|
|
874
|
-
* console.error(error)
|
|
875
|
-
* }
|
|
876
|
-
* })()
|
|
877
|
-
*
|
|
878
|
-
*/
|
|
879
|
-
async txDetails (txid) {
|
|
880
|
-
try {
|
|
881
|
-
if (
|
|
882
|
-
!txid ||
|
|
883
|
-
txid === '' ||
|
|
884
|
-
typeof txid !== 'string' ||
|
|
885
|
-
txid.length !== 64
|
|
886
|
-
) {
|
|
887
|
-
throw new Error('txid string must be included.')
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// console.log(`this.restURL: ${this.restURL}`)
|
|
891
|
-
const path = `${this.restURL}slp/txDetails/${txid}`
|
|
892
|
-
|
|
893
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
894
|
-
return response.data
|
|
895
|
-
} catch (error) {
|
|
896
|
-
if (error.response && error.response.data) throw error.response.data
|
|
897
|
-
throw error
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
43
|
/**
|
|
902
44
|
* @api SLP.Utils.decodeOpReturn() decodeOpReturn()
|
|
903
45
|
* @apiName decodeOpReturn
|
|
@@ -1040,910 +182,6 @@ class Utils {
|
|
|
1040
182
|
throw error
|
|
1041
183
|
}
|
|
1042
184
|
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails()
|
|
1046
|
-
* @apiName tokenUtxoDetails
|
|
1047
|
-
* @apiGroup SLP Utils
|
|
1048
|
-
* @apiDescription Hydrate a UTXO with SLP token metadata.
|
|
1049
|
-
*
|
|
1050
|
-
* Expects an array of UTXO objects as input. Returns an array of equal size.
|
|
1051
|
-
* Returns UTXO data hydrated with token information.
|
|
1052
|
-
*
|
|
1053
|
-
* - If the UTXO does not belong to a SLP transaction, it will return an
|
|
1054
|
-
* `isValid` property set to `false`.
|
|
1055
|
-
*
|
|
1056
|
-
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
|
|
1057
|
-
* with additional SLP information attached. An `isValid` property will be
|
|
1058
|
-
* included.
|
|
1059
|
-
* - If the `isValid` property is `true`, the UTXO is a valid SLP UTXO.
|
|
1060
|
-
* - If the `isValid` property is `null`, then SLPDB has not yet processed
|
|
1061
|
-
* that txid and validity has not been confirmed, or a 429 rate-limit error
|
|
1062
|
-
* was enountered during the processing of the request.
|
|
1063
|
-
*
|
|
1064
|
-
* An optional second input object, `usrObj`, allows the user to inject an
|
|
1065
|
-
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
|
1066
|
-
* a number, the call will delay by that number of milliseconds between
|
|
1067
|
-
* processing UTXOs.
|
|
1068
|
-
*
|
|
1069
|
-
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
|
1070
|
-
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
|
1071
|
-
* UTXOs at a
|
|
1072
|
-
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
|
1073
|
-
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
|
1074
|
-
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
* @apiExample Example usage:
|
|
1078
|
-
*
|
|
1079
|
-
* (async () => {
|
|
1080
|
-
* try {
|
|
1081
|
-
* const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
|
1082
|
-
*
|
|
1083
|
-
* // Delay 100mS between processing UTXOs, to prevent rate-limit errors.
|
|
1084
|
-
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos, { utxoDelay: 100 })
|
|
1085
|
-
*
|
|
1086
|
-
* console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
|
|
1087
|
-
* } catch (error) {
|
|
1088
|
-
* console.error(error)
|
|
1089
|
-
* }
|
|
1090
|
-
* })()
|
|
1091
|
-
*
|
|
1092
|
-
* // returns
|
|
1093
|
-
* {
|
|
1094
|
-
* "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
|
1095
|
-
* "vout": 1,
|
|
1096
|
-
* "amount": 0.00000546,
|
|
1097
|
-
* "satoshis": 546,
|
|
1098
|
-
* "height": 596089,
|
|
1099
|
-
* "confirmations": 748,
|
|
1100
|
-
* "utxoType": "token",
|
|
1101
|
-
* "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
|
1102
|
-
* "tokenTicker": "TOK-CH",
|
|
1103
|
-
* "tokenName": "TokyoCash",
|
|
1104
|
-
* "tokenDocumentUrl": "",
|
|
1105
|
-
* "tokenDocumentHash": "",
|
|
1106
|
-
* "decimals": 8,
|
|
1107
|
-
* "tokenQty": 2,
|
|
1108
|
-
* "isValid": true,
|
|
1109
|
-
* "tokenType": 1
|
|
1110
|
-
* }
|
|
1111
|
-
*/
|
|
1112
|
-
async tokenUtxoDetails (utxos, usrObj = null) {
|
|
1113
|
-
try {
|
|
1114
|
-
// Throw error if input is not an array.
|
|
1115
|
-
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
|
1116
|
-
|
|
1117
|
-
// console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
|
1118
|
-
|
|
1119
|
-
// Loop through each element in the array and validate the input before
|
|
1120
|
-
// further processing.
|
|
1121
|
-
for (let i = 0; i < utxos.length; i++) {
|
|
1122
|
-
const utxo = utxos[i]
|
|
1123
|
-
|
|
1124
|
-
// Ensure the UTXO has a txid or tx_hash property.
|
|
1125
|
-
if (!utxo.txid) {
|
|
1126
|
-
// If Electrumx, convert the tx_hash property to txid.
|
|
1127
|
-
if (utxo.tx_hash) {
|
|
1128
|
-
utxo.txid = utxo.tx_hash
|
|
1129
|
-
} else {
|
|
1130
|
-
// If there is neither a txid or tx_hash property, throw an error.
|
|
1131
|
-
throw new Error(
|
|
1132
|
-
`utxo ${i} does not have a txid or tx_hash property.`
|
|
1133
|
-
)
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// Ensure the UTXO has a vout or tx_pos property.
|
|
1138
|
-
if (!Number.isInteger(utxo.vout)) {
|
|
1139
|
-
if (Number.isInteger(utxo.tx_pos)) {
|
|
1140
|
-
utxo.vout = utxo.tx_pos
|
|
1141
|
-
} else {
|
|
1142
|
-
throw new Error(
|
|
1143
|
-
`utxo ${i} does not have a vout or tx_pos property.`
|
|
1144
|
-
)
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
|
1150
|
-
const outAry = await this._hydrateUtxo(utxos, usrObj)
|
|
1151
|
-
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
|
1152
|
-
|
|
1153
|
-
// *After* each UTXO has been hydrated with SLP data,
|
|
1154
|
-
// validate the TXID with SLPDB.
|
|
1155
|
-
for (let i = 0; i < outAry.length; i++) {
|
|
1156
|
-
const utxo = outAry[i]
|
|
1157
|
-
|
|
1158
|
-
// *After* the UTXO has been hydrated with SLP data,
|
|
1159
|
-
// validate the TXID with SLPDB.
|
|
1160
|
-
if (utxo.tokenType) {
|
|
1161
|
-
// Only execute this code-path if the current UTXO has a 'tokenType'
|
|
1162
|
-
// property. i.e. it has been successfully hydrated with SLP
|
|
1163
|
-
// information.
|
|
1164
|
-
|
|
1165
|
-
// Validate using a 'waterfall' of validators.
|
|
1166
|
-
utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
|
|
1167
|
-
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
return outAry
|
|
1172
|
-
} catch (error) {
|
|
1173
|
-
// console.log('Error in tokenUtxoDetails()')
|
|
1174
|
-
if (error.response && error.response.data) throw error.response.data
|
|
1175
|
-
throw error
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
/**
|
|
1180
|
-
* @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
|
|
1181
|
-
* @apiName tokenUtxoDetailsWL
|
|
1182
|
-
* @apiGroup SLP Utils
|
|
1183
|
-
* @apiDescription
|
|
1184
|
-
*
|
|
1185
|
-
* Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
|
|
1186
|
-
* validate UTXOs. This will result in a lot of `isValid: null` values,
|
|
1187
|
-
* but much more performant handling of SLP tokens. Some wallet apps prefer
|
|
1188
|
-
* the scaling performance over the breadth of supported tokens.
|
|
1189
|
-
*
|
|
1190
|
-
* An optional second input object, `usrObj`, allows the user to inject an
|
|
1191
|
-
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
|
1192
|
-
* a number, the call will delay by that number of milliseconds between
|
|
1193
|
-
* processing UTXOs.
|
|
1194
|
-
*
|
|
1195
|
-
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
|
1196
|
-
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
|
1197
|
-
* UTXOs at a
|
|
1198
|
-
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
|
1199
|
-
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
|
1200
|
-
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
|
1201
|
-
*
|
|
1202
|
-
*/
|
|
1203
|
-
async tokenUtxoDetailsWL (utxos, usrObj = null) {
|
|
1204
|
-
try {
|
|
1205
|
-
// Throw error if input is not an array.
|
|
1206
|
-
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
|
1207
|
-
|
|
1208
|
-
// Loop through each element in the array and validate the input before
|
|
1209
|
-
// further processing.
|
|
1210
|
-
for (let i = 0; i < utxos.length; i++) {
|
|
1211
|
-
const utxo = utxos[i]
|
|
1212
|
-
|
|
1213
|
-
// Ensure the UTXO has a txid or tx_hash property.
|
|
1214
|
-
if (!utxo.txid) {
|
|
1215
|
-
// If Electrumx, convert the tx_hash property to txid.
|
|
1216
|
-
if (utxo.tx_hash) {
|
|
1217
|
-
utxo.txid = utxo.tx_hash
|
|
1218
|
-
} else {
|
|
1219
|
-
// If there is neither a txid or tx_hash property, throw an error.
|
|
1220
|
-
throw new Error(
|
|
1221
|
-
`utxo ${i} does not have a txid or tx_hash property.`
|
|
1222
|
-
)
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
// Ensure the UTXO has a vout or tx_pos property.
|
|
1227
|
-
if (!Number.isInteger(utxo.vout)) {
|
|
1228
|
-
if (Number.isInteger(utxo.tx_pos)) {
|
|
1229
|
-
utxo.vout = utxo.tx_pos
|
|
1230
|
-
} else {
|
|
1231
|
-
throw new Error(
|
|
1232
|
-
`utxo ${i} does not have a vout or tx_pos property.`
|
|
1233
|
-
)
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
|
1239
|
-
const outAry = await this._hydrateUtxo(utxos, usrObj)
|
|
1240
|
-
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
|
1241
|
-
|
|
1242
|
-
// *After* each UTXO has been hydrated with SLP data,
|
|
1243
|
-
// validate the TXID with SLPDB.
|
|
1244
|
-
for (let i = 0; i < outAry.length; i++) {
|
|
1245
|
-
const utxo = outAry[i]
|
|
1246
|
-
|
|
1247
|
-
// *After* the UTXO has been hydrated with SLP data,
|
|
1248
|
-
// validate the TXID with SLPDB.
|
|
1249
|
-
if (utxo.tokenType) {
|
|
1250
|
-
// Only execute this block if the current UTXO has a 'tokenType'
|
|
1251
|
-
// property. i.e. it has been successfully hydrated with SLP
|
|
1252
|
-
// information.
|
|
1253
|
-
|
|
1254
|
-
// Validate against the whitelist SLPDB.
|
|
1255
|
-
const whitelistResult = await this.validateTxid3(utxo.txid, usrObj)
|
|
1256
|
-
// console.log(
|
|
1257
|
-
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
|
1258
|
-
// whitelistResult,
|
|
1259
|
-
// null,
|
|
1260
|
-
// 2
|
|
1261
|
-
// )}`
|
|
1262
|
-
// )
|
|
1263
|
-
|
|
1264
|
-
let isValid = null
|
|
1265
|
-
|
|
1266
|
-
// Safely retrieve the returned value.
|
|
1267
|
-
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
|
1268
|
-
|
|
1269
|
-
utxo.isValid = isValid
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
return outAry
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
if (error.response && error.response.data) throw error.response.data
|
|
1276
|
-
throw error
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
// This is a private function that is called by tokenUtxoDetails().
|
|
1281
|
-
// It loops through an array of UTXOs and tries to hydrate them with SLP
|
|
1282
|
-
// token information from the OP_RETURN data.
|
|
1283
|
-
//
|
|
1284
|
-
// This function makes several calls to decodeOpReturn() to retrieve SLP
|
|
1285
|
-
// token data. If that call throws an error due to hitting rate limits, this
|
|
1286
|
-
// function will not throw an error. Instead, it will mark the `isValid`
|
|
1287
|
-
// property as `null`
|
|
1288
|
-
//
|
|
1289
|
-
// Exception to the above: It *will* throw an error if decodeOpReturn() throws
|
|
1290
|
-
// an error while trying to get the Genesis transaction for a Send or Mint
|
|
1291
|
-
// transaction. However, that is a rare occurence since the cache of
|
|
1292
|
-
// decodeOpReturn() will minimize API calls for this case. This behavior
|
|
1293
|
-
// could be changed, but right now it's a corner case of a corner case.
|
|
1294
|
-
//
|
|
1295
|
-
// If the usrObj has a utxoDelay property, then it will delay the loop for
|
|
1296
|
-
// each UTXO by that many milliseconds.
|
|
1297
|
-
async _hydrateUtxo (utxos, usrObj = null) {
|
|
1298
|
-
try {
|
|
1299
|
-
const decodeOpReturnCache = {}
|
|
1300
|
-
|
|
1301
|
-
// console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
|
1302
|
-
|
|
1303
|
-
// Output Array
|
|
1304
|
-
const outAry = []
|
|
1305
|
-
|
|
1306
|
-
// Loop through each utxo
|
|
1307
|
-
for (let i = 0; i < utxos.length; i++) {
|
|
1308
|
-
const utxo = utxos[i]
|
|
1309
|
-
|
|
1310
|
-
// If the user passes in a delay, then wait.
|
|
1311
|
-
if (usrObj && usrObj.utxoDelay && !isNaN(Number(usrObj.utxoDelay))) {
|
|
1312
|
-
const delayMs = Number(usrObj.utxoDelay)
|
|
1313
|
-
await this.util.sleep(delayMs)
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// Get raw transaction data from the full node and attempt to decode
|
|
1317
|
-
// the OP_RETURN data.
|
|
1318
|
-
// If there is no OP_RETURN, mark the UTXO as false.
|
|
1319
|
-
let slpData = false
|
|
1320
|
-
try {
|
|
1321
|
-
slpData = await this.decodeOpReturn(
|
|
1322
|
-
utxo.txid,
|
|
1323
|
-
decodeOpReturnCache,
|
|
1324
|
-
usrObj // pass user data when making an internal call.
|
|
1325
|
-
)
|
|
1326
|
-
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
|
1327
|
-
} catch (err) {
|
|
1328
|
-
// console.log(
|
|
1329
|
-
// `error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `,
|
|
1330
|
-
// err
|
|
1331
|
-
// )
|
|
1332
|
-
|
|
1333
|
-
// An error will be thrown if the txid is not SLP.
|
|
1334
|
-
// If error is for some other reason, like a 429 error, mark utxo as 'null'
|
|
1335
|
-
// to display the unknown state.
|
|
1336
|
-
if (
|
|
1337
|
-
!err.message ||
|
|
1338
|
-
(err.message.indexOf('scriptpubkey not op_return') === -1 &&
|
|
1339
|
-
err.message.indexOf('lokad id') === -1 &&
|
|
1340
|
-
err.message.indexOf('trailing data') === -1)
|
|
1341
|
-
) {
|
|
1342
|
-
// console.log(
|
|
1343
|
-
// "unknown error from decodeOpReturn(). Marking as 'null'",
|
|
1344
|
-
// err
|
|
1345
|
-
// )
|
|
1346
|
-
|
|
1347
|
-
utxo.isValid = null
|
|
1348
|
-
outAry.push(utxo)
|
|
1349
|
-
|
|
1350
|
-
// If error is thrown because there is no OP_RETURN, then it's not
|
|
1351
|
-
// an SLP UTXO.
|
|
1352
|
-
// Mark as false and continue the loop.
|
|
1353
|
-
} else {
|
|
1354
|
-
// console.log('marking as invalid')
|
|
1355
|
-
utxo.isValid = false
|
|
1356
|
-
outAry.push(utxo)
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
// Halt the execution of the loop and increase to the next index.
|
|
1360
|
-
continue
|
|
1361
|
-
}
|
|
1362
|
-
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
|
1363
|
-
|
|
1364
|
-
const txType = slpData.txType.toLowerCase()
|
|
1365
|
-
|
|
1366
|
-
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
|
1367
|
-
|
|
1368
|
-
// If there is an OP_RETURN, attempt to decode it.
|
|
1369
|
-
// Handle Genesis SLP transactions.
|
|
1370
|
-
if (txType === 'genesis') {
|
|
1371
|
-
if (
|
|
1372
|
-
utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
|
|
1373
|
-
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
|
1374
|
-
) {
|
|
1375
|
-
// Can safely be marked as false.
|
|
1376
|
-
utxo.isValid = false
|
|
1377
|
-
outAry[i] = utxo
|
|
1378
|
-
} else {
|
|
1379
|
-
// If this is a valid SLP UTXO, then return the decoded OP_RETURN data.
|
|
1380
|
-
// Minting Baton
|
|
1381
|
-
if (utxo.vout === slpData.mintBatonVout) {
|
|
1382
|
-
utxo.utxoType = 'minting-baton'
|
|
1383
|
-
} else {
|
|
1384
|
-
// Tokens
|
|
1385
|
-
|
|
1386
|
-
utxo.utxoType = 'token'
|
|
1387
|
-
utxo.tokenQty = new BigNumber(slpData.qty)
|
|
1388
|
-
.div(Math.pow(10, slpData.decimals))
|
|
1389
|
-
.toString()
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
utxo.tokenId = utxo.txid
|
|
1393
|
-
utxo.tokenTicker = slpData.ticker
|
|
1394
|
-
utxo.tokenName = slpData.name
|
|
1395
|
-
utxo.tokenDocumentUrl = slpData.documentUri
|
|
1396
|
-
utxo.tokenDocumentHash = slpData.documentHash
|
|
1397
|
-
utxo.decimals = slpData.decimals
|
|
1398
|
-
utxo.tokenType = slpData.tokenType
|
|
1399
|
-
|
|
1400
|
-
// Initial value is null until UTXO can be validated and confirmed
|
|
1401
|
-
// to be valid (true) or not (false).
|
|
1402
|
-
utxo.isValid = null
|
|
1403
|
-
|
|
1404
|
-
outAry[i] = utxo
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
// Handle Mint SLP transactions.
|
|
1409
|
-
if (txType === 'mint') {
|
|
1410
|
-
if (
|
|
1411
|
-
utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
|
|
1412
|
-
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
|
1413
|
-
) {
|
|
1414
|
-
// Can safely be marked as false.
|
|
1415
|
-
utxo.isValid = false
|
|
1416
|
-
|
|
1417
|
-
outAry[i] = utxo
|
|
1418
|
-
} else {
|
|
1419
|
-
// If UTXO passes validation, then return formatted token data.
|
|
1420
|
-
|
|
1421
|
-
const genesisData = await this.decodeOpReturn(
|
|
1422
|
-
slpData.tokenId,
|
|
1423
|
-
decodeOpReturnCache,
|
|
1424
|
-
usrObj // pass user data when making an internal call.
|
|
1425
|
-
)
|
|
1426
|
-
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
|
1427
|
-
|
|
1428
|
-
// Minting Baton
|
|
1429
|
-
if (utxo.vout === slpData.mintBatonVout) {
|
|
1430
|
-
utxo.utxoType = 'minting-baton'
|
|
1431
|
-
} else {
|
|
1432
|
-
// Tokens
|
|
1433
|
-
|
|
1434
|
-
utxo.utxoType = 'token'
|
|
1435
|
-
utxo.tokenQty = new BigNumber(slpData.qty)
|
|
1436
|
-
.div(Math.pow(10, genesisData.decimals))
|
|
1437
|
-
.toString()
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
// Hydrate the UTXO object with information about the SLP token.
|
|
1441
|
-
utxo.transactionType = 'mint'
|
|
1442
|
-
utxo.tokenId = slpData.tokenId
|
|
1443
|
-
utxo.tokenType = slpData.tokenType
|
|
1444
|
-
|
|
1445
|
-
utxo.tokenTicker = genesisData.ticker
|
|
1446
|
-
utxo.tokenName = genesisData.name
|
|
1447
|
-
utxo.tokenDocumentUrl = genesisData.documentUri
|
|
1448
|
-
utxo.tokenDocumentHash = genesisData.documentHash
|
|
1449
|
-
utxo.decimals = genesisData.decimals
|
|
1450
|
-
|
|
1451
|
-
utxo.mintBatonVout = slpData.mintBatonVout
|
|
1452
|
-
|
|
1453
|
-
// Initial value is null until UTXO can be validated and confirmed
|
|
1454
|
-
// to be valid (true) or not (false).
|
|
1455
|
-
utxo.isValid = null
|
|
1456
|
-
|
|
1457
|
-
outAry[i] = utxo
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
// Handle Send SLP transactions.
|
|
1462
|
-
if (txType === 'send') {
|
|
1463
|
-
// Filter out any vouts that match.
|
|
1464
|
-
// const voutMatch = slpData.spendData.filter(x => utxo.vout === x.vout)
|
|
1465
|
-
// console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`)
|
|
1466
|
-
|
|
1467
|
-
// Figure out what token quantity is represented by this utxo.
|
|
1468
|
-
const tokenQty = slpData.amounts[utxo.vout - 1]
|
|
1469
|
-
// console.log('tokenQty: ', tokenQty)
|
|
1470
|
-
|
|
1471
|
-
if (!tokenQty) {
|
|
1472
|
-
utxo.isValid = false
|
|
1473
|
-
|
|
1474
|
-
outAry[i] = utxo
|
|
1475
|
-
} else {
|
|
1476
|
-
// If UTXO passes validation, then return formatted token data.
|
|
1477
|
-
|
|
1478
|
-
const genesisData = await this.decodeOpReturn(
|
|
1479
|
-
slpData.tokenId,
|
|
1480
|
-
decodeOpReturnCache,
|
|
1481
|
-
usrObj // pass user data when making an internal call.
|
|
1482
|
-
)
|
|
1483
|
-
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
|
1484
|
-
|
|
1485
|
-
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
|
1486
|
-
|
|
1487
|
-
// Hydrate the UTXO object with information about the SLP token.
|
|
1488
|
-
utxo.utxoType = 'token'
|
|
1489
|
-
utxo.transactionType = 'send'
|
|
1490
|
-
utxo.tokenId = slpData.tokenId
|
|
1491
|
-
utxo.tokenTicker = genesisData.ticker
|
|
1492
|
-
utxo.tokenName = genesisData.name
|
|
1493
|
-
utxo.tokenDocumentUrl = genesisData.documentUri
|
|
1494
|
-
utxo.tokenDocumentHash = genesisData.documentHash
|
|
1495
|
-
utxo.decimals = genesisData.decimals
|
|
1496
|
-
utxo.tokenType = slpData.tokenType
|
|
1497
|
-
|
|
1498
|
-
// Initial value is null until UTXO can be validated and confirmed
|
|
1499
|
-
// to be valid (true) or not (false).
|
|
1500
|
-
utxo.isValid = null
|
|
1501
|
-
|
|
1502
|
-
// Calculate the real token quantity.
|
|
1503
|
-
|
|
1504
|
-
const tokenQtyBig = new BigNumber(tokenQty).div(
|
|
1505
|
-
Math.pow(10, genesisData.decimals)
|
|
1506
|
-
)
|
|
1507
|
-
// console.log(`tokenQtyBig`, tokenQtyBig.toString())
|
|
1508
|
-
utxo.tokenQty = tokenQtyBig.toString()
|
|
1509
|
-
|
|
1510
|
-
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
|
1511
|
-
|
|
1512
|
-
outAry[i] = utxo
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
return outAry
|
|
1518
|
-
} catch (error) {
|
|
1519
|
-
// console.log('_hydrateUtxo error: ', error)
|
|
1520
|
-
throw error
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
/**
|
|
1525
|
-
* @api SLP.Utils.waterfallValidateTxid() waterfallValidateTxid()
|
|
1526
|
-
* @apiName waterfallValidateTxid
|
|
1527
|
-
* @apiGroup SLP Utils
|
|
1528
|
-
* @apiDescription Use multiple validators to validate an SLP TXID.
|
|
1529
|
-
*
|
|
1530
|
-
* This function aggregates all the available SLP token validation sources.
|
|
1531
|
-
* It starts with the fastest, most-efficient source first, and continues
|
|
1532
|
-
* to other validation sources until the txid is validated (true or false).
|
|
1533
|
-
* If the txid goes through all sources and can't be validated, it will
|
|
1534
|
-
* return null.
|
|
1535
|
-
*
|
|
1536
|
-
* Validation sources from most efficient to least efficient:
|
|
1537
|
-
* - SLPDB with whitelist filter
|
|
1538
|
-
* - SLPDB general purpose
|
|
1539
|
-
* - slp-api
|
|
1540
|
-
*
|
|
1541
|
-
* Currently only supports a single txid at a time.
|
|
1542
|
-
*
|
|
1543
|
-
* @apiExample Example usage:
|
|
1544
|
-
*
|
|
1545
|
-
* // validate single SLP txid
|
|
1546
|
-
* (async () => {
|
|
1547
|
-
* try {
|
|
1548
|
-
* let validated = await bchjs.SLP.Utils.waterfallValidateTxid(
|
|
1549
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
1550
|
-
* );
|
|
1551
|
-
* console.log(validated);
|
|
1552
|
-
* } catch (error) {
|
|
1553
|
-
* console.error(error);
|
|
1554
|
-
* }
|
|
1555
|
-
* })();
|
|
1556
|
-
*
|
|
1557
|
-
* // returns
|
|
1558
|
-
* true
|
|
1559
|
-
*/
|
|
1560
|
-
async waterfallValidateTxid (txid, usrObj = null) {
|
|
1561
|
-
try {
|
|
1562
|
-
// console.log('txid: ', txid)
|
|
1563
|
-
|
|
1564
|
-
const cachedTxValidation = {}
|
|
1565
|
-
|
|
1566
|
-
// If the value has been cached, use the cached version first.
|
|
1567
|
-
let isValid = cachedTxValidation[txid]
|
|
1568
|
-
if (!isValid && isValid !== false) {
|
|
1569
|
-
isValid = null
|
|
1570
|
-
} else {
|
|
1571
|
-
return isValid
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
// There are two possible responses from SLPDB. If SLPDB is functioning
|
|
1575
|
-
// correctly, then validateTxid() will return this:
|
|
1576
|
-
// isValid: [
|
|
1577
|
-
// {
|
|
1578
|
-
// "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
|
|
1579
|
-
// "valid": true
|
|
1580
|
-
// }
|
|
1581
|
-
// ]
|
|
1582
|
-
//
|
|
1583
|
-
// If SLPDB has fallen behind real-time processing, it will return this:
|
|
1584
|
-
// isValid: [
|
|
1585
|
-
// null
|
|
1586
|
-
// ]
|
|
1587
|
-
//
|
|
1588
|
-
// Note: validateTxid3() has the same output as validateTxid().
|
|
1589
|
-
// validateTxid2() uses slp-validate, which has a different output format.
|
|
1590
|
-
|
|
1591
|
-
// Validate against the whitelist SLPDB first.
|
|
1592
|
-
const whitelistResult = await this.validateTxid3(txid, usrObj)
|
|
1593
|
-
// console.log(
|
|
1594
|
-
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
|
1595
|
-
// whitelistResult,
|
|
1596
|
-
// null,
|
|
1597
|
-
// 2
|
|
1598
|
-
// )}`
|
|
1599
|
-
// )
|
|
1600
|
-
|
|
1601
|
-
// Safely retrieve the returned value.
|
|
1602
|
-
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
|
1603
|
-
|
|
1604
|
-
// Exit if isValid is not null.
|
|
1605
|
-
if (isValid !== null) {
|
|
1606
|
-
// Save to the cache.
|
|
1607
|
-
cachedTxValidation[txid] = isValid
|
|
1608
|
-
|
|
1609
|
-
return isValid
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// Try the general SLPDB, if the whitelist returned null.
|
|
1613
|
-
const generalResult = await this.validateTxid(txid, usrObj)
|
|
1614
|
-
// console.log(
|
|
1615
|
-
// `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
|
|
1616
|
-
// )
|
|
1617
|
-
|
|
1618
|
-
// Safely retrieve the returned value.
|
|
1619
|
-
if (generalResult[0] !== null) isValid = generalResult[0].valid
|
|
1620
|
-
|
|
1621
|
-
// Exit if isValid is not null.
|
|
1622
|
-
if (isValid !== null) {
|
|
1623
|
-
// Save to the cache.
|
|
1624
|
-
cachedTxValidation[txid] = isValid
|
|
1625
|
-
|
|
1626
|
-
return isValid
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
// If still null, as a last resort, check it against slp-validate
|
|
1630
|
-
let slpValidateResult = null
|
|
1631
|
-
try {
|
|
1632
|
-
slpValidateResult = await this.validateTxid2(txid)
|
|
1633
|
-
} catch (err) {
|
|
1634
|
-
/* exit quietly */
|
|
1635
|
-
}
|
|
1636
|
-
// console.log(
|
|
1637
|
-
// `slpValidateResult: ${JSON.stringify(slpValidateResult, null, 2)}`
|
|
1638
|
-
// )
|
|
1639
|
-
|
|
1640
|
-
// Exit if isValid is not null.
|
|
1641
|
-
if (slpValidateResult !== null) {
|
|
1642
|
-
isValid = slpValidateResult.isValid
|
|
1643
|
-
|
|
1644
|
-
// Save to the cache.
|
|
1645
|
-
cachedTxValidation[txid] = isValid
|
|
1646
|
-
|
|
1647
|
-
return isValid
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
// If isValid is still null, return that value, signaling that the txid
|
|
1651
|
-
// could not be validated.
|
|
1652
|
-
return isValid
|
|
1653
|
-
} catch (error) {
|
|
1654
|
-
// This case handles rate limit errors.
|
|
1655
|
-
if (error.response && error.response.data && error.response.data.error) {
|
|
1656
|
-
throw new Error(error.response.data.error)
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
// console.log('Error in waterfallValidateTxid()')
|
|
1660
|
-
if (error.response && error.response.data) throw error.response.data
|
|
1661
|
-
throw error
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
/**
|
|
1666
|
-
* @api SLP.Utils.hydrateUtxos() hydrateUtxos()
|
|
1667
|
-
* @apiName hydrateUtxos
|
|
1668
|
-
* @apiGroup SLP Utils
|
|
1669
|
-
* @apiDescription Hydrate a UTXO with SLP token metadata.
|
|
1670
|
-
*
|
|
1671
|
-
* The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting,
|
|
1672
|
-
* which greatly increases the speed, since fewer API calls need to be made.
|
|
1673
|
-
* However, internal API calls are still counted against your rate limits.
|
|
1674
|
-
*
|
|
1675
|
-
* This function expects an array of UTXO objects as input. It returns an
|
|
1676
|
-
* array of equal size. The UTXO data hydrated with token information.
|
|
1677
|
-
* - If the UTXO does not belong to a SLP transaction, it will return an
|
|
1678
|
-
* `isValid` property set to `false`.
|
|
1679
|
-
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
|
|
1680
|
-
* with additional SLP information attached. An `isValid` property will be
|
|
1681
|
-
* included.
|
|
1682
|
-
* - If `isValid` is `true`, the UTXO is a valid SLP UTXO.
|
|
1683
|
-
* - If `isValid` is `null`, then SLPDB has not yet processed that txid
|
|
1684
|
-
* and validity has not been confirmed,
|
|
1685
|
-
* or a 429 rate-limit error was enountered during the processing of the
|
|
1686
|
-
* request.
|
|
1687
|
-
*
|
|
1688
|
-
* An optional second input object, `usrObj`, allows the user to inject an
|
|
1689
|
-
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
|
1690
|
-
* a number, the call will delay by that number of milliseconds between
|
|
1691
|
-
* processing UTXOs.
|
|
1692
|
-
*
|
|
1693
|
-
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
|
1694
|
-
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
|
1695
|
-
* UTXOs at a
|
|
1696
|
-
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
|
1697
|
-
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
|
1698
|
-
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
|
1699
|
-
*
|
|
1700
|
-
* @apiExample Example usage:
|
|
1701
|
-
*
|
|
1702
|
-
* (async () => {
|
|
1703
|
-
* try {
|
|
1704
|
-
* const utxos = await bchjs.Electrumx.utxo([
|
|
1705
|
-
* "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg",
|
|
1706
|
-
* "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89",
|
|
1707
|
-
* "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
|
1708
|
-
* ])
|
|
1709
|
-
*
|
|
1710
|
-
* // Wait 100mS between processing UTXOs, to prevent rate limit errors.
|
|
1711
|
-
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos, { utxoDelay: 100 })
|
|
1712
|
-
*
|
|
1713
|
-
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
|
|
1714
|
-
* } catch (error) {
|
|
1715
|
-
* console.error(error)
|
|
1716
|
-
* }
|
|
1717
|
-
* })()
|
|
1718
|
-
*
|
|
1719
|
-
* // returns
|
|
1720
|
-
* {
|
|
1721
|
-
* "slpUtxos": [
|
|
1722
|
-
* {
|
|
1723
|
-
* "utxos": [
|
|
1724
|
-
* {
|
|
1725
|
-
* "height": 654522,
|
|
1726
|
-
* "tx_hash": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
|
|
1727
|
-
* "tx_pos": 0,
|
|
1728
|
-
* "value": 6000,
|
|
1729
|
-
* "satoshis": 6000,
|
|
1730
|
-
* "txid": "516e763932061f9e868652d727045b714db1ecac459e84cd52b5b4cb39572ecc",
|
|
1731
|
-
* "vout": 0,
|
|
1732
|
-
* "isValid": false
|
|
1733
|
-
* }
|
|
1734
|
-
* ],
|
|
1735
|
-
* "address": "bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg"
|
|
1736
|
-
* },
|
|
1737
|
-
* {
|
|
1738
|
-
* "utxos": [
|
|
1739
|
-
* {
|
|
1740
|
-
* "height": 654522,
|
|
1741
|
-
* "tx_hash": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
|
|
1742
|
-
* "tx_pos": 1,
|
|
1743
|
-
* "value": 546,
|
|
1744
|
-
* "satoshis": 546,
|
|
1745
|
-
* "txid": "8ec01d851d9df9fb4b4331275e2ff680257c224100d0081cec6fbeedf982f738",
|
|
1746
|
-
* "vout": 1,
|
|
1747
|
-
* "utxoType": "token",
|
|
1748
|
-
* "transactionType": "send",
|
|
1749
|
-
* "tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
|
|
1750
|
-
* "tokenTicker": "TROUT",
|
|
1751
|
-
* "tokenName": "Trout's test token",
|
|
1752
|
-
* "tokenDocumentUrl": "troutsblog.com",
|
|
1753
|
-
* "tokenDocumentHash": "",
|
|
1754
|
-
* "decimals": 2,
|
|
1755
|
-
* "tokenType": 1,
|
|
1756
|
-
* "tokenQty": 10,
|
|
1757
|
-
* "isValid": true
|
|
1758
|
-
* }
|
|
1759
|
-
* ],
|
|
1760
|
-
* "address": "bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89"
|
|
1761
|
-
* },
|
|
1762
|
-
* {
|
|
1763
|
-
* "utxos": [
|
|
1764
|
-
* {
|
|
1765
|
-
* "height": 654522,
|
|
1766
|
-
* "tx_hash": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
|
|
1767
|
-
* "tx_pos": 0,
|
|
1768
|
-
* "value": 6999,
|
|
1769
|
-
* "satoshis": 6999,
|
|
1770
|
-
* "txid": "072a1e2c2d5f1309bf4eef7f88684e4ecd544a903b386b07f3e04b91b13d8af1",
|
|
1771
|
-
* "vout": 0,
|
|
1772
|
-
* "isValid": false
|
|
1773
|
-
* },
|
|
1774
|
-
* {
|
|
1775
|
-
* "height": 654522,
|
|
1776
|
-
* "tx_hash": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
|
|
1777
|
-
* "tx_pos": 1,
|
|
1778
|
-
* "value": 546,
|
|
1779
|
-
* "satoshis": 546,
|
|
1780
|
-
* "txid": "a72db6a0883ecb8e379f317231b2571e41e041b7b1107e3e54c2e0b3386ac6ca",
|
|
1781
|
-
* "vout": 1,
|
|
1782
|
-
* "utxoType": "token",
|
|
1783
|
-
* "transactionType": "send",
|
|
1784
|
-
* "tokenId": "6201f3efe486c577433622817b99645e1d473cd3882378f9a0efc128ab839a82",
|
|
1785
|
-
* "tokenTicker": "VALENTINE",
|
|
1786
|
-
* "tokenName": "Valentine day token",
|
|
1787
|
-
* "tokenDocumentUrl": "fullstack.cash",
|
|
1788
|
-
* "tokenDocumentHash": "",
|
|
1789
|
-
* "decimals": 2,
|
|
1790
|
-
* "tokenType": 1,
|
|
1791
|
-
* "tokenQty": 5,
|
|
1792
|
-
* "isValid": true
|
|
1793
|
-
* }
|
|
1794
|
-
* ],
|
|
1795
|
-
* "address": "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
|
1796
|
-
* }
|
|
1797
|
-
* ]
|
|
1798
|
-
* }
|
|
1799
|
-
*
|
|
1800
|
-
* (async () => {
|
|
1801
|
-
* try {
|
|
1802
|
-
* const utxos = [
|
|
1803
|
-
* {
|
|
1804
|
-
* utxos: [
|
|
1805
|
-
* {
|
|
1806
|
-
* txid: "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
|
1807
|
-
* vout: 3,
|
|
1808
|
-
* value: "6816",
|
|
1809
|
-
* height: 606848,
|
|
1810
|
-
* confirmations: 13,
|
|
1811
|
-
* satoshis: 6816
|
|
1812
|
-
* }
|
|
1813
|
-
* ]
|
|
1814
|
-
* }
|
|
1815
|
-
* ]
|
|
1816
|
-
*
|
|
1817
|
-
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos)
|
|
1818
|
-
*
|
|
1819
|
-
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
|
|
1820
|
-
* } catch (error) {
|
|
1821
|
-
* console.error(error)
|
|
1822
|
-
* }
|
|
1823
|
-
* })()
|
|
1824
|
-
*
|
|
1825
|
-
* // returns
|
|
1826
|
-
* {
|
|
1827
|
-
* "slpUtxos": [
|
|
1828
|
-
* {
|
|
1829
|
-
* "utxos": [
|
|
1830
|
-
* {
|
|
1831
|
-
* "txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
|
1832
|
-
* "vout": 3,
|
|
1833
|
-
* "value": "6816",
|
|
1834
|
-
* "height": 606848,
|
|
1835
|
-
* "confirmations": 13,
|
|
1836
|
-
* "satoshis": 6816,
|
|
1837
|
-
* "isValid": false
|
|
1838
|
-
* }
|
|
1839
|
-
* ]
|
|
1840
|
-
* }
|
|
1841
|
-
* ]
|
|
1842
|
-
*/
|
|
1843
|
-
// Same as tokenUtxoDetails(), but reduces API calls by having bch-api server
|
|
1844
|
-
// do the heavy lifting.
|
|
1845
|
-
async hydrateUtxos (utxos, usrObj) {
|
|
1846
|
-
try {
|
|
1847
|
-
// Throw error if input is not an array.
|
|
1848
|
-
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
|
1849
|
-
|
|
1850
|
-
const response = await _this.axios.post(
|
|
1851
|
-
`${this.restURL}slp/hydrateUtxos`,
|
|
1852
|
-
{
|
|
1853
|
-
utxos: utxos,
|
|
1854
|
-
usrObj
|
|
1855
|
-
},
|
|
1856
|
-
this.axiosOptions
|
|
1857
|
-
)
|
|
1858
|
-
|
|
1859
|
-
return response.data
|
|
1860
|
-
} catch (error) {
|
|
1861
|
-
console.log('Error in hydrateUtxos(): ', error)
|
|
1862
|
-
|
|
1863
|
-
if (error.response && error.response.data) {
|
|
1864
|
-
throw new Error(JSON.stringify(error.response.data, null, 2))
|
|
1865
|
-
}
|
|
1866
|
-
throw error
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
/**
|
|
1871
|
-
* @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL()
|
|
1872
|
-
* @apiName hydrateUtxosWL
|
|
1873
|
-
* @apiGroup SLP Utils
|
|
1874
|
-
* @apiDescription
|
|
1875
|
-
* This call is exactly the same as `hydrateUtxos()`. This version hydrate a
|
|
1876
|
-
* UTXO with SLP token metadata, but only uses the whitelist SLPDB for
|
|
1877
|
-
* validation.
|
|
1878
|
-
*
|
|
1879
|
-
* Whitelist SLPDBs will return `isValid: null` for any token not in the
|
|
1880
|
-
* 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable
|
|
1881
|
-
* to operate.
|
|
1882
|
-
*
|
|
1883
|
-
*/
|
|
1884
|
-
// Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server
|
|
1885
|
-
// do the heavy lifting.
|
|
1886
|
-
async hydrateUtxosWL (utxos) {
|
|
1887
|
-
try {
|
|
1888
|
-
// Throw error if input is not an array.
|
|
1889
|
-
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
|
1890
|
-
|
|
1891
|
-
const response = await _this.axios.post(
|
|
1892
|
-
`${this.restURL}slp/hydrateUtxosWL`,
|
|
1893
|
-
{
|
|
1894
|
-
utxos: utxos
|
|
1895
|
-
},
|
|
1896
|
-
this.axiosOptions
|
|
1897
|
-
)
|
|
1898
|
-
|
|
1899
|
-
return response.data
|
|
1900
|
-
} catch (error) {
|
|
1901
|
-
if (error.response && error.response.data) throw error.response.data
|
|
1902
|
-
else throw error
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
/**
|
|
1907
|
-
* @api SLP.Utils.getStatus() getStatus()
|
|
1908
|
-
* @apiName getStatus
|
|
1909
|
-
* @apiGroup SLP Utils
|
|
1910
|
-
* @apiDescription Get the status and health of the SLPDB connected to bch-api.
|
|
1911
|
-
*
|
|
1912
|
-
* @apiExample Example usage:
|
|
1913
|
-
*
|
|
1914
|
-
* // Get the current blockheight of the SLPDB indexer.
|
|
1915
|
-
* (async () => {
|
|
1916
|
-
* try {
|
|
1917
|
-
* let validated = await bchjs.SLP.Utils.validateTxid(
|
|
1918
|
-
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
1919
|
-
* );
|
|
1920
|
-
* console.log(validated);
|
|
1921
|
-
* } catch (error) {
|
|
1922
|
-
* console.error(error);
|
|
1923
|
-
* }
|
|
1924
|
-
* })();
|
|
1925
|
-
*
|
|
1926
|
-
* // returns
|
|
1927
|
-
* [ { txid:
|
|
1928
|
-
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
1929
|
-
* valid: true } ]
|
|
1930
|
-
*
|
|
1931
|
-
*/
|
|
1932
|
-
async getStatus (txid) {
|
|
1933
|
-
const path = `${this.restURL}slp/status`
|
|
1934
|
-
|
|
1935
|
-
try {
|
|
1936
|
-
const response = await _this.axios.get(path, this.axiosOptions)
|
|
1937
|
-
// console.log(
|
|
1938
|
-
// `getStatus response.data: ${JSON.stringify(response.data, null, 2)}`
|
|
1939
|
-
// )
|
|
1940
|
-
|
|
1941
|
-
return response.data
|
|
1942
|
-
} catch (error) {
|
|
1943
|
-
if (error.response && error.response.data) throw error.response.data
|
|
1944
|
-
throw error
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
185
|
}
|
|
1948
186
|
|
|
1949
187
|
module.exports = Utils
|