@1sat/cli 0.0.43 → 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 +147 -37
- 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
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
* BRC-100 interface commands - list-outputs, relinquish-output, list-actions, etc.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
deriveDepositAddresses,
|
|
8
|
+
sendAllBsv,
|
|
9
|
+
sendBsv,
|
|
10
|
+
syncAddresses,
|
|
11
|
+
} from '@1sat/actions'
|
|
7
12
|
import { confirm, isCancel } from '@clack/prompts'
|
|
8
13
|
import type { GlobalFlags } from '../args'
|
|
9
14
|
import { extractFlag, extractFlags } from '../args'
|
|
10
15
|
import { loadContext } from '../context'
|
|
11
16
|
import { printCommandHelp } from '../help'
|
|
12
|
-
import { loadKey
|
|
17
|
+
import { loadKey } from '../keys'
|
|
13
18
|
import { fatal, formatValue, output, printKeyValue } from '../output'
|
|
14
19
|
|
|
15
20
|
export async function handleWalletCommand(
|
|
@@ -27,6 +32,8 @@ export async function handleWalletCommand(
|
|
|
27
32
|
return walletSend(rest, opts)
|
|
28
33
|
case 'send-all':
|
|
29
34
|
return walletSendAll(rest, opts)
|
|
35
|
+
case 'sync':
|
|
36
|
+
return walletSync(rest, opts)
|
|
30
37
|
case 'info':
|
|
31
38
|
return walletInfo(rest, opts)
|
|
32
39
|
case 'list-outputs':
|
|
@@ -48,9 +55,11 @@ export async function handleWalletCommand(
|
|
|
48
55
|
default:
|
|
49
56
|
printCommandHelp('wallet', {
|
|
50
57
|
balance: 'Show wallet balance in satoshis',
|
|
51
|
-
address:
|
|
52
|
-
|
|
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>")',
|
|
53
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>]',
|
|
54
63
|
info: 'Show wallet info (address, balance, network)',
|
|
55
64
|
'list-outputs':
|
|
56
65
|
'List wallet outputs (--basket <name> [--tags <t1,t2>] [--limit N] [--include-tags] [--include <val>])',
|
|
@@ -74,7 +83,7 @@ async function walletBalance(
|
|
|
74
83
|
_args: string[],
|
|
75
84
|
opts: GlobalFlags,
|
|
76
85
|
): Promise<void> {
|
|
77
|
-
const privateKey = await loadKey(
|
|
86
|
+
const privateKey = await loadKey()
|
|
78
87
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
79
88
|
chain: opts.chain,
|
|
80
89
|
})
|
|
@@ -99,27 +108,44 @@ async function walletBalance(
|
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
|
|
102
|
-
async function walletAddress(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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()
|
|
107
127
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
108
128
|
chain: opts.chain,
|
|
109
129
|
})
|
|
110
130
|
|
|
111
131
|
try {
|
|
112
132
|
const result = await deriveDepositAddresses.execute(ctx, {
|
|
113
|
-
prefix
|
|
114
|
-
|
|
133
|
+
prefix,
|
|
134
|
+
startIndex,
|
|
135
|
+
count,
|
|
115
136
|
})
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
if (!primary) {
|
|
138
|
+
if (!result.derivations.length) {
|
|
119
139
|
fatal('Failed to derive deposit address')
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
|
|
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
|
+
}
|
|
123
149
|
} finally {
|
|
124
150
|
await destroy()
|
|
125
151
|
}
|
|
@@ -127,34 +153,68 @@ async function walletAddress(
|
|
|
127
153
|
|
|
128
154
|
async function walletSend(args: string[], opts: GlobalFlags): Promise<void> {
|
|
129
155
|
const to = extractFlag(args, '--to')
|
|
156
|
+
const script = extractFlag(args, '--script')
|
|
157
|
+
const dataAsm = extractFlag(args, '--data-asm')
|
|
130
158
|
const satsStr = extractFlag(args, '--sats')
|
|
131
159
|
|
|
132
|
-
|
|
133
|
-
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
|
+
}
|
|
134
169
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
}
|
|
138
202
|
}
|
|
139
203
|
|
|
140
204
|
if (!opts.yes) {
|
|
141
|
-
const ok = await confirm({
|
|
142
|
-
message: `Send ${satoshis} satoshis to ${to}?`,
|
|
143
|
-
})
|
|
205
|
+
const ok = await confirm({ message: confirmMessage })
|
|
144
206
|
if (isCancel(ok) || !ok) {
|
|
145
207
|
fatal('Send cancelled.')
|
|
146
208
|
}
|
|
147
209
|
}
|
|
148
210
|
|
|
149
|
-
const privateKey = await loadKey(
|
|
211
|
+
const privateKey = await loadKey()
|
|
150
212
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
151
213
|
chain: opts.chain,
|
|
152
214
|
})
|
|
153
215
|
|
|
154
216
|
try {
|
|
155
|
-
const result = await sendBsv.execute(ctx, {
|
|
156
|
-
requests: [{ address: to, satoshis }],
|
|
157
|
-
})
|
|
217
|
+
const result = await sendBsv.execute(ctx, { requests: [request] })
|
|
158
218
|
|
|
159
219
|
if (result.error) {
|
|
160
220
|
fatal(result.error)
|
|
@@ -180,7 +240,7 @@ async function walletSendAll(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
180
240
|
}
|
|
181
241
|
}
|
|
182
242
|
|
|
183
|
-
const privateKey = await loadKey(
|
|
243
|
+
const privateKey = await loadKey()
|
|
184
244
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
185
245
|
chain: opts.chain,
|
|
186
246
|
})
|
|
@@ -198,8 +258,58 @@ async function walletSendAll(args: string[], opts: GlobalFlags): Promise<void> {
|
|
|
198
258
|
}
|
|
199
259
|
}
|
|
200
260
|
|
|
261
|
+
async function walletSync(args: string[], opts: GlobalFlags): Promise<void> {
|
|
262
|
+
const prefix = extractFlag(args, '--prefix')
|
|
263
|
+
const startIndexRaw = extractFlag(args, '--start-index')
|
|
264
|
+
const countRaw = extractFlag(args, '--count')
|
|
265
|
+
|
|
266
|
+
const startIndex =
|
|
267
|
+
startIndexRaw !== undefined ? Number.parseInt(startIndexRaw, 10) : undefined
|
|
268
|
+
const count =
|
|
269
|
+
countRaw !== undefined ? Number.parseInt(countRaw, 10) : undefined
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
startIndex !== undefined &&
|
|
273
|
+
(!Number.isFinite(startIndex) || startIndex < 0)
|
|
274
|
+
) {
|
|
275
|
+
fatal('--start-index must be a non-negative integer')
|
|
276
|
+
}
|
|
277
|
+
if (count !== undefined && (!Number.isFinite(count) || count < 1)) {
|
|
278
|
+
fatal('--count must be a positive integer')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const privateKey = await loadKey()
|
|
282
|
+
const { ctx, destroy } = await loadContext(privateKey, {
|
|
283
|
+
chain: opts.chain,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const result = await syncAddresses.execute(ctx, {
|
|
288
|
+
...(prefix ? { prefix } : {}),
|
|
289
|
+
...(startIndex !== undefined ? { startIndex } : {}),
|
|
290
|
+
...(count !== undefined ? { count } : {}),
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
if (opts.json) {
|
|
294
|
+
output(result, opts)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log(
|
|
299
|
+
`\nprocessed: ${result.processed} failed: ${result.failed} lastScore: ${result.lastScore}`,
|
|
300
|
+
)
|
|
301
|
+
if (result.addresses.length > 0) {
|
|
302
|
+
console.log('addresses:')
|
|
303
|
+
for (const addr of result.addresses) console.log(` ${addr}`)
|
|
304
|
+
}
|
|
305
|
+
console.log()
|
|
306
|
+
} finally {
|
|
307
|
+
await destroy()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
201
311
|
async function walletInfo(_args: string[], opts: GlobalFlags): Promise<void> {
|
|
202
|
-
const privateKey = await loadKey(
|
|
312
|
+
const privateKey = await loadKey()
|
|
203
313
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
204
314
|
chain: opts.chain,
|
|
205
315
|
})
|
|
@@ -264,7 +374,7 @@ async function walletListOutputs(
|
|
|
264
374
|
const includeTags = args.includes('--include-tags')
|
|
265
375
|
const include = extractFlag(args, '--include')
|
|
266
376
|
|
|
267
|
-
const privateKey = await loadKey(
|
|
377
|
+
const privateKey = await loadKey()
|
|
268
378
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
269
379
|
chain: opts.chain,
|
|
270
380
|
})
|
|
@@ -314,7 +424,7 @@ async function walletRelinquishOutput(
|
|
|
314
424
|
fatal('Invalid --output format. Expected: txid.vout (e.g., abc123...0)')
|
|
315
425
|
}
|
|
316
426
|
|
|
317
|
-
const privateKey = await loadKey(
|
|
427
|
+
const privateKey = await loadKey()
|
|
318
428
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
319
429
|
chain: opts.chain,
|
|
320
430
|
})
|
|
@@ -346,7 +456,7 @@ async function walletListActions(
|
|
|
346
456
|
fatal('--limit must be between 1 and 10000')
|
|
347
457
|
}
|
|
348
458
|
|
|
349
|
-
const privateKey = await loadKey(
|
|
459
|
+
const privateKey = await loadKey()
|
|
350
460
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
351
461
|
chain: opts.chain,
|
|
352
462
|
})
|
|
@@ -389,7 +499,7 @@ async function walletCreateAction(
|
|
|
389
499
|
fatal(`Invalid JSON: ${jsonInput}`)
|
|
390
500
|
}
|
|
391
501
|
|
|
392
|
-
const privateKey = await loadKey(
|
|
502
|
+
const privateKey = await loadKey()
|
|
393
503
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
394
504
|
chain: opts.chain,
|
|
395
505
|
})
|
|
@@ -418,7 +528,7 @@ async function walletSignAction(
|
|
|
418
528
|
fatal(`Invalid JSON: ${jsonInput}`)
|
|
419
529
|
}
|
|
420
530
|
|
|
421
|
-
const privateKey = await loadKey(
|
|
531
|
+
const privateKey = await loadKey()
|
|
422
532
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
423
533
|
chain: opts.chain,
|
|
424
534
|
})
|
|
@@ -439,7 +549,7 @@ async function walletAbortAction(
|
|
|
439
549
|
|
|
440
550
|
if (!reference) fatal('Missing required --reference <ref>')
|
|
441
551
|
|
|
442
|
-
const privateKey = await loadKey(
|
|
552
|
+
const privateKey = await loadKey()
|
|
443
553
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
444
554
|
chain: opts.chain,
|
|
445
555
|
})
|
|
@@ -465,7 +575,7 @@ async function walletListCertificates(
|
|
|
465
575
|
fatal('--limit must be between 1 and 10000')
|
|
466
576
|
}
|
|
467
577
|
|
|
468
|
-
const privateKey = await loadKey(
|
|
578
|
+
const privateKey = await loadKey()
|
|
469
579
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
470
580
|
chain: opts.chain,
|
|
471
581
|
})
|
|
@@ -505,7 +615,7 @@ async function walletRelinquishCertificate(
|
|
|
505
615
|
if (!serialNumber) fatal('Missing required --serialNumber <serial>')
|
|
506
616
|
if (!certifier) fatal('Missing required --certifier <certifier>')
|
|
507
617
|
|
|
508
|
-
const privateKey = await loadKey(
|
|
618
|
+
const privateKey = await loadKey()
|
|
509
619
|
const { ctx, destroy } = await loadContext(privateKey, {
|
|
510
620
|
chain: opts.chain,
|
|
511
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
|
}
|