@1sat/cli 0.0.83 → 0.0.85

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/package.json CHANGED
@@ -1,49 +1,50 @@
1
1
  {
2
- "name": "@1sat/cli",
3
- "version": "0.0.83",
4
- "description": "CLI for 1Sat Ordinals SDK",
5
- "type": "module",
6
- "main": "./src/cli.ts",
7
- "bin": {
8
- "1sat": "./src/cli.ts"
9
- },
10
- "files": [
11
- "src",
12
- "scripts"
13
- ],
14
- "scripts": {
15
- "preinstall": "node scripts/check-bun.cjs",
16
- "build": "bun build ./src/cli.ts --compile --outfile=bin/1sat --external pg --external pg-native --external pg-query-stream --external tedious --external oracledb --external mysql --external mariadb --external 'mariadb/*'",
17
- "dev": "bun run src/cli.ts",
18
- "lint": "biome check src",
19
- "test": "bun test"
20
- },
21
- "keywords": [
22
- "1sat",
23
- "bsv",
24
- "ordinals",
25
- "cli"
26
- ],
27
- "license": "MIT",
28
- "dependencies": {
29
- "@1sat/actions": "0.0.187",
30
- "@1sat/client": "0.0.43",
31
- "@1sat/types": "0.0.34",
32
- "@1sat/wallet-node": "0.0.60",
33
- "@1sat/wallet-server": "0.0.29",
34
- "@bsv/sdk": "^2.1.6",
35
- "@bsv/wallet-toolbox": "2.1.24",
36
- "chalk": "^5.0.0",
37
- "@clack/prompts": "^0.8.0",
38
- "bitcoin-backup": "^0.0.11",
39
- "dotenv": "^17.0.0",
40
- "evlog": "^2.10.0",
41
- "knex": "^3.1.0",
42
- "@bopen-io/messagebox-server": "^1.1.5",
43
- "pg": "^8.11.3"
44
- },
45
- "devDependencies": {
46
- "@types/bun": "^1.3.9",
47
- "typescript": "^5.9.3"
48
- }
2
+ "name": "@1sat/cli",
3
+ "version": "0.0.85",
4
+ "description": "CLI for 1Sat Ordinals SDK",
5
+ "type": "module",
6
+ "main": "./src/cli.ts",
7
+ "bin": {
8
+ "1sat": "./src/cli.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "scripts"
13
+ ],
14
+ "scripts": {
15
+ "preinstall": "node scripts/check-bun.cjs",
16
+ "build": "bun build ./src/cli.ts --compile --outfile=bin/1sat --external pg --external pg-native --external pg-query-stream --external tedious --external oracledb --external mysql --external mariadb --external 'mariadb/*'",
17
+ "dev": "bun run src/cli.ts",
18
+ "lint": "biome check src",
19
+ "test": "bun test"
20
+ },
21
+ "keywords": [
22
+ "1sat",
23
+ "bsv",
24
+ "ordinals",
25
+ "cli"
26
+ ],
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@1sat/actions": "0.0.189",
30
+ "@1sat/client": "0.0.45",
31
+ "@1sat/types": "0.0.36",
32
+ "@1sat/wallet-node": "0.0.62",
33
+ "@1sat/wallet-server": "0.0.31",
34
+ "@bsv/sdk": "^2.1.6",
35
+ "@bsv/wallet-toolbox": "2.1.24",
36
+ "chalk": "^5.0.0",
37
+ "@clack/prompts": "^0.8.0",
38
+ "bitcoin-backup": "^0.0.11",
39
+ "dotenv": "^17.0.0",
40
+ "evlog": "^2.10.0",
41
+ "knex": "^3.1.0",
42
+ "@bopen-io/messagebox-server": "^1.3.2",
43
+ "pg": "^8.11.3",
44
+ "sqlite3": "^6.0.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/bun": "^1.3.9",
48
+ "typescript": "^5.9.3"
49
+ }
49
50
  }
@@ -1,10 +1,9 @@
1
1
  /**
2
- * `1sat serve` command — launch wallet server and/or monitor.
2
+ * `1sat serve` command — launch the unified host server and/or monitor.
3
3
  *
4
- * 1sat serve Wallet server + monitor daemon
5
- * 1sat serve wallet Wallet server only
4
+ * 1sat serve Host server (storage + hosting + paymail + messagebox) + monitor
5
+ * 1sat serve wallet Wallet storage server only
6
6
  * 1sat serve monitor Monitor daemon only
7
- * 1sat serve messagebox BSV message-box server
8
7
  *
9
8
  * The server wraps the same wallet instance the CLI uses. Storage, active
10
9
  * remote, and backups all come from `~/.1sat/cli/config.json` via the same
@@ -14,7 +13,9 @@
14
13
  * 1sat config set server.port 8100
15
14
  * 1sat config set server.host 0.0.0.0
16
15
  * 1sat config set server.accounts.enabled true
17
- * 1sat config set server.messagebox.port 8771
16
+ * 1sat config set server.hosting.enabled true
17
+ * 1sat config set server.paymail.baseUrl https://1sat.app
18
+ * 1sat config set server.monitor.enabled false
18
19
  */
19
20
 
20
21
  import { join } from 'node:path'
@@ -23,12 +24,18 @@ import {
23
24
  type NodeWalletStorageConfig,
24
25
  createNodeWallet,
25
26
  } from '@1sat/wallet-node'
26
- import { createWalletServer } from '@1sat/wallet-server'
27
+ import {
28
+ KnexPendingStore,
29
+ createHostServer,
30
+ createWalletServer,
31
+ } from '@1sat/wallet-server'
27
32
  import type { PrivateKey } from '@bsv/sdk'
28
33
  import { initLogger } from 'evlog'
34
+ import knexLib from 'knex'
29
35
  import type { GlobalFlags } from '../args'
30
36
  import {
31
37
  type ServerAccountsConfig,
38
+ type ServerMessageboxConfig,
32
39
  type ServerStorageConfig,
33
40
  loadConfig,
34
41
  setConfigPath,
@@ -43,7 +50,6 @@ import {
43
50
  createAccountsConfigLoader,
44
51
  resolveRateProvider,
45
52
  } from '../repricer'
46
- import { startMessagebox } from './serve-messagebox'
47
53
 
48
54
  const DEFAULT_HOST = '127.0.0.1'
49
55
  const DEFAULT_PORT = 8100
@@ -54,7 +60,7 @@ const DEFAULT_SATS_PER_UNIT = 1_000_000
54
60
  const DEFAULT_DURATION_BLOCKS = 4383
55
61
  const DEFAULT_STORAGE_IDENTITY_KEY = '1sat-cli-default'
56
62
 
57
- type ServeMode = 'all' | 'wallet' | 'monitor' | 'messagebox'
63
+ type ServeMode = 'all' | 'wallet' | 'monitor'
58
64
 
59
65
  interface ResolvedServe {
60
66
  chain: 'main' | 'test'
@@ -68,6 +74,7 @@ interface ResolvedServe {
68
74
  activeRemote?: string
69
75
  backups?: string[]
70
76
  accounts: ResolvedAccounts
77
+ monitorEnabled: boolean
71
78
  privateKey: PrivateKey
72
79
  }
73
80
 
@@ -103,20 +110,6 @@ export async function handleServeCommand(
103
110
 
104
111
  initLogger({ env: { service: `1sat-cli-serve-${mode}` } })
105
112
 
106
- if (mode === 'messagebox') {
107
- const handle = await startMessagebox(opts)
108
- try {
109
- await waitForShutdown()
110
- } finally {
111
- try {
112
- await handle.stop()
113
- } catch (err) {
114
- console.error(`Error during shutdown: ${(err as Error).message}`)
115
- }
116
- }
117
- return
118
- }
119
-
120
113
  const resolved = await resolveServe(opts)
121
114
  const handles: Stoppable[] = []
122
115
 
@@ -139,7 +132,6 @@ function resolveMode(subcommand: string | undefined): ServeMode | null {
139
132
  switch (subcommand) {
140
133
  case 'wallet':
141
134
  case 'monitor':
142
- case 'messagebox':
143
135
  return subcommand
144
136
  default:
145
137
  return null
@@ -187,6 +179,7 @@ async function resolveServe(opts: GlobalFlags): Promise<ResolvedServe> {
187
179
  activeRemote: config.activeRemote,
188
180
  backups: config.backups,
189
181
  accounts: resolveAccounts(server.accounts),
182
+ monitorEnabled: server.monitor?.enabled !== false,
190
183
  privateKey,
191
184
  }
192
185
  }
@@ -258,12 +251,17 @@ async function runWithStorage(
258
251
  storage,
259
252
  activeRemote: resolved.activeRemote,
260
253
  backups: resolved.backups,
261
- // Every serve mode manages monitor work explicitly: wallet mode
262
- // does none, monitor/all modes run startTasks. The factory's
263
- // initial runOnce is redundant in all three cases.
254
+ // Each serve mode manages monitor work explicitly (see runMonitor
255
+ // below); the factory's initial runOnce is redundant in every case.
264
256
  skipInitialMonitor: true,
265
257
  })
266
258
 
259
+ // One active monitor per deployment: wallet mode never runs it, monitor
260
+ // mode always does, all mode runs it unless server.monitor.enabled=false
261
+ // (set on redundant host instances that share a dedicated serve monitor).
262
+ const runMonitor =
263
+ mode === 'monitor' || (mode === 'all' && resolved.monitorEnabled)
264
+
267
265
  const accounts =
268
266
  mode === 'monitor'
269
267
  ? undefined
@@ -272,9 +270,9 @@ async function runWithStorage(
272
270
  const serverHandle =
273
271
  mode === 'monitor'
274
272
  ? undefined
275
- : await startWalletServer(resolved, walletResult, accounts)
273
+ : await startWalletServer(resolved, walletResult, accounts, mode)
276
274
 
277
- if (mode !== 'wallet') {
275
+ if (runMonitor) {
278
276
  const accountsCfg = resolved.accounts
279
277
  const r = accountsCfg.repricer
280
278
  if (
@@ -310,7 +308,7 @@ async function runWithStorage(
310
308
  }
311
309
  }
312
310
 
313
- if (mode !== 'wallet') {
311
+ if (runMonitor) {
314
312
  // startTasks loops until stopTasks flips its flag. Fire without
315
313
  // awaiting so the caller can install shutdown handlers and write
316
314
  // the monitor pid file.
@@ -323,7 +321,7 @@ async function runWithStorage(
323
321
 
324
322
  return {
325
323
  async stop() {
326
- if (mode !== 'wallet') {
324
+ if (runMonitor) {
327
325
  walletResult.monitor.stopTasks()
328
326
  clearMonitorPid(resolved.dataDir, process.pid)
329
327
  }
@@ -339,20 +337,133 @@ async function startWalletServer(
339
337
  resolved: ResolvedServe,
340
338
  walletResult: NodeWalletResult,
341
339
  accounts: AccountsRuntime | undefined,
340
+ mode: ServeMode,
342
341
  ): Promise<{ stop(): Promise<void> }> {
343
- const handle = createWalletServer({
342
+ const config = loadConfig()
343
+ const hostingCfg = config.server?.hosting
344
+ const hostingEnabled = hostingCfg?.enabled === true
345
+ const hosting = hostingEnabled
346
+ ? {
347
+ getConfig: () => {
348
+ const h = loadConfig().server?.hosting
349
+ return {
350
+ enabled: h?.enabled === true,
351
+ priceSats: h?.priceSats ?? 10_000,
352
+ periodSeconds: h?.periodSeconds ?? 2_592_000,
353
+ }
354
+ },
355
+ }
356
+ : undefined
357
+
358
+ if (mode === 'wallet') {
359
+ const handle = createWalletServer({
360
+ wallet: walletResult.wallet,
361
+ storage: walletResult.getActiveStorage(),
362
+ serverIdentityKey: walletResult.wallet.identityKey,
363
+ listen: { port: resolved.port, host: resolved.host },
364
+ publicPath: '/',
365
+ internalPath: null,
366
+ accounts: accounts?.walletServerAccounts,
367
+ hosting,
368
+ })
369
+ const port = await handle.start()
370
+ const notes = [
371
+ resolved.accounts.enabled ? 'accounts: on' : '',
372
+ hostingEnabled ? 'hosting: on' : '',
373
+ ]
374
+ .filter(Boolean)
375
+ .join(', ')
376
+ console.log(
377
+ `[wallet] listening on ${resolved.host}:${port}${notes ? ` (${notes})` : ''}`,
378
+ )
379
+ return { stop: () => handle.stop() }
380
+ }
381
+
382
+ // Unified host: storage + messagebox + paymail + hosting
383
+ const serverCfg = config.server ?? {}
384
+ const paymailCfg = serverCfg.paymail
385
+ const messageboxCfg = serverCfg.messagebox
386
+
387
+ const knex = createMessageboxKnex(
388
+ serverCfg.storage ?? { provider: 'bun-sqlite' },
389
+ messageboxCfg,
390
+ resolved,
391
+ )
392
+ const pendingStore = new KnexPendingStore(knex)
393
+ await pendingStore.init()
394
+
395
+ const handle = await createHostServer({
344
396
  wallet: walletResult.wallet,
345
397
  storage: walletResult.getActiveStorage(),
346
398
  serverIdentityKey: walletResult.wallet.identityKey,
347
399
  listen: { port: resolved.port, host: resolved.host },
348
- publicPath: '/',
349
- internalPath: null,
350
400
  accounts: accounts?.walletServerAccounts,
401
+ hosting,
402
+ paymail: {
403
+ baseUrl: paymailCfg?.baseUrl ?? `http://${resolved.host}:${resolved.port}`,
404
+ stackUrl: paymailCfg?.stackUrl ?? 'https://api.1sat.app',
405
+ pendingStore,
406
+ hostWallet: hostingEnabled ? walletResult.wallet : undefined,
407
+ requireEntitlement: hostingEnabled,
408
+ messageboxUrl: `http://127.0.0.1:${resolved.port}/`,
409
+ hostPrivateKey: resolved.privateKey,
410
+ },
411
+ messagebox: {
412
+ knex,
413
+ websockets: messageboxCfg?.websockets !== false,
414
+ },
351
415
  })
352
416
  const port = await handle.start()
353
- const accountsNote = resolved.accounts.enabled ? ' (accounts: on)' : ''
354
- console.log(`[wallet] listening on ${resolved.host}:${port}${accountsNote}`)
355
- return { stop: () => handle.stop() }
417
+ const notes = [
418
+ resolved.accounts.enabled ? 'accounts: on' : '',
419
+ hostingEnabled ? 'hosting: on' : '',
420
+ ]
421
+ .filter(Boolean)
422
+ .join(', ')
423
+ console.log(
424
+ `[host] listening on ${resolved.host}:${port}${notes ? ` (${notes})` : ''}`,
425
+ )
426
+ return {
427
+ stop: async () => {
428
+ await handle.stop()
429
+ if (knex) await knex.destroy()
430
+ },
431
+ }
432
+ }
433
+
434
+ const DEFAULT_PG_SCHEMA = 'messagebox'
435
+
436
+ function createMessageboxKnex(
437
+ storage: ServerStorageConfig,
438
+ messagebox: ServerMessageboxConfig | undefined,
439
+ resolved: ResolvedServe,
440
+ ) {
441
+ if (storage.provider === 'bun-sqlite') {
442
+ const filename =
443
+ messagebox?.dbPath ?? join(resolved.dataDir, `messagebox-${resolved.chain}.db`)
444
+ return (knexLib as any)({
445
+ client: 'sqlite3',
446
+ connection: { filename },
447
+ useNullAsDefault: true,
448
+ })
449
+ }
450
+ if (storage.provider === 'pg') {
451
+ if (messagebox?.dbUrl) {
452
+ return (knexLib as any)({
453
+ client: 'pg',
454
+ connection: { connectionString: messagebox.dbUrl },
455
+ })
456
+ }
457
+ const schema = messagebox?.pgSchema ?? DEFAULT_PG_SCHEMA
458
+ return (knexLib as any)({
459
+ client: 'pg',
460
+ connection: { connectionString: storage.dbUrl },
461
+ searchPath: [schema],
462
+ })
463
+ }
464
+ fatal(
465
+ `server.storage.provider '${(storage as { provider: string }).provider}' is not supported for messagebox storage.`,
466
+ )
356
467
  }
357
468
 
358
469
  interface AccountsRuntime {
package/src/config.ts CHANGED
@@ -85,6 +85,59 @@ export interface ServerMessageboxConfig {
85
85
  walletStorageUrl?: string
86
86
  }
87
87
 
88
+ export interface ServerPaymailConfig {
89
+ /** Port to bind. Defaults to `8466` or ONESAT_PAYMAIL_PORT. */
90
+ port?: number
91
+ /**
92
+ * Public base URL used in /.well-known/bsvalias capability links
93
+ * (e.g. https://1sat.app). Required for serve paymail.
94
+ */
95
+ baseUrl?: string
96
+ /** Stack API root (OpNS, beef, tx). Defaults to https://api.1sat.app */
97
+ stackUrl?: string
98
+ /**
99
+ * SQLite path for in-flight P2P payment references (Go paymail port).
100
+ * Defaults to `<dataDir>/paymail-pending-{chain}.db`.
101
+ */
102
+ pendingDbPath?: string
103
+ /**
104
+ * Messagebox URL for payment_inbox delivery after receive.
105
+ * Defaults to https://messagebox.1sat.app when unset is OK to skip —
106
+ * set explicitly to enable (same as Go messagebox_url).
107
+ */
108
+ messageboxUrl?: string
109
+ /**
110
+ * When true, open host wallet storage and require active HOSTING_BASKET
111
+ * receipt for the bound identity. Default false until subscribe path ships.
112
+ */
113
+ requireEntitlement?: boolean
114
+ /**
115
+ * Wallet storage URL for entitlement listOutputs (and optional later
116
+ * subscribe). Defaults to local wallet serve URL like messagebox.
117
+ */
118
+ walletStorageUrl?: string
119
+ }
120
+
121
+ export interface ServerHostingConfig {
122
+ /** Master toggle. Defaults to false. */
123
+ enabled?: boolean
124
+ /** Sats per subscription period. */
125
+ priceSats?: number
126
+ /** Period length in seconds (e.g. 2592000 ≈ 30 days). */
127
+ periodSeconds?: number
128
+ }
129
+
130
+ export interface ServerMonitorConfig {
131
+ /**
132
+ * Run the background monitor task loop (post waiting txs, check for
133
+ * proofs, account repricing). Defaults to true. Set false on redundant
134
+ * host instances that run alongside a dedicated `serve monitor`, so a
135
+ * single active monitor runs per deployment. `serve monitor` always runs
136
+ * the loop regardless of this flag.
137
+ */
138
+ enabled?: boolean
139
+ }
140
+
88
141
  export interface ServerConfig {
89
142
  /** Hostname to bind. Defaults to `127.0.0.1`. */
90
143
  host?: string
@@ -96,6 +149,12 @@ export interface ServerConfig {
96
149
  accounts?: ServerAccountsConfig
97
150
  /** Messagebox subcommand settings. */
98
151
  messagebox?: ServerMessageboxConfig
152
+ /** Paymail subcommand settings. */
153
+ paymail?: ServerPaymailConfig
154
+ /** Hosted paymail subscription (wallet serve /hosting/*). */
155
+ hosting?: ServerHostingConfig
156
+ /** Background monitor task loop. Defaults to enabled. */
157
+ monitor?: ServerMonitorConfig
99
158
  }
100
159
 
101
160
  export interface OneSatCliConfig {
package/src/help.ts CHANGED
@@ -560,19 +560,14 @@ export const COMMANDS: CommandSpec[] = [
560
560
  group: 'Server',
561
561
  name: 'serve',
562
562
  description:
563
- 'Run wallet HTTP server and/or monitor daemon (config under server.* in config.json)',
563
+ 'Run unified host server (storage + hosting + paymail + messagebox) and/or monitor (config under server.* in config.json)',
564
564
  subcommands: [
565
565
  {
566
566
  name: '(no subcommand)',
567
- description: 'Wallet server + monitor daemon',
567
+ description: 'Host server + monitor daemon',
568
568
  },
569
- { name: 'wallet', description: 'Wallet server only (BRC-100 HTTP)' },
569
+ { name: 'wallet', description: 'Wallet storage server only (BRC-100 HTTP)' },
570
570
  { name: 'monitor', description: 'Monitor daemon only' },
571
- {
572
- name: 'messagebox',
573
- description:
574
- 'BSV message-box server (port 8771 default; uses wallet identity)',
575
- },
576
571
  ],
577
572
  },
578
573
  {
@@ -1,221 +0,0 @@
1
- /**
2
- * `1sat serve messagebox` — launch the BSV message-box server using the
3
- * CLI's wallet identity.
4
- *
5
- * messagebox-server boots itself as a side effect of `import` once env vars
6
- * are populated, so this handler:
7
- * 1. Reads CLI config + private key
8
- * 2. Composes env (SERVER_PRIVATE_KEY, PORT, KNEX_DB_*, WALLET_STORAGE_URL, …)
9
- * 3. Switches cwd to the messagebox-server install dir so its knexfile's
10
- * relative `./out/src/migrations` path resolves correctly
11
- * 4. Dynamically imports `@bopen-io/messagebox-server`
12
- * 5. Awaits SIGINT/SIGTERM, then closes the HTTP and WebSocket servers
13
- */
14
-
15
- import { dirname, join } from 'node:path'
16
- import { fileURLToPath } from 'node:url'
17
- import type { PrivateKey } from '@bsv/sdk'
18
- import type { GlobalFlags } from '../args'
19
- import {
20
- type ServerMessageboxConfig,
21
- type ServerStorageConfig,
22
- ensureDataDir,
23
- loadConfig,
24
- } from '../config'
25
- import { loadKey } from '../keys'
26
- import { fatal } from '../output'
27
-
28
- const DEFAULT_MESSAGEBOX_PORT = 8771
29
- const DEFAULT_WALLET_HOST = '127.0.0.1'
30
- const DEFAULT_WALLET_PORT = 8100
31
- const DEFAULT_PG_SCHEMA = 'messagebox'
32
-
33
- interface ResolvedMessagebox {
34
- chain: 'main' | 'test'
35
- port: number
36
- websockets: boolean
37
- walletStorageUrl: string
38
- knexClient: string
39
- knexConnection: Record<string, unknown>
40
- identityHex: string
41
- identityPub: string
42
- }
43
-
44
- interface MessageboxModule {
45
- http: { close(cb?: (err?: Error) => void): void }
46
- io: { close(cb?: () => void): void } | null
47
- }
48
-
49
- export interface MessageboxStoppable {
50
- stop(): Promise<void>
51
- }
52
-
53
- export async function startMessagebox(
54
- opts: GlobalFlags,
55
- ): Promise<MessageboxStoppable> {
56
- const resolved = await resolveMessagebox(opts)
57
- applyEnv(resolved)
58
-
59
- console.log(`[messagebox] starting on 0.0.0.0:${resolved.port}`)
60
- console.log(`[messagebox] identity: ${resolved.identityPub.slice(0, 16)}…`)
61
- console.log(`[messagebox] storage: ${resolved.knexClient}`)
62
- console.log(`[messagebox] wallet: ${resolved.walletStorageUrl}`)
63
- console.log(
64
- `[messagebox] websockets: ${resolved.websockets ? 'enabled' : 'disabled'}`,
65
- )
66
-
67
- // messagebox-server's knexfile uses `./out/src/migrations` relative to
68
- // cwd. Switch cwd to its install dir so that path resolves correctly
69
- // regardless of where the CLI was invoked from.
70
- const packageJsonUrl = import.meta.resolve(
71
- '@bopen-io/messagebox-server/package.json',
72
- )
73
- const messageboxDir = dirname(fileURLToPath(packageJsonUrl))
74
- process.chdir(messageboxDir)
75
-
76
- // messagebox-server boots itself when its index module is loaded.
77
- const mbox = (await import(
78
- '@bopen-io/messagebox-server'
79
- )) as unknown as MessageboxModule
80
-
81
- return {
82
- async stop() {
83
- await new Promise<void>((resolve) => {
84
- try {
85
- mbox.http.close(() => resolve())
86
- } catch {
87
- resolve()
88
- }
89
- })
90
- if (mbox.io) {
91
- await new Promise<void>((resolve) => {
92
- try {
93
- mbox.io?.close(() => resolve())
94
- } catch {
95
- resolve()
96
- }
97
- })
98
- }
99
- },
100
- }
101
- }
102
-
103
- async function resolveMessagebox(
104
- opts: GlobalFlags,
105
- ): Promise<ResolvedMessagebox> {
106
- const config = loadConfig()
107
- const server = config.server ?? {}
108
- const messagebox: ServerMessageboxConfig = server.messagebox ?? {}
109
-
110
- const chain = opts.chain ?? config.chain ?? 'main'
111
- const port = resolvePort(messagebox.port)
112
- const websockets = messagebox.websockets !== false
113
-
114
- let privateKey: PrivateKey
115
- try {
116
- privateKey = await loadKey()
117
- } catch (err) {
118
- fatal((err as Error).message)
119
- }
120
-
121
- const identityHex = privateKey.toString()
122
- const identityPub = privateKey.toPublicKey().toString()
123
-
124
- const walletHost = server.host ?? DEFAULT_WALLET_HOST
125
- const walletPort = server.port ?? DEFAULT_WALLET_PORT
126
- const walletStorageUrl =
127
- messagebox.walletStorageUrl ?? `http://${walletHost}:${walletPort}/`
128
-
129
- const { knexClient, knexConnection } = resolveStorage(
130
- server.storage ?? { provider: 'bun-sqlite' },
131
- messagebox,
132
- chain,
133
- )
134
-
135
- return {
136
- chain,
137
- port,
138
- websockets,
139
- walletStorageUrl,
140
- knexClient,
141
- knexConnection,
142
- identityHex,
143
- identityPub,
144
- }
145
- }
146
-
147
- function resolvePort(configured: number | undefined): number {
148
- const fromEnv = process.env.ONESAT_MESSAGEBOX_PORT
149
- if (fromEnv && fromEnv.trim() !== '') {
150
- const parsed = Number(fromEnv)
151
- if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
152
- fatal(
153
- `ONESAT_MESSAGEBOX_PORT must be a valid port number, got: ${fromEnv}`,
154
- )
155
- }
156
- return parsed
157
- }
158
- return configured ?? DEFAULT_MESSAGEBOX_PORT
159
- }
160
-
161
- function resolveStorage(
162
- storage: ServerStorageConfig,
163
- messagebox: ServerMessageboxConfig,
164
- chain: 'main' | 'test',
165
- ): { knexClient: string; knexConnection: Record<string, unknown> } {
166
- if (storage.provider === 'bun-sqlite') {
167
- const dataDir = ensureDataDir()
168
- const filename =
169
- messagebox.dbPath ?? join(dataDir, `messagebox-${chain}.db`)
170
- return {
171
- knexClient: 'sqlite3',
172
- knexConnection: { filename },
173
- }
174
- }
175
-
176
- if (storage.provider === 'pg') {
177
- // Prefer a dedicated messagebox database when configured (recommended:
178
- // avoids `knex_migrations` table collisions with wallet-toolbox).
179
- // Fall back to schema isolation against the wallet's database — note
180
- // the `messagebox` schema must exist in that database for table
181
- // creation to land there instead of `public`.
182
- if (messagebox.dbUrl) {
183
- return {
184
- knexClient: 'pg',
185
- knexConnection: { connectionString: messagebox.dbUrl },
186
- }
187
- }
188
- const schema = messagebox.pgSchema ?? DEFAULT_PG_SCHEMA
189
- return {
190
- knexClient: 'pg',
191
- knexConnection: {
192
- connectionString: storage.dbUrl,
193
- searchPath: [schema, 'public'],
194
- },
195
- }
196
- }
197
-
198
- fatal(
199
- `server.storage.provider '${(storage as { provider: string }).provider}' is not supported by 'serve messagebox'.`,
200
- )
201
- }
202
-
203
- function applyEnv(resolved: ResolvedMessagebox): void {
204
- // messagebox-server reads env at module-load time, so we set everything
205
- // before the dynamic import. dotenv.config() inside messagebox-server
206
- // does not overwrite values already set on process.env.
207
- process.env.SERVER_PRIVATE_KEY = resolved.identityHex
208
- process.env.BSV_NETWORK = resolved.chain === 'main' ? 'mainnet' : 'testnet'
209
- process.env.PORT = String(resolved.port)
210
- // messagebox-server's index.ts forces HTTP_PORT=3000 when NODE_ENV
211
- // is anything other than 'development', ignoring our PORT entirely.
212
- // Use 'development' so PORT is honored.
213
- process.env.NODE_ENV = 'development'
214
- process.env.ENABLE_WEBSOCKETS = resolved.websockets ? 'true' : 'false'
215
- process.env.WALLET_STORAGE_URL = resolved.walletStorageUrl
216
- process.env.KNEX_DB_CLIENT = resolved.knexClient
217
- process.env.KNEX_DB_CONNECTION = JSON.stringify(resolved.knexConnection)
218
- if (process.env.ENABLE_FIREBASE === undefined) {
219
- process.env.ENABLE_FIREBASE = 'false'
220
- }
221
- }