polkadot-cli 0.9.0 → 0.10.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 +17 -1
- package/dist/cli.mjs +87 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ dot chain remove westend
|
|
|
42
42
|
|
|
43
43
|
Dev accounts (Alice, Bob, Charlie, Dave, Eve, Ferdie) are always available for testnets. Create or import your own accounts for any chain.
|
|
44
44
|
|
|
45
|
-
> **Security warning:** Account secrets (mnemonics and seeds) are currently stored **unencrypted** in `~/.polkadot/accounts.json`. Do not use this for high-value accounts on mainnet. Encrypted storage is planned for a future release.
|
|
45
|
+
> **Security warning:** Account secrets (mnemonics and seeds) are currently stored **unencrypted** in `~/.polkadot/accounts.json`. Do not use this for high-value accounts on mainnet. Encrypted storage is planned for a future release. Use `--env` to keep secrets off disk entirely.
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# List all accounts (dev + stored)
|
|
@@ -55,10 +55,26 @@ dot account create my-validator
|
|
|
55
55
|
# Import from a BIP39 mnemonic
|
|
56
56
|
dot account import treasury --secret "word1 word2 ... word12"
|
|
57
57
|
|
|
58
|
+
# Add an env-var-backed account (secret stays off disk)
|
|
59
|
+
dot account add ci-signer --env MY_SECRET
|
|
60
|
+
|
|
61
|
+
# Use it — the env var is read at signing time
|
|
62
|
+
MY_SECRET="word1 word2 ..." dot tx System.remark 0xdead --from ci-signer
|
|
63
|
+
|
|
58
64
|
# Remove an account
|
|
59
65
|
dot account remove my-validator
|
|
60
66
|
```
|
|
61
67
|
|
|
68
|
+
#### Env-var-backed accounts
|
|
69
|
+
|
|
70
|
+
For CI/CD and security-conscious workflows, store a reference to an environment variable instead of the secret itself:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dot account add ci-signer --env MY_SECRET
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The secret is never written to disk. At signing time, the CLI reads `$MY_SECRET` and derives the keypair. If the variable is not set, the CLI errors with a clear message. `account list` shows an `(env: MY_SECRET)` badge and resolves the address live when the variable is available.
|
|
77
|
+
|
|
62
78
|
**Supported secret formats for import:**
|
|
63
79
|
|
|
64
80
|
| Format | Example | Status |
|
package/dist/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
// src/cli.ts
|
|
6
6
|
import cac from "cac";
|
|
7
7
|
// package.json
|
|
8
|
-
var version = "0.
|
|
8
|
+
var version = "0.10.0";
|
|
9
9
|
|
|
10
10
|
// src/config/accounts-store.ts
|
|
11
11
|
import { access as access2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -120,6 +120,11 @@ function findAccount(file, name) {
|
|
|
120
120
|
return file.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase());
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// src/config/accounts-types.ts
|
|
124
|
+
function isEnvSecret(secret) {
|
|
125
|
+
return typeof secret === "object" && secret !== null && "env" in secret;
|
|
126
|
+
}
|
|
127
|
+
|
|
123
128
|
// src/core/accounts.ts
|
|
124
129
|
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
|
|
125
130
|
import {
|
|
@@ -195,6 +200,27 @@ function toSs58(publicKey, prefix = 42) {
|
|
|
195
200
|
}
|
|
196
201
|
return ss58Address(publicKey, prefix);
|
|
197
202
|
}
|
|
203
|
+
function resolveSecret(secret) {
|
|
204
|
+
if (isEnvSecret(secret)) {
|
|
205
|
+
const value = process.env[secret.env];
|
|
206
|
+
if (!value) {
|
|
207
|
+
throw new Error(`Environment variable "${secret.env}" is not set. Set it before signing.`);
|
|
208
|
+
}
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
return secret;
|
|
212
|
+
}
|
|
213
|
+
function tryDerivePublicKey(envVarName) {
|
|
214
|
+
const value = process.env[envVarName];
|
|
215
|
+
if (!value)
|
|
216
|
+
return null;
|
|
217
|
+
try {
|
|
218
|
+
const { publicKey } = importAccount(value);
|
|
219
|
+
return publicKeyToHex(publicKey);
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
198
224
|
async function resolveAccountSigner(name) {
|
|
199
225
|
if (isDevAccount(name)) {
|
|
200
226
|
const keypair2 = getDevKeypair(name);
|
|
@@ -206,8 +232,9 @@ async function resolveAccountSigner(name) {
|
|
|
206
232
|
const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
|
|
207
233
|
throw new Error(`Unknown account "${name}". Available accounts: ${available.join(", ")}`);
|
|
208
234
|
}
|
|
209
|
-
const
|
|
210
|
-
const
|
|
235
|
+
const secret = resolveSecret(account.secret);
|
|
236
|
+
const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
|
|
237
|
+
const keypair = isHexSeed ? deriveFromHexSeed(secret, account.derivationPath) : deriveFromMnemonic(secret, account.derivationPath);
|
|
211
238
|
return getPolkadotSigner(keypair.publicKey, "Sr25519", keypair.sign);
|
|
212
239
|
}
|
|
213
240
|
|
|
@@ -309,20 +336,23 @@ var ACCOUNT_HELP = `
|
|
|
309
336
|
${BOLD}Usage:${RESET}
|
|
310
337
|
$ dot account create <name> Create a new account
|
|
311
338
|
$ dot account import <name> --secret <s> Import from BIP39 mnemonic
|
|
339
|
+
$ dot account add <name> --env <VAR> Add account backed by env variable
|
|
312
340
|
$ dot account list List all accounts
|
|
313
341
|
$ dot account remove <name> Remove a stored account
|
|
314
342
|
|
|
315
343
|
${BOLD}Examples:${RESET}
|
|
316
344
|
$ dot account create my-validator
|
|
317
345
|
$ dot account import treasury --secret "word1 word2 ... word12"
|
|
346
|
+
$ dot account add ci-signer --env MY_SECRET
|
|
318
347
|
$ dot account list
|
|
319
348
|
$ dot account remove my-validator
|
|
320
349
|
|
|
321
350
|
${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
|
|
351
|
+
Use --env to keep secrets off disk entirely.
|
|
322
352
|
Hex seed import (0x...) is not supported via CLI.${RESET}
|
|
323
353
|
`.trimStart();
|
|
324
354
|
function registerAccountCommands(cli) {
|
|
325
|
-
cli.command("account [action] [name]", "Manage local accounts (create, import, list, remove)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").action(async (action, name, opts) => {
|
|
355
|
+
cli.command("account [action] [name]", "Manage local accounts (create, import, add, list, remove)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").option("--env <varName>", "Environment variable name holding the secret").action(async (action, name, opts) => {
|
|
326
356
|
if (!action) {
|
|
327
357
|
return accountList();
|
|
328
358
|
}
|
|
@@ -331,6 +361,8 @@ function registerAccountCommands(cli) {
|
|
|
331
361
|
return accountCreate(name);
|
|
332
362
|
case "import":
|
|
333
363
|
return accountImport(name, opts);
|
|
364
|
+
case "add":
|
|
365
|
+
return accountAdd(name, opts);
|
|
334
366
|
case "list":
|
|
335
367
|
return accountList();
|
|
336
368
|
case "remove":
|
|
@@ -410,6 +442,44 @@ async function accountImport(name, opts) {
|
|
|
410
442
|
console.log(` ${BOLD}Address:${RESET} ${address}`);
|
|
411
443
|
console.log();
|
|
412
444
|
}
|
|
445
|
+
async function accountAdd(name, opts) {
|
|
446
|
+
if (!name) {
|
|
447
|
+
console.error(`Account name is required.
|
|
448
|
+
`);
|
|
449
|
+
console.error("Usage: dot account add <name> --env <VAR>");
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
if (!opts.env) {
|
|
453
|
+
console.error(`--env is required.
|
|
454
|
+
`);
|
|
455
|
+
console.error("Usage: dot account add <name> --env <VAR>");
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
if (isDevAccount(name)) {
|
|
459
|
+
throw new Error(`"${name}" is a built-in dev account and cannot be used as a custom account name.`);
|
|
460
|
+
}
|
|
461
|
+
const accountsFile = await loadAccounts();
|
|
462
|
+
if (findAccount(accountsFile, name)) {
|
|
463
|
+
throw new Error(`Account "${name}" already exists.`);
|
|
464
|
+
}
|
|
465
|
+
const publicKey = tryDerivePublicKey(opts.env) ?? "";
|
|
466
|
+
accountsFile.accounts.push({
|
|
467
|
+
name,
|
|
468
|
+
secret: { env: opts.env },
|
|
469
|
+
publicKey,
|
|
470
|
+
derivationPath: ""
|
|
471
|
+
});
|
|
472
|
+
await saveAccounts(accountsFile);
|
|
473
|
+
printHeading("Account Added");
|
|
474
|
+
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
475
|
+
console.log(` ${BOLD}Env:${RESET} ${opts.env}`);
|
|
476
|
+
if (publicKey) {
|
|
477
|
+
console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
|
|
478
|
+
} else {
|
|
479
|
+
console.log(` ${YELLOW}Address will resolve when $${opts.env} is set.${RESET}`);
|
|
480
|
+
}
|
|
481
|
+
console.log();
|
|
482
|
+
}
|
|
413
483
|
async function accountList() {
|
|
414
484
|
printHeading("Dev Accounts");
|
|
415
485
|
for (const name of DEV_NAMES) {
|
|
@@ -421,8 +491,19 @@ async function accountList() {
|
|
|
421
491
|
if (accountsFile.accounts.length > 0) {
|
|
422
492
|
printHeading("Stored Accounts");
|
|
423
493
|
for (const account of accountsFile.accounts) {
|
|
424
|
-
|
|
425
|
-
|
|
494
|
+
let displayName = account.name;
|
|
495
|
+
let address;
|
|
496
|
+
if (isEnvSecret(account.secret)) {
|
|
497
|
+
displayName += ` (env: ${account.secret.env})`;
|
|
498
|
+
let pubKey = account.publicKey;
|
|
499
|
+
if (!pubKey) {
|
|
500
|
+
pubKey = tryDerivePublicKey(account.secret.env) ?? "";
|
|
501
|
+
}
|
|
502
|
+
address = pubKey ? toSs58(pubKey) : "n/a";
|
|
503
|
+
} else {
|
|
504
|
+
address = toSs58(account.publicKey);
|
|
505
|
+
}
|
|
506
|
+
printItem(displayName, address);
|
|
426
507
|
}
|
|
427
508
|
} else {
|
|
428
509
|
printHeading("Stored Accounts");
|