@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.
@@ -3,12 +3,16 @@
3
3
  */
4
4
 
5
5
  import {
6
+ deployBsv21Auth,
7
+ deployBsv21Mint,
6
8
  getBsv21Balances,
7
9
  getDisplayValue,
8
10
  listTokens,
11
+ mintBsv21,
9
12
  purchaseBsv21,
10
13
  sendBsv21,
11
14
  } from '@1sat/actions'
15
+ import type { Destination } from '@1sat/types'
12
16
  import { confirm, isCancel } from '@clack/prompts'
13
17
  import type { GlobalFlags } from '../args'
14
18
  import { extractFlag } from '../args'
@@ -30,18 +34,16 @@ export async function handleTokensCommand(
30
34
  return tokenList(rest, opts)
31
35
  case 'send':
32
36
  return tokenSend(rest, opts)
33
- case 'deploy':
34
- return tokenDeploy(rest, opts)
37
+ case 'deploy-mint':
38
+ return tokenDeployMint(rest, opts)
39
+ case 'deploy-auth':
40
+ return tokenDeployAuth(rest, opts)
41
+ case 'mint':
42
+ return tokenMint(rest, opts)
35
43
  case 'buy':
36
44
  return tokenBuy(rest, opts)
37
45
  default:
38
- printCommandHelp('tokens', {
39
- balances: 'Show token balances by token ID',
40
- list: 'List owned token UTXOs (--token-id <id>)',
41
- send: 'Transfer tokens (--token-id <id> --to <addr> --amount <n>)',
42
- deploy: 'Deploy a new BSV21 token (not yet available)',
43
- buy: 'Purchase listed tokens (--outpoint <op> --token-id <id> --amount <n>)',
44
- })
46
+ printCommandHelp('tokens', opts.json)
45
47
  if (subcommand && subcommand !== 'help') {
46
48
  process.exit(1)
47
49
  }
@@ -131,23 +133,47 @@ async function tokenList(args: string[], opts: GlobalFlags): Promise<void> {
131
133
  }
132
134
  }
133
135
 
136
+ /**
137
+ * Build a {@link Destination} from CLI flags. Only the first set wins.
138
+ * Returns undefined when no flags are set — actions interpret that as 'self'.
139
+ */
140
+ function destinationFromFlags(opts: {
141
+ to?: string
142
+ counterparty?: string
143
+ lockingScript?: string
144
+ }): Destination | undefined {
145
+ if (opts.lockingScript) return { lockingScript: opts.lockingScript }
146
+ if (opts.counterparty) return { counterparty: opts.counterparty }
147
+ if (opts.to) return { address: opts.to }
148
+ return undefined
149
+ }
150
+
134
151
  async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
135
152
  const tokenId = extractFlag(args, '--token-id')
136
153
  const to = extractFlag(args, '--to')
154
+ const counterparty = extractFlag(args, '--counterparty')
155
+ const lockingScript = extractFlag(args, '--locking-script')
137
156
  const amountStr = extractFlag(args, '--amount')
138
157
 
139
158
  if (!tokenId) fatal('Missing --token-id <id>')
140
- if (!to) fatal('Missing --to <address>')
141
159
  if (!amountStr) fatal('Missing --amount <number>')
142
160
 
161
+ const destination = destinationFromFlags({ to, counterparty, lockingScript })
162
+ if (!destination) {
163
+ fatal(
164
+ 'Missing destination — provide one of: --to <address>, --counterparty <pubkey>, --locking-script <hex>',
165
+ )
166
+ }
167
+
143
168
  const amount = BigInt(amountStr)
144
169
  if (amount <= 0n) {
145
170
  fatal('--amount must be a positive number')
146
171
  }
147
172
 
148
173
  if (!opts.yes) {
174
+ const dest = to ?? counterparty ?? lockingScript ?? ''
149
175
  const ok = await confirm({
150
- message: `Send ${amountStr} tokens (${tokenId.slice(0, 12)}...) to ${to}?`,
176
+ message: `Send ${amountStr} tokens (${tokenId.slice(0, 12)}...) to ${dest.slice(0, 32)}?`,
151
177
  })
152
178
  if (isCancel(ok) || !ok) {
153
179
  fatal('Token send cancelled.')
@@ -162,7 +188,9 @@ async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
162
188
  try {
163
189
  const result = await sendBsv21.execute(ctx, {
164
190
  tokenId,
165
- recipients: [{ amount: amountStr, address: to }],
191
+ recipients: [
192
+ { amount: amountStr, destination: destination as Destination },
193
+ ],
166
194
  })
167
195
 
168
196
  if (result.error) {
@@ -175,10 +203,214 @@ async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
175
203
  }
176
204
  }
177
205
 
178
- async function tokenDeploy(_args: string[], _opts: GlobalFlags): Promise<void> {
179
- fatal(
180
- 'tokens deploy is not yet available. No deploy action exists in the actions package.',
181
- )
206
+ async function tokenDeployMint(
207
+ args: string[],
208
+ opts: GlobalFlags,
209
+ ): Promise<void> {
210
+ const symbol = extractFlag(args, '--symbol')
211
+ const amountStr = extractFlag(args, '--amount')
212
+ const decimalsStr = extractFlag(args, '--decimals')
213
+ const icon = extractFlag(args, '--icon')
214
+ const to = extractFlag(args, '--to')
215
+ const counterparty = extractFlag(args, '--counterparty')
216
+ const lockingScript = extractFlag(args, '--locking-script')
217
+
218
+ if (!symbol) fatal('Missing --symbol <ticker>')
219
+ if (!amountStr) fatal('Missing --amount <total-supply>')
220
+
221
+ const amount = BigInt(amountStr)
222
+ if (amount <= 0n) fatal('--amount must be a positive number')
223
+
224
+ const decimals = decimalsStr ? Number.parseInt(decimalsStr, 10) : 0
225
+ if (Number.isNaN(decimals) || decimals < 0 || decimals > 18) {
226
+ fatal('--decimals must be an integer between 0 and 18')
227
+ }
228
+
229
+ if (!opts.yes) {
230
+ const ok = await confirm({
231
+ message: `Deploy ${symbol} with fixed supply ${amountStr} (decimals=${decimals})?`,
232
+ })
233
+ if (isCancel(ok) || !ok) {
234
+ fatal('Deploy cancelled.')
235
+ }
236
+ }
237
+
238
+ const privateKey = await loadKey()
239
+ const { ctx, destroy } = await loadContext(privateKey, {
240
+ chain: opts.chain,
241
+ })
242
+
243
+ try {
244
+ const result = await deployBsv21Mint.execute(ctx, {
245
+ symbol,
246
+ amount: amountStr,
247
+ decimals,
248
+ icon,
249
+ destination: destinationFromFlags({ to, counterparty, lockingScript }),
250
+ })
251
+
252
+ if (result.error) {
253
+ fatal(result.error)
254
+ }
255
+
256
+ output(
257
+ opts.json ? result : { txid: result.txid, tokenId: result.tokenId },
258
+ opts,
259
+ )
260
+ } finally {
261
+ await destroy()
262
+ }
263
+ }
264
+
265
+ async function tokenDeployAuth(
266
+ args: string[],
267
+ opts: GlobalFlags,
268
+ ): Promise<void> {
269
+ const symbol = extractFlag(args, '--symbol')
270
+ const decimalsStr = extractFlag(args, '--decimals')
271
+ const icon = extractFlag(args, '--icon')
272
+ const to = extractFlag(args, '--to')
273
+ const counterparty = extractFlag(args, '--counterparty')
274
+ const lockingScript = extractFlag(args, '--locking-script')
275
+
276
+ if (!symbol) fatal('Missing --symbol <ticker>')
277
+
278
+ const decimals = decimalsStr ? Number.parseInt(decimalsStr, 10) : 0
279
+ if (Number.isNaN(decimals) || decimals < 0 || decimals > 18) {
280
+ fatal('--decimals must be an integer between 0 and 18')
281
+ }
282
+
283
+ if (!opts.yes) {
284
+ const ok = await confirm({
285
+ message: `Deploy ${symbol} as a mintable token (decimals=${decimals})?`,
286
+ })
287
+ if (isCancel(ok) || !ok) {
288
+ fatal('Deploy cancelled.')
289
+ }
290
+ }
291
+
292
+ const privateKey = await loadKey()
293
+ const { ctx, destroy } = await loadContext(privateKey, {
294
+ chain: opts.chain,
295
+ })
296
+
297
+ try {
298
+ const result = await deployBsv21Auth.execute(ctx, {
299
+ symbol,
300
+ decimals,
301
+ icon,
302
+ destination: destinationFromFlags({ to, counterparty, lockingScript }),
303
+ })
304
+
305
+ if (result.error) {
306
+ fatal(result.error)
307
+ }
308
+
309
+ output(
310
+ opts.json
311
+ ? result
312
+ : {
313
+ txid: result.txid,
314
+ tokenId: result.tokenId,
315
+ authOutpoint: result.authOutpoint,
316
+ },
317
+ opts,
318
+ )
319
+ } finally {
320
+ await destroy()
321
+ }
322
+ }
323
+
324
+ async function tokenMint(args: string[], opts: GlobalFlags): Promise<void> {
325
+ const tokenId = extractFlag(args, '--token-id')
326
+ const amountStr = extractFlag(args, '--amount')
327
+
328
+ const mintTo = extractFlag(args, '--to')
329
+ const mintCounterparty = extractFlag(args, '--counterparty')
330
+ const mintLockingScript = extractFlag(args, '--locking-script')
331
+
332
+ const authTo = extractFlag(args, '--auth-to')
333
+ const authCounterparty = extractFlag(args, '--auth-counterparty')
334
+ const authLockingScript = extractFlag(args, '--auth-locking-script')
335
+
336
+ const endMinting = args.includes('--end-minting')
337
+
338
+ if (!tokenId) fatal('Missing --token-id <id>')
339
+
340
+ const mintDestination = destinationFromFlags({
341
+ to: mintTo,
342
+ counterparty: mintCounterparty,
343
+ lockingScript: mintLockingScript,
344
+ })
345
+ const authDestination = destinationFromFlags({
346
+ to: authTo,
347
+ counterparty: authCounterparty,
348
+ lockingScript: authLockingScript,
349
+ })
350
+
351
+ // At least one operation
352
+ if (!amountStr && !authDestination && !endMinting) {
353
+ fatal(
354
+ 'Provide either --amount (and optional mint destination) or auth destination, or --end-minting to burn authority',
355
+ )
356
+ }
357
+
358
+ const mint = amountStr
359
+ ? {
360
+ amount: amountStr,
361
+ // Default mint destination to self when only amount is provided
362
+ destination: mintDestination ?? ({} as Destination),
363
+ }
364
+ : undefined
365
+ // Default auth destination to self when not ending minting. Symmetric
366
+ // with the mint default above. The action treats omitted `auth` as
367
+ // "burn authority" and requires `endMinting: true` for that, so leave
368
+ // auth undefined only when the user explicitly opted into ending.
369
+ const auth = endMinting
370
+ ? authDestination
371
+ ? { destination: authDestination }
372
+ : undefined
373
+ : { destination: authDestination ?? ({} as Destination) }
374
+
375
+ if (!opts.yes) {
376
+ const summary: string[] = []
377
+ if (mint) summary.push(`mint ${amountStr}`)
378
+ if (auth) summary.push('re-issue auth')
379
+ if (!auth && endMinting) summary.push('END MINTING (burn auth)')
380
+ const ok = await confirm({
381
+ message: `${summary.join(' + ')} for token ${tokenId.slice(0, 12)}...?`,
382
+ })
383
+ if (isCancel(ok) || !ok) {
384
+ fatal('Mint cancelled.')
385
+ }
386
+ }
387
+
388
+ const privateKey = await loadKey()
389
+ const { ctx, destroy } = await loadContext(privateKey, {
390
+ chain: opts.chain,
391
+ })
392
+
393
+ try {
394
+ const result = await mintBsv21.execute(ctx, {
395
+ tokenId,
396
+ mint,
397
+ auth,
398
+ endMinting: endMinting || undefined,
399
+ })
400
+
401
+ if (result.error) {
402
+ fatal(result.error)
403
+ }
404
+
405
+ output(
406
+ opts.json
407
+ ? result
408
+ : { txid: result.txid, authOutpoint: result.authOutpoint },
409
+ opts,
410
+ )
411
+ } finally {
412
+ await destroy()
413
+ }
182
414
  }
183
415
 
184
416
  async function tokenBuy(args: string[], opts: GlobalFlags): Promise<void> {
@@ -17,9 +17,7 @@ export async function handleTxCommand(
17
17
  case 'decode':
18
18
  return txDecode(rest, opts)
19
19
  default:
20
- printCommandHelp('tx', {
21
- decode: 'Decode a raw transaction hex',
22
- })
20
+ printCommandHelp('tx', opts.json)
23
21
  if (subcommand && subcommand !== 'help') {
24
22
  process.exit(1)
25
23
  }
@@ -53,26 +53,7 @@ export async function handleWalletCommand(
53
53
  case 'relinquish-certificate':
54
54
  return walletRelinquishCertificate(rest, opts)
55
55
  default:
56
- printCommandHelp('wallet', {
57
- balance: 'Show wallet balance in satoshis',
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>")',
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>]',
63
- info: 'Show wallet info (address, balance, network)',
64
- 'list-outputs':
65
- 'List wallet outputs (--basket <name> [--tags <t1,t2>] [--limit N] [--include-tags] [--include <val>])',
66
- 'relinquish-output':
67
- 'Remove output from basket (--basket <name> --output <txid.vout>)',
68
- 'list-actions': 'List wallet actions [--labels <l1,l2>] [--limit N]',
69
- 'create-action': 'Create action (JSON args)',
70
- 'sign-action': 'Sign action (JSON args)',
71
- 'abort-action': 'Abort action (--reference <ref>)',
72
- 'list-certificates': 'List certificates',
73
- 'relinquish-certificate':
74
- 'Relinquish certificate (--type <t> --serialNumber <s> --certifier <c>)',
75
- })
56
+ printCommandHelp('wallet', opts.json)
76
57
  if (subcommand && subcommand !== 'help') {
77
58
  process.exit(1)
78
59
  }
package/src/config.ts CHANGED
@@ -40,6 +40,28 @@ export interface ServerAccountsConfig {
40
40
  freeIdentityKeys?: string[]
41
41
  }
42
42
 
43
+ export interface ServerMessageboxConfig {
44
+ /** Port to bind. Defaults to `8101`. */
45
+ port?: number
46
+ /** Enable WebSocket delivery. Defaults to `true`. */
47
+ websockets?: boolean
48
+ /**
49
+ * SQLite file path for messagebox storage. Used only when wallet storage
50
+ * is `bun-sqlite`. Defaults to `<dataDir>/messagebox-{chain}.db`.
51
+ */
52
+ dbPath?: string
53
+ /**
54
+ * Postgres schema for messagebox tables. Used only when wallet storage
55
+ * is `pg`. Defaults to `messagebox`.
56
+ */
57
+ pgSchema?: string
58
+ /**
59
+ * Override the wallet storage URL messagebox calls for BRC-31/BRC-29
60
+ * operations. Defaults to the local wallet server URL (`http://<host>:<port>/`).
61
+ */
62
+ walletStorageUrl?: string
63
+ }
64
+
43
65
  export interface ServerConfig {
44
66
  /** Hostname to bind. Defaults to `127.0.0.1`. */
45
67
  host?: string
@@ -49,6 +71,8 @@ export interface ServerConfig {
49
71
  storage?: ServerStorageConfig
50
72
  /** Optional account/metering layer (opt-in per-deployment). */
51
73
  accounts?: ServerAccountsConfig
74
+ /** Messagebox subcommand settings. */
75
+ messagebox?: ServerMessageboxConfig
52
76
  }
53
77
 
54
78
  export interface OneSatCliConfig {