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