@psf/bch-js 4.20.26 → 4.22.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/LICENSE.md CHANGED
@@ -1,7 +1,8 @@
1
- Copyright 2021 Chris Troutner
1
+ Copyright 2021 Permissionless Software Foundation contributors
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
5
5
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software
6
6
 
7
7
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psf/bch-js",
3
- "version": "4.20.26",
3
+ "version": "4.22.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": [
@@ -18,8 +18,8 @@
18
18
  "test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
19
19
  "test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
20
20
  "test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
21
- "test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
22
- "test:integration:decatur:abc": "export RESTURL=http://192.168.0.38:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
21
+ "test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
22
+ "test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
23
23
  "test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
24
24
  "test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
25
25
  "test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
package/src/address.js CHANGED
@@ -186,6 +186,21 @@ class Address {
186
186
  return cashAddress.split(':')[1]
187
187
  }
188
188
 
189
+ /**
190
+ * @api Address.toHash160() toHash160()
191
+ * @apiName toHash160
192
+ * @apiGroup Address
193
+ * @apiDescription Converts any address format to hash160
194
+ *
195
+ * @apiExample Example usage:
196
+ * // cash address mainnet p2pkh
197
+ * bchjs.Address.toHash160("bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh")
198
+ * // 573d93b475be4f1925f3b74ed951201b0147eac1
199
+ *
200
+ * // cash address mainnet p2sh
201
+ * bchjs.Address.toHash160("bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug")
202
+ * // 7dc85da64d1d93ef01ef62e0221c02f512e3942f
203
+ */
189
204
  // Converts any address format to hash160
190
205
  toHash160 (address) {
191
206
  const legacyAddress = this.toLegacyAddress(address)
@@ -223,7 +238,7 @@ class Address {
223
238
  * @api Address.hash160ToCash() hash160ToCash()
224
239
  * @apiName hash160ToCash
225
240
  * @apiGroup Address
226
- * @apiDescription Convert hash160 to cash address.
241
+ * @apiDescription Convert hash160 to cash address. Accepts either hexadecimal or buffer.
227
242
  *
228
243
  * @apiExample Example usage:
229
244
  * bchjs.Address.hash160ToCash("573d93b475be4f1925f3b74ed951201b0147eac1")
@@ -794,13 +809,13 @@ class Address {
794
809
  * @apiDescription Detect an addess from an OutputScript..
795
810
  *
796
811
  * @apiExample Example usage:
797
- * const script = bchjs.Script.encode([
812
+ * const scriptBuffer = bchjs.Script.encode([
798
813
  * Buffer.from("BOX", "ascii"),
799
814
  * bchjs.Script.opcodes.OP_CAT,
800
815
  * Buffer.from("BITBOX", "ascii"),
801
816
  * bchjs.Script.opcodes.OP_EQUAL
802
817
  * ]);
803
- * const p2sh_hash160 = bchjs.Crypto.hash160(script);
818
+ * const p2sh_hash160 = bchjs.Crypto.hash160(scriptBuffer);
804
819
  * const scriptPubKey = bchjs.Script.scriptHash.output.encode(p2sh_hash160);
805
820
  *
806
821
  * // mainnet address from output script
package/src/bch-js.js CHANGED
@@ -33,10 +33,12 @@ const Encryption = require('./encryption')
33
33
  const Utxo = require('./utxo')
34
34
  const Transaction = require('./transaction')
35
35
  const DSProof = require('./dsproof')
36
+ const Ecash = require('./ecash')
36
37
 
37
38
  // Indexers
38
39
  const Ninsight = require('./ninsight')
39
40
  const Electrumx = require('./electrumx')
41
+ const PsfSlpIndexer = require('./psf-slp-indexer')
40
42
 
41
43
  class BCHJS {
42
44
  constructor (config) {
@@ -118,6 +120,9 @@ class BCHJS {
118
120
  this.Transaction = new Transaction(libConfig)
119
121
 
120
122
  this.DSProof = new DSProof(libConfig)
123
+ this.eCash = new Ecash()
124
+
125
+ this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
121
126
  }
122
127
  }
123
128
 
package/src/crypto.js CHANGED
@@ -22,7 +22,7 @@ class Crypto {
22
22
  * // buffer from hex
23
23
  * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex')
24
24
  * bchjs.Crypto.sha256(buffer)
25
- * // <Buffer 26 b0 78 0a 68 3a 1e 09 8e 9c b8 cf a1 b0 92 42 28 25 00 97>
25
+ * // <Buffer 97 8c 09 dd 46 09 1d 19 22 fa 01 e9 f4 a9 75 b9 1a 37 1f 26 ba 83 99 de 27 d5 38 01 15 21 21 de>
26
26
  *
27
27
  * */
28
28
  // Translate address from any address format into a specific format.
@@ -60,7 +60,7 @@ class Crypto {
60
60
  * @api Crypto.hash256() hash256()
61
61
  * @apiName hash256
62
62
  * @apiGroup Crypto
63
- * @apiDescription Utility for creating double sha256 hash digests of data.
63
+ * @apiDescription Utility for creating double sha256 hash digests of buffer encoded data.
64
64
  *
65
65
  * @apiExample Example usage:
66
66
  * // buffer from hex
@@ -86,7 +86,7 @@ class Crypto {
86
86
  * @api Crypto.hash160() hash160()
87
87
  * @apiName hash160
88
88
  * @apiGroup Crypto
89
- * @apiDescription Utility for creating ripemd160(sha256()) hash digests of data.
89
+ * @apiDescription Utility for creating ripemd160(sha256()) hash digests of buffer encoded data.
90
90
  *
91
91
  * @apiExample Example usage:
92
92
  * // buffer from hex
package/src/ecash.js ADDED
@@ -0,0 +1,47 @@
1
+ /*
2
+ Utility library for converting units with eCash.
3
+ */
4
+
5
+ class eCash {
6
+ /**
7
+ * @api eCash.toSatoshi() toSatoshi()
8
+ * @apiName toSatoshi
9
+ * @apiGroup eCash
10
+ * @apiDescription
11
+ * Convert XEC units into satoshi units
12
+ *
13
+ * @apiExample Example usage:
14
+ * // convert 10,704.35 XEC to satoshis:
15
+ * bchjs.eCash.toSatoshi(10704.35)
16
+ * // 1070435
17
+ */
18
+ toSatoshi (xec) {
19
+ if (typeof xec !== 'number') {
20
+ throw new Error('input must be a floating number representing XEC')
21
+ }
22
+
23
+ return Math.floor(xec * 100)
24
+ }
25
+
26
+ /**
27
+ * @api eCash.toXec() toXec()
28
+ * @apiName toXec
29
+ * @apiGroup eCash
30
+ * @apiDescription
31
+ * Convert satoshi units to XEC units
32
+ *
33
+ * @apiExample Example usage:
34
+ * // convert 1,070,435 satoshis to XEC:
35
+ * bchjs.eCash.toSatoshi(1070435)
36
+ * // 10704.35
37
+ */
38
+ toXec (sats) {
39
+ if (typeof sats !== 'number') {
40
+ throw new Error('input must be a floating number representing satoshis')
41
+ }
42
+
43
+ return sats / 100
44
+ }
45
+ }
46
+
47
+ module.exports = eCash
package/src/electrumx.js CHANGED
@@ -142,7 +142,7 @@ class ElectrumX {
142
142
  * (async () => {
143
143
  * try {
144
144
  * let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf');
145
- * console.log(utxo);
145
+ * console.log(balance);
146
146
  * } catch(error) {
147
147
  * console.error(error)
148
148
  * }
@@ -159,7 +159,7 @@ class ElectrumX {
159
159
  * (async () => {
160
160
  * try {
161
161
  * let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']);
162
- * console.log(utxo);
162
+ * console.log(balance);
163
163
  * } catch(error) {
164
164
  * console.error(error)
165
165
  * }
package/src/price.js CHANGED
@@ -58,7 +58,7 @@ class Price {
58
58
  * let current = await bchjs.Price.getUsd();
59
59
  * console.log(current);
60
60
  * } catch(err) {
61
- * console.err(err)
61
+ * console.error(err)
62
62
  * }
63
63
  *})()
64
64
  *
@@ -94,7 +94,7 @@ class Price {
94
94
  * let current = await bchjs.Price.rates();
95
95
  * console.log(current);
96
96
  * } catch(err) {
97
- * console.err(err)
97
+ * console.error(err)
98
98
  * }
99
99
  *})()
100
100
  *
@@ -137,7 +137,7 @@ class Price {
137
137
  * let current = await bchjs.Price.getBchaUsd();
138
138
  * console.log(current);
139
139
  * } catch(err) {
140
- * console.err(err)
140
+ * console.error(err)
141
141
  * }
142
142
  *})()
143
143
  *
@@ -175,7 +175,7 @@ class Price {
175
175
  * let current = await bchjs.Price.getXecUsd();
176
176
  * console.log(current);
177
177
  * } catch(err) {
178
- * console.err(err)
178
+ * console.error(err)
179
179
  * }
180
180
  *})()
181
181
  *
@@ -210,7 +210,7 @@ class Price {
210
210
  * let current = await bchjs.Price.getBchUsd();
211
211
  * console.log(current);
212
212
  * } catch(err) {
213
- * console.err(err)
213
+ * console.error(err)
214
214
  * }
215
215
  *})()
216
216
  *
@@ -0,0 +1,292 @@
1
+ /*
2
+ This library interacts with the PSF slp indexer REST API endpoints operated
3
+ by FullStack.cash
4
+ */
5
+ // Public npm libraries
6
+ const axios = require('axios')
7
+
8
+ // let _this
9
+
10
+ class PsfSlpIndexer {
11
+ constructor (config) {
12
+ this.restURL = config.restURL
13
+ this.apiToken = config.apiToken
14
+ this.authToken = config.authToken
15
+
16
+ if (this.authToken) {
17
+ // Add Basic Authentication token to the authorization header.
18
+ this.axiosOptions = {
19
+ headers: {
20
+ authorization: this.authToken
21
+ }
22
+ }
23
+ } else {
24
+ // Add JWT token to the authorization header.
25
+ this.axiosOptions = {
26
+ headers: {
27
+ authorization: `Token ${this.apiToken}`
28
+ }
29
+ }
30
+ }
31
+
32
+ // _this = this
33
+ }
34
+
35
+ /**
36
+ * @api PsfSlpIndexer.status() status()
37
+ * @apiName Status
38
+ * @apiGroup PSF SLP
39
+ * @apiDescription Return status from psf slp indexer.
40
+ *
41
+ * @apiExample Example usage:
42
+ * (async () => {
43
+ * try {
44
+ * let status = await bchjs.PsfSlpIndexer.status();
45
+ * console.log(status);
46
+ * } catch(error) {
47
+ * console.error(error)
48
+ * }
49
+ * })()
50
+ *
51
+ * {
52
+ * "status": {
53
+ * "startBlockHeight": 543376,
54
+ * "syncedBlockHeight": 723249,
55
+ * "chainBlockHeight": 722679
56
+ * }
57
+ * }
58
+ *
59
+ */
60
+ async status () {
61
+ try {
62
+ const response = await axios.get(
63
+ `${this.restURL}psf/slp/status`,
64
+ this.axiosOptions
65
+ )
66
+ return response.data
67
+ } catch (error) {
68
+ if (error.response && error.response.data) throw error.response.data
69
+ else throw error
70
+ }
71
+ }
72
+
73
+ /**
74
+ * @api PsfSlpIndexer.balance() balance()
75
+ * @apiName SLP Balance
76
+ * @apiGroup PSF SLP
77
+ * @apiDescription Return slp balance for a single address.
78
+ *
79
+ * @apiExample Example usage:
80
+ * (async () => {
81
+ * try {
82
+ * let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
83
+ * console.log(balance);
84
+ * } catch(error) {
85
+ * console.error(error)
86
+ * }
87
+ * })()
88
+ *
89
+ * {
90
+ * balance: {
91
+ * utxos: [
92
+ * {
93
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
94
+ * vout: 1,
95
+ * type: 'token',
96
+ * qty: '1800',
97
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
98
+ * address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
99
+ * }
100
+ * ],
101
+ * txs: [
102
+ * {
103
+ * txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
104
+ * height: 717796
105
+ * },
106
+ * {
107
+ * txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
108
+ * height: 717832
109
+ * }
110
+ * ],
111
+ * balances: [
112
+ * {
113
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
114
+ * qty: '1800'
115
+ * }
116
+ * ]
117
+ * }
118
+ * }
119
+ *
120
+ */
121
+ async balance (address) {
122
+ try {
123
+ // Handle single address.
124
+ if (typeof address === 'string') {
125
+ const response = await axios.post(
126
+ `${this.restURL}psf/slp/address`,
127
+ { address },
128
+ this.axiosOptions
129
+ )
130
+ return response.data
131
+ }
132
+ throw new Error('Input address must be a string.')
133
+ } catch (error) {
134
+ if (error.response && error.response.data) throw error.response.data
135
+ else throw error
136
+ }
137
+ }
138
+
139
+ /**
140
+ * @api PsfSlpIndexer.tokenStats() tokenStats()
141
+ * @apiName Token Stats
142
+ * @apiGroup PSF SLP
143
+ * @apiDescription Return list stats for a single slp token.
144
+ *
145
+ * @apiExample Example usage:
146
+ * (async () => {
147
+ * try {
148
+ * let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
149
+ * console.log(tokenStats);
150
+ * } catch(error) {
151
+ * console.error(error)
152
+ * }
153
+ * })()
154
+ *
155
+ * {
156
+ * tokenData: {
157
+ * type: 1,
158
+ * ticker: 'TROUT',
159
+ * name: "Trout's test token",
160
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
161
+ * documentUri: 'troutsblog.com',
162
+ * documentHash: '',
163
+ * decimals: 2,
164
+ * mintBatonIsActive: true,
165
+ * tokensInCirculationBN: '100098953386',
166
+ * tokensInCirculationStr: '100098953386',
167
+ * blockCreated: 622414,
168
+ * totalBurned: '1046614',
169
+ * totalMinted: '100100000000'
170
+ * txs: [
171
+ * {
172
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
173
+ * height: 622414,
174
+ * type: 'GENESIS',
175
+ * qty: '100000000000'
176
+ * }
177
+ * ]
178
+ * }
179
+ * }
180
+ *
181
+ */
182
+
183
+ async tokenStats (tokenId) {
184
+ try {
185
+ // Handle single address.
186
+ if (typeof tokenId === 'string') {
187
+ const response = await axios.post(
188
+ `${this.restURL}psf/slp/token`,
189
+ { tokenId },
190
+ this.axiosOptions
191
+ )
192
+ return response.data
193
+ }
194
+ throw new Error('Input tokenId must be a string.')
195
+ } catch (error) {
196
+ if (error.response && error.response.data) throw error.response.data
197
+ else throw error
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @api PsfSlpIndexer.tx() tx()
203
+ * @apiName SLP Transaction Data
204
+ * @apiGroup PSF SLP
205
+ * @apiDescription Return slp transaction data.
206
+ *
207
+ * @apiExample Example usage:
208
+ * (async () => {
209
+ * try {
210
+ * let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
211
+ * console.log(txData);
212
+ * } catch(error) {
213
+ * console.error(error)
214
+ * }
215
+ * })()
216
+ *
217
+ * {
218
+ * txData: {
219
+ * txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
220
+ * hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
221
+ * version: 2,
222
+ * size: 339,
223
+ * locktime: 0,
224
+ * vin: [
225
+ * {
226
+ * txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
227
+ * vout: 0,
228
+ * scriptSig: {
229
+ * asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
230
+ * hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
231
+ * },
232
+ * sequence: 4294967295,
233
+ * address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
234
+ * value: 0.00051303,
235
+ * tokenQty: 0,
236
+ * tokenQtyStr: '0',
237
+ * tokenId: null
238
+ * }
239
+ * ],
240
+ * vout: [
241
+ * {
242
+ * value: 0,
243
+ * n: 0,
244
+ * scriptPubKey: {
245
+ * asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
246
+ * hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
247
+ * type: 'nulldata'
248
+ * },
249
+ * tokenQtyStr: '0',
250
+ * tokenQty: 0
251
+ * }
252
+ * ],
253
+ * hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
254
+ * blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
255
+ * confirmations: 97398,
256
+ * time: 1581773131,
257
+ * blocktime: 1581773131,
258
+ * blockheight: 622414,
259
+ * isSlpTx: true,
260
+ * tokenTxType: 'GENESIS',
261
+ * tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
262
+ * tokenType: 1,
263
+ * tokenTicker: 'TROUT',
264
+ * tokenName: "Trout's test token",
265
+ * tokenDecimals: 2,
266
+ * tokenUri: 'troutsblog.com',
267
+ * tokenDocHash: '',
268
+ * isValidSlp: true
269
+ * }
270
+ * }
271
+ *
272
+ */
273
+ async tx (txid) {
274
+ try {
275
+ // Handle single address.
276
+ if (typeof txid === 'string') {
277
+ const response = await axios.post(
278
+ `${this.restURL}psf/slp/txid`,
279
+ { txid },
280
+ this.axiosOptions
281
+ )
282
+ return response.data
283
+ }
284
+ throw new Error('Input txid must be a string.')
285
+ } catch (error) {
286
+ if (error.response && error.response.data) throw error.response.data
287
+ else throw error
288
+ }
289
+ }
290
+ }
291
+
292
+ module.exports = PsfSlpIndexer
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
36
36
  * bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
37
37
  * // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
38
38
  *
39
- * // tesnet legacy
39
+ * // testnet legacy
40
40
  * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
41
41
  * // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
42
42
  *
package/src/utxo.js CHANGED
@@ -183,6 +183,10 @@ class UTXO {
183
183
  */
184
184
  async get (address, useWhitelist = false) {
185
185
  try {
186
+ if (!address) {
187
+ throw new Error('Address must be an array or a string')
188
+ }
189
+
186
190
  // Convert address to an array if it is a string.
187
191
  if (typeof address === 'string') address = [address]
188
192
 
@@ -313,6 +313,14 @@ describe('#blockchain', () => {
313
313
  assert.isString(result)
314
314
  })
315
315
  })
316
+
317
+ describe('#getBlockchainInfo', () => {
318
+ it('should get info about the blockchain', async () => {
319
+ const result = await bchjs.Blockchain.getBlockchainInfo()
320
+
321
+ console.log(`blockchain info: ${JSON.stringify(result, null, 2)}`)
322
+ })
323
+ })
316
324
  })
317
325
 
318
326
  function sleep (ms) {
@@ -0,0 +1,74 @@
1
+ /*
2
+ Integration tests for the psf-slp-indexer.js library
3
+ */
4
+
5
+ const assert = require('chai').assert
6
+
7
+ const BCHJS = require('../../../../src/bch-js')
8
+ let bchjs
9
+
10
+ describe('#psf-slp-indexer', () => {
11
+ beforeEach(async () => {
12
+ // Introduce a delay so that the BVT doesn't trip the rate limits.
13
+ if (process.env.IS_USING_FREE_TIER) await sleep(3000)
14
+
15
+ bchjs = new BCHJS()
16
+ })
17
+
18
+ describe('#status', () => {
19
+ it('should return the status of the indexer.', async () => {
20
+ const result = await bchjs.PsfSlpIndexer.status()
21
+ // console.log('result: ', result)
22
+
23
+ assert.property(result.status, 'startBlockHeight')
24
+ assert.property(result.status, 'syncedBlockHeight')
25
+ assert.property(result.status, 'chainBlockHeight')
26
+ })
27
+ })
28
+
29
+ describe('#balance', () => {
30
+ it('should get balance data for an address.', async () => {
31
+ const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
32
+
33
+ const result = await bchjs.PsfSlpIndexer.balance(addr)
34
+ // console.log('result: ', result)
35
+
36
+ assert.property(result.balance, 'utxos')
37
+ assert.property(result.balance, 'txs')
38
+ assert.property(result.balance, 'balances')
39
+ })
40
+ })
41
+
42
+ describe('#tokenStats', () => {
43
+ it('should get stats on a token', async () => {
44
+ const tokenId =
45
+ '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
46
+
47
+ const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
48
+ // console.log('result: ', result)
49
+
50
+ assert.property(result.tokenData, 'documentUri')
51
+ assert.property(result.tokenData, 'txs')
52
+ assert.property(result.tokenData, 'totalBurned')
53
+ })
54
+ })
55
+
56
+ describe('#tx', () => {
57
+ it('should get hydrated tx data', async () => {
58
+ const txid =
59
+ '83361c34cac2ea7f9ca287fca57a96cc0763719f0cdf4850f9696c1e68eb635c'
60
+
61
+ const result = await bchjs.PsfSlpIndexer.tx(txid)
62
+ // console.log('result: ', result)
63
+
64
+ assert.property(result.txData, 'vin')
65
+ assert.property(result.txData, 'vout')
66
+ assert.property(result.txData, 'isValidSlp')
67
+ })
68
+ })
69
+ })
70
+
71
+ // Promise-based sleep function
72
+ function sleep (ms) {
73
+ return new Promise(resolve => setTimeout(resolve, ms))
74
+ }
@@ -0,0 +1,56 @@
1
+ /*
2
+ Unit tests for eCash library.
3
+ */
4
+
5
+ // Global npm libraries
6
+ const assert = require('chai').assert
7
+ const Ecash = require('../../src/ecash')
8
+ const uut = new Ecash()
9
+
10
+ describe('#eCash', () => {
11
+ describe('#toSatoshi', () => {
12
+ it('should convert XEC to satoshis', () => {
13
+ const xec = 10704.35
14
+
15
+ const result = uut.toSatoshi(xec)
16
+
17
+ assert.equal(result, 1070435)
18
+ })
19
+
20
+ it('should throw an error if input is not a number', () => {
21
+ try {
22
+ uut.toSatoshi('test')
23
+
24
+ assert.fail('Unexpected code path')
25
+ } catch (err) {
26
+ assert.equal(
27
+ err.message,
28
+ 'input must be a floating number representing XEC'
29
+ )
30
+ }
31
+ })
32
+ })
33
+
34
+ describe('#toXec', () => {
35
+ it('should convert satoshis to XEC', () => {
36
+ const sats = 1070435
37
+
38
+ const result = uut.toXec(sats)
39
+
40
+ assert.equal(result, 10704.35)
41
+ })
42
+
43
+ it('should throw an error if input is not a number', () => {
44
+ try {
45
+ uut.toXec('test')
46
+
47
+ assert.fail('Unexpected code path')
48
+ } catch (err) {
49
+ assert.equal(
50
+ err.message,
51
+ 'input must be a floating number representing satoshis'
52
+ )
53
+ }
54
+ })
55
+ })
56
+ })
@@ -0,0 +1,130 @@
1
+ /*
2
+ Mocking data for unit tests for the PSF SLP INDEXER library.
3
+ */
4
+
5
+ 'use strict'
6
+
7
+ const tokenStats = {
8
+ tokenData: {
9
+ type: 1,
10
+ ticker: 'TP03',
11
+ name: 'Test Plugin 03',
12
+ tokenId: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
13
+ documentUri: 'fullstack.cash',
14
+ documentHash: '',
15
+ decimals: 0,
16
+ mintBatonIsActive: false,
17
+ tokensInCirculationBN: '1',
18
+ tokensInCirculationStr: '1',
19
+ blockCreated: 722420,
20
+ totalBurned: '0',
21
+ totalMinted: '1',
22
+ txs: [
23
+ {
24
+ txid: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
25
+ height: 722420,
26
+ type: 'GENESIS',
27
+ qty: '1'
28
+ }
29
+ ]
30
+ }
31
+ }
32
+
33
+ const txData = {
34
+ txData: {
35
+ txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
36
+ hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
37
+ version: 2,
38
+ size: 339,
39
+ locktime: 0,
40
+ vin: [
41
+ {
42
+ txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
43
+ vout: 0,
44
+ scriptSig: {
45
+ asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
46
+ hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
47
+ },
48
+ sequence: 4294967295,
49
+ address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
50
+ value: 0.00051303,
51
+ tokenQty: 0,
52
+ tokenQtyStr: '0',
53
+ tokenId: null
54
+ }
55
+ ],
56
+ vout: [
57
+ {
58
+ value: 0,
59
+ n: 0,
60
+ scriptPubKey: {
61
+ asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
62
+ hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
63
+ type: 'nulldata'
64
+ },
65
+ tokenQtyStr: '0',
66
+ tokenQty: 0
67
+ }
68
+ ],
69
+ hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
70
+ blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
71
+ confirmations: 97398,
72
+ time: 1581773131,
73
+ blocktime: 1581773131,
74
+ blockheight: 622414,
75
+ isSlpTx: true,
76
+ tokenTxType: 'GENESIS',
77
+ tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
78
+ tokenType: 1,
79
+ tokenTicker: 'TROUT',
80
+ tokenName: "Trout's test token",
81
+ tokenDecimals: 2,
82
+ tokenUri: 'troutsblog.com',
83
+ tokenDocHash: '',
84
+ isValidSlp: true
85
+ }
86
+ }
87
+ const balance = {
88
+ balance: {
89
+ utxos: [
90
+ {
91
+ txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
92
+ vout: 1,
93
+ type: 'token',
94
+ qty: '1800',
95
+ tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
96
+ address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
97
+ }
98
+ ],
99
+ txs: [
100
+ {
101
+ txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
102
+ height: 717796
103
+ },
104
+ {
105
+ txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
106
+ height: 717832
107
+ }
108
+ ],
109
+ balances: [
110
+ {
111
+ tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
112
+ qty: '1800'
113
+ }
114
+ ]
115
+ }
116
+ }
117
+
118
+ const status = {
119
+ status: {
120
+ startBlockHeight: 543376,
121
+ syncedBlockHeight: 722860,
122
+ chainBlockHeight: 722679
123
+ }
124
+ }
125
+ module.exports = {
126
+ tokenStats,
127
+ txData,
128
+ balance,
129
+ status
130
+ }
@@ -0,0 +1,258 @@
1
+ const chai = require('chai')
2
+ const assert = chai.assert
3
+ const axios = require('axios')
4
+ const sinon = require('sinon')
5
+
6
+ const BCHJS = require('../../src/bch-js')
7
+ const bchjs = new BCHJS()
8
+
9
+ const mockData = require('./fixtures/psf-slp-indexer-mock')
10
+
11
+ describe('#PsfSlpIndexer', () => {
12
+ let sandbox
13
+ beforeEach(() => (sandbox = sinon.createSandbox()))
14
+ afterEach(() => sandbox.restore())
15
+
16
+ describe('#status', () => {
17
+ it('should GET status', async () => {
18
+ // Stub the network call.
19
+ sandbox.stub(axios, 'get').resolves({ data: mockData.status })
20
+
21
+ const result = await bchjs.PsfSlpIndexer.status()
22
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
23
+ assert.property(result, 'status')
24
+ assert.property(result.status, 'startBlockHeight')
25
+ assert.property(result.status, 'syncedBlockHeight')
26
+ assert.property(result.status, 'chainBlockHeight')
27
+ })
28
+
29
+ it('should handle axios error', async () => {
30
+ try {
31
+ // Stub the network call.
32
+ sandbox.stub(axios, 'get').throws(new Error('test error'))
33
+
34
+ await bchjs.PsfSlpIndexer.status()
35
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
36
+ assert.equal(true, false, 'Unexpected result!')
37
+ } catch (err) {
38
+ assert.include(err.message, 'test error')
39
+ }
40
+ })
41
+ it('should handle request error', async () => {
42
+ try {
43
+ // Stub the network call.
44
+ const testErr = new Error()
45
+ testErr.response = { data: { status: 422 } }
46
+ sandbox.stub(axios, 'get').throws(testErr)
47
+
48
+ await bchjs.PsfSlpIndexer.status()
49
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
50
+ assert.equal(true, false, 'Unexpected result!')
51
+ } catch (err) {
52
+ assert.equal(err.status, 422)
53
+ }
54
+ })
55
+ })
56
+
57
+ describe('#balance', () => {
58
+ it('should GET balance', async () => {
59
+ // Stub the network call.
60
+ sandbox.stub(axios, 'post').resolves({ data: mockData.balance })
61
+ const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
62
+ const result = await bchjs.PsfSlpIndexer.balance(addr)
63
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
64
+ assert.property(result, 'balance')
65
+
66
+ assert.property(result.balance, 'utxos')
67
+ assert.property(result.balance, 'txs')
68
+ assert.property(result.balance, 'balances')
69
+ assert.isArray(result.balance.utxos)
70
+ assert.isArray(result.balance.txs)
71
+ assert.isArray(result.balance.balances)
72
+ })
73
+ it('should throw an error for improper input', async () => {
74
+ try {
75
+ const addr = 12345
76
+
77
+ await bchjs.PsfSlpIndexer.balance(addr)
78
+ assert.equal(true, false, 'Unexpected result!')
79
+ } catch (err) {
80
+ // console.log(`err: `, err)
81
+ assert.include(err.message, 'Input address must be a string.')
82
+ }
83
+ })
84
+ it('should handle axios error', async () => {
85
+ try {
86
+ // Stub the network call.
87
+ sandbox.stub(axios, 'post').throws(new Error('test error'))
88
+
89
+ const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
90
+
91
+ await bchjs.PsfSlpIndexer.balance(addr)
92
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
93
+ assert.equal(true, false, 'Unexpected result!')
94
+ } catch (err) {
95
+ assert.include(err.message, 'test error')
96
+ }
97
+ })
98
+ it('should handle request error', async () => {
99
+ try {
100
+ // Stub the network call.
101
+ const testErr = new Error()
102
+ testErr.response = { data: { status: 422 } }
103
+ sandbox.stub(axios, 'post').throws(testErr)
104
+
105
+ const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
106
+
107
+ await bchjs.PsfSlpIndexer.balance(addr)
108
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
109
+ assert.equal(true, false, 'Unexpected result!')
110
+ } catch (err) {
111
+ assert.equal(err.status, 422)
112
+ }
113
+ })
114
+ })
115
+
116
+ describe('#tokenStats', () => {
117
+ it('should GET token stats', async () => {
118
+ // Stub the network call.
119
+ sandbox.stub(axios, 'post').resolves({ data: mockData.tokenStats })
120
+
121
+ const tokenId =
122
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
123
+ const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
124
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
125
+ assert.property(result, 'tokenData')
126
+ assert.property(result.tokenData, 'type')
127
+ assert.property(result.tokenData, 'ticker')
128
+ assert.property(result.tokenData, 'name')
129
+ assert.property(result.tokenData, 'tokenId')
130
+ assert.property(result.tokenData, 'documentUri')
131
+ assert.property(result.tokenData, 'documentHash')
132
+ assert.property(result.tokenData, 'decimals')
133
+ assert.property(result.tokenData, 'mintBatonIsActive')
134
+ assert.property(result.tokenData, 'tokensInCirculationBN')
135
+ assert.property(result.tokenData, 'tokensInCirculationStr')
136
+ assert.property(result.tokenData, 'blockCreated')
137
+ assert.property(result.tokenData, 'totalBurned')
138
+ assert.property(result.tokenData, 'totalMinted')
139
+ assert.property(result.tokenData, 'txs')
140
+ })
141
+ it('should throw an error for improper input', async () => {
142
+ try {
143
+ const tokenId = 12345
144
+
145
+ await bchjs.PsfSlpIndexer.tokenStats(tokenId)
146
+ assert.equal(true, false, 'Unexpected result!')
147
+ } catch (err) {
148
+ // console.log(`err: `, err)
149
+ assert.include(err.message, 'Input tokenId must be a string.')
150
+ }
151
+ })
152
+ it('should handle axios error', async () => {
153
+ try {
154
+ // Stub the network call.
155
+ sandbox.stub(axios, 'post').throws(new Error('test error'))
156
+
157
+ const tokenId =
158
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
159
+ await bchjs.PsfSlpIndexer.tokenStats(tokenId)
160
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
161
+ assert.equal(true, false, 'Unexpected result!')
162
+ } catch (err) {
163
+ assert.include(err.message, 'test error')
164
+ }
165
+ })
166
+ it('should handle request error', async () => {
167
+ try {
168
+ // Stub the network call.
169
+ const testErr = new Error()
170
+ testErr.response = { data: { status: 422 } }
171
+ sandbox.stub(axios, 'post').throws(testErr)
172
+
173
+ const tokenId =
174
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
175
+ await bchjs.PsfSlpIndexer.tokenStats(tokenId)
176
+ assert.equal(true, false, 'Unexpected result!')
177
+ } catch (err) {
178
+ assert.equal(err.status, 422)
179
+ }
180
+ })
181
+ })
182
+
183
+ describe('#tx', () => {
184
+ it('should GET transaction data', async () => {
185
+ // Stub the network call.
186
+ sandbox.stub(axios, 'post').resolves({ data: mockData.txData })
187
+
188
+ const txid =
189
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
190
+ const result = await bchjs.PsfSlpIndexer.tx(txid)
191
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
192
+ assert.property(result, 'txData')
193
+ assert.property(result.txData, 'txid')
194
+ assert.property(result.txData, 'hash')
195
+ assert.property(result.txData, 'version')
196
+ assert.property(result.txData, 'size')
197
+ assert.property(result.txData, 'locktime')
198
+ assert.property(result.txData, 'vin')
199
+ assert.property(result.txData, 'vout')
200
+ assert.property(result.txData, 'hex')
201
+ assert.property(result.txData, 'blockhash')
202
+ assert.property(result.txData, 'confirmations')
203
+ assert.property(result.txData, 'time')
204
+ assert.property(result.txData, 'blocktime')
205
+ assert.property(result.txData, 'blockheight')
206
+ assert.property(result.txData, 'isSlpTx')
207
+ assert.property(result.txData, 'tokenTxType')
208
+ assert.property(result.txData, 'tokenId')
209
+ assert.property(result.txData, 'tokenType')
210
+ assert.property(result.txData, 'tokenTicker')
211
+ assert.property(result.txData, 'tokenName')
212
+ assert.property(result.txData, 'tokenDecimals')
213
+ assert.property(result.txData, 'tokenUri')
214
+ assert.property(result.txData, 'tokenDocHash')
215
+ assert.property(result.txData, 'isValidSlp')
216
+ })
217
+ it('should throw an error for improper input', async () => {
218
+ try {
219
+ const txid = 12345
220
+
221
+ await bchjs.PsfSlpIndexer.tx(txid)
222
+ assert.equal(true, false, 'Unexpected result!')
223
+ } catch (err) {
224
+ // console.log(`err: `, err)
225
+ assert.include(err.message, 'Input txid must be a string.')
226
+ }
227
+ })
228
+ it('should handle axios error', async () => {
229
+ try {
230
+ // Stub the network call.
231
+ sandbox.stub(axios, 'post').throws(new Error('test error'))
232
+
233
+ const txid =
234
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
235
+ await bchjs.PsfSlpIndexer.tx(txid)
236
+ // console.log(`result: ${JSON.stringify(result, null, 2)}`)
237
+ assert.equal(true, false, 'Unexpected result!')
238
+ } catch (err) {
239
+ assert.include(err.message, 'test error')
240
+ }
241
+ })
242
+ it('should handle request error', async () => {
243
+ try {
244
+ // Stub the network call.
245
+ const testErr = new Error()
246
+ testErr.response = { data: { status: 422 } }
247
+ sandbox.stub(axios, 'post').throws(testErr)
248
+
249
+ const txid =
250
+ 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
251
+ await bchjs.PsfSlpIndexer.tx(txid)
252
+ assert.equal(true, false, 'Unexpected result!')
253
+ } catch (err) {
254
+ assert.equal(err.status, 422)
255
+ }
256
+ })
257
+ })
258
+ })