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.
- package/.env-local +28 -0
- package/bin/server.js +61 -9
- 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 +29 -25
- package/src/config/x402.js +7 -0
- 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 +23 -3
- package/src/controllers/rest-api/price/controller.js +96 -0
- package/src/controllers/rest-api/price/router.js +52 -0
- 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/middleware/basic-auth.js +61 -0
- 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 +10 -0
- package/src/use-cases/price-use-cases.js +83 -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/price-controller-unit.js +116 -0
- package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
- package/test/unit/controllers/rest-api-index-unit.js +67 -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/price-use-cases-unit.js +103 -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/{index.js → psf-bch-api.js} +0 -0
- /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,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/
|
|
11
|
-
import ControlRouter from './full-node/control/
|
|
12
|
-
import DSProofRouter from './full-node/dsproof/
|
|
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
|