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.
Files changed (53) hide show
  1. package/.env-local +28 -0
  2. package/bin/server.js +61 -9
  3. package/package.json +6 -2
  4. package/src/adapters/fulcrum-api.js +124 -0
  5. package/src/adapters/full-node-rpc.js +2 -6
  6. package/src/adapters/index.js +4 -0
  7. package/src/adapters/slp-indexer-api.js +124 -0
  8. package/src/config/env/common.js +29 -25
  9. package/src/config/x402.js +7 -0
  10. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  11. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  12. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  13. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  14. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  16. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  17. package/src/controllers/rest-api/index.js +23 -3
  18. package/src/controllers/rest-api/price/controller.js +96 -0
  19. package/src/controllers/rest-api/price/router.js +52 -0
  20. package/src/controllers/rest-api/slp/controller.js +218 -0
  21. package/src/controllers/rest-api/slp/router.js +55 -0
  22. package/src/controllers/timer-controller.js +1 -1
  23. package/src/middleware/basic-auth.js +61 -0
  24. package/src/use-cases/fulcrum-use-cases.js +155 -0
  25. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  26. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  27. package/src/use-cases/index.js +10 -0
  28. package/src/use-cases/price-use-cases.js +83 -0
  29. package/src/use-cases/slp-use-cases.js +321 -0
  30. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  31. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  32. package/test/unit/controllers/mining-controller-unit.js +139 -0
  33. package/test/unit/controllers/price-controller-unit.js +116 -0
  34. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  35. package/test/unit/controllers/rest-api-index-unit.js +67 -3
  36. package/test/unit/controllers/slp-controller-unit.js +312 -0
  37. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  38. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  39. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  40. package/test/unit/use-cases/price-use-cases-unit.js +103 -0
  41. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  42. package/src/entities/event.js +0 -71
  43. package/test/integration/api/event-integration.js +0 -250
  44. package/test/integration/api/req-integration.js +0 -173
  45. package/test/integration/api/subscription-integration.js +0 -198
  46. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  47. package/test/integration/use-cases/publish-event-integration.js +0 -104
  48. package/test/integration/use-cases/query-events-integration.js +0 -95
  49. package/test/unit/entities/event-unit.js +0 -139
  50. /package/{index.js → psf-bch-api.js} +0 -0
  51. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  52. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  53. /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
package/.env-local CHANGED
@@ -1,4 +1,32 @@
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
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
+ # x402 payments required to access this API?
23
+ X402_ENABLED=true
24
+ SERVER_BCH_ADDRESS=bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d
25
+ FACILITATOR_URL=http://localhost:4345/facilitator
26
+
27
+ # Basic Authentication required to access this API?
28
+ USE_BASIC_AUTH=true
29
+ BASIC_AUTH_TOKEN=some-random-token
30
+
31
+ # END ACCESS CONTROL
32
+
package/bin/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- Express server for REST2NOSTR Proxy API.
2
+ Express server for psf-bch-api REST API.
3
3
  The architecture of the code follows the Clean Architecture pattern.
4
4
  */
5
5
 
@@ -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,22 +74,72 @@ class Server {
72
74
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
73
75
  }))
74
76
 
75
- 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
76
92
  const routes = buildX402Routes(this.config.apiPrefix)
77
93
  const facilitatorOptions = x402Settings.facilitatorUrl
78
94
  ? { url: x402Settings.facilitatorUrl }
79
95
  : undefined
80
96
 
81
- wlogger.info(`x402 middleware enabled; enforcing ${x402Settings.priceSat} satoshis per request`)
82
- app.use(
83
- 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(
84
108
  x402Settings.serverAddress,
85
109
  routes,
86
110
  facilitatorOptions
87
- )
88
- )
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)
89
140
  } else {
90
- 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')
91
143
  }
92
144
 
93
145
  // Endpoint logging middleware
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "psf-bch-api",
3
- "version": "1.2.0",
4
- "main": "index.js",
3
+ "version": "7.1.0",
4
+ "main": "psf-bch-api.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "node bin/server.js",
@@ -15,10 +15,14 @@
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
19
  "axios": "1.7.7",
19
20
  "cors": "2.8.5",
20
21
  "dotenv": "16.3.1",
21
22
  "express": "5.1.0",
23
+ "minimal-slp-wallet": "5.13.3",
24
+ "psffpp": "1.2.0",
25
+ "slp-token-media": "1.2.10",
22
26
  "winston": "3.11.0",
23
27
  "winston-daily-rotate-file": "4.7.1",
24
28
  "x402-bch-express": "1.1.1"
@@ -0,0 +1,124 @@
1
+ /*
2
+ Adapter library for interacting with Fulcrum API service over HTTP.
3
+ */
4
+
5
+ import axios from 'axios'
6
+ import wlogger from './wlogger.js'
7
+ import config from '../config/index.js'
8
+
9
+ class FulcrumAPIAdapter {
10
+ constructor (localConfig = {}) {
11
+ this.config = localConfig.config || config
12
+
13
+ // Allow missing config for testing environments
14
+ if (!this.config.fulcrumApi || !this.config.fulcrumApi.baseUrl) {
15
+ if (process.env.NODE_ENV === 'test' || process.env.TEST) {
16
+ // In test environment, create a mock baseURL
17
+ this.config.fulcrumApi = {
18
+ baseUrl: 'http://localhost:50001',
19
+ timeoutMs: 15000
20
+ }
21
+ } else {
22
+ throw new Error('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.')
23
+ }
24
+ }
25
+
26
+ const {
27
+ baseUrl,
28
+ timeoutMs = 15000
29
+ } = this.config.fulcrumApi
30
+
31
+ this.http = axios.create({
32
+ baseURL: baseUrl,
33
+ timeout: timeoutMs
34
+ })
35
+ }
36
+
37
+ async get (path) {
38
+ try {
39
+ const response = await this.http.get(path)
40
+ return response.data
41
+ } catch (err) {
42
+ throw this._handleError(err)
43
+ }
44
+ }
45
+
46
+ async post (path, data) {
47
+ try {
48
+ const response = await this.http.post(path, data)
49
+ return response.data
50
+ } catch (err) {
51
+ throw this._handleError(err)
52
+ }
53
+ }
54
+
55
+ _handleError (err) {
56
+ const { status, message } = this.decodeError(err)
57
+ const error = new Error(message)
58
+ error.status = status
59
+ error.originalError = err
60
+ return error
61
+ }
62
+
63
+ decodeError (err) {
64
+ try {
65
+ // Attempt to extract error message from response data
66
+ if (err.response && err.response.data) {
67
+ const data = err.response.data
68
+ // Handle structured error responses
69
+ if (data.error) {
70
+ return this._formatError(data.error, err.response.status || 400)
71
+ }
72
+ // Handle string error messages
73
+ if (typeof data === 'string') {
74
+ return this._formatError(data, err.response.status || 400)
75
+ }
76
+ // Handle object responses that might contain error info
77
+ if (typeof data === 'object' && data.message) {
78
+ return this._formatError(data.message, err.response.status || 400)
79
+ }
80
+ // Fallback to returning the status
81
+ return this._formatError('Fulcrum API error', err.response.status || 500)
82
+ }
83
+
84
+ // Network errors
85
+ if (err.message) {
86
+ if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) {
87
+ return this._formatError(
88
+ 'Network error: Could not communicate with Fulcrum API service.',
89
+ 503
90
+ )
91
+ }
92
+ }
93
+
94
+ if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) {
95
+ return this._formatError(
96
+ 'Network error: Could not communicate with Fulcrum API service.',
97
+ 503
98
+ )
99
+ }
100
+
101
+ if (err.error && typeof err.error === 'string' && err.error.includes('429')) {
102
+ return this._formatError('429 Too Many Requests', 429)
103
+ }
104
+
105
+ if (err.message) {
106
+ return this._formatError(err.message, err.status || 422)
107
+ }
108
+
109
+ return this._formatError('Unhandled Fulcrum API error', 500)
110
+ } catch (decodeError) {
111
+ wlogger.error('Unhandled error in FulcrumAPIAdapter.decodeError()', decodeError)
112
+ return this._formatError('Internal server error', 500)
113
+ }
114
+ }
115
+
116
+ _formatError (message, status = 500) {
117
+ return {
118
+ message: message || 'Internal server error',
119
+ status: status || 500
120
+ }
121
+ }
122
+ }
123
+
124
+ export default FulcrumAPIAdapter
@@ -113,12 +113,8 @@ class FullNodeRPCAdapter {
113
113
  }
114
114
  }
115
115
 
116
- validateArraySize (length, options = {}) {
117
- const { isProUser = false } = options
118
- const freemiumLimit = Number(this.config.fullNode?.freemiumArrayLimit || 20)
119
- const proLimit = Number(this.config.fullNode?.proArrayLimit || freemiumLimit)
120
-
121
- const limit = isProUser ? proLimit : freemiumLimit
116
+ validateArraySize (length) {
117
+ const limit = 20
122
118
  return length <= limit
123
119
  }
124
120
 
@@ -7,6 +7,8 @@
7
7
  // Load individual adapter libraries.
8
8
  // import NostrRelayAdapter from './nostr-relay.js'
9
9
  import FullNodeRPCAdapter from './full-node-rpc.js'
10
+ import FulcrumAPIAdapter from './fulcrum-api.js'
11
+ import SlpIndexerAPIAdapter from './slp-indexer-api.js'
10
12
  import config from '../config/index.js'
11
13
 
12
14
  class Adapters {
@@ -33,6 +35,8 @@ class Adapters {
33
35
  // this.nostrRelay = this.nostrRelays[0]
34
36
 
35
37
  this.fullNode = new FullNodeRPCAdapter({ config: this.config })
38
+ this.fulcrum = new FulcrumAPIAdapter({ config: this.config })
39
+ this.slpIndexer = new SlpIndexerAPIAdapter({ config: this.config })
36
40
  }
37
41
 
38
42
  async start () {
@@ -0,0 +1,124 @@
1
+ /*
2
+ Adapter library for interacting with SLP Indexer API service over HTTP.
3
+ */
4
+
5
+ import axios from 'axios'
6
+ import wlogger from './wlogger.js'
7
+ import config from '../config/index.js'
8
+
9
+ class SlpIndexerAPIAdapter {
10
+ constructor (localConfig = {}) {
11
+ this.config = localConfig.config || config
12
+
13
+ // Allow missing config for testing environments
14
+ if (!this.config.slpIndexerApi || !this.config.slpIndexerApi.baseUrl) {
15
+ if (process.env.NODE_ENV === 'test' || process.env.TEST) {
16
+ // In test environment, create a mock baseURL
17
+ this.config.slpIndexerApi = {
18
+ baseUrl: 'http://localhost:5021',
19
+ timeoutMs: 15000
20
+ }
21
+ } else {
22
+ throw new Error('SLP_INDEXER_API env var not set. Can not connect to PSF SLP indexer.')
23
+ }
24
+ }
25
+
26
+ const {
27
+ baseUrl,
28
+ timeoutMs = 15000
29
+ } = this.config.slpIndexerApi
30
+
31
+ this.http = axios.create({
32
+ baseURL: baseUrl,
33
+ timeout: timeoutMs
34
+ })
35
+ }
36
+
37
+ async get (path) {
38
+ try {
39
+ const response = await this.http.get(path)
40
+ return response.data
41
+ } catch (err) {
42
+ throw this._handleError(err)
43
+ }
44
+ }
45
+
46
+ async post (path, data) {
47
+ try {
48
+ const response = await this.http.post(path, data)
49
+ return response.data
50
+ } catch (err) {
51
+ throw this._handleError(err)
52
+ }
53
+ }
54
+
55
+ _handleError (err) {
56
+ const { status, message } = this.decodeError(err)
57
+ const error = new Error(message)
58
+ error.status = status
59
+ error.originalError = err
60
+ return error
61
+ }
62
+
63
+ decodeError (err) {
64
+ try {
65
+ // Attempt to extract error message from response data
66
+ if (err.response && err.response.data) {
67
+ const data = err.response.data
68
+ // Handle structured error responses
69
+ if (data.error) {
70
+ return this._formatError(data.error, err.response.status || 400)
71
+ }
72
+ // Handle string error messages
73
+ if (typeof data === 'string') {
74
+ return this._formatError(data, err.response.status || 400)
75
+ }
76
+ // Handle object responses that might contain error info
77
+ if (typeof data === 'object' && data.message) {
78
+ return this._formatError(data.message, err.response.status || 400)
79
+ }
80
+ // Fallback to returning the status
81
+ return this._formatError('SLP Indexer API error', err.response.status || 500)
82
+ }
83
+
84
+ // Network errors
85
+ if (err.message) {
86
+ if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) {
87
+ return this._formatError(
88
+ 'Network error: Could not communicate with SLP Indexer API service.',
89
+ 503
90
+ )
91
+ }
92
+ }
93
+
94
+ if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) {
95
+ return this._formatError(
96
+ 'Network error: Could not communicate with SLP Indexer API service.',
97
+ 503
98
+ )
99
+ }
100
+
101
+ if (err.error && typeof err.error === 'string' && err.error.includes('429')) {
102
+ return this._formatError('429 Too Many Requests', 429)
103
+ }
104
+
105
+ if (err.message) {
106
+ return this._formatError(err.message, err.status || 422)
107
+ }
108
+
109
+ return this._formatError('Unhandled SLP Indexer API error', 500)
110
+ } catch (decodeError) {
111
+ wlogger.error('Unhandled error in SlpIndexerAPIAdapter.decodeError()', decodeError)
112
+ return this._formatError('Internal server error', 500)
113
+ }
114
+ }
115
+
116
+ _formatError (message, status = 500) {
117
+ return {
118
+ message: message || 'Internal server error',
119
+ status: status || 500
120
+ }
121
+ }
122
+ }
123
+
124
+ export default SlpIndexerAPIAdapter
@@ -16,6 +16,7 @@ const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../../packag
16
16
 
17
17
  const version = pkgInfo.version
18
18
 
19
+ // This function is used to convert the string input of an environment variable to a boolean value.
19
20
  const normalizeBoolean = (value, defaultValue) => {
20
21
  if (value === undefined || value === null || value === '') return defaultValue
21
22
 
@@ -25,16 +26,23 @@ const normalizeBoolean = (value, defaultValue) => {
25
26
  return defaultValue
26
27
  }
27
28
 
29
+ // By default, the price per API call is 2000 satoshis.
30
+ // But the user can override this value by setting the X402_PRICE_SAT environment variable.
28
31
  const parsedPriceSat = Number(process.env.X402_PRICE_SAT)
29
32
  const priceSat = Number.isFinite(parsedPriceSat) && parsedPriceSat > 0 ? parsedPriceSat : 2000
30
33
 
31
34
  const x402Defaults = {
32
35
  enabled: normalizeBoolean(process.env.X402_ENABLED, true),
33
36
  facilitatorUrl: process.env.FACILITATOR_URL || 'http://localhost:4345/facilitator',
34
- serverAddress: process.env.SERVER_BCH_ADDRESS || 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
37
+ serverAddress: process.env.SERVER_BCH_ADDRESS || 'bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr',
35
38
  priceSat
36
39
  }
37
40
 
41
+ const basicAuthDefaults = {
42
+ enabled: normalizeBoolean(process.env.USE_BASIC_AUTH, false),
43
+ token: process.env.BASIC_AUTH_TOKEN || ''
44
+ }
45
+
38
46
  export default {
39
47
  // Server port
40
48
  port: process.env.PORT || 5942,
@@ -48,30 +56,6 @@ export default {
48
56
  // Logging level
49
57
  logLevel: process.env.LOG_LEVEL || 'info',
50
58
 
51
- // Nostr relay configuration (array of relay URLs)
52
- nostrRelayUrls: (() => {
53
- // Support NOSTR_RELAY_URLS (plural) as comma-separated string or JSON array
54
- if (process.env.NOSTR_RELAY_URLS) {
55
- try {
56
- // Try parsing as JSON array first
57
- const parsed = JSON.parse(process.env.NOSTR_RELAY_URLS)
58
- if (Array.isArray(parsed)) {
59
- return parsed.filter(url => url && typeof url === 'string')
60
- }
61
- } catch (e) {
62
- // Not JSON, treat as comma-separated string
63
- return process.env.NOSTR_RELAY_URLS.split(',').map(url => url.trim()).filter(url => url.length > 0)
64
- }
65
- }
66
- // Backward compatibility: support NOSTR_RELAY_URL (singular)
67
- if (process.env.NOSTR_RELAY_URL) {
68
- return [process.env.NOSTR_RELAY_URL]
69
- }
70
-
71
- // Default
72
- return ['wss://nostr-relay.psfoundation.info', 'wss://relay.damus.io']
73
- })(),
74
-
75
59
  // Full node RPC configuration
76
60
  fullNode: {
77
61
  rpcBaseUrl: process.env.RPC_BASEURL || 'http://127.0.0.1:8332',
@@ -81,8 +65,28 @@ export default {
81
65
  rpcRequestIdPrefix: process.env.RPC_REQUEST_ID_PREFIX || 'psf-bch-api'
82
66
  },
83
67
 
68
+ // Fulcrum API configuration
69
+ fulcrumApi: {
70
+ baseUrl: process.env.FULCRUM_API || '',
71
+ timeoutMs: Number(process.env.FULCRUM_TIMEOUT_MS || 15000)
72
+ },
73
+
74
+ // SLP Indexer API configuration
75
+ slpIndexerApi: {
76
+ baseUrl: process.env.SLP_INDEXER_API || '',
77
+ timeoutMs: Number(process.env.SLP_INDEXER_TIMEOUT_MS || 15000)
78
+ },
79
+
80
+ // REST API URL for wallet operations
81
+ restURL: process.env.REST_URL || process.env.LOCAL_RESTURL || 'http://127.0.0.1:5942/v6/',
82
+
83
+ // IPFS Gateway URL
84
+ ipfsGateway: process.env.IPFS_GATEWAY || 'p2wdb-gateway-678.fullstack.cash',
85
+
84
86
  x402: x402Defaults,
85
87
 
88
+ basicAuth: basicAuthDefaults,
89
+
86
90
  // Version
87
91
  version
88
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
+ }