genlayer 0.32.0 → 0.32.2
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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/dist/index.js +1425 -222
- package/docs/delegator-guide.md +6 -6
- package/docs/validator-guide.md +51 -18
- package/package.json +2 -2
- package/src/commands/account/create.ts +10 -4
- package/src/commands/account/export.ts +106 -0
- package/src/commands/account/import.ts +85 -31
- package/src/commands/account/index.ts +77 -18
- package/src/commands/account/list.ts +34 -0
- package/src/commands/account/lock.ts +16 -7
- package/src/commands/account/remove.ts +30 -0
- package/src/commands/account/send.ts +14 -8
- package/src/commands/account/show.ts +22 -8
- package/src/commands/account/unlock.ts +20 -10
- package/src/commands/account/use.ts +21 -0
- package/src/commands/network/index.ts +18 -3
- package/src/commands/network/setNetwork.ts +38 -22
- package/src/commands/staking/StakingAction.ts +51 -19
- package/src/commands/staking/delegatorJoin.ts +2 -0
- package/src/commands/staking/index.ts +29 -2
- package/src/commands/staking/setIdentity.ts +5 -0
- package/src/commands/staking/stakingInfo.ts +29 -21
- package/src/commands/staking/wizard.ts +809 -0
- package/src/lib/actions/BaseAction.ts +71 -45
- package/src/lib/config/ConfigFileManager.ts +143 -0
- package/src/lib/config/KeychainManager.ts +68 -8
- package/tests/actions/create.test.ts +30 -10
- package/tests/actions/deploy.test.ts +7 -0
- package/tests/actions/lock.test.ts +28 -8
- package/tests/actions/unlock.test.ts +44 -26
- package/tests/commands/account.test.ts +43 -18
- package/tests/commands/network.test.ts +10 -10
- package/tests/commands/staking.test.ts +122 -0
- package/tests/libs/baseAction.test.ts +64 -41
- package/tests/libs/configFileManager.test.ts +8 -1
- package/tests/libs/keychainManager.test.ts +62 -18
- package/src/lib/interfaces/KeystoreData.ts +0 -5
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
2
|
|
|
3
|
+
export interface LockAccountOptions {
|
|
4
|
+
account?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export class LockAccountAction extends BaseAction {
|
|
4
|
-
async execute(): Promise<void> {
|
|
8
|
+
async execute(options?: LockAccountOptions): Promise<void> {
|
|
5
9
|
this.startSpinner("Checking keychain availability...");
|
|
6
10
|
|
|
7
11
|
const keychainAvailable = await this.keychainManager.isKeychainAvailable();
|
|
@@ -10,19 +14,24 @@ export class LockAccountAction extends BaseAction {
|
|
|
10
14
|
return;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
if (options?.account) {
|
|
18
|
+
this.accountOverride = options.account;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const accountName = this.resolveAccountName();
|
|
22
|
+
this.setSpinnerText(`Checking for cached private key for '${accountName}'...`);
|
|
14
23
|
|
|
15
|
-
const hasCachedKey = await this.keychainManager.getPrivateKey();
|
|
24
|
+
const hasCachedKey = await this.keychainManager.getPrivateKey(accountName);
|
|
16
25
|
if (!hasCachedKey) {
|
|
17
|
-
this.succeedSpinner(
|
|
26
|
+
this.succeedSpinner(`Account '${accountName}' is already locked.`);
|
|
18
27
|
return;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
this.setSpinnerText(
|
|
30
|
+
this.setSpinnerText(`Removing private key for '${accountName}' from OS keychain...`);
|
|
22
31
|
|
|
23
32
|
try {
|
|
24
|
-
await this.keychainManager.removePrivateKey();
|
|
25
|
-
this.succeedSpinner(
|
|
33
|
+
await this.keychainManager.removePrivateKey(accountName);
|
|
34
|
+
this.succeedSpinner(`Account '${accountName}' locked! Private key removed from OS keychain.`);
|
|
26
35
|
} catch (error) {
|
|
27
36
|
this.failSpinner("Failed to lock account.", error);
|
|
28
37
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
|
+
|
|
3
|
+
export class RemoveAccountAction extends BaseAction {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async execute(name: string, options: {force?: boolean}): Promise<void> {
|
|
9
|
+
try {
|
|
10
|
+
if (!this.accountExists(name)) {
|
|
11
|
+
this.failSpinner(`Account '${name}' does not exist.`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!options.force) {
|
|
16
|
+
await this.confirmPrompt(`Are you sure you want to remove account '${name}'? This cannot be undone.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Remove from keychain if unlocked
|
|
20
|
+
await this.keychainManager.removePrivateKey(name);
|
|
21
|
+
|
|
22
|
+
// Remove keystore file
|
|
23
|
+
this.removeAccount(name);
|
|
24
|
+
|
|
25
|
+
this.logSuccess(`Account '${name}' removed`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
this.failSpinner("Failed to remove account", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -4,13 +4,13 @@ import {createClient, createAccount} from "genlayer-js";
|
|
|
4
4
|
import type {GenLayerChain, Address, Hash} from "genlayer-js/types";
|
|
5
5
|
import {readFileSync, existsSync} from "fs";
|
|
6
6
|
import {ethers} from "ethers";
|
|
7
|
-
import {KeystoreData} from "../../lib/interfaces/KeystoreData";
|
|
8
7
|
|
|
9
8
|
export interface SendOptions {
|
|
10
9
|
to: string;
|
|
11
10
|
amount: string;
|
|
12
11
|
rpc?: string;
|
|
13
12
|
network?: string;
|
|
13
|
+
account?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class SendAction extends BaseAction {
|
|
@@ -48,14 +48,20 @@ export class SendAction extends BaseAction {
|
|
|
48
48
|
this.startSpinner("Preparing transfer...");
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
|
|
51
|
+
if (options.account) {
|
|
52
|
+
this.accountOverride = options.account;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const accountName = this.resolveAccountName();
|
|
56
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
52
57
|
|
|
53
|
-
if (!
|
|
54
|
-
this.failSpinner(
|
|
58
|
+
if (!existsSync(keystorePath)) {
|
|
59
|
+
this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
const
|
|
63
|
+
const keystoreJson = readFileSync(keystorePath, "utf-8");
|
|
64
|
+
const keystoreData = JSON.parse(keystoreJson);
|
|
59
65
|
|
|
60
66
|
if (!this.isValidKeystoreFormat(keystoreData)) {
|
|
61
67
|
this.failSpinner("Invalid keystore format.");
|
|
@@ -63,16 +69,16 @@ export class SendAction extends BaseAction {
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
// Get private key
|
|
66
|
-
const cachedKey = await this.keychainManager.getPrivateKey();
|
|
72
|
+
const cachedKey = await this.keychainManager.getPrivateKey(accountName);
|
|
67
73
|
let privateKey: string;
|
|
68
74
|
|
|
69
75
|
if (cachedKey) {
|
|
70
76
|
privateKey = cachedKey;
|
|
71
77
|
} else {
|
|
72
78
|
this.stopSpinner();
|
|
73
|
-
const password = await this.promptPassword(
|
|
79
|
+
const password = await this.promptPassword(`Enter password to unlock account '${accountName}':`);
|
|
74
80
|
this.startSpinner("Preparing transfer...");
|
|
75
|
-
const wallet = await ethers.Wallet.fromEncryptedJson(
|
|
81
|
+
const wallet = await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
|
|
76
82
|
privateKey = wallet.privateKey;
|
|
77
83
|
}
|
|
78
84
|
|
|
@@ -3,7 +3,11 @@ import {formatEther} from "viem";
|
|
|
3
3
|
import {createClient} from "genlayer-js";
|
|
4
4
|
import type {GenLayerChain, Address} from "genlayer-js/types";
|
|
5
5
|
import {readFileSync, existsSync} from "fs";
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
export interface ShowAccountOptions {
|
|
8
|
+
rpc?: string;
|
|
9
|
+
account?: string;
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
export class ShowAccountAction extends BaseAction {
|
|
9
13
|
constructor() {
|
|
@@ -14,42 +18,52 @@ export class ShowAccountAction extends BaseAction {
|
|
|
14
18
|
return resolveNetwork(this.getConfig().network);
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
async execute(): Promise<void> {
|
|
21
|
+
async execute(options?: ShowAccountOptions): Promise<void> {
|
|
18
22
|
this.startSpinner("Fetching account info...");
|
|
19
23
|
|
|
20
24
|
try {
|
|
21
|
-
|
|
25
|
+
if (options?.account) {
|
|
26
|
+
this.accountOverride = options.account;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const accountName = this.resolveAccountName();
|
|
30
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
22
31
|
|
|
23
|
-
if (!
|
|
24
|
-
this.failSpinner(
|
|
32
|
+
if (!existsSync(keystorePath)) {
|
|
33
|
+
this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
|
|
25
34
|
return;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
const keystoreData
|
|
37
|
+
const keystoreData = JSON.parse(readFileSync(keystorePath, "utf-8"));
|
|
29
38
|
|
|
30
39
|
if (!this.isValidKeystoreFormat(keystoreData)) {
|
|
31
40
|
this.failSpinner("Invalid keystore format.");
|
|
32
41
|
return;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
const
|
|
44
|
+
const rawAddr = keystoreData.address;
|
|
45
|
+
const address = (rawAddr.startsWith("0x") ? rawAddr : `0x${rawAddr}`) as Address;
|
|
36
46
|
const network = this.getNetwork();
|
|
37
47
|
|
|
38
48
|
const client = createClient({
|
|
39
49
|
chain: network,
|
|
40
50
|
account: address,
|
|
51
|
+
endpoint: options?.rpc,
|
|
41
52
|
});
|
|
42
53
|
|
|
43
54
|
const balance = await client.getBalance({address});
|
|
44
55
|
const formattedBalance = formatEther(balance);
|
|
45
56
|
|
|
46
|
-
const isUnlocked = await this.keychainManager.
|
|
57
|
+
const isUnlocked = await this.keychainManager.isAccountUnlocked(accountName);
|
|
58
|
+
const isActive = this.getActiveAccount() === accountName;
|
|
47
59
|
|
|
48
60
|
const result = {
|
|
61
|
+
name: accountName,
|
|
49
62
|
address,
|
|
50
63
|
balance: `${formattedBalance} GEN`,
|
|
51
64
|
network: network.name || "localnet",
|
|
52
65
|
status: isUnlocked ? "unlocked" : "locked",
|
|
66
|
+
active: isActive,
|
|
53
67
|
};
|
|
54
68
|
|
|
55
69
|
this.succeedSpinner("Account info", result);
|
|
@@ -2,8 +2,12 @@ import {BaseAction} from "../../lib/actions/BaseAction";
|
|
|
2
2
|
import {readFileSync, existsSync} from "fs";
|
|
3
3
|
import {ethers} from "ethers";
|
|
4
4
|
|
|
5
|
+
export interface UnlockAccountOptions {
|
|
6
|
+
account?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
export class UnlockAccountAction extends BaseAction {
|
|
6
|
-
async execute(): Promise<void> {
|
|
10
|
+
async execute(options?: UnlockAccountOptions): Promise<void> {
|
|
7
11
|
this.startSpinner("Checking keychain availability...");
|
|
8
12
|
|
|
9
13
|
const keychainAvailable = await this.keychainManager.isKeychainAvailable();
|
|
@@ -12,15 +16,21 @@ export class UnlockAccountAction extends BaseAction {
|
|
|
12
16
|
return;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
if (options?.account) {
|
|
20
|
+
this.accountOverride = options.account;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const accountName = this.resolveAccountName();
|
|
24
|
+
this.setSpinnerText(`Checking for account '${accountName}'...`);
|
|
16
25
|
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
-
this.failSpinner(
|
|
26
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
27
|
+
if (!existsSync(keystorePath)) {
|
|
28
|
+
this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
|
|
20
29
|
return;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
const
|
|
32
|
+
const keystoreJson = readFileSync(keystorePath, "utf-8");
|
|
33
|
+
const keystoreData = JSON.parse(keystoreJson);
|
|
24
34
|
if (!this.isValidKeystoreFormat(keystoreData)) {
|
|
25
35
|
this.failSpinner("Invalid keystore format.");
|
|
26
36
|
return;
|
|
@@ -29,11 +39,11 @@ export class UnlockAccountAction extends BaseAction {
|
|
|
29
39
|
this.stopSpinner();
|
|
30
40
|
|
|
31
41
|
try {
|
|
32
|
-
const password = await this.promptPassword(
|
|
33
|
-
const wallet = await ethers.Wallet.fromEncryptedJson(
|
|
42
|
+
const password = await this.promptPassword(`Enter password to unlock '${accountName}':`);
|
|
43
|
+
const wallet = await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
|
|
34
44
|
|
|
35
|
-
await this.keychainManager.storePrivateKey(wallet.privateKey);
|
|
36
|
-
this.succeedSpinner(
|
|
45
|
+
await this.keychainManager.storePrivateKey(accountName, wallet.privateKey);
|
|
46
|
+
this.succeedSpinner(`Account '${accountName}' unlocked! Private key cached in OS keychain.`);
|
|
37
47
|
} catch (error) {
|
|
38
48
|
this.failSpinner("Failed to unlock account.", error);
|
|
39
49
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
2
|
+
|
|
3
|
+
export class UseAccountAction extends BaseAction {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async execute(name: string): Promise<void> {
|
|
9
|
+
try {
|
|
10
|
+
if (!this.accountExists(name)) {
|
|
11
|
+
this.failSpinner(`Account '${name}' does not exist. Run 'genlayer account list' to see available accounts.`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.setActiveAccount(name);
|
|
16
|
+
this.logSuccess(`Active account set to '${name}'`);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
this.failSpinner("Failed to set active account", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -4,11 +4,26 @@ import {NetworkActions} from "./setNetwork";
|
|
|
4
4
|
export function initializeNetworkCommands(program: Command) {
|
|
5
5
|
const networkActions = new NetworkActions();
|
|
6
6
|
|
|
7
|
-
program
|
|
8
|
-
|
|
7
|
+
const network = program.command("network").description("Network configuration");
|
|
8
|
+
|
|
9
|
+
// genlayer network set [name]
|
|
10
|
+
network
|
|
11
|
+
.command("set")
|
|
9
12
|
.description("Set the network to use")
|
|
10
|
-
.argument("[network]", "The network to
|
|
13
|
+
.argument("[network]", "The network to set")
|
|
11
14
|
.action((networkName?: string) => networkActions.setNetwork(networkName));
|
|
12
15
|
|
|
16
|
+
// genlayer network info
|
|
17
|
+
network
|
|
18
|
+
.command("info")
|
|
19
|
+
.description("Show current network configuration and contract addresses")
|
|
20
|
+
.action(() => networkActions.showInfo());
|
|
21
|
+
|
|
22
|
+
// genlayer network list
|
|
23
|
+
network
|
|
24
|
+
.command("list")
|
|
25
|
+
.description("List available networks")
|
|
26
|
+
.action(() => networkActions.listNetworks());
|
|
27
|
+
|
|
13
28
|
return program;
|
|
14
29
|
}
|
|
@@ -1,32 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
1
|
+
import {BaseAction, BUILT_IN_NETWORKS, resolveNetwork} from "../../lib/actions/BaseAction";
|
|
3
2
|
import inquirer, {DistinctQuestion} from "inquirer";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
alias: "localnet",
|
|
11
|
-
value: localnet,
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
name: studionet.name,
|
|
15
|
-
alias: "studionet",
|
|
16
|
-
value: studionet,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: testnetAsimov.name,
|
|
20
|
-
alias: "testnet-asimov",
|
|
21
|
-
value: testnetAsimov,
|
|
22
|
-
},
|
|
23
|
-
];
|
|
3
|
+
|
|
4
|
+
const networks = Object.entries(BUILT_IN_NETWORKS).map(([alias, network]) => ({
|
|
5
|
+
name: network.name,
|
|
6
|
+
alias,
|
|
7
|
+
value: network,
|
|
8
|
+
}));
|
|
24
9
|
|
|
25
10
|
export class NetworkActions extends BaseAction {
|
|
26
11
|
constructor() {
|
|
27
12
|
super();
|
|
28
13
|
}
|
|
29
14
|
|
|
15
|
+
async showInfo(): Promise<void> {
|
|
16
|
+
const storedNetwork = this.getConfigByKey("network") || "localnet";
|
|
17
|
+
const network = resolveNetwork(storedNetwork);
|
|
18
|
+
|
|
19
|
+
const info: Record<string, string> = {
|
|
20
|
+
alias: storedNetwork,
|
|
21
|
+
name: network.name,
|
|
22
|
+
chainId: network.id?.toString() || "unknown",
|
|
23
|
+
rpc: network.rpcUrls?.default?.http?.[0] || "unknown",
|
|
24
|
+
mainContract: network.consensusMainContract?.address || "not set",
|
|
25
|
+
stakingContract: network.stakingContract?.address || "not set",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (network.blockExplorers?.default?.url) {
|
|
29
|
+
info.explorer = network.blockExplorers.default.url;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.succeedSpinner("Current network", info);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async listNetworks(): Promise<void> {
|
|
36
|
+
const currentNetwork = this.getConfigByKey("network") || "localnet";
|
|
37
|
+
|
|
38
|
+
console.log("");
|
|
39
|
+
for (const net of networks) {
|
|
40
|
+
const marker = net.alias === currentNetwork ? "*" : " ";
|
|
41
|
+
console.log(`${marker} ${net.alias.padEnd(16)} ${net.name}`);
|
|
42
|
+
}
|
|
43
|
+
console.log("");
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
async setNetwork(networkName?: string): Promise<void> {
|
|
31
47
|
if (networkName || networkName === "") {
|
|
32
48
|
if (!networks.some(n => n.name === networkName || n.alias === networkName)) {
|
|
@@ -3,12 +3,15 @@ import {createClient, createAccount, formatStakingAmount, parseStakingAmount, ab
|
|
|
3
3
|
import type {GenLayerClient, GenLayerChain, Address} from "genlayer-js/types";
|
|
4
4
|
import {readFileSync, existsSync} from "fs";
|
|
5
5
|
import {ethers} from "ethers";
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
// Re-export for use by other staking commands
|
|
8
|
+
export {BUILT_IN_NETWORKS};
|
|
7
9
|
|
|
8
10
|
export interface StakingConfig {
|
|
9
11
|
rpc?: string;
|
|
10
12
|
stakingAddress?: string;
|
|
11
13
|
network?: string;
|
|
14
|
+
account?: string;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export class StakingAction extends BaseAction {
|
|
@@ -33,6 +36,11 @@ export class StakingAction extends BaseAction {
|
|
|
33
36
|
|
|
34
37
|
protected async getStakingClient(config: StakingConfig): Promise<GenLayerClient<GenLayerChain>> {
|
|
35
38
|
if (!this._stakingClient) {
|
|
39
|
+
// Set account override if provided
|
|
40
|
+
if (config.account) {
|
|
41
|
+
this.accountOverride = config.account;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
const network = this.getNetwork(config);
|
|
37
45
|
|
|
38
46
|
// Override staking address if provided
|
|
@@ -56,6 +64,11 @@ export class StakingAction extends BaseAction {
|
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
protected async getReadOnlyStakingClient(config: StakingConfig): Promise<GenLayerClient<GenLayerChain>> {
|
|
67
|
+
// Set account override if provided
|
|
68
|
+
if (config.account) {
|
|
69
|
+
this.accountOverride = config.account;
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
const network = this.getNetwork(config);
|
|
60
73
|
|
|
61
74
|
if (config.stakingAddress) {
|
|
@@ -65,44 +78,61 @@ export class StakingAction extends BaseAction {
|
|
|
65
78
|
};
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
const accountName = this.resolveAccountName();
|
|
82
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
83
|
+
|
|
84
|
+
if (!existsSync(keystorePath)) {
|
|
85
|
+
throw new Error(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
|
|
71
86
|
}
|
|
72
87
|
|
|
73
|
-
const keystoreData
|
|
88
|
+
const keystoreData = JSON.parse(readFileSync(keystorePath, "utf-8"));
|
|
89
|
+
const addr = keystoreData.address as string;
|
|
90
|
+
const normalizedAddress = (addr.startsWith("0x") ? addr : `0x${addr}`) as Address;
|
|
74
91
|
|
|
75
92
|
return createClient({
|
|
76
93
|
chain: network,
|
|
77
94
|
endpoint: config.rpc,
|
|
78
|
-
account:
|
|
95
|
+
account: normalizedAddress,
|
|
79
96
|
});
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
private async getPrivateKeyForStaking(): Promise<string> {
|
|
83
|
-
const
|
|
100
|
+
const accountName = this.resolveAccountName();
|
|
101
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
84
102
|
|
|
85
|
-
if (!
|
|
86
|
-
throw new Error(
|
|
103
|
+
if (!existsSync(keystorePath)) {
|
|
104
|
+
throw new Error(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
const
|
|
107
|
+
const keystoreJson = readFileSync(keystorePath, "utf-8");
|
|
108
|
+
const keystoreData = JSON.parse(keystoreJson);
|
|
90
109
|
|
|
91
110
|
if (!this.isValidKeystoreFormat(keystoreData)) {
|
|
92
111
|
throw new Error("Invalid keystore format.");
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
const cachedKey = await this.keychainManager.getPrivateKey();
|
|
114
|
+
const cachedKey = await this.keychainManager.getPrivateKey(accountName);
|
|
96
115
|
if (cachedKey) {
|
|
97
|
-
|
|
116
|
+
// Verify cached key matches keystore address - safety check
|
|
117
|
+
const tempAccount = createAccount(cachedKey as `0x${string}`);
|
|
118
|
+
const cachedAddress = tempAccount.address.toLowerCase();
|
|
119
|
+
const keystoreAddress = `0x${keystoreData.address.toLowerCase().replace(/^0x/, '')}`;
|
|
120
|
+
|
|
121
|
+
if (cachedAddress !== keystoreAddress) {
|
|
122
|
+
// Cached key doesn't match keystore - invalidate it
|
|
123
|
+
await this.keychainManager.removePrivateKey(accountName);
|
|
124
|
+
// Fall through to prompt for password
|
|
125
|
+
} else {
|
|
126
|
+
return cachedKey;
|
|
127
|
+
}
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
// Stop spinner before prompting for password
|
|
101
131
|
this.stopSpinner();
|
|
102
|
-
const password = await this.promptPassword(
|
|
132
|
+
const password = await this.promptPassword(`Enter password to unlock account '${accountName}':`);
|
|
103
133
|
this.startSpinner("Continuing...");
|
|
104
134
|
|
|
105
|
-
const wallet = await ethers.Wallet.fromEncryptedJson(
|
|
135
|
+
const wallet = await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
|
|
106
136
|
return wallet.privateKey;
|
|
107
137
|
}
|
|
108
138
|
|
|
@@ -115,11 +145,13 @@ export class StakingAction extends BaseAction {
|
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
protected async getSignerAddress(): Promise<Address> {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
148
|
+
const accountName = this.resolveAccountName();
|
|
149
|
+
const keystorePath = this.getKeystorePath(accountName);
|
|
150
|
+
if (!existsSync(keystorePath)) {
|
|
151
|
+
throw new Error(`Account '${accountName}' not found.`);
|
|
121
152
|
}
|
|
122
|
-
const keystoreData
|
|
123
|
-
|
|
153
|
+
const keystoreData = JSON.parse(readFileSync(keystorePath, "utf-8"));
|
|
154
|
+
const addr = keystoreData.address as string;
|
|
155
|
+
return (addr.startsWith("0x") ? addr : `0x${addr}`) as Address;
|
|
124
156
|
}
|
|
125
157
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {StakingAction, StakingConfig} from "./StakingAction";
|
|
2
2
|
import type {Address} from "genlayer-js/types";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
|
|
4
5
|
export interface DelegatorJoinOptions extends StakingConfig {
|
|
5
6
|
validator: string;
|
|
@@ -35,6 +36,7 @@ export class DelegatorJoinAction extends StakingAction {
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
this.succeedSpinner("Successfully joined as delegator!", output);
|
|
39
|
+
console.log(chalk.dim(`\nTo view your delegation: genlayer staking delegation-info --validator ${options.validator}`));
|
|
38
40
|
} catch (error: any) {
|
|
39
41
|
this.failSpinner("Failed to join as delegator", error.message || error);
|
|
40
42
|
}
|