@ldgr/cli 0.1.0 → 0.2.0
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 +1 -1
- package/dist/cli.js +57 -16
- package/dist/wallet-file.d.ts +16 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,4 +15,4 @@ ldgr send-token t1abcdef01234567 ldgr1… 10
|
|
|
15
15
|
ldgr network set testnet
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The default owner-only wallet store lives at `~/.config/ldgr/wallet.json
|
|
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`.
|
package/dist/cli.js
CHANGED
|
@@ -118,7 +118,13 @@ function defaultWalletPath(input = {}) {
|
|
|
118
118
|
const home = input.home ?? process.env.HOME;
|
|
119
119
|
if (!home)
|
|
120
120
|
throw new Error("Could not determine your home directory; use --wallet <path>");
|
|
121
|
-
|
|
121
|
+
return join(home, ".ldgr", "wallet.json");
|
|
122
|
+
}
|
|
123
|
+
function legacyDefaultWalletPath(input = {}) {
|
|
124
|
+
const home = input.home ?? process.env.HOME;
|
|
125
|
+
if (!home)
|
|
126
|
+
throw new Error("Could not determine your home directory; use --wallet <path>");
|
|
127
|
+
const configHome = input.configHome ?? (process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config"));
|
|
122
128
|
return join(configHome, "ldgr", "wallet.json");
|
|
123
129
|
}
|
|
124
130
|
async function createEncryptedWalletFile(input) {
|
|
@@ -146,7 +152,7 @@ async function encryptWallet(input) {
|
|
|
146
152
|
return { file, serialized: `${JSON.stringify(file, null, 2)}
|
|
147
153
|
`, recoveryKey: input.recoveryKey };
|
|
148
154
|
}
|
|
149
|
-
async function
|
|
155
|
+
async function writeNewWalletStore(path, store) {
|
|
150
156
|
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
151
157
|
const handle = await open(path, "wx", 384).catch((error) => {
|
|
152
158
|
if (error.code === "EEXIST")
|
|
@@ -154,12 +160,15 @@ async function writeNewWalletFile(path, file) {
|
|
|
154
160
|
throw error;
|
|
155
161
|
});
|
|
156
162
|
try {
|
|
157
|
-
await handle.writeFile(serializeWalletStore(
|
|
163
|
+
await handle.writeFile(serializeWalletStore(store), "utf8");
|
|
158
164
|
await handle.sync();
|
|
159
165
|
} finally {
|
|
160
166
|
await handle.close();
|
|
161
167
|
}
|
|
162
168
|
}
|
|
169
|
+
async function writeNewWalletFile(path, file) {
|
|
170
|
+
await writeNewWalletStore(path, newWalletStore(file));
|
|
171
|
+
}
|
|
163
172
|
async function readWalletStore(path) {
|
|
164
173
|
const details = await lstat(path).catch((error) => {
|
|
165
174
|
if (error.code === "ENOENT")
|
|
@@ -178,6 +187,32 @@ async function readWalletStore(path) {
|
|
|
178
187
|
}
|
|
179
188
|
return parseWalletStore(parsed);
|
|
180
189
|
}
|
|
190
|
+
async function migrateDefaultWalletPath(input = {}) {
|
|
191
|
+
const path = defaultWalletPath(input);
|
|
192
|
+
try {
|
|
193
|
+
await lstat(path);
|
|
194
|
+
return { path, migrated: false };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error.code !== "ENOENT")
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
const legacyPath = legacyDefaultWalletPath(input);
|
|
200
|
+
let legacyStore;
|
|
201
|
+
try {
|
|
202
|
+
legacyStore = await readWalletStore(legacyPath);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof Error && error.message === `Wallet file not found: ${legacyPath}`)
|
|
205
|
+
return { path, migrated: false };
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
await writeNewWalletStore(path, legacyStore);
|
|
209
|
+
try {
|
|
210
|
+
await unlink(legacyPath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw new Error(`Migrated wallet to ${path}, but could not remove legacy wallet file ${legacyPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
213
|
+
}
|
|
214
|
+
return { path, migrated: true };
|
|
215
|
+
}
|
|
181
216
|
async function unlockWalletFile(file, passphrase) {
|
|
182
217
|
try {
|
|
183
218
|
const key = await deriveEncryptionKey(passphrase, decodeBase64Url(file.kdf.salt));
|
|
@@ -277,7 +312,7 @@ async function walletFileMetadata(path) {
|
|
|
277
312
|
|
|
278
313
|
// src/cli.ts
|
|
279
314
|
var HELP = `
|
|
280
|
-
LDGR CLI 0.
|
|
315
|
+
LDGR CLI 0.2.0 — self-custody wallet
|
|
281
316
|
|
|
282
317
|
Usage:
|
|
283
318
|
ldgr wallet create [--testnet] [--wallet <path>]
|
|
@@ -291,7 +326,8 @@ Usage:
|
|
|
291
326
|
ldgr network show [--wallet <path>]
|
|
292
327
|
ldgr network set <mainnet|testnet> [--wallet <path>]
|
|
293
328
|
|
|
294
|
-
The default wallet store is ~/.
|
|
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
|
|
295
331
|
is independently encrypted with a passphrase you enter through the terminal;
|
|
296
332
|
the CLI never accepts recovery keys or passphrases as arguments or environment
|
|
297
333
|
variables. Use \`ldgr wallet select <index>\` to change the active wallet, or
|
|
@@ -301,6 +337,7 @@ prints a warning on every wallet command.
|
|
|
301
337
|
function parseArguments(argv) {
|
|
302
338
|
const command = [];
|
|
303
339
|
let walletPath = defaultWalletPath();
|
|
340
|
+
let walletPathExplicit = false;
|
|
304
341
|
let walletOnTestnet = false;
|
|
305
342
|
for (let index = 0;index < argv.length; index += 1) {
|
|
306
343
|
const value = argv[index];
|
|
@@ -309,6 +346,7 @@ function parseArguments(argv) {
|
|
|
309
346
|
if (!path || path.startsWith("-"))
|
|
310
347
|
throw new TypeError("--wallet requires a file path");
|
|
311
348
|
walletPath = path;
|
|
349
|
+
walletPathExplicit = true;
|
|
312
350
|
index += 1;
|
|
313
351
|
} else if (value === "--testnet") {
|
|
314
352
|
walletOnTestnet = true;
|
|
@@ -321,7 +359,7 @@ function parseArguments(argv) {
|
|
|
321
359
|
if (walletOnTestnet && !(command[0] === "wallet" && (command[1] === "create" || command[1] === "import"))) {
|
|
322
360
|
throw new TypeError("--testnet is only used with wallet create or wallet import; use `ldgr network set testnet` to switch an existing wallet");
|
|
323
361
|
}
|
|
324
|
-
return { command, walletPath, walletOnTestnet };
|
|
362
|
+
return { command, walletPath, walletPathExplicit, walletOnTestnet };
|
|
325
363
|
}
|
|
326
364
|
function networkLabel(file) {
|
|
327
365
|
return file.ledgerId === 0 ? "testnet" : "mainnet";
|
|
@@ -561,26 +599,29 @@ async function main() {
|
|
|
561
599
|
}
|
|
562
600
|
p.intro("LDGR CLI");
|
|
563
601
|
try {
|
|
602
|
+
const resolved = parsed.walletPathExplicit ? { path: parsed.walletPath, migrated: false } : await migrateDefaultWalletPath();
|
|
603
|
+
if (resolved.migrated)
|
|
604
|
+
p.log.info(`Moved legacy wallet store to ${resolved.path}`);
|
|
564
605
|
if (first === "wallet" && second === "create" && third === undefined)
|
|
565
|
-
await runCreate(
|
|
606
|
+
await runCreate(resolved.path, parsed.walletOnTestnet);
|
|
566
607
|
else if (first === "wallet" && second === "import" && third === undefined)
|
|
567
|
-
await runImport(
|
|
608
|
+
await runImport(resolved.path, parsed.walletOnTestnet);
|
|
568
609
|
else if (first === "wallet" && second === "list" && third === undefined)
|
|
569
|
-
await runWalletList(
|
|
610
|
+
await runWalletList(resolved.path);
|
|
570
611
|
else if (first === "wallet" && second === "select" && third && fourth === undefined)
|
|
571
|
-
await runWalletSelect(
|
|
612
|
+
await runWalletSelect(resolved.path, third);
|
|
572
613
|
else if (first === "address" && second === undefined)
|
|
573
|
-
await runAddress(
|
|
614
|
+
await runAddress(resolved.path);
|
|
574
615
|
else if (first === "balance" && second === undefined)
|
|
575
|
-
await runBalance(
|
|
616
|
+
await runBalance(resolved.path);
|
|
576
617
|
else if (first === "send" && second && third && fourth === undefined)
|
|
577
|
-
await runSend(
|
|
618
|
+
await runSend(resolved.path, "dng", second, third);
|
|
578
619
|
else if (first === "send-token" && second && third && fourth)
|
|
579
|
-
await runSend(
|
|
620
|
+
await runSend(resolved.path, second, third, fourth);
|
|
580
621
|
else if (first === "network" && second === "show" && third === undefined)
|
|
581
|
-
await runNetwork(
|
|
622
|
+
await runNetwork(resolved.path);
|
|
582
623
|
else if (first === "network" && second === "set" && third && fourth === undefined)
|
|
583
|
-
await runNetwork(
|
|
624
|
+
await runNetwork(resolved.path, third);
|
|
584
625
|
else
|
|
585
626
|
throw new TypeError("Unknown command. Run `ldgr --help` for available commands.");
|
|
586
627
|
} catch (error) {
|
package/dist/wallet-file.d.ts
CHANGED
|
@@ -27,20 +27,33 @@ export type WalletFileMetadata = EncryptedWalletFile & {
|
|
|
27
27
|
readonly index: number;
|
|
28
28
|
readonly selected: boolean;
|
|
29
29
|
};
|
|
30
|
+
export type DefaultWalletPathInput = {
|
|
31
|
+
readonly home?: string | undefined;
|
|
32
|
+
/** Test seam for the former XDG default; production reads XDG_CONFIG_HOME. */
|
|
33
|
+
readonly configHome?: string | undefined;
|
|
34
|
+
};
|
|
30
35
|
export type CreatedEncryptedWallet = {
|
|
31
36
|
readonly file: EncryptedWalletFile;
|
|
32
37
|
readonly serialized: string;
|
|
33
38
|
/** Display once during creation; never persist or log this value. */
|
|
34
39
|
readonly recoveryKey: string;
|
|
35
40
|
};
|
|
36
|
-
export declare function defaultWalletPath(input?:
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
export declare function defaultWalletPath(input?: DefaultWalletPathInput): string;
|
|
42
|
+
/** The v0.1 default location, used only to preserve existing local wallets. */
|
|
43
|
+
export declare function legacyDefaultWalletPath(input?: DefaultWalletPathInput): string;
|
|
39
44
|
export declare function createEncryptedWalletFile(input: {
|
|
40
45
|
readonly ledgerId: LedgerId;
|
|
41
46
|
readonly passphrase: string;
|
|
42
47
|
}): Promise<CreatedEncryptedWallet>;
|
|
43
48
|
export declare function writeNewWalletFile(path: string, file: EncryptedWalletFile): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Moves the validated former XDG default into ~/.ldgr/ on first wallet use.
|
|
51
|
+
* Explicit --wallet paths are deliberately never considered here.
|
|
52
|
+
*/
|
|
53
|
+
export declare function migrateDefaultWalletPath(input?: DefaultWalletPathInput): Promise<{
|
|
54
|
+
readonly path: string;
|
|
55
|
+
readonly migrated: boolean;
|
|
56
|
+
}>;
|
|
44
57
|
export declare function readEncryptedWalletFile(path: string, passphrase: string): Promise<LdgrSigner>;
|
|
45
58
|
/** Adds a separately encrypted wallet and selects it. */
|
|
46
59
|
export declare function addWalletFile(path: string, file: EncryptedWalletFile): Promise<number>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ldgr/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ldgr": "dist/cli.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@ldgr/client": "^0.2.1",
|
|
20
|
-
"@ldgr/protocol": "^0.2.
|
|
20
|
+
"@ldgr/protocol": "^0.2.1",
|
|
21
21
|
"@clack/prompts": "^0.11.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|