@1sat/cli 0.0.51 → 0.0.54

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/cli",
3
- "version": "0.0.51",
3
+ "version": "0.0.54",
4
4
  "description": "CLI for 1Sat Ordinals SDK",
5
5
  "type": "module",
6
6
  "main": "./src/cli.ts",
@@ -25,11 +25,11 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "@1sat/actions": "0.0.115",
29
- "@1sat/client": "0.0.26",
30
- "@1sat/types": "0.0.18",
31
- "@1sat/wallet-node": "0.0.42",
32
- "@1sat/wallet-server": "0.0.12",
28
+ "@1sat/actions": "0.0.117",
29
+ "@1sat/client": "0.0.27",
30
+ "@1sat/types": "0.0.19",
31
+ "@1sat/wallet-node": "0.0.43",
32
+ "@1sat/wallet-server": "0.0.13",
33
33
  "@bsv/sdk": "^2.0.13",
34
34
  "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.2",
35
35
  "chalk": "^5.0.0",
@@ -38,6 +38,7 @@
38
38
  "dotenv": "^17.0.0",
39
39
  "evlog": "^2.10.0",
40
40
  "knex": "^3.1.0",
41
+ "@bopen-io/messagebox-server": "^1.1.5",
41
42
  "pg": "^8.11.3"
42
43
  },
43
44
  "devDependencies": {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One-off recovery script: re-submit a BSV21 deploy tx to the discovery
3
+ * overlay topic (tm_bsv21).
4
+ *
5
+ * Use when a deploy was broadcast on-chain but didn't reach the overlay
6
+ * (e.g. previous URL bug, network blip, server downtime).
7
+ *
8
+ * Usage: bun run scripts/resubmit-bsv21-discovery.ts <txid>
9
+ */
10
+
11
+ import { OneSatServices } from '@1sat/client'
12
+
13
+ const txid = process.argv[2]
14
+ if (!txid || !/^[0-9a-f]{64}$/i.test(txid)) {
15
+ console.error(
16
+ 'Usage: bun run scripts/resubmit-bsv21-discovery.ts <txid>\n' +
17
+ ' txid must be a 64-char hex string',
18
+ )
19
+ process.exit(1)
20
+ }
21
+
22
+ const services = new OneSatServices('main')
23
+
24
+ console.log(`Fetching BEEF for ${txid}...`)
25
+ const beef = await services.getBeefForTxid(txid)
26
+ console.log(` beef has ${beef.txs.length} txs and ${beef.bumps.length} bumps`)
27
+
28
+ const atomicBeef = beef.toBinaryAtomic(txid)
29
+ console.log(` atomic BEEF size: ${atomicBeef.length} bytes`)
30
+
31
+ console.log('Submitting to tm_bsv21 discovery topic...')
32
+ const result = await services.overlay.submitBsv21Discovery(atomicBeef)
33
+ console.log('Result:', result)
package/src/cli.ts CHANGED
@@ -22,7 +22,7 @@ import { handleSweepCommand } from './commands/sweep'
22
22
  import { handleTokensCommand } from './commands/tokens'
23
23
  import { handleTxCommand } from './commands/tx'
24
24
  import { handleWalletCommand } from './commands/wallet'
25
- import { printHelp, printVersion } from './help'
25
+ import { getCommand, printCommandHelp, printHelp, printVersion } from './help'
26
26
  import { formatError } from './output'
27
27
 
28
28
  const rawArgs = process.argv.slice(2)
@@ -37,8 +37,17 @@ async function main(): Promise<void> {
37
37
 
38
38
  const [command, ...rest] = flags.rest
39
39
 
40
- if (!command || flags.help) {
41
- printHelp()
40
+ if (!command) {
41
+ printHelp(flags.json)
42
+ process.exit(0)
43
+ }
44
+
45
+ if (flags.help) {
46
+ if (getCommand(command)) {
47
+ printCommandHelp(command, flags.json)
48
+ } else {
49
+ printHelp(flags.json)
50
+ }
42
51
  process.exit(0)
43
52
  }
44
53
 
@@ -104,12 +113,12 @@ async function main(): Promise<void> {
104
113
  break
105
114
 
106
115
  case 'help':
107
- printHelp()
116
+ printHelp(flags.json)
108
117
  break
109
118
 
110
119
  default:
111
120
  console.error(formatError(`Unknown command: ${command}`))
112
- printHelp()
121
+ printHelp(flags.json)
113
122
  process.exit(1)
114
123
  }
115
124
  }
@@ -42,12 +42,7 @@ export async function handleConfigCommand(
42
42
  case 'path':
43
43
  return configPath(opts)
44
44
  default:
45
- printCommandHelp('config', {
46
- show: 'Display current configuration',
47
- set: 'Set a config value (e.g. 1sat config set chain test)',
48
- unset: 'Remove a configuration key (e.g. 1sat config unset chain)',
49
- path: 'Print config directory path',
50
- })
45
+ printCommandHelp('config', opts.json)
51
46
  if (subcommand && subcommand !== 'help') {
52
47
  process.exit(1)
53
48
  }
@@ -30,14 +30,7 @@ export async function handleIdentityCommand(
30
30
  case 'verify':
31
31
  return identityVerify(rest, opts)
32
32
  default:
33
- printCommandHelp('identity', {
34
- create: 'Create/publish a BAP identity',
35
- 'update-profile': 'Update BAP identity profile (--profile <json>)',
36
- info: 'Show BAP identity information',
37
- sign: 'Sign a message with identity key (--message <text> [--encoding <utf8|hex|base64>])',
38
- verify:
39
- 'Verify a signed message (--message <text> --sig <sig> --address <addr>)',
40
- })
33
+ printCommandHelp('identity', opts.json)
41
34
  if (subcommand && subcommand !== 'help') {
42
35
  process.exit(1)
43
36
  }
@@ -25,11 +25,7 @@ export async function handleLocksCommand(
25
25
  case 'unlock':
26
26
  return locksUnlock(rest, opts)
27
27
  default:
28
- printCommandHelp('locks', {
29
- info: 'Show lock information and maturity status',
30
- lock: 'Time-lock BSV (--sats <amount> --blocks <n>)',
31
- unlock: 'Unlock matured BSV locks',
32
- })
28
+ printCommandHelp('locks', opts.json)
33
29
  if (subcommand && subcommand !== 'help') {
34
30
  process.exit(1)
35
31
  }
@@ -32,11 +32,7 @@ export async function handleOpnsCommand(
32
32
  case 'lookup':
33
33
  return opnsLookup(rest, opts)
34
34
  default:
35
- printCommandHelp('opns', {
36
- register: 'Register identity on an OpNS name (--outpoint <op>)',
37
- deregister: 'Deregister identity from an OpNS name (--outpoint <op>)',
38
- lookup: 'List OpNS names from wallet',
39
- })
35
+ printCommandHelp('opns', opts.json)
40
36
  if (subcommand && subcommand !== 'help') {
41
37
  process.exit(1)
42
38
  }
@@ -46,15 +46,7 @@ export async function handleOrdinalsCommand(
46
46
  case 'burn':
47
47
  return ordinalsBurn(rest, opts)
48
48
  default:
49
- printCommandHelp('ordinals', {
50
- list: 'List owned ordinals/inscriptions',
51
- mint: 'Mint a new ordinal inscription (--file <path> [--type <mime>] [--map <json>] [--sign-with-bap])',
52
- transfer: 'Transfer an ordinal (--outpoint <op> --to <addr>)',
53
- sell: 'List an ordinal for sale (--outpoint <op> --price <sats>)',
54
- cancel: 'Cancel an ordinal listing (--outpoint <op>)',
55
- buy: 'Purchase a listed ordinal (--outpoint <op>)',
56
- burn: 'Burn ordinals permanently (--outpoints <op1,op2,...>)',
57
- })
49
+ printCommandHelp('ordinals', opts.json)
58
50
  if (subcommand && subcommand !== 'help') {
59
51
  process.exit(1)
60
52
  }
@@ -38,17 +38,7 @@ export async function handleRemoteCommand(
38
38
  case 'topup':
39
39
  return remoteTopup(rest, opts)
40
40
  default:
41
- printCommandHelp('remote', {
42
- add: 'Add a remote storage as backup (1sat remote add <url>)',
43
- list: 'List all configured remotes and their status',
44
- delete:
45
- 'Remove a remote from the backup list (1sat remote delete <url>)',
46
- 'set-active':
47
- 'Switch active storage (1sat remote set-active <url | local>)',
48
- status:
49
- 'Fetch GET /account/status from a remote (1sat remote status [url])',
50
- topup: 'Buy capacity on a remote (1sat remote topup [url] [--units N])',
51
- })
41
+ printCommandHelp('remote', opts.json)
52
42
  if (subcommand && subcommand !== 'help') {
53
43
  process.exit(1)
54
44
  }
@@ -0,0 +1,213 @@
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
+ // We pass `sqlite3` to satisfy the env-driven knexfile's alias check;
171
+ // the actual Client class is swapped in via ESM module mutation
172
+ // just before messagebox-server is imported.
173
+ return {
174
+ knexClient: 'sqlite3',
175
+ knexConnection: { filename },
176
+ }
177
+ }
178
+
179
+ if (storage.provider === 'pg') {
180
+ const schema = messagebox.pgSchema ?? DEFAULT_PG_SCHEMA
181
+ return {
182
+ knexClient: 'pg',
183
+ knexConnection: {
184
+ connectionString: storage.dbUrl,
185
+ searchPath: [schema, 'public'],
186
+ },
187
+ }
188
+ }
189
+
190
+ fatal(
191
+ `server.storage.provider '${(storage as { provider: string }).provider}' is not supported by 'serve messagebox'.`,
192
+ )
193
+ }
194
+
195
+ function applyEnv(resolved: ResolvedMessagebox): void {
196
+ // messagebox-server reads env at module-load time, so we set everything
197
+ // before the dynamic import. dotenv.config() inside messagebox-server
198
+ // does not overwrite values already set on process.env.
199
+ process.env.SERVER_PRIVATE_KEY = resolved.identityHex
200
+ process.env.BSV_NETWORK = resolved.chain === 'main' ? 'mainnet' : 'testnet'
201
+ process.env.PORT = String(resolved.port)
202
+ // messagebox-server's index.ts forces HTTP_PORT=3000 when NODE_ENV
203
+ // is anything other than 'development', ignoring our PORT entirely.
204
+ // Use 'development' so PORT is honored.
205
+ process.env.NODE_ENV = 'development'
206
+ process.env.ENABLE_WEBSOCKETS = resolved.websockets ? 'true' : 'false'
207
+ process.env.WALLET_STORAGE_URL = resolved.walletStorageUrl
208
+ process.env.KNEX_DB_CLIENT = resolved.knexClient
209
+ process.env.KNEX_DB_CONNECTION = JSON.stringify(resolved.knexConnection)
210
+ if (process.env.ENABLE_FIREBASE === undefined) {
211
+ process.env.ENABLE_FIREBASE = 'false'
212
+ }
213
+ }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * `1sat serve` command — launch wallet server and/or monitor.
3
3
  *
4
- * 1sat serve Wallet server + monitor daemon
5
- * 1sat serve wallet Wallet server only
6
- * 1sat serve monitor Monitor daemon only
4
+ * 1sat serve Wallet server + monitor daemon
5
+ * 1sat serve wallet Wallet server only
6
+ * 1sat serve monitor Monitor daemon only
7
+ * 1sat serve messagebox BSV message-box server
7
8
  *
8
9
  * The server wraps the same wallet instance the CLI uses. Storage, active
9
10
  * remote, and backups all come from `~/.1sat/cli/config.json` via the same
@@ -13,6 +14,7 @@
13
14
  * 1sat config set server.port 8100
14
15
  * 1sat config set server.host 0.0.0.0
15
16
  * 1sat config set server.accounts.enabled true
17
+ * 1sat config set server.messagebox.port 8771
16
18
  */
17
19
 
18
20
  import { join } from 'node:path'
@@ -23,6 +25,7 @@ import {
23
25
  } from '@1sat/wallet-node'
24
26
  import { createWalletServer } from '@1sat/wallet-server'
25
27
  import type { PrivateKey } from '@bsv/sdk'
28
+ import { initLogger } from 'evlog'
26
29
  import type { GlobalFlags } from '../args'
27
30
  import {
28
31
  type ServerAccountsConfig,
@@ -34,6 +37,7 @@ import { printCommandHelp } from '../help'
34
37
  import { loadKey } from '../keys'
35
38
  import { clearMonitorPid, writeMonitorPid } from '../monitor-lock'
36
39
  import { fatal } from '../output'
40
+ import { startMessagebox } from './serve-messagebox'
37
41
 
38
42
  const DEFAULT_HOST = '127.0.0.1'
39
43
  const DEFAULT_PORT = 8100
@@ -44,7 +48,7 @@ const DEFAULT_SATS_PER_UNIT = 1_000_000
44
48
  const DEFAULT_DURATION_BLOCKS = 4383
45
49
  const DEFAULT_STORAGE_IDENTITY_KEY = '1sat-cli-default'
46
50
 
47
- type ServeMode = 'all' | 'wallet' | 'monitor'
51
+ type ServeMode = 'all' | 'wallet' | 'monitor' | 'messagebox'
48
52
 
49
53
  interface ResolvedServe {
50
54
  chain: 'main' | 'test'
@@ -78,15 +82,27 @@ export async function handleServeCommand(
78
82
  const mode = resolveMode(subcommand)
79
83
 
80
84
  if (mode === null) {
81
- printCommandHelp('serve', {
82
- '(no subcommand)': 'Wallet server plus monitor daemon',
83
- wallet: 'Wallet server only',
84
- monitor: 'Monitor daemon only',
85
- })
85
+ printCommandHelp('serve', opts.json)
86
86
  if (subcommand && subcommand !== 'help') process.exit(1)
87
87
  return
88
88
  }
89
89
 
90
+ initLogger({ env: { service: `1sat-cli-serve-${mode}` } })
91
+
92
+ if (mode === 'messagebox') {
93
+ const handle = await startMessagebox(opts)
94
+ try {
95
+ await waitForShutdown()
96
+ } finally {
97
+ try {
98
+ await handle.stop()
99
+ } catch (err) {
100
+ console.error(`Error during shutdown: ${(err as Error).message}`)
101
+ }
102
+ }
103
+ return
104
+ }
105
+
90
106
  const resolved = await resolveServe(opts)
91
107
  const handles: Stoppable[] = []
92
108
 
@@ -109,6 +125,7 @@ function resolveMode(subcommand: string | undefined): ServeMode | null {
109
125
  switch (subcommand) {
110
126
  case 'wallet':
111
127
  case 'monitor':
128
+ case 'messagebox':
112
129
  return subcommand
113
130
  default:
114
131
  return null
@@ -22,9 +22,7 @@ export async function handleSocialCommand(
22
22
  case 'post':
23
23
  return socialPost(rest, opts)
24
24
  default:
25
- printCommandHelp('social', {
26
- post: 'Create an on-chain social post (--content <text> [--app <name>] [--content-type <text/plain|text/markdown>] [--tags <t1,t2>])',
27
- })
25
+ printCommandHelp('social', opts.json)
28
26
  if (subcommand && subcommand !== 'help') {
29
27
  process.exit(1)
30
28
  }
@@ -32,10 +32,7 @@ export async function handleSweepCommand(
32
32
  case 'import':
33
33
  return sweepImport(rest, opts)
34
34
  default:
35
- printCommandHelp('sweep', {
36
- scan: 'Scan an address for UTXOs (--wif <key>)',
37
- import: 'Import UTXOs into wallet (--wif <key>)',
38
- })
35
+ printCommandHelp('sweep', opts.json)
39
36
  if (subcommand && subcommand !== 'help') {
40
37
  process.exit(1)
41
38
  }