@1sat/cli 0.0.44 → 0.0.46
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 +6 -6
- package/src/commands/action.ts +2 -2
- package/src/commands/identity.ts +19 -7
- package/src/commands/init.ts +11 -64
- package/src/commands/locks.ts +4 -4
- package/src/commands/opns.ts +4 -4
- package/src/commands/ordinals.ts +36 -9
- package/src/commands/remote.ts +10 -10
- package/src/commands/serve.ts +2 -2
- package/src/commands/social.ts +21 -5
- package/src/commands/sweep.ts +3 -3
- package/src/commands/tokens.ts +5 -5
- package/src/commands/wallet.ts +95 -39
- package/src/keys.ts +24 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1sat/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"description": "CLI for 1Sat Ordinals SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/cli.ts",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@1sat/actions": "0.0.
|
|
29
|
-
"@1sat/client": "0.0.
|
|
28
|
+
"@1sat/actions": "0.0.112",
|
|
29
|
+
"@1sat/client": "0.0.25",
|
|
30
30
|
"@1sat/types": "0.0.18",
|
|
31
|
-
"@1sat/wallet-node": "0.0.
|
|
32
|
-
"@1sat/wallet-server": "0.0.
|
|
31
|
+
"@1sat/wallet-node": "0.0.40",
|
|
32
|
+
"@1sat/wallet-server": "0.0.10",
|
|
33
33
|
"@bsv/sdk": "^2.0.13",
|
|
34
|
-
"@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.
|
|
34
|
+
"@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@2.1.21-parity-fix.2",
|
|
35
35
|
"chalk": "^5.0.0",
|
|
36
36
|
"@clack/prompts": "^0.8.0",
|
|
37
37
|
"bitcoin-backup": "^0.0.11",
|
package/src/commands/action.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { actionRegistry } from '@1sat/actions'
|
|
9
9
|
import type { GlobalFlags } from '../args'
|
|
10
10
|
import { loadContext } from '../context'
|
|
11
|
-
import { loadKey
|
|
11
|
+
import { loadKey } from '../keys'
|
|
12
12
|
import { fatal, output } from '../output'
|
|
13
13
|
|
|
14
14
|
export async function handleActionCommand(
|
|
@@ -65,7 +65,7 @@ export async function handleActionCommand(
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const privateKey = await loadKey(
|
|
68
|
+
const privateKey = await loadKey()
|
|
69
69
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
70
70
|
chain: opts.chain,
|
|
71
71
|
})
|
package/src/commands/identity.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { GlobalFlags } from '../args'
|
|
|
9
9
|
import { extractFlag } from '../args'
|
|
10
10
|
import { loadContext } from '../context'
|
|
11
11
|
import { printCommandHelp } from '../help'
|
|
12
|
-
import { loadKey
|
|
12
|
+
import { loadKey } from '../keys'
|
|
13
13
|
import { fatal, output, printKeyValue } from '../output'
|
|
14
14
|
|
|
15
15
|
export async function handleIdentityCommand(
|
|
@@ -34,7 +34,7 @@ export async function handleIdentityCommand(
|
|
|
34
34
|
create: 'Create/publish a BAP identity',
|
|
35
35
|
'update-profile': 'Update BAP identity profile (--profile <json>)',
|
|
36
36
|
info: 'Show BAP identity information',
|
|
37
|
-
sign: 'Sign a message with identity key (--message <text>)',
|
|
37
|
+
sign: 'Sign a message with identity key (--message <text> [--encoding <utf8|hex|base64>])',
|
|
38
38
|
verify:
|
|
39
39
|
'Verify a signed message (--message <text> --sig <sig> --address <addr>)',
|
|
40
40
|
})
|
|
@@ -48,7 +48,7 @@ async function identityCreate(
|
|
|
48
48
|
_args: string[],
|
|
49
49
|
opts: GlobalFlags,
|
|
50
50
|
): Promise<void> {
|
|
51
|
-
const privateKey = await loadKey(
|
|
51
|
+
const privateKey = await loadKey()
|
|
52
52
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
53
53
|
chain: opts.chain,
|
|
54
54
|
})
|
|
@@ -67,7 +67,7 @@ async function identityCreate(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async function identityInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
70
|
-
const privateKey = await loadKey(
|
|
70
|
+
const privateKey = await loadKey()
|
|
71
71
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
72
72
|
chain: opts.chain,
|
|
73
73
|
})
|
|
@@ -92,16 +92,28 @@ async function identityInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
92
92
|
|
|
93
93
|
async function identitySign(args: string[], opts: GlobalFlags): Promise<void> {
|
|
94
94
|
const message = extractFlag(args, '--message')
|
|
95
|
+
const encoding = extractFlag(args, '--encoding')
|
|
95
96
|
|
|
96
97
|
if (!message) fatal('Missing --message <text>')
|
|
98
|
+
if (
|
|
99
|
+
encoding !== undefined &&
|
|
100
|
+
encoding !== 'utf8' &&
|
|
101
|
+
encoding !== 'hex' &&
|
|
102
|
+
encoding !== 'base64'
|
|
103
|
+
) {
|
|
104
|
+
fatal('--encoding must be one of: utf8, hex, base64')
|
|
105
|
+
}
|
|
97
106
|
|
|
98
|
-
const privateKey = await loadKey(
|
|
107
|
+
const privateKey = await loadKey()
|
|
99
108
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
100
109
|
chain: opts.chain,
|
|
101
110
|
})
|
|
102
111
|
|
|
103
112
|
try {
|
|
104
|
-
const result = await signBsm.execute(ctx, {
|
|
113
|
+
const result = await signBsm.execute(ctx, {
|
|
114
|
+
message,
|
|
115
|
+
...(encoding ? { encoding: encoding as 'utf8' | 'hex' | 'base64' } : {}),
|
|
116
|
+
})
|
|
105
117
|
|
|
106
118
|
if (result.error) {
|
|
107
119
|
fatal(result.error)
|
|
@@ -147,7 +159,7 @@ async function identityUpdateProfile(
|
|
|
147
159
|
fatal(`Invalid JSON in --profile: ${profileStr}`)
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
const privateKey = await loadKey(
|
|
162
|
+
const privateKey = await loadKey()
|
|
151
163
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
152
164
|
chain: opts.chain,
|
|
153
165
|
})
|
package/src/commands/init.ts
CHANGED
|
@@ -18,11 +18,10 @@ import {
|
|
|
18
18
|
outro,
|
|
19
19
|
password,
|
|
20
20
|
select,
|
|
21
|
-
text,
|
|
22
21
|
} from '@clack/prompts'
|
|
23
22
|
import type { GlobalFlags } from '../args'
|
|
24
23
|
import { ensureConfigDir, loadConfig, saveConfig } from '../config'
|
|
25
|
-
import {
|
|
24
|
+
import { hasKey, saveKey } from '../keys'
|
|
26
25
|
import { fatal, formatSuccess, formatValue, formatWarning } from '../output'
|
|
27
26
|
|
|
28
27
|
export async function handleInitCommand(
|
|
@@ -144,82 +143,30 @@ export async function handleInitCommand(
|
|
|
144
143
|
fatal('Passwords do not match.')
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
// 3.5. Touch ID protection (macOS arm64)
|
|
148
|
-
let useTouchID = false
|
|
149
|
-
try {
|
|
150
|
-
const { isTouchIDAvailable } = await import('bitcoin-backup')
|
|
151
|
-
if (isTouchIDAvailable()) {
|
|
152
|
-
const enableTouchID = await confirm({
|
|
153
|
-
message:
|
|
154
|
-
'Enable Touch ID? (unlock your wallet without typing a password)',
|
|
155
|
-
})
|
|
156
|
-
if (isCancel(enableTouchID)) {
|
|
157
|
-
cancel('Setup cancelled.')
|
|
158
|
-
process.exit(0)
|
|
159
|
-
}
|
|
160
|
-
useTouchID = enableTouchID as boolean
|
|
161
|
-
}
|
|
162
|
-
} catch {
|
|
163
|
-
// bitcoin-backup Touch ID not available — skip silently
|
|
164
|
-
}
|
|
165
|
-
|
|
166
146
|
// 4. Generate random storage identity key
|
|
167
147
|
const storageId = `1sat-cli-${randomBytes(8).toString('hex')}`
|
|
168
148
|
|
|
169
|
-
// 5.
|
|
170
|
-
const useRemote = await confirm({
|
|
171
|
-
message: 'Configure remote storage? (remote is active, local is backup)',
|
|
172
|
-
defaultValue: false,
|
|
173
|
-
})
|
|
174
|
-
let activeRemote: string | undefined
|
|
175
|
-
|
|
176
|
-
if (useRemote) {
|
|
177
|
-
const url = await text({
|
|
178
|
-
message: 'Primary remote storage URL:',
|
|
179
|
-
validate(value) {
|
|
180
|
-
if (!value) return 'Required'
|
|
181
|
-
try {
|
|
182
|
-
new URL(value)
|
|
183
|
-
} catch {
|
|
184
|
-
return 'Invalid URL'
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
})
|
|
188
|
-
if (isCancel(url)) {
|
|
189
|
-
cancel('Setup cancelled.')
|
|
190
|
-
process.exit(0)
|
|
191
|
-
}
|
|
192
|
-
activeRemote = url as string
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 6. Save everything
|
|
149
|
+
// 5. Save everything
|
|
196
150
|
ensureConfigDir()
|
|
197
151
|
|
|
198
152
|
await saveKey(wif, pw as string)
|
|
199
153
|
|
|
200
|
-
// Cache password with Touch ID if user opted in
|
|
201
|
-
if (useTouchID) {
|
|
202
|
-
try {
|
|
203
|
-
await cacheKeyPassword(pw as string)
|
|
204
|
-
} catch {
|
|
205
|
-
console.log(
|
|
206
|
-
formatWarning(
|
|
207
|
-
' Touch ID caching failed. You can enable it later with "1sat touchid enable".',
|
|
208
|
-
),
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
154
|
saveConfig({
|
|
214
155
|
...loadConfig(),
|
|
215
156
|
chain: chain as 'main' | 'test',
|
|
216
157
|
storageIdentityKey: storageId,
|
|
217
|
-
activeRemote,
|
|
218
158
|
})
|
|
219
159
|
|
|
220
160
|
const pk = PrivateKey.fromWif(wif)
|
|
221
161
|
const address = pk.toPublicKey().toAddress()
|
|
222
162
|
|
|
223
|
-
|
|
224
|
-
|
|
163
|
+
outro(formatSuccess(`Wallet configured! Address: ${address}`))
|
|
164
|
+
|
|
165
|
+
console.log()
|
|
166
|
+
console.log(
|
|
167
|
+
` Want hosted storage? Run ${formatValue('1sat remote add <url>')} to attach a backup remote,`,
|
|
168
|
+
)
|
|
169
|
+
console.log(
|
|
170
|
+
` then ${formatValue('1sat remote set-active <url>')} to make it primary.`,
|
|
171
|
+
)
|
|
225
172
|
}
|
package/src/commands/locks.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { GlobalFlags } from '../args'
|
|
|
8
8
|
import { extractFlag } from '../args'
|
|
9
9
|
import { loadContext } from '../context'
|
|
10
10
|
import { printCommandHelp } from '../help'
|
|
11
|
-
import { loadKey
|
|
11
|
+
import { loadKey } from '../keys'
|
|
12
12
|
import { fatal, output, printKeyValue } from '../output'
|
|
13
13
|
|
|
14
14
|
export async function handleLocksCommand(
|
|
@@ -37,7 +37,7 @@ export async function handleLocksCommand(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async function locksInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
40
|
-
const privateKey = await loadKey(
|
|
40
|
+
const privateKey = await loadKey()
|
|
41
41
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
42
42
|
chain: opts.chain,
|
|
43
43
|
})
|
|
@@ -86,7 +86,7 @@ async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
const privateKey = await loadKey(
|
|
89
|
+
const privateKey = await loadKey()
|
|
90
90
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
91
91
|
chain: opts.chain,
|
|
92
92
|
})
|
|
@@ -107,7 +107,7 @@ async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
async function locksUnlock(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
110
|
-
const privateKey = await loadKey(
|
|
110
|
+
const privateKey = await loadKey()
|
|
111
111
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
112
112
|
chain: opts.chain,
|
|
113
113
|
})
|
package/src/commands/opns.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { GlobalFlags } from '../args'
|
|
|
15
15
|
import { extractFlag } from '../args'
|
|
16
16
|
import { loadContext } from '../context'
|
|
17
17
|
import { printCommandHelp } from '../help'
|
|
18
|
-
import { loadKey
|
|
18
|
+
import { loadKey } from '../keys'
|
|
19
19
|
import { fatal, formatLabel, formatValue, output } from '../output'
|
|
20
20
|
|
|
21
21
|
export async function handleOpnsCommand(
|
|
@@ -57,7 +57,7 @@ async function opnsRegister(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const privateKey = await loadKey(
|
|
60
|
+
const privateKey = await loadKey()
|
|
61
61
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
62
62
|
chain: opts.chain,
|
|
63
63
|
})
|
|
@@ -102,7 +102,7 @@ async function opnsDeregister(
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const privateKey = await loadKey(
|
|
105
|
+
const privateKey = await loadKey()
|
|
106
106
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
107
107
|
chain: opts.chain,
|
|
108
108
|
})
|
|
@@ -131,7 +131,7 @@ async function opnsDeregister(
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
async function opnsLookup(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
134
|
-
const privateKey = await loadKey(
|
|
134
|
+
const privateKey = await loadKey()
|
|
135
135
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
136
136
|
chain: opts.chain,
|
|
137
137
|
})
|
package/src/commands/ordinals.ts
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
import { Utils } from '@bsv/sdk'
|
|
18
18
|
import { confirm, isCancel } from '@clack/prompts'
|
|
19
19
|
import type { GlobalFlags } from '../args'
|
|
20
|
-
import { extractFlag } from '../args'
|
|
20
|
+
import { extractFlag, hasFlag } from '../args'
|
|
21
21
|
import { loadContext } from '../context'
|
|
22
22
|
import { printCommandHelp } from '../help'
|
|
23
|
-
import { loadKey
|
|
23
|
+
import { loadKey } from '../keys'
|
|
24
24
|
import { fatal, formatLabel, formatValue, output } from '../output'
|
|
25
25
|
|
|
26
26
|
export async function handleOrdinalsCommand(
|
|
@@ -45,7 +45,7 @@ export async function handleOrdinalsCommand(
|
|
|
45
45
|
default:
|
|
46
46
|
printCommandHelp('ordinals', {
|
|
47
47
|
list: 'List owned ordinals/inscriptions',
|
|
48
|
-
mint: 'Mint a new ordinal inscription (--file <path> --type <mime>)',
|
|
48
|
+
mint: 'Mint a new ordinal inscription (--file <path> [--type <mime>] [--map <json>] [--sign-with-bap])',
|
|
49
49
|
transfer: 'Transfer an ordinal (--outpoint <op> --to <addr>)',
|
|
50
50
|
sell: 'List an ordinal for sale (--outpoint <op> --price <sats>)',
|
|
51
51
|
cancel: 'Cancel an ordinal listing (--outpoint <op>)',
|
|
@@ -58,7 +58,7 @@ export async function handleOrdinalsCommand(
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async function ordinalsList(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
61
|
-
const privateKey = await loadKey(
|
|
61
|
+
const privateKey = await loadKey()
|
|
62
62
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
63
63
|
chain: opts.chain,
|
|
64
64
|
})
|
|
@@ -121,6 +121,8 @@ const MIME_TYPES: Record<string, string> = {
|
|
|
121
121
|
async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
|
|
122
122
|
const file = extractFlag(args, '--file')
|
|
123
123
|
const type = extractFlag(args, '--type')
|
|
124
|
+
const mapStr = extractFlag(args, '--map')
|
|
125
|
+
const signWithBAP = hasFlag(args, '--sign-with-bap')
|
|
124
126
|
|
|
125
127
|
if (!file) fatal('Missing --file <path>')
|
|
126
128
|
|
|
@@ -131,6 +133,29 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
131
133
|
)
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
let map: Record<string, string> | undefined
|
|
137
|
+
if (mapStr !== undefined) {
|
|
138
|
+
let parsed: unknown
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(mapStr)
|
|
141
|
+
} catch (err) {
|
|
142
|
+
fatal(`--map must be valid JSON: ${(err as Error).message}`)
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
typeof parsed !== 'object' ||
|
|
146
|
+
parsed === null ||
|
|
147
|
+
Array.isArray(parsed)
|
|
148
|
+
) {
|
|
149
|
+
fatal('--map must be a JSON object')
|
|
150
|
+
}
|
|
151
|
+
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
152
|
+
if (typeof v !== 'string') {
|
|
153
|
+
fatal(`--map values must be strings (key "${k}" is ${typeof v})`)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
map = parsed as Record<string, string>
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
let fileBytes: Uint8Array
|
|
135
160
|
try {
|
|
136
161
|
fileBytes = readFileSync(file)
|
|
@@ -149,7 +174,7 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
176
|
|
|
152
|
-
const privateKey = await loadKey(
|
|
177
|
+
const privateKey = await loadKey()
|
|
153
178
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
154
179
|
chain: opts.chain,
|
|
155
180
|
})
|
|
@@ -158,6 +183,8 @@ async function ordinalsMint(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
158
183
|
const result = await inscribe.execute(ctx, {
|
|
159
184
|
base64Content,
|
|
160
185
|
contentType,
|
|
186
|
+
...(map ? { map } : {}),
|
|
187
|
+
...(signWithBAP ? { signWithBAP: true } : {}),
|
|
161
188
|
})
|
|
162
189
|
|
|
163
190
|
if (result.error) {
|
|
@@ -189,7 +216,7 @@ async function ordinalsTransfer(
|
|
|
189
216
|
}
|
|
190
217
|
}
|
|
191
218
|
|
|
192
|
-
const privateKey = await loadKey(
|
|
219
|
+
const privateKey = await loadKey()
|
|
193
220
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
194
221
|
chain: opts.chain,
|
|
195
222
|
})
|
|
@@ -238,7 +265,7 @@ async function ordinalsSell(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
238
265
|
}
|
|
239
266
|
}
|
|
240
267
|
|
|
241
|
-
const privateKey = await loadKey(
|
|
268
|
+
const privateKey = await loadKey()
|
|
242
269
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
243
270
|
chain: opts.chain,
|
|
244
271
|
})
|
|
@@ -295,7 +322,7 @@ async function ordinalsCancel(
|
|
|
295
322
|
}
|
|
296
323
|
}
|
|
297
324
|
|
|
298
|
-
const privateKey = await loadKey(
|
|
325
|
+
const privateKey = await loadKey()
|
|
299
326
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
300
327
|
chain: opts.chain,
|
|
301
328
|
})
|
|
@@ -337,7 +364,7 @@ async function ordinalsBuy(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
337
364
|
}
|
|
338
365
|
}
|
|
339
366
|
|
|
340
|
-
const privateKey = await loadKey(
|
|
367
|
+
const privateKey = await loadKey()
|
|
341
368
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
342
369
|
chain: opts.chain,
|
|
343
370
|
})
|
package/src/commands/remote.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { GlobalFlags } from '../args'
|
|
|
15
15
|
import { loadConfig, saveConfig } from '../config'
|
|
16
16
|
import { loadContext } from '../context'
|
|
17
17
|
import { printCommandHelp } from '../help'
|
|
18
|
-
import { loadKey
|
|
18
|
+
import { loadKey } from '../keys'
|
|
19
19
|
import { fatal, formatSuccess, formatWarning, output } from '../output'
|
|
20
20
|
|
|
21
21
|
export async function handleRemoteCommand(
|
|
@@ -47,8 +47,7 @@ export async function handleRemoteCommand(
|
|
|
47
47
|
'Switch active storage (1sat remote set-active <url | local>)',
|
|
48
48
|
status:
|
|
49
49
|
'Fetch GET /account/status from a remote (1sat remote status [url])',
|
|
50
|
-
topup:
|
|
51
|
-
'Buy capacity on a remote (1sat remote topup [url] [--units N])',
|
|
50
|
+
topup: 'Buy capacity on a remote (1sat remote topup [url] [--units N])',
|
|
52
51
|
})
|
|
53
52
|
if (subcommand && subcommand !== 'help') {
|
|
54
53
|
process.exit(1)
|
|
@@ -86,7 +85,7 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
const privateKey = await loadKey(
|
|
88
|
+
const privateKey = await loadKey()
|
|
90
89
|
const { walletResult, destroy } = await loadContext(privateKey, {
|
|
91
90
|
chain: opts.chain,
|
|
92
91
|
})
|
|
@@ -133,7 +132,7 @@ async function remoteAdd(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
133
132
|
// ============================================================================
|
|
134
133
|
|
|
135
134
|
async function remoteList(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
136
|
-
const privateKey = await loadKey(
|
|
135
|
+
const privateKey = await loadKey()
|
|
137
136
|
const { destroy } = await loadContext(privateKey, {
|
|
138
137
|
chain: opts.chain,
|
|
139
138
|
})
|
|
@@ -259,7 +258,7 @@ async function remoteSetActive(
|
|
|
259
258
|
fatal('No remote storages configured')
|
|
260
259
|
}
|
|
261
260
|
|
|
262
|
-
const privateKey = await loadKey(
|
|
261
|
+
const privateKey = await loadKey()
|
|
263
262
|
const { walletResult, destroy } = await loadContext(privateKey, {
|
|
264
263
|
chain: opts.chain,
|
|
265
264
|
})
|
|
@@ -299,7 +298,7 @@ async function remoteSetActive(
|
|
|
299
298
|
fatal(`Invalid URL: ${target}`)
|
|
300
299
|
}
|
|
301
300
|
|
|
302
|
-
const privateKey = await loadKey(
|
|
301
|
+
const privateKey = await loadKey()
|
|
303
302
|
const { walletResult, destroy } = await loadContext(privateKey, {
|
|
304
303
|
chain: opts.chain,
|
|
305
304
|
})
|
|
@@ -345,7 +344,7 @@ async function remoteStatus(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
345
344
|
fatal(`Invalid URL: ${url}`)
|
|
346
345
|
}
|
|
347
346
|
|
|
348
|
-
const privateKey = await loadKey(
|
|
347
|
+
const privateKey = await loadKey()
|
|
349
348
|
const { walletResult, destroy } = await loadContext(privateKey, {
|
|
350
349
|
chain: opts.chain,
|
|
351
350
|
})
|
|
@@ -430,7 +429,7 @@ async function remoteTopup(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
430
429
|
fatal(`Invalid URL: ${url}`)
|
|
431
430
|
}
|
|
432
431
|
|
|
433
|
-
const privateKey = await loadKey(
|
|
432
|
+
const privateKey = await loadKey()
|
|
434
433
|
const { walletResult, destroy } = await loadContext(privateKey, {
|
|
435
434
|
chain: opts.chain,
|
|
436
435
|
})
|
|
@@ -452,7 +451,8 @@ async function remoteTopup(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
452
451
|
` ${bold('New capacity:')} ${formatBytes(result.status.capacityBytes)} (${formatBytes(result.status.usedBytes)} used, ${formatBytes(result.status.deficitBytes)} deficit)`,
|
|
453
452
|
)
|
|
454
453
|
if (result.status.paidThroughBlock != null) {
|
|
455
|
-
const remaining =
|
|
454
|
+
const remaining =
|
|
455
|
+
result.status.paidThroughBlock - result.status.currentBlock
|
|
456
456
|
console.log(
|
|
457
457
|
` ${bold('Paid through:')} block ${result.status.paidThroughBlock} (${remaining} blocks left)`,
|
|
458
458
|
)
|
package/src/commands/serve.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
} from '../config'
|
|
32
32
|
import { ensureDataDir } from '../config'
|
|
33
33
|
import { printCommandHelp } from '../help'
|
|
34
|
-
import { loadKey
|
|
34
|
+
import { loadKey } from '../keys'
|
|
35
35
|
import { clearMonitorPid, writeMonitorPid } from '../monitor-lock'
|
|
36
36
|
import { fatal } from '../output'
|
|
37
37
|
|
|
@@ -138,7 +138,7 @@ async function resolveServe(opts: GlobalFlags): Promise<ResolvedServe> {
|
|
|
138
138
|
|
|
139
139
|
let privateKey: PrivateKey
|
|
140
140
|
try {
|
|
141
|
-
privateKey = await loadKey(
|
|
141
|
+
privateKey = await loadKey()
|
|
142
142
|
} catch (err) {
|
|
143
143
|
fatal((err as Error).message)
|
|
144
144
|
}
|
package/src/commands/social.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import { createSocialPost } from '@1sat/actions'
|
|
8
8
|
import type { GlobalFlags } from '../args'
|
|
9
|
-
import { extractFlag } from '../args'
|
|
9
|
+
import { extractFlag, extractFlags } from '../args'
|
|
10
10
|
import { loadContext } from '../context'
|
|
11
11
|
import { printCommandHelp } from '../help'
|
|
12
|
-
import { loadKey
|
|
12
|
+
import { loadKey } from '../keys'
|
|
13
13
|
import { fatal, output } from '../output'
|
|
14
14
|
|
|
15
15
|
export async function handleSocialCommand(
|
|
@@ -23,7 +23,7 @@ export async function handleSocialCommand(
|
|
|
23
23
|
return socialPost(rest, opts)
|
|
24
24
|
default:
|
|
25
25
|
printCommandHelp('social', {
|
|
26
|
-
post: 'Create an on-chain social post (--content <text> --app <name>)',
|
|
26
|
+
post: 'Create an on-chain social post (--content <text> [--app <name>] [--content-type <text/plain|text/markdown>] [--tags <t1,t2>])',
|
|
27
27
|
})
|
|
28
28
|
if (subcommand && subcommand !== 'help') {
|
|
29
29
|
process.exit(1)
|
|
@@ -34,16 +34,32 @@ export async function handleSocialCommand(
|
|
|
34
34
|
async function socialPost(args: string[], opts: GlobalFlags): Promise<void> {
|
|
35
35
|
const content = extractFlag(args, '--content')
|
|
36
36
|
const app = extractFlag(args, '--app') ?? '1sat-cli'
|
|
37
|
+
const contentType = extractFlag(args, '--content-type')
|
|
38
|
+
const tags = extractFlags(args, '--tags')
|
|
37
39
|
|
|
38
40
|
if (!content) fatal('Missing --content <text>')
|
|
41
|
+
if (
|
|
42
|
+
contentType !== undefined &&
|
|
43
|
+
contentType !== 'text/plain' &&
|
|
44
|
+
contentType !== 'text/markdown'
|
|
45
|
+
) {
|
|
46
|
+
fatal('--content-type must be one of: text/plain, text/markdown')
|
|
47
|
+
}
|
|
39
48
|
|
|
40
|
-
const privateKey = await loadKey(
|
|
49
|
+
const privateKey = await loadKey()
|
|
41
50
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
42
51
|
chain: opts.chain,
|
|
43
52
|
})
|
|
44
53
|
|
|
45
54
|
try {
|
|
46
|
-
const result = await createSocialPost.execute(ctx, {
|
|
55
|
+
const result = await createSocialPost.execute(ctx, {
|
|
56
|
+
app,
|
|
57
|
+
content,
|
|
58
|
+
...(contentType
|
|
59
|
+
? { contentType: contentType as 'text/plain' | 'text/markdown' }
|
|
60
|
+
: {}),
|
|
61
|
+
...(tags.length ? { tags } : {}),
|
|
62
|
+
})
|
|
47
63
|
|
|
48
64
|
if (result.error) {
|
|
49
65
|
fatal(result.error)
|
package/src/commands/sweep.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { GlobalFlags } from '../args'
|
|
|
17
17
|
import { extractFlag } from '../args'
|
|
18
18
|
import { loadContext } from '../context'
|
|
19
19
|
import { printCommandHelp } from '../help'
|
|
20
|
-
import { loadKey
|
|
20
|
+
import { loadKey } from '../keys'
|
|
21
21
|
import { fatal, formatLabel, formatValue, output } from '../output'
|
|
22
22
|
|
|
23
23
|
export async function handleSweepCommand(
|
|
@@ -55,7 +55,7 @@ async function sweepScan(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
55
55
|
fatal('Invalid WIF private key')
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const privateKey = await loadKey(
|
|
58
|
+
const privateKey = await loadKey()
|
|
59
59
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
60
60
|
chain: opts.chain,
|
|
61
61
|
})
|
|
@@ -145,7 +145,7 @@ async function sweepImport(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
145
145
|
fatal('Invalid WIF private key')
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const privateKey = await loadKey(
|
|
148
|
+
const privateKey = await loadKey()
|
|
149
149
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
150
150
|
chain: opts.chain,
|
|
151
151
|
})
|
package/src/commands/tokens.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { GlobalFlags } from '../args'
|
|
|
14
14
|
import { extractFlag } from '../args'
|
|
15
15
|
import { loadContext } from '../context'
|
|
16
16
|
import { printCommandHelp } from '../help'
|
|
17
|
-
import { loadKey
|
|
17
|
+
import { loadKey } from '../keys'
|
|
18
18
|
import { fatal, formatLabel, formatValue, output } from '../output'
|
|
19
19
|
|
|
20
20
|
export async function handleTokensCommand(
|
|
@@ -52,7 +52,7 @@ async function tokenBalances(
|
|
|
52
52
|
_args: string[],
|
|
53
53
|
opts: GlobalFlags,
|
|
54
54
|
): Promise<void> {
|
|
55
|
-
const privateKey = await loadKey(
|
|
55
|
+
const privateKey = await loadKey()
|
|
56
56
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
57
57
|
chain: opts.chain,
|
|
58
58
|
})
|
|
@@ -84,7 +84,7 @@ async function tokenBalances(
|
|
|
84
84
|
async function tokenList(args: string[], opts: GlobalFlags): Promise<void> {
|
|
85
85
|
const tokenId = extractFlag(args, '--token-id')
|
|
86
86
|
|
|
87
|
-
const privateKey = await loadKey(
|
|
87
|
+
const privateKey = await loadKey()
|
|
88
88
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
89
89
|
chain: opts.chain,
|
|
90
90
|
})
|
|
@@ -154,7 +154,7 @@ async function tokenSend(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const privateKey = await loadKey(
|
|
157
|
+
const privateKey = await loadKey()
|
|
158
158
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
159
159
|
chain: opts.chain,
|
|
160
160
|
})
|
|
@@ -199,7 +199,7 @@ async function tokenBuy(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
const privateKey = await loadKey(
|
|
202
|
+
const privateKey = await loadKey()
|
|
203
203
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
204
204
|
chain: opts.chain,
|
|
205
205
|
})
|
package/src/commands/wallet.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { GlobalFlags } from '../args'
|
|
|
14
14
|
import { extractFlag, extractFlags } from '../args'
|
|
15
15
|
import { loadContext } from '../context'
|
|
16
16
|
import { printCommandHelp } from '../help'
|
|
17
|
-
import { loadKey
|
|
17
|
+
import { loadKey } from '../keys'
|
|
18
18
|
import { fatal, formatValue, output, printKeyValue } from '../output'
|
|
19
19
|
|
|
20
20
|
export async function handleWalletCommand(
|
|
@@ -55,8 +55,9 @@ export async function handleWalletCommand(
|
|
|
55
55
|
default:
|
|
56
56
|
printCommandHelp('wallet', {
|
|
57
57
|
balance: 'Show wallet balance in satoshis',
|
|
58
|
-
address:
|
|
59
|
-
|
|
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>")',
|
|
60
61
|
'send-all': 'Send all BSV to an address (--to <addr>)',
|
|
61
62
|
sync: 'Sync inbound payments at BRC-29 deposit addresses [--prefix <p>] [--start-index <n>] [--count <n>]',
|
|
62
63
|
info: 'Show wallet info (address, balance, network)',
|
|
@@ -82,7 +83,7 @@ async function walletBalance(
|
|
|
82
83
|
_args: string[],
|
|
83
84
|
opts: GlobalFlags,
|
|
84
85
|
): Promise<void> {
|
|
85
|
-
const privateKey = await loadKey(
|
|
86
|
+
const privateKey = await loadKey()
|
|
86
87
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
87
88
|
chain: opts.chain,
|
|
88
89
|
})
|
|
@@ -107,27 +108,44 @@ async function walletBalance(
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
async function walletAddress(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
async function walletAddress(args: string[], opts: GlobalFlags): Promise<void> {
|
|
112
|
+
const prefix = extractFlag(args, '--prefix') ?? '1sat'
|
|
113
|
+
const startIndexStr = extractFlag(args, '--start-index')
|
|
114
|
+
const countStr = extractFlag(args, '--count')
|
|
115
|
+
|
|
116
|
+
const startIndex = startIndexStr === undefined ? 0 : Number(startIndexStr)
|
|
117
|
+
const count = countStr === undefined ? 1 : Number(countStr)
|
|
118
|
+
|
|
119
|
+
if (!Number.isInteger(startIndex) || startIndex < 0) {
|
|
120
|
+
fatal('--start-index must be a non-negative integer')
|
|
121
|
+
}
|
|
122
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
123
|
+
fatal('--count must be a positive integer')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const privateKey = await loadKey()
|
|
115
127
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
116
128
|
chain: opts.chain,
|
|
117
129
|
})
|
|
118
130
|
|
|
119
131
|
try {
|
|
120
132
|
const result = await deriveDepositAddresses.execute(ctx, {
|
|
121
|
-
prefix
|
|
122
|
-
|
|
133
|
+
prefix,
|
|
134
|
+
startIndex,
|
|
135
|
+
count,
|
|
123
136
|
})
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
if (!primary) {
|
|
138
|
+
if (!result.derivations.length) {
|
|
127
139
|
fatal('Failed to derive deposit address')
|
|
128
140
|
}
|
|
129
141
|
|
|
130
|
-
|
|
142
|
+
if (opts.json) {
|
|
143
|
+
output(count === 1 ? result.derivations[0] : result.derivations, opts)
|
|
144
|
+
} else if (!opts.quiet) {
|
|
145
|
+
for (const d of result.derivations) {
|
|
146
|
+
console.log(d.address)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
131
149
|
} finally {
|
|
132
150
|
await destroy()
|
|
133
151
|
}
|
|
@@ -135,34 +153,68 @@ async function walletAddress(
|
|
|
135
153
|
|
|
136
154
|
async function walletSend(args: string[], opts: GlobalFlags): Promise<void> {
|
|
137
155
|
const to = extractFlag(args, '--to')
|
|
156
|
+
const script = extractFlag(args, '--script')
|
|
157
|
+
const dataAsm = extractFlag(args, '--data-asm')
|
|
138
158
|
const satsStr = extractFlag(args, '--sats')
|
|
139
159
|
|
|
140
|
-
|
|
141
|
-
if (
|
|
160
|
+
const modes = [to, script, dataAsm].filter((v) => v !== undefined).length
|
|
161
|
+
if (modes === 0) {
|
|
162
|
+
fatal(
|
|
163
|
+
'Specify one of --to <address>, --script <hex>, or --data-asm "<asm>"',
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
if (modes > 1) {
|
|
167
|
+
fatal('--to, --script, and --data-asm are mutually exclusive')
|
|
168
|
+
}
|
|
142
169
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
170
|
+
let request: {
|
|
171
|
+
address?: string
|
|
172
|
+
script?: string
|
|
173
|
+
data?: string[]
|
|
174
|
+
satoshis: number
|
|
175
|
+
}
|
|
176
|
+
let confirmMessage: string
|
|
177
|
+
|
|
178
|
+
if (dataAsm !== undefined) {
|
|
179
|
+
if (satsStr !== undefined) {
|
|
180
|
+
fatal(
|
|
181
|
+
'--sats is not allowed with --data-asm (OP_RETURN outputs are 0 sats)',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
request = { data: [dataAsm], satoshis: 0 }
|
|
185
|
+
confirmMessage = `Publish OP_RETURN data "${dataAsm}"?`
|
|
186
|
+
} else {
|
|
187
|
+
if (!satsStr) fatal('Missing --sats <amount>')
|
|
188
|
+
const satoshis = Number(satsStr)
|
|
189
|
+
if (!Number.isFinite(satoshis) || satoshis <= 0) {
|
|
190
|
+
fatal('--sats must be a positive number')
|
|
191
|
+
}
|
|
192
|
+
if (script !== undefined) {
|
|
193
|
+
if (!/^[0-9a-fA-F]*$/.test(script) || script.length % 2 !== 0) {
|
|
194
|
+
fatal('--script must be a hex string')
|
|
195
|
+
}
|
|
196
|
+
request = { script, satoshis }
|
|
197
|
+
confirmMessage = `Send ${satoshis} satoshis to custom script?`
|
|
198
|
+
} else {
|
|
199
|
+
request = { address: to, satoshis }
|
|
200
|
+
confirmMessage = `Send ${satoshis} satoshis to ${to}?`
|
|
201
|
+
}
|
|
146
202
|
}
|
|
147
203
|
|
|
148
204
|
if (!opts.yes) {
|
|
149
|
-
const ok = await confirm({
|
|
150
|
-
message: `Send ${satoshis} satoshis to ${to}?`,
|
|
151
|
-
})
|
|
205
|
+
const ok = await confirm({ message: confirmMessage })
|
|
152
206
|
if (isCancel(ok) || !ok) {
|
|
153
207
|
fatal('Send cancelled.')
|
|
154
208
|
}
|
|
155
209
|
}
|
|
156
210
|
|
|
157
|
-
const privateKey = await loadKey(
|
|
211
|
+
const privateKey = await loadKey()
|
|
158
212
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
159
213
|
chain: opts.chain,
|
|
160
214
|
})
|
|
161
215
|
|
|
162
216
|
try {
|
|
163
|
-
const result = await sendBsv.execute(ctx, {
|
|
164
|
-
requests: [{ address: to, satoshis }],
|
|
165
|
-
})
|
|
217
|
+
const result = await sendBsv.execute(ctx, { requests: [request] })
|
|
166
218
|
|
|
167
219
|
if (result.error) {
|
|
168
220
|
fatal(result.error)
|
|
@@ -188,7 +240,7 @@ async function walletSendAll(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
188
240
|
}
|
|
189
241
|
}
|
|
190
242
|
|
|
191
|
-
const privateKey = await loadKey(
|
|
243
|
+
const privateKey = await loadKey()
|
|
192
244
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
193
245
|
chain: opts.chain,
|
|
194
246
|
})
|
|
@@ -213,16 +265,20 @@ async function walletSync(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
213
265
|
|
|
214
266
|
const startIndex =
|
|
215
267
|
startIndexRaw !== undefined ? Number.parseInt(startIndexRaw, 10) : undefined
|
|
216
|
-
const count =
|
|
268
|
+
const count =
|
|
269
|
+
countRaw !== undefined ? Number.parseInt(countRaw, 10) : undefined
|
|
217
270
|
|
|
218
|
-
if (
|
|
271
|
+
if (
|
|
272
|
+
startIndex !== undefined &&
|
|
273
|
+
(!Number.isFinite(startIndex) || startIndex < 0)
|
|
274
|
+
) {
|
|
219
275
|
fatal('--start-index must be a non-negative integer')
|
|
220
276
|
}
|
|
221
277
|
if (count !== undefined && (!Number.isFinite(count) || count < 1)) {
|
|
222
278
|
fatal('--count must be a positive integer')
|
|
223
279
|
}
|
|
224
280
|
|
|
225
|
-
const privateKey = await loadKey(
|
|
281
|
+
const privateKey = await loadKey()
|
|
226
282
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
227
283
|
chain: opts.chain,
|
|
228
284
|
})
|
|
@@ -253,7 +309,7 @@ async function walletSync(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
253
309
|
}
|
|
254
310
|
|
|
255
311
|
async function walletInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
256
|
-
const privateKey = await loadKey(
|
|
312
|
+
const privateKey = await loadKey()
|
|
257
313
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
258
314
|
chain: opts.chain,
|
|
259
315
|
})
|
|
@@ -318,7 +374,7 @@ async function walletListOutputs(
|
|
|
318
374
|
const includeTags = args.includes('--include-tags')
|
|
319
375
|
const include = extractFlag(args, '--include')
|
|
320
376
|
|
|
321
|
-
const privateKey = await loadKey(
|
|
377
|
+
const privateKey = await loadKey()
|
|
322
378
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
323
379
|
chain: opts.chain,
|
|
324
380
|
})
|
|
@@ -368,7 +424,7 @@ async function walletRelinquishOutput(
|
|
|
368
424
|
fatal('Invalid --output format. Expected: txid.vout (e.g., abc123...0)')
|
|
369
425
|
}
|
|
370
426
|
|
|
371
|
-
const privateKey = await loadKey(
|
|
427
|
+
const privateKey = await loadKey()
|
|
372
428
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
373
429
|
chain: opts.chain,
|
|
374
430
|
})
|
|
@@ -400,7 +456,7 @@ async function walletListActions(
|
|
|
400
456
|
fatal('--limit must be between 1 and 10000')
|
|
401
457
|
}
|
|
402
458
|
|
|
403
|
-
const privateKey = await loadKey(
|
|
459
|
+
const privateKey = await loadKey()
|
|
404
460
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
405
461
|
chain: opts.chain,
|
|
406
462
|
})
|
|
@@ -443,7 +499,7 @@ async function walletCreateAction(
|
|
|
443
499
|
fatal(`Invalid JSON: ${jsonInput}`)
|
|
444
500
|
}
|
|
445
501
|
|
|
446
|
-
const privateKey = await loadKey(
|
|
502
|
+
const privateKey = await loadKey()
|
|
447
503
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
448
504
|
chain: opts.chain,
|
|
449
505
|
})
|
|
@@ -472,7 +528,7 @@ async function walletSignAction(
|
|
|
472
528
|
fatal(`Invalid JSON: ${jsonInput}`)
|
|
473
529
|
}
|
|
474
530
|
|
|
475
|
-
const privateKey = await loadKey(
|
|
531
|
+
const privateKey = await loadKey()
|
|
476
532
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
477
533
|
chain: opts.chain,
|
|
478
534
|
})
|
|
@@ -493,7 +549,7 @@ async function walletAbortAction(
|
|
|
493
549
|
|
|
494
550
|
if (!reference) fatal('Missing required --reference <ref>')
|
|
495
551
|
|
|
496
|
-
const privateKey = await loadKey(
|
|
552
|
+
const privateKey = await loadKey()
|
|
497
553
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
498
554
|
chain: opts.chain,
|
|
499
555
|
})
|
|
@@ -519,7 +575,7 @@ async function walletListCertificates(
|
|
|
519
575
|
fatal('--limit must be between 1 and 10000')
|
|
520
576
|
}
|
|
521
577
|
|
|
522
|
-
const privateKey = await loadKey(
|
|
578
|
+
const privateKey = await loadKey()
|
|
523
579
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
524
580
|
chain: opts.chain,
|
|
525
581
|
})
|
|
@@ -559,7 +615,7 @@ async function walletRelinquishCertificate(
|
|
|
559
615
|
if (!serialNumber) fatal('Missing required --serialNumber <serial>')
|
|
560
616
|
if (!certifier) fatal('Missing required --certifier <certifier>')
|
|
561
617
|
|
|
562
|
-
const privateKey = await loadKey(
|
|
618
|
+
const privateKey = await loadKey()
|
|
563
619
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
564
620
|
chain: opts.chain,
|
|
565
621
|
})
|
package/src/keys.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Encrypted key management for the 1sat CLI.
|
|
3
3
|
*
|
|
4
|
-
* Key resolution priority:
|
|
4
|
+
* Key resolution priority inside loadKey():
|
|
5
5
|
* 1. PRIVATE_KEY_WIF env var (headless/CI)
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
6
|
+
* 2. ONESAT_PASSWORD env var → decrypt keys.bep
|
|
7
|
+
* 3. Interactive TTY prompt → decrypt keys.bep
|
|
8
8
|
* 4. Fail with guidance
|
|
9
|
+
*
|
|
10
|
+
* A biometric vault tier will be added once @1sat/wallet-mac is
|
|
11
|
+
* CLI-ready (resolves its own enclave binary relative to its module
|
|
12
|
+
* directory instead of wallet-desktop's layout).
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
15
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
12
|
-
import { arch, platform } from 'node:os'
|
|
13
16
|
import { join } from 'node:path'
|
|
14
17
|
import { PrivateKey } from '@bsv/sdk'
|
|
18
|
+
import { isCancel, password as promptPassword } from '@clack/prompts'
|
|
15
19
|
import { type WifBackup, decryptBackup, encryptBackup } from 'bitcoin-backup'
|
|
16
20
|
import { ensureConfigDir, getConfigDir } from './config'
|
|
17
21
|
|
|
@@ -30,28 +34,14 @@ export function hasKey(): boolean {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
|
-
*
|
|
37
|
+
* Load the private key from env, env password, or TTY prompt.
|
|
34
38
|
*/
|
|
35
|
-
export function
|
|
36
|
-
return platform() === 'darwin' && arch() === 'arm64'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Load the private key from env, Touch ID cache, or password.
|
|
41
|
-
*
|
|
42
|
-
* Resolution order:
|
|
43
|
-
* 1. PRIVATE_KEY_WIF env var
|
|
44
|
-
* 2. Touch ID cached password (if available)
|
|
45
|
-
* 3. Explicit password parameter
|
|
46
|
-
*/
|
|
47
|
-
export async function loadKey(password?: string): Promise<PrivateKey> {
|
|
48
|
-
// Priority 1: Environment variable
|
|
39
|
+
export async function loadKey(): Promise<PrivateKey> {
|
|
49
40
|
const envWif = process.env.PRIVATE_KEY_WIF
|
|
50
41
|
if (envWif) {
|
|
51
42
|
return PrivateKey.fromWif(envWif)
|
|
52
43
|
}
|
|
53
44
|
|
|
54
|
-
// Priority 2: Encrypted file
|
|
55
45
|
const keysPath = getKeysPath()
|
|
56
46
|
if (!existsSync(keysPath)) {
|
|
57
47
|
throw new Error(
|
|
@@ -61,24 +51,21 @@ export async function loadKey(password?: string): Promise<PrivateKey> {
|
|
|
61
51
|
|
|
62
52
|
const encrypted = readFileSync(keysPath, 'utf8')
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
let resolvedPassword = password
|
|
66
|
-
if (!resolvedPassword) {
|
|
67
|
-
try {
|
|
68
|
-
const { getCachedPassword } = await import('bitcoin-backup')
|
|
69
|
-
const cached = await getCachedPassword(keysPath)
|
|
70
|
-
if (cached) {
|
|
71
|
-
resolvedPassword = cached
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
// Touch ID not available or no cached password — fall through
|
|
75
|
-
}
|
|
76
|
-
}
|
|
54
|
+
let resolvedPassword = process.env.ONESAT_PASSWORD
|
|
77
55
|
|
|
78
56
|
if (!resolvedPassword) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
if (!process.stdin.isTTY) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Password required to decrypt key file. Set ONESAT_PASSWORD or run in an interactive terminal.',
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
const input = await promptPassword({
|
|
63
|
+
message: 'Password:',
|
|
64
|
+
})
|
|
65
|
+
if (isCancel(input) || typeof input !== 'string' || input.length === 0) {
|
|
66
|
+
throw new Error('Password required to decrypt key file.')
|
|
67
|
+
}
|
|
68
|
+
resolvedPassword = input
|
|
82
69
|
}
|
|
83
70
|
|
|
84
71
|
const backup = await decryptBackup(encrypted, resolvedPassword)
|
|
@@ -93,8 +80,6 @@ export async function loadKey(password?: string): Promise<PrivateKey> {
|
|
|
93
80
|
*/
|
|
94
81
|
export async function saveKey(wif: string, password: string): Promise<void> {
|
|
95
82
|
ensureConfigDir()
|
|
96
|
-
|
|
97
|
-
// Validate the WIF before saving
|
|
98
83
|
PrivateKey.fromWif(wif)
|
|
99
84
|
|
|
100
85
|
const payload: WifBackup = {
|
|
@@ -103,29 +88,5 @@ export async function saveKey(wif: string, password: string): Promise<void> {
|
|
|
103
88
|
createdAt: new Date().toISOString(),
|
|
104
89
|
}
|
|
105
90
|
const encrypted = await encryptBackup(payload, password)
|
|
106
|
-
|
|
107
|
-
writeFileSync(keysPath, encrypted, { mode: 0o600 })
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Cache the password for keys.bep using Touch ID.
|
|
112
|
-
*/
|
|
113
|
-
export async function cacheKeyPassword(password: string): Promise<void> {
|
|
114
|
-
const { cachePassword } = await import('bitcoin-backup')
|
|
115
|
-
await cachePassword(getKeysPath(), password)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Remove the cached password for keys.bep.
|
|
120
|
-
*/
|
|
121
|
-
export async function forgetKeyPassword(): Promise<void> {
|
|
122
|
-
const { forgetPassword } = await import('bitcoin-backup')
|
|
123
|
-
await forgetPassword(getKeysPath())
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Resolve a password from flag or environment variable.
|
|
128
|
-
*/
|
|
129
|
-
export function resolvePassword(flagValue?: string): string | undefined {
|
|
130
|
-
return flagValue ?? process.env.ONESAT_PASSWORD
|
|
91
|
+
writeFileSync(getKeysPath(), encrypted, { mode: 0o600 })
|
|
131
92
|
}
|