psf-bch-api 1.2.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 (45) hide show
  1. package/.env-local +9 -0
  2. package/bin/server.js +2 -1
  3. package/package.json +4 -1
  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 +21 -24
  9. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  10. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  11. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  12. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  13. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  14. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  16. package/src/controllers/rest-api/index.js +19 -3
  17. package/src/controllers/rest-api/slp/controller.js +218 -0
  18. package/src/controllers/rest-api/slp/router.js +55 -0
  19. package/src/controllers/timer-controller.js +1 -1
  20. package/src/use-cases/fulcrum-use-cases.js +155 -0
  21. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  22. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  23. package/src/use-cases/index.js +8 -0
  24. package/src/use-cases/slp-use-cases.js +321 -0
  25. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  26. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  27. package/test/unit/controllers/mining-controller-unit.js +139 -0
  28. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  29. package/test/unit/controllers/rest-api-index-unit.js +59 -3
  30. package/test/unit/controllers/slp-controller-unit.js +312 -0
  31. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  32. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  33. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  34. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  35. package/src/entities/event.js +0 -71
  36. package/test/integration/api/event-integration.js +0 -250
  37. package/test/integration/api/req-integration.js +0 -173
  38. package/test/integration/api/subscription-integration.js +0 -198
  39. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  40. package/test/integration/use-cases/publish-event-integration.js +0 -104
  41. package/test/integration/use-cases/query-events-integration.js +0 -95
  42. package/test/unit/entities/event-unit.js +0 -139
  43. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  44. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  45. /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,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'
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 RawTransactionsRouter from './full-node/rawtransactions/router.js'
16
+ import SlpRouter from './slp/router.js'
13
17
  import config from '../../config/index.js'
14
18
 
15
19
  class RESTControllers {
@@ -64,6 +68,18 @@ class RESTControllers {
64
68
 
65
69
  const dsproofRouter = new DSProofRouter(dependencies)
66
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)
67
83
  }
68
84
  }
69
85