@1sat/cli 0.0.80 → 0.0.82
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 +4 -4
- package/src/commands/opns.ts +155 -3
- package/src/commands/storage.ts +117 -0
- package/src/help.ts +43 -3
- package/src/main.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1sat/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.82",
|
|
4
4
|
"description": "CLI for 1Sat Ordinals SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/cli.ts",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@1sat/actions": "0.0.
|
|
29
|
+
"@1sat/actions": "0.0.187",
|
|
30
30
|
"@1sat/client": "0.0.43",
|
|
31
31
|
"@1sat/types": "0.0.34",
|
|
32
|
-
"@1sat/wallet-node": "0.0.
|
|
33
|
-
"@1sat/wallet-server": "0.0.
|
|
32
|
+
"@1sat/wallet-node": "0.0.60",
|
|
33
|
+
"@1sat/wallet-server": "0.0.29",
|
|
34
34
|
"@bsv/sdk": "^2.1.6",
|
|
35
35
|
"@bsv/wallet-toolbox": "2.1.24",
|
|
36
36
|
"chalk": "^5.0.0",
|
package/src/commands/opns.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpNS commands - register, deregister, lookup.
|
|
2
|
+
* OpNS commands - register, deregister, lookup, sell, buy, cancel-listing.
|
|
3
3
|
*
|
|
4
|
-
* Manage OpNS
|
|
4
|
+
* Manage OpNS names: payment identity bindings and market listings. Paid
|
|
5
|
+
* mining is product code on 1sat.name.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import {
|
|
9
|
+
cancelListing,
|
|
10
|
+
deriveDepositAddresses,
|
|
8
11
|
getDisplayValue,
|
|
9
12
|
getOpnsNames,
|
|
13
|
+
listOrdinal,
|
|
10
14
|
opnsDeregister as opnsDeregisterAction,
|
|
11
15
|
opnsRegister as opnsRegisterAction,
|
|
16
|
+
purchaseOrdinal,
|
|
12
17
|
} from '@1sat/actions'
|
|
13
18
|
import { confirm, isCancel } from '@clack/prompts'
|
|
14
19
|
import type { GlobalFlags } from '../args'
|
|
@@ -31,6 +36,12 @@ export async function handleOpnsCommand(
|
|
|
31
36
|
return opnsDeregister(rest, opts)
|
|
32
37
|
case 'lookup':
|
|
33
38
|
return opnsLookup(rest, opts)
|
|
39
|
+
case 'sell':
|
|
40
|
+
return opnsSell(rest, opts)
|
|
41
|
+
case 'buy':
|
|
42
|
+
return opnsBuy(rest, opts)
|
|
43
|
+
case 'cancel-listing':
|
|
44
|
+
return opnsCancelListing(rest, opts)
|
|
34
45
|
default:
|
|
35
46
|
printCommandHelp('opns', opts.json)
|
|
36
47
|
if (subcommand && subcommand !== 'help') {
|
|
@@ -149,9 +160,15 @@ async function opnsLookup(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
149
160
|
const nameTag = getDisplayValue(o, 'name', 'name') ?? ''
|
|
150
161
|
const publishedTag = o.tags?.find((t) => t === 'opns:published')
|
|
151
162
|
const status = publishedTag ? 'registered' : 'unregistered'
|
|
163
|
+
const price = o.tags?.some((t) => t === 'ordlock')
|
|
164
|
+
? (o.tags
|
|
165
|
+
?.find((t) => t.startsWith('price:'))
|
|
166
|
+
?.slice('price:'.length) ?? '?')
|
|
167
|
+
: undefined
|
|
168
|
+
const listed = price ? ` ${formatLabel(`listed @ ${price}`)}` : ''
|
|
152
169
|
|
|
153
170
|
console.log(
|
|
154
|
-
` ${formatValue(o.outpoint)} ${nameTag ? formatValue(nameTag) : ''} ${formatLabel(status)}`,
|
|
171
|
+
` ${formatValue(o.outpoint)} ${nameTag ? formatValue(nameTag) : ''} ${formatLabel(status)}${listed}`,
|
|
155
172
|
)
|
|
156
173
|
}
|
|
157
174
|
|
|
@@ -160,3 +177,138 @@ async function opnsLookup(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
160
177
|
await destroy()
|
|
161
178
|
}
|
|
162
179
|
}
|
|
180
|
+
|
|
181
|
+
async function opnsCancelListing(
|
|
182
|
+
args: string[],
|
|
183
|
+
opts: GlobalFlags,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
const outpoint = extractFlag(args, '--outpoint')
|
|
186
|
+
|
|
187
|
+
if (!outpoint) fatal('Missing --outpoint <txid.vout>')
|
|
188
|
+
|
|
189
|
+
if (!opts.yes) {
|
|
190
|
+
const ok = await confirm({
|
|
191
|
+
message: `Cancel listing for OpNS name ${outpoint}?`,
|
|
192
|
+
})
|
|
193
|
+
if (isCancel(ok) || !ok) {
|
|
194
|
+
fatal('Cancellation cancelled.')
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const privateKey = await loadKey()
|
|
199
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
200
|
+
chain: opts.chain,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const namesResult = await getOpnsNames.execute(ctx, { limit: 10000 })
|
|
205
|
+
const listing = namesResult.outputs.find((o) => o.outpoint === outpoint)
|
|
206
|
+
if (!listing) {
|
|
207
|
+
fatal(`Listing not found in wallet OpNS basket: ${outpoint}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = await cancelListing.execute(ctx, {
|
|
211
|
+
listing,
|
|
212
|
+
inputBEEF: namesResult.BEEF as number[] | undefined,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
if (result.error) {
|
|
216
|
+
fatal(result.error)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
output(opts.json ? result : { txid: result.txid }, opts)
|
|
220
|
+
} finally {
|
|
221
|
+
await destroy()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function opnsSell(args: string[], opts: GlobalFlags): Promise<void> {
|
|
226
|
+
const outpoint = extractFlag(args, '--outpoint')
|
|
227
|
+
const priceStr = extractFlag(args, '--price')
|
|
228
|
+
|
|
229
|
+
if (!outpoint) fatal('Missing --outpoint <txid.vout>')
|
|
230
|
+
if (!priceStr) fatal('Missing --price <satoshis>')
|
|
231
|
+
|
|
232
|
+
const price = Number(priceStr)
|
|
233
|
+
if (!Number.isFinite(price) || price <= 0) {
|
|
234
|
+
fatal('--price must be a positive number')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!opts.yes) {
|
|
238
|
+
const ok = await confirm({
|
|
239
|
+
message: `List OpNS name ${outpoint} for sale at ${price} satoshis?`,
|
|
240
|
+
})
|
|
241
|
+
if (isCancel(ok) || !ok) {
|
|
242
|
+
fatal('Listing cancelled.')
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const privateKey = await loadKey()
|
|
247
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
248
|
+
chain: opts.chain,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const namesResult = await getOpnsNames.execute(ctx, { limit: 10000 })
|
|
253
|
+
const ordinal = namesResult.outputs.find((o) => o.outpoint === outpoint)
|
|
254
|
+
if (!ordinal) {
|
|
255
|
+
fatal(`OpNS name not found in wallet: ${outpoint}`)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const addressResult = await deriveDepositAddresses.execute(ctx, {
|
|
259
|
+
prefix: '1sat',
|
|
260
|
+
count: 1,
|
|
261
|
+
})
|
|
262
|
+
const payAddress = addressResult.derivations[0]?.address
|
|
263
|
+
if (!payAddress) {
|
|
264
|
+
fatal('Failed to derive pay address')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result = await listOrdinal.execute(ctx, {
|
|
268
|
+
ordinal,
|
|
269
|
+
price,
|
|
270
|
+
payAddress,
|
|
271
|
+
inputBEEF: namesResult.BEEF as number[] | undefined,
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
if (result.error) {
|
|
275
|
+
fatal(result.error)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
output(opts.json ? result : { txid: result.txid }, opts)
|
|
279
|
+
} finally {
|
|
280
|
+
await destroy()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function opnsBuy(args: string[], opts: GlobalFlags): Promise<void> {
|
|
285
|
+
const outpoint = extractFlag(args, '--outpoint')
|
|
286
|
+
|
|
287
|
+
if (!outpoint) fatal('Missing --outpoint <txid.vout>')
|
|
288
|
+
|
|
289
|
+
if (!opts.yes) {
|
|
290
|
+
const ok = await confirm({
|
|
291
|
+
message: `Purchase OpNS name listing ${outpoint}?`,
|
|
292
|
+
})
|
|
293
|
+
if (isCancel(ok) || !ok) {
|
|
294
|
+
fatal('Purchase cancelled.')
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const privateKey = await loadKey()
|
|
299
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
300
|
+
chain: opts.chain,
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const result = await purchaseOrdinal.execute(ctx, { outpoint })
|
|
305
|
+
|
|
306
|
+
if (result.error) {
|
|
307
|
+
fatal(result.error)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
output(opts.json ? result : { txid: result.txid }, opts)
|
|
311
|
+
} finally {
|
|
312
|
+
await destroy()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage maintenance commands — operate directly on the serve wallet's
|
|
3
|
+
* storage database (config under server.storage in config.json).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { nominateInvalidReqs, StorageBunSqlite } from '@1sat/wallet-node'
|
|
7
|
+
import { StorageProvider } from '@bsv/wallet-toolbox'
|
|
8
|
+
import { join } from 'node:path'
|
|
9
|
+
import type { GlobalFlags } from '../args'
|
|
10
|
+
import { extractFlag } from '../args'
|
|
11
|
+
import { ensureDataDir, loadConfig } from '../config'
|
|
12
|
+
import { printCommandHelp } from '../help'
|
|
13
|
+
import { fatal, output } from '../output'
|
|
14
|
+
|
|
15
|
+
export async function handleStorageCommand(
|
|
16
|
+
args: string[],
|
|
17
|
+
opts: GlobalFlags,
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const [subcommand, ...rest] = args
|
|
20
|
+
|
|
21
|
+
switch (subcommand) {
|
|
22
|
+
case 'unfail':
|
|
23
|
+
return storageUnfail(rest, opts)
|
|
24
|
+
default:
|
|
25
|
+
printCommandHelp('storage', opts.json)
|
|
26
|
+
if (subcommand && subcommand !== 'help') {
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Nominate invalid proven_tx_reqs for recovery. Writes only the `unfail`
|
|
34
|
+
* status; the running monitor's TaskUnFail verifies each txid against
|
|
35
|
+
* the chain, restores the ones that actually mined, and returns the rest
|
|
36
|
+
* to `invalid`. Safe to run against a live serve instance — same
|
|
37
|
+
* single-field status update the monitor's own review tasks perform.
|
|
38
|
+
*/
|
|
39
|
+
async function storageUnfail(
|
|
40
|
+
args: string[],
|
|
41
|
+
opts: GlobalFlags,
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const rawWindow = extractFlag(args, '--window')
|
|
44
|
+
if (!rawWindow) {
|
|
45
|
+
fatal(
|
|
46
|
+
"--window is required: 'all' for every invalid record (one-time cleanup), or a duration like 30d / 12h / 90m",
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
const windowMsecs = parseWindow(rawWindow)
|
|
50
|
+
|
|
51
|
+
const storage = await openServeStorage(opts)
|
|
52
|
+
try {
|
|
53
|
+
const { nominated } = await nominateInvalidReqs(storage, windowMsecs)
|
|
54
|
+
|
|
55
|
+
if (opts.json) {
|
|
56
|
+
output({ nominated }, opts)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (nominated.length === 0) {
|
|
61
|
+
console.log('No invalid reqs in window — nothing nominated.')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
console.log(`Nominated ${nominated.length} invalid req(s) for unfail review:`)
|
|
65
|
+
for (const n of nominated) {
|
|
66
|
+
console.log(` ${n.provenTxReqId} ${n.txid}`)
|
|
67
|
+
}
|
|
68
|
+
console.log(
|
|
69
|
+
'The running monitor (1sat serve / serve monitor) processes these on its next TaskUnFail tick: mined transactions are restored, the rest return to invalid.',
|
|
70
|
+
)
|
|
71
|
+
} finally {
|
|
72
|
+
await storage.destroy()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseWindow(raw: string): number | undefined {
|
|
77
|
+
if (raw === 'all') return undefined
|
|
78
|
+
const m = raw.match(/^(\d+)([dhm])$/)
|
|
79
|
+
if (!m) {
|
|
80
|
+
fatal(
|
|
81
|
+
`--window must be 'all' or a duration like 30d, 12h, 90m — got: ${raw}`,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
const units: Record<string, number> = {
|
|
85
|
+
d: 24 * 60 * 60 * 1000,
|
|
86
|
+
h: 60 * 60 * 1000,
|
|
87
|
+
m: 60 * 1000,
|
|
88
|
+
}
|
|
89
|
+
return Number(m[1]) * units[m[2]]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Open the serve wallet's storage exactly as `1sat serve` resolves it. */
|
|
93
|
+
async function openServeStorage(opts: GlobalFlags): Promise<StorageProvider> {
|
|
94
|
+
const config = loadConfig()
|
|
95
|
+
const chain = opts.chain ?? config.chain ?? 'main'
|
|
96
|
+
const storageConfig = config.server?.storage ?? { provider: 'bun-sqlite' }
|
|
97
|
+
const baseOptions = StorageProvider.createStorageBaseOptions(chain)
|
|
98
|
+
|
|
99
|
+
let storage: StorageProvider
|
|
100
|
+
if (storageConfig.provider === 'pg') {
|
|
101
|
+
if (!storageConfig.dbUrl) {
|
|
102
|
+
fatal(
|
|
103
|
+
'server.storage.provider is pg but server.storage.dbUrl is not set.',
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
const { StoragePg } = await import('@1sat/wallet-node')
|
|
107
|
+
storage = new StoragePg({ ...baseOptions, dbUrl: storageConfig.dbUrl })
|
|
108
|
+
} else {
|
|
109
|
+
storage = new StorageBunSqlite({
|
|
110
|
+
...baseOptions,
|
|
111
|
+
filename: join(ensureDataDir(), `wallet-${chain}.db`),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await storage.makeAvailable()
|
|
116
|
+
return storage
|
|
117
|
+
}
|
package/src/help.ts
CHANGED
|
@@ -496,19 +496,37 @@ export const COMMANDS: CommandSpec[] = [
|
|
|
496
496
|
{
|
|
497
497
|
group: 'OpNS',
|
|
498
498
|
name: 'opns',
|
|
499
|
-
description: 'Ordinals Name System —
|
|
499
|
+
description: 'Ordinals Name System — on-chain names',
|
|
500
500
|
subcommands: [
|
|
501
501
|
{
|
|
502
502
|
name: 'register',
|
|
503
|
-
description: 'Register identity on an OpNS name',
|
|
503
|
+
description: 'Register a payment identity key on an OpNS name',
|
|
504
504
|
args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
|
|
505
505
|
},
|
|
506
506
|
{
|
|
507
507
|
name: 'deregister',
|
|
508
|
-
description: 'Deregister identity from an OpNS name',
|
|
508
|
+
description: 'Deregister the payment identity from an OpNS name',
|
|
509
509
|
args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
|
|
510
510
|
},
|
|
511
511
|
{ name: 'lookup', description: 'List OpNS names from wallet' },
|
|
512
|
+
{
|
|
513
|
+
name: 'sell',
|
|
514
|
+
description: 'List an OpNS name for sale',
|
|
515
|
+
args: [
|
|
516
|
+
{ flag: '--outpoint', values: '<txid.vout>', required: true },
|
|
517
|
+
{ flag: '--price', values: '<satoshis>', required: true },
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'buy',
|
|
522
|
+
description: 'Purchase an OpNS name listing',
|
|
523
|
+
args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: 'cancel-listing',
|
|
527
|
+
description: 'Cancel a market listing on an OpNS name',
|
|
528
|
+
args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
|
|
529
|
+
},
|
|
512
530
|
],
|
|
513
531
|
},
|
|
514
532
|
|
|
@@ -552,6 +570,28 @@ export const COMMANDS: CommandSpec[] = [
|
|
|
552
570
|
},
|
|
553
571
|
],
|
|
554
572
|
},
|
|
573
|
+
{
|
|
574
|
+
group: 'Server',
|
|
575
|
+
name: 'storage',
|
|
576
|
+
description:
|
|
577
|
+
'Serve-wallet storage maintenance (config under server.storage in config.json)',
|
|
578
|
+
subcommands: [
|
|
579
|
+
{
|
|
580
|
+
name: 'unfail',
|
|
581
|
+
description:
|
|
582
|
+
'Nominate invalid proven_tx_reqs for chain re-check; the running monitor restores any that actually mined',
|
|
583
|
+
args: [
|
|
584
|
+
{
|
|
585
|
+
flag: '--window',
|
|
586
|
+
values: "<all|30d|12h|90m>",
|
|
587
|
+
required: true,
|
|
588
|
+
description:
|
|
589
|
+
"Only reqs created within the window; 'all' for every invalid record",
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
},
|
|
555
595
|
|
|
556
596
|
// MCP
|
|
557
597
|
{
|
package/src/main.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { handleOpnsCommand } from './commands/opns'
|
|
|
21
21
|
import { handleOrdinalsCommand } from './commands/ordinals'
|
|
22
22
|
import { handleRemoteCommand } from './commands/remote'
|
|
23
23
|
import { handleServeCommand } from './commands/serve'
|
|
24
|
+
import { handleStorageCommand } from './commands/storage'
|
|
24
25
|
import { handleSocialCommand } from './commands/social'
|
|
25
26
|
import { handleSweepCommand } from './commands/sweep'
|
|
26
27
|
import { handleTokensCommand } from './commands/tokens'
|
|
@@ -134,6 +135,10 @@ async function main(): Promise<void> {
|
|
|
134
135
|
await handleServeCommand(rest, flags)
|
|
135
136
|
break
|
|
136
137
|
|
|
138
|
+
case 'storage':
|
|
139
|
+
await handleStorageCommand(rest, flags)
|
|
140
|
+
break
|
|
141
|
+
|
|
137
142
|
// Hidden: parent CLI spawns this after wallet destroy so monitor
|
|
138
143
|
// stdout/stderr go to ~/.1sat/cli/monitor.log instead of the TTY.
|
|
139
144
|
case '__monitor-once':
|