@psf/bch-js 4.20.28 → 5.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/package.json +4 -4
- package/src/address.js +15 -0
- package/src/bch-js.js +5 -0
- package/src/ecash.js +47 -0
- package/src/psf-slp-indexer.js +332 -0
- package/src/slp/address.js +1 -1
- package/src/transaction.js +11 -2
- package/test/integration/blockchain.js +8 -0
- package/test/integration/chains/bchn/psf-slp-indexer.integration.js +105 -0
- package/test/integration/chains/bchn/slp.js +229 -225
- package/test/integration/slp.js +907 -901
- package/test/integration/transaction-integration.js +6 -5
- package/test/unit/ecash.js +56 -0
- package/test/unit/fixtures/psf-slp-indexer-mock.js +130 -0
- package/test/unit/psf-slp-indexer.js +264 -0
- package/test/unit/transaction-unit.js +19 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@psf/bch-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.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,12 +14,12 @@
|
|
|
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.
|
|
22
|
-
"test:integration:decatur:abc": "export RESTURL=http://192.168.
|
|
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
|
+
"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)
|
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/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
|
|
@@ -0,0 +1,332 @@
|
|
|
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
|
+
|
|
15
|
+
// let _this
|
|
16
|
+
|
|
17
|
+
class PsfSlpIndexer {
|
|
18
|
+
constructor (config) {
|
|
19
|
+
this.restURL = config.restURL
|
|
20
|
+
this.apiToken = config.apiToken
|
|
21
|
+
this.authToken = config.authToken
|
|
22
|
+
|
|
23
|
+
if (this.authToken) {
|
|
24
|
+
// Add Basic Authentication token to the authorization header.
|
|
25
|
+
this.axiosOptions = {
|
|
26
|
+
headers: {
|
|
27
|
+
authorization: this.authToken
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
// Add JWT token to the authorization header.
|
|
32
|
+
this.axiosOptions = {
|
|
33
|
+
headers: {
|
|
34
|
+
authorization: `Token ${this.apiToken}`
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Encapsulate dependencies
|
|
40
|
+
this.rawTransaction = new RawTransaction(config)
|
|
41
|
+
|
|
42
|
+
// _this = this
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @api PsfSlpIndexer.status() status()
|
|
47
|
+
* @apiName Status
|
|
48
|
+
* @apiGroup PSF SLP
|
|
49
|
+
* @apiDescription Return status from psf slp indexer.
|
|
50
|
+
*
|
|
51
|
+
* @apiExample Example usage:
|
|
52
|
+
* (async () => {
|
|
53
|
+
* try {
|
|
54
|
+
* let status = await bchjs.PsfSlpIndexer.status();
|
|
55
|
+
* console.log(status);
|
|
56
|
+
* } catch(error) {
|
|
57
|
+
* console.error(error)
|
|
58
|
+
* }
|
|
59
|
+
* })()
|
|
60
|
+
*
|
|
61
|
+
* {
|
|
62
|
+
* "status": {
|
|
63
|
+
* "startBlockHeight": 543376,
|
|
64
|
+
* "syncedBlockHeight": 723249,
|
|
65
|
+
* "chainBlockHeight": 722679
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
async status () {
|
|
71
|
+
try {
|
|
72
|
+
const response = await axios.get(
|
|
73
|
+
`${this.restURL}psf/slp/status`,
|
|
74
|
+
this.axiosOptions
|
|
75
|
+
)
|
|
76
|
+
return response.data
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error.response && error.response.data) throw error.response.data
|
|
79
|
+
else throw error
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @api PsfSlpIndexer.balance() balance()
|
|
85
|
+
* @apiName SLP Balance
|
|
86
|
+
* @apiGroup PSF SLP
|
|
87
|
+
* @apiDescription Return slp balance for a single address.
|
|
88
|
+
*
|
|
89
|
+
* @apiExample Example usage:
|
|
90
|
+
* (async () => {
|
|
91
|
+
* try {
|
|
92
|
+
* let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
|
|
93
|
+
* console.log(balance);
|
|
94
|
+
* } catch(error) {
|
|
95
|
+
* console.error(error)
|
|
96
|
+
* }
|
|
97
|
+
* })()
|
|
98
|
+
*
|
|
99
|
+
* {
|
|
100
|
+
* balance: {
|
|
101
|
+
* utxos: [
|
|
102
|
+
* {
|
|
103
|
+
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
|
104
|
+
* vout: 1,
|
|
105
|
+
* type: 'token',
|
|
106
|
+
* qty: '1800',
|
|
107
|
+
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
108
|
+
* address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
|
|
109
|
+
* }
|
|
110
|
+
* ],
|
|
111
|
+
* txs: [
|
|
112
|
+
* {
|
|
113
|
+
* txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
|
|
114
|
+
* height: 717796
|
|
115
|
+
* },
|
|
116
|
+
* {
|
|
117
|
+
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
|
118
|
+
* height: 717832
|
|
119
|
+
* }
|
|
120
|
+
* ],
|
|
121
|
+
* balances: [
|
|
122
|
+
* {
|
|
123
|
+
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
124
|
+
* qty: '1800'
|
|
125
|
+
* }
|
|
126
|
+
* ]
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
*
|
|
130
|
+
*/
|
|
131
|
+
async balance (address) {
|
|
132
|
+
try {
|
|
133
|
+
// Handle single address.
|
|
134
|
+
if (typeof address === 'string') {
|
|
135
|
+
const response = await axios.post(
|
|
136
|
+
`${this.restURL}psf/slp/address`,
|
|
137
|
+
{ address },
|
|
138
|
+
this.axiosOptions
|
|
139
|
+
)
|
|
140
|
+
return response.data
|
|
141
|
+
}
|
|
142
|
+
throw new Error('Input address must be a string.')
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.response && error.response.data) throw error.response.data
|
|
145
|
+
else throw error
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @api PsfSlpIndexer.tokenStats() tokenStats()
|
|
151
|
+
* @apiName Token Stats
|
|
152
|
+
* @apiGroup PSF SLP
|
|
153
|
+
* @apiDescription Return list stats for a single slp token.
|
|
154
|
+
*
|
|
155
|
+
* @apiExample Example usage:
|
|
156
|
+
* (async () => {
|
|
157
|
+
* try {
|
|
158
|
+
* let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
|
|
159
|
+
* console.log(tokenStats);
|
|
160
|
+
* } catch(error) {
|
|
161
|
+
* console.error(error)
|
|
162
|
+
* }
|
|
163
|
+
* })()
|
|
164
|
+
*
|
|
165
|
+
* {
|
|
166
|
+
* tokenData: {
|
|
167
|
+
* type: 1,
|
|
168
|
+
* ticker: 'TROUT',
|
|
169
|
+
* name: "Trout's test token",
|
|
170
|
+
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
171
|
+
* documentUri: 'troutsblog.com',
|
|
172
|
+
* documentHash: '',
|
|
173
|
+
* decimals: 2,
|
|
174
|
+
* mintBatonIsActive: true,
|
|
175
|
+
* tokensInCirculationBN: '100098953386',
|
|
176
|
+
* tokensInCirculationStr: '100098953386',
|
|
177
|
+
* blockCreated: 622414,
|
|
178
|
+
* totalBurned: '1046614',
|
|
179
|
+
* totalMinted: '100100000000'
|
|
180
|
+
* txs: [
|
|
181
|
+
* {
|
|
182
|
+
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
183
|
+
* height: 622414,
|
|
184
|
+
* type: 'GENESIS',
|
|
185
|
+
* qty: '100000000000'
|
|
186
|
+
* }
|
|
187
|
+
* ]
|
|
188
|
+
* }
|
|
189
|
+
* }
|
|
190
|
+
*
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
async tokenStats (tokenId) {
|
|
194
|
+
try {
|
|
195
|
+
// Handle single address.
|
|
196
|
+
if (typeof tokenId === 'string') {
|
|
197
|
+
const response = await axios.post(
|
|
198
|
+
`${this.restURL}psf/slp/token`,
|
|
199
|
+
{ tokenId },
|
|
200
|
+
this.axiosOptions
|
|
201
|
+
)
|
|
202
|
+
return response.data
|
|
203
|
+
}
|
|
204
|
+
throw new Error('Input tokenId must be a string.')
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (error.response && error.response.data) throw error.response.data
|
|
207
|
+
else throw error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @api PsfSlpIndexer.tx() tx()
|
|
213
|
+
* @apiName SLP Transaction Data
|
|
214
|
+
* @apiGroup PSF SLP
|
|
215
|
+
* @apiDescription Return slp transaction data.
|
|
216
|
+
*
|
|
217
|
+
* @apiExample Example usage:
|
|
218
|
+
* (async () => {
|
|
219
|
+
* try {
|
|
220
|
+
* let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
|
|
221
|
+
* console.log(txData);
|
|
222
|
+
* } catch(error) {
|
|
223
|
+
* console.error(error)
|
|
224
|
+
* }
|
|
225
|
+
* })()
|
|
226
|
+
*
|
|
227
|
+
* {
|
|
228
|
+
* txData: {
|
|
229
|
+
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
230
|
+
* hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
231
|
+
* version: 2,
|
|
232
|
+
* size: 339,
|
|
233
|
+
* locktime: 0,
|
|
234
|
+
* vin: [
|
|
235
|
+
* {
|
|
236
|
+
* txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
|
|
237
|
+
* vout: 0,
|
|
238
|
+
* scriptSig: {
|
|
239
|
+
* asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
|
|
240
|
+
* hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
|
|
241
|
+
* },
|
|
242
|
+
* sequence: 4294967295,
|
|
243
|
+
* address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
|
|
244
|
+
* value: 0.00051303,
|
|
245
|
+
* tokenQty: 0,
|
|
246
|
+
* tokenQtyStr: '0',
|
|
247
|
+
* tokenId: null
|
|
248
|
+
* }
|
|
249
|
+
* ],
|
|
250
|
+
* vout: [
|
|
251
|
+
* {
|
|
252
|
+
* value: 0,
|
|
253
|
+
* n: 0,
|
|
254
|
+
* scriptPubKey: {
|
|
255
|
+
* asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
|
|
256
|
+
* hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
|
|
257
|
+
* type: 'nulldata'
|
|
258
|
+
* },
|
|
259
|
+
* tokenQtyStr: '0',
|
|
260
|
+
* tokenQty: 0
|
|
261
|
+
* }
|
|
262
|
+
* ],
|
|
263
|
+
* hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
|
|
264
|
+
* blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
|
|
265
|
+
* confirmations: 97398,
|
|
266
|
+
* time: 1581773131,
|
|
267
|
+
* blocktime: 1581773131,
|
|
268
|
+
* blockheight: 622414,
|
|
269
|
+
* isSlpTx: true,
|
|
270
|
+
* tokenTxType: 'GENESIS',
|
|
271
|
+
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
|
272
|
+
* tokenType: 1,
|
|
273
|
+
* tokenTicker: 'TROUT',
|
|
274
|
+
* tokenName: "Trout's test token",
|
|
275
|
+
* tokenDecimals: 2,
|
|
276
|
+
* tokenUri: 'troutsblog.com',
|
|
277
|
+
* tokenDocHash: '',
|
|
278
|
+
* isValidSlp: true
|
|
279
|
+
* }
|
|
280
|
+
* }
|
|
281
|
+
*
|
|
282
|
+
*/
|
|
283
|
+
async tx (txid) {
|
|
284
|
+
try {
|
|
285
|
+
// Handle single address.
|
|
286
|
+
if (typeof txid === 'string') {
|
|
287
|
+
const response = await axios.post(
|
|
288
|
+
`${this.restURL}psf/slp/txid`,
|
|
289
|
+
{ txid },
|
|
290
|
+
this.axiosOptions
|
|
291
|
+
)
|
|
292
|
+
return response.data
|
|
293
|
+
}
|
|
294
|
+
throw new Error('Input txid must be a string.')
|
|
295
|
+
} catch (error) {
|
|
296
|
+
// console.log('error: ', error)
|
|
297
|
+
|
|
298
|
+
// Case: txid is not stored in the psf-slp-indexer tx database.
|
|
299
|
+
// Response: If it's not in the database, then it can be assumed the TX
|
|
300
|
+
// is not a token TX?
|
|
301
|
+
if (
|
|
302
|
+
error.response &&
|
|
303
|
+
error.response.data &&
|
|
304
|
+
error.response.data.error &&
|
|
305
|
+
error.response.data.error.includes('Key not found in database')
|
|
306
|
+
) {
|
|
307
|
+
// console.log(
|
|
308
|
+
// 'TX not found in psf-slp-indexer. Retrieving from full node.'
|
|
309
|
+
// )
|
|
310
|
+
|
|
311
|
+
// Get the TX Details from the full node.
|
|
312
|
+
const txDetails = await this.rawTransaction.getTxData(txid)
|
|
313
|
+
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
|
314
|
+
|
|
315
|
+
// TODO: Run the TX through decodeOpReturn() to see if it has a tokenID
|
|
316
|
+
// that is in the blacklist. If it is, then set isValidSlp = null to
|
|
317
|
+
// signal that it's state can not be determined.
|
|
318
|
+
|
|
319
|
+
// Assumption: transaction is not a valid SLP UTXO.
|
|
320
|
+
txDetails.isValidSlp = false
|
|
321
|
+
|
|
322
|
+
const outObj = {
|
|
323
|
+
txData: txDetails
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return outObj
|
|
327
|
+
} else throw error
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = PsfSlpIndexer
|
package/src/slp/address.js
CHANGED
|
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
|
|
|
36
36
|
* bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
|
|
37
37
|
* // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
|
|
38
38
|
*
|
|
39
|
-
* //
|
|
39
|
+
* // testnet legacy
|
|
40
40
|
* bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
|
|
41
41
|
* // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
|
|
42
42
|
*
|
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(
|
|
@@ -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,105 @@
|
|
|
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 for an SLP transaction', 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
|
+
assert.equal(result.txData.isValidSlp, true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should handle non-SLP transaction', async () => {
|
|
71
|
+
const txid =
|
|
72
|
+
'03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
|
|
73
|
+
|
|
74
|
+
const result = await bchjs.PsfSlpIndexer.tx(txid)
|
|
75
|
+
// console.log('result: ', result)
|
|
76
|
+
|
|
77
|
+
assert.property(result.txData, 'vin')
|
|
78
|
+
assert.property(result.txData, 'vout')
|
|
79
|
+
assert.property(result.txData, 'isValidSlp')
|
|
80
|
+
assert.equal(result.txData.isValidSlp, false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// FlexUSD transactions
|
|
84
|
+
// Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means
|
|
85
|
+
// the wallet will burn them. There is a TODO in the code. This test will
|
|
86
|
+
// need to be changed when it is done.
|
|
87
|
+
it('should handle a blacklisted token', async () => {
|
|
88
|
+
const txid =
|
|
89
|
+
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
|
|
90
|
+
|
|
91
|
+
const result = await bchjs.PsfSlpIndexer.tx(txid)
|
|
92
|
+
// console.log('result: ', result)
|
|
93
|
+
|
|
94
|
+
assert.property(result.txData, 'vin')
|
|
95
|
+
assert.property(result.txData, 'vout')
|
|
96
|
+
assert.property(result.txData, 'isValidSlp')
|
|
97
|
+
assert.equal(result.txData.isValidSlp, false)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Promise-based sleep function
|
|
103
|
+
function sleep (ms) {
|
|
104
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
105
|
+
}
|