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.
- package/.env-local +9 -0
- package/bin/server.js +2 -1
- package/package.json +4 -1
- 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 +21 -24
- 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 +4 -4
- 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 +19 -3
- 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-mining-use-cases.js +28 -0
- package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
- package/src/use-cases/index.js +8 -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/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 +59 -3
- 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-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
- /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
- /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
- /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/*
|
|
2
|
+
REST API Controller for the /fulcrum 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 FulcrumRESTController {
|
|
11
|
+
constructor (localConfig = {}) {
|
|
12
|
+
this.adapters = localConfig.adapters
|
|
13
|
+
if (!this.adapters) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'Instance of Adapters library required when instantiating Fulcrum REST Controller.'
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.useCases = localConfig.useCases
|
|
20
|
+
if (!this.useCases || !this.useCases.fulcrum) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'Instance of Fulcrum use cases required when instantiating Fulcrum REST Controller.'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.fulcrumUseCases = this.useCases.fulcrum
|
|
27
|
+
|
|
28
|
+
// Bind functions
|
|
29
|
+
this.root = this.root.bind(this)
|
|
30
|
+
this.getBalance = this.getBalance.bind(this)
|
|
31
|
+
this.balanceBulk = this.balanceBulk.bind(this)
|
|
32
|
+
this.getUtxos = this.getUtxos.bind(this)
|
|
33
|
+
this.utxosBulk = this.utxosBulk.bind(this)
|
|
34
|
+
this.getTransactionDetails = this.getTransactionDetails.bind(this)
|
|
35
|
+
this.transactionDetailsBulk = this.transactionDetailsBulk.bind(this)
|
|
36
|
+
this.broadcastTransaction = this.broadcastTransaction.bind(this)
|
|
37
|
+
this.getBlockHeaders = this.getBlockHeaders.bind(this)
|
|
38
|
+
this.blockHeadersBulk = this.blockHeadersBulk.bind(this)
|
|
39
|
+
this.getTransactions = this.getTransactions.bind(this)
|
|
40
|
+
this.transactionsBulk = this.transactionsBulk.bind(this)
|
|
41
|
+
this.getMempool = this.getMempool.bind(this)
|
|
42
|
+
this.mempoolBulk = this.mempoolBulk.bind(this)
|
|
43
|
+
this.handleError = this.handleError.bind(this)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @api {get} /v6/fulcrum/ Service status
|
|
48
|
+
* @apiName FulcrumRoot
|
|
49
|
+
* @apiGroup Fulcrum
|
|
50
|
+
*
|
|
51
|
+
* @apiDescription Returns the status of the fulcrum service.
|
|
52
|
+
*
|
|
53
|
+
* @apiSuccess {String} status Service identifier
|
|
54
|
+
*/
|
|
55
|
+
async root (req, res) {
|
|
56
|
+
return res.status(200).json({ status: 'fulcrum' })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validates and converts an address to cash address format
|
|
61
|
+
* @param {string} address - Address to validate and convert
|
|
62
|
+
* @returns {string} Cash address
|
|
63
|
+
* @throws {Error} If address is invalid or not mainnet
|
|
64
|
+
*/
|
|
65
|
+
_validateAndConvertAddress (address) {
|
|
66
|
+
if (!address) {
|
|
67
|
+
throw new Error('address is empty')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Convert legacy to cash address
|
|
71
|
+
const cashAddr = bchjs.Address.toCashAddress(address)
|
|
72
|
+
|
|
73
|
+
// Ensure it's a valid BCH address
|
|
74
|
+
try {
|
|
75
|
+
bchjs.Address.toLegacyAddress(cashAddr)
|
|
76
|
+
} catch (err) {
|
|
77
|
+
throw new Error(`Invalid BCH address. Double check your address is valid: ${address}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Ensure it's mainnet (no testnet support)
|
|
81
|
+
const isMainnet = bchjs.Address.isMainnetAddress(cashAddr)
|
|
82
|
+
if (!isMainnet) {
|
|
83
|
+
throw new Error('Invalid network. Only mainnet addresses are supported.')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return cashAddr
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @api {get} /v6/fulcrum/balance/:address Get balance for a single address
|
|
91
|
+
* @apiName GetBalance
|
|
92
|
+
* @apiGroup Fulcrum
|
|
93
|
+
* @apiDescription Returns an object with confirmed and unconfirmed balance associated with an address.
|
|
94
|
+
*/
|
|
95
|
+
async getBalance (req, res) {
|
|
96
|
+
try {
|
|
97
|
+
const address = req.params.address
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(address)) {
|
|
100
|
+
return res.status(400).json({
|
|
101
|
+
success: false,
|
|
102
|
+
error: 'address can not be an array. Use POST for bulk upload.'
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const cashAddr = this._validateAndConvertAddress(address)
|
|
107
|
+
|
|
108
|
+
const result = await this.fulcrumUseCases.getBalance({ address: cashAddr })
|
|
109
|
+
return res.status(200).json(result)
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return this.handleError(err, res)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @api {post} /v6/fulcrum/balance Get balances for an array of addresses
|
|
117
|
+
* @apiName GetBalances
|
|
118
|
+
* @apiGroup Fulcrum
|
|
119
|
+
* @apiDescription Returns an array of balances associated with an array of addresses. Limited to 20 items per request.
|
|
120
|
+
*/
|
|
121
|
+
async balanceBulk (req, res) {
|
|
122
|
+
try {
|
|
123
|
+
const addresses = req.body.addresses
|
|
124
|
+
|
|
125
|
+
if (!Array.isArray(addresses)) {
|
|
126
|
+
return res.status(400).json({
|
|
127
|
+
success: false,
|
|
128
|
+
error: 'addresses needs to be an array. Use GET for single address.'
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!this.adapters.fullNode.validateArraySize(addresses.length)) {
|
|
133
|
+
return res.status(400).json({
|
|
134
|
+
success: false,
|
|
135
|
+
error: 'Array too large.'
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Validate and convert all addresses
|
|
140
|
+
const validatedAddresses = []
|
|
141
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
142
|
+
try {
|
|
143
|
+
const cashAddr = this._validateAndConvertAddress(addresses[i])
|
|
144
|
+
validatedAddresses.push(cashAddr)
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return res.status(400).json({
|
|
147
|
+
success: false,
|
|
148
|
+
error: err.message
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = await this.fulcrumUseCases.getBalances({ addresses: validatedAddresses })
|
|
154
|
+
return res.status(200).json(result)
|
|
155
|
+
} catch (err) {
|
|
156
|
+
return this.handleError(err, res)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @api {get} /v6/fulcrum/utxos/:address Get utxos for a single address
|
|
162
|
+
* @apiName GetUtxos
|
|
163
|
+
* @apiGroup Fulcrum
|
|
164
|
+
* @apiDescription Returns an object with UTXOs associated with an address.
|
|
165
|
+
*/
|
|
166
|
+
async getUtxos (req, res) {
|
|
167
|
+
try {
|
|
168
|
+
const address = req.params.address
|
|
169
|
+
|
|
170
|
+
if (Array.isArray(address)) {
|
|
171
|
+
return res.status(400).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: 'address can not be an array. Use POST for bulk upload.'
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const cashAddr = this._validateAndConvertAddress(address)
|
|
178
|
+
|
|
179
|
+
const result = await this.fulcrumUseCases.getUtxos({ address: cashAddr })
|
|
180
|
+
return res.status(200).json(result)
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return this.handleError(err, res)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @api {post} /v6/fulcrum/utxos Get utxos for an array of addresses
|
|
188
|
+
* @apiName GetUtxosBulk
|
|
189
|
+
* @apiGroup Fulcrum
|
|
190
|
+
* @apiDescription Returns an array of objects with UTXOs associated with an address. Limited to 20 items per request.
|
|
191
|
+
*/
|
|
192
|
+
async utxosBulk (req, res) {
|
|
193
|
+
try {
|
|
194
|
+
const addresses = req.body.addresses
|
|
195
|
+
|
|
196
|
+
if (!Array.isArray(addresses)) {
|
|
197
|
+
return res.status(400).json({
|
|
198
|
+
success: false,
|
|
199
|
+
error: 'addresses needs to be an array. Use GET for single address.'
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!this.adapters.fullNode.validateArraySize(addresses.length)) {
|
|
204
|
+
return res.status(400).json({
|
|
205
|
+
success: false,
|
|
206
|
+
error: 'Array too large.'
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Validate and convert all addresses
|
|
211
|
+
const validatedAddresses = []
|
|
212
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
213
|
+
try {
|
|
214
|
+
const cashAddr = this._validateAndConvertAddress(addresses[i])
|
|
215
|
+
validatedAddresses.push(cashAddr)
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return res.status(400).json({
|
|
218
|
+
success: false,
|
|
219
|
+
error: err.message
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = await this.fulcrumUseCases.getUtxosBulk({ addresses: validatedAddresses })
|
|
225
|
+
return res.status(200).json(result)
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return this.handleError(err, res)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @api {get} /v6/fulcrum/tx/data/:txid Get transaction details for a TXID
|
|
233
|
+
* @apiName GetTransactionDetails
|
|
234
|
+
* @apiGroup Fulcrum
|
|
235
|
+
* @apiDescription Returns an object with transaction details of the TXID
|
|
236
|
+
*/
|
|
237
|
+
async getTransactionDetails (req, res) {
|
|
238
|
+
try {
|
|
239
|
+
const txid = req.params.txid
|
|
240
|
+
|
|
241
|
+
if (typeof txid !== 'string') {
|
|
242
|
+
return res.status(400).json({
|
|
243
|
+
success: false,
|
|
244
|
+
error: 'txid must be a string'
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = await this.fulcrumUseCases.getTransactionDetails({ txid })
|
|
249
|
+
return res.status(200).json(result)
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return this.handleError(err, res)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @api {post} /v6/fulcrum/tx/data Get transaction details for an array of TXIDs
|
|
257
|
+
* @apiName GetTransactionDetailsBulk
|
|
258
|
+
* @apiGroup Fulcrum
|
|
259
|
+
* @apiDescription Returns an array of objects with transaction details of an array of TXIDs. Limited to 20 items per request.
|
|
260
|
+
*/
|
|
261
|
+
async transactionDetailsBulk (req, res) {
|
|
262
|
+
try {
|
|
263
|
+
const txids = req.body.txids
|
|
264
|
+
const verbose = req.body.verbose !== undefined ? req.body.verbose : true
|
|
265
|
+
|
|
266
|
+
if (!Array.isArray(txids)) {
|
|
267
|
+
return res.status(400).json({
|
|
268
|
+
success: false,
|
|
269
|
+
error: 'txids needs to be an array. Use GET for single txid.'
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!this.adapters.fullNode.validateArraySize(txids.length)) {
|
|
274
|
+
return res.status(400).json({
|
|
275
|
+
success: false,
|
|
276
|
+
error: 'Array too large.'
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const result = await this.fulcrumUseCases.getTransactionDetailsBulk({ txids, verbose })
|
|
281
|
+
return res.status(200).json(result)
|
|
282
|
+
} catch (err) {
|
|
283
|
+
return this.handleError(err, res)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @api {post} /v6/fulcrum/tx/broadcast Broadcast a raw transaction
|
|
289
|
+
* @apiName BroadcastTransaction
|
|
290
|
+
* @apiGroup Fulcrum
|
|
291
|
+
* @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure.
|
|
292
|
+
*/
|
|
293
|
+
async broadcastTransaction (req, res) {
|
|
294
|
+
try {
|
|
295
|
+
const txHex = req.body.txHex
|
|
296
|
+
|
|
297
|
+
if (typeof txHex !== 'string') {
|
|
298
|
+
return res.status(400).json({
|
|
299
|
+
success: false,
|
|
300
|
+
error: 'txHex must be a string'
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const result = await this.fulcrumUseCases.broadcastTransaction({ txHex })
|
|
305
|
+
return res.status(200).json(result)
|
|
306
|
+
} catch (err) {
|
|
307
|
+
return this.handleError(err, res)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @api {get} /v6/fulcrum/block/headers/:height Get block headers
|
|
313
|
+
* @apiName GetBlockHeaders
|
|
314
|
+
* @apiGroup Fulcrum
|
|
315
|
+
* @apiDescription Returns an array with block headers starting at the block height
|
|
316
|
+
*
|
|
317
|
+
* @apiParam {Number} height Block height
|
|
318
|
+
* @apiParam {Number} count Number of block headers to return (query parameter, default: 1)
|
|
319
|
+
*/
|
|
320
|
+
async getBlockHeaders (req, res) {
|
|
321
|
+
try {
|
|
322
|
+
const heightRaw = req.params.height
|
|
323
|
+
const countRaw = req.query.count
|
|
324
|
+
|
|
325
|
+
const height = Number(heightRaw)
|
|
326
|
+
const count = countRaw === undefined ? 1 : Number(countRaw)
|
|
327
|
+
|
|
328
|
+
if (Number.isNaN(height) || height < 0) {
|
|
329
|
+
return res.status(400).json({
|
|
330
|
+
success: false,
|
|
331
|
+
error: 'height must be a positive number'
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (Number.isNaN(count) || count < 0) {
|
|
336
|
+
return res.status(400).json({
|
|
337
|
+
success: false,
|
|
338
|
+
error: 'count must be a positive number'
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = await this.fulcrumUseCases.getBlockHeaders({ height, count })
|
|
343
|
+
return res.status(200).json(result)
|
|
344
|
+
} catch (err) {
|
|
345
|
+
return this.handleError(err, res)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @api {post} /v6/fulcrum/block/headers Get block headers for an array of height + count pairs
|
|
351
|
+
* @apiName GetBlockHeadersBulk
|
|
352
|
+
* @apiGroup Fulcrum
|
|
353
|
+
* @apiDescription Returns an array of objects with block headers. Limited to 20 items per request.
|
|
354
|
+
*/
|
|
355
|
+
async blockHeadersBulk (req, res) {
|
|
356
|
+
try {
|
|
357
|
+
const heights = req.body.heights
|
|
358
|
+
|
|
359
|
+
if (!Array.isArray(heights)) {
|
|
360
|
+
return res.status(400).json({
|
|
361
|
+
success: false,
|
|
362
|
+
error: 'heights needs to be an array. Use GET for single height.'
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!this.adapters.fullNode.validateArraySize(heights.length)) {
|
|
367
|
+
return res.status(400).json({
|
|
368
|
+
success: false,
|
|
369
|
+
error: 'Array too large.'
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Validate each height object
|
|
374
|
+
for (const item of heights) {
|
|
375
|
+
if (!item || typeof item.height !== 'number' || typeof item.count !== 'number') {
|
|
376
|
+
return res.status(400).json({
|
|
377
|
+
success: false,
|
|
378
|
+
error: 'Each height object must have numeric height and count properties'
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
if (item.height < 0 || item.count < 0) {
|
|
382
|
+
return res.status(400).json({
|
|
383
|
+
success: false,
|
|
384
|
+
error: 'height and count must be positive numbers'
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const result = await this.fulcrumUseCases.getBlockHeadersBulk({ heights })
|
|
390
|
+
return res.status(200).json(result)
|
|
391
|
+
} catch (err) {
|
|
392
|
+
return this.handleError(err, res)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @api {get} /v6/fulcrum/transactions/:address Get transaction history for a single address
|
|
398
|
+
* @apiName GetTransactions
|
|
399
|
+
* @apiGroup Fulcrum
|
|
400
|
+
* @apiDescription Returns an array of historical transactions associated with an address. Results are returned in descending order (most recent TX first). Passing allTxs=true will return the entire transaction history, otherwise, only the last 100 TXIDs will be returned.
|
|
401
|
+
*
|
|
402
|
+
* @apiParam {String} address Address
|
|
403
|
+
* @apiParam {Boolean} allTxs Optional: return all transactions (default: false, limited to 100)
|
|
404
|
+
*/
|
|
405
|
+
async getTransactions (req, res) {
|
|
406
|
+
try {
|
|
407
|
+
const address = req.params.address
|
|
408
|
+
let allTxs = false
|
|
409
|
+
|
|
410
|
+
// Check if allTxs is in params or query
|
|
411
|
+
if (req.params.allTxs) {
|
|
412
|
+
allTxs = req.params.allTxs === 'true'
|
|
413
|
+
} else if (req.query.allTxs) {
|
|
414
|
+
allTxs = req.query.allTxs === 'true'
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (Array.isArray(address)) {
|
|
418
|
+
return res.status(400).json({
|
|
419
|
+
success: false,
|
|
420
|
+
error: 'address can not be an array. Use POST for bulk upload.'
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const cashAddr = this._validateAndConvertAddress(address)
|
|
425
|
+
|
|
426
|
+
const result = await this.fulcrumUseCases.getTransactions({ address: cashAddr, allTxs })
|
|
427
|
+
return res.status(200).json(result)
|
|
428
|
+
} catch (err) {
|
|
429
|
+
return this.handleError(err, res)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @api {post} /v6/fulcrum/transactions Get the transaction history for an array of addresses
|
|
435
|
+
* @apiName GetTransactionsBulk
|
|
436
|
+
* @apiGroup Fulcrum
|
|
437
|
+
* @apiDescription Returns an array of transactions associated with an array of addresses. Limited to 20 items per request. Passing allTxs=true will return the entire transaction history, otherwise, only the last 100 TXIDs will be returned.
|
|
438
|
+
*/
|
|
439
|
+
async transactionsBulk (req, res) {
|
|
440
|
+
try {
|
|
441
|
+
const addresses = req.body.addresses
|
|
442
|
+
const allTxs = req.body.allTxs === true
|
|
443
|
+
|
|
444
|
+
if (!Array.isArray(addresses)) {
|
|
445
|
+
return res.status(400).json({
|
|
446
|
+
success: false,
|
|
447
|
+
error: 'addresses needs to be an array. Use GET for single address.'
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!this.adapters.fullNode.validateArraySize(addresses.length)) {
|
|
452
|
+
return res.status(400).json({
|
|
453
|
+
success: false,
|
|
454
|
+
error: 'Array too large.'
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Validate and convert all addresses
|
|
459
|
+
const validatedAddresses = []
|
|
460
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
461
|
+
try {
|
|
462
|
+
const cashAddr = this._validateAndConvertAddress(addresses[i])
|
|
463
|
+
validatedAddresses.push(cashAddr)
|
|
464
|
+
} catch (err) {
|
|
465
|
+
return res.status(400).json({
|
|
466
|
+
success: false,
|
|
467
|
+
error: err.message
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const result = await this.fulcrumUseCases.getTransactionsBulk({
|
|
473
|
+
addresses: validatedAddresses,
|
|
474
|
+
allTxs
|
|
475
|
+
})
|
|
476
|
+
return res.status(200).json(result)
|
|
477
|
+
} catch (err) {
|
|
478
|
+
return this.handleError(err, res)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @api {get} /v6/fulcrum/unconfirmed/:address Get unconfirmed utxos for a single address
|
|
484
|
+
* @apiName GetMempool
|
|
485
|
+
* @apiGroup Fulcrum
|
|
486
|
+
* @apiDescription Returns an object with unconfirmed UTXOs associated with an address.
|
|
487
|
+
*/
|
|
488
|
+
async getMempool (req, res) {
|
|
489
|
+
try {
|
|
490
|
+
const address = req.params.address
|
|
491
|
+
|
|
492
|
+
if (Array.isArray(address)) {
|
|
493
|
+
return res.status(400).json({
|
|
494
|
+
success: false,
|
|
495
|
+
error: 'address can not be an array. Use POST for bulk upload.'
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const cashAddr = this._validateAndConvertAddress(address)
|
|
500
|
+
|
|
501
|
+
const result = await this.fulcrumUseCases.getMempool({ address: cashAddr })
|
|
502
|
+
return res.status(200).json(result)
|
|
503
|
+
} catch (err) {
|
|
504
|
+
return this.handleError(err, res)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* @api {post} /v6/fulcrum/unconfirmed Get unconfirmed utxos for an array of addresses
|
|
510
|
+
* @apiName GetMempoolBulk
|
|
511
|
+
* @apiGroup Fulcrum
|
|
512
|
+
* @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address. Limited to 20 items per request.
|
|
513
|
+
*/
|
|
514
|
+
async mempoolBulk (req, res) {
|
|
515
|
+
try {
|
|
516
|
+
const addresses = req.body.addresses
|
|
517
|
+
|
|
518
|
+
if (!Array.isArray(addresses)) {
|
|
519
|
+
return res.status(400).json({
|
|
520
|
+
success: false,
|
|
521
|
+
error: 'addresses needs to be an array. Use GET for single address.'
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!this.adapters.fullNode.validateArraySize(addresses.length)) {
|
|
526
|
+
return res.status(400).json({
|
|
527
|
+
success: false,
|
|
528
|
+
error: 'Array too large.'
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Validate and convert all addresses
|
|
533
|
+
const validatedAddresses = []
|
|
534
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
535
|
+
try {
|
|
536
|
+
const cashAddr = this._validateAndConvertAddress(addresses[i])
|
|
537
|
+
validatedAddresses.push(cashAddr)
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return res.status(400).json({
|
|
540
|
+
success: false,
|
|
541
|
+
error: err.message
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const result = await this.fulcrumUseCases.getMempoolBulk({ addresses: validatedAddresses })
|
|
547
|
+
return res.status(200).json(result)
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return this.handleError(err, res)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
handleError (err, res) {
|
|
554
|
+
wlogger.error('Error in FulcrumRESTController:', err)
|
|
555
|
+
|
|
556
|
+
const status = err.status || 500
|
|
557
|
+
const message = err.message || 'Internal server error'
|
|
558
|
+
|
|
559
|
+
return res.status(status).json({ error: message })
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export default FulcrumRESTController
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
REST API router for /full-node/fulcrum routes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import FulcrumRESTController from './controller.js'
|
|
7
|
+
|
|
8
|
+
class FulcrumRouter {
|
|
9
|
+
constructor (localConfig = {}) {
|
|
10
|
+
this.adapters = localConfig.adapters
|
|
11
|
+
if (!this.adapters) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Instance of Adapters library required when instantiating Fulcrum 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 Fulcrum REST Router.'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dependencies = {
|
|
25
|
+
adapters: this.adapters,
|
|
26
|
+
useCases: this.useCases
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.fulcrumController = new FulcrumRESTController(dependencies)
|
|
30
|
+
|
|
31
|
+
this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
|
|
32
|
+
this.baseUrl = `${this.apiPrefix}/fulcrum`
|
|
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.fulcrumController.root)
|
|
45
|
+
this.router.get('/balance/:address', this.fulcrumController.getBalance)
|
|
46
|
+
this.router.post('/balance', this.fulcrumController.balanceBulk)
|
|
47
|
+
this.router.get('/utxos/:address', this.fulcrumController.getUtxos)
|
|
48
|
+
this.router.post('/utxos', this.fulcrumController.utxosBulk)
|
|
49
|
+
this.router.get('/tx/data/:txid', this.fulcrumController.getTransactionDetails)
|
|
50
|
+
this.router.post('/tx/data', this.fulcrumController.transactionDetailsBulk)
|
|
51
|
+
this.router.post('/tx/broadcast', this.fulcrumController.broadcastTransaction)
|
|
52
|
+
this.router.get('/block/headers/:height', this.fulcrumController.getBlockHeaders)
|
|
53
|
+
this.router.post('/block/headers', this.fulcrumController.blockHeadersBulk)
|
|
54
|
+
this.router.get('/transactions/:address', this.fulcrumController.getTransactions)
|
|
55
|
+
this.router.get('/transactions/:address/:allTxs', this.fulcrumController.getTransactions)
|
|
56
|
+
this.router.post('/transactions', this.fulcrumController.transactionsBulk)
|
|
57
|
+
this.router.get('/unconfirmed/:address', this.fulcrumController.getMempool)
|
|
58
|
+
this.router.post('/unconfirmed', this.fulcrumController.mempoolBulk)
|
|
59
|
+
|
|
60
|
+
app.use(this.baseUrl, this.router)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default FulcrumRouter
|
|
@@ -155,7 +155,7 @@ class BlockchainRESTController {
|
|
|
155
155
|
})
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
if (!this.adapters.fullNode.validateArraySize(hashes.length
|
|
158
|
+
if (!this.adapters.fullNode.validateArraySize(hashes.length)) {
|
|
159
159
|
return res.status(400).json({ error: 'Array too large.' })
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -238,7 +238,7 @@ class BlockchainRESTController {
|
|
|
238
238
|
})
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
if (!this.adapters.fullNode.validateArraySize(txids.length
|
|
241
|
+
if (!this.adapters.fullNode.validateArraySize(txids.length)) {
|
|
242
242
|
return res.status(400).json({ error: 'Array too large.' })
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -415,7 +415,7 @@ class BlockchainRESTController {
|
|
|
415
415
|
})
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
if (!this.adapters.fullNode.validateArraySize(txids.length
|
|
418
|
+
if (!this.adapters.fullNode.validateArraySize(txids.length)) {
|
|
419
419
|
return res.status(400).json({ error: 'Array too large.' })
|
|
420
420
|
}
|
|
421
421
|
|
|
@@ -470,7 +470,7 @@ class BlockchainRESTController {
|
|
|
470
470
|
})
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
if (!this.adapters.fullNode.validateArraySize(proofs.length
|
|
473
|
+
if (!this.adapters.fullNode.validateArraySize(proofs.length)) {
|
|
474
474
|
return res.status(400).json({ error: 'Array too large.' })
|
|
475
475
|
}
|
|
476
476
|
|