@psf/bch-js 5.4.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.on-save.json +8 -0
- package/LICENSE.md +1 -2
- package/package.json +6 -6
- package/src/slp/utils.js +0 -1241
- package/src/utxo.js +0 -270
- package/test/integration/slp.js +0 -470
- package/test/unit/slp-utils.js +0 -724
- package/test/unit/utxo-unit.js +0 -131
package/src/slp/utils.js
CHANGED
|
@@ -41,863 +41,6 @@ class Utils {
|
|
|
41
41
|
this.util = new Util(config)
|
|
42
42
|
}
|
|
43
43
|
|
|
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
44
|
/**
|
|
902
45
|
* @api SLP.Utils.decodeOpReturn() decodeOpReturn()
|
|
903
46
|
* @apiName decodeOpReturn
|
|
@@ -1176,107 +319,6 @@ class Utils {
|
|
|
1176
319
|
}
|
|
1177
320
|
}
|
|
1178
321
|
|
|
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
322
|
// This is a private function that is called by tokenUtxoDetails().
|
|
1281
323
|
// It loops through an array of UTXOs and tries to hydrate them with SLP
|
|
1282
324
|
// token information from the OP_RETURN data.
|
|
@@ -1661,289 +703,6 @@ class Utils {
|
|
|
1661
703
|
throw error
|
|
1662
704
|
}
|
|
1663
705
|
}
|
|
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
706
|
}
|
|
1948
707
|
|
|
1949
708
|
module.exports = Utils
|