psf-bch-api 1.3.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.env-local +26 -5
  2. package/bin/server.js +60 -9
  3. package/package.json +5 -4
  4. package/production/docker/.env-local +34 -0
  5. package/production/docker/Dockerfile +8 -25
  6. package/production/docker/docker-compose.yml +4 -3
  7. package/production/docker/temp.js +7 -0
  8. package/src/config/env/common.js +10 -3
  9. package/src/config/x402.js +7 -0
  10. package/src/controllers/rest-api/encryption/controller.js +100 -0
  11. package/src/controllers/rest-api/encryption/router.js +51 -0
  12. package/src/controllers/rest-api/fulcrum/controller.js +2 -1
  13. package/src/controllers/rest-api/index.js +8 -0
  14. package/src/controllers/rest-api/price/controller.js +96 -0
  15. package/src/controllers/rest-api/price/router.js +52 -0
  16. package/src/controllers/rest-api/slp/controller.js +3 -1
  17. package/src/middleware/basic-auth.js +61 -0
  18. package/src/use-cases/encryption-use-cases.js +120 -0
  19. package/src/use-cases/fulcrum-use-cases.js +10 -2
  20. package/src/use-cases/index.js +9 -0
  21. package/src/use-cases/price-use-cases.js +83 -0
  22. package/src/use-cases/slp-use-cases.js +5 -1
  23. package/test/unit/controllers/encryption-controller-unit.js +203 -0
  24. package/test/unit/controllers/price-controller-unit.js +116 -0
  25. package/test/unit/controllers/rest-api-index-unit.js +15 -0
  26. package/test/unit/use-cases/encryption-use-cases-unit.js +247 -0
  27. package/test/unit/use-cases/fulcrum-use-cases-unit.js +1 -1
  28. package/test/unit/use-cases/price-use-cases-unit.js +103 -0
  29. package/test/unit/use-cases/slp-use-cases-unit.js +1 -1
  30. /package/{index.js → psf-bch-api.js} +0 -0
package/.env-local CHANGED
@@ -1,13 +1,34 @@
1
+ # START INFRASTRUCTURE SETUP
2
+
1
3
  # Full Node Connection
2
4
  RPC_BASEURL=http://172.17.0.1:8332
3
5
  RPC_USERNAME=bitcoin
4
6
  RPC_PASSWORD=password
5
7
 
6
- # x402 payments required to access this API?
7
- X402_ENABLED=false
8
-
9
8
  # Fulcrum Indexer
10
- FULCRUM_API=http://192.168.2.127:3001
9
+ FULCRUM_API=http://172.17.0.1:3001/v1
11
10
 
12
11
  # SLP Indexer
13
- SLP_INDEXER_API=http://192.168.2.127:5010
12
+ SLP_INDEXER_API=http://localhost:5010
13
+
14
+ # REST API URL for wallet operations
15
+ LOCAL_RESTURL=http://localhost:5942/v6
16
+
17
+ # END INFRASTRUCTURE SETUP
18
+
19
+
20
+ # START ACCESS CONTROL
21
+
22
+ PORT=5942
23
+
24
+ # x402 payments required to access this API?
25
+ X402_ENABLED=true
26
+ SERVER_BCH_ADDRESS=bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d
27
+ FACILITATOR_URL=http://localhost:4345/facilitator
28
+
29
+ # Basic Authentication required to access this API?
30
+ USE_BASIC_AUTH=true
31
+ BASIC_AUTH_TOKEN=some-random-token
32
+
33
+ # END ACCESS CONTROL
34
+
package/bin/server.js CHANGED
@@ -15,7 +15,8 @@ import { dirname, join } from 'path'
15
15
  import config from '../src/config/index.js'
16
16
  import Controllers from '../src/controllers/index.js'
17
17
  import wlogger from '../src/adapters/wlogger.js'
18
- import { buildX402Routes, getX402Settings } from '../src/config/x402.js'
18
+ import { buildX402Routes, getX402Settings, getBasicAuthSettings } from '../src/config/x402.js'
19
+ import { basicAuthMiddleware } from '../src/middleware/basic-auth.js'
19
20
 
20
21
  // Load environment variables
21
22
  dotenv.config()
@@ -60,6 +61,7 @@ class Server {
60
61
  const app = express()
61
62
 
62
63
  const x402Settings = getX402Settings()
64
+ const basicAuthSettings = getBasicAuthSettings()
63
65
 
64
66
  // MIDDLEWARE START
65
67
  app.use(express.json())
@@ -72,23 +74,72 @@ class Server {
72
74
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
73
75
  }))
74
76
 
75
- // Wrap all endpoints in x402 middleware. This handles payments for the API calls.
76
- if (x402Settings.enabled) {
77
+ // Apply basic auth middleware if enabled
78
+ // This must run before x402 middleware to set req.locals.basicAuthValid
79
+ if (basicAuthSettings.enabled) {
80
+ wlogger.info('Basic auth middleware enabled')
81
+ app.use(basicAuthMiddleware)
82
+ }
83
+
84
+ // Apply x402 middleware based on configuration
85
+ // Logic:
86
+ // - If X402_ENABLED=false OR USE_BASIC_AUTH=false: Don't apply x402 (no rate limits)
87
+ // - If X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally (bypass if basic auth valid)
88
+
89
+ // Apply access control middleware based on configuration
90
+ if (x402Settings.enabled && basicAuthSettings.enabled) {
91
+ // X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally
77
92
  const routes = buildX402Routes(this.config.apiPrefix)
78
93
  const facilitatorOptions = x402Settings.facilitatorUrl
79
94
  ? { url: x402Settings.facilitatorUrl }
80
95
  : undefined
81
96
 
82
- wlogger.info(`x402 middleware enabled; enforcing ${x402Settings.priceSat} satoshis per request`)
83
- app.use(
84
- x402PaymentMiddleware(
97
+ wlogger.info(`x402 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceSat} satoshis per request (unless basic auth provided)`)
98
+
99
+ // Create conditional x402 middleware that bypasses if basic auth is valid
100
+ const conditionalX402Middleware = (req, res, next) => {
101
+ // If basic auth is valid, bypass x402
102
+ if (req.locals?.basicAuthValid === true) {
103
+ return next()
104
+ }
105
+
106
+ // Otherwise, apply x402 middleware
107
+ return x402PaymentMiddleware(
85
108
  x402Settings.serverAddress,
86
109
  routes,
87
110
  facilitatorOptions
88
- )
89
- )
111
+ )(req, res, next)
112
+ }
113
+
114
+ app.use(conditionalX402Middleware)
115
+ } else if (basicAuthSettings.enabled && !x402Settings.enabled) {
116
+ // USE_BASIC_AUTH=true AND X402_ENABLED=false: Require basic auth, reject unauthenticated requests
117
+ wlogger.info('Basic auth enforcement enabled (x402 disabled)')
118
+
119
+ // Middleware that rejects requests without valid basic auth
120
+ const requireBasicAuthMiddleware = (req, res, next) => {
121
+ // Skip auth check for health endpoint and root
122
+ if (req.path === '/health' || req.path === '/') {
123
+ return next()
124
+ }
125
+
126
+ // If basic auth is valid, allow the request
127
+ if (req.locals?.basicAuthValid === true) {
128
+ return next()
129
+ }
130
+
131
+ // Reject unauthenticated requests
132
+ wlogger.warn(`Unauthenticated request rejected: ${req.method} ${req.path}`)
133
+ return res.status(401).json({
134
+ error: 'Unauthorized',
135
+ message: 'Valid Bearer token required in Authorization header'
136
+ })
137
+ }
138
+
139
+ app.use(requireBasicAuthMiddleware)
90
140
  } else {
91
- wlogger.info('x402 middleware disabled via configuration')
141
+ // X402_ENABLED=false AND USE_BASIC_AUTH=false: No access control middleware
142
+ wlogger.info('No access control middleware enabled')
92
143
  }
93
144
 
94
145
  // Endpoint logging middleware
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "psf-bch-api",
3
- "version": "1.3.0",
4
- "main": "index.js",
3
+ "version": "7.2.0",
4
+ "main": "psf-bch-api.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "node bin/server.js",
@@ -15,12 +15,13 @@
15
15
  "license": "MIT",
16
16
  "description": "REST API proxy to Bitcoin Cash infrastructure",
17
17
  "dependencies": {
18
- "@psf/bch-js": "6.8.3",
18
+ "@psf/bch-js": "7.1.0",
19
19
  "axios": "1.7.7",
20
20
  "cors": "2.8.5",
21
21
  "dotenv": "16.3.1",
22
22
  "express": "5.1.0",
23
- "minimal-slp-wallet": "5.13.3",
23
+ "minimal-slp-wallet": "7.0.1",
24
+ "psffpp": "1.2.0",
24
25
  "slp-token-media": "1.2.10",
25
26
  "winston": "3.11.0",
26
27
  "winston-daily-rotate-file": "4.7.1",
@@ -0,0 +1,34 @@
1
+ # START INFRASTRUCTURE SETUP
2
+
3
+ # Full Node Connection
4
+ RPC_BASEURL=http://172.17.0.1:8332
5
+ RPC_USERNAME=bitcoin
6
+ RPC_PASSWORD=password
7
+
8
+ # Fulcrum Indexer
9
+ FULCRUM_API=http://172.17.0.1:3001/v1
10
+
11
+ # SLP Indexer
12
+ SLP_INDEXER_API=http://localhost:5010
13
+
14
+ # REST API URL for wallet operations
15
+ LOCAL_RESTURL=http://localhost:5942/v6
16
+
17
+ # END INFRASTRUCTURE SETUP
18
+
19
+
20
+ # START ACCESS CONTROL
21
+
22
+ PORT=5942
23
+
24
+ # x402 payments required to access this API?
25
+ X402_ENABLED=true
26
+ SERVER_BCH_ADDRESS=bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d
27
+ FACILITATOR_URL=http://localhost:4345/facilitator
28
+
29
+ # Basic Authentication required to access this API?
30
+ USE_BASIC_AUTH=true
31
+ BASIC_AUTH_TOKEN=some-random-token
32
+
33
+ # END ACCESS CONTROL
34
+
@@ -2,8 +2,6 @@
2
2
  #
3
3
 
4
4
  #IMAGE BUILD COMMANDS
5
- # ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
6
- #FROM christroutner/ct-base-ubuntu
7
5
  FROM ubuntu:22.04
8
6
  MAINTAINER Chris Troutner <chris.troutner@gmail.com>
9
7
 
@@ -47,39 +45,24 @@ RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
47
45
 
48
46
  # Clone the rest.bitcoin.com repository
49
47
  WORKDIR /home/safeuser
50
- RUN git clone https://github.com/christroutner/REST2NOSTR
48
+ RUN git clone https://github.com/Permissionless-Software-Foundation/psf-bch-api
51
49
 
52
50
  # Switch to the desired branch. `master` is usually stable,
53
51
  # and `stage` has the most up-to-date changes.
54
- WORKDIR /home/safeuser/REST2NOSTR
55
-
56
- # For development: switch to unstable branch
57
- #RUN git checkout pin-ipfs
52
+ WORKDIR /home/safeuser/psf-bch-api
58
53
 
59
54
  # Install dependencies
60
55
  RUN npm install
56
+ RUN npm install minimal-slp-wallet
61
57
 
62
58
  # Generate the API docs
63
59
  RUN npm run docs
64
60
 
65
- #VOLUME /home/safeuser/keys
66
-
67
- # Make leveldb folders
68
- #RUN mkdir leveldb
69
- #WORKDIR /home/safeuser/psf-slp-indexer/leveldb
70
- #RUN mkdir current
71
- #RUN mkdir zips
72
- #RUN mkdir backup
73
- #WORKDIR /home/safeuser/psf-slp-indexer/leveldb/zips
74
- #COPY restore-auto.sh restore-auto.sh
75
- #WORKDIR /home/safeuser/psf-slp-indexer
61
+ COPY .env-local .env
76
62
 
77
- # Expose the port the API will be served on.
78
- #EXPOSE 5011
79
63
 
80
- # Start the application.
81
- #COPY start-production.sh start-production.sh
82
- VOLUME start-rest2nostr.sh
83
- CMD ["./start-rest2nostr.sh"]
64
+ CMD ["npm", "start"]
84
65
 
85
- #CMD ["npm", "start"]
66
+ # Used to debug the container.
67
+ #COPY temp.js temp.js
68
+ #CMD ["node", "temp.js"]
@@ -1,9 +1,9 @@
1
1
  # Start the service with the command 'docker-compose up -d'
2
2
 
3
3
  services:
4
- rest2nostr:
4
+ psf-bch-api:
5
5
  build: .
6
- container_name: rest2nostr
6
+ container_name: psf-bch-api
7
7
  logging:
8
8
  driver: 'json-file'
9
9
  options:
@@ -15,5 +15,6 @@ services:
15
15
  ports:
16
16
  - '5942:5942' # <host port>:<container port>
17
17
  volumes:
18
- - ./start-rest2nostr.sh:/home/safeuser/REST2NOSTR/start-rest2nostr.sh
18
+ #- ./start-rest2nostr.sh:/home/safeuser/REST2NOSTR/start-rest2nostr.sh
19
+ - ./.env:/home/safeuser/.env
19
20
  restart: always
@@ -0,0 +1,7 @@
1
+ // Simple Node.js app that prints 'hello world' every 10 seconds
2
+
3
+ setInterval(() => {
4
+ console.log('hello world')
5
+ }, 10000)
6
+
7
+ console.log('Timer started. Printing "hello world" every 10 seconds...')
@@ -34,13 +34,18 @@ const priceSat = Number.isFinite(parsedPriceSat) && parsedPriceSat > 0 ? parsedP
34
34
  const x402Defaults = {
35
35
  enabled: normalizeBoolean(process.env.X402_ENABLED, true),
36
36
  facilitatorUrl: process.env.FACILITATOR_URL || 'http://localhost:4345/facilitator',
37
- serverAddress: process.env.SERVER_BCH_ADDRESS || 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
37
+ serverAddress: process.env.SERVER_BCH_ADDRESS || 'bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr',
38
38
  priceSat
39
39
  }
40
40
 
41
+ const basicAuthDefaults = {
42
+ enabled: normalizeBoolean(process.env.USE_BASIC_AUTH, false),
43
+ token: process.env.BASIC_AUTH_TOKEN || ''
44
+ }
45
+
41
46
  export default {
42
47
  // Server port
43
- port: process.env.PORT || 5942,
48
+ port: parseInt(process.env.PORT, 10) || 5942,
44
49
 
45
50
  // Environment
46
51
  env: process.env.NODE_ENV || 'development',
@@ -73,13 +78,15 @@ export default {
73
78
  },
74
79
 
75
80
  // REST API URL for wallet operations
76
- restURL: process.env.REST_URL || process.env.LOCAL_RESTURL || 'http://127.0.0.1:3000/v5/',
81
+ restURL: process.env.REST_URL || process.env.LOCAL_RESTURL || 'http://127.0.0.1:5942/v6/',
77
82
 
78
83
  // IPFS Gateway URL
79
84
  ipfsGateway: process.env.IPFS_GATEWAY || 'p2wdb-gateway-678.fullstack.cash',
80
85
 
81
86
  x402: x402Defaults,
82
87
 
88
+ basicAuth: basicAuthDefaults,
89
+
83
90
  // Version
84
91
  version
85
92
  }
@@ -41,3 +41,10 @@ export function getX402Settings () {
41
41
  priceSat: config.x402?.priceSat
42
42
  }
43
43
  }
44
+
45
+ export function getBasicAuthSettings () {
46
+ return {
47
+ enabled: Boolean(config.basicAuth?.enabled),
48
+ token: config.basicAuth?.token || ''
49
+ }
50
+ }
@@ -0,0 +1,100 @@
1
+ /*
2
+ REST API Controller for the /encryption routes.
3
+ */
4
+
5
+ import wlogger from '../../../adapters/wlogger.js'
6
+
7
+ class EncryptionRESTController {
8
+ constructor (localConfig = {}) {
9
+ this.adapters = localConfig.adapters
10
+ if (!this.adapters) {
11
+ throw new Error(
12
+ 'Instance of Adapters library required when instantiating Encryption REST Controller.'
13
+ )
14
+ }
15
+
16
+ this.useCases = localConfig.useCases
17
+ if (!this.useCases || !this.useCases.encryption) {
18
+ throw new Error(
19
+ 'Instance of Encryption use cases required when instantiating Encryption REST Controller.'
20
+ )
21
+ }
22
+
23
+ this.encryptionUseCases = this.useCases.encryption
24
+
25
+ // Bind functions
26
+ this.root = this.root.bind(this)
27
+ this.getPublicKey = this.getPublicKey.bind(this)
28
+ this.handleError = this.handleError.bind(this)
29
+ }
30
+
31
+ /**
32
+ * @api {get} /v6/encryption/ Service status
33
+ * @apiName EncryptionRoot
34
+ * @apiGroup Encryption
35
+ *
36
+ * @apiDescription Returns the status of the encryption service.
37
+ *
38
+ * @apiSuccess {String} status Service identifier
39
+ */
40
+ async root (req, res) {
41
+ return res.status(200).json({ status: 'encryption' })
42
+ }
43
+
44
+ /**
45
+ * @api {get} /v6/encryption/publickey/:address Get public key for a BCH address
46
+ * @apiName GetPublicKey
47
+ * @apiGroup Encryption
48
+ * @apiDescription Searches the blockchain for a public key associated with a
49
+ * BCH address. Returns an object. If successful, the publicKey property will
50
+ * contain a hexadecimal representation of the public key.
51
+ *
52
+ * @apiParam {String} address BCH address (cash address or legacy format)
53
+ *
54
+ * @apiExample Example usage:
55
+ * curl -X GET "https://api.fullstack.cash/v6/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
56
+ *
57
+ * @apiSuccess {Boolean} success Indicates if the operation was successful
58
+ * @apiSuccess {String} publicKey The public key in hexadecimal format, or "not found"
59
+ */
60
+ async getPublicKey (req, res) {
61
+ try {
62
+ const address = req.params.address
63
+
64
+ // Reject if address is an array
65
+ if (Array.isArray(address)) {
66
+ res.status(400)
67
+ return res.json({
68
+ success: false,
69
+ error: 'address can not be an array.'
70
+ })
71
+ }
72
+
73
+ // Reject if address is missing
74
+ if (!address) {
75
+ res.status(400)
76
+ return res.json({
77
+ success: false,
78
+ error: 'address is required.'
79
+ })
80
+ }
81
+
82
+ const result = await this.encryptionUseCases.getPublicKey({ address })
83
+
84
+ return res.status(200).json(result)
85
+ } catch (err) {
86
+ return this.handleError(err, res)
87
+ }
88
+ }
89
+
90
+ handleError (err, res) {
91
+ wlogger.error('Error in EncryptionRESTController:', err)
92
+
93
+ const status = err.status || 500
94
+ const message = err.message || 'Internal server error'
95
+
96
+ return res.status(status).json({ success: false, error: message })
97
+ }
98
+ }
99
+
100
+ export default EncryptionRESTController
@@ -0,0 +1,51 @@
1
+ /*
2
+ REST API router for /encryption routes.
3
+ */
4
+
5
+ import express from 'express'
6
+ import EncryptionRESTController from './controller.js'
7
+
8
+ class EncryptionRouter {
9
+ constructor (localConfig = {}) {
10
+ this.adapters = localConfig.adapters
11
+ if (!this.adapters) {
12
+ throw new Error(
13
+ 'Instance of Adapters library required when instantiating Encryption 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 Encryption REST Router.'
21
+ )
22
+ }
23
+
24
+ const dependencies = {
25
+ adapters: this.adapters,
26
+ useCases: this.useCases
27
+ }
28
+
29
+ this.encryptionController = new EncryptionRESTController(dependencies)
30
+
31
+ this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
32
+ this.baseUrl = `${this.apiPrefix}/encryption`
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.encryptionController.root)
45
+ this.router.get('/publickey/:address', this.encryptionController.getPublicKey)
46
+
47
+ app.use(this.baseUrl, this.router)
48
+ }
49
+ }
50
+
51
+ export default EncryptionRouter
@@ -4,8 +4,9 @@
4
4
 
5
5
  import wlogger from '../../../adapters/wlogger.js'
6
6
  import BCHJS from '@psf/bch-js'
7
+ import config from '../../../config/index.js'
7
8
 
8
- const bchjs = new BCHJS()
9
+ const bchjs = new BCHJS({ restURL: config.restURL })
9
10
 
10
11
  class FulcrumRESTController {
11
12
  constructor (localConfig = {}) {
@@ -10,8 +10,10 @@
10
10
  import BlockchainRouter from './full-node/blockchain/router.js'
11
11
  import ControlRouter from './full-node/control/router.js'
12
12
  import DSProofRouter from './full-node/dsproof/router.js'
13
+ import EncryptionRouter from './encryption/router.js'
13
14
  import FulcrumRouter from './fulcrum/router.js'
14
15
  import MiningRouter from './full-node/mining/router.js'
16
+ import PriceRouter from './price/router.js'
15
17
  import RawTransactionsRouter from './full-node/rawtransactions/router.js'
16
18
  import SlpRouter from './slp/router.js'
17
19
  import config from '../../config/index.js'
@@ -69,12 +71,18 @@ class RESTControllers {
69
71
  const dsproofRouter = new DSProofRouter(dependencies)
70
72
  dsproofRouter.attach(app)
71
73
 
74
+ const encryptionRouter = new EncryptionRouter(dependencies)
75
+ encryptionRouter.attach(app)
76
+
72
77
  const fulcrumRouter = new FulcrumRouter(dependencies)
73
78
  fulcrumRouter.attach(app)
74
79
 
75
80
  const miningRouter = new MiningRouter(dependencies)
76
81
  miningRouter.attach(app)
77
82
 
83
+ const priceRouter = new PriceRouter(dependencies)
84
+ priceRouter.attach(app)
85
+
78
86
  const rawtransactionsRouter = new RawTransactionsRouter(dependencies)
79
87
  rawtransactionsRouter.attach(app)
80
88
 
@@ -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
@@ -0,0 +1,52 @@
1
+ /*
2
+ REST API router for /price routes.
3
+ */
4
+
5
+ import express from 'express'
6
+ import PriceRESTController from './controller.js'
7
+
8
+ class PriceRouter {
9
+ constructor (localConfig = {}) {
10
+ this.adapters = localConfig.adapters
11
+ if (!this.adapters) {
12
+ throw new Error(
13
+ 'Instance of Adapters library required when instantiating Price 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 Price REST Router.'
21
+ )
22
+ }
23
+
24
+ const dependencies = {
25
+ adapters: this.adapters,
26
+ useCases: this.useCases
27
+ }
28
+
29
+ this.priceController = new PriceRESTController(dependencies)
30
+
31
+ this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
32
+ this.baseUrl = `${this.apiPrefix}/price`
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.priceController.root)
45
+ this.router.get('/bchusd', this.priceController.getBCHUSD)
46
+ this.router.get('/psffpp', this.priceController.getPsffppWritePrice)
47
+
48
+ app.use(this.baseUrl, this.router)
49
+ }
50
+ }
51
+
52
+ export default PriceRouter