@1sat/cli 0.0.43 → 0.0.46

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.43",
3
+ "version": "0.0.46",
4
4
  "description": "CLI for 1Sat Ordinals SDK",
5
5
  "type": "module",
6
6
  "main": "./src/cli.ts",
@@ -25,13 +25,13 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "@1sat/actions": "0.0.109",
29
- "@1sat/client": "0.0.24",
28
+ "@1sat/actions": "0.0.112",
29
+ "@1sat/client": "0.0.25",
30
30
  "@1sat/types": "0.0.18",
31
- "@1sat/wallet-node": "0.0.39",
32
- "@1sat/wallet-server": "0.0.9",
31
+ "@1sat/wallet-node": "0.0.40",
32
+ "@1sat/wallet-server": "0.0.10",
33
33
  "@bsv/sdk": "^2.0.13",
34
- "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.1",
34
+ "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.2",
35
35
  "chalk": "^5.0.0",
36
36
  "@clack/prompts": "^0.8.0",
37
37
  "bitcoin-backup": "^0.0.11",
@@ -8,7 +8,7 @@
8
8
  import { actionRegistry } from '@1sat/actions'
9
9
  import type { GlobalFlags } from '../args'
10
10
  import { loadContext } from '../context'
11
- import { loadKey, resolvePassword } from '../keys'
11
+ import { loadKey } from '../keys'
12
12
  import { fatal, output } from '../output'
13
13
 
14
14
  export async function handleActionCommand(
@@ -65,7 +65,7 @@ export async function handleActionCommand(
65
65
  }
66
66
  }
67
67
 
68
- const privateKey = await loadKey(resolvePassword())
68
+ const privateKey = await loadKey()
69
69
  const { ctx, destroy } = await loadContext(privateKey, {
70
70
  chain: opts.chain,
71
71
  })
@@ -9,7 +9,7 @@ import type { GlobalFlags } from '../args'
9
9
  import { extractFlag } from '../args'
10
10
  import { loadContext } from '../context'
11
11
  import { printCommandHelp } from '../help'
12
- import { loadKey, resolvePassword } from '../keys'
12
+ import { loadKey } from '../keys'
13
13
  import { fatal, output, printKeyValue } from '../output'
14
14
 
15
15
  export async function handleIdentityCommand(
@@ -34,7 +34,7 @@ export async function handleIdentityCommand(
34
34
  create: 'Create/publish a BAP identity',
35
35
  'update-profile': 'Update BAP identity profile (--profile <json>)',
36
36
  info: 'Show BAP identity information',
37
- sign: 'Sign a message with identity key (--message <text>)',
37
+ sign: 'Sign a message with identity key (--message <text> [--encoding <utf8|hex|base64>])',
38
38
  verify:
39
39
  'Verify a signed message (--message <text> --sig <sig> --address <addr>)',
40
40
  })
@@ -48,7 +48,7 @@ async function identityCreate(
48
48
  _args: string[],
49
49
  opts: GlobalFlags,
50
50
  ): Promise<void> {
51
- const privateKey = await loadKey(resolvePassword())
51
+ const privateKey = await loadKey()
52
52
  const { ctx, destroy } = await loadContext(privateKey, {
53
53
  chain: opts.chain,
54
54
  })
@@ -67,7 +67,7 @@ async function identityCreate(
67
67
  }
68
68
 
69
69
  async function identityInfo(_args: string[], opts: GlobalFlags): Promise<void> {
70
- const privateKey = await loadKey(resolvePassword())
70
+ const privateKey = await loadKey()
71
71
  const { ctx, destroy } = await loadContext(privateKey, {
72
72
  chain: opts.chain,
73
73
  })
@@ -92,16 +92,28 @@ async function identityInfo(_args: string[], opts: GlobalFlags): Promise<void> {
92
92
 
93
93
  async function identitySign(args: string[], opts: GlobalFlags): Promise<void> {
94
94
  const message = extractFlag(args, '--message')
95
+ const encoding = extractFlag(args, '--encoding')
95
96
 
96
97
  if (!message) fatal('Missing --message <text>')
98
+ if (
99
+ encoding !== undefined &&
100
+ encoding !== 'utf8' &&
101
+ encoding !== 'hex' &&
102
+ encoding !== 'base64'
103
+ ) {
104
+ fatal('--encoding must be one of: utf8, hex, base64')
105
+ }
97
106
 
98
- const privateKey = await loadKey(resolvePassword())
107
+ const privateKey = await loadKey()
99
108
  const { ctx, destroy } = await loadContext(privateKey, {
100
109
  chain: opts.chain,
101
110
  })
102
111
 
103
112
  try {
104
- const result = await signBsm.execute(ctx, { message })
113
+ const result = await signBsm.execute(ctx, {
114
+ message,
115
+ ...(encoding ? { encoding: encoding as 'utf8' | 'hex' | 'base64' } : {}),
116
+ })
105
117
 
106
118
  if (result.error) {
107
119
  fatal(result.error)
@@ -147,7 +159,7 @@ async function identityUpdateProfile(
147
159
  fatal(`Invalid JSON in --profile: ${profileStr}`)
148
160
  }
149
161
 
150
- const privateKey = await loadKey(resolvePassword())
162
+ const privateKey = await loadKey()
151
163
  const { ctx, destroy } = await loadContext(privateKey, {
152
164
  chain: opts.chain,
153
165
  })
@@ -18,11 +18,10 @@ import {
18
18
  outro,
19
19
  password,
20
20
  select,
21
- text,
22
21
  } from '@clack/prompts'
23
22
  import type { GlobalFlags } from '../args'
24
23
  import { ensureConfigDir, loadConfig, saveConfig } from '../config'
25
- import { cacheKeyPassword, hasKey, saveKey } from '../keys'
24
+ import { hasKey, saveKey } from '../keys'
26
25
  import { fatal, formatSuccess, formatValue, formatWarning } from '../output'
27
26
 
28
27
  export async function handleInitCommand(
@@ -144,82 +143,30 @@ export async function handleInitCommand(
144
143
  fatal('Passwords do not match.')
145
144
  }
146
145
 
147
- // 3.5. Touch ID protection (macOS arm64)
148
- let useTouchID = false
149
- try {
150
- const { isTouchIDAvailable } = await import('bitcoin-backup')
151
- if (isTouchIDAvailable()) {
152
- const enableTouchID = await confirm({
153
- message:
154
- 'Enable Touch ID? (unlock your wallet without typing a password)',
155
- })
156
- if (isCancel(enableTouchID)) {
157
- cancel('Setup cancelled.')
158
- process.exit(0)
159
- }
160
- useTouchID = enableTouchID as boolean
161
- }
162
- } catch {
163
- // bitcoin-backup Touch ID not available — skip silently
164
- }
165
-
166
146
  // 4. Generate random storage identity key
167
147
  const storageId = `1sat-cli-${randomBytes(8).toString('hex')}`
168
148
 
169
- // 5. Optional: remote storage configuration
170
- const useRemote = await confirm({
171
- message: 'Configure remote storage? (remote is active, local is backup)',
172
- defaultValue: false,
173
- })
174
- let activeRemote: string | undefined
175
-
176
- if (useRemote) {
177
- const url = await text({
178
- message: 'Primary remote storage URL:',
179
- validate(value) {
180
- if (!value) return 'Required'
181
- try {
182
- new URL(value)
183
- } catch {
184
- return 'Invalid URL'
185
- }
186
- },
187
- })
188
- if (isCancel(url)) {
189
- cancel('Setup cancelled.')
190
- process.exit(0)
191
- }
192
- activeRemote = url as string
193
- }
194
-
195
- // 6. Save everything
149
+ // 5. Save everything
196
150
  ensureConfigDir()
197
151
 
198
152
  await saveKey(wif, pw as string)
199
153
 
200
- // Cache password with Touch ID if user opted in
201
- if (useTouchID) {
202
- try {
203
- await cacheKeyPassword(pw as string)
204
- } catch {
205
- console.log(
206
- formatWarning(
207
- ' Touch ID caching failed. You can enable it later with "1sat touchid enable".',
208
- ),
209
- )
210
- }
211
- }
212
-
213
154
  saveConfig({
214
155
  ...loadConfig(),
215
156
  chain: chain as 'main' | 'test',
216
157
  storageIdentityKey: storageId,
217
- activeRemote,
218
158
  })
219
159
 
220
160
  const pk = PrivateKey.fromWif(wif)
221
161
  const address = pk.toPublicKey().toAddress()
222
162
 
223
- const touchIdNote = useTouchID ? ' (Touch ID enabled)' : ''
224
- outro(formatSuccess(`Wallet configured${touchIdNote}! Address: ${address}`))
163
+ outro(formatSuccess(`Wallet configured! Address: ${address}`))
164
+
165
+ console.log()
166
+ console.log(
167
+ ` Want hosted storage? Run ${formatValue('1sat remote add <url>')} to attach a backup remote,`,
168
+ )
169
+ console.log(
170
+ ` then ${formatValue('1sat remote set-active <url>')} to make it primary.`,
171
+ )
225
172
  }
@@ -8,7 +8,7 @@ import type { GlobalFlags } from '../args'
8
8
  import { extractFlag } from '../args'
9
9
  import { loadContext } from '../context'
10
10
  import { printCommandHelp } from '../help'
11
- import { loadKey, resolvePassword } from '../keys'
11
+ import { loadKey } from '../keys'
12
12
  import { fatal, output, printKeyValue } from '../output'
13
13
 
14
14
  export async function handleLocksCommand(
@@ -37,7 +37,7 @@ export async function handleLocksCommand(
37
37
  }
38
38
 
39
39
  async function locksInfo(_args: string[], opts: GlobalFlags): Promise<void> {
40
- const privateKey = await loadKey(resolvePassword())
40
+ const privateKey = await loadKey()
41
41
  const { ctx, destroy } = await loadContext(privateKey, {
42
42
  chain: opts.chain,
43
43
  })
@@ -86,7 +86,7 @@ async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
86
86
  }
87
87
  }
88
88
 
89
- const privateKey = await loadKey(resolvePassword())
89
+ const privateKey = await loadKey()
90
90
  const { ctx, destroy } = await loadContext(privateKey, {
91
91
  chain: opts.chain,
92
92
  })
@@ -107,7 +107,7 @@ async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
107
107
  }
108
108
 
109
109
  async function locksUnlock(_args: string[], opts: GlobalFlags): Promise<void> {
110
- const privateKey = await loadKey(resolvePassword())
110
+ const privateKey = await loadKey()
111
111
  const { ctx, destroy } = await loadContext(privateKey, {
112
112
  chain: opts.chain,
113
113
  })
@@ -15,7 +15,7 @@ import type { GlobalFlags } from '../args'
15
15
  import { extractFlag } from '../args'
16
16
  import { loadContext } from '../context'
17
17
  import { printCommandHelp } from '../help'
18
- import { loadKey, resolvePassword } from '../keys'
18
+ import { loadKey } from '../keys'
19
19
  import { fatal, formatLabel, formatValue, output } from '../output'
20
20
 
21
21
  export async function handleOpnsCommand(
@@ -57,7 +57,7 @@ async function opnsRegister(args: string[], opts: GlobalFlags): Promise<void> {
57
57
  }
58
58
  }
59
59
 
60
- const privateKey = await loadKey(resolvePassword())
60
+ const privateKey = await loadKey()
61
61
  const { ctx, destroy } = await loadContext(privateKey, {
62
62
  chain: opts.chain,
63
63
  })
@@ -102,7 +102,7 @@ async function opnsDeregister(
102
102
  }
103
103
  }
104
104
 
105
- const privateKey = await loadKey(resolvePassword())
105
+ const privateKey = await loadKey()
106
106
  const { ctx, destroy } = await loadContext(privateKey, {
107
107
  chain: opts.chain,
108
108
  })
@@ -131,7 +131,7 @@ async function opnsDeregister(
131
131
  }
132
132
 
133
133
  async function opnsLookup(_args: string[], opts: GlobalFlags): Promise<void> {
134
- const privateKey = await loadKey(resolvePassword())
134
+ const privateKey = await loadKey()
135
135
  const { ctx, destroy } = await loadContext(privateKey, {
136
136
  chain: opts.chain,
137
137
  })
@@ -17,10 +17,10 @@ import {
17
17
  import { Utils } from '@bsv/sdk'
18
18
  import { confirm, isCancel } from '@clack/prompts'
19
19
  import type { GlobalFlags } from '../args'
20
- import { extractFlag } from '../args'
20
+ import { extractFlag, hasFlag } from '../args'
21
21
  import { loadContext } from '../context'
22
22
  import { printCommandHelp } from '../help'
23
- import { loadKey, resolvePassword } from '../keys'
23
+ import { loadKey } from '../keys'
24
24
  import { fatal, formatLabel, formatValue, output } from '../output'
25
25
 
26
26
  export async function handleOrdinalsCommand(
@@ -45,7 +45,7 @@ export async function handleOrdinalsCommand(
45
45
  default:
46
46
  printCommandHelp('ordinals', {
47
47
  list: 'List owned ordinals/inscriptions',
48
- mint: 'Mint a new ordinal inscription (--file <path> --type <mime>)',
48
+ mint: 'Mint a new ordinal inscription (--file <path> [--type <mime>] [--map <json>] [--sign-with-bap])',
49
49
  transfer: 'Transfer an ordinal (--outpoint <op> --to <addr>)',
50
50
  sell: 'List an ordinal for sale (--outpoint <op> --price <sats>)',
51
51
  cancel: 'Cancel an ordinal listing (--outpoint <op>)',
@@ -58,7 +58,7 @@ export async function handleOrdinalsCommand(
58
58
  }
59
59
 
60
60
  async function ordinalsList(_args: string[], opts: GlobalFlags): Promise<void> {
61
- const privateKey = await loadKey(resolvePassword())
61
+ const privateKey = await loadKey()
62
62
  const { ctx, destroy } = await loadContext(privateKey, {
63
63
  chain: opts.chain,
64
64
  })
@@ -121,6 +121,8 @@ const MIME_TYPES: Record<string, string> = {
121
121
  async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
122
122
  const file = extractFlag(args, '--file')
123
123
  const type = extractFlag(args, '--type')
124
+ const mapStr = extractFlag(args, '--map')
125
+ const signWithBAP = hasFlag(args, '--sign-with-bap')
124
126
 
125
127
  if (!file) fatal('Missing --file <path>')
126
128
 
@@ -131,6 +133,29 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
131
133
  )
132
134
  }
133
135
 
136
+ let map: Record<string, string> | undefined
137
+ if (mapStr !== undefined) {
138
+ let parsed: unknown
139
+ try {
140
+ parsed = JSON.parse(mapStr)
141
+ } catch (err) {
142
+ fatal(`--map must be valid JSON: ${(err as Error).message}`)
143
+ }
144
+ if (
145
+ typeof parsed !== 'object' ||
146
+ parsed === null ||
147
+ Array.isArray(parsed)
148
+ ) {
149
+ fatal('--map must be a JSON object')
150
+ }
151
+ for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
152
+ if (typeof v !== 'string') {
153
+ fatal(`--map values must be strings (key "${k}" is ${typeof v})`)
154
+ }
155
+ }
156
+ map = parsed as Record<string, string>
157
+ }
158
+
134
159
  let fileBytes: Uint8Array
135
160
  try {
136
161
  fileBytes = readFileSync(file)
@@ -149,7 +174,7 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
149
174
  }
150
175
  }
151
176
 
152
- const privateKey = await loadKey(resolvePassword())
177
+ const privateKey = await loadKey()
153
178
  const { ctx, destroy } = await loadContext(privateKey, {
154
179
  chain: opts.chain,
155
180
  })
@@ -158,6 +183,8 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
158
183
  const result = await inscribe.execute(ctx, {
159
184
  base64Content,
160
185
  contentType,
186
+ ...(map ? { map } : {}),
187
+ ...(signWithBAP ? { signWithBAP: true } : {}),
161
188
  })
162
189
 
163
190
  if (result.error) {
@@ -189,7 +216,7 @@ async function ordinalsTransfer(
189
216
  }
190
217
  }
191
218
 
192
- const privateKey = await loadKey(resolvePassword())
219
+ const privateKey = await loadKey()
193
220
  const { ctx, destroy } = await loadContext(privateKey, {
194
221
  chain: opts.chain,
195
222
  })
@@ -238,7 +265,7 @@ async function ordinalsSell(args: string[], opts: GlobalFlags): Promise<void> {
238
265
  }
239
266
  }
240
267
 
241
- const privateKey = await loadKey(resolvePassword())
268
+ const privateKey = await loadKey()
242
269
  const { ctx, destroy } = await loadContext(privateKey, {
243
270
  chain: opts.chain,
244
271
  })
@@ -295,7 +322,7 @@ async function ordinalsCancel(
295
322
  }
296
323
  }
297
324
 
298
- const privateKey = await loadKey(resolvePassword())
325
+ const privateKey = await loadKey()
299
326
  const { ctx, destroy } = await loadContext(privateKey, {
300
327
  chain: opts.chain,
301
328
  })
@@ -337,7 +364,7 @@ async function ordinalsBuy(args: string[], opts: GlobalFlags): Promise<void> {
337
364
  }
338
365
  }
339
366
 
340
- const privateKey = await loadKey(resolvePassword())
367
+ const privateKey = await loadKey()
341
368
  const { ctx, destroy } = await loadContext(privateKey, {
342
369
  chain: opts.chain,
343
370
  })
@@ -15,7 +15,7 @@ import type { GlobalFlags } from '../args'
15
15
  import { loadConfig, saveConfig } from '../config'
16
16
  import { loadContext } from '../context'
17
17
  import { printCommandHelp } from '../help'
18
- import { loadKey, resolvePassword } from '../keys'
18
+ import { loadKey } from '../keys'
19
19
  import { fatal, formatSuccess, formatWarning, output } from '../output'
20
20
 
21
21
  export async function handleRemoteCommand(
@@ -47,8 +47,7 @@ export async function handleRemoteCommand(
47
47
  'Switch active storage (1sat remote set-active <url | local>)',
48
48
  status:
49
49
  'Fetch GET /account/status from a remote (1sat remote status [url])',
50
- topup:
51
- 'Buy capacity on a remote (1sat remote topup [url] [--units N])',
50
+ topup: 'Buy capacity on a remote (1sat remote topup [url] [--units N])',
52
51
  })
53
52
  if (subcommand && subcommand !== 'help') {
54
53
  process.exit(1)
@@ -86,7 +85,7 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
86
85
  }
87
86
  }
88
87
 
89
- const privateKey = await loadKey(resolvePassword())
88
+ const privateKey = await loadKey()
90
89
  const { walletResult, destroy } = await loadContext(privateKey, {
91
90
  chain: opts.chain,
92
91
  })
@@ -133,7 +132,7 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
133
132
  // ============================================================================
134
133
 
135
134
  async function remoteList(_args: string[], opts: GlobalFlags): Promise<void> {
136
- const privateKey = await loadKey(resolvePassword())
135
+ const privateKey = await loadKey()
137
136
  const { destroy } = await loadContext(privateKey, {
138
137
  chain: opts.chain,
139
138
  })
@@ -259,7 +258,7 @@ async function remoteSetActive(
259
258
  fatal('No remote storages configured')
260
259
  }
261
260
 
262
- const privateKey = await loadKey(resolvePassword())
261
+ const privateKey = await loadKey()
263
262
  const { walletResult, destroy } = await loadContext(privateKey, {
264
263
  chain: opts.chain,
265
264
  })
@@ -299,7 +298,7 @@ async function remoteSetActive(
299
298
  fatal(`Invalid URL: ${target}`)
300
299
  }
301
300
 
302
- const privateKey = await loadKey(resolvePassword())
301
+ const privateKey = await loadKey()
303
302
  const { walletResult, destroy } = await loadContext(privateKey, {
304
303
  chain: opts.chain,
305
304
  })
@@ -345,7 +344,7 @@ async function remoteStatus(args: string[], opts: GlobalFlags): Promise<void> {
345
344
  fatal(`Invalid URL: ${url}`)
346
345
  }
347
346
 
348
- const privateKey = await loadKey(resolvePassword())
347
+ const privateKey = await loadKey()
349
348
  const { walletResult, destroy } = await loadContext(privateKey, {
350
349
  chain: opts.chain,
351
350
  })
@@ -430,7 +429,7 @@ async function remoteTopup(args: string[], opts: GlobalFlags): Promise<void> {
430
429
  fatal(`Invalid URL: ${url}`)
431
430
  }
432
431
 
433
- const privateKey = await loadKey(resolvePassword())
432
+ const privateKey = await loadKey()
434
433
  const { walletResult, destroy } = await loadContext(privateKey, {
435
434
  chain: opts.chain,
436
435
  })
@@ -452,7 +451,8 @@ async function remoteTopup(args: string[], opts: GlobalFlags): Promise<void> {
452
451
  ` ${bold('New capacity:')} ${formatBytes(result.status.capacityBytes)} (${formatBytes(result.status.usedBytes)} used, ${formatBytes(result.status.deficitBytes)} deficit)`,
453
452
  )
454
453
  if (result.status.paidThroughBlock != null) {
455
- const remaining = result.status.paidThroughBlock - result.status.currentBlock
454
+ const remaining =
455
+ result.status.paidThroughBlock - result.status.currentBlock
456
456
  console.log(
457
457
  ` ${bold('Paid through:')} block ${result.status.paidThroughBlock} (${remaining} blocks left)`,
458
458
  )
@@ -31,7 +31,7 @@ import {
31
31
  } from '../config'
32
32
  import { ensureDataDir } from '../config'
33
33
  import { printCommandHelp } from '../help'
34
- import { loadKey, resolvePassword } from '../keys'
34
+ import { loadKey } from '../keys'
35
35
  import { clearMonitorPid, writeMonitorPid } from '../monitor-lock'
36
36
  import { fatal } from '../output'
37
37
 
@@ -138,7 +138,7 @@ async function resolveServe(opts: GlobalFlags): Promise<ResolvedServe> {
138
138
 
139
139
  let privateKey: PrivateKey
140
140
  try {
141
- privateKey = await loadKey(resolvePassword())
141
+ privateKey = await loadKey()
142
142
  } catch (err) {
143
143
  fatal((err as Error).message)
144
144
  }
@@ -6,10 +6,10 @@
6
6
 
7
7
  import { createSocialPost } from '@1sat/actions'
8
8
  import type { GlobalFlags } from '../args'
9
- import { extractFlag } from '../args'
9
+ import { extractFlag, extractFlags } from '../args'
10
10
  import { loadContext } from '../context'
11
11
  import { printCommandHelp } from '../help'
12
- import { loadKey, resolvePassword } from '../keys'
12
+ import { loadKey } from '../keys'
13
13
  import { fatal, output } from '../output'
14
14
 
15
15
  export async function handleSocialCommand(
@@ -23,7 +23,7 @@ export async function handleSocialCommand(
23
23
  return socialPost(rest, opts)
24
24
  default:
25
25
  printCommandHelp('social', {
26
- post: 'Create an on-chain social post (--content <text> --app <name>)',
26
+ post: 'Create an on-chain social post (--content <text> [--app <name>] [--content-type <text/plain|text/markdown>] [--tags <t1,t2>])',
27
27
  })
28
28
  if (subcommand && subcommand !== 'help') {
29
29
  process.exit(1)
@@ -34,16 +34,32 @@ export async function handleSocialCommand(
34
34
  async function socialPost(args: string[], opts: GlobalFlags): Promise<void> {
35
35
  const content = extractFlag(args, '--content')
36
36
  const app = extractFlag(args, '--app') ?? '1sat-cli'
37
+ const contentType = extractFlag(args, '--content-type')
38
+ const tags = extractFlags(args, '--tags')
37
39
 
38
40
  if (!content) fatal('Missing --content <text>')
41
+ if (
42
+ contentType !== undefined &&
43
+ contentType !== 'text/plain' &&
44
+ contentType !== 'text/markdown'
45
+ ) {
46
+ fatal('--content-type must be one of: text/plain, text/markdown')
47
+ }
39
48
 
40
- const privateKey = await loadKey(resolvePassword())
49
+ const privateKey = await loadKey()
41
50
  const { ctx, destroy } = await loadContext(privateKey, {
42
51
  chain: opts.chain,
43
52
  })
44
53
 
45
54
  try {
46
- const result = await createSocialPost.execute(ctx, { app, content })
55
+ const result = await createSocialPost.execute(ctx, {
56
+ app,
57
+ content,
58
+ ...(contentType
59
+ ? { contentType: contentType as 'text/plain' | 'text/markdown' }
60
+ : {}),
61
+ ...(tags.length ? { tags } : {}),
62
+ })
47
63
 
48
64
  if (result.error) {
49
65
  fatal(result.error)
@@ -17,7 +17,7 @@ import type { GlobalFlags } from '../args'
17
17
  import { extractFlag } from '../args'
18
18
  import { loadContext } from '../context'
19
19
  import { printCommandHelp } from '../help'
20
- import { loadKey, resolvePassword } from '../keys'
20
+ import { loadKey } from '../keys'
21
21
  import { fatal, formatLabel, formatValue, output } from '../output'
22
22
 
23
23
  export async function handleSweepCommand(
@@ -55,7 +55,7 @@ async function sweepScan(args: string[], opts: GlobalFlags): Promise<void> {
55
55
  fatal('Invalid WIF private key')
56
56
  }
57
57
 
58
- const privateKey = await loadKey(resolvePassword())
58
+ const privateKey = await loadKey()
59
59
  const { ctx, destroy } = await loadContext(privateKey, {
60
60
  chain: opts.chain,
61
61
  })
@@ -145,7 +145,7 @@ async function sweepImport(args: string[], opts: GlobalFlags): Promise<void> {
145
145
  fatal('Invalid WIF private key')
146
146
  }
147
147
 
148
- const privateKey = await loadKey(resolvePassword())
148
+ const privateKey = await loadKey()
149
149
  const { ctx, destroy } = await loadContext(privateKey, {
150
150
  chain: opts.chain,
151
151
  })
@@ -14,7 +14,7 @@ import type { GlobalFlags } from '../args'
14
14
  import { extractFlag } from '../args'
15
15
  import { loadContext } from '../context'
16
16
  import { printCommandHelp } from '../help'
17
- import { loadKey, resolvePassword } from '../keys'
17
+ import { loadKey } from '../keys'
18
18
  import { fatal, formatLabel, formatValue, output } from '../output'
19
19
 
20
20
  export async function handleTokensCommand(
@@ -52,7 +52,7 @@ async function tokenBalances(
52
52
  _args: string[],
53
53
  opts: GlobalFlags,
54
54
  ): Promise<void> {
55
- const privateKey = await loadKey(resolvePassword())
55
+ const privateKey = await loadKey()
56
56
  const { ctx, destroy } = await loadContext(privateKey, {
57
57
  chain: opts.chain,
58
58
  })
@@ -84,7 +84,7 @@ async function tokenBalances(
84
84
  async function tokenList(args: string[], opts: GlobalFlags): Promise<void> {
85
85
  const tokenId = extractFlag(args, '--token-id')
86
86
 
87
- const privateKey = await loadKey(resolvePassword())
87
+ const privateKey = await loadKey()
88
88
  const { ctx, destroy } = await loadContext(privateKey, {
89
89
  chain: opts.chain,
90
90
  })
@@ -154,7 +154,7 @@ async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
154
154
  }
155
155
  }
156
156
 
157
- const privateKey = await loadKey(resolvePassword())
157
+ const privateKey = await loadKey()
158
158
  const { ctx, destroy } = await loadContext(privateKey, {
159
159
  chain: opts.chain,
160
160
  })
@@ -199,7 +199,7 @@ async function tokenBuy(args: string[], opts: GlobalFlags): Promise<void> {
199
199
  }
200
200
  }
201
201
 
202
- const privateKey = await loadKey(resolvePassword())
202
+ const privateKey = await loadKey()
203
203
  const { ctx, destroy } = await loadContext(privateKey, {
204
204
  chain: opts.chain,
205
205
  })
@@ -3,13 +3,18 @@
3
3
  * BRC-100 interface commands - list-outputs, relinquish-output, list-actions, etc.
4
4
  */
5
5
 
6
- import { deriveDepositAddresses, sendAllBsv, sendBsv } from '@1sat/actions'
6
+ import {
7
+ deriveDepositAddresses,
8
+ sendAllBsv,
9
+ sendBsv,
10
+ syncAddresses,
11
+ } from '@1sat/actions'
7
12
  import { confirm, isCancel } from '@clack/prompts'
8
13
  import type { GlobalFlags } from '../args'
9
14
  import { extractFlag, extractFlags } from '../args'
10
15
  import { loadContext } from '../context'
11
16
  import { printCommandHelp } from '../help'
12
- import { loadKey, resolvePassword } from '../keys'
17
+ import { loadKey } from '../keys'
13
18
  import { fatal, formatValue, output, printKeyValue } from '../output'
14
19
 
15
20
  export async function handleWalletCommand(
@@ -27,6 +32,8 @@ export async function handleWalletCommand(
27
32
  return walletSend(rest, opts)
28
33
  case 'send-all':
29
34
  return walletSendAll(rest, opts)
35
+ case 'sync':
36
+ return walletSync(rest, opts)
30
37
  case 'info':
31
38
  return walletInfo(rest, opts)
32
39
  case 'list-outputs':
@@ -48,9 +55,11 @@ export async function handleWalletCommand(
48
55
  default:
49
56
  printCommandHelp('wallet', {
50
57
  balance: 'Show wallet balance in satoshis',
51
- address: 'Show deposit address',
52
- send: 'Send BSV to an address (--to <addr> --sats <amount>)',
58
+ address:
59
+ 'Show deposit address [--prefix <p>] [--start-index <n>] [--count <n>]',
60
+ send: 'Send BSV (--to <addr> --sats <n> | --script <hex> --sats <n> | --data-asm "<asm>")',
53
61
  'send-all': 'Send all BSV to an address (--to <addr>)',
62
+ sync: 'Sync inbound payments at BRC-29 deposit addresses [--prefix <p>] [--start-index <n>] [--count <n>]',
54
63
  info: 'Show wallet info (address, balance, network)',
55
64
  'list-outputs':
56
65
  'List wallet outputs (--basket <name> [--tags <t1,t2>] [--limit N] [--include-tags] [--include <val>])',
@@ -74,7 +83,7 @@ async function walletBalance(
74
83
  _args: string[],
75
84
  opts: GlobalFlags,
76
85
  ): Promise<void> {
77
- const privateKey = await loadKey(resolvePassword())
86
+ const privateKey = await loadKey()
78
87
  const { ctx, destroy } = await loadContext(privateKey, {
79
88
  chain: opts.chain,
80
89
  })
@@ -99,27 +108,44 @@ async function walletBalance(
99
108
  }
100
109
  }
101
110
 
102
- async function walletAddress(
103
- _args: string[],
104
- opts: GlobalFlags,
105
- ): Promise<void> {
106
- const privateKey = await loadKey(resolvePassword())
111
+ async function walletAddress(args: string[], opts: GlobalFlags): Promise<void> {
112
+ const prefix = extractFlag(args, '--prefix') ?? '1sat'
113
+ const startIndexStr = extractFlag(args, '--start-index')
114
+ const countStr = extractFlag(args, '--count')
115
+
116
+ const startIndex = startIndexStr === undefined ? 0 : Number(startIndexStr)
117
+ const count = countStr === undefined ? 1 : Number(countStr)
118
+
119
+ if (!Number.isInteger(startIndex) || startIndex < 0) {
120
+ fatal('--start-index must be a non-negative integer')
121
+ }
122
+ if (!Number.isInteger(count) || count < 1) {
123
+ fatal('--count must be a positive integer')
124
+ }
125
+
126
+ const privateKey = await loadKey()
107
127
  const { ctx, destroy } = await loadContext(privateKey, {
108
128
  chain: opts.chain,
109
129
  })
110
130
 
111
131
  try {
112
132
  const result = await deriveDepositAddresses.execute(ctx, {
113
- prefix: '1sat',
114
- count: 1,
133
+ prefix,
134
+ startIndex,
135
+ count,
115
136
  })
116
137
 
117
- const primary = result.derivations[0]
118
- if (!primary) {
138
+ if (!result.derivations.length) {
119
139
  fatal('Failed to derive deposit address')
120
140
  }
121
141
 
122
- output(opts.json ? primary : primary.address, opts)
142
+ if (opts.json) {
143
+ output(count === 1 ? result.derivations[0] : result.derivations, opts)
144
+ } else if (!opts.quiet) {
145
+ for (const d of result.derivations) {
146
+ console.log(d.address)
147
+ }
148
+ }
123
149
  } finally {
124
150
  await destroy()
125
151
  }
@@ -127,34 +153,68 @@ async function walletAddress(
127
153
 
128
154
  async function walletSend(args: string[], opts: GlobalFlags): Promise<void> {
129
155
  const to = extractFlag(args, '--to')
156
+ const script = extractFlag(args, '--script')
157
+ const dataAsm = extractFlag(args, '--data-asm')
130
158
  const satsStr = extractFlag(args, '--sats')
131
159
 
132
- if (!to) fatal('Missing --to <address>')
133
- if (!satsStr) fatal('Missing --sats <amount>')
160
+ const modes = [to, script, dataAsm].filter((v) => v !== undefined).length
161
+ if (modes === 0) {
162
+ fatal(
163
+ 'Specify one of --to <address>, --script <hex>, or --data-asm "<asm>"',
164
+ )
165
+ }
166
+ if (modes > 1) {
167
+ fatal('--to, --script, and --data-asm are mutually exclusive')
168
+ }
134
169
 
135
- const satoshis = Number(satsStr)
136
- if (!Number.isFinite(satoshis) || satoshis <= 0) {
137
- fatal('--sats must be a positive number')
170
+ let request: {
171
+ address?: string
172
+ script?: string
173
+ data?: string[]
174
+ satoshis: number
175
+ }
176
+ let confirmMessage: string
177
+
178
+ if (dataAsm !== undefined) {
179
+ if (satsStr !== undefined) {
180
+ fatal(
181
+ '--sats is not allowed with --data-asm (OP_RETURN outputs are 0 sats)',
182
+ )
183
+ }
184
+ request = { data: [dataAsm], satoshis: 0 }
185
+ confirmMessage = `Publish OP_RETURN data "${dataAsm}"?`
186
+ } else {
187
+ if (!satsStr) fatal('Missing --sats <amount>')
188
+ const satoshis = Number(satsStr)
189
+ if (!Number.isFinite(satoshis) || satoshis <= 0) {
190
+ fatal('--sats must be a positive number')
191
+ }
192
+ if (script !== undefined) {
193
+ if (!/^[0-9a-fA-F]*$/.test(script) || script.length % 2 !== 0) {
194
+ fatal('--script must be a hex string')
195
+ }
196
+ request = { script, satoshis }
197
+ confirmMessage = `Send ${satoshis} satoshis to custom script?`
198
+ } else {
199
+ request = { address: to, satoshis }
200
+ confirmMessage = `Send ${satoshis} satoshis to ${to}?`
201
+ }
138
202
  }
139
203
 
140
204
  if (!opts.yes) {
141
- const ok = await confirm({
142
- message: `Send ${satoshis} satoshis to ${to}?`,
143
- })
205
+ const ok = await confirm({ message: confirmMessage })
144
206
  if (isCancel(ok) || !ok) {
145
207
  fatal('Send cancelled.')
146
208
  }
147
209
  }
148
210
 
149
- const privateKey = await loadKey(resolvePassword())
211
+ const privateKey = await loadKey()
150
212
  const { ctx, destroy } = await loadContext(privateKey, {
151
213
  chain: opts.chain,
152
214
  })
153
215
 
154
216
  try {
155
- const result = await sendBsv.execute(ctx, {
156
- requests: [{ address: to, satoshis }],
157
- })
217
+ const result = await sendBsv.execute(ctx, { requests: [request] })
158
218
 
159
219
  if (result.error) {
160
220
  fatal(result.error)
@@ -180,7 +240,7 @@ async function walletSendAll(args: string[], opts: GlobalFlags): Promise<void> {
180
240
  }
181
241
  }
182
242
 
183
- const privateKey = await loadKey(resolvePassword())
243
+ const privateKey = await loadKey()
184
244
  const { ctx, destroy } = await loadContext(privateKey, {
185
245
  chain: opts.chain,
186
246
  })
@@ -198,8 +258,58 @@ async function walletSendAll(args: string[], opts: GlobalFlags): Promise<void> {
198
258
  }
199
259
  }
200
260
 
261
+ async function walletSync(args: string[], opts: GlobalFlags): Promise<void> {
262
+ const prefix = extractFlag(args, '--prefix')
263
+ const startIndexRaw = extractFlag(args, '--start-index')
264
+ const countRaw = extractFlag(args, '--count')
265
+
266
+ const startIndex =
267
+ startIndexRaw !== undefined ? Number.parseInt(startIndexRaw, 10) : undefined
268
+ const count =
269
+ countRaw !== undefined ? Number.parseInt(countRaw, 10) : undefined
270
+
271
+ if (
272
+ startIndex !== undefined &&
273
+ (!Number.isFinite(startIndex) || startIndex < 0)
274
+ ) {
275
+ fatal('--start-index must be a non-negative integer')
276
+ }
277
+ if (count !== undefined && (!Number.isFinite(count) || count < 1)) {
278
+ fatal('--count must be a positive integer')
279
+ }
280
+
281
+ const privateKey = await loadKey()
282
+ const { ctx, destroy } = await loadContext(privateKey, {
283
+ chain: opts.chain,
284
+ })
285
+
286
+ try {
287
+ const result = await syncAddresses.execute(ctx, {
288
+ ...(prefix ? { prefix } : {}),
289
+ ...(startIndex !== undefined ? { startIndex } : {}),
290
+ ...(count !== undefined ? { count } : {}),
291
+ })
292
+
293
+ if (opts.json) {
294
+ output(result, opts)
295
+ return
296
+ }
297
+
298
+ console.log(
299
+ `\nprocessed: ${result.processed} failed: ${result.failed} lastScore: ${result.lastScore}`,
300
+ )
301
+ if (result.addresses.length > 0) {
302
+ console.log('addresses:')
303
+ for (const addr of result.addresses) console.log(` ${addr}`)
304
+ }
305
+ console.log()
306
+ } finally {
307
+ await destroy()
308
+ }
309
+ }
310
+
201
311
  async function walletInfo(_args: string[], opts: GlobalFlags): Promise<void> {
202
- const privateKey = await loadKey(resolvePassword())
312
+ const privateKey = await loadKey()
203
313
  const { ctx, destroy } = await loadContext(privateKey, {
204
314
  chain: opts.chain,
205
315
  })
@@ -264,7 +374,7 @@ async function walletListOutputs(
264
374
  const includeTags = args.includes('--include-tags')
265
375
  const include = extractFlag(args, '--include')
266
376
 
267
- const privateKey = await loadKey(resolvePassword())
377
+ const privateKey = await loadKey()
268
378
  const { ctx, destroy } = await loadContext(privateKey, {
269
379
  chain: opts.chain,
270
380
  })
@@ -314,7 +424,7 @@ async function walletRelinquishOutput(
314
424
  fatal('Invalid --output format. Expected: txid.vout (e.g., abc123...0)')
315
425
  }
316
426
 
317
- const privateKey = await loadKey(resolvePassword())
427
+ const privateKey = await loadKey()
318
428
  const { ctx, destroy } = await loadContext(privateKey, {
319
429
  chain: opts.chain,
320
430
  })
@@ -346,7 +456,7 @@ async function walletListActions(
346
456
  fatal('--limit must be between 1 and 10000')
347
457
  }
348
458
 
349
- const privateKey = await loadKey(resolvePassword())
459
+ const privateKey = await loadKey()
350
460
  const { ctx, destroy } = await loadContext(privateKey, {
351
461
  chain: opts.chain,
352
462
  })
@@ -389,7 +499,7 @@ async function walletCreateAction(
389
499
  fatal(`Invalid JSON: ${jsonInput}`)
390
500
  }
391
501
 
392
- const privateKey = await loadKey(resolvePassword())
502
+ const privateKey = await loadKey()
393
503
  const { ctx, destroy } = await loadContext(privateKey, {
394
504
  chain: opts.chain,
395
505
  })
@@ -418,7 +528,7 @@ async function walletSignAction(
418
528
  fatal(`Invalid JSON: ${jsonInput}`)
419
529
  }
420
530
 
421
- const privateKey = await loadKey(resolvePassword())
531
+ const privateKey = await loadKey()
422
532
  const { ctx, destroy } = await loadContext(privateKey, {
423
533
  chain: opts.chain,
424
534
  })
@@ -439,7 +549,7 @@ async function walletAbortAction(
439
549
 
440
550
  if (!reference) fatal('Missing required --reference <ref>')
441
551
 
442
- const privateKey = await loadKey(resolvePassword())
552
+ const privateKey = await loadKey()
443
553
  const { ctx, destroy } = await loadContext(privateKey, {
444
554
  chain: opts.chain,
445
555
  })
@@ -465,7 +575,7 @@ async function walletListCertificates(
465
575
  fatal('--limit must be between 1 and 10000')
466
576
  }
467
577
 
468
- const privateKey = await loadKey(resolvePassword())
578
+ const privateKey = await loadKey()
469
579
  const { ctx, destroy } = await loadContext(privateKey, {
470
580
  chain: opts.chain,
471
581
  })
@@ -505,7 +615,7 @@ async function walletRelinquishCertificate(
505
615
  if (!serialNumber) fatal('Missing required --serialNumber <serial>')
506
616
  if (!certifier) fatal('Missing required --certifier <certifier>')
507
617
 
508
- const privateKey = await loadKey(resolvePassword())
618
+ const privateKey = await loadKey()
509
619
  const { ctx, destroy } = await loadContext(privateKey, {
510
620
  chain: opts.chain,
511
621
  })
package/src/keys.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  /**
2
2
  * Encrypted key management for the 1sat CLI.
3
3
  *
4
- * Key resolution priority:
4
+ * Key resolution priority inside loadKey():
5
5
  * 1. PRIVATE_KEY_WIF env var (headless/CI)
6
- * 2. Touch ID cached password → decrypt keys.bep (macOS arm64)
7
- * 3. Explicit password → decrypt keys.bep
6
+ * 2. ONESAT_PASSWORD env var → decrypt keys.bep
7
+ * 3. Interactive TTY prompt → decrypt keys.bep
8
8
  * 4. Fail with guidance
9
+ *
10
+ * A biometric vault tier will be added once @1sat/wallet-mac is
11
+ * CLI-ready (resolves its own enclave binary relative to its module
12
+ * directory instead of wallet-desktop's layout).
9
13
  */
10
14
 
11
15
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
12
- import { arch, platform } from 'node:os'
13
16
  import { join } from 'node:path'
14
17
  import { PrivateKey } from '@bsv/sdk'
18
+ import { isCancel, password as promptPassword } from '@clack/prompts'
15
19
  import { type WifBackup, decryptBackup, encryptBackup } from 'bitcoin-backup'
16
20
  import { ensureConfigDir, getConfigDir } from './config'
17
21
 
@@ -30,28 +34,14 @@ export function hasKey(): boolean {
30
34
  }
31
35
 
32
36
  /**
33
- * Check if Touch ID is available for password caching.
37
+ * Load the private key from env, env password, or TTY prompt.
34
38
  */
35
- export function isTouchIDAvailable(): boolean {
36
- return platform() === 'darwin' && arch() === 'arm64'
37
- }
38
-
39
- /**
40
- * Load the private key from env, Touch ID cache, or password.
41
- *
42
- * Resolution order:
43
- * 1. PRIVATE_KEY_WIF env var
44
- * 2. Touch ID cached password (if available)
45
- * 3. Explicit password parameter
46
- */
47
- export async function loadKey(password?: string): Promise<PrivateKey> {
48
- // Priority 1: Environment variable
39
+ export async function loadKey(): Promise<PrivateKey> {
49
40
  const envWif = process.env.PRIVATE_KEY_WIF
50
41
  if (envWif) {
51
42
  return PrivateKey.fromWif(envWif)
52
43
  }
53
44
 
54
- // Priority 2: Encrypted file
55
45
  const keysPath = getKeysPath()
56
46
  if (!existsSync(keysPath)) {
57
47
  throw new Error(
@@ -61,24 +51,21 @@ export async function loadKey(password?: string): Promise<PrivateKey> {
61
51
 
62
52
  const encrypted = readFileSync(keysPath, 'utf8')
63
53
 
64
- // Priority 2a: Try Touch ID cached password
65
- let resolvedPassword = password
66
- if (!resolvedPassword) {
67
- try {
68
- const { getCachedPassword } = await import('bitcoin-backup')
69
- const cached = await getCachedPassword(keysPath)
70
- if (cached) {
71
- resolvedPassword = cached
72
- }
73
- } catch {
74
- // Touch ID not available or no cached password — fall through
75
- }
76
- }
54
+ let resolvedPassword = process.env.ONESAT_PASSWORD
77
55
 
78
56
  if (!resolvedPassword) {
79
- throw new Error(
80
- 'Password required to decrypt key file. Pass --password, set ONESAT_PASSWORD, or run "1sat init --touchid" to enable Touch ID.',
81
- )
57
+ if (!process.stdin.isTTY) {
58
+ throw new Error(
59
+ 'Password required to decrypt key file. Set ONESAT_PASSWORD or run in an interactive terminal.',
60
+ )
61
+ }
62
+ const input = await promptPassword({
63
+ message: 'Password:',
64
+ })
65
+ if (isCancel(input) || typeof input !== 'string' || input.length === 0) {
66
+ throw new Error('Password required to decrypt key file.')
67
+ }
68
+ resolvedPassword = input
82
69
  }
83
70
 
84
71
  const backup = await decryptBackup(encrypted, resolvedPassword)
@@ -93,8 +80,6 @@ export async function loadKey(password?: string): Promise<PrivateKey> {
93
80
  */
94
81
  export async function saveKey(wif: string, password: string): Promise<void> {
95
82
  ensureConfigDir()
96
-
97
- // Validate the WIF before saving
98
83
  PrivateKey.fromWif(wif)
99
84
 
100
85
  const payload: WifBackup = {
@@ -103,29 +88,5 @@ export async function saveKey(wif: string, password: string): Promise<void> {
103
88
  createdAt: new Date().toISOString(),
104
89
  }
105
90
  const encrypted = await encryptBackup(payload, password)
106
- const keysPath = getKeysPath()
107
- writeFileSync(keysPath, encrypted, { mode: 0o600 })
108
- }
109
-
110
- /**
111
- * Cache the password for keys.bep using Touch ID.
112
- */
113
- export async function cacheKeyPassword(password: string): Promise<void> {
114
- const { cachePassword } = await import('bitcoin-backup')
115
- await cachePassword(getKeysPath(), password)
116
- }
117
-
118
- /**
119
- * Remove the cached password for keys.bep.
120
- */
121
- export async function forgetKeyPassword(): Promise<void> {
122
- const { forgetPassword } = await import('bitcoin-backup')
123
- await forgetPassword(getKeysPath())
124
- }
125
-
126
- /**
127
- * Resolve a password from flag or environment variable.
128
- */
129
- export function resolvePassword(flagValue?: string): string | undefined {
130
- return flagValue ?? process.env.ONESAT_PASSWORD
91
+ writeFileSync(getKeysPath(), encrypted, { mode: 0o600 })
131
92
  }