@1sat/cli 0.0.1

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.
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Ordinals commands - list, mint, transfer, sell, cancel, buy.
3
+ */
4
+
5
+ import {
6
+ cancelListing,
7
+ deriveDepositAddresses,
8
+ getOrdinals,
9
+ inscribe,
10
+ listOrdinal,
11
+ purchaseOrdinal,
12
+ transferOrdinals,
13
+ } from '@1sat/actions'
14
+ import { Utils } from '@bsv/sdk'
15
+ import { confirm, isCancel } from '@clack/prompts'
16
+ import { readFileSync } from 'node:fs'
17
+ import { basename, extname } from 'node:path'
18
+ import type { GlobalFlags } from '../args'
19
+ import { extractFlag } from '../args'
20
+ import { loadContext } from '../context'
21
+ import { printCommandHelp } from '../help'
22
+ import { loadKey, resolvePassword } from '../keys'
23
+ import { fatal, formatLabel, formatValue, output } from '../output'
24
+
25
+ export async function handleOrdinalsCommand(
26
+ args: string[],
27
+ opts: GlobalFlags,
28
+ ): Promise<void> {
29
+ const [subcommand, ...rest] = args
30
+
31
+ switch (subcommand) {
32
+ case 'list':
33
+ return ordinalsList(rest, opts)
34
+ case 'mint':
35
+ return ordinalsMint(rest, opts)
36
+ case 'transfer':
37
+ return ordinalsTransfer(rest, opts)
38
+ case 'sell':
39
+ return ordinalsSell(rest, opts)
40
+ case 'cancel':
41
+ return ordinalsCancel(rest, opts)
42
+ case 'buy':
43
+ return ordinalsBuy(rest, opts)
44
+ default:
45
+ printCommandHelp('ordinals', {
46
+ list: 'List owned ordinals/inscriptions',
47
+ mint: 'Mint a new ordinal inscription (--file <path> --type <mime>)',
48
+ transfer: 'Transfer an ordinal (--outpoint <op> --to <addr>)',
49
+ sell: 'List an ordinal for sale (--outpoint <op> --price <sats>)',
50
+ cancel: 'Cancel an ordinal listing (--outpoint <op>)',
51
+ buy: 'Purchase a listed ordinal (--outpoint <op>)',
52
+ })
53
+ if (subcommand && subcommand !== 'help') {
54
+ process.exit(1)
55
+ }
56
+ }
57
+ }
58
+
59
+ async function ordinalsList(_args: string[], opts: GlobalFlags): Promise<void> {
60
+ const privateKey = await loadKey(resolvePassword())
61
+ const { ctx, destroy } = await loadContext(privateKey, {
62
+ chain: opts.chain,
63
+ })
64
+
65
+ try {
66
+ const result = await getOrdinals.execute(ctx, {
67
+ limit: 100,
68
+ offset: 0,
69
+ })
70
+
71
+ if (opts.json) {
72
+ output(result.outputs, opts)
73
+ return
74
+ }
75
+
76
+ if (result.outputs.length === 0) {
77
+ output('No ordinals found.', opts)
78
+ return
79
+ }
80
+
81
+ for (const o of result.outputs) {
82
+ const typeTag =
83
+ o.tags?.find((t) => t.startsWith('type:'))?.slice(5) ?? 'unknown'
84
+ const originTag =
85
+ o.tags?.find((t) => t.startsWith('origin:'))?.slice(7) ?? ''
86
+ const nameTag = o.tags?.find((t) => t.startsWith('name:'))?.slice(5) ?? ''
87
+
88
+ console.log(
89
+ ` ${formatValue(o.outpoint)} ${formatLabel(typeTag)}${nameTag ? ` ${nameTag}` : ''}${originTag ? ` origin:${originTag}` : ''}`,
90
+ )
91
+ }
92
+
93
+ console.log(`\n ${result.outputs.length} ordinal(s) found.`)
94
+ } finally {
95
+ await destroy()
96
+ }
97
+ }
98
+
99
+ const MIME_TYPES: Record<string, string> = {
100
+ '.txt': 'text/plain',
101
+ '.html': 'text/html',
102
+ '.css': 'text/css',
103
+ '.js': 'application/javascript',
104
+ '.json': 'application/json',
105
+ '.xml': 'application/xml',
106
+ '.svg': 'image/svg+xml',
107
+ '.png': 'image/png',
108
+ '.jpg': 'image/jpeg',
109
+ '.jpeg': 'image/jpeg',
110
+ '.gif': 'image/gif',
111
+ '.webp': 'image/webp',
112
+ '.bmp': 'image/bmp',
113
+ '.mp3': 'audio/mpeg',
114
+ '.wav': 'audio/wav',
115
+ '.mp4': 'video/mp4',
116
+ '.webm': 'video/webm',
117
+ '.pdf': 'application/pdf',
118
+ }
119
+
120
+ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
121
+ const file = extractFlag(args, '--file')
122
+ const type = extractFlag(args, '--type')
123
+
124
+ if (!file) fatal('Missing --file <path>')
125
+
126
+ const contentType = type ?? MIME_TYPES[extname(file).toLowerCase()]
127
+ if (!contentType) {
128
+ fatal(
129
+ `Cannot detect content type for ${basename(file)}. Use --type <mime-type>`,
130
+ )
131
+ }
132
+
133
+ let fileBytes: Uint8Array
134
+ try {
135
+ fileBytes = readFileSync(file)
136
+ } catch (err) {
137
+ fatal(`Failed to read file: ${(err as Error).message}`)
138
+ }
139
+
140
+ const base64Content = Utils.toBase64(Array.from(fileBytes))
141
+
142
+ if (!opts.yes) {
143
+ const ok = await confirm({
144
+ message: `Inscribe ${basename(file)} (${contentType}, ${fileBytes.length} bytes)?`,
145
+ })
146
+ if (isCancel(ok) || !ok) {
147
+ fatal('Inscription cancelled.')
148
+ }
149
+ }
150
+
151
+ const privateKey = await loadKey(resolvePassword())
152
+ const { ctx, destroy } = await loadContext(privateKey, {
153
+ chain: opts.chain,
154
+ })
155
+
156
+ try {
157
+ const result = await inscribe.execute(ctx, {
158
+ base64Content,
159
+ contentType,
160
+ })
161
+
162
+ if (result.error) {
163
+ fatal(result.error)
164
+ }
165
+
166
+ output(opts.json ? result : { txid: result.txid }, opts)
167
+ } finally {
168
+ await destroy()
169
+ }
170
+ }
171
+
172
+ async function ordinalsTransfer(
173
+ args: string[],
174
+ opts: GlobalFlags,
175
+ ): Promise<void> {
176
+ const outpoint = extractFlag(args, '--outpoint')
177
+ const to = extractFlag(args, '--to')
178
+
179
+ if (!outpoint) fatal('Missing --outpoint <txid.vout>')
180
+ if (!to) fatal('Missing --to <address>')
181
+
182
+ if (!opts.yes) {
183
+ const ok = await confirm({
184
+ message: `Transfer ordinal ${outpoint} to ${to}?`,
185
+ })
186
+ if (isCancel(ok) || !ok) {
187
+ fatal('Transfer cancelled.')
188
+ }
189
+ }
190
+
191
+ const privateKey = await loadKey(resolvePassword())
192
+ const { ctx, destroy } = await loadContext(privateKey, {
193
+ chain: opts.chain,
194
+ })
195
+
196
+ try {
197
+ // Look up the ordinal from the wallet
198
+ const ordinalsResult = await getOrdinals.execute(ctx, { limit: 10000 })
199
+ const ordinal = ordinalsResult.outputs.find((o) => o.outpoint === outpoint)
200
+ if (!ordinal) {
201
+ fatal(`Ordinal not found in wallet: ${outpoint}`)
202
+ }
203
+
204
+ const result = await transferOrdinals.execute(ctx, {
205
+ transfers: [{ ordinal, address: to }],
206
+ inputBEEF: ordinalsResult.BEEF as number[] | undefined,
207
+ })
208
+
209
+ if (result.error) {
210
+ fatal(result.error)
211
+ }
212
+
213
+ output(opts.json ? result : { txid: result.txid }, opts)
214
+ } finally {
215
+ await destroy()
216
+ }
217
+ }
218
+
219
+ async function ordinalsSell(args: string[], opts: GlobalFlags): Promise<void> {
220
+ const outpoint = extractFlag(args, '--outpoint')
221
+ const priceStr = extractFlag(args, '--price')
222
+
223
+ if (!outpoint) fatal('Missing --outpoint <txid.vout>')
224
+ if (!priceStr) fatal('Missing --price <satoshis>')
225
+
226
+ const price = Number(priceStr)
227
+ if (!Number.isFinite(price) || price <= 0) {
228
+ fatal('--price must be a positive number')
229
+ }
230
+
231
+ if (!opts.yes) {
232
+ const ok = await confirm({
233
+ message: `List ordinal ${outpoint} for sale at ${price} satoshis?`,
234
+ })
235
+ if (isCancel(ok) || !ok) {
236
+ fatal('Listing cancelled.')
237
+ }
238
+ }
239
+
240
+ const privateKey = await loadKey(resolvePassword())
241
+ const { ctx, destroy } = await loadContext(privateKey, {
242
+ chain: opts.chain,
243
+ })
244
+
245
+ try {
246
+ // Look up the ordinal from the wallet
247
+ const ordinalsResult = await getOrdinals.execute(ctx, { limit: 10000 })
248
+ const ordinal = ordinalsResult.outputs.find((o) => o.outpoint === outpoint)
249
+ if (!ordinal) {
250
+ fatal(`Ordinal not found in wallet: ${outpoint}`)
251
+ }
252
+
253
+ // Derive a BRC-29 address to receive payment
254
+ const addressResult = await deriveDepositAddresses.execute(ctx, {
255
+ prefix: '1sat',
256
+ count: 1,
257
+ })
258
+ const payAddress = addressResult.derivations[0]?.address
259
+ if (!payAddress) {
260
+ fatal('Failed to derive pay address')
261
+ }
262
+
263
+ const result = await listOrdinal.execute(ctx, {
264
+ ordinal,
265
+ price,
266
+ payAddress,
267
+ inputBEEF: ordinalsResult.BEEF as number[] | undefined,
268
+ })
269
+
270
+ if (result.error) {
271
+ fatal(result.error)
272
+ }
273
+
274
+ output(opts.json ? result : { txid: result.txid }, opts)
275
+ } finally {
276
+ await destroy()
277
+ }
278
+ }
279
+
280
+ async function ordinalsCancel(
281
+ args: string[],
282
+ opts: GlobalFlags,
283
+ ): Promise<void> {
284
+ const outpoint = extractFlag(args, '--outpoint')
285
+
286
+ if (!outpoint) fatal('Missing --outpoint <txid.vout>')
287
+
288
+ if (!opts.yes) {
289
+ const ok = await confirm({
290
+ message: `Cancel listing for ordinal ${outpoint}?`,
291
+ })
292
+ if (isCancel(ok) || !ok) {
293
+ fatal('Cancellation cancelled.')
294
+ }
295
+ }
296
+
297
+ const privateKey = await loadKey(resolvePassword())
298
+ const { ctx, destroy } = await loadContext(privateKey, {
299
+ chain: opts.chain,
300
+ })
301
+
302
+ try {
303
+ // Look up the listing from the wallet (listings are in ordinals basket with ordlock tag)
304
+ const ordinalsResult = await getOrdinals.execute(ctx, { limit: 10000 })
305
+ const listing = ordinalsResult.outputs.find((o) => o.outpoint === outpoint)
306
+ if (!listing) {
307
+ fatal(`Listing not found in wallet: ${outpoint}`)
308
+ }
309
+
310
+ const result = await cancelListing.execute(ctx, {
311
+ listing,
312
+ inputBEEF: ordinalsResult.BEEF as number[] | undefined,
313
+ })
314
+
315
+ if (result.error) {
316
+ fatal(result.error)
317
+ }
318
+
319
+ output(opts.json ? result : { txid: result.txid }, opts)
320
+ } finally {
321
+ await destroy()
322
+ }
323
+ }
324
+
325
+ async function ordinalsBuy(args: string[], opts: GlobalFlags): Promise<void> {
326
+ const outpoint = extractFlag(args, '--outpoint')
327
+
328
+ if (!outpoint) fatal('Missing --outpoint <txid.vout>')
329
+
330
+ if (!opts.yes) {
331
+ const ok = await confirm({
332
+ message: `Purchase ordinal listing ${outpoint}?`,
333
+ })
334
+ if (isCancel(ok) || !ok) {
335
+ fatal('Purchase cancelled.')
336
+ }
337
+ }
338
+
339
+ const privateKey = await loadKey(resolvePassword())
340
+ const { ctx, destroy } = await loadContext(privateKey, {
341
+ chain: opts.chain,
342
+ })
343
+
344
+ try {
345
+ const result = await purchaseOrdinal.execute(ctx, { outpoint })
346
+
347
+ if (result.error) {
348
+ fatal(result.error)
349
+ }
350
+
351
+ output(opts.json ? result : { txid: result.txid }, opts)
352
+ } finally {
353
+ await destroy()
354
+ }
355
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Social commands - post.
3
+ *
4
+ * On-chain social protocol (BSocial) actions.
5
+ */
6
+
7
+ import { createSocialPost } from '@1sat/actions'
8
+ import type { GlobalFlags } from '../args'
9
+ import { extractFlag } from '../args'
10
+ import { loadContext } from '../context'
11
+ import { printCommandHelp } from '../help'
12
+ import { loadKey, resolvePassword } from '../keys'
13
+ import { fatal, output } from '../output'
14
+
15
+ export async function handleSocialCommand(
16
+ args: string[],
17
+ opts: GlobalFlags,
18
+ ): Promise<void> {
19
+ const [subcommand, ...rest] = args
20
+
21
+ switch (subcommand) {
22
+ case 'post':
23
+ return socialPost(rest, opts)
24
+ default:
25
+ printCommandHelp('social', {
26
+ post: 'Create an on-chain social post (--content <text> --app <name>)',
27
+ })
28
+ if (subcommand && subcommand !== 'help') {
29
+ process.exit(1)
30
+ }
31
+ }
32
+ }
33
+
34
+ async function socialPost(args: string[], opts: GlobalFlags): Promise<void> {
35
+ const content = extractFlag(args, '--content')
36
+ const app = extractFlag(args, '--app') ?? '1sat-cli'
37
+
38
+ if (!content) fatal('Missing --content <text>')
39
+
40
+ const privateKey = await loadKey(resolvePassword())
41
+ const { ctx, destroy } = await loadContext(privateKey, {
42
+ chain: opts.chain,
43
+ })
44
+
45
+ try {
46
+ const result = await createSocialPost.execute(ctx, { app, content })
47
+
48
+ if (result.error) {
49
+ fatal(result.error)
50
+ }
51
+
52
+ output(opts.json ? result : { txid: result.txid }, opts)
53
+ } finally {
54
+ await destroy()
55
+ }
56
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Sweep commands - scan, import.
3
+ *
4
+ * Sweep assets from external wallets into the BRC-100 wallet.
5
+ */
6
+
7
+ import type { GlobalFlags } from '../args'
8
+ import { extractFlag } from '../args'
9
+ import { printCommandHelp } from '../help'
10
+ import { fatal } from '../output'
11
+
12
+ export async function handleSweepCommand(
13
+ args: string[],
14
+ opts: GlobalFlags,
15
+ ): Promise<void> {
16
+ const [subcommand, ...rest] = args
17
+
18
+ switch (subcommand) {
19
+ case 'scan':
20
+ return sweepScan(rest, opts)
21
+ case 'import':
22
+ return sweepImport(rest, opts)
23
+ default:
24
+ printCommandHelp('sweep', {
25
+ scan: 'Scan an address for UTXOs (--address <addr>)',
26
+ import: 'Import UTXOs into wallet (--wif <key>)',
27
+ })
28
+ if (subcommand && subcommand !== 'help') {
29
+ process.exit(1)
30
+ }
31
+ }
32
+ }
33
+
34
+ async function sweepScan(args: string[], _opts: GlobalFlags): Promise<void> {
35
+ const address = extractFlag(args, '--address')
36
+
37
+ if (!address) fatal('Missing --address <addr>')
38
+
39
+ // TODO: Load context via loadContext() + loadKey()
40
+ // TODO: Use services to scan address for UTXOs (BSV, ordinals, tokens)
41
+ // TODO: Display found UTXOs with type classification
42
+ fatal('sweep scan is not yet implemented')
43
+ }
44
+
45
+ async function sweepImport(args: string[], _opts: GlobalFlags): Promise<void> {
46
+ const wif = extractFlag(args, '--wif')
47
+
48
+ if (!wif) fatal('Missing --wif <private-key>')
49
+
50
+ // TODO: Load context via loadContext() + loadKey()
51
+ // TODO: Call sweepBsv/sweepOrdinals/sweepBsv21 actions
52
+ // TODO: Output sweep results: txids and amounts imported
53
+ fatal('sweep import is not yet implemented')
54
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Token commands - balances, list, send, deploy, buy.
3
+ */
4
+
5
+ import { getBsv21Balances, listTokens, sendBsv21 } from '@1sat/actions'
6
+ import { confirm, isCancel } from '@clack/prompts'
7
+ import type { GlobalFlags } from '../args'
8
+ import { extractFlag } from '../args'
9
+ import { loadContext } from '../context'
10
+ import { printCommandHelp } from '../help'
11
+ import { loadKey, resolvePassword } from '../keys'
12
+ import { fatal, formatLabel, formatValue, output } from '../output'
13
+
14
+ export async function handleTokensCommand(
15
+ args: string[],
16
+ opts: GlobalFlags,
17
+ ): Promise<void> {
18
+ const [subcommand, ...rest] = args
19
+
20
+ switch (subcommand) {
21
+ case 'balances':
22
+ return tokenBalances(rest, opts)
23
+ case 'list':
24
+ return tokenList(rest, opts)
25
+ case 'send':
26
+ return tokenSend(rest, opts)
27
+ case 'deploy':
28
+ return tokenDeploy(rest, opts)
29
+ case 'buy':
30
+ return tokenBuy(rest, opts)
31
+ default:
32
+ printCommandHelp('tokens', {
33
+ balances: 'Show token balances by token ID',
34
+ list: 'List owned token UTXOs (--token-id <id>)',
35
+ send: 'Transfer tokens (--token-id <id> --to <addr> --amount <n>)',
36
+ deploy: 'Deploy a new BSV21 token (--symbol <sym> --amount <n>)',
37
+ buy: 'Purchase listed tokens (--outpoint <op>)',
38
+ })
39
+ if (subcommand && subcommand !== 'help') {
40
+ process.exit(1)
41
+ }
42
+ }
43
+ }
44
+
45
+ async function tokenBalances(
46
+ _args: string[],
47
+ opts: GlobalFlags,
48
+ ): Promise<void> {
49
+ const privateKey = await loadKey(resolvePassword())
50
+ const { ctx, destroy } = await loadContext(privateKey, {
51
+ chain: opts.chain,
52
+ })
53
+
54
+ try {
55
+ const balances = await getBsv21Balances.execute(ctx, {})
56
+
57
+ if (opts.json) {
58
+ output(balances, opts)
59
+ return
60
+ }
61
+
62
+ if (balances.length === 0) {
63
+ output('No token balances found.', opts)
64
+ return
65
+ }
66
+
67
+ for (const b of balances) {
68
+ const symbol = b.sym ?? b.id.slice(0, 12)
69
+ console.log(
70
+ ` ${formatValue(symbol)} ${formatLabel('amount:')} ${formatValue(b.amt)} ${formatLabel('id:')} ${b.id} ${formatLabel('dec:')} ${b.dec}`,
71
+ )
72
+ }
73
+ } finally {
74
+ await destroy()
75
+ }
76
+ }
77
+
78
+ async function tokenList(args: string[], opts: GlobalFlags): Promise<void> {
79
+ const tokenId = extractFlag(args, '--token-id')
80
+
81
+ const privateKey = await loadKey(resolvePassword())
82
+ const { ctx, destroy } = await loadContext(privateKey, {
83
+ chain: opts.chain,
84
+ })
85
+
86
+ try {
87
+ const outputs = await listTokens.execute(ctx, { limit: 10000 })
88
+
89
+ const filtered = tokenId
90
+ ? outputs.filter((o) => {
91
+ const idTag = o.tags?.find((t) => t.startsWith('id:'))
92
+ return idTag && idTag.slice(3) === tokenId
93
+ })
94
+ : outputs
95
+
96
+ if (opts.json) {
97
+ output(filtered, opts)
98
+ return
99
+ }
100
+
101
+ if (filtered.length === 0) {
102
+ output(
103
+ tokenId
104
+ ? `No token UTXOs found for token ${tokenId}.`
105
+ : 'No token UTXOs found.',
106
+ opts,
107
+ )
108
+ return
109
+ }
110
+
111
+ for (const o of filtered) {
112
+ const idTag =
113
+ o.tags?.find((t) => t.startsWith('id:'))?.slice(3) ?? 'unknown'
114
+ const amtTag = o.tags?.find((t) => t.startsWith('amt:'))?.slice(4) ?? '0'
115
+ const symTag = o.tags?.find((t) => t.startsWith('sym:'))?.slice(4) ?? ''
116
+
117
+ console.log(
118
+ ` ${formatValue(o.outpoint)} ${formatLabel(symTag || idTag.slice(0, 12))} ${formatLabel('amt:')} ${formatValue(amtTag)}`,
119
+ )
120
+ }
121
+
122
+ console.log(`\n ${filtered.length} token UTXO(s) found.`)
123
+ } finally {
124
+ await destroy()
125
+ }
126
+ }
127
+
128
+ async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
129
+ const tokenId = extractFlag(args, '--token-id')
130
+ const to = extractFlag(args, '--to')
131
+ const amountStr = extractFlag(args, '--amount')
132
+
133
+ if (!tokenId) fatal('Missing --token-id <id>')
134
+ if (!to) fatal('Missing --to <address>')
135
+ if (!amountStr) fatal('Missing --amount <number>')
136
+
137
+ const amount = BigInt(amountStr)
138
+ if (amount <= 0n) {
139
+ fatal('--amount must be a positive number')
140
+ }
141
+
142
+ if (!opts.yes) {
143
+ const ok = await confirm({
144
+ message: `Send ${amountStr} tokens (${tokenId.slice(0, 12)}...) to ${to}?`,
145
+ })
146
+ if (isCancel(ok) || !ok) {
147
+ fatal('Token send cancelled.')
148
+ }
149
+ }
150
+
151
+ const privateKey = await loadKey(resolvePassword())
152
+ const { ctx, destroy } = await loadContext(privateKey, {
153
+ chain: opts.chain,
154
+ })
155
+
156
+ try {
157
+ const result = await sendBsv21.execute(ctx, {
158
+ tokenId,
159
+ amount: amountStr,
160
+ address: to,
161
+ })
162
+
163
+ if (result.error) {
164
+ fatal(result.error)
165
+ }
166
+
167
+ output(opts.json ? result : { txid: result.txid }, opts)
168
+ } finally {
169
+ await destroy()
170
+ }
171
+ }
172
+
173
+ async function tokenDeploy(args: string[], _opts: GlobalFlags): Promise<void> {
174
+ const symbol = extractFlag(args, '--symbol')
175
+ const amountStr = extractFlag(args, '--amount')
176
+ const _icon = extractFlag(args, '--icon')
177
+
178
+ if (!symbol) fatal('Missing --symbol <sym>')
179
+ if (!amountStr) fatal('Missing --amount <initial-supply>')
180
+
181
+ // TODO: Call deployToken action
182
+ // TODO: Output txid and token origin
183
+ fatal('tokens deploy is not yet implemented')
184
+ }
185
+
186
+ async function tokenBuy(args: string[], _opts: GlobalFlags): Promise<void> {
187
+ const outpoint = extractFlag(args, '--outpoint')
188
+
189
+ if (!outpoint) fatal('Missing --outpoint <txid.vout>')
190
+
191
+ // TODO: Call purchaseTokenListing action
192
+ // TODO: Output txid
193
+ fatal('tokens buy is not yet implemented')
194
+ }