@1sat/cli 0.0.51 → 0.0.53

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.53",
4
4
  "description": "CLI for 1Sat Ordinals SDK",
5
5
  "type": "module",
6
6
  "main": "./src/cli.ts",
@@ -25,11 +25,12 @@
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/knex-bun-sqlite": "0.0.1",
31
+ "@1sat/types": "0.0.19",
32
+ "@1sat/wallet-node": "0.0.43",
33
+ "@1sat/wallet-server": "0.0.13",
33
34
  "@bsv/sdk": "^2.0.13",
34
35
  "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.2",
35
36
  "chalk": "^5.0.0",
@@ -38,6 +39,7 @@
38
39
  "dotenv": "^17.0.0",
39
40
  "evlog": "^2.10.0",
40
41
  "knex": "^3.1.0",
42
+ "@bopen-io/messagebox-server": "^1.1.5",
41
43
  "pg": "^8.11.3"
42
44
  },
43
45
  "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,233 @@
1
+ /**
2
+ * `1sat serve messagebox` — launch the BSV message-box server using the
3
+ * CLI's wallet identity.
4
+ *
5
+ * Storage mirrors the wallet's choice: SQLite via `@1sat/knex-bun-sqlite`
6
+ * when wallet storage is `bun-sqlite`, Postgres when wallet storage is `pg`.
7
+ *
8
+ * messagebox-server boots itself as a side effect of `import` once env vars
9
+ * are populated, so this handler:
10
+ * 1. Reads CLI config + private key
11
+ * 2. Composes env (SERVER_PRIVATE_KEY, PORT, KNEX_DB_*, WALLET_STORAGE_URL, …)
12
+ * 3. Dynamically imports `messagebox-server`
13
+ * 4. Awaits SIGINT/SIGTERM, then closes the HTTP and WebSocket servers
14
+ */
15
+
16
+ import { join } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+ import type { PrivateKey } from '@bsv/sdk'
19
+ import type { GlobalFlags } from '../args'
20
+ import {
21
+ type ServerMessageboxConfig,
22
+ type ServerStorageConfig,
23
+ ensureDataDir,
24
+ loadConfig,
25
+ } from '../config'
26
+ import { loadKey } from '../keys'
27
+ import { fatal } from '../output'
28
+
29
+ const DEFAULT_MESSAGEBOX_PORT = 8771
30
+ const DEFAULT_WALLET_HOST = '127.0.0.1'
31
+ const DEFAULT_WALLET_PORT = 8100
32
+ const DEFAULT_PG_SCHEMA = 'messagebox'
33
+
34
+ interface ResolvedMessagebox {
35
+ chain: 'main' | 'test'
36
+ port: number
37
+ websockets: boolean
38
+ walletStorageUrl: string
39
+ knexClient: string
40
+ knexConnection: Record<string, unknown>
41
+ identityHex: string
42
+ identityPub: string
43
+ }
44
+
45
+ interface MessageboxModule {
46
+ http: { close(cb?: (err?: Error) => void): void }
47
+ io: { close(cb?: () => void): void } | null
48
+ }
49
+
50
+ export interface MessageboxStoppable {
51
+ stop(): Promise<void>
52
+ }
53
+
54
+ export async function startMessagebox(
55
+ opts: GlobalFlags,
56
+ ): Promise<MessageboxStoppable> {
57
+ const resolved = await resolveMessagebox(opts)
58
+ applyEnv(resolved)
59
+
60
+ console.log(`[messagebox] starting on 0.0.0.0:${resolved.port}`)
61
+ console.log(`[messagebox] identity: ${resolved.identityPub.slice(0, 16)}…`)
62
+ console.log(`[messagebox] storage: ${resolved.knexClient}`)
63
+ console.log(`[messagebox] wallet: ${resolved.walletStorageUrl}`)
64
+ console.log(
65
+ `[messagebox] websockets: ${resolved.websockets ? 'enabled' : 'disabled'}`,
66
+ )
67
+
68
+ // When the env-driven knexfile resolves to 'sqlite3', swap in our
69
+ // bun:sqlite-backed Knex dialect class and rewrite the migrations
70
+ // directory to an absolute path. ESM singleton semantics mean
71
+ // messagebox-server's app.ts gets the same (mutated) module instance
72
+ // when it imports the knexfile next.
73
+ if (resolved.knexClient === 'sqlite3') {
74
+ const knexfileUrl = import.meta.resolve(
75
+ '@bopen-io/messagebox-server/out/knexfile.js',
76
+ )
77
+ const migrationsDir = fileURLToPath(new URL('src/migrations/', knexfileUrl))
78
+ const [knexfile, dialect] = await Promise.all([
79
+ import('@bopen-io/messagebox-server/out/knexfile.js') as Promise<{
80
+ default: Record<
81
+ string,
82
+ { client: unknown; migrations?: { directory: string } }
83
+ >
84
+ }>,
85
+ import('@1sat/knex-bun-sqlite'),
86
+ ])
87
+ const Client = dialect.default
88
+ for (const env of ['development', 'staging', 'production']) {
89
+ const cfg = knexfile.default[env]
90
+ if (!cfg) continue
91
+ cfg.client = Client
92
+ if (cfg.migrations) cfg.migrations.directory = migrationsDir
93
+ }
94
+ }
95
+
96
+ // messagebox-server boots itself when its index module is loaded.
97
+ const mbox = (await import(
98
+ '@bopen-io/messagebox-server'
99
+ )) as unknown as MessageboxModule
100
+
101
+ return {
102
+ async stop() {
103
+ await new Promise<void>((resolve) => {
104
+ try {
105
+ mbox.http.close(() => resolve())
106
+ } catch {
107
+ resolve()
108
+ }
109
+ })
110
+ if (mbox.io) {
111
+ await new Promise<void>((resolve) => {
112
+ try {
113
+ mbox.io?.close(() => resolve())
114
+ } catch {
115
+ resolve()
116
+ }
117
+ })
118
+ }
119
+ },
120
+ }
121
+ }
122
+
123
+ async function resolveMessagebox(
124
+ opts: GlobalFlags,
125
+ ): Promise<ResolvedMessagebox> {
126
+ const config = loadConfig()
127
+ const server = config.server ?? {}
128
+ const messagebox: ServerMessageboxConfig = server.messagebox ?? {}
129
+
130
+ const chain = opts.chain ?? config.chain ?? 'main'
131
+ const port = resolvePort(messagebox.port)
132
+ const websockets = messagebox.websockets !== false
133
+
134
+ let privateKey: PrivateKey
135
+ try {
136
+ privateKey = await loadKey()
137
+ } catch (err) {
138
+ fatal((err as Error).message)
139
+ }
140
+
141
+ const identityHex = privateKey.toString()
142
+ const identityPub = privateKey.toPublicKey().toString()
143
+
144
+ const walletHost = server.host ?? DEFAULT_WALLET_HOST
145
+ const walletPort = server.port ?? DEFAULT_WALLET_PORT
146
+ const walletStorageUrl =
147
+ messagebox.walletStorageUrl ?? `http://${walletHost}:${walletPort}/`
148
+
149
+ const { knexClient, knexConnection } = resolveStorage(
150
+ server.storage ?? { provider: 'bun-sqlite' },
151
+ messagebox,
152
+ chain,
153
+ )
154
+
155
+ return {
156
+ chain,
157
+ port,
158
+ websockets,
159
+ walletStorageUrl,
160
+ knexClient,
161
+ knexConnection,
162
+ identityHex,
163
+ identityPub,
164
+ }
165
+ }
166
+
167
+ function resolvePort(configured: number | undefined): number {
168
+ const fromEnv = process.env.ONESAT_MESSAGEBOX_PORT
169
+ if (fromEnv && fromEnv.trim() !== '') {
170
+ const parsed = Number(fromEnv)
171
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
172
+ fatal(
173
+ `ONESAT_MESSAGEBOX_PORT must be a valid port number, got: ${fromEnv}`,
174
+ )
175
+ }
176
+ return parsed
177
+ }
178
+ return configured ?? DEFAULT_MESSAGEBOX_PORT
179
+ }
180
+
181
+ function resolveStorage(
182
+ storage: ServerStorageConfig,
183
+ messagebox: ServerMessageboxConfig,
184
+ chain: 'main' | 'test',
185
+ ): { knexClient: string; knexConnection: Record<string, unknown> } {
186
+ if (storage.provider === 'bun-sqlite') {
187
+ const dataDir = ensureDataDir()
188
+ const filename =
189
+ messagebox.dbPath ?? join(dataDir, `messagebox-${chain}.db`)
190
+ // We pass `sqlite3` to satisfy the env-driven knexfile's alias check;
191
+ // the actual Client class is swapped in via ESM module mutation
192
+ // just before messagebox-server is imported.
193
+ return {
194
+ knexClient: 'sqlite3',
195
+ knexConnection: { filename },
196
+ }
197
+ }
198
+
199
+ if (storage.provider === 'pg') {
200
+ const schema = messagebox.pgSchema ?? DEFAULT_PG_SCHEMA
201
+ return {
202
+ knexClient: 'pg',
203
+ knexConnection: {
204
+ connectionString: storage.dbUrl,
205
+ searchPath: [schema, 'public'],
206
+ },
207
+ }
208
+ }
209
+
210
+ fatal(
211
+ `server.storage.provider '${(storage as { provider: string }).provider}' is not supported by 'serve messagebox'.`,
212
+ )
213
+ }
214
+
215
+ function applyEnv(resolved: ResolvedMessagebox): void {
216
+ // messagebox-server reads env at module-load time, so we set everything
217
+ // before the dynamic import. dotenv.config() inside messagebox-server
218
+ // does not overwrite values already set on process.env.
219
+ process.env.SERVER_PRIVATE_KEY = resolved.identityHex
220
+ process.env.BSV_NETWORK = resolved.chain === 'main' ? 'mainnet' : 'testnet'
221
+ process.env.PORT = String(resolved.port)
222
+ // messagebox-server's index.ts forces HTTP_PORT=3000 when NODE_ENV
223
+ // is anything other than 'development', ignoring our PORT entirely.
224
+ // Use 'development' so PORT is honored.
225
+ process.env.NODE_ENV = 'development'
226
+ process.env.ENABLE_WEBSOCKETS = resolved.websockets ? 'true' : 'false'
227
+ process.env.WALLET_STORAGE_URL = resolved.walletStorageUrl
228
+ process.env.KNEX_DB_CLIENT = resolved.knexClient
229
+ process.env.KNEX_DB_CONNECTION = JSON.stringify(resolved.knexConnection)
230
+ if (process.env.ENABLE_FIREBASE === undefined) {
231
+ process.env.ENABLE_FIREBASE = 'false'
232
+ }
233
+ }
@@ -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
  }