@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 +4 -3
- package/dist/cli.js +111 -39
- package/dist/wallet-file.d.ts +32 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @ldgr/cli
|
|
2
2
|
|
|
3
|
-
LDGR's self-custody terminal wallet. Version 0.
|
|
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.
|
|
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"
|
|
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
|
-
|
|
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
|
|
277
|
-
return { 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
|
|
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] =
|
|
354
|
+
wallets[store.activeWalletIndex] = updated.file;
|
|
305
355
|
await replaceWalletStore(path, { ...store, wallets });
|
|
306
|
-
return
|
|
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.
|
|
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.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
variables. Use \`ldgr wallet
|
|
334
|
-
|
|
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
|
|
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
|
|
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("
|
|
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.
|
|
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
|
|
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(
|
|
505
|
+
}) : (await readFile2(recoveryKeyPath, "utf8")).trim();
|
|
506
|
+
if (p.isCancel(enteredRecoveryKey) || !enteredRecoveryKey)
|
|
443
507
|
throw new Error("Cancelled");
|
|
444
|
-
const
|
|
445
|
-
const
|
|
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
|
|
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
|
|
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" &&
|
|
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)
|
package/dist/wallet-file.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
35
|
+
readonly wallets: readonly LocalWalletFile[];
|
|
25
36
|
};
|
|
26
|
-
export type WalletFileMetadata =
|
|
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:
|
|
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
|
|
57
|
+
readonly passphrase?: string | undefined;
|
|
47
58
|
}): Promise<CreatedEncryptedWallet>;
|
|
48
|
-
export declare function writeNewWalletFile(path: string, file:
|
|
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
|
|
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:
|
|
60
|
-
/**
|
|
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
|
|
82
|
+
readonly passphrase?: string | undefined;
|
|
66
83
|
}): Promise<{
|
|
67
|
-
readonly file:
|
|
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<
|
|
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<
|
|
74
|
-
export declare function walletFileMetadata(path: string): Promise<
|
|
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 {};
|