@ldgr/cli 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.1 — 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>]
@@ -327,12 +378,11 @@ Usage:
327
378
  ldgr network set <mainnet|testnet> [--wallet <path>]
328
379
 
329
380
  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.
381
+ ~/.config/ldgr/wallet.json move there safely on first use. A wallet can be
382
+ passphrase-protected or unprotected (local-file permissions only). The CLI
383
+ never accepts recovery-key values or passphrases as arguments or environment
384
+ variables. Use \`ldgr wallet export <path>\` for an extension-compatible
385
+ recovery-key file. Testnet mode prints a warning on every wallet command.
336
386
  `;
337
387
  function parseArguments(argv) {
338
388
  const command = [];
@@ -380,6 +430,23 @@ async function askPassphrase(message) {
380
430
  throw new Error("Cancelled");
381
431
  return answer;
382
432
  }
433
+ async function chooseWalletPassphrase() {
434
+ const protectedWallet = await p.confirm({ message: "Protect this wallet with a passphrase?", initialValue: true });
435
+ if (p.isCancel(protectedWallet))
436
+ throw new Error("Cancelled");
437
+ if (!protectedWallet) {
438
+ p.log.warn("This wallet will be unprotected. Anyone who can read its owner-only local file can spend its funds.");
439
+ return;
440
+ }
441
+ const passphrase = await askPassphrase("Choose a wallet passphrase");
442
+ const confirmation = await askPassphrase("Repeat the wallet passphrase");
443
+ if (passphrase !== confirmation)
444
+ throw new Error("Passphrases did not match");
445
+ return passphrase;
446
+ }
447
+ async function signingPassphrase(file, message) {
448
+ return file.protection === "none" ? undefined : askPassphrase(message);
449
+ }
383
450
  async function confirm2(message) {
384
451
  const answer = await p.confirm({ message, initialValue: false });
385
452
  if (p.isCancel(answer) || !answer)
@@ -412,39 +479,34 @@ async function runCreate(path, testnet) {
412
479
  const ledgerId = testnet ? 0 : 1;
413
480
  if (ledgerId === 0)
414
481
  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");
482
+ const passphrase = await chooseWalletPassphrase();
419
483
  const spinner2 = p.spinner();
420
- spinner2.start("Generating and encrypting your wallet");
484
+ spinner2.start("Generating your wallet");
421
485
  const created = await createEncryptedWalletFile({ ledgerId, passphrase });
422
486
  const index = await addWalletFile(path, created.file);
423
- spinner2.stop("Encrypted wallet added");
487
+ spinner2.stop(passphrase === undefined ? "Unprotected wallet added" : "Protected wallet added");
424
488
  p.note(`Address
425
489
  ${created.file.address}
426
490
 
427
491
  Recovery key — save this offline now; it will not be shown again
428
492
  ${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.");
493
+ 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
494
  p.outro(`Wallet #${index + 1} selected in ${path}`);
431
495
  }
432
- async function runImport(path, testnet) {
496
+ async function runImport(path, testnet, recoveryKeyPath) {
433
497
  const ledgerId = testnet ? 0 : 1;
434
498
  if (ledgerId === 0)
435
499
  showNetworkWarning({ ledgerId });
436
- const recoveryKey = await p.password({
500
+ const enteredRecoveryKey = recoveryKeyPath === undefined ? await p.password({
437
501
  message: "Enter the recovery key to import",
438
502
  validate(value) {
439
503
  return value.trim() ? undefined : "Enter a recovery key";
440
504
  }
441
- });
442
- if (p.isCancel(recoveryKey))
505
+ }) : (await readFile2(recoveryKeyPath, "utf8")).trim();
506
+ if (p.isCancel(enteredRecoveryKey) || !enteredRecoveryKey)
443
507
  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");
508
+ const recoveryKey = enteredRecoveryKey;
509
+ const passphrase = await chooseWalletPassphrase();
448
510
  const spinner2 = p.spinner();
449
511
  spinner2.start("Validating and encrypting imported wallet");
450
512
  const imported = await importEncryptedWalletFile({ path, recoveryKey, ledgerId, passphrase });
@@ -458,12 +520,20 @@ ${networkLabel(imported.file)}`, `Wallet #${imported.index + 1} selected`);
458
520
  showNetworkWarning(imported.file);
459
521
  p.outro(`Wallet #${imported.index + 1} added to ${path}`);
460
522
  }
523
+ async function runExport(path, backupPath) {
524
+ const file = await walletFileMetadata(path);
525
+ showNetworkWarning(file);
526
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to export");
527
+ await exportRecoveryKeyFile({ walletPath: path, backupPath, passphrase });
528
+ p.log.warn("Anyone with this recovery-key file can control the wallet. Keep it offline and never share it.");
529
+ p.outro(`Recovery-key file saved at ${backupPath}`);
530
+ }
461
531
  async function runWalletList(path) {
462
532
  const wallets = await listWalletFileMetadata(path);
463
533
  const selected = wallets.find((wallet) => wallet.selected);
464
534
  showNetworkWarning(selected);
465
535
  const rows = wallets.map((wallet) => [
466
- `${wallet.selected ? "● Selected" : "○"} #${wallet.index + 1} · ${networkLabel(wallet)}`,
536
+ `${wallet.selected ? "● Selected" : "○"} #${wallet.index + 1} · ${networkLabel(wallet)} · ${wallet.protection === "none" ? "Unprotected" : "Protected"}`,
467
537
  wallet.address
468
538
  ].join(`
469
539
  `));
@@ -515,7 +585,7 @@ async function runSend(path, rawAsset, recipient, amount) {
515
585
  const client = await selectedClient(file);
516
586
  const asset = resolvePublicAssetCode(rawAsset, file.ledgerId);
517
587
  const definition = await definitionForAsset(client, asset, file.ledgerId);
518
- const passphrase = await askPassphrase("Enter your wallet passphrase to sign");
588
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to sign");
519
589
  const wallet = await readEncryptedWalletFile(path, passphrase);
520
590
  const makeSubmission = async () => buildTransferSubmission({
521
591
  wallet,
@@ -573,7 +643,7 @@ ${file.address}`, "Selected network");
573
643
  if (ledgerId === 0)
574
644
  showNetworkWarning({ ledgerId });
575
645
  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");
646
+ const passphrase = await signingPassphrase(file, "Enter your wallet passphrase to switch networks");
577
647
  await confirm2(`Switch this wallet to ${target}?`);
578
648
  const spinner2 = p.spinner();
579
649
  spinner2.start("Re-encrypting wallet for the selected network");
@@ -604,8 +674,10 @@ async function main() {
604
674
  p.log.info(`Moved legacy wallet store to ${resolved.path}`);
605
675
  if (first === "wallet" && second === "create" && third === undefined)
606
676
  await runCreate(resolved.path, parsed.walletOnTestnet);
607
- else if (first === "wallet" && second === "import" && third === undefined)
608
- await runImport(resolved.path, parsed.walletOnTestnet);
677
+ else if (first === "wallet" && second === "import" && fourth === undefined)
678
+ await runImport(resolved.path, parsed.walletOnTestnet, third);
679
+ else if (first === "wallet" && second === "export" && third && fourth === undefined)
680
+ await runExport(resolved.path, third);
609
681
  else if (first === "wallet" && second === "list" && third === undefined)
610
682
  await runWalletList(resolved.path);
611
683
  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.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "ldgr": "dist/cli.js"