@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.
- package/bin/1sat +0 -0
- package/package.json +32 -0
- package/src/args.ts +100 -0
- package/src/cli.ts +105 -0
- package/src/commands/action.ts +79 -0
- package/src/commands/config.ts +109 -0
- package/src/commands/identity.ts +133 -0
- package/src/commands/init.ts +172 -0
- package/src/commands/locks.ts +126 -0
- package/src/commands/opns.ts +73 -0
- package/src/commands/ordinals.ts +355 -0
- package/src/commands/social.ts +56 -0
- package/src/commands/sweep.ts +54 -0
- package/src/commands/tokens.ts +194 -0
- package/src/commands/tx.ts +65 -0
- package/src/commands/wallet.ts +218 -0
- package/src/config.ts +97 -0
- package/src/context.ts +63 -0
- package/src/help.ts +117 -0
- package/src/keys.ts +88 -0
- package/src/output.ts +82 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive wallet setup wizard.
|
|
3
|
+
*
|
|
4
|
+
* Guides the user through:
|
|
5
|
+
* 1. Network selection (mainnet/testnet)
|
|
6
|
+
* 2. Key generation or import
|
|
7
|
+
* 3. Password-protected key encryption
|
|
8
|
+
* 4. Config file creation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { PrivateKey } from '@bsv/sdk'
|
|
12
|
+
import {
|
|
13
|
+
cancel,
|
|
14
|
+
confirm,
|
|
15
|
+
intro,
|
|
16
|
+
isCancel,
|
|
17
|
+
outro,
|
|
18
|
+
password,
|
|
19
|
+
select,
|
|
20
|
+
text,
|
|
21
|
+
} from '@clack/prompts'
|
|
22
|
+
import type { GlobalFlags } from '../args'
|
|
23
|
+
import { ensureConfigDir, loadConfig, saveConfig } from '../config'
|
|
24
|
+
import { hasKey, saveKey } from '../keys'
|
|
25
|
+
import { fatal, formatSuccess, formatValue, formatWarning } from '../output'
|
|
26
|
+
|
|
27
|
+
export async function handleInitCommand(
|
|
28
|
+
_args: string[],
|
|
29
|
+
opts: GlobalFlags,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
fatal('init command requires interactive mode (remove --json)')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
intro('1sat wallet setup')
|
|
36
|
+
|
|
37
|
+
// Warn if key already exists
|
|
38
|
+
if (hasKey()) {
|
|
39
|
+
const overwrite = await confirm({
|
|
40
|
+
message: 'A wallet key already exists. Overwrite it?',
|
|
41
|
+
})
|
|
42
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
43
|
+
cancel('Setup cancelled.')
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 1. Network selection
|
|
49
|
+
const chain = await select({
|
|
50
|
+
message: 'Select network:',
|
|
51
|
+
options: [
|
|
52
|
+
{ value: 'main', label: 'Mainnet', hint: 'real BSV' },
|
|
53
|
+
{ value: 'test', label: 'Testnet', hint: 'test BSV' },
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
if (isCancel(chain)) {
|
|
57
|
+
cancel('Setup cancelled.')
|
|
58
|
+
process.exit(0)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Key source
|
|
62
|
+
const keySource = await select({
|
|
63
|
+
message: 'How would you like to set up your key?',
|
|
64
|
+
options: [
|
|
65
|
+
{
|
|
66
|
+
value: 'generate',
|
|
67
|
+
label: 'Generate new key',
|
|
68
|
+
hint: 'creates a new random private key',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
value: 'import',
|
|
72
|
+
label: 'Import existing key',
|
|
73
|
+
hint: 'enter a WIF-encoded private key',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
})
|
|
77
|
+
if (isCancel(keySource)) {
|
|
78
|
+
cancel('Setup cancelled.')
|
|
79
|
+
process.exit(0)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let wif: string
|
|
83
|
+
|
|
84
|
+
if (keySource === 'generate') {
|
|
85
|
+
const pk = PrivateKey.fromRandom()
|
|
86
|
+
wif = pk.toWif()
|
|
87
|
+
const address = pk.toPublicKey().toAddress()
|
|
88
|
+
|
|
89
|
+
console.log()
|
|
90
|
+
console.log(formatWarning(' IMPORTANT: Back up your private key!'))
|
|
91
|
+
console.log(formatWarning(' If you lose it, your funds are gone forever.'))
|
|
92
|
+
console.log()
|
|
93
|
+
console.log(` ${formatValue('WIF:')} ${wif}`)
|
|
94
|
+
console.log(` ${formatValue('Address:')} ${address}`)
|
|
95
|
+
console.log()
|
|
96
|
+
|
|
97
|
+
const confirmed = await confirm({
|
|
98
|
+
message: 'Have you saved your private key somewhere safe?',
|
|
99
|
+
})
|
|
100
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
101
|
+
cancel('Setup cancelled. Please save your key and try again.')
|
|
102
|
+
process.exit(0)
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const wifInput = await text({
|
|
106
|
+
message: 'Enter your WIF-encoded private key:',
|
|
107
|
+
validate(value) {
|
|
108
|
+
try {
|
|
109
|
+
PrivateKey.fromWif(value)
|
|
110
|
+
} catch {
|
|
111
|
+
return 'Invalid WIF key. Keys start with 5, K, L (mainnet) or c (testnet).'
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
if (isCancel(wifInput)) {
|
|
116
|
+
cancel('Setup cancelled.')
|
|
117
|
+
process.exit(0)
|
|
118
|
+
}
|
|
119
|
+
wif = wifInput as string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3. Password for encryption
|
|
123
|
+
const pw = await password({
|
|
124
|
+
message: 'Set a password to encrypt your key:',
|
|
125
|
+
validate(value) {
|
|
126
|
+
if (value.length < 8) return 'Password must be at least 8 characters.'
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
if (isCancel(pw)) {
|
|
130
|
+
cancel('Setup cancelled.')
|
|
131
|
+
process.exit(0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const pwConfirm = await password({
|
|
135
|
+
message: 'Confirm password:',
|
|
136
|
+
})
|
|
137
|
+
if (isCancel(pwConfirm)) {
|
|
138
|
+
cancel('Setup cancelled.')
|
|
139
|
+
process.exit(0)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (pw !== pwConfirm) {
|
|
143
|
+
fatal('Passwords do not match.')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 4. Optional: storage identity key
|
|
147
|
+
const storageId = await text({
|
|
148
|
+
message: 'Storage identity key (for wallet persistence):',
|
|
149
|
+
defaultValue: '1sat-cli-default',
|
|
150
|
+
placeholder: '1sat-cli-default',
|
|
151
|
+
})
|
|
152
|
+
if (isCancel(storageId)) {
|
|
153
|
+
cancel('Setup cancelled.')
|
|
154
|
+
process.exit(0)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 5. Save everything
|
|
158
|
+
ensureConfigDir()
|
|
159
|
+
|
|
160
|
+
await saveKey(wif, pw as string)
|
|
161
|
+
|
|
162
|
+
saveConfig({
|
|
163
|
+
...loadConfig(),
|
|
164
|
+
chain: chain as 'main' | 'test',
|
|
165
|
+
storageIdentityKey: storageId as string,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const pk = PrivateKey.fromWif(wif)
|
|
169
|
+
const address = pk.toPublicKey().toAddress()
|
|
170
|
+
|
|
171
|
+
outro(formatSuccess(`Wallet configured! Address: ${address}`))
|
|
172
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock commands - info, lock, unlock.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getLockData, lockBsv, unlockBsv } 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, output, printKeyValue } from '../output'
|
|
13
|
+
|
|
14
|
+
export async function handleLocksCommand(
|
|
15
|
+
args: string[],
|
|
16
|
+
opts: GlobalFlags,
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const [subcommand, ...rest] = args
|
|
19
|
+
|
|
20
|
+
switch (subcommand) {
|
|
21
|
+
case 'info':
|
|
22
|
+
return locksInfo(rest, opts)
|
|
23
|
+
case 'lock':
|
|
24
|
+
return locksLock(rest, opts)
|
|
25
|
+
case 'unlock':
|
|
26
|
+
return locksUnlock(rest, opts)
|
|
27
|
+
default:
|
|
28
|
+
printCommandHelp('locks', {
|
|
29
|
+
info: 'Show lock information and maturity status',
|
|
30
|
+
lock: 'Time-lock BSV (--sats <amount> --blocks <n>)',
|
|
31
|
+
unlock: 'Unlock matured BSV locks',
|
|
32
|
+
})
|
|
33
|
+
if (subcommand && subcommand !== 'help') {
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function locksInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
40
|
+
const privateKey = await loadKey(resolvePassword())
|
|
41
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
42
|
+
chain: opts.chain,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const data = await getLockData.execute(ctx, {})
|
|
47
|
+
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
output(data, opts)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
printKeyValue({
|
|
54
|
+
'Total Locked (sats)': data.totalLocked,
|
|
55
|
+
'Unlockable (sats)': data.unlockable,
|
|
56
|
+
'Next Unlock Block': data.nextUnlock || 'none',
|
|
57
|
+
})
|
|
58
|
+
} finally {
|
|
59
|
+
await destroy()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
|
|
64
|
+
const satsStr = extractFlag(args, '--sats')
|
|
65
|
+
const blocksStr = extractFlag(args, '--blocks')
|
|
66
|
+
|
|
67
|
+
if (!satsStr) fatal('Missing --sats <amount>')
|
|
68
|
+
if (!blocksStr) fatal('Missing --blocks <n>')
|
|
69
|
+
|
|
70
|
+
const satoshis = Number(satsStr)
|
|
71
|
+
if (!Number.isFinite(satoshis) || satoshis <= 0) {
|
|
72
|
+
fatal('--sats must be a positive number')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const until = Number(blocksStr)
|
|
76
|
+
if (!Number.isFinite(until) || until <= 0) {
|
|
77
|
+
fatal('--blocks must be a positive block height')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!opts.yes) {
|
|
81
|
+
const ok = await confirm({
|
|
82
|
+
message: `Lock ${satoshis} satoshis until block ${until}?`,
|
|
83
|
+
})
|
|
84
|
+
if (isCancel(ok) || !ok) {
|
|
85
|
+
fatal('Lock cancelled.')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const privateKey = await loadKey(resolvePassword())
|
|
90
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
91
|
+
chain: opts.chain,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await lockBsv.execute(ctx, {
|
|
96
|
+
requests: [{ satoshis, until }],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (result.error) {
|
|
100
|
+
fatal(result.error)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
output(opts.json ? result : { txid: result.txid }, opts)
|
|
104
|
+
} finally {
|
|
105
|
+
await destroy()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function locksUnlock(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
110
|
+
const privateKey = await loadKey(resolvePassword())
|
|
111
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
112
|
+
chain: opts.chain,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const result = await unlockBsv.execute(ctx, {})
|
|
117
|
+
|
|
118
|
+
if (result.error) {
|
|
119
|
+
fatal(result.error)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
output(opts.json ? result : { txid: result.txid }, opts)
|
|
123
|
+
} finally {
|
|
124
|
+
await destroy()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpNS commands - register, deregister, lookup.
|
|
3
|
+
*
|
|
4
|
+
* Manage OpNS name identity bindings.
|
|
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 handleOpnsCommand(
|
|
13
|
+
args: string[],
|
|
14
|
+
opts: GlobalFlags,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const [subcommand, ...rest] = args
|
|
17
|
+
|
|
18
|
+
switch (subcommand) {
|
|
19
|
+
case 'register':
|
|
20
|
+
return opnsRegister(rest, opts)
|
|
21
|
+
case 'deregister':
|
|
22
|
+
return opnsDeregister(rest, opts)
|
|
23
|
+
case 'lookup':
|
|
24
|
+
return opnsLookup(rest, opts)
|
|
25
|
+
default:
|
|
26
|
+
printCommandHelp('opns', {
|
|
27
|
+
register: 'Register identity on an OpNS name (--outpoint <op>)',
|
|
28
|
+
deregister: 'Deregister identity from an OpNS name (--outpoint <op>)',
|
|
29
|
+
lookup: 'Look up an OpNS name (--name <name>)',
|
|
30
|
+
})
|
|
31
|
+
if (subcommand && subcommand !== 'help') {
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function opnsRegister(args: string[], _opts: GlobalFlags): Promise<void> {
|
|
38
|
+
const outpoint = extractFlag(args, '--outpoint')
|
|
39
|
+
|
|
40
|
+
if (!outpoint) fatal('Missing --outpoint <txid.vout>')
|
|
41
|
+
|
|
42
|
+
// TODO: Load context via loadContext() + loadKey()
|
|
43
|
+
// TODO: Look up OpNS ordinal from wallet by outpoint
|
|
44
|
+
// TODO: Call opnsRegister.execute(ctx, { ordinal })
|
|
45
|
+
// TODO: Output txid
|
|
46
|
+
fatal('opns register is not yet implemented')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function opnsDeregister(
|
|
50
|
+
args: string[],
|
|
51
|
+
_opts: GlobalFlags,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const outpoint = extractFlag(args, '--outpoint')
|
|
54
|
+
|
|
55
|
+
if (!outpoint) fatal('Missing --outpoint <txid.vout>')
|
|
56
|
+
|
|
57
|
+
// TODO: Load context via loadContext() + loadKey()
|
|
58
|
+
// TODO: Look up OpNS ordinal from wallet by outpoint
|
|
59
|
+
// TODO: Call opnsDeregister.execute(ctx, { ordinal })
|
|
60
|
+
// TODO: Output txid
|
|
61
|
+
fatal('opns deregister is not yet implemented')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function opnsLookup(args: string[], _opts: GlobalFlags): Promise<void> {
|
|
65
|
+
const name = extractFlag(args, '--name')
|
|
66
|
+
|
|
67
|
+
if (!name) fatal('Missing --name <name>')
|
|
68
|
+
|
|
69
|
+
// TODO: Load context via loadContext() + loadKey()
|
|
70
|
+
// TODO: Use services.ordfs to look up OpNS name resolution
|
|
71
|
+
// TODO: Output resolved identity info
|
|
72
|
+
fatal('opns lookup is not yet implemented')
|
|
73
|
+
}
|