genlayer 0.31.0 → 0.32.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CLAUDE.md +55 -0
  3. package/README.md +121 -8
  4. package/dist/index.js +7161 -3706
  5. package/docs/delegator-guide.md +203 -0
  6. package/docs/validator-guide.md +291 -0
  7. package/package.json +2 -2
  8. package/src/commands/account/create.ts +29 -0
  9. package/src/commands/account/export.ts +106 -0
  10. package/src/commands/account/import.ts +135 -0
  11. package/src/commands/account/index.ts +126 -0
  12. package/src/commands/account/list.ts +34 -0
  13. package/src/commands/account/lock.ts +39 -0
  14. package/src/commands/account/remove.ts +30 -0
  15. package/src/commands/account/send.ts +156 -0
  16. package/src/commands/account/show.ts +74 -0
  17. package/src/commands/account/unlock.ts +51 -0
  18. package/src/commands/account/use.ts +21 -0
  19. package/src/commands/network/index.ts +18 -3
  20. package/src/commands/network/setNetwork.ts +43 -26
  21. package/src/commands/staking/StakingAction.ts +157 -0
  22. package/src/commands/staking/delegatorClaim.ts +41 -0
  23. package/src/commands/staking/delegatorExit.ts +50 -0
  24. package/src/commands/staking/delegatorJoin.ts +44 -0
  25. package/src/commands/staking/index.ts +251 -0
  26. package/src/commands/staking/setIdentity.ts +66 -0
  27. package/src/commands/staking/setOperator.ts +40 -0
  28. package/src/commands/staking/stakingInfo.ts +300 -0
  29. package/src/commands/staking/validatorClaim.ts +38 -0
  30. package/src/commands/staking/validatorDeposit.ts +35 -0
  31. package/src/commands/staking/validatorExit.ts +44 -0
  32. package/src/commands/staking/validatorJoin.ts +47 -0
  33. package/src/commands/staking/validatorPrime.ts +35 -0
  34. package/src/commands/staking/wizard.ts +802 -0
  35. package/src/index.ts +4 -2
  36. package/src/lib/actions/BaseAction.ts +114 -55
  37. package/src/lib/config/ConfigFileManager.ts +143 -0
  38. package/src/lib/config/KeychainManager.ts +23 -7
  39. package/tests/actions/create.test.ts +41 -21
  40. package/tests/actions/deploy.test.ts +7 -0
  41. package/tests/actions/lock.test.ts +33 -13
  42. package/tests/actions/setNetwork.test.ts +18 -57
  43. package/tests/actions/staking.test.ts +323 -0
  44. package/tests/actions/unlock.test.ts +51 -33
  45. package/tests/commands/account.test.ts +146 -0
  46. package/tests/commands/network.test.ts +10 -10
  47. package/tests/commands/staking.test.ts +333 -0
  48. package/tests/index.test.ts +6 -2
  49. package/tests/libs/baseAction.test.ts +71 -42
  50. package/tests/libs/configFileManager.test.ts +8 -1
  51. package/tests/libs/keychainManager.test.ts +56 -16
  52. package/src/commands/keygen/create.ts +0 -23
  53. package/src/commands/keygen/index.ts +0 -39
  54. package/src/commands/keygen/lock.ts +0 -31
  55. package/src/commands/keygen/unlock.ts +0 -41
  56. package/src/lib/interfaces/KeystoreData.ts +0 -5
  57. package/tests/commands/keygen.test.ts +0 -123
@@ -0,0 +1,203 @@
1
+ # Delegator Guide
2
+
3
+ This guide walks you through delegating GEN tokens to a validator on the GenLayer testnet.
4
+
5
+ ## What is Delegation?
6
+
7
+ Delegation allows you to stake your GEN tokens with an existing validator without running validator infrastructure yourself. You earn staking rewards proportional to your stake.
8
+
9
+ ## Prerequisites
10
+
11
+ - Node.js installed
12
+ - GenLayer CLI installed (`npm install -g genlayer`)
13
+ - GEN tokens for staking
14
+
15
+ ## Step 1: Create an Account
16
+
17
+ ```bash
18
+ genlayer account create
19
+ ```
20
+
21
+ You'll be prompted to set a password. This creates an encrypted keystore file.
22
+
23
+ ## Step 2: Set Network to Testnet
24
+
25
+ ```bash
26
+ genlayer network testnet-asimov
27
+ ```
28
+
29
+ ## Step 3: Fund Your Account
30
+
31
+ Transfer GEN tokens to your address. Check your balance:
32
+
33
+ ```bash
34
+ genlayer account
35
+ ```
36
+
37
+ ## Step 4: Check Minimum Delegation
38
+
39
+ ```bash
40
+ genlayer staking epoch-info
41
+ ```
42
+
43
+ Note the `delegatorMinStake` - you need at least this amount.
44
+
45
+ ## Step 5: Find a Validator
46
+
47
+ List all active validators:
48
+
49
+ ```bash
50
+ genlayer staking active-validators
51
+ ```
52
+
53
+ Output:
54
+ ```json
55
+ {
56
+ count: 6,
57
+ validators: [
58
+ '0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130',
59
+ '0xe9246A020cbb4fC6C46e60677981879c9219e8B9',
60
+ ...
61
+ ]
62
+ }
63
+ ```
64
+
65
+ Get details about a specific validator:
66
+
67
+ ```bash
68
+ genlayer staking validator-info --validator 0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130
69
+ ```
70
+
71
+ Look for:
72
+ - `live: true` - Validator is active
73
+ - `banned: 'Not banned'` - Validator is in good standing
74
+ - `identity` - Validator's metadata (moniker, website, etc.)
75
+
76
+ ## Step 6: Unlock Your Account (Optional)
77
+
78
+ For convenience:
79
+
80
+ ```bash
81
+ genlayer account unlock
82
+ ```
83
+
84
+ ## Step 7: Delegate to a Validator
85
+
86
+ ```bash
87
+ genlayer staking delegator-join --validator 0xa8f1...130 --amount 100gen
88
+ ```
89
+
90
+ Options:
91
+ - `--validator <address>` - Validator address to delegate to (required)
92
+ - `--amount <amount>` - Amount to stake (e.g., `100gen`)
93
+
94
+ ## Step 8: Verify Your Delegation
95
+
96
+ ```bash
97
+ genlayer staking delegation-info --validator 0xa8f1...130
98
+ ```
99
+
100
+ Output:
101
+ ```json
102
+ {
103
+ delegator: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
104
+ validator: '0xa8f1BF1e5e709593b4468d7ac5DC315Ea3CAe130',
105
+ shares: '100000000000000000000',
106
+ stake: '100 GEN',
107
+ projectedReward: '0.2 GEN per epoch',
108
+ pendingDeposits: 'None',
109
+ pendingWithdrawals: 'None'
110
+ }
111
+ ```
112
+
113
+ The `projectedReward` shows your estimated earnings per epoch based on current inflation and your stake weight.
114
+
115
+ ## Managing Your Delegation
116
+
117
+ ### Check Your Stake
118
+
119
+ ```bash
120
+ genlayer staking delegation-info --validator 0xa8f1...130
121
+ ```
122
+
123
+ ### Withdraw (Exit) Delegation
124
+
125
+ To withdraw your stake:
126
+
127
+ ```bash
128
+ genlayer staking delegator-exit --validator 0xa8f1...130 --shares 50
129
+ ```
130
+
131
+ Options:
132
+ - `--validator <address>` - Validator you delegated to
133
+ - `--shares <shares>` - Number of shares to withdraw
134
+
135
+ This initiates a withdrawal. Your tokens enter an **unbonding period of 7 epochs** before they can be claimed.
136
+
137
+ Check your pending withdrawals with `delegation-info`:
138
+ ```json
139
+ pendingWithdrawals: [
140
+ {
141
+ epoch: '5',
142
+ shares: '50',
143
+ stake: '50 GEN',
144
+ claimableAtEpoch: '12',
145
+ status: 'Unbonding (4 epochs remaining)'
146
+ }
147
+ ]
148
+ ```
149
+
150
+ ### Claim Withdrawals
151
+
152
+ After the 7-epoch unbonding period, claim your tokens:
153
+
154
+ ```bash
155
+ genlayer staking delegator-claim --validator 0xa8f1...130
156
+ ```
157
+
158
+ ## Choosing a Validator
159
+
160
+ Consider these factors when choosing a validator:
161
+
162
+ 1. **Uptime** - Validators with high uptime earn more rewards
163
+ 2. **Reputation** - Check their identity metadata and community presence
164
+ 3. **Stake** - Higher stake may indicate trust from the community
165
+ 4. **Not banned/quarantined** - Avoid validators with issues
166
+
167
+ Check quarantined validators:
168
+ ```bash
169
+ genlayer staking quarantined-validators
170
+ ```
171
+
172
+ Check banned validators:
173
+ ```bash
174
+ genlayer staking banned-validators
175
+ ```
176
+
177
+ ## Troubleshooting
178
+
179
+ ### "No account found"
180
+ Run `genlayer account create` first.
181
+
182
+ ### "Insufficient balance"
183
+ Ensure you have enough GEN. Check with `genlayer account`.
184
+
185
+ ### "Below minimum stake"
186
+ Check minimum with `genlayer staking epoch-info` and increase your amount.
187
+
188
+ ### "Validator not found"
189
+ Verify the validator address is correct and they are still active.
190
+
191
+ ### Transaction Stuck
192
+ Check the transaction status:
193
+ ```bash
194
+ genlayer receipt <tx-hash>
195
+ ```
196
+
197
+ ## Lock Your Account
198
+
199
+ When done, lock your account:
200
+
201
+ ```bash
202
+ genlayer account lock
203
+ ```
@@ -0,0 +1,291 @@
1
+ # Validator Guide
2
+
3
+ This guide walks you through becoming a validator on the GenLayer testnet using the CLI.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js installed
8
+ - GenLayer CLI installed (`npm install -g genlayer`)
9
+ - GEN tokens for staking (minimum stake required)
10
+
11
+ ## Quick Start: Validator Wizard
12
+
13
+ The easiest way to become a validator is using the interactive wizard:
14
+
15
+ ```bash
16
+ genlayer staking wizard
17
+ ```
18
+
19
+ The wizard guides you through all steps:
20
+ 1. Account setup (create or select)
21
+ 2. Network selection
22
+ 3. Balance verification
23
+ 4. Operator setup (optional, recommended for security)
24
+ 5. Stake amount selection
25
+ 6. Validator creation
26
+ 7. Identity setup (moniker, website, etc.)
27
+
28
+ If you prefer manual setup, follow the steps below.
29
+
30
+ ---
31
+
32
+ ## Manual Setup
33
+
34
+ ## Step 1: Create an Owner Account
35
+
36
+ ```bash
37
+ genlayer account create --name owner
38
+ ```
39
+
40
+ You'll be prompted to set a password. This creates an encrypted keystore file in standard web3 format.
41
+
42
+ The owner account holds your staked funds and controls the validator. Keep it secure.
43
+
44
+ ## Step 2: View Your Account
45
+
46
+ ```bash
47
+ genlayer account show
48
+ ```
49
+
50
+ Output:
51
+ ```
52
+ {
53
+ name: 'owner',
54
+ address: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
55
+ balance: '0 GEN',
56
+ network: 'localnet',
57
+ status: 'locked',
58
+ active: true
59
+ }
60
+ ```
61
+
62
+ ## Step 3: Set Network to Testnet
63
+
64
+ ```bash
65
+ genlayer network testnet-asimov
66
+ ```
67
+
68
+ Verify with:
69
+ ```bash
70
+ genlayer account show
71
+ ```
72
+
73
+ You should see `network: 'Asimov Testnet'`.
74
+
75
+ ## Step 4: Fund Your Account
76
+
77
+ Transfer GEN tokens to your address:
78
+ - Use the faucet (if available)
79
+ - Transfer from another funded account using `genlayer account send`
80
+
81
+ ## Step 5: Check Staking Requirements
82
+
83
+ ```bash
84
+ genlayer staking epoch-info
85
+ ```
86
+
87
+ Output:
88
+ ```
89
+ {
90
+ currentEpoch: '2',
91
+ epochStarted: '2025-01-15T00:00:00.000Z',
92
+ nextEpochEstimate: '2025-01-16T00:00:00.000Z',
93
+ timeUntilNextEpoch: '12h 30m',
94
+ minEpochDuration: '24h 0m',
95
+ validatorMinStake: '42000 GEN',
96
+ delegatorMinStake: '42 GEN',
97
+ activeValidatorsCount: '6',
98
+ epochInflation: '1000 GEN',
99
+ totalWeight: '500000000000000000000000',
100
+ totalClaimed: '500 GEN'
101
+ }
102
+ ```
103
+
104
+ Note the `validatorMinStake` - you need at least this amount.
105
+
106
+ ## Step 6: Unlock Your Account (Optional)
107
+
108
+ For convenience, unlock your account to avoid entering password repeatedly:
109
+
110
+ ```bash
111
+ genlayer account unlock
112
+ ```
113
+
114
+ This caches your private key in the OS keychain.
115
+
116
+ ## Step 7: Join as Validator
117
+
118
+ ```bash
119
+ genlayer staking validator-join --amount 42000gen --operator 0xOperator...
120
+ ```
121
+
122
+ Options:
123
+ - `--amount <amount>` - Stake amount (e.g., `42000gen` or `42000`)
124
+ - `--operator <address>` - Operator address (recommended, see below)
125
+
126
+ ### Why Use an Operator Address?
127
+
128
+ **Recommended:** Use a separate operator address for security.
129
+
130
+ - **Validator wallet** - Holds your staked funds (keep offline/cold)
131
+ - **Operator wallet** - Signs blocks and performs validator duties (hot wallet on server)
132
+
133
+ This way, if your operator server is compromised, your staked funds remain safe.
134
+
135
+ If you already have an operator wallet (e.g., from geth, foundry, or another tool), you can use its address directly. Otherwise, create one:
136
+
137
+ ```bash
138
+ # Create operator account (skip if you already have one)
139
+ genlayer account create --name operator
140
+
141
+ # View operator address
142
+ genlayer account show --account operator
143
+ # Address: 0xOperator123...
144
+
145
+ # Export keystore for validator node software (standard web3 format)
146
+ genlayer account export --account operator --output ./operator-keystore.json
147
+
148
+ # Join as validator with separate operator
149
+ genlayer staking validator-join --amount 42000gen --operator 0xOperator123...
150
+ ```
151
+
152
+ Transfer `operator-keystore.json` to your validator server and import it into your validator node software. The keystore is in standard web3 format, compatible with geth, foundry, and other Ethereum tools.
153
+
154
+ You can change the operator later:
155
+
156
+ ```bash
157
+ genlayer staking set-operator --validator 0xYourValidator... --operator 0xNewOperator...
158
+ ```
159
+
160
+ ## Step 8: Verify Your Validator Status
161
+
162
+ ```bash
163
+ genlayer staking validator-info
164
+ ```
165
+
166
+ Output:
167
+ ```
168
+ {
169
+ validator: '0x86D0d159483CBf01E920ECfF8bB7F0Cd7E964E7E',
170
+ vStake: '42000 GEN',
171
+ vShares: '42000000000000000000000',
172
+ live: true,
173
+ banned: 'Not banned',
174
+ ...
175
+ }
176
+ ```
177
+
178
+ ## Step 9: Set Validator Identity (Metadata)
179
+
180
+ Set your validator's public identity so delegators can find you:
181
+
182
+ ```bash
183
+ genlayer staking set-identity \
184
+ --validator 0x86D0...7E \
185
+ --moniker "My Validator" \
186
+ --website "https://myvalidator.com" \
187
+ --description "Reliable validator with 99.9% uptime" \
188
+ --twitter "myvalidator" \
189
+ --github "myvalidator"
190
+ ```
191
+
192
+ **Required:**
193
+ - `--validator <address>` - Your validator address
194
+ - `--moniker <name>` - Display name for your validator
195
+
196
+ **Optional:**
197
+ - `--logo-uri <uri>` - Logo image URL
198
+ - `--website <url>` - Website URL
199
+ - `--description <text>` - Description of your validator
200
+ - `--email <email>` - Contact email
201
+ - `--twitter <handle>` - Twitter handle
202
+ - `--telegram <handle>` - Telegram handle
203
+ - `--github <handle>` - GitHub handle
204
+ - `--extra-cid <cid>` - Additional data as IPFS CID
205
+
206
+ Your identity will show in `validator-info`:
207
+
208
+ ```bash
209
+ genlayer staking validator-info
210
+ ```
211
+
212
+ Output will include:
213
+ ```
214
+ {
215
+ ...
216
+ identity: {
217
+ moniker: 'My Validator',
218
+ website: 'https://myvalidator.com',
219
+ twitter: 'myvalidator',
220
+ github: 'myvalidator'
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Managing Your Validator
226
+
227
+ ### Add More Stake
228
+
229
+ ```bash
230
+ genlayer staking validator-deposit --amount 1000gen
231
+ ```
232
+
233
+ ### Check Active Validators
234
+
235
+ ```bash
236
+ genlayer staking active-validators
237
+ ```
238
+
239
+ ### Exit as Validator
240
+
241
+ ```bash
242
+ genlayer staking validator-exit --shares 100
243
+ ```
244
+
245
+ This initiates a withdrawal. Your tokens enter an **unbonding period of 7 epochs** before they can be claimed.
246
+
247
+ Check your pending withdrawals with `validator-info`:
248
+ ```
249
+ selfStakePendingWithdrawals: [
250
+ {
251
+ epoch: '5',
252
+ shares: '100',
253
+ stake: '100 GEN',
254
+ claimableAtEpoch: '12',
255
+ status: 'Unbonding (4 epochs remaining)'
256
+ }
257
+ ]
258
+ ```
259
+
260
+ ### Claim Withdrawals
261
+
262
+ After the 7-epoch unbonding period:
263
+
264
+ ```bash
265
+ genlayer staking validator-claim
266
+ ```
267
+
268
+ ## Troubleshooting
269
+
270
+ ### "No account found"
271
+ Run `genlayer account create` first.
272
+
273
+ ### "Insufficient balance"
274
+ Ensure you have enough GEN. Check with `genlayer account show`.
275
+
276
+ ### "Below minimum stake"
277
+ Check minimum with `genlayer staking epoch-info` and increase your stake amount.
278
+
279
+ ### Transaction Stuck
280
+ Check the transaction status:
281
+ ```bash
282
+ genlayer receipt <tx-hash>
283
+ ```
284
+
285
+ ## Lock Your Account
286
+
287
+ When done, lock your account to remove the cached private key:
288
+
289
+ ```bash
290
+ genlayer account lock
291
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.31.0",
3
+ "version": "0.32.1",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -65,7 +65,7 @@
65
65
  "dotenv": "^17.0.0",
66
66
  "ethers": "^6.13.4",
67
67
  "fs-extra": "^11.3.0",
68
- "genlayer-js": "^0.16.0",
68
+ "genlayer-js": "^0.18.7",
69
69
  "inquirer": "^12.0.0",
70
70
  "keytar": "^7.9.0",
71
71
  "node-fetch": "^3.0.0",
@@ -0,0 +1,29 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+
3
+ export interface CreateAccountOptions {
4
+ name: string;
5
+ overwrite: boolean;
6
+ setActive?: boolean;
7
+ }
8
+
9
+ export class CreateAccountAction extends BaseAction {
10
+ constructor() {
11
+ super();
12
+ }
13
+
14
+ async execute(options: CreateAccountOptions): Promise<void> {
15
+ try {
16
+ this.startSpinner(`Creating account '${options.name}'...`);
17
+ await this.createKeypairByName(options.name, options.overwrite);
18
+
19
+ if (options.setActive !== false) {
20
+ this.setActiveAccount(options.name);
21
+ }
22
+
23
+ const keystorePath = this.getKeystorePath(options.name);
24
+ this.succeedSpinner(`Account '${options.name}' created at: ${keystorePath}`);
25
+ } catch (error) {
26
+ this.failSpinner("Failed to create account", error);
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,106 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+ import {ethers} from "ethers";
3
+ import {writeFileSync, existsSync, readFileSync} from "fs";
4
+ import path from "path";
5
+
6
+ export interface ExportAccountOptions {
7
+ account?: string;
8
+ output: string;
9
+ password?: string;
10
+ sourcePassword?: string;
11
+ overwrite?: boolean;
12
+ }
13
+
14
+ export class ExportAccountAction extends BaseAction {
15
+ constructor() {
16
+ super();
17
+ }
18
+
19
+ async execute(options: ExportAccountOptions): Promise<void> {
20
+ try {
21
+ if (options.account) {
22
+ this.accountOverride = options.account;
23
+ }
24
+
25
+ const accountName = this.resolveAccountName();
26
+ const keystorePath = this.getKeystorePath(accountName);
27
+
28
+ if (!existsSync(keystorePath)) {
29
+ this.failSpinner(`Account '${accountName}' not found.`);
30
+ }
31
+
32
+ const outputPath = path.resolve(options.output);
33
+
34
+ if (existsSync(outputPath) && !options.overwrite) {
35
+ this.failSpinner(`Output file already exists: ${outputPath}`);
36
+ }
37
+
38
+ // Get the private key
39
+ const privateKey = await this.getPrivateKeyForExport(accountName, keystorePath, options.sourcePassword);
40
+
41
+ // Get password for the exported keystore
42
+ let password: string;
43
+ if (options.password) {
44
+ password = options.password;
45
+ } else {
46
+ password = await this.promptPassword("Enter password for exported keystore (minimum 8 characters):");
47
+ const confirmPassword = await this.promptPassword("Confirm password:");
48
+ if (password !== confirmPassword) {
49
+ this.failSpinner("Passwords do not match");
50
+ }
51
+ }
52
+
53
+ if (password.length < BaseAction.MIN_PASSWORD_LENGTH) {
54
+ this.failSpinner(`Password must be at least ${BaseAction.MIN_PASSWORD_LENGTH} characters long`);
55
+ }
56
+
57
+ this.startSpinner(`Exporting account '${accountName}'...`);
58
+
59
+ const wallet = new ethers.Wallet(privateKey);
60
+ const encryptedJson = await wallet.encrypt(password);
61
+
62
+ // Write standard web3 keystore format (compatible with geth, foundry, etc.)
63
+ writeFileSync(outputPath, encryptedJson);
64
+
65
+ this.succeedSpinner(`Account '${accountName}' exported to: ${outputPath}`);
66
+ this.logInfo(`Address: ${wallet.address}`);
67
+ } catch (error) {
68
+ this.failSpinner("Failed to export account", error);
69
+ }
70
+ }
71
+
72
+ private async getPrivateKeyForExport(
73
+ accountName: string,
74
+ keystorePath: string,
75
+ sourcePassword?: string
76
+ ): Promise<string> {
77
+ // First check if key is cached in keychain
78
+ const isAvailable = await this.keychainManager.isKeychainAvailable();
79
+ if (isAvailable) {
80
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
81
+ if (cachedKey) {
82
+ return cachedKey;
83
+ }
84
+ }
85
+
86
+ // Need to decrypt the keystore
87
+ const fileContent = readFileSync(keystorePath, "utf-8");
88
+ const parsed = JSON.parse(fileContent);
89
+
90
+ const encryptedJson = parsed.encrypted || fileContent;
91
+
92
+ const password = sourcePassword || await this.promptPassword(`Enter password to unlock '${accountName}':`);
93
+
94
+ this.startSpinner("Decrypting keystore...");
95
+
96
+ try {
97
+ const wallet = await ethers.Wallet.fromEncryptedJson(encryptedJson, password);
98
+ this.stopSpinner();
99
+ return wallet.privateKey;
100
+ } catch {
101
+ this.failSpinner("Failed to decrypt keystore. Wrong password?");
102
+ }
103
+
104
+ throw new Error("Unreachable");
105
+ }
106
+ }