psf-bch-api 1.1.0 → 1.3.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.
Files changed (56) hide show
  1. package/.env-local +9 -0
  2. package/README.md +22 -0
  3. package/bin/server.js +24 -1
  4. package/package.json +6 -2
  5. package/src/adapters/fulcrum-api.js +124 -0
  6. package/src/adapters/full-node-rpc.js +2 -6
  7. package/src/adapters/index.js +4 -0
  8. package/src/adapters/slp-indexer-api.js +124 -0
  9. package/src/config/env/common.js +45 -24
  10. package/src/config/x402.js +43 -0
  11. package/src/controllers/index.js +3 -1
  12. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  13. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  14. package/src/controllers/rest-api/full-node/blockchain/controller.js +26 -26
  15. package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +5 -1
  16. package/src/controllers/rest-api/full-node/control/controller.js +68 -0
  17. package/src/controllers/rest-api/full-node/control/router.js +51 -0
  18. package/src/controllers/rest-api/full-node/dsproof/controller.js +90 -0
  19. package/src/controllers/rest-api/full-node/dsproof/router.js +51 -0
  20. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  21. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  22. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  23. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  24. package/src/controllers/rest-api/index.js +33 -2
  25. package/src/controllers/rest-api/slp/controller.js +218 -0
  26. package/src/controllers/rest-api/slp/router.js +55 -0
  27. package/src/controllers/timer-controller.js +1 -1
  28. package/src/use-cases/fulcrum-use-cases.js +155 -0
  29. package/src/use-cases/full-node-control-use-cases.js +24 -0
  30. package/src/use-cases/full-node-dsproof-use-cases.js +24 -0
  31. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  32. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  33. package/src/use-cases/index.js +12 -0
  34. package/src/use-cases/slp-use-cases.js +321 -0
  35. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  36. package/test/unit/controllers/control-controller-unit.js +88 -0
  37. package/test/unit/controllers/dsproof-controller-unit.js +117 -0
  38. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  39. package/test/unit/controllers/mining-controller-unit.js +139 -0
  40. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  41. package/test/unit/controllers/rest-api-index-unit.js +76 -6
  42. package/test/unit/controllers/slp-controller-unit.js +312 -0
  43. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  44. package/test/unit/use-cases/full-node-control-use-cases-unit.js +53 -0
  45. package/test/unit/use-cases/full-node-dsproof-use-cases-unit.js +54 -0
  46. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  47. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  48. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  49. package/src/entities/event.js +0 -71
  50. package/test/integration/api/event-integration.js +0 -250
  51. package/test/integration/api/req-integration.js +0 -173
  52. package/test/integration/api/subscription-integration.js +0 -198
  53. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  54. package/test/integration/use-cases/publish-event-integration.js +0 -104
  55. package/test/integration/use-cases/query-events-integration.js +0 -95
  56. package/test/unit/entities/event-unit.js +0 -139
@@ -0,0 +1,333 @@
1
+ /*
2
+ REST API Controller for the /full-node/rawtransactions routes.
3
+ */
4
+
5
+ import wlogger from '../../../../adapters/wlogger.js'
6
+
7
+ class RawTransactionsRESTController {
8
+ constructor (localConfig = {}) {
9
+ this.adapters = localConfig.adapters
10
+ if (!this.adapters) {
11
+ throw new Error(
12
+ 'Instance of Adapters library required when instantiating RawTransactions REST Controller.'
13
+ )
14
+ }
15
+
16
+ this.useCases = localConfig.useCases
17
+ if (!this.useCases || !this.useCases.rawtransactions) {
18
+ throw new Error(
19
+ 'Instance of RawTransactions use cases required when instantiating RawTransactions REST Controller.'
20
+ )
21
+ }
22
+
23
+ this.rawtransactionsUseCases = this.useCases.rawtransactions
24
+
25
+ // Bind functions
26
+ this.root = this.root.bind(this)
27
+ this.decodeRawTransactionSingle = this.decodeRawTransactionSingle.bind(this)
28
+ this.decodeRawTransactionBulk = this.decodeRawTransactionBulk.bind(this)
29
+ this.decodeScriptSingle = this.decodeScriptSingle.bind(this)
30
+ this.decodeScriptBulk = this.decodeScriptBulk.bind(this)
31
+ this.getRawTransactionSingle = this.getRawTransactionSingle.bind(this)
32
+ this.getRawTransactionBulk = this.getRawTransactionBulk.bind(this)
33
+ this.sendRawTransactionSingle = this.sendRawTransactionSingle.bind(this)
34
+ this.sendRawTransactionBulk = this.sendRawTransactionBulk.bind(this)
35
+ this.handleError = this.handleError.bind(this)
36
+ }
37
+
38
+ /**
39
+ * @api {get} /v6/full-node/rawtransactions/ Service status
40
+ * @apiName RawTransactionsRoot
41
+ * @apiGroup RawTransactions
42
+ *
43
+ * @apiDescription Returns the status of the rawtransactions service.
44
+ *
45
+ * @apiSuccess {String} status Service identifier
46
+ */
47
+ async root (req, res) {
48
+ return res.status(200).json({ status: 'rawtransactions' })
49
+ }
50
+
51
+ /**
52
+ * @api {get} /v6/full-node/rawtransactions/decodeRawTransaction/:hex Decode Single Raw Transaction
53
+ * @apiName DecodeSingleRawTransaction
54
+ * @apiGroup RawTransactions
55
+ * @apiDescription Return a JSON object representing the serialized, hex-encoded transaction.
56
+ *
57
+ * @apiParam {String} hex Hex-encoded transaction
58
+ *
59
+ * @apiExample Example usage:
60
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json"
61
+ */
62
+ async decodeRawTransactionSingle (req, res) {
63
+ try {
64
+ const hex = req.params.hex
65
+
66
+ if (!hex || hex === '') {
67
+ return res.status(400).json({ error: 'hex can not be empty' })
68
+ }
69
+
70
+ const result = await this.rawtransactionsUseCases.decodeRawTransaction({ hex })
71
+ return res.status(200).json(result)
72
+ } catch (err) {
73
+ return this.handleError(err, res)
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @api {post} /v6/full-node/rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions
79
+ * @apiName DecodeBulkRawTransactions
80
+ * @apiGroup RawTransactions
81
+ * @apiDescription Return bulk hex encoded transaction.
82
+ *
83
+ * @apiParam {String[]} hexes Array of hex-encoded transactions
84
+ *
85
+ * @apiExample Example usage:
86
+ * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
87
+ */
88
+ async decodeRawTransactionBulk (req, res) {
89
+ try {
90
+ const hexes = req.body.hexes
91
+
92
+ if (!Array.isArray(hexes)) {
93
+ return res.status(400).json({ error: 'hexes must be an array' })
94
+ }
95
+
96
+ if (!this.adapters.fullNode.validateArraySize(hexes.length)) {
97
+ return res.status(400).json({ error: 'Array too large.' })
98
+ }
99
+
100
+ // Validate each element in the array
101
+ for (const hex of hexes) {
102
+ if (!hex || hex === '') {
103
+ return res.status(400).json({ error: 'Encountered empty hex' })
104
+ }
105
+ }
106
+
107
+ const result = await this.rawtransactionsUseCases.decodeRawTransactions({ hexes })
108
+ return res.status(200).json(result)
109
+ } catch (err) {
110
+ return this.handleError(err, res)
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @api {get} /v6/full-node/rawtransactions/decodeScript/:hex Decode Single Script
116
+ * @apiName DecodeSingleScript
117
+ * @apiGroup RawTransactions
118
+ * @apiDescription Decode a hex-encoded script.
119
+ *
120
+ * @apiParam {String} hex Hex-encoded script
121
+ *
122
+ * @apiExample Example usage:
123
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json"
124
+ */
125
+ async decodeScriptSingle (req, res) {
126
+ try {
127
+ const hex = req.params.hex
128
+
129
+ if (!hex || hex === '') {
130
+ return res.status(400).json({ error: 'hex can not be empty' })
131
+ }
132
+
133
+ const result = await this.rawtransactionsUseCases.decodeScript({ hex })
134
+ return res.status(200).json(result)
135
+ } catch (err) {
136
+ return this.handleError(err, res)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * @api {post} /v6/full-node/rawtransactions/decodeScript Bulk Decode Script
142
+ * @apiName DecodeBulkScript
143
+ * @apiGroup RawTransactions
144
+ * @apiDescription Decode multiple hex-encoded scripts.
145
+ *
146
+ * @apiParam {String[]} hexes Array of hex-encoded scripts
147
+ *
148
+ * @apiExample Example usage:
149
+ * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeScript" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
150
+ */
151
+ async decodeScriptBulk (req, res) {
152
+ try {
153
+ const hexes = req.body.hexes
154
+
155
+ if (!Array.isArray(hexes)) {
156
+ return res.status(400).json({ error: 'hexes must be an array' })
157
+ }
158
+
159
+ if (!this.adapters.fullNode.validateArraySize(hexes.length)) {
160
+ return res.status(400).json({ error: 'Array too large.' })
161
+ }
162
+
163
+ // Validate each hex in the array
164
+ for (const hex of hexes) {
165
+ if (!hex || hex === '') {
166
+ return res.status(400).json({ error: 'Encountered empty hex' })
167
+ }
168
+ }
169
+
170
+ const result = await this.rawtransactionsUseCases.decodeScripts({ hexes })
171
+ return res.status(200).json(result)
172
+ } catch (err) {
173
+ return this.handleError(err, res)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @api {get} /v6/full-node/rawtransactions/getRawTransaction/:txid Get Raw Transaction
179
+ * @apiName GetRawTransaction
180
+ * @apiGroup RawTransactions
181
+ * @apiDescription Return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.
182
+ *
183
+ * @apiParam {String} txid Transaction ID
184
+ * @apiParam {Boolean} verbose Return verbose data (default false)
185
+ *
186
+ * @apiExample Example usage:
187
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json"
188
+ */
189
+ async getRawTransactionSingle (req, res) {
190
+ try {
191
+ const txid = req.params.txid
192
+ const verbose = req.query.verbose === 'true'
193
+
194
+ if (!txid || txid === '') {
195
+ return res.status(400).json({ error: 'txid can not be empty' })
196
+ }
197
+
198
+ if (txid.length !== 64) {
199
+ return res.status(400).json({
200
+ error: `parameter 1 must be of length 64 (not ${txid.length})`
201
+ })
202
+ }
203
+
204
+ const result = await this.rawtransactionsUseCases.getRawTransactionWithHeight({ txid, verbose })
205
+ return res.status(200).json(result)
206
+ } catch (err) {
207
+ return this.handleError(err, res)
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @api {post} /v6/full-node/rawtransactions/getRawTransaction Get Bulk Raw Transactions
213
+ * @apiName GetBulkRawTransactions
214
+ * @apiGroup RawTransactions
215
+ * @apiDescription Return the raw transaction data for multiple transactions. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.
216
+ *
217
+ * @apiParam {String[]} txids Array of transaction IDs
218
+ * @apiParam {Boolean} verbose Return verbose data (default false)
219
+ *
220
+ * @apiExample Example usage:
221
+ * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}'
222
+ */
223
+ async getRawTransactionBulk (req, res) {
224
+ try {
225
+ const txids = req.body.txids
226
+ const verbose = !!req.body.verbose
227
+
228
+ if (!Array.isArray(txids)) {
229
+ return res.status(400).json({ error: 'txids must be an array' })
230
+ }
231
+
232
+ if (!this.adapters.fullNode.validateArraySize(txids.length)) {
233
+ return res.status(400).json({ error: 'Array too large.' })
234
+ }
235
+
236
+ // Validate each txid in the array
237
+ for (const txid of txids) {
238
+ if (!txid || txid === '') {
239
+ return res.status(400).json({ error: 'Encountered empty TXID' })
240
+ }
241
+
242
+ if (txid.length !== 64) {
243
+ return res.status(400).json({
244
+ error: `parameter 1 must be of length 64 (not ${txid.length})`
245
+ })
246
+ }
247
+ }
248
+
249
+ const result = await this.rawtransactionsUseCases.getRawTransactions({ txids, verbose })
250
+ return res.status(200).json(result)
251
+ } catch (err) {
252
+ return this.handleError(err, res)
253
+ }
254
+ }
255
+
256
+ /**
257
+ * @api {get} /v6/full-node/rawtransactions/sendRawTransaction/:hex Send Single Raw Transaction
258
+ * @apiName SendSingleRawTransaction
259
+ * @apiGroup RawTransactions
260
+ * @apiDescription Submits single raw transaction (serialized, hex-encoded) to local node and network.
261
+ *
262
+ * @apiParam {String} hex Hex-encoded transaction
263
+ *
264
+ * @apiExample Example usage:
265
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: application/json"
266
+ */
267
+ async sendRawTransactionSingle (req, res) {
268
+ try {
269
+ const hex = req.params.hex
270
+
271
+ if (typeof hex !== 'string') {
272
+ return res.status(400).json({ error: 'hex must be a string' })
273
+ }
274
+
275
+ if (hex === '') {
276
+ return res.status(400).json({ error: 'Encountered empty hex' })
277
+ }
278
+
279
+ const result = await this.rawtransactionsUseCases.sendRawTransaction({ hex })
280
+ return res.status(200).json(result)
281
+ } catch (err) {
282
+ return this.handleError(err, res)
283
+ }
284
+ }
285
+
286
+ /**
287
+ * @api {post} /v6/full-node/rawtransactions/sendRawTransaction Send Bulk Raw Transactions
288
+ * @apiName SendBulkRawTransactions
289
+ * @apiGroup RawTransactions
290
+ * @apiDescription Submits multiple raw transaction (serialized, hex-encoded) to local node and network.
291
+ *
292
+ * @apiParam {String[]} hexes Array of hex-encoded transactions
293
+ *
294
+ * @apiExample Example usage:
295
+ * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/sendRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
296
+ */
297
+ async sendRawTransactionBulk (req, res) {
298
+ try {
299
+ const hexes = req.body.hexes
300
+
301
+ if (!Array.isArray(hexes)) {
302
+ return res.status(400).json({ error: 'hex must be an array' })
303
+ }
304
+
305
+ if (!this.adapters.fullNode.validateArraySize(hexes.length)) {
306
+ return res.status(400).json({ error: 'Array too large.' })
307
+ }
308
+
309
+ // Validate each element
310
+ for (const hex of hexes) {
311
+ if (hex === '') {
312
+ return res.status(400).json({ error: 'Encountered empty hex' })
313
+ }
314
+ }
315
+
316
+ const result = await this.rawtransactionsUseCases.sendRawTransactions({ hexes })
317
+ return res.status(200).json(result)
318
+ } catch (err) {
319
+ return this.handleError(err, res)
320
+ }
321
+ }
322
+
323
+ handleError (err, res) {
324
+ wlogger.error('Error in RawTransactionsRESTController:', err)
325
+
326
+ const status = err.status || 500
327
+ const message = err.message || 'Internal server error'
328
+
329
+ return res.status(status).json({ error: message })
330
+ }
331
+ }
332
+
333
+ export default RawTransactionsRESTController
@@ -0,0 +1,58 @@
1
+ /*
2
+ REST API router for /full-node/rawtransactions routes.
3
+ */
4
+
5
+ import express from 'express'
6
+ import RawTransactionsRESTController from './controller.js'
7
+
8
+ class RawTransactionsRouter {
9
+ constructor (localConfig = {}) {
10
+ this.adapters = localConfig.adapters
11
+ if (!this.adapters) {
12
+ throw new Error(
13
+ 'Instance of Adapters library required when instantiating RawTransactions REST Router.'
14
+ )
15
+ }
16
+
17
+ this.useCases = localConfig.useCases
18
+ if (!this.useCases) {
19
+ throw new Error(
20
+ 'Instance of Use Cases library required when instantiating RawTransactions REST Router.'
21
+ )
22
+ }
23
+
24
+ const dependencies = {
25
+ adapters: this.adapters,
26
+ useCases: this.useCases
27
+ }
28
+
29
+ this.rawtransactionsController = new RawTransactionsRESTController(dependencies)
30
+
31
+ this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
32
+ this.baseUrl = `${this.apiPrefix}/full-node/rawtransactions`
33
+ if (!this.baseUrl.startsWith('/')) {
34
+ this.baseUrl = `/${this.baseUrl}`
35
+ }
36
+ this.router = express.Router()
37
+ }
38
+
39
+ attach (app) {
40
+ if (!app) {
41
+ throw new Error('Must pass app object when attaching REST API controllers.')
42
+ }
43
+
44
+ this.router.get('/', this.rawtransactionsController.root)
45
+ this.router.get('/decodeRawTransaction/:hex', this.rawtransactionsController.decodeRawTransactionSingle)
46
+ this.router.post('/decodeRawTransaction', this.rawtransactionsController.decodeRawTransactionBulk)
47
+ this.router.get('/decodeScript/:hex', this.rawtransactionsController.decodeScriptSingle)
48
+ this.router.post('/decodeScript', this.rawtransactionsController.decodeScriptBulk)
49
+ this.router.get('/getRawTransaction/:txid', this.rawtransactionsController.getRawTransactionSingle)
50
+ this.router.post('/getRawTransaction', this.rawtransactionsController.getRawTransactionBulk)
51
+ this.router.get('/sendRawTransaction/:hex', this.rawtransactionsController.sendRawTransactionSingle)
52
+ this.router.post('/sendRawTransaction', this.rawtransactionsController.sendRawTransactionBulk)
53
+
54
+ app.use(this.baseUrl, this.router)
55
+ }
56
+ }
57
+
58
+ export default RawTransactionsRouter
@@ -7,7 +7,13 @@
7
7
  // Local libraries
8
8
  // import EventRouter from './event/index.js'
9
9
  // import ReqRouter from './req/index.js'
10
- import BlockchainRouter from './full-node/blockchain/index.js'
10
+ import BlockchainRouter from './full-node/blockchain/router.js'
11
+ import ControlRouter from './full-node/control/router.js'
12
+ import DSProofRouter from './full-node/dsproof/router.js'
13
+ import FulcrumRouter from './fulcrum/router.js'
14
+ import MiningRouter from './full-node/mining/router.js'
15
+ import RawTransactionsRouter from './full-node/rawtransactions/router.js'
16
+ import SlpRouter from './slp/router.js'
11
17
  import config from '../../config/index.js'
12
18
 
13
19
  class RESTControllers {
@@ -26,6 +32,12 @@ class RESTControllers {
26
32
  )
27
33
  }
28
34
 
35
+ // Allow overriding the API prefix for testing, default to v6.
36
+ this.apiPrefix = localConfig.apiPrefix || '/v6'
37
+ if (this.apiPrefix.length > 1 && this.apiPrefix.endsWith('/')) {
38
+ this.apiPrefix = this.apiPrefix.slice(0, -1)
39
+ }
40
+
29
41
  // Bind 'this' object to all subfunctions.
30
42
  this.attachRESTControllers = this.attachRESTControllers.bind(this)
31
43
 
@@ -36,7 +48,8 @@ class RESTControllers {
36
48
  attachRESTControllers (app) {
37
49
  const dependencies = {
38
50
  adapters: this.adapters,
39
- useCases: this.useCases
51
+ useCases: this.useCases,
52
+ apiPrefix: this.apiPrefix
40
53
  }
41
54
 
42
55
  // Attach the REST API Controllers associated with the /event route
@@ -49,6 +62,24 @@ class RESTControllers {
49
62
 
50
63
  const blockchainRouter = new BlockchainRouter(dependencies)
51
64
  blockchainRouter.attach(app)
65
+
66
+ const controlRouter = new ControlRouter(dependencies)
67
+ controlRouter.attach(app)
68
+
69
+ const dsproofRouter = new DSProofRouter(dependencies)
70
+ dsproofRouter.attach(app)
71
+
72
+ const fulcrumRouter = new FulcrumRouter(dependencies)
73
+ fulcrumRouter.attach(app)
74
+
75
+ const miningRouter = new MiningRouter(dependencies)
76
+ miningRouter.attach(app)
77
+
78
+ const rawtransactionsRouter = new RawTransactionsRouter(dependencies)
79
+ rawtransactionsRouter.attach(app)
80
+
81
+ const slpRouter = new SlpRouter(dependencies)
82
+ slpRouter.attach(app)
52
83
  }
53
84
  }
54
85
 
@@ -0,0 +1,218 @@
1
+ /*
2
+ REST API Controller for the /slp routes.
3
+ */
4
+
5
+ import wlogger from '../../../adapters/wlogger.js'
6
+ import BCHJS from '@psf/bch-js'
7
+
8
+ const bchjs = new BCHJS()
9
+
10
+ class SlpRESTController {
11
+ constructor (localConfig = {}) {
12
+ this.adapters = localConfig.adapters
13
+ if (!this.adapters) {
14
+ throw new Error(
15
+ 'Instance of Adapters library required when instantiating SLP REST Controller.'
16
+ )
17
+ }
18
+
19
+ this.useCases = localConfig.useCases
20
+ if (!this.useCases || !this.useCases.slp) {
21
+ throw new Error(
22
+ 'Instance of SLP use cases required when instantiating SLP REST Controller.'
23
+ )
24
+ }
25
+
26
+ this.slpUseCases = this.useCases.slp
27
+
28
+ // Bind functions
29
+ this.root = this.root.bind(this)
30
+ this.getStatus = this.getStatus.bind(this)
31
+ this.getAddress = this.getAddress.bind(this)
32
+ this.getTxid = this.getTxid.bind(this)
33
+ this.getTokenStats = this.getTokenStats.bind(this)
34
+ this.getTokenData = this.getTokenData.bind(this)
35
+ this.handleError = this.handleError.bind(this)
36
+ }
37
+
38
+ /**
39
+ * @api {get} /v6/slp/ Service status
40
+ * @apiName SlpRoot
41
+ * @apiGroup SLP
42
+ *
43
+ * @apiDescription Returns the status of the SLP service.
44
+ *
45
+ * @apiSuccess {String} status Service identifier
46
+ */
47
+ async root (req, res) {
48
+ return res.status(200).json({ status: 'psf-slp-indexer' })
49
+ }
50
+
51
+ /**
52
+ * Validates and converts an address to cash address format
53
+ * @param {string} address - Address to validate and convert
54
+ * @returns {string} Cash address
55
+ * @throws {Error} If address is invalid or not mainnet
56
+ */
57
+ _validateAndConvertAddress (address) {
58
+ if (!address) {
59
+ throw new Error('address is empty')
60
+ }
61
+
62
+ // Convert legacy to cash address
63
+ const cashAddr = bchjs.SLP.Address.toCashAddress(address)
64
+
65
+ // Ensure it's a valid BCH address
66
+ try {
67
+ bchjs.SLP.Address.toLegacyAddress(cashAddr)
68
+ } catch (err) {
69
+ throw new Error(`Invalid BCH address. Double check your address is valid: ${address}`)
70
+ }
71
+
72
+ // Ensure it's mainnet (no testnet support)
73
+ const isMainnet = bchjs.Address.isMainnetAddress(cashAddr)
74
+ if (!isMainnet) {
75
+ throw new Error('Invalid network. Only mainnet addresses are supported.')
76
+ }
77
+
78
+ return cashAddr
79
+ }
80
+
81
+ /**
82
+ * @api {get} /v6/slp/status Get indexer status
83
+ * @apiName GetStatus
84
+ * @apiGroup SLP
85
+ * @apiDescription Returns the status of the SLP indexer.
86
+ */
87
+ async getStatus (req, res) {
88
+ try {
89
+ const result = await this.slpUseCases.getStatus()
90
+ return res.status(200).json(result)
91
+ } catch (err) {
92
+ return this.handleError(err, res)
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @api {post} /v6/slp/address Get SLP balance for address
98
+ * @apiName GetAddress
99
+ * @apiGroup SLP
100
+ * @apiDescription Returns SLP balance for an address.
101
+ */
102
+ async getAddress (req, res) {
103
+ try {
104
+ const address = req.body.address
105
+
106
+ if (!address || address === '') {
107
+ return res.status(400).json({
108
+ success: false,
109
+ error: 'address can not be empty'
110
+ })
111
+ }
112
+
113
+ // Validate and convert address
114
+ const cashAddr = this._validateAndConvertAddress(address)
115
+
116
+ const result = await this.slpUseCases.getAddress({ address: cashAddr })
117
+ return res.status(200).json(result)
118
+ } catch (err) {
119
+ return this.handleError(err, res)
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @api {post} /v6/slp/txid Get SLP transaction data
125
+ * @apiName GetTxid
126
+ * @apiGroup SLP
127
+ * @apiDescription Returns SLP transaction data for a TXID.
128
+ */
129
+ async getTxid (req, res) {
130
+ try {
131
+ const txid = req.body.txid
132
+
133
+ if (!txid || txid === '') {
134
+ return res.status(400).json({
135
+ success: false,
136
+ error: 'txid can not be empty'
137
+ })
138
+ }
139
+
140
+ if (txid.length !== 64) {
141
+ return res.status(400).json({
142
+ success: false,
143
+ error: 'This is not a txid'
144
+ })
145
+ }
146
+
147
+ const result = await this.slpUseCases.getTxid({ txid })
148
+ return res.status(200).json(result)
149
+ } catch (err) {
150
+ return this.handleError(err, res)
151
+ }
152
+ }
153
+
154
+ /**
155
+ * @api {post} /v6/slp/token Get token statistics
156
+ * @apiName GetTokenStats
157
+ * @apiGroup SLP
158
+ * @apiDescription Returns statistics for a single SLP token.
159
+ */
160
+ async getTokenStats (req, res) {
161
+ try {
162
+ const tokenId = req.body.tokenId
163
+
164
+ if (!tokenId || tokenId === '') {
165
+ return res.status(400).json({
166
+ success: false,
167
+ error: 'tokenId can not be empty'
168
+ })
169
+ }
170
+
171
+ // Flag to toggle tx history of the token
172
+ const withTxHistory = req.body.withTxHistory === true
173
+
174
+ const result = await this.slpUseCases.getTokenStats({ tokenId, withTxHistory })
175
+ return res.status(200).json(result)
176
+ } catch (err) {
177
+ return this.handleError(err, res)
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @api {post} /v6/slp/token/data Get token data
183
+ * @apiName GetTokenData
184
+ * @apiGroup SLP
185
+ * @apiDescription Get mutable and immutable data if the token contains them.
186
+ */
187
+ async getTokenData (req, res) {
188
+ try {
189
+ const tokenId = req.body.tokenId
190
+
191
+ if (!tokenId || tokenId === '') {
192
+ return res.status(400).json({
193
+ success: false,
194
+ error: 'tokenId can not be empty'
195
+ })
196
+ }
197
+
198
+ // Flag to toggle tx history of the token
199
+ const withTxHistory = req.body.withTxHistory === true
200
+
201
+ const result = await this.slpUseCases.getTokenData({ tokenId, withTxHistory })
202
+ return res.status(200).json(result)
203
+ } catch (err) {
204
+ return this.handleError(err, res)
205
+ }
206
+ }
207
+
208
+ handleError (err, res) {
209
+ wlogger.error('Error in SlpRESTController:', err)
210
+
211
+ const status = err.status || 500
212
+ const message = err.message || 'Internal server error'
213
+
214
+ return res.status(status).json({ error: message })
215
+ }
216
+ }
217
+
218
+ export default SlpRESTController