@1sat/cli 0.0.21 → 0.0.22
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 +1 -1
- package/src/args.ts +18 -0
- package/src/commands/init.ts +4 -11
- package/src/commands/remote.ts +3 -1
- package/src/commands/wallet.ts +281 -1
- package/src/help.ts +16 -0
package/package.json
CHANGED
package/src/args.ts
CHANGED
|
@@ -82,6 +82,24 @@ export function extractFlag(args: string[], flag: string): string | undefined {
|
|
|
82
82
|
return args[idx + 1]
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Extract multiple values for a flag, supporting comma-delimited and multiple flag occurrences.
|
|
87
|
+
* Example: --tags a,b,c OR --tags a --tags b --tags c
|
|
88
|
+
*/
|
|
89
|
+
export function extractFlags(args: string[], flag: string): string[] {
|
|
90
|
+
const values: string[] = []
|
|
91
|
+
for (let i = 0; i < args.length - 1; i++) {
|
|
92
|
+
if (args[i] === flag) {
|
|
93
|
+
const value = args[i + 1]
|
|
94
|
+
if (value && !value.startsWith('--')) {
|
|
95
|
+
// Split comma-delimited values
|
|
96
|
+
values.push(...value.split(',').filter(v => v.length > 0))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return values
|
|
101
|
+
}
|
|
102
|
+
|
|
85
103
|
/**
|
|
86
104
|
* Check if a boolean flag is present.
|
|
87
105
|
*/
|
package/src/commands/init.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 4. Config file creation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { randomBytes } from 'node:crypto'
|
|
11
12
|
import { PrivateKey } from '@bsv/sdk'
|
|
12
13
|
import {
|
|
13
14
|
cancel,
|
|
@@ -162,16 +163,8 @@ export async function handleInitCommand(
|
|
|
162
163
|
// bitcoin-backup Touch ID not available — skip silently
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
// 4.
|
|
166
|
-
const storageId =
|
|
167
|
-
message: 'Storage identity key (for wallet persistence):',
|
|
168
|
-
defaultValue: '1sat-cli-default',
|
|
169
|
-
placeholder: '1sat-cli-default',
|
|
170
|
-
})
|
|
171
|
-
if (isCancel(storageId)) {
|
|
172
|
-
cancel('Setup cancelled.')
|
|
173
|
-
process.exit(0)
|
|
174
|
-
}
|
|
166
|
+
// 4. Generate random storage identity key
|
|
167
|
+
const storageId = `1sat-cli-${randomBytes(8).toString('hex')}`
|
|
175
168
|
|
|
176
169
|
// 5. Optional: remote storage configuration
|
|
177
170
|
const useRemote = await confirm({
|
|
@@ -220,7 +213,7 @@ export async function handleInitCommand(
|
|
|
220
213
|
saveConfig({
|
|
221
214
|
...loadConfig(),
|
|
222
215
|
chain: chain as 'main' | 'test',
|
|
223
|
-
storageIdentityKey: storageId
|
|
216
|
+
storageIdentityKey: storageId,
|
|
224
217
|
activeRemote,
|
|
225
218
|
})
|
|
226
219
|
|
package/src/commands/remote.ts
CHANGED
|
@@ -81,6 +81,9 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
81
81
|
chain: opts.chain,
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
+
// Load config early — needed for storage identity key lookups
|
|
85
|
+
const config = loadConfig()
|
|
86
|
+
|
|
84
87
|
try {
|
|
85
88
|
// For backup-only, we use StorageClient via wallet-node
|
|
86
89
|
// biome-ignore lint/suspicious/noExplicitAny: StorageClient constructor not typed in wallet-toolbox
|
|
@@ -99,7 +102,6 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
99
102
|
await walletResult.storage.updateBackups()
|
|
100
103
|
|
|
101
104
|
// Persist to config — connectivity will be validated on next monitor run
|
|
102
|
-
const config = loadConfig()
|
|
103
105
|
const existing = config.backups ?? []
|
|
104
106
|
if (!existing.includes(url)) {
|
|
105
107
|
saveConfig({ ...config, backups: [...existing, url] })
|
package/src/commands/wallet.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wallet commands - balance, address, send, send-all, info.
|
|
3
|
+
* BRC-100 interface commands - list-outputs, relinquish-output, list-actions, etc.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { deriveDepositAddresses, sendAllBsv, sendBsv } from '@1sat/actions'
|
|
6
7
|
import { confirm, isCancel } from '@clack/prompts'
|
|
7
8
|
import type { GlobalFlags } from '../args'
|
|
8
|
-
import { extractFlag } from '../args'
|
|
9
|
+
import { extractFlag, extractFlags } from '../args'
|
|
9
10
|
import { loadContext } from '../context'
|
|
10
11
|
import { printCommandHelp } from '../help'
|
|
11
12
|
import { loadKey, resolvePassword } from '../keys'
|
|
@@ -28,6 +29,22 @@ export async function handleWalletCommand(
|
|
|
28
29
|
return walletSendAll(rest, opts)
|
|
29
30
|
case 'info':
|
|
30
31
|
return walletInfo(rest, opts)
|
|
32
|
+
case 'list-outputs':
|
|
33
|
+
return walletListOutputs(rest, opts)
|
|
34
|
+
case 'relinquish-output':
|
|
35
|
+
return walletRelinquishOutput(rest, opts)
|
|
36
|
+
case 'list-actions':
|
|
37
|
+
return walletListActions(rest, opts)
|
|
38
|
+
case 'create-action':
|
|
39
|
+
return walletCreateAction(rest, opts)
|
|
40
|
+
case 'sign-action':
|
|
41
|
+
return walletSignAction(rest, opts)
|
|
42
|
+
case 'abort-action':
|
|
43
|
+
return walletAbortAction(rest, opts)
|
|
44
|
+
case 'list-certificates':
|
|
45
|
+
return walletListCertificates(rest, opts)
|
|
46
|
+
case 'relinquish-certificate':
|
|
47
|
+
return walletRelinquishCertificate(rest, opts)
|
|
31
48
|
default:
|
|
32
49
|
printCommandHelp('wallet', {
|
|
33
50
|
balance: 'Show wallet balance in satoshis',
|
|
@@ -35,6 +52,14 @@ export async function handleWalletCommand(
|
|
|
35
52
|
send: 'Send BSV to an address (--to <addr> --sats <amount>)',
|
|
36
53
|
'send-all': 'Send all BSV to an address (--to <addr>)',
|
|
37
54
|
info: 'Show wallet info (address, balance, network)',
|
|
55
|
+
'list-outputs': 'List wallet outputs (--basket <name> [--tags <t1,t2>] [--limit N])',
|
|
56
|
+
'relinquish-output': 'Remove output from basket (--basket <name> --output <txid.vout>)',
|
|
57
|
+
'list-actions': 'List wallet actions [--labels <l1,l2>] [--limit N]',
|
|
58
|
+
'create-action': 'Create action (JSON args)',
|
|
59
|
+
'sign-action': 'Sign action (JSON args)',
|
|
60
|
+
'abort-action': 'Abort action (--reference <ref>)',
|
|
61
|
+
'list-certificates': 'List certificates',
|
|
62
|
+
'relinquish-certificate': 'Relinquish certificate (--type <t> --serialNumber <s> --certifier <c>)',
|
|
38
63
|
})
|
|
39
64
|
if (subcommand && subcommand !== 'help') {
|
|
40
65
|
process.exit(1)
|
|
@@ -216,3 +241,258 @@ async function walletInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
216
241
|
await destroy()
|
|
217
242
|
}
|
|
218
243
|
}
|
|
244
|
+
|
|
245
|
+
// BRC-100 Interface Commands
|
|
246
|
+
|
|
247
|
+
async function walletListOutputs(args: string[], opts: GlobalFlags): Promise<void> {
|
|
248
|
+
const basket = extractFlag(args, '--basket')
|
|
249
|
+
if (!basket) fatal('Missing required --basket <name>')
|
|
250
|
+
|
|
251
|
+
const tags = extractFlags(args, '--tags')
|
|
252
|
+
const limitStr = extractFlag(args, '--limit')
|
|
253
|
+
const limit = limitStr ? parseInt(limitStr, 10) : 10
|
|
254
|
+
if (!Number.isFinite(limit) || limit < 1 || limit > 10000) {
|
|
255
|
+
fatal('--limit must be between 1 and 10000')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const privateKey = await loadKey(resolvePassword())
|
|
259
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
260
|
+
chain: opts.chain,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const listArgs: Parameters<typeof ctx.wallet.listOutputs>[0] = {
|
|
265
|
+
basket,
|
|
266
|
+
limit,
|
|
267
|
+
}
|
|
268
|
+
if (tags.length > 0) {
|
|
269
|
+
listArgs.tags = tags
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = await ctx.wallet.listOutputs(listArgs)
|
|
273
|
+
|
|
274
|
+
if (opts.json) {
|
|
275
|
+
output(result, opts)
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`\n${result.totalOutputs} total outputs in basket '${basket}':\n`)
|
|
278
|
+
for (const out of result.outputs) {
|
|
279
|
+
const tags = out.tags?.join(', ') || 'none'
|
|
280
|
+
console.log(` ${out.outpoint} | ${out.satoshis} sats | tags: ${tags}`)
|
|
281
|
+
}
|
|
282
|
+
console.log()
|
|
283
|
+
}
|
|
284
|
+
} finally {
|
|
285
|
+
await destroy()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function walletRelinquishOutput(
|
|
290
|
+
args: string[],
|
|
291
|
+
opts: GlobalFlags,
|
|
292
|
+
): Promise<void> {
|
|
293
|
+
const basket = extractFlag(args, '--basket')
|
|
294
|
+
const outpoint = extractFlag(args, '--output')
|
|
295
|
+
|
|
296
|
+
if (!basket) fatal('Missing required --basket <name>')
|
|
297
|
+
if (!outpoint) fatal('Missing required --output <txid.vout>')
|
|
298
|
+
|
|
299
|
+
// Validate output format (txid.vout)
|
|
300
|
+
if (!outpoint.includes('.') || outpoint.split('.').length !== 2) {
|
|
301
|
+
fatal('Invalid --output format. Expected: txid.vout (e.g., abc123...0)')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const privateKey = await loadKey(resolvePassword())
|
|
305
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
306
|
+
chain: opts.chain,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const result = await ctx.wallet.relinquishOutput({ basket, output: outpoint })
|
|
311
|
+
|
|
312
|
+
if (opts.json) {
|
|
313
|
+
output(result, opts)
|
|
314
|
+
} else {
|
|
315
|
+
output({ relinquished: true, basket, output: outpoint }, opts)
|
|
316
|
+
}
|
|
317
|
+
} finally {
|
|
318
|
+
await destroy()
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function walletListActions(args: string[], opts: GlobalFlags): Promise<void> {
|
|
323
|
+
const labels = extractFlags(args, '--labels')
|
|
324
|
+
const limitStr = extractFlag(args, '--limit')
|
|
325
|
+
const limit = limitStr ? parseInt(limitStr, 10) : 10
|
|
326
|
+
if (!Number.isFinite(limit) || limit < 1 || limit > 10000) {
|
|
327
|
+
fatal('--limit must be between 1 and 10000')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const privateKey = await loadKey(resolvePassword())
|
|
331
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
332
|
+
chain: opts.chain,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const listArgs: Parameters<typeof ctx.wallet.listActions>[0] = {
|
|
337
|
+
labels: labels.length > 0 ? labels : ['*'], // Default to all labels if none specified
|
|
338
|
+
limit,
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = await ctx.wallet.listActions(listArgs)
|
|
342
|
+
|
|
343
|
+
if (opts.json) {
|
|
344
|
+
output(result, opts)
|
|
345
|
+
} else {
|
|
346
|
+
console.log(`\n${result.totalActions} total actions:\n`)
|
|
347
|
+
for (const action of result.actions) {
|
|
348
|
+
console.log(` ${action.txid || 'pending'} | status: ${action.status}`)
|
|
349
|
+
}
|
|
350
|
+
console.log()
|
|
351
|
+
}
|
|
352
|
+
} finally {
|
|
353
|
+
await destroy()
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function walletCreateAction(args: string[], opts: GlobalFlags): Promise<void> {
|
|
358
|
+
const jsonInput = args[0]
|
|
359
|
+
|
|
360
|
+
if (!jsonInput) fatal('Missing JSON arguments. Usage: wallet create-action \'{...}\'')
|
|
361
|
+
|
|
362
|
+
let actionArgs: Parameters<typeof ctx.wallet.createAction>[0]
|
|
363
|
+
try {
|
|
364
|
+
actionArgs = JSON.parse(jsonInput)
|
|
365
|
+
} catch {
|
|
366
|
+
fatal(`Invalid JSON: ${jsonInput}`)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const privateKey = await loadKey(resolvePassword())
|
|
370
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
371
|
+
chain: opts.chain,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const result = await ctx.wallet.createAction(actionArgs)
|
|
376
|
+
output(result, opts)
|
|
377
|
+
} finally {
|
|
378
|
+
await destroy()
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function walletSignAction(args: string[], opts: GlobalFlags): Promise<void> {
|
|
383
|
+
const jsonInput = args[0]
|
|
384
|
+
|
|
385
|
+
if (!jsonInput) fatal('Missing JSON arguments. Usage: wallet sign-action \'{...}\'')
|
|
386
|
+
|
|
387
|
+
let signArgs: Parameters<typeof ctx.wallet.signAction>[0]
|
|
388
|
+
try {
|
|
389
|
+
signArgs = JSON.parse(jsonInput)
|
|
390
|
+
} catch {
|
|
391
|
+
fatal(`Invalid JSON: ${jsonInput}`)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const privateKey = await loadKey(resolvePassword())
|
|
395
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
396
|
+
chain: opts.chain,
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const result = await ctx.wallet.signAction(signArgs)
|
|
401
|
+
output(result, opts)
|
|
402
|
+
} finally {
|
|
403
|
+
await destroy()
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function walletAbortAction(args: string[], opts: GlobalFlags): Promise<void> {
|
|
408
|
+
const reference = extractFlag(args, '--reference')
|
|
409
|
+
|
|
410
|
+
if (!reference) fatal('Missing required --reference <ref>')
|
|
411
|
+
|
|
412
|
+
const privateKey = await loadKey(resolvePassword())
|
|
413
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
414
|
+
chain: opts.chain,
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
const result = await ctx.wallet.abortAction({ reference })
|
|
419
|
+
output(result, opts)
|
|
420
|
+
} finally {
|
|
421
|
+
await destroy()
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function walletListCertificates(
|
|
426
|
+
args: string[],
|
|
427
|
+
opts: GlobalFlags,
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
const certifiers = extractFlags(args, '--certifiers')
|
|
430
|
+
const types = extractFlags(args, '--types')
|
|
431
|
+
const limitStr = extractFlag(args, '--limit')
|
|
432
|
+
const limit = limitStr ? parseInt(limitStr, 10) : 10
|
|
433
|
+
|
|
434
|
+
if (!Number.isFinite(limit) || limit < 1 || limit > 10000) {
|
|
435
|
+
fatal('--limit must be between 1 and 10000')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const privateKey = await loadKey(resolvePassword())
|
|
439
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
440
|
+
chain: opts.chain,
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const listArgs: Parameters<typeof ctx.wallet.listCertificates>[0] = {
|
|
445
|
+
certifiers: certifiers.length > 0 ? certifiers : [],
|
|
446
|
+
types: types.length > 0 ? types : [],
|
|
447
|
+
limit,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const result = await ctx.wallet.listCertificates(listArgs)
|
|
451
|
+
|
|
452
|
+
if (opts.json) {
|
|
453
|
+
output(result, opts)
|
|
454
|
+
} else {
|
|
455
|
+
console.log(`\n${result.totalCertificates} total certificates:\n`)
|
|
456
|
+
for (const cert of result.certificates) {
|
|
457
|
+
console.log(` ${cert.type} | ${cert.serialNumber} | ${cert.certifier}`)
|
|
458
|
+
}
|
|
459
|
+
console.log()
|
|
460
|
+
}
|
|
461
|
+
} finally {
|
|
462
|
+
await destroy()
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function walletRelinquishCertificate(
|
|
467
|
+
args: string[],
|
|
468
|
+
opts: GlobalFlags,
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
const type = extractFlag(args, '--type')
|
|
471
|
+
const serialNumber = extractFlag(args, '--serialNumber')
|
|
472
|
+
const certifier = extractFlag(args, '--certifier')
|
|
473
|
+
|
|
474
|
+
if (!type) fatal('Missing required --type <type>')
|
|
475
|
+
if (!serialNumber) fatal('Missing required --serialNumber <serial>')
|
|
476
|
+
if (!certifier) fatal('Missing required --certifier <certifier>')
|
|
477
|
+
|
|
478
|
+
const privateKey = await loadKey(resolvePassword())
|
|
479
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
480
|
+
chain: opts.chain,
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
const result = await ctx.wallet.relinquishCertificate({
|
|
485
|
+
type,
|
|
486
|
+
serialNumber,
|
|
487
|
+
certifier,
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
if (opts.json) {
|
|
491
|
+
output(result, opts)
|
|
492
|
+
} else {
|
|
493
|
+
output({ relinquished: true, type, serialNumber, certifier }, opts)
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
await destroy()
|
|
497
|
+
}
|
|
498
|
+
}
|
package/src/help.ts
CHANGED
|
@@ -47,6 +47,22 @@ ${bold('Wallet:')}
|
|
|
47
47
|
${cyan('wallet send-all')} Send all BSV to an address
|
|
48
48
|
${cyan('wallet info')} Show wallet info
|
|
49
49
|
|
|
50
|
+
${bold('Wallet (BRC-100 Interface):')}
|
|
51
|
+
${cyan('wallet list-outputs')} List outputs in basket
|
|
52
|
+
${dim('--basket <name> [--tags <t1,t2>] [--limit N]')}
|
|
53
|
+
${cyan('wallet relinquish-output')} Remove output from basket
|
|
54
|
+
${dim('--basket <name> --output <txid.vout>')}
|
|
55
|
+
${cyan('wallet list-actions')} List wallet actions
|
|
56
|
+
${dim('[--labels <l1,l2>] [--limit N]')}
|
|
57
|
+
${cyan('wallet create-action')} Create raw action (JSON args)
|
|
58
|
+
${cyan('wallet sign-action')} Sign raw action (JSON args)
|
|
59
|
+
${cyan('wallet abort-action')} Abort pending action
|
|
60
|
+
${dim('--reference <ref>')}
|
|
61
|
+
${cyan('wallet list-certificates')} List certificates
|
|
62
|
+
${dim('[--certifiers <c1,c2>] [--types <t1,t2>] [--limit N]')}
|
|
63
|
+
${cyan('wallet relinquish-certificate')} Relinquish certificate
|
|
64
|
+
${dim('--type <t> --serialNumber <s> --certifier <c>')}
|
|
65
|
+
|
|
50
66
|
${bold('Ordinals:')}
|
|
51
67
|
${cyan('ordinals list')} List owned ordinals
|
|
52
68
|
${cyan('ordinals mint')} Mint a new ordinal inscription
|