psf-bch-api 1.2.0 → 7.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.
Files changed (53) hide show
  1. package/.env-local +28 -0
  2. package/bin/server.js +61 -9
  3. package/package.json +6 -2
  4. package/src/adapters/fulcrum-api.js +124 -0
  5. package/src/adapters/full-node-rpc.js +2 -6
  6. package/src/adapters/index.js +4 -0
  7. package/src/adapters/slp-indexer-api.js +124 -0
  8. package/src/config/env/common.js +29 -25
  9. package/src/config/x402.js +7 -0
  10. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  11. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  12. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  13. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  14. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  16. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  17. package/src/controllers/rest-api/index.js +23 -3
  18. package/src/controllers/rest-api/price/controller.js +96 -0
  19. package/src/controllers/rest-api/price/router.js +52 -0
  20. package/src/controllers/rest-api/slp/controller.js +218 -0
  21. package/src/controllers/rest-api/slp/router.js +55 -0
  22. package/src/controllers/timer-controller.js +1 -1
  23. package/src/middleware/basic-auth.js +61 -0
  24. package/src/use-cases/fulcrum-use-cases.js +155 -0
  25. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  26. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  27. package/src/use-cases/index.js +10 -0
  28. package/src/use-cases/price-use-cases.js +83 -0
  29. package/src/use-cases/slp-use-cases.js +321 -0
  30. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  31. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  32. package/test/unit/controllers/mining-controller-unit.js +139 -0
  33. package/test/unit/controllers/price-controller-unit.js +116 -0
  34. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  35. package/test/unit/controllers/rest-api-index-unit.js +67 -3
  36. package/test/unit/controllers/slp-controller-unit.js +312 -0
  37. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  38. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  39. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  40. package/test/unit/use-cases/price-use-cases-unit.js +103 -0
  41. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  42. package/src/entities/event.js +0 -71
  43. package/test/integration/api/event-integration.js +0 -250
  44. package/test/integration/api/req-integration.js +0 -173
  45. package/test/integration/api/subscription-integration.js +0 -198
  46. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  47. package/test/integration/use-cases/publish-event-integration.js +0 -104
  48. package/test/integration/use-cases/query-events-integration.js +0 -95
  49. package/test/unit/entities/event-unit.js +0 -139
  50. /package/{index.js → psf-bch-api.js} +0 -0
  51. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  52. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  53. /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
@@ -0,0 +1,99 @@
1
+ /*
2
+ REST API Controller for the /full-node/mining routes.
3
+ */
4
+
5
+ import wlogger from '../../../../adapters/wlogger.js'
6
+
7
+ class MiningRESTController {
8
+ constructor (localConfig = {}) {
9
+ this.adapters = localConfig.adapters
10
+ if (!this.adapters) {
11
+ throw new Error(
12
+ 'Instance of Adapters library required when instantiating Mining REST Controller.'
13
+ )
14
+ }
15
+
16
+ this.useCases = localConfig.useCases
17
+ if (!this.useCases || !this.useCases.mining) {
18
+ throw new Error(
19
+ 'Instance of Mining use cases required when instantiating Mining REST Controller.'
20
+ )
21
+ }
22
+
23
+ this.miningUseCases = this.useCases.mining
24
+
25
+ // Bind functions
26
+ this.root = this.root.bind(this)
27
+ this.getMiningInfo = this.getMiningInfo.bind(this)
28
+ this.getNetworkHashPS = this.getNetworkHashPS.bind(this)
29
+ this.handleError = this.handleError.bind(this)
30
+ }
31
+
32
+ /**
33
+ * @api {get} /v6/full-node/mining/ Service status
34
+ * @apiName MiningRoot
35
+ * @apiGroup Mining
36
+ *
37
+ * @apiDescription Returns the status of the mining service.
38
+ *
39
+ * @apiSuccess {String} status Service identifier
40
+ */
41
+ async root (req, res) {
42
+ return res.status(200).json({ status: 'mining' })
43
+ }
44
+
45
+ /**
46
+ * @api {get} /v6/full-node/mining/getMiningInfo Get Mining Info
47
+ * @apiName GetMiningInfo
48
+ * @apiGroup Mining
49
+ * @apiDescription Returns a json object containing mining-related information.
50
+ *
51
+ * @apiExample Example usage:
52
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getMiningInfo" -H "accept: application/json"
53
+ */
54
+ async getMiningInfo (req, res) {
55
+ try {
56
+ const result = await this.miningUseCases.getMiningInfo()
57
+ return res.status(200).json(result)
58
+ } catch (err) {
59
+ return this.handleError(err, res)
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @api {get} /v6/full-node/mining/getNetworkHashPS Get Estimated network hashes per second
65
+ * @apiName GetNetworkHashPS
66
+ * @apiGroup Mining
67
+ * @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [nblocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found.
68
+ *
69
+ * @apiParam {Number} nblocks Number of blocks to use for estimation (default: 120)
70
+ * @apiParam {Number} height Block height to estimate at (default: -1)
71
+ *
72
+ * @apiExample Example usage:
73
+ * curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getNetworkHashPS?nblocks=120&height=-1" -H "accept: application/json"
74
+ */
75
+ async getNetworkHashPS (req, res) {
76
+ try {
77
+ let nblocks = 120 // Default
78
+ let height = -1 // Default
79
+ if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
80
+ if (req.query.height) height = parseInt(req.query.height)
81
+
82
+ const result = await this.miningUseCases.getNetworkHashPS({ nblocks, height })
83
+ return res.status(200).json(result)
84
+ } catch (err) {
85
+ return this.handleError(err, res)
86
+ }
87
+ }
88
+
89
+ handleError (err, res) {
90
+ wlogger.error('Error in MiningRESTController:', err)
91
+
92
+ const status = err.status || 500
93
+ const message = err.message || 'Internal server error'
94
+
95
+ return res.status(status).json({ error: message })
96
+ }
97
+ }
98
+
99
+ export default MiningRESTController
@@ -0,0 +1,52 @@
1
+ /*
2
+ REST API router for /full-node/mining routes.
3
+ */
4
+
5
+ import express from 'express'
6
+ import MiningRESTController from './controller.js'
7
+
8
+ class MiningRouter {
9
+ constructor (localConfig = {}) {
10
+ this.adapters = localConfig.adapters
11
+ if (!this.adapters) {
12
+ throw new Error(
13
+ 'Instance of Adapters library required when instantiating Mining 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 Mining REST Router.'
21
+ )
22
+ }
23
+
24
+ const dependencies = {
25
+ adapters: this.adapters,
26
+ useCases: this.useCases
27
+ }
28
+
29
+ this.miningController = new MiningRESTController(dependencies)
30
+
31
+ this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
32
+ this.baseUrl = `${this.apiPrefix}/full-node/mining`
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.miningController.root)
45
+ this.router.get('/getMiningInfo', this.miningController.getMiningInfo)
46
+ this.router.get('/getNetworkHashPS', this.miningController.getNetworkHashPS)
47
+
48
+ app.use(this.baseUrl, this.router)
49
+ }
50
+ }
51
+
52
+ export default MiningRouter
@@ -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,9 +7,14 @@
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'
11
- import ControlRouter from './full-node/control/index.js'
12
- import DSProofRouter from './full-node/dsproof/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 PriceRouter from './price/router.js'
16
+ import RawTransactionsRouter from './full-node/rawtransactions/router.js'
17
+ import SlpRouter from './slp/router.js'
13
18
  import config from '../../config/index.js'
14
19
 
15
20
  class RESTControllers {
@@ -64,6 +69,21 @@ class RESTControllers {
64
69
 
65
70
  const dsproofRouter = new DSProofRouter(dependencies)
66
71
  dsproofRouter.attach(app)
72
+
73
+ const fulcrumRouter = new FulcrumRouter(dependencies)
74
+ fulcrumRouter.attach(app)
75
+
76
+ const miningRouter = new MiningRouter(dependencies)
77
+ miningRouter.attach(app)
78
+
79
+ const priceRouter = new PriceRouter(dependencies)
80
+ priceRouter.attach(app)
81
+
82
+ const rawtransactionsRouter = new RawTransactionsRouter(dependencies)
83
+ rawtransactionsRouter.attach(app)
84
+
85
+ const slpRouter = new SlpRouter(dependencies)
86
+ slpRouter.attach(app)
67
87
  }
68
88
  }
69
89
 
@@ -0,0 +1,96 @@
1
+ /*
2
+ REST API Controller for the /price routes.
3
+ */
4
+
5
+ import wlogger from '../../../adapters/wlogger.js'
6
+
7
+ class PriceRESTController {
8
+ constructor (localConfig = {}) {
9
+ this.adapters = localConfig.adapters
10
+ if (!this.adapters) {
11
+ throw new Error(
12
+ 'Instance of Adapters library required when instantiating Price REST Controller.'
13
+ )
14
+ }
15
+
16
+ this.useCases = localConfig.useCases
17
+ if (!this.useCases || !this.useCases.price) {
18
+ throw new Error(
19
+ 'Instance of Price use cases required when instantiating Price REST Controller.'
20
+ )
21
+ }
22
+
23
+ this.priceUseCases = this.useCases.price
24
+
25
+ // Bind functions
26
+ this.root = this.root.bind(this)
27
+ this.getBCHUSD = this.getBCHUSD.bind(this)
28
+ this.getPsffppWritePrice = this.getPsffppWritePrice.bind(this)
29
+ this.handleError = this.handleError.bind(this)
30
+ }
31
+
32
+ /**
33
+ * @api {get} /v6/price/ Service status
34
+ * @apiName PriceRoot
35
+ * @apiGroup Price
36
+ *
37
+ * @apiDescription Returns the status of the price service.
38
+ *
39
+ * @apiSuccess {String} status Service identifier
40
+ */
41
+ async root (req, res) {
42
+ return res.status(200).json({ status: 'price' })
43
+ }
44
+
45
+ /**
46
+ * @api {get} /v6/price/bchusd Get the USD price of BCH
47
+ * @apiName GetBCHUSD
48
+ * @apiGroup Price
49
+ * @apiDescription Get the USD price of BCH from Coinex.
50
+ *
51
+ * @apiExample Example usage:
52
+ * curl -X GET "https://api.fullstack.cash/v6/price/bchusd" -H "accept: application/json"
53
+ *
54
+ * @apiSuccess {Number} usd The USD price of BCH
55
+ */
56
+ async getBCHUSD (req, res) {
57
+ try {
58
+ const price = await this.priceUseCases.getBCHUSD()
59
+ return res.status(200).json({ usd: price })
60
+ } catch (err) {
61
+ return this.handleError(err, res)
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @api {get} /v6/price/psffpp Get the PSF price for writing to the PSFFPP
67
+ * @apiName GetPsffppWritePrice
68
+ * @apiGroup Price
69
+ * @apiDescription Get the price to pin 1MB of content to the PSFFPP pinning
70
+ * network on IPFS. The price is denominated in PSF tokens.
71
+ *
72
+ * @apiExample Example usage:
73
+ * curl -X GET "https://api.fullstack.cash/v6/price/psffpp" -H "accept: application/json"
74
+ *
75
+ * @apiSuccess {Number} writePrice The price in PSF tokens to write 1MB to PSFFPP
76
+ */
77
+ async getPsffppWritePrice (req, res) {
78
+ try {
79
+ const writePrice = await this.priceUseCases.getPsffppWritePrice()
80
+ return res.status(200).json({ writePrice })
81
+ } catch (err) {
82
+ return this.handleError(err, res)
83
+ }
84
+ }
85
+
86
+ handleError (err, res) {
87
+ wlogger.error('Error in PriceRESTController:', err)
88
+
89
+ const status = err.status || 500
90
+ const message = err.message || 'Internal server error'
91
+
92
+ return res.status(status).json({ error: message })
93
+ }
94
+ }
95
+
96
+ export default PriceRESTController