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
@@ -1,19 +1,30 @@
1
1
  import {describe, test, vi, beforeEach, afterEach, expect} from "vitest";
2
- import {LockAction} from "../../src/commands/keygen/lock";
2
+ import {LockAccountAction} from "../../src/commands/account/lock";
3
+ import {readFileSync, existsSync} from "fs";
4
+ import os from "os";
3
5
 
4
- describe("LockAction", () => {
5
- let lockAction: LockAction;
6
+ vi.mock("fs");
7
+ vi.mock("os");
8
+
9
+ describe("LockAccountAction", () => {
10
+ let lockAction: LockAccountAction;
6
11
 
7
12
  beforeEach(() => {
8
13
  vi.clearAllMocks();
9
- lockAction = new LockAction();
10
-
14
+ // Setup mocks before creating the action (needed for constructor)
15
+ vi.mocked(os.homedir).mockReturnValue("/mocked/home");
16
+ vi.mocked(existsSync).mockReturnValue(true);
17
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({activeAccount: "default"}));
18
+
19
+ lockAction = new LockAccountAction();
20
+
11
21
  // Mock the BaseAction methods
12
22
  vi.spyOn(lockAction as any, "startSpinner").mockImplementation(() => {});
13
23
  vi.spyOn(lockAction as any, "setSpinnerText").mockImplementation(() => {});
14
24
  vi.spyOn(lockAction as any, "succeedSpinner").mockImplementation(() => {});
15
25
  vi.spyOn(lockAction as any, "failSpinner").mockImplementation(() => {});
16
-
26
+ vi.spyOn(lockAction as any, "resolveAccountName").mockReturnValue("default");
27
+
17
28
  // Mock keychainManager
18
29
  vi.spyOn(lockAction["keychainManager"], "isKeychainAvailable").mockResolvedValue(true);
19
30
  vi.spyOn(lockAction["keychainManager"], "getPrivateKey").mockResolvedValue("test-private-key");
@@ -29,11 +40,11 @@ describe("LockAction", () => {
29
40
 
30
41
  expect(lockAction["startSpinner"]).toHaveBeenCalledWith("Checking keychain availability...");
31
42
  expect(lockAction["keychainManager"].isKeychainAvailable).toHaveBeenCalled();
32
- expect(lockAction["setSpinnerText"]).toHaveBeenCalledWith("Checking for cached private key...");
33
- expect(lockAction["keychainManager"].getPrivateKey).toHaveBeenCalled();
34
- expect(lockAction["setSpinnerText"]).toHaveBeenCalledWith("Removing private key from OS keychain...");
35
- expect(lockAction["keychainManager"].removePrivateKey).toHaveBeenCalled();
36
- expect(lockAction["succeedSpinner"]).toHaveBeenCalledWith("Wallet locked successfully! Your private key has been removed from the OS keychain.");
43
+ expect(lockAction["setSpinnerText"]).toHaveBeenCalledWith("Checking for cached private key for 'default'...");
44
+ expect(lockAction["keychainManager"].getPrivateKey).toHaveBeenCalledWith("default");
45
+ expect(lockAction["setSpinnerText"]).toHaveBeenCalledWith("Removing private key for 'default' from OS keychain...");
46
+ expect(lockAction["keychainManager"].removePrivateKey).toHaveBeenCalledWith("default");
47
+ expect(lockAction["succeedSpinner"]).toHaveBeenCalledWith("Account 'default' locked! Private key removed from OS keychain.");
37
48
  });
38
49
 
39
50
  test("fails when keychain is not available", async () => {
@@ -51,7 +62,7 @@ describe("LockAction", () => {
51
62
 
52
63
  await lockAction.execute();
53
64
 
54
- expect(lockAction["succeedSpinner"]).toHaveBeenCalledWith("Wallet is already locked (no cached key found in OS keychain).");
65
+ expect(lockAction["succeedSpinner"]).toHaveBeenCalledWith("Account 'default' is already locked.");
55
66
  expect(lockAction["keychainManager"].removePrivateKey).not.toHaveBeenCalled();
56
67
  });
57
68
 
@@ -61,6 +72,15 @@ describe("LockAction", () => {
61
72
 
62
73
  await lockAction.execute();
63
74
 
64
- expect(lockAction["failSpinner"]).toHaveBeenCalledWith("Failed to lock wallet.", mockError);
75
+ expect(lockAction["failSpinner"]).toHaveBeenCalledWith("Failed to lock account.", mockError);
76
+ });
77
+
78
+ test("uses account option when provided", async () => {
79
+ vi.spyOn(lockAction as any, "resolveAccountName").mockReturnValue("validator");
80
+
81
+ await lockAction.execute({account: "validator"});
82
+
83
+ expect(lockAction["accountOverride"]).toBe("validator");
84
+ expect(lockAction["setSpinnerText"]).toHaveBeenCalledWith("Checking for cached private key for 'validator'...");
65
85
  });
66
86
  });
@@ -26,7 +26,7 @@ describe("NetworkActions", () => {
26
26
  test("setNetwork method sets network by valid name", async () => {
27
27
  await networkActions.setNetwork(localnet.name);
28
28
 
29
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(localnet));
29
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "localnet");
30
30
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
31
31
  `Network successfully set to ${localnet.name}`,
32
32
  );
@@ -35,7 +35,7 @@ describe("NetworkActions", () => {
35
35
  test("setNetwork method sets network by valid alias", async () => {
36
36
  await networkActions.setNetwork("localnet");
37
37
 
38
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(localnet));
38
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "localnet");
39
39
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
40
40
  `Network successfully set to ${localnet.name}`,
41
41
  );
@@ -44,7 +44,7 @@ describe("NetworkActions", () => {
44
44
  test("setNetwork method sets studionet by name", async () => {
45
45
  await networkActions.setNetwork(studionet.name);
46
46
 
47
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(studionet));
47
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "studionet");
48
48
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
49
49
  `Network successfully set to ${studionet.name}`,
50
50
  );
@@ -53,7 +53,7 @@ describe("NetworkActions", () => {
53
53
  test("setNetwork method sets studionet by alias", async () => {
54
54
  await networkActions.setNetwork("studionet");
55
55
 
56
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(studionet));
56
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "studionet");
57
57
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
58
58
  `Network successfully set to ${studionet.name}`,
59
59
  );
@@ -62,7 +62,7 @@ describe("NetworkActions", () => {
62
62
  test("setNetwork method sets testnet-asimov by name", async () => {
63
63
  await networkActions.setNetwork(testnetAsimov.name);
64
64
 
65
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(testnetAsimov));
65
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "testnet-asimov");
66
66
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
67
67
  `Network successfully set to ${testnetAsimov.name}`,
68
68
  );
@@ -71,7 +71,7 @@ describe("NetworkActions", () => {
71
71
  test("setNetwork method sets testnet-asimov by alias", async () => {
72
72
  await networkActions.setNetwork("testnet-asimov");
73
73
 
74
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", JSON.stringify(testnetAsimov));
74
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "testnet-asimov");
75
75
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
76
76
  `Network successfully set to ${testnetAsimov.name}`,
77
77
  );
@@ -94,14 +94,8 @@ describe("NetworkActions", () => {
94
94
  });
95
95
 
96
96
  test("setNetwork method prompts user when no network name provided", async () => {
97
- const mockSelectedNetwork = {
98
- name: localnet.name,
99
- alias: "localnet",
100
- value: localnet,
101
- };
102
-
103
97
  vi.mocked(inquirer.prompt).mockResolvedValue({
104
- selectedNetwork: mockSelectedNetwork,
98
+ selectedNetwork: "localnet",
105
99
  });
106
100
 
107
101
  await networkActions.setNetwork();
@@ -112,74 +106,41 @@ describe("NetworkActions", () => {
112
106
  name: "selectedNetwork",
113
107
  message: "Select which network do you want to use:",
114
108
  choices: [
115
- {
116
- name: localnet.name,
117
- alias: "localnet",
118
- value: localnet,
119
- },
120
- {
121
- name: studionet.name,
122
- alias: "studionet",
123
- value: studionet,
124
- },
125
- {
126
- name: testnetAsimov.name,
127
- alias: "testnet-asimov",
128
- value: testnetAsimov,
129
- },
109
+ {name: localnet.name, value: "localnet"},
110
+ {name: studionet.name, value: "studionet"},
111
+ {name: testnetAsimov.name, value: "testnet-asimov"},
130
112
  ],
131
113
  },
132
114
  ]);
133
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith(
134
- "network",
135
- JSON.stringify(mockSelectedNetwork),
136
- );
115
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "localnet");
137
116
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
138
- `Network successfully set to ${mockSelectedNetwork.name}`,
117
+ `Network successfully set to ${localnet.name}`,
139
118
  );
140
119
  });
141
120
 
142
121
  test("setNetwork method handles interactive selection of studionet", async () => {
143
- const mockSelectedNetwork = {
144
- name: studionet.name,
145
- alias: "studionet",
146
- value: studionet,
147
- };
148
-
149
122
  vi.mocked(inquirer.prompt).mockResolvedValue({
150
- selectedNetwork: mockSelectedNetwork,
123
+ selectedNetwork: "studionet",
151
124
  });
152
125
 
153
126
  await networkActions.setNetwork();
154
127
 
155
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith(
156
- "network",
157
- JSON.stringify(mockSelectedNetwork),
158
- );
128
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "studionet");
159
129
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
160
- `Network successfully set to ${mockSelectedNetwork.name}`,
130
+ `Network successfully set to ${studionet.name}`,
161
131
  );
162
132
  });
163
133
 
164
134
  test("setNetwork method handles interactive selection of testnet-asimov", async () => {
165
- const mockSelectedNetwork = {
166
- name: testnetAsimov.name,
167
- alias: "testnet-asimov",
168
- value: testnetAsimov,
169
- };
170
-
171
135
  vi.mocked(inquirer.prompt).mockResolvedValue({
172
- selectedNetwork: mockSelectedNetwork,
136
+ selectedNetwork: "testnet-asimov",
173
137
  });
174
138
 
175
139
  await networkActions.setNetwork();
176
140
 
177
- expect(networkActions["writeConfig"]).toHaveBeenCalledWith(
178
- "network",
179
- JSON.stringify(mockSelectedNetwork),
180
- );
141
+ expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "testnet-asimov");
181
142
  expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
182
- `Network successfully set to ${mockSelectedNetwork.name}`,
143
+ `Network successfully set to ${testnetAsimov.name}`,
183
144
  );
184
145
  });
185
146
 
@@ -0,0 +1,323 @@
1
+ import {describe, test, vi, beforeEach, afterEach, expect} from "vitest";
2
+ import {ValidatorJoinAction} from "../../src/commands/staking/validatorJoin";
3
+ import {ValidatorDepositAction} from "../../src/commands/staking/validatorDeposit";
4
+ import {ValidatorExitAction} from "../../src/commands/staking/validatorExit";
5
+ import {ValidatorClaimAction} from "../../src/commands/staking/validatorClaim";
6
+ import {DelegatorJoinAction} from "../../src/commands/staking/delegatorJoin";
7
+ import {DelegatorExitAction} from "../../src/commands/staking/delegatorExit";
8
+ import {DelegatorClaimAction} from "../../src/commands/staking/delegatorClaim";
9
+ import {StakingInfoAction} from "../../src/commands/staking/stakingInfo";
10
+
11
+ // Mock genlayer-js
12
+ vi.mock("genlayer-js", () => ({
13
+ createClient: vi.fn(),
14
+ createAccount: vi.fn(() => ({address: "0xMockedAddress"})),
15
+ formatStakingAmount: vi.fn((val: bigint) => `${Number(val) / 1e18} GEN`),
16
+ parseStakingAmount: vi.fn((val: string) => {
17
+ if (val.toLowerCase().endsWith("gen") || val.toLowerCase().endsWith("eth")) {
18
+ return BigInt(parseFloat(val.slice(0, -3)) * 1e18);
19
+ }
20
+ return BigInt(val);
21
+ }),
22
+ abi: {
23
+ STAKING_ABI: [],
24
+ },
25
+ }));
26
+
27
+ vi.mock("genlayer-js/chains", () => ({
28
+ localnet: {id: 1, name: "localnet", rpcUrls: {default: {http: ["http://localhost:8545"]}}},
29
+ studionet: {id: 2, name: "studionet", rpcUrls: {default: {http: ["https://studionet.genlayer.com"]}}},
30
+ testnetAsimov: {id: 3, name: "testnet-asimov", rpcUrls: {default: {http: ["https://testnet.genlayer.com"]}}},
31
+ }));
32
+
33
+ const mockTxResult = {
34
+ transactionHash: "0xMockedHash" as `0x${string}`,
35
+ blockNumber: 123n,
36
+ gasUsed: 21000n,
37
+ };
38
+
39
+ const mockValidatorJoinResult = {
40
+ ...mockTxResult,
41
+ validatorWallet: "0xValidatorWallet",
42
+ operator: "0xOperator",
43
+ amount: "42000 GEN",
44
+ amountRaw: 42000n * BigInt(1e18),
45
+ };
46
+
47
+ const mockDelegatorJoinResult = {
48
+ ...mockTxResult,
49
+ validator: "0xValidator",
50
+ delegator: "0xDelegator",
51
+ amount: "42 GEN",
52
+ amountRaw: 42n * BigInt(1e18),
53
+ };
54
+
55
+ const mockClient = {
56
+ validatorJoin: vi.fn(),
57
+ validatorDeposit: vi.fn(),
58
+ validatorExit: vi.fn(),
59
+ validatorClaim: vi.fn(),
60
+ delegatorJoin: vi.fn(),
61
+ delegatorExit: vi.fn(),
62
+ delegatorClaim: vi.fn(),
63
+ isValidator: vi.fn(),
64
+ getValidatorInfo: vi.fn(),
65
+ getStakeInfo: vi.fn(),
66
+ getEpochInfo: vi.fn(),
67
+ getActiveValidators: vi.fn(),
68
+ };
69
+
70
+ function setupActionMocks(action: any) {
71
+ vi.spyOn(action as any, "getStakingClient").mockResolvedValue(mockClient);
72
+ vi.spyOn(action as any, "getReadOnlyStakingClient").mockResolvedValue(mockClient);
73
+ vi.spyOn(action as any, "getSignerAddress").mockResolvedValue("0xMockedSigner");
74
+ vi.spyOn(action as any, "startSpinner").mockImplementation(() => {});
75
+ vi.spyOn(action as any, "setSpinnerText").mockImplementation(() => {});
76
+ vi.spyOn(action as any, "succeedSpinner").mockImplementation(() => {});
77
+ vi.spyOn(action as any, "failSpinner").mockImplementation(() => {});
78
+ }
79
+
80
+ describe("ValidatorJoinAction", () => {
81
+ let action: ValidatorJoinAction;
82
+
83
+ beforeEach(() => {
84
+ vi.clearAllMocks();
85
+ action = new ValidatorJoinAction();
86
+ setupActionMocks(action);
87
+ mockClient.validatorJoin.mockResolvedValue(mockValidatorJoinResult);
88
+ });
89
+
90
+ afterEach(() => {
91
+ vi.restoreAllMocks();
92
+ });
93
+
94
+ test("joins as validator without operator", async () => {
95
+ await action.execute({amount: "42000gen", stakingAddress: "0xStaking"});
96
+
97
+ expect(mockClient.validatorJoin).toHaveBeenCalledWith({
98
+ amount: expect.any(BigInt),
99
+ operator: undefined,
100
+ });
101
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Validator created successfully!", expect.any(Object));
102
+ });
103
+
104
+ test("joins as validator with operator", async () => {
105
+ await action.execute({amount: "42000gen", operator: "0xOperator", stakingAddress: "0xStaking"});
106
+
107
+ expect(mockClient.validatorJoin).toHaveBeenCalledWith({
108
+ amount: expect.any(BigInt),
109
+ operator: "0xOperator",
110
+ });
111
+ });
112
+
113
+ test("handles errors", async () => {
114
+ mockClient.validatorJoin.mockRejectedValue(new Error("Join failed"));
115
+
116
+ await action.execute({amount: "42000gen", stakingAddress: "0xStaking"});
117
+
118
+ expect(action["failSpinner"]).toHaveBeenCalledWith("Failed to create validator", "Join failed");
119
+ });
120
+ });
121
+
122
+ describe("ValidatorDepositAction", () => {
123
+ let action: ValidatorDepositAction;
124
+
125
+ beforeEach(() => {
126
+ vi.clearAllMocks();
127
+ action = new ValidatorDepositAction();
128
+ setupActionMocks(action);
129
+ mockClient.validatorDeposit.mockResolvedValue(mockTxResult);
130
+ });
131
+
132
+ test("deposits successfully", async () => {
133
+ await action.execute({amount: "1000gen", stakingAddress: "0xStaking"});
134
+
135
+ expect(mockClient.validatorDeposit).toHaveBeenCalledWith({amount: expect.any(BigInt)});
136
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Deposit successful!", expect.any(Object));
137
+ });
138
+ });
139
+
140
+ describe("ValidatorExitAction", () => {
141
+ let action: ValidatorExitAction;
142
+
143
+ beforeEach(() => {
144
+ vi.clearAllMocks();
145
+ action = new ValidatorExitAction();
146
+ setupActionMocks(action);
147
+ mockClient.validatorExit.mockResolvedValue(mockTxResult);
148
+ });
149
+
150
+ test("exits successfully", async () => {
151
+ await action.execute({shares: "100", stakingAddress: "0xStaking"});
152
+
153
+ expect(mockClient.validatorExit).toHaveBeenCalledWith({shares: 100n});
154
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Exit initiated successfully!", expect.any(Object));
155
+ });
156
+ });
157
+
158
+ describe("ValidatorClaimAction", () => {
159
+ let action: ValidatorClaimAction;
160
+
161
+ beforeEach(() => {
162
+ vi.clearAllMocks();
163
+ action = new ValidatorClaimAction();
164
+ setupActionMocks(action);
165
+ mockClient.validatorClaim.mockResolvedValue({...mockTxResult, claimedAmount: 0n});
166
+ });
167
+
168
+ test("claims successfully", async () => {
169
+ await action.execute({validator: "0xValidator", stakingAddress: "0xStaking"});
170
+
171
+ expect(mockClient.validatorClaim).toHaveBeenCalledWith({validator: "0xValidator"});
172
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Claim successful!", expect.any(Object));
173
+ });
174
+
175
+ test("uses signer address if no validator specified", async () => {
176
+ await action.execute({stakingAddress: "0xStaking"});
177
+
178
+ expect(mockClient.validatorClaim).toHaveBeenCalledWith({validator: "0xMockedSigner"});
179
+ });
180
+ });
181
+
182
+ describe("DelegatorJoinAction", () => {
183
+ let action: DelegatorJoinAction;
184
+
185
+ beforeEach(() => {
186
+ vi.clearAllMocks();
187
+ action = new DelegatorJoinAction();
188
+ setupActionMocks(action);
189
+ mockClient.delegatorJoin.mockResolvedValue(mockDelegatorJoinResult);
190
+ });
191
+
192
+ test("joins as delegator successfully", async () => {
193
+ await action.execute({validator: "0xValidator", amount: "42gen", stakingAddress: "0xStaking"});
194
+
195
+ expect(mockClient.delegatorJoin).toHaveBeenCalledWith({
196
+ validator: "0xValidator",
197
+ amount: expect.any(BigInt),
198
+ });
199
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Successfully joined as delegator!", expect.any(Object));
200
+ });
201
+ });
202
+
203
+ describe("DelegatorExitAction", () => {
204
+ let action: DelegatorExitAction;
205
+
206
+ beforeEach(() => {
207
+ vi.clearAllMocks();
208
+ action = new DelegatorExitAction();
209
+ setupActionMocks(action);
210
+ mockClient.delegatorExit.mockResolvedValue(mockTxResult);
211
+ });
212
+
213
+ test("exits successfully", async () => {
214
+ await action.execute({validator: "0xValidator", shares: "50", stakingAddress: "0xStaking"});
215
+
216
+ expect(mockClient.delegatorExit).toHaveBeenCalledWith({validator: "0xValidator", shares: 50n});
217
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Exit initiated successfully!", expect.any(Object));
218
+ });
219
+ });
220
+
221
+ describe("DelegatorClaimAction", () => {
222
+ let action: DelegatorClaimAction;
223
+
224
+ beforeEach(() => {
225
+ vi.clearAllMocks();
226
+ action = new DelegatorClaimAction();
227
+ setupActionMocks(action);
228
+ mockClient.delegatorClaim.mockResolvedValue(mockTxResult);
229
+ });
230
+
231
+ test("claims successfully", async () => {
232
+ await action.execute({validator: "0xValidator", delegator: "0xDelegator", stakingAddress: "0xStaking"});
233
+
234
+ expect(mockClient.delegatorClaim).toHaveBeenCalledWith({validator: "0xValidator", delegator: "0xDelegator"});
235
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Claim successful!", expect.any(Object));
236
+ });
237
+ });
238
+
239
+ const mockEpochInfo = {
240
+ currentEpoch: 10n,
241
+ currentEpochStart: new Date("2024-01-01T00:00:00Z"),
242
+ currentEpochEnd: new Date("2024-01-01T01:00:00Z"),
243
+ nextEpochEstimate: new Date("2024-01-01T02:00:00Z"),
244
+ epochMinDuration: 3600n,
245
+ validatorMinStake: "42000 GEN",
246
+ validatorMinStakeRaw: 42000n * BigInt(1e18),
247
+ delegatorMinStake: "42 GEN",
248
+ delegatorMinStakeRaw: 42n * BigInt(1e18),
249
+ activeValidatorsCount: 5n,
250
+ inflation: "1000 GEN",
251
+ inflationRaw: 1000n * BigInt(1e18),
252
+ totalWeight: 100000n * BigInt(1e18),
253
+ totalClaimed: "500 GEN",
254
+ totalClaimedRaw: 500n * BigInt(1e18),
255
+ };
256
+
257
+ describe("StakingInfoAction", () => {
258
+ let action: StakingInfoAction;
259
+
260
+ beforeEach(() => {
261
+ vi.clearAllMocks();
262
+ action = new StakingInfoAction();
263
+ setupActionMocks(action);
264
+ mockClient.getEpochInfo.mockResolvedValue(mockEpochInfo);
265
+ });
266
+
267
+ test("gets validator info", async () => {
268
+ mockClient.isValidator.mockResolvedValue(true);
269
+ mockClient.getValidatorInfo.mockResolvedValue({
270
+ address: "0xValidator",
271
+ owner: "0xOwner",
272
+ operator: "0xOperator",
273
+ vStake: "1000 GEN",
274
+ vStakeRaw: 1000n,
275
+ vShares: 100n,
276
+ dStake: "500 GEN",
277
+ dStakeRaw: 500n,
278
+ dShares: 50n,
279
+ vDeposit: "0 GEN",
280
+ vDepositRaw: 0n,
281
+ vWithdrawal: "0 GEN",
282
+ vWithdrawalRaw: 0n,
283
+ ePrimed: 5n,
284
+ needsPriming: false,
285
+ live: true,
286
+ banned: false,
287
+ bannedEpoch: null,
288
+ pendingDeposits: [],
289
+ pendingWithdrawals: [],
290
+ identity: null,
291
+ });
292
+
293
+ await action.getValidatorInfo({validator: "0xValidator", stakingAddress: "0xStaking"});
294
+
295
+ expect(mockClient.isValidator).toHaveBeenCalledWith("0xValidator");
296
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Validator info retrieved", expect.any(Object));
297
+ });
298
+
299
+ test("fails if not a validator", async () => {
300
+ mockClient.isValidator.mockResolvedValue(false);
301
+
302
+ await action.getValidatorInfo({validator: "0xNotValidator", stakingAddress: "0xStaking"});
303
+
304
+ expect(action["failSpinner"]).toHaveBeenCalledWith("Address 0xNotValidator is not a validator");
305
+ });
306
+
307
+ test("gets epoch info", async () => {
308
+ await action.getEpochInfo({stakingAddress: "0xStaking"});
309
+
310
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Epoch info retrieved", expect.any(Object));
311
+ });
312
+
313
+ test("lists active validators", async () => {
314
+ mockClient.getActiveValidators.mockResolvedValue(["0xV1", "0xV2", "0xV3"]);
315
+
316
+ await action.listActiveValidators({stakingAddress: "0xStaking"});
317
+
318
+ expect(action["succeedSpinner"]).toHaveBeenCalledWith("Active validators retrieved", {
319
+ count: 3,
320
+ validators: ["0xV1", "0xV2", "0xV3"],
321
+ });
322
+ });
323
+ });