@elytro/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/dist/index.js +206 -80
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -363,6 +363,8 @@ var STORAGE_KEY = "keyring";
|
|
|
363
363
|
var KeyringService = class {
|
|
364
364
|
store;
|
|
365
365
|
vault = null;
|
|
366
|
+
/** Vault key kept in memory for re-encryption operations (addOwner, switchOwner). */
|
|
367
|
+
vaultKey = null;
|
|
366
368
|
constructor(store) {
|
|
367
369
|
this.store = store;
|
|
368
370
|
}
|
|
@@ -373,9 +375,9 @@ var KeyringService = class {
|
|
|
373
375
|
}
|
|
374
376
|
/**
|
|
375
377
|
* Create a brand-new vault with one owner.
|
|
376
|
-
* Called during `elytro init`. Encrypts with
|
|
378
|
+
* Called during `elytro init`. Encrypts with vault key.
|
|
377
379
|
*/
|
|
378
|
-
async createNewOwner(
|
|
380
|
+
async createNewOwner(vaultKey) {
|
|
379
381
|
const privateKey = generatePrivateKey();
|
|
380
382
|
const account = privateKeyToAccount(privateKey);
|
|
381
383
|
const owner = { id: account.address, key: privateKey };
|
|
@@ -383,26 +385,32 @@ var KeyringService = class {
|
|
|
383
385
|
owners: [owner],
|
|
384
386
|
currentOwnerId: account.address
|
|
385
387
|
};
|
|
386
|
-
const encrypted = await encryptWithKey(
|
|
388
|
+
const encrypted = await encryptWithKey(vaultKey, vault);
|
|
387
389
|
await this.store.save(STORAGE_KEY, encrypted);
|
|
388
390
|
this.vault = vault;
|
|
391
|
+
this.vaultKey = new Uint8Array(vaultKey);
|
|
389
392
|
return account.address;
|
|
390
393
|
}
|
|
391
394
|
// ─── Unlock / Access ────────────────────────────────────────────
|
|
392
395
|
/**
|
|
393
|
-
* Decrypt the vault with the
|
|
394
|
-
* Called automatically by context at CLI startup.
|
|
396
|
+
* Decrypt the vault with the vault key.
|
|
397
|
+
* Called automatically by context at CLI startup via SecretProvider.
|
|
395
398
|
*/
|
|
396
|
-
async unlock(
|
|
399
|
+
async unlock(vaultKey) {
|
|
397
400
|
const encrypted = await this.store.load(STORAGE_KEY);
|
|
398
401
|
if (!encrypted) {
|
|
399
402
|
throw new Error("Keyring not initialized. Run `elytro init` first.");
|
|
400
403
|
}
|
|
401
|
-
this.vault = await decryptWithKey(
|
|
404
|
+
this.vault = await decryptWithKey(vaultKey, encrypted);
|
|
405
|
+
this.vaultKey = new Uint8Array(vaultKey);
|
|
402
406
|
}
|
|
403
|
-
/** Lock the vault, clearing decrypted keys from memory. */
|
|
407
|
+
/** Lock the vault, clearing decrypted keys and vault key from memory. */
|
|
404
408
|
lock() {
|
|
405
409
|
this.vault = null;
|
|
410
|
+
if (this.vaultKey) {
|
|
411
|
+
this.vaultKey.fill(0);
|
|
412
|
+
this.vaultKey = null;
|
|
413
|
+
}
|
|
406
414
|
}
|
|
407
415
|
get isUnlocked() {
|
|
408
416
|
return this.vault !== null;
|
|
@@ -441,22 +449,22 @@ var KeyringService = class {
|
|
|
441
449
|
return privateKeyToAccount(key);
|
|
442
450
|
}
|
|
443
451
|
// ─── Multi-owner management ─────────────────────────────────────
|
|
444
|
-
async addOwner(
|
|
452
|
+
async addOwner() {
|
|
445
453
|
this.ensureUnlocked();
|
|
446
454
|
const privateKey = generatePrivateKey();
|
|
447
455
|
const account = privateKeyToAccount(privateKey);
|
|
448
456
|
this.vault.owners.push({ id: account.address, key: privateKey });
|
|
449
|
-
await this.persistVault(
|
|
457
|
+
await this.persistVault();
|
|
450
458
|
return account.address;
|
|
451
459
|
}
|
|
452
|
-
async switchOwner(ownerId
|
|
460
|
+
async switchOwner(ownerId) {
|
|
453
461
|
this.ensureUnlocked();
|
|
454
462
|
const exists = this.vault.owners.some((o) => o.id === ownerId);
|
|
455
463
|
if (!exists) {
|
|
456
464
|
throw new Error(`Owner ${ownerId} not found in vault.`);
|
|
457
465
|
}
|
|
458
466
|
this.vault.currentOwnerId = ownerId;
|
|
459
|
-
await this.persistVault(
|
|
467
|
+
await this.persistVault();
|
|
460
468
|
}
|
|
461
469
|
// ─── Export / Import (password-based for portability) ───────────
|
|
462
470
|
/**
|
|
@@ -469,18 +477,20 @@ var KeyringService = class {
|
|
|
469
477
|
}
|
|
470
478
|
/**
|
|
471
479
|
* Import vault from a password-encrypted backup.
|
|
472
|
-
* Decrypts with the backup password, then re-encrypts with
|
|
480
|
+
* Decrypts with the backup password, then re-encrypts with vault key.
|
|
473
481
|
*/
|
|
474
|
-
async importVault(encrypted, password2,
|
|
482
|
+
async importVault(encrypted, password2, vaultKey) {
|
|
475
483
|
const vault = await decrypt(password2, encrypted);
|
|
476
484
|
this.vault = vault;
|
|
477
|
-
|
|
485
|
+
this.vaultKey = new Uint8Array(vaultKey);
|
|
486
|
+
const reEncrypted = await encryptWithKey(vaultKey, vault);
|
|
478
487
|
await this.store.save(STORAGE_KEY, reEncrypted);
|
|
479
488
|
}
|
|
480
|
-
// ─── Rekey (
|
|
481
|
-
async rekey(
|
|
489
|
+
// ─── Rekey (vault key rotation) ───────────────────────────────
|
|
490
|
+
async rekey(newVaultKey) {
|
|
482
491
|
this.ensureUnlocked();
|
|
483
|
-
|
|
492
|
+
this.vaultKey = new Uint8Array(newVaultKey);
|
|
493
|
+
await this.persistVault();
|
|
484
494
|
}
|
|
485
495
|
// ─── Internal ───────────────────────────────────────────────────
|
|
486
496
|
getCurrentKey() {
|
|
@@ -498,9 +508,10 @@ var KeyringService = class {
|
|
|
498
508
|
throw new Error("Keyring is locked. Run `elytro init` first.");
|
|
499
509
|
}
|
|
500
510
|
}
|
|
501
|
-
async persistVault(
|
|
511
|
+
async persistVault() {
|
|
502
512
|
if (!this.vault) throw new Error("No vault to persist.");
|
|
503
|
-
|
|
513
|
+
if (!this.vaultKey) throw new Error("No vault key available for re-encryption.");
|
|
514
|
+
const encrypted = await encryptWithKey(this.vaultKey, this.vault);
|
|
504
515
|
await this.store.save(STORAGE_KEY, encrypted);
|
|
505
516
|
}
|
|
506
517
|
};
|
|
@@ -555,11 +566,36 @@ function resolveBundler(chainId, pimlicoKey) {
|
|
|
555
566
|
return PUBLIC_BUNDLER[chainId] ?? PUBLIC_BUNDLER[11155420];
|
|
556
567
|
}
|
|
557
568
|
var CHAIN_META = [
|
|
558
|
-
{
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
569
|
+
{
|
|
570
|
+
id: 1,
|
|
571
|
+
name: "Ethereum",
|
|
572
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
573
|
+
blockExplorer: "https://etherscan.io"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: 10,
|
|
577
|
+
name: "Optimism",
|
|
578
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
579
|
+
blockExplorer: "https://optimistic.etherscan.io"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: 42161,
|
|
583
|
+
name: "Arbitrum One",
|
|
584
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
585
|
+
blockExplorer: "https://arbiscan.io"
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
id: 11155111,
|
|
589
|
+
name: "Sepolia",
|
|
590
|
+
nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
|
|
591
|
+
blockExplorer: "https://sepolia.etherscan.io"
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: 11155420,
|
|
595
|
+
name: "Optimism Sepolia",
|
|
596
|
+
nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
|
|
597
|
+
blockExplorer: "https://sepolia-optimism.etherscan.io"
|
|
598
|
+
}
|
|
563
599
|
];
|
|
564
600
|
function buildChains(alchemyKey, pimlicoKey) {
|
|
565
601
|
return CHAIN_META.map((meta) => ({
|
|
@@ -2064,53 +2100,120 @@ var SecurityHookService = class {
|
|
|
2064
2100
|
}
|
|
2065
2101
|
};
|
|
2066
2102
|
|
|
2067
|
-
// src/
|
|
2068
|
-
import {
|
|
2069
|
-
import {
|
|
2070
|
-
|
|
2071
|
-
var
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
async function loadDeviceKey(dataDir) {
|
|
2084
|
-
const path = keyPath(dataDir);
|
|
2085
|
-
try {
|
|
2086
|
-
const st = await stat(path);
|
|
2087
|
-
const mode = st.mode & 511;
|
|
2088
|
-
if (mode !== REQUIRED_MODE) {
|
|
2089
|
-
throw new Error(
|
|
2090
|
-
`Device key has insecure permissions (${modeStr(mode)}). Expected 600. Fix with: chmod 600 ${path}`
|
|
2091
|
-
);
|
|
2103
|
+
// src/providers/keychainProvider.ts
|
|
2104
|
+
import { execFile } from "child_process";
|
|
2105
|
+
import { promisify } from "util";
|
|
2106
|
+
var execFileAsync = promisify(execFile);
|
|
2107
|
+
var KeychainProvider = class {
|
|
2108
|
+
name = "macos-keychain";
|
|
2109
|
+
service = "elytro-wallet";
|
|
2110
|
+
account = "vault-key";
|
|
2111
|
+
async available() {
|
|
2112
|
+
if (process.platform !== "darwin") return false;
|
|
2113
|
+
try {
|
|
2114
|
+
await execFileAsync("security", ["help"], { timeout: 5e3 });
|
|
2115
|
+
return true;
|
|
2116
|
+
} catch {
|
|
2117
|
+
return process.platform === "darwin";
|
|
2092
2118
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2119
|
+
}
|
|
2120
|
+
async store(secret) {
|
|
2121
|
+
validateKeyLength(secret);
|
|
2122
|
+
const b64 = Buffer.from(secret).toString("base64");
|
|
2123
|
+
try {
|
|
2124
|
+
await execFileAsync("security", [
|
|
2125
|
+
"add-generic-password",
|
|
2126
|
+
"-U",
|
|
2127
|
+
"-s",
|
|
2128
|
+
this.service,
|
|
2129
|
+
"-a",
|
|
2130
|
+
this.account,
|
|
2131
|
+
"-w",
|
|
2132
|
+
b64
|
|
2133
|
+
]);
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
throw new Error(`Failed to store vault key in Keychain: ${err.message}`);
|
|
2100
2136
|
}
|
|
2101
|
-
throw err;
|
|
2102
2137
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2138
|
+
async load() {
|
|
2139
|
+
try {
|
|
2140
|
+
const { stdout } = await execFileAsync("security", [
|
|
2141
|
+
"find-generic-password",
|
|
2142
|
+
"-s",
|
|
2143
|
+
this.service,
|
|
2144
|
+
"-a",
|
|
2145
|
+
this.account,
|
|
2146
|
+
"-w"
|
|
2147
|
+
]);
|
|
2148
|
+
const trimmed = stdout.trim();
|
|
2149
|
+
if (!trimmed) return null;
|
|
2150
|
+
const key = Buffer.from(trimmed, "base64");
|
|
2151
|
+
if (key.length !== 32) {
|
|
2152
|
+
throw new Error(`Keychain vault key has invalid length: expected 32 bytes, got ${key.length}.`);
|
|
2153
|
+
}
|
|
2154
|
+
return new Uint8Array(key);
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
const msg = err.message || "";
|
|
2157
|
+
if (msg.includes("could not be found") || msg.includes("SecKeychainSearchCopyNext")) {
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
throw new Error(`Failed to load vault key from Keychain: ${msg}`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
async delete() {
|
|
2164
|
+
try {
|
|
2165
|
+
await execFileAsync("security", ["delete-generic-password", "-s", this.service, "-a", this.account]);
|
|
2166
|
+
} catch {
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
function validateKeyLength(key) {
|
|
2171
|
+
if (key.length !== 32) {
|
|
2172
|
+
throw new Error(`Invalid vault key: expected 32 bytes, got ${key.length}.`);
|
|
2107
2173
|
}
|
|
2108
2174
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2175
|
+
|
|
2176
|
+
// src/providers/envVarProvider.ts
|
|
2177
|
+
var ENV_KEY = "ELYTRO_VAULT_SECRET";
|
|
2178
|
+
var EnvVarProvider = class {
|
|
2179
|
+
name = "env-var";
|
|
2180
|
+
async available() {
|
|
2181
|
+
return !!process.env[ENV_KEY];
|
|
2182
|
+
}
|
|
2183
|
+
async store(_secret) {
|
|
2184
|
+
throw new Error(
|
|
2185
|
+
"EnvVarProvider is read-only. Cannot store vault key in an environment variable. Use a persistent provider (macOS Keychain) or store the secret manually."
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
async load() {
|
|
2189
|
+
const raw = process.env[ENV_KEY];
|
|
2190
|
+
if (!raw) return null;
|
|
2191
|
+
delete process.env[ENV_KEY];
|
|
2192
|
+
const key = Buffer.from(raw, "base64");
|
|
2193
|
+
if (key.length !== 32) {
|
|
2194
|
+
throw new Error(
|
|
2195
|
+
`${ENV_KEY} has invalid length: expected 32 bytes (base64), got ${key.length}. The value must be a base64-encoded 256-bit key.`
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
return new Uint8Array(key);
|
|
2199
|
+
}
|
|
2200
|
+
async delete() {
|
|
2201
|
+
delete process.env[ENV_KEY];
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
// src/providers/resolveProvider.ts
|
|
2206
|
+
async function resolveProvider() {
|
|
2207
|
+
const keychainProvider = new KeychainProvider();
|
|
2208
|
+
const envProvider = new EnvVarProvider();
|
|
2209
|
+
const initProvider = await keychainProvider.available() ? keychainProvider : null;
|
|
2210
|
+
let loadProvider = null;
|
|
2211
|
+
if (await keychainProvider.available()) {
|
|
2212
|
+
loadProvider = keychainProvider;
|
|
2213
|
+
} else if (await envProvider.available()) {
|
|
2214
|
+
loadProvider = envProvider;
|
|
2215
|
+
}
|
|
2216
|
+
return { initProvider, loadProvider };
|
|
2114
2217
|
}
|
|
2115
2218
|
|
|
2116
2219
|
// src/context.ts
|
|
@@ -2125,9 +2228,13 @@ async function createAppContext() {
|
|
|
2125
2228
|
const defaultChain = chain.currentChain;
|
|
2126
2229
|
walletClient.initForChain(defaultChain);
|
|
2127
2230
|
await sdk.initForChain(defaultChain);
|
|
2128
|
-
const
|
|
2129
|
-
if (
|
|
2130
|
-
await
|
|
2231
|
+
const { loadProvider } = await resolveProvider();
|
|
2232
|
+
if (loadProvider && await keyring.isInitialized()) {
|
|
2233
|
+
const vaultKey = await loadProvider.load();
|
|
2234
|
+
if (vaultKey) {
|
|
2235
|
+
await keyring.unlock(vaultKey);
|
|
2236
|
+
vaultKey.fill(0);
|
|
2237
|
+
}
|
|
2131
2238
|
}
|
|
2132
2239
|
const account = new AccountService({
|
|
2133
2240
|
store,
|
|
@@ -2148,10 +2255,11 @@ async function createAppContext() {
|
|
|
2148
2255
|
}
|
|
2149
2256
|
}
|
|
2150
2257
|
}
|
|
2151
|
-
return { store, keyring, chain, sdk, walletClient, account,
|
|
2258
|
+
return { store, keyring, chain, sdk, walletClient, account, secretProvider: loadProvider };
|
|
2152
2259
|
}
|
|
2153
2260
|
|
|
2154
2261
|
// src/commands/init.ts
|
|
2262
|
+
import { webcrypto as webcrypto2 } from "crypto";
|
|
2155
2263
|
import ora from "ora";
|
|
2156
2264
|
|
|
2157
2265
|
// src/utils/display.ts
|
|
@@ -2219,13 +2327,31 @@ function registerInitCommand(program2, ctx) {
|
|
|
2219
2327
|
heading("Initialize Elytro Wallet");
|
|
2220
2328
|
const spinner = ora("Setting up wallet...").start();
|
|
2221
2329
|
try {
|
|
2222
|
-
const
|
|
2223
|
-
await
|
|
2224
|
-
|
|
2225
|
-
|
|
2330
|
+
const vaultKey = webcrypto2.getRandomValues(new Uint8Array(32));
|
|
2331
|
+
const { initProvider } = await resolveProvider();
|
|
2332
|
+
if (initProvider) {
|
|
2333
|
+
await initProvider.store(vaultKey);
|
|
2334
|
+
spinner.text = `Vault key stored in ${initProvider.name}.`;
|
|
2335
|
+
} else {
|
|
2336
|
+
spinner.stop();
|
|
2337
|
+
const b64 = Buffer.from(vaultKey).toString("base64");
|
|
2338
|
+
console.log("");
|
|
2339
|
+
warn("No persistent secret provider available (not on macOS).");
|
|
2340
|
+
warn("Save the following vault secret \u2014 it will NOT be shown again:");
|
|
2341
|
+
console.log("");
|
|
2342
|
+
console.log(` ELYTRO_VAULT_SECRET="${b64}"`);
|
|
2343
|
+
console.log("");
|
|
2344
|
+
info("Hint", "Set this environment variable before running any elytro command.");
|
|
2345
|
+
spinner.start("Creating wallet...");
|
|
2346
|
+
}
|
|
2347
|
+
await ctx.keyring.createNewOwner(vaultKey);
|
|
2348
|
+
vaultKey.fill(0);
|
|
2226
2349
|
spinner.succeed("Wallet initialized.");
|
|
2227
2350
|
console.log("");
|
|
2228
2351
|
info("Data", ctx.store.dataDir);
|
|
2352
|
+
if (initProvider) {
|
|
2353
|
+
info("Secret Provider", initProvider.name);
|
|
2354
|
+
}
|
|
2229
2355
|
console.log("");
|
|
2230
2356
|
success("Run `elytro account create --chain <chainId>` to create your first smart account.");
|
|
2231
2357
|
} catch (err) {
|
|
@@ -2257,7 +2383,7 @@ init_sponsor();
|
|
|
2257
2383
|
function registerAccountCommand(program2, ctx) {
|
|
2258
2384
|
const account = program2.command("account").description("Manage smart accounts");
|
|
2259
2385
|
account.command("create").description("Create a new smart account").requiredOption("-c, --chain <chainId>", "Target chain ID").option("-a, --alias <alias>", "Human-readable alias (default: random)").action(async (opts) => {
|
|
2260
|
-
if (!ctx.
|
|
2386
|
+
if (!ctx.keyring.isUnlocked) {
|
|
2261
2387
|
error("Wallet not initialized. Run `elytro init` first.");
|
|
2262
2388
|
process.exitCode = 1;
|
|
2263
2389
|
return;
|
|
@@ -2306,7 +2432,7 @@ function registerAccountCommand(program2, ctx) {
|
|
|
2306
2432
|
}
|
|
2307
2433
|
});
|
|
2308
2434
|
account.command("activate").description("Deploy the smart contract on-chain").argument("[account]", "Alias or address (default: current)").option("--no-sponsor", "Skip sponsorship check (user pays gas)").action(async (target, opts) => {
|
|
2309
|
-
if (!ctx.
|
|
2435
|
+
if (!ctx.keyring.isUnlocked) {
|
|
2310
2436
|
error("Wallet not initialized. Run `elytro init` first.");
|
|
2311
2437
|
process.exitCode = 1;
|
|
2312
2438
|
return;
|
|
@@ -2704,7 +2830,7 @@ function registerTxCommand(program2, ctx) {
|
|
|
2704
2830
|
}
|
|
2705
2831
|
});
|
|
2706
2832
|
tx.command("send").description("Send a transaction on-chain").argument("[account]", "Source account alias or address (default: current)").option("--tx <spec...>", 'Transaction spec: "to:0xAddr,value:0.1,data:0x..." (repeatable, ordered)').option("--no-sponsor", "Skip sponsorship check").option("--no-hook", "Skip SecurityHook signing (bypass 2FA)").option("--userop <json>", "Send a pre-built UserOp JSON (skips build step)").action(async (target, opts) => {
|
|
2707
|
-
if (!ctx.
|
|
2833
|
+
if (!ctx.keyring.isUnlocked) {
|
|
2708
2834
|
handleTxError(new TxError(ERR_ACCOUNT_NOT_READY, "Wallet not initialized. Run `elytro init` first."));
|
|
2709
2835
|
return;
|
|
2710
2836
|
}
|
|
@@ -2902,7 +3028,7 @@ function registerTxCommand(program2, ctx) {
|
|
|
2902
3028
|
}
|
|
2903
3029
|
});
|
|
2904
3030
|
tx.command("simulate").description("Preview a transaction (gas estimate, sponsor check)").argument("[account]", "Source account alias or address (default: current)").option("--tx <spec...>", 'Transaction spec: "to:0xAddr,value:0.1,data:0x..." (repeatable, ordered)').option("--no-sponsor", "Skip sponsorship check").action(async (target, opts) => {
|
|
2905
|
-
if (!ctx.
|
|
3031
|
+
if (!ctx.keyring.isUnlocked) {
|
|
2906
3032
|
handleTxError(new TxError(ERR_ACCOUNT_NOT_READY, "Wallet not initialized. Run `elytro init` first."));
|
|
2907
3033
|
return;
|
|
2908
3034
|
}
|
|
@@ -3500,7 +3626,7 @@ function handleSecurityError(err) {
|
|
|
3500
3626
|
process.exitCode = 1;
|
|
3501
3627
|
}
|
|
3502
3628
|
function initSecurityContext(ctx) {
|
|
3503
|
-
if (!ctx.
|
|
3629
|
+
if (!ctx.keyring.isUnlocked) {
|
|
3504
3630
|
throw new SecurityError(ERR_ACCOUNT_NOT_READY2, "Wallet not initialized. Run `elytro init` first.");
|
|
3505
3631
|
}
|
|
3506
3632
|
const current = ctx.account.currentAccount;
|