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.
Files changed (3) hide show
  1. package/README.md +17 -1
  2. package/dist/cli.mjs +87 -6
  3. 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.9.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 isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(account.secret);
210
- const keypair = isHexSeed ? deriveFromHexSeed(account.secret, account.derivationPath) : deriveFromMnemonic(account.secret, account.derivationPath);
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
- const address = toSs58(account.publicKey);
425
- printItem(account.name, address);
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {