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.
- package/.env-local +9 -0
- package/README.md +22 -0
- package/bin/server.js +24 -1
- package/package.json +6 -2
- package/src/adapters/fulcrum-api.js +124 -0
- package/src/adapters/full-node-rpc.js +2 -6
- package/src/adapters/index.js +4 -0
- package/src/adapters/slp-indexer-api.js +124 -0
- package/src/config/env/common.js +45 -24
- package/src/config/x402.js +43 -0
- package/src/controllers/index.js +3 -1
- package/src/controllers/rest-api/fulcrum/controller.js +563 -0
- package/src/controllers/rest-api/fulcrum/router.js +64 -0
- package/src/controllers/rest-api/full-node/blockchain/controller.js +26 -26
- package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +5 -1
- package/src/controllers/rest-api/full-node/control/controller.js +68 -0
- package/src/controllers/rest-api/full-node/control/router.js +51 -0
- package/src/controllers/rest-api/full-node/dsproof/controller.js +90 -0
- package/src/controllers/rest-api/full-node/dsproof/router.js +51 -0
- package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
- package/src/controllers/rest-api/full-node/mining/router.js +52 -0
- package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
- package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
- package/src/controllers/rest-api/index.js +33 -2
- package/src/controllers/rest-api/slp/controller.js +218 -0
- package/src/controllers/rest-api/slp/router.js +55 -0
- package/src/controllers/timer-controller.js +1 -1
- package/src/use-cases/fulcrum-use-cases.js +155 -0
- package/src/use-cases/full-node-control-use-cases.js +24 -0
- package/src/use-cases/full-node-dsproof-use-cases.js +24 -0
- package/src/use-cases/full-node-mining-use-cases.js +28 -0
- package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
- package/src/use-cases/index.js +12 -0
- package/src/use-cases/slp-use-cases.js +321 -0
- package/test/unit/controllers/blockchain-controller-unit.js +2 -3
- package/test/unit/controllers/control-controller-unit.js +88 -0
- package/test/unit/controllers/dsproof-controller-unit.js +117 -0
- package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
- package/test/unit/controllers/mining-controller-unit.js +139 -0
- package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
- package/test/unit/controllers/rest-api-index-unit.js +76 -6
- package/test/unit/controllers/slp-controller-unit.js +312 -0
- package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
- package/test/unit/use-cases/full-node-control-use-cases-unit.js +53 -0
- package/test/unit/use-cases/full-node-dsproof-use-cases-unit.js +54 -0
- package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
- package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
- package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
- package/src/entities/event.js +0 -71
- package/test/integration/api/event-integration.js +0 -250
- package/test/integration/api/req-integration.js +0 -173
- package/test/integration/api/subscription-integration.js +0 -198
- package/test/integration/use-cases/manage-subscription-integration.js +0 -163
- package/test/integration/use-cases/publish-event-integration.js +0 -104
- package/test/integration/use-cases/query-events-integration.js +0 -95
- 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/
|
|
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
|