@ldgr/cli 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # @ldgr/cli
2
2
 
3
- LDGR's self-custody terminal wallet. Version 0.1.0 creates or imports encrypted local wallets, lets you select between them, shows addresses and balances, sends native LDGR or a token, and persistently switches the selected wallet between mainnet and testnet.
3
+ LDGR's self-custody terminal wallet. Version 0.2.0 creates or imports local wallets, lets you select between them, shows addresses and balances, sends native LDGR or a token, and persistently switches the selected wallet between mainnet and testnet.
4
4
 
5
5
  ```bash
6
6
  npm install -g @ldgr/cli
7
7
  ldgr wallet create
8
- ldgr wallet import
8
+ ldgr wallet import ~/Downloads/ldgr-recovery-key.txt
9
+ ldgr wallet export ~/Desktop/ldgr-recovery-key.txt
9
10
  ldgr wallet list
10
11
  ldgr wallet select 2
11
12
  ldgr address
@@ -15,4 +16,4 @@ ldgr send-token t1abcdef01234567 ldgr1… 10
15
16
  ldgr network set testnet
16
17
  ```
17
18
 
18
- The default owner-only wallet store lives at `~/.ldgr/wallet.json`. On first use, an existing `~/.config/ldgr/wallet.json` store is validated, copied to the new owner-only location, and removed from its former location. Each added wallet is encrypted separately using a passphrase requested on the terminal. `wallet list` shows one-based indexes and marks the active wallet; `wallet select <index>` changes it without exposing a secret. Record the recovery key shown at creation offline. `wallet import --testnet` imports a recovery key as a testnet wallet. Testnet prints a warning on every wallet command and only uses `https://tst.ldgr.ltd`.
19
+ The default owner-only wallet store lives at `~/.ldgr/wallet.json`. On first use, an existing `~/.config/ldgr/wallet.json` store is validated, copied to the new owner-only location, and removed from its former location. At creation/import you can choose passphrase protection or an **unprotected** wallet, which follows the extension model and relies only on the local file's permissions. `wallet list` shows one-based indexes and marks the active wallet; `wallet select <index>` changes it without exposing a secret. `wallet export <path>` writes a `0600` `ldgr-recovery-key.txt` file containing the raw recovery-key value accepted by the extension; `wallet import <path>` reads the same file. Testnet prints a warning on every wallet command and only uses `https://tst.ldgr.ltd`.
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  nativeAssetDefinitionForLedger,
9
9
  resolvePublicAssetCode
10
10
  } from "@ldgr/protocol";
11
+ import { readFile as readFile2 } from "node:fs/promises";
11
12
 
12
13
  // src/transfer.ts
13
14
  import {
@@ -49,7 +50,7 @@ async function buildTransferSubmission(input) {
49
50
 
50
51
  // src/wallet-file.ts
51
52
  import { generateLocalWallet, loadLocalWallet } from "@ldgr/client";
52
- import { canonicalizeLedgerAddress as canonicalizeLedgerAddress2 } from "@ldgr/protocol";
53
+ import { canonicalizeLedgerAddress as canonicalizeLedgerAddress2, decodeEd25519RecoveryKey } from "@ldgr/protocol";
53
54
  import { lstat, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
54
55
  import { dirname, join } from "node:path";
55
56
  var KDF_ITERATIONS = 310000;
@@ -82,11 +83,21 @@ function parseWalletEntry(value) {
82
83
  if (!value || typeof value !== "object" || Array.isArray(value))
83
84
  throw new TypeError("Wallet file is not a JSON object");
84
85
  const file = value;
85
- if (file.format !== "ldgr-cli-wallet" || file.version !== 1 || file.network !== "mainnet" && file.network !== "testnet" || file.ledgerId !== 0 && file.ledgerId !== 1 || typeof file.address !== "string" || typeof file.publicKey !== "string" || typeof file.ciphertext !== "string" || typeof file.iv !== "string" || !file.kdf || file.kdf.name !== "PBKDF2" || file.kdf.hash !== "SHA-256" || file.kdf.iterations !== KDF_ITERATIONS || typeof file.kdf.salt !== "string")
86
+ if (file.format !== "ldgr-cli-wallet" || file.version !== 1 || file.network !== "mainnet" && file.network !== "testnet" || file.ledgerId !== 0 && file.ledgerId !== 1 || typeof file.address !== "string" || typeof file.publicKey !== "string")
86
87
  throw new TypeError("Unsupported wallet file format");
87
88
  if (file.network !== networkForLedger(file.ledgerId))
88
89
  throw new TypeError("Wallet file network does not match ledgerId");
89
90
  canonicalizeLedgerAddress2(file.address, file.ledgerId);
91
+ if (file.protection === "none") {
92
+ if (typeof file.recoveryKey !== "string")
93
+ throw new TypeError("Unsupported wallet file format");
94
+ decodeEd25519RecoveryKey(file.recoveryKey);
95
+ return file;
96
+ }
97
+ if (file.protection !== undefined && file.protection !== "passphrase")
98
+ throw new TypeError("Unsupported wallet file format");
99
+ if (typeof file.ciphertext !== "string" || typeof file.iv !== "string" || !file.kdf || file.kdf.name !== "PBKDF2" || file.kdf.hash !== "SHA-256" || file.kdf.iterations !== KDF_ITERATIONS || typeof file.kdf.salt !== "string")
100
+ throw new TypeError("Unsupported wallet file format");
90
101
  return file;
91
102
  }
92
103
  function parseWalletStore(value) {
@@ -129,8 +140,24 @@ function legacyDefaultWalletPath(input = {}) {
129
140
  }
130
141
  async function createEncryptedWalletFile(input) {
131
142
  const wallet = await generateLocalWallet({ ledgerId: input.ledgerId });
143
+ if (input.passphrase === undefined)
144
+ return createUnprotectedWallet({ wallet, recoveryKey: wallet.recoveryKey });
132
145
  return encryptWallet({ wallet, recoveryKey: wallet.recoveryKey, passphrase: input.passphrase });
133
146
  }
147
+ function createUnprotectedWallet(input) {
148
+ const file = {
149
+ format: "ldgr-cli-wallet",
150
+ version: 1,
151
+ protection: "none",
152
+ network: networkForLedger(input.wallet.ledgerId),
153
+ ledgerId: input.wallet.ledgerId,
154
+ address: input.wallet.address,
155
+ publicKey: input.wallet.publicKey,
156
+ recoveryKey: input.recoveryKey
157
+ };
158
+ return { file, serialized: `${JSON.stringify(file, null, 2)}
159
+ `, recoveryKey: input.recoveryKey };
160
+ }
134
161
  async function encryptWallet(input) {
135
162
  const salt = crypto.getRandomValues(new Uint8Array(16));
136
163
  const iv = crypto.getRandomValues(new Uint8Array(12));
@@ -213,7 +240,17 @@ async function migrateDefaultWalletPath(input = {}) {
213
240
  }
214
241
  return { path, migrated: true };
215
242
  }
243
+ async function walletFromRecoveryKey(file, recoveryKey) {
244
+ const wallet = await loadLocalWallet({ recoveryKey, ledgerId: file.ledgerId });
245
+ if (wallet.address !== file.address || wallet.publicKey !== file.publicKey)
246
+ throw new Error("wallet identity mismatch");
247
+ return { wallet, recoveryKey };
248
+ }
216
249
  async function unlockWalletFile(file, passphrase) {
250
+ if (file.protection === "none")
251
+ return walletFromRecoveryKey(file, file.recoveryKey);
252
+ if (passphrase === undefined)
253
+ throw new Error("This wallet requires a passphrase");
217
254
  try {
218
255
  const key = await deriveEncryptionKey(passphrase, decodeBase64Url(file.kdf.salt));
219
256
  const plaintext = new Uint8Array(await crypto.subtle.decrypt({ name: AES_GCM, iv: arrayBuffer(decodeBase64Url(file.iv)) }, key, arrayBuffer(decodeBase64Url(file.ciphertext))));
@@ -221,10 +258,7 @@ async function unlockWalletFile(file, passphrase) {
221
258
  plaintext.fill(0);
222
259
  if (typeof value.recoveryKey !== "string")
223
260
  throw new Error("missing recovery key");
224
- const wallet = await loadLocalWallet({ recoveryKey: value.recoveryKey, ledgerId: file.ledgerId });
225
- if (wallet.address !== file.address || wallet.publicKey !== file.publicKey)
226
- throw new Error("wallet identity mismatch");
227
- return { wallet, recoveryKey: value.recoveryKey };
261
+ return await walletFromRecoveryKey(file, value.recoveryKey);
228
262
  } catch {
229
263
  throw new Error("Could not unlock wallet: check your passphrase or wallet file");
230
264
  }
@@ -234,6 +268,22 @@ async function readEncryptedWalletFile(path, passphrase) {
234
268
  const file = store.wallets[store.activeWalletIndex];
235
269
  return (await unlockWalletFile(file, passphrase)).wallet;
236
270
  }
271
+ async function exportRecoveryKeyFile(input) {
272
+ const store = await readWalletStore(input.walletPath);
273
+ const unlocked = await unlockWalletFile(store.wallets[store.activeWalletIndex], input.passphrase);
274
+ await mkdir(dirname(input.backupPath), { recursive: true, mode: 448 });
275
+ const handle = await open(input.backupPath, "wx", 384).catch((error) => {
276
+ if (error.code === "EEXIST")
277
+ throw new Error(`Recovery-key file already exists: ${input.backupPath}`);
278
+ throw error;
279
+ });
280
+ try {
281
+ await handle.writeFile(unlocked.recoveryKey, "utf8");
282
+ await handle.sync();
283
+ } finally {
284
+ await handle.close();
285
+ }
286
+ }
237
287
  async function replaceWalletStore(path, store) {
238
288
  await readWalletStore(path);
239
289
  const temporaryPath = join(dirname(path), `.${crypto.randomUUID()}.wallet.tmp`);
@@ -273,8 +323,8 @@ async function addWalletFile(path, file) {
273
323
  }
274
324
  async function importEncryptedWalletFile(input) {
275
325
  const wallet = await loadLocalWallet({ recoveryKey: input.recoveryKey, ledgerId: input.ledgerId });
276
- const encrypted = await encryptWallet({ wallet, recoveryKey: input.recoveryKey, passphrase: input.passphrase });
277
- return { file: encrypted.file, index: await addWalletFile(input.path, encrypted.file) };
326
+ const created = input.passphrase === undefined ? createUnprotectedWallet({ wallet, recoveryKey: input.recoveryKey }) : await encryptWallet({ wallet, recoveryKey: input.recoveryKey, passphrase: input.passphrase });
327
+ return { file: created.file, index: await addWalletFile(input.path, created.file) };
278
328
  }
279
329
  async function listWalletFileMetadata(path) {
280
330
  const store = await readWalletStore(path);
@@ -296,14 +346,14 @@ async function switchWalletNetwork(path, passphrase, ledgerId) {
296
346
  return existing;
297
347
  const unlocked = await unlockWalletFile(existing, passphrase);
298
348
  const wallet = await loadLocalWallet({ recoveryKey: unlocked.recoveryKey, ledgerId });
299
- const encrypted = await encryptWallet({ wallet, recoveryKey: unlocked.recoveryKey, passphrase });
349
+ const updated = existing.protection === "none" ? createUnprotectedWallet({ wallet, recoveryKey: unlocked.recoveryKey }) : await encryptWallet({ wallet, recoveryKey: unlocked.recoveryKey, passphrase });
300
350
  if (store.wallets.some((entry, index) => index !== store.activeWalletIndex && entry.ledgerId === ledgerId && entry.publicKey === wallet.publicKey)) {
301
351
  throw new Error("This recovery key already has a wallet in the selected network; select that wallet instead");
302
352
  }
303
353
  const wallets = [...store.wallets];
304
- wallets[store.activeWalletIndex] = encrypted.file;
354
+ wallets[store.activeWalletIndex] = updated.file;
305
355
  await replaceWalletStore(path, { ...store, wallets });
306
- return encrypted.file;
356
+ return updated.file;
307
357
  }
308
358
  async function walletFileMetadata(path) {
309
359
  const store = await readWalletStore(path);
@@ -312,11 +362,12 @@ async function walletFileMetadata(path) {
312
362
 
313
363
  // src/cli.ts
314
364
  var HELP = `
315
- LDGR CLI 0.2.0 — self-custody wallet
365
+ LDGR CLI 0.2.2 — self-custody wallet
316
366
 
317
367
  Usage:
318
368
  ldgr wallet create [--testnet] [--wallet <path>]
319
- ldgr wallet import [--testnet] [--wallet <path>]
369
+ ldgr wallet import [recovery-key-file] [--testnet] [--wallet <path>]
370
+ ldgr wallet export <recovery-key-file> [--wallet <path>]
320
371
  ldgr wallet list [--wallet <path>]
321
372
  ldgr wallet select <index> [--wallet <path>]
322
373
  ldgr address [--wallet <path>]
@@ -326,13 +377,11 @@ Usage:
326
377
  ldgr network show [--wallet <path>]
327
378
  ldgr network set <mainnet|testnet> [--wallet <path>]
328
379
 
329
- The default wallet store is ~/.ldgr/wallet.json. Existing wallets at
330
- ~/.config/ldgr/wallet.json move there safely on first use. Each wallet inside it
331
- is independently encrypted with a passphrase you enter through the terminal;
332
- the CLI never accepts recovery keys or passphrases as arguments or environment
333
- variables. Use \`ldgr wallet select <index>\` to change the active wallet, or
334
- \`ldgr network set testnet\` to switch its network persistently. Testnet mode
335
- prints a warning on every wallet command.
380
+ The default wallet store is ~/.ldgr/wallet.json. A wallet can be
381
+ passphrase-protected or unprotected (local-file permissions only). The CLI
382
+ never accepts recovery-key values or passphrases as arguments or environment
383
+ variables. Use \`ldgr wallet export <path>\` for an extension-compatible
384
+ recovery-key file. Testnet mode prints a warning on every wallet command.
336
385
  `;
337
386
  function parseArguments(argv) {
338
387
  const command = [];
@@ -380,6 +429,23 @@ async function askPassphrase(message) {
380
429
  throw new Error("Cancelled");
381
430
  return answer;
382
431
  }
432
+ async function chooseWalletPassphrase() {
433
+ const protectedWallet = await p.confirm({ message: "Protect this wallet with a passphrase?", initialValue: true });
434
+ if (p.isCancel(protectedWallet))
435
+ throw new Error("Cancelled");
436
+ if (!protectedWallet) {
437
+ p.log.warn("This wallet will be unprotected. Anyone who can read its owner-only local file can spend its funds.");
438
+ return;
439
+ }
440
+ const passphrase = await askPassphrase("Choose a wallet passphrase");
441
+ const confirmation = await askPassphrase("Repeat the wallet passphrase");
442
+ if (passphrase !== confirmation)
443
+ throw new Error("Passphrases did not match");
444
+ return passphrase;
445
+ }
446
+ async function signingPassphrase(file, message) {
447
+ return file.protection === "none" ? undefined : askPassphrase(message);
448
+ }
383
449
  async function confirm2(message) {
384
450
  const answer = await p.confirm({ message, initialValue: false });
385
451
  if (p.isCancel(answer) || !answer)
@@ -412,39 +478,34 @@ async function runCreate(path, testnet) {
412
478
  const ledgerId = testnet ? 0 : 1;
413
479
  if (ledgerId === 0)
414
480
  showNetworkWarning({ ledgerId });
415
- const passphrase = await askPassphrase("Choose a wallet passphrase");
416
- const confirmation = await askPassphrase("Repeat the wallet passphrase");
417
- if (passphrase !== confirmation)
418
- throw new Error("Passphrases did not match");
481
+ const passphrase = await chooseWalletPassphrase();
419
482
  const spinner2 = p.spinner();
420
- spinner2.start("Generating and encrypting your wallet");
483
+ spinner2.start("Generating your wallet");
421
484
  const created = await createEncryptedWalletFile({ ledgerId, passphrase });
422
485
  const index = await addWalletFile(path, created.file);
423
- spinner2.stop("Encrypted wallet added");
486
+ spinner2.stop(passphrase === undefined ? "Unprotected wallet added" : "Protected wallet added");
424
487
  p.note(`Address
425
488
  ${created.file.address}
426
489
 
427
490
  Recovery key — save this offline now; it will not be shown again
428
491
  ${created.recoveryKey}`, "Your self-custody backup");
429
- p.log.warn("This recovery key alone can restore and control this wallet. Your passphrase protects only this local file; never share either secret.");
492
+ p.log.warn("This recovery key alone can restore and control this wallet. Export it as a file with `ldgr wallet export <path>` and keep it offline.");
430
493
  p.outro(`Wallet #${index + 1} selected in ${path}`);
431
494
  }
432
- async function runImport(path, testnet) {
495
+ async function runImport(path, testnet, recoveryKeyPath) {
433
496
  const ledgerId = testnet ? 0 : 1;
434
497
  if (ledgerId === 0)
435
498
  showNetworkWarning({ ledgerId });
436
- const recoveryKey = await p.password({
499
+ const enteredRecoveryKey = recoveryKeyPath === undefined ? await p.password({
437
500
  message: "Enter the recovery key to import",
438
501
  validate(value) {
439
502
  return value.trim() ? undefined : "Enter a recovery key";
440
503
  }
441
- });
442
- if (p.isCancel(recoveryKey))
504
+ }) : (await readFile2(recoveryKeyPath, "utf8")).trim();
505
+ if (p.isCancel(enteredRecoveryKey) || !enteredRecoveryKey)
443
506
  throw new Error("Cancelled");
444
- const passphrase = await askPassphrase("Choose a passphrase for this imported wallet");
445
- const confirmation = await askPassphrase("Repeat the wallet passphrase");
446
- if (passphrase !== confirmation)
447
- throw new Error("Passphrases did not match");
507
+ const recoveryKey = enteredRecoveryKey;
508
+ const passphrase = await chooseWalletPassphrase();
448
509
  const spinner2 = p.spinner();
449
510
  spinner2.start("Validating and encrypting imported wallet");
450
511
  const imported = await importEncryptedWalletFile({ path, recoveryKey, ledgerId, passphrase });
@@ -458,12 +519,20 @@ ${networkLabel(imported.file)}`, `Wallet #${imported.index + 1} selected`);
458
519
  showNetworkWarning(imported.file);
459
520
  p.outro(`Wallet #${imported.index + 1} added to ${path}`);
460
521
  }
522
+ async function runExport(path, backupPath) {
523
+ const file = await walletFileMetadata(path);
524
+ showNetworkWarning(file);
525
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to export");
526
+ await exportRecoveryKeyFile({ walletPath: path, backupPath, passphrase });
527
+ p.log.warn("Anyone with this recovery-key file can control the wallet. Keep it offline and never share it.");
528
+ p.outro(`Recovery-key file saved at ${backupPath}`);
529
+ }
461
530
  async function runWalletList(path) {
462
531
  const wallets = await listWalletFileMetadata(path);
463
532
  const selected = wallets.find((wallet) => wallet.selected);
464
533
  showNetworkWarning(selected);
465
534
  const rows = wallets.map((wallet) => [
466
- `${wallet.selected ? "● Selected" : "○"} #${wallet.index + 1} · ${networkLabel(wallet)}`,
535
+ `${wallet.selected ? "● Selected" : "○"} #${wallet.index + 1} · ${networkLabel(wallet)} · ${wallet.protection === "none" ? "Unprotected" : "Protected"}`,
467
536
  wallet.address
468
537
  ].join(`
469
538
  `));
@@ -515,7 +584,7 @@ async function runSend(path, rawAsset, recipient, amount) {
515
584
  const client = await selectedClient(file);
516
585
  const asset = resolvePublicAssetCode(rawAsset, file.ledgerId);
517
586
  const definition = await definitionForAsset(client, asset, file.ledgerId);
518
- const passphrase = await askPassphrase("Enter your wallet passphrase to sign");
587
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to sign");
519
588
  const wallet = await readEncryptedWalletFile(path, passphrase);
520
589
  const makeSubmission = async () => buildTransferSubmission({
521
590
  wallet,
@@ -573,7 +642,7 @@ ${file.address}`, "Selected network");
573
642
  if (ledgerId === 0)
574
643
  showNetworkWarning({ ledgerId });
575
644
  p.log.info("Switching networks derives a different address from the same self-custody key.");
576
- const passphrase = await askPassphrase("Enter your wallet passphrase to switch networks");
645
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to switch networks");
577
646
  await confirm2(`Switch this wallet to ${target}?`);
578
647
  const spinner2 = p.spinner();
579
648
  spinner2.start("Re-encrypting wallet for the selected network");
@@ -604,8 +673,10 @@ async function main() {
604
673
  p.log.info(`Moved legacy wallet store to ${resolved.path}`);
605
674
  if (first === "wallet" && second === "create" && third === undefined)
606
675
  await runCreate(resolved.path, parsed.walletOnTestnet);
607
- else if (first === "wallet" && second === "import" && third === undefined)
608
- await runImport(resolved.path, parsed.walletOnTestnet);
676
+ else if (first === "wallet" && second === "import" && fourth === undefined)
677
+ await runImport(resolved.path, parsed.walletOnTestnet, third);
678
+ else if (first === "wallet" && second === "export" && third && fourth === undefined)
679
+ await runExport(resolved.path, third);
609
680
  else if (first === "wallet" && second === "list" && third === undefined)
610
681
  await runWalletList(resolved.path);
611
682
  else if (first === "wallet" && second === "select" && third && fourth === undefined)
@@ -1,13 +1,17 @@
1
1
  import { type LdgrSigner } from "@ldgr/client";
2
2
  import { type LedgerId } from "@ldgr/protocol";
3
3
  export type WalletNetwork = "mainnet" | "testnet";
4
- export type EncryptedWalletFile = {
4
+ type WalletIdentity = {
5
5
  readonly format: "ldgr-cli-wallet";
6
6
  readonly version: 1;
7
7
  readonly network: WalletNetwork;
8
8
  readonly ledgerId: LedgerId;
9
9
  readonly address: string;
10
10
  readonly publicKey: string;
11
+ };
12
+ /** Passphrase protection is the legacy format when `protection` is omitted. */
13
+ export type EncryptedWalletFile = WalletIdentity & {
14
+ readonly protection?: "passphrase";
11
15
  readonly ciphertext: string;
12
16
  readonly iv: string;
13
17
  readonly kdf: {
@@ -17,13 +21,20 @@ export type EncryptedWalletFile = {
17
21
  readonly salt: string;
18
22
  };
19
23
  };
24
+ /** Matches the extension's explicit local-only, no-passphrase vault mode. */
25
+ export type UnprotectedWalletFile = WalletIdentity & {
26
+ readonly protection: "none";
27
+ /** Local secret material; protected by the file's owner-only permissions only. */
28
+ readonly recoveryKey: string;
29
+ };
30
+ export type LocalWalletFile = EncryptedWalletFile | UnprotectedWalletFile;
20
31
  export type EncryptedWalletStore = {
21
32
  readonly format: "ldgr-cli-wallet-store";
22
33
  readonly version: 1;
23
34
  readonly activeWalletIndex: number;
24
- readonly wallets: readonly EncryptedWalletFile[];
35
+ readonly wallets: readonly LocalWalletFile[];
25
36
  };
26
- export type WalletFileMetadata = EncryptedWalletFile & {
37
+ export type WalletFileMetadata = LocalWalletFile & {
27
38
  readonly index: number;
28
39
  readonly selected: boolean;
29
40
  };
@@ -33,7 +44,7 @@ export type DefaultWalletPathInput = {
33
44
  readonly configHome?: string | undefined;
34
45
  };
35
46
  export type CreatedEncryptedWallet = {
36
- readonly file: EncryptedWalletFile;
47
+ readonly file: LocalWalletFile;
37
48
  readonly serialized: string;
38
49
  /** Display once during creation; never persist or log this value. */
39
50
  readonly recoveryKey: string;
@@ -43,9 +54,9 @@ export declare function defaultWalletPath(input?: DefaultWalletPathInput): strin
43
54
  export declare function legacyDefaultWalletPath(input?: DefaultWalletPathInput): string;
44
55
  export declare function createEncryptedWalletFile(input: {
45
56
  readonly ledgerId: LedgerId;
46
- readonly passphrase: string;
57
+ readonly passphrase?: string | undefined;
47
58
  }): Promise<CreatedEncryptedWallet>;
48
- export declare function writeNewWalletFile(path: string, file: EncryptedWalletFile): Promise<void>;
59
+ export declare function writeNewWalletFile(path: string, file: LocalWalletFile): Promise<void>;
49
60
  /**
50
61
  * Moves the validated former XDG default into ~/.ldgr/ on first wallet use.
51
62
  * Explicit --wallet paths are deliberately never considered here.
@@ -54,21 +65,28 @@ export declare function migrateDefaultWalletPath(input?: DefaultWalletPathInput)
54
65
  readonly path: string;
55
66
  readonly migrated: boolean;
56
67
  }>;
57
- export declare function readEncryptedWalletFile(path: string, passphrase: string): Promise<LdgrSigner>;
68
+ export declare function readEncryptedWalletFile(path: string, passphrase?: string): Promise<LdgrSigner>;
69
+ /** Writes the exact raw recovery-key text accepted by the extension importer. */
70
+ export declare function exportRecoveryKeyFile(input: {
71
+ readonly walletPath: string;
72
+ readonly backupPath: string;
73
+ readonly passphrase?: string | undefined;
74
+ }): Promise<void>;
58
75
  /** Adds a separately encrypted wallet and selects it. */
59
- export declare function addWalletFile(path: string, file: EncryptedWalletFile): Promise<number>;
60
- /** Encrypts canonical recovery material, adds it to the store, and selects it. */
76
+ export declare function addWalletFile(path: string, file: LocalWalletFile): Promise<number>;
77
+ /** Adds canonical recovery material with optional passphrase protection and selects it. */
61
78
  export declare function importEncryptedWalletFile(input: {
62
79
  readonly path: string;
63
80
  readonly recoveryKey: string;
64
81
  readonly ledgerId: LedgerId;
65
- readonly passphrase: string;
82
+ readonly passphrase?: string | undefined;
66
83
  }): Promise<{
67
- readonly file: EncryptedWalletFile;
84
+ readonly file: LocalWalletFile;
68
85
  readonly index: number;
69
86
  }>;
70
87
  export declare function listWalletFileMetadata(path: string): Promise<readonly WalletFileMetadata[]>;
71
- export declare function selectWalletFile(path: string, index: number): Promise<EncryptedWalletFile>;
88
+ export declare function selectWalletFile(path: string, index: number): Promise<LocalWalletFile>;
72
89
  /** Re-encrypts the same self-custody key for a different LDGR ledger. */
73
- export declare function switchWalletNetwork(path: string, passphrase: string, ledgerId: LedgerId): Promise<EncryptedWalletFile>;
74
- export declare function walletFileMetadata(path: string): Promise<EncryptedWalletFile>;
90
+ export declare function switchWalletNetwork(path: string, passphrase: string | undefined, ledgerId: LedgerId): Promise<LocalWalletFile>;
91
+ export declare function walletFileMetadata(path: string): Promise<LocalWalletFile>;
92
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldgr/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "ldgr": "dist/cli.js"