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,66 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ export interface SetIdentityOptions extends StakingConfig {
5
+ validator: string;
6
+ moniker: string;
7
+ logoUri?: string;
8
+ website?: string;
9
+ description?: string;
10
+ email?: string;
11
+ twitter?: string;
12
+ telegram?: string;
13
+ github?: string;
14
+ extraCid?: string;
15
+ }
16
+
17
+ export class SetIdentityAction extends StakingAction {
18
+ constructor() {
19
+ super();
20
+ }
21
+
22
+ async execute(options: SetIdentityOptions): Promise<void> {
23
+ this.startSpinner("Setting validator identity...");
24
+
25
+ try {
26
+ const client = await this.getStakingClient(options);
27
+
28
+ this.setSpinnerText(`Setting identity for ${options.validator}...`);
29
+
30
+ const result = await client.setIdentity({
31
+ validator: options.validator as Address,
32
+ moniker: options.moniker,
33
+ logoUri: options.logoUri,
34
+ website: options.website,
35
+ description: options.description,
36
+ email: options.email,
37
+ twitter: options.twitter,
38
+ telegram: options.telegram,
39
+ github: options.github,
40
+ extraCid: options.extraCid,
41
+ });
42
+
43
+ const output: Record<string, any> = {
44
+ transactionHash: result.transactionHash,
45
+ validator: options.validator,
46
+ moniker: options.moniker,
47
+ blockNumber: result.blockNumber.toString(),
48
+ gasUsed: result.gasUsed.toString(),
49
+ };
50
+
51
+ // Add optional fields that were set
52
+ if (options.logoUri) output.logoUri = options.logoUri;
53
+ if (options.website) output.website = options.website;
54
+ if (options.description) output.description = options.description;
55
+ if (options.email) output.email = options.email;
56
+ if (options.twitter) output.twitter = options.twitter;
57
+ if (options.telegram) output.telegram = options.telegram;
58
+ if (options.github) output.github = options.github;
59
+ if (options.extraCid) output.extraCid = options.extraCid;
60
+
61
+ this.succeedSpinner("Validator identity set!", output);
62
+ } catch (error: any) {
63
+ this.failSpinner("Failed to set identity", error.message || error);
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,40 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ export interface SetOperatorOptions extends StakingConfig {
5
+ validator: string;
6
+ operator: string;
7
+ }
8
+
9
+ export class SetOperatorAction extends StakingAction {
10
+ constructor() {
11
+ super();
12
+ }
13
+
14
+ async execute(options: SetOperatorOptions): Promise<void> {
15
+ this.startSpinner("Setting operator...");
16
+
17
+ try {
18
+ const client = await this.getStakingClient(options);
19
+
20
+ this.setSpinnerText(`Setting operator to ${options.operator}...`);
21
+
22
+ const result = await client.setOperator({
23
+ validator: options.validator as Address,
24
+ operator: options.operator as Address,
25
+ });
26
+
27
+ const output = {
28
+ transactionHash: result.transactionHash,
29
+ validator: options.validator,
30
+ newOperator: options.operator,
31
+ blockNumber: result.blockNumber.toString(),
32
+ gasUsed: result.gasUsed.toString(),
33
+ };
34
+
35
+ this.succeedSpinner("Operator updated!", output);
36
+ } catch (error: any) {
37
+ this.failSpinner("Failed to set operator", error.message || error);
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,300 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ // Epoch-related constants
5
+ const ACTIVATION_DELAY_EPOCHS = 2n;
6
+ const UNBONDING_PERIOD_EPOCHS = 7n;
7
+
8
+ export interface StakingInfoOptions extends StakingConfig {
9
+ validator?: string;
10
+ }
11
+
12
+ export class StakingInfoAction extends StakingAction {
13
+ constructor() {
14
+ super();
15
+ }
16
+
17
+ async getValidatorInfo(options: StakingInfoOptions): Promise<void> {
18
+ this.startSpinner("Fetching validator info...");
19
+
20
+ try {
21
+ const client = await this.getReadOnlyStakingClient(options);
22
+ const validatorAddress = options.validator || (await this.getSignerAddress());
23
+
24
+ const isValidator = await client.isValidator(validatorAddress as Address);
25
+
26
+ if (!isValidator) {
27
+ this.failSpinner(`Address ${validatorAddress} is not a validator`);
28
+ return;
29
+ }
30
+
31
+ const [info, epochInfo] = await Promise.all([
32
+ client.getValidatorInfo(validatorAddress as Address),
33
+ client.getEpochInfo(),
34
+ ]);
35
+
36
+ const currentEpoch = epochInfo.currentEpoch;
37
+
38
+ const result: Record<string, any> = {
39
+ validator: info.address,
40
+ owner: info.owner,
41
+ operator: info.operator,
42
+ vStake: info.vStake,
43
+ vShares: info.vShares.toString(),
44
+ dStake: info.dStake,
45
+ dShares: info.dShares.toString(),
46
+ vDeposit: info.vDeposit,
47
+ vWithdrawal: info.vWithdrawal,
48
+ ePrimed: info.ePrimed.toString(),
49
+ needsPriming: info.needsPriming,
50
+ live: info.live,
51
+ banned: info.banned ? info.bannedEpoch?.toString() : "Not banned",
52
+ selfStakePendingDeposits: (() => {
53
+ // Filter to only truly pending deposits (not yet active)
54
+ const pending = info.pendingDeposits.filter(d => d.epoch + ACTIVATION_DELAY_EPOCHS > currentEpoch);
55
+ return pending.length > 0
56
+ ? pending.map(d => {
57
+ const depositEpoch = d.epoch;
58
+ const activationEpoch = depositEpoch + ACTIVATION_DELAY_EPOCHS;
59
+ const epochsUntilActive = activationEpoch - currentEpoch;
60
+ return {
61
+ epoch: depositEpoch.toString(),
62
+ stake: d.stake,
63
+ shares: d.shares.toString(),
64
+ activatesAtEpoch: activationEpoch.toString(),
65
+ epochsRemaining: epochsUntilActive.toString(),
66
+ };
67
+ })
68
+ : "None";
69
+ })(),
70
+ selfStakePendingWithdrawals:
71
+ info.pendingWithdrawals.length > 0
72
+ ? info.pendingWithdrawals.map(w => {
73
+ const exitEpoch = w.epoch;
74
+ const claimableEpoch = exitEpoch + UNBONDING_PERIOD_EPOCHS;
75
+ const epochsUntilClaimable = claimableEpoch - currentEpoch;
76
+ return {
77
+ epoch: exitEpoch.toString(),
78
+ shares: w.shares.toString(),
79
+ stake: w.stake,
80
+ claimableAtEpoch: claimableEpoch.toString(),
81
+ status:
82
+ epochsUntilClaimable <= 0n
83
+ ? "Claimable now"
84
+ : `Unbonding (${epochsUntilClaimable} epoch${epochsUntilClaimable > 1n ? "s" : ""} remaining)`,
85
+ };
86
+ })
87
+ : "None",
88
+ };
89
+
90
+ // Add identity if set
91
+ if (info.identity?.moniker) {
92
+ result.identity = {
93
+ moniker: info.identity.moniker,
94
+ ...(info.identity.website && {website: info.identity.website}),
95
+ ...(info.identity.description && {description: info.identity.description}),
96
+ ...(info.identity.twitter && {twitter: info.identity.twitter}),
97
+ ...(info.identity.telegram && {telegram: info.identity.telegram}),
98
+ ...(info.identity.github && {github: info.identity.github}),
99
+ ...(info.identity.email && {email: info.identity.email}),
100
+ ...(info.identity.logoUri && {logoUri: info.identity.logoUri}),
101
+ };
102
+ }
103
+
104
+ this.succeedSpinner("Validator info retrieved", result);
105
+ } catch (error: any) {
106
+ this.failSpinner("Failed to get validator info", error.message || error);
107
+ }
108
+ }
109
+
110
+ async getStakeInfo(options: StakingInfoOptions & {delegator?: string}): Promise<void> {
111
+ this.startSpinner("Fetching stake info...");
112
+
113
+ try {
114
+ const client = await this.getReadOnlyStakingClient(options);
115
+ const delegatorAddress = options.delegator || (await this.getSignerAddress());
116
+ const isOwnDelegation = !options.delegator;
117
+
118
+ this.setSpinnerText(`Fetching delegation info for ${delegatorAddress}...`);
119
+
120
+ if (!options.validator) {
121
+ this.failSpinner("Validator address is required");
122
+ return;
123
+ }
124
+
125
+ const [info, epochInfo] = await Promise.all([
126
+ client.getStakeInfo(delegatorAddress as Address, options.validator as Address),
127
+ client.getEpochInfo(),
128
+ ]);
129
+
130
+ const currentEpoch = epochInfo.currentEpoch;
131
+
132
+ // Calculate projected rewards
133
+ let projectedReward = "N/A";
134
+ if (epochInfo.totalWeight > 0n && epochInfo.inflationRaw > 0n && info.stakeRaw > 0n) {
135
+ const rewardRaw = (info.stakeRaw * epochInfo.inflationRaw) / epochInfo.totalWeight;
136
+ projectedReward = client.formatStakingAmount(rewardRaw) + " per epoch";
137
+ } else if (epochInfo.inflationRaw === 0n) {
138
+ projectedReward = "0 GEN (no inflation this epoch)";
139
+ }
140
+
141
+ const result = {
142
+ delegator: info.delegator,
143
+ validator: info.validator,
144
+ shares: info.shares.toString(),
145
+ stake: info.stake,
146
+ projectedReward,
147
+ pendingDeposits: (() => {
148
+ // Filter to only truly pending deposits (not yet active)
149
+ const pending = info.pendingDeposits.filter(d => d.epoch + ACTIVATION_DELAY_EPOCHS > currentEpoch);
150
+ return pending.length > 0
151
+ ? pending.map(d => {
152
+ const depositEpoch = d.epoch;
153
+ const activationEpoch = depositEpoch + ACTIVATION_DELAY_EPOCHS;
154
+ const epochsUntilActive = activationEpoch - currentEpoch;
155
+ return {
156
+ epoch: depositEpoch.toString(),
157
+ stake: d.stake,
158
+ shares: d.shares.toString(),
159
+ activatesAtEpoch: activationEpoch.toString(),
160
+ epochsRemaining: epochsUntilActive.toString(),
161
+ };
162
+ })
163
+ : "None";
164
+ })(),
165
+ pendingWithdrawals:
166
+ info.pendingWithdrawals.length > 0
167
+ ? info.pendingWithdrawals.map(w => {
168
+ const exitEpoch = w.epoch;
169
+ const claimableEpoch = exitEpoch + UNBONDING_PERIOD_EPOCHS; // Must wait 7 full epochs
170
+ const epochsUntilClaimable = claimableEpoch - currentEpoch;
171
+ return {
172
+ epoch: exitEpoch.toString(),
173
+ shares: w.shares.toString(),
174
+ stake: w.stake,
175
+ claimableAtEpoch: claimableEpoch.toString(),
176
+ status:
177
+ epochsUntilClaimable <= 0n
178
+ ? "Claimable now"
179
+ : `Unbonding (${epochsUntilClaimable} epoch${epochsUntilClaimable > 1n ? "s" : ""} remaining)`,
180
+ };
181
+ })
182
+ : "None",
183
+ };
184
+
185
+ const msg = isOwnDelegation ? "Your delegation info" : `Delegation info for ${delegatorAddress}`;
186
+ this.succeedSpinner(msg, result);
187
+ } catch (error: any) {
188
+ this.failSpinner("Failed to get stake info", error.message || error);
189
+ }
190
+ }
191
+
192
+ async getEpochInfo(options: StakingConfig): Promise<void> {
193
+ this.startSpinner("Fetching epoch info...");
194
+
195
+ try {
196
+ const client = await this.getReadOnlyStakingClient(options);
197
+
198
+ const info = await client.getEpochInfo();
199
+
200
+ const formatDuration = (ms: number): string => {
201
+ const hours = Math.floor(ms / (1000 * 60 * 60));
202
+ const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
203
+ if (hours > 24) {
204
+ const days = Math.floor(hours / 24);
205
+ const remainingHours = hours % 24;
206
+ return `${days}d ${remainingHours}h ${minutes}m`;
207
+ }
208
+ return `${hours}h ${minutes}m`;
209
+ };
210
+
211
+ const now = Date.now();
212
+ const timeUntilNext = info.nextEpochEstimate ? info.nextEpochEstimate.getTime() - now : null;
213
+
214
+ const result = {
215
+ currentEpoch: info.currentEpoch.toString(),
216
+ epochStarted: info.currentEpochStart.toISOString(),
217
+ epochEnded: info.currentEpochEnd?.toISOString() || "Not ended",
218
+ nextEpochEstimate: info.nextEpochEstimate?.toISOString() || "N/A",
219
+ timeUntilNextEpoch: timeUntilNext && timeUntilNext > 0 ? formatDuration(timeUntilNext) : "N/A",
220
+ minEpochDuration: formatDuration(Number(info.epochMinDuration) * 1000),
221
+ validatorMinStake: info.validatorMinStake,
222
+ delegatorMinStake: info.delegatorMinStake,
223
+ activeValidatorsCount: info.activeValidatorsCount.toString(),
224
+ // Inflation/rewards
225
+ epochInflation: info.inflation,
226
+ totalWeight: info.totalWeight.toString(),
227
+ totalClaimed: info.totalClaimed,
228
+ };
229
+
230
+ this.succeedSpinner("Epoch info retrieved", result);
231
+ } catch (error: any) {
232
+ this.failSpinner("Failed to get epoch info", error.message || error);
233
+ }
234
+ }
235
+
236
+ async listActiveValidators(options: StakingConfig): Promise<void> {
237
+ this.startSpinner("Fetching active validators...");
238
+
239
+ try {
240
+ const client = await this.getReadOnlyStakingClient(options);
241
+
242
+ const activeValidators = await client.getActiveValidators();
243
+
244
+ const result = {
245
+ count: activeValidators.length,
246
+ validators: activeValidators,
247
+ };
248
+
249
+ this.succeedSpinner("Active validators retrieved", result);
250
+ } catch (error: any) {
251
+ this.failSpinner("Failed to get active validators", error.message || error);
252
+ }
253
+ }
254
+
255
+ async listQuarantinedValidators(options: StakingConfig): Promise<void> {
256
+ this.startSpinner("Fetching quarantined validators...");
257
+
258
+ try {
259
+ const client = await this.getReadOnlyStakingClient(options);
260
+
261
+ const validators = await client.getQuarantinedValidatorsDetailed();
262
+
263
+ const result = {
264
+ count: validators.length,
265
+ validators: validators.map(v => ({
266
+ validator: v.validator,
267
+ untilEpoch: v.untilEpoch.toString(),
268
+ permanentlyBanned: v.permanentlyBanned,
269
+ })),
270
+ };
271
+
272
+ this.succeedSpinner("Quarantined validators retrieved", result);
273
+ } catch (error: any) {
274
+ this.failSpinner("Failed to get quarantined validators", error.message || error);
275
+ }
276
+ }
277
+
278
+ async listBannedValidators(options: StakingConfig): Promise<void> {
279
+ this.startSpinner("Fetching banned validators...");
280
+
281
+ try {
282
+ const client = await this.getReadOnlyStakingClient(options);
283
+
284
+ const validators = await client.getBannedValidators();
285
+
286
+ const result = {
287
+ count: validators.length,
288
+ validators: validators.map(v => ({
289
+ validator: v.validator,
290
+ untilEpoch: v.permanentlyBanned ? "permanent" : v.untilEpoch.toString(),
291
+ permanentlyBanned: v.permanentlyBanned,
292
+ })),
293
+ };
294
+
295
+ this.succeedSpinner("Banned validators retrieved", result);
296
+ } catch (error: any) {
297
+ this.failSpinner("Failed to get banned validators", error.message || error);
298
+ }
299
+ }
300
+ }
@@ -0,0 +1,38 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ export interface ValidatorClaimOptions extends StakingConfig {
5
+ validator?: string;
6
+ }
7
+
8
+ export class ValidatorClaimAction extends StakingAction {
9
+ constructor() {
10
+ super();
11
+ }
12
+
13
+ async execute(options: ValidatorClaimOptions): Promise<void> {
14
+ this.startSpinner("Claiming validator withdrawals...");
15
+
16
+ try {
17
+ const client = await this.getStakingClient(options);
18
+ const validatorAddress = options.validator || (await this.getSignerAddress());
19
+
20
+ this.setSpinnerText(`Claiming for validator ${validatorAddress}...`);
21
+
22
+ const result = await client.validatorClaim({
23
+ validator: validatorAddress as Address,
24
+ });
25
+
26
+ const output = {
27
+ transactionHash: result.transactionHash,
28
+ validator: validatorAddress,
29
+ blockNumber: result.blockNumber.toString(),
30
+ gasUsed: result.gasUsed.toString(),
31
+ };
32
+
33
+ this.succeedSpinner("Claim successful!", output);
34
+ } catch (error: any) {
35
+ this.failSpinner("Failed to claim", error.message || error);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,35 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+
3
+ export interface ValidatorDepositOptions extends StakingConfig {
4
+ amount: string;
5
+ }
6
+
7
+ export class ValidatorDepositAction extends StakingAction {
8
+ constructor() {
9
+ super();
10
+ }
11
+
12
+ async execute(options: ValidatorDepositOptions): Promise<void> {
13
+ this.startSpinner("Making validator deposit...");
14
+
15
+ try {
16
+ const client = await this.getStakingClient(options);
17
+ const amount = this.parseAmount(options.amount);
18
+
19
+ this.setSpinnerText(`Depositing ${this.formatAmount(amount)} to validator stake...`);
20
+
21
+ const result = await client.validatorDeposit({amount});
22
+
23
+ const output = {
24
+ transactionHash: result.transactionHash,
25
+ amount: this.formatAmount(amount),
26
+ blockNumber: result.blockNumber.toString(),
27
+ gasUsed: result.gasUsed.toString(),
28
+ };
29
+
30
+ this.succeedSpinner("Deposit successful!", output);
31
+ } catch (error: any) {
32
+ this.failSpinner("Failed to make deposit", error.message || error);
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,44 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+
3
+ export interface ValidatorExitOptions extends StakingConfig {
4
+ shares: string;
5
+ }
6
+
7
+ export class ValidatorExitAction extends StakingAction {
8
+ constructor() {
9
+ super();
10
+ }
11
+
12
+ async execute(options: ValidatorExitOptions): Promise<void> {
13
+ this.startSpinner("Initiating validator exit...");
14
+
15
+ try {
16
+ let shares: bigint;
17
+ try {
18
+ shares = BigInt(options.shares);
19
+ if (shares <= 0n) throw new Error("must be positive");
20
+ } catch {
21
+ this.failSpinner(`Invalid shares value: "${options.shares}". Must be a positive whole number.`);
22
+ return;
23
+ }
24
+
25
+ const client = await this.getStakingClient(options);
26
+
27
+ this.setSpinnerText(`Exiting with ${shares} shares...`);
28
+
29
+ const result = await client.validatorExit({shares});
30
+
31
+ const output = {
32
+ transactionHash: result.transactionHash,
33
+ sharesWithdrawn: shares.toString(),
34
+ blockNumber: result.blockNumber.toString(),
35
+ gasUsed: result.gasUsed.toString(),
36
+ note: "Withdrawal will be claimable after the unbonding period",
37
+ };
38
+
39
+ this.succeedSpinner("Exit initiated successfully!", output);
40
+ } catch (error: any) {
41
+ this.failSpinner("Failed to exit", error.message || error);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,47 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ export interface ValidatorJoinOptions extends StakingConfig {
5
+ amount: string;
6
+ operator?: string;
7
+ }
8
+
9
+ export class ValidatorJoinAction extends StakingAction {
10
+ constructor() {
11
+ super();
12
+ }
13
+
14
+ async execute(options: ValidatorJoinOptions): Promise<void> {
15
+ this.startSpinner("Creating a new validator...");
16
+
17
+ try {
18
+ const client = await this.getStakingClient(options);
19
+ const amount = this.parseAmount(options.amount);
20
+ const signerAddress = await this.getSignerAddress();
21
+
22
+ this.setSpinnerText(`Creating validator with ${this.formatAmount(amount)} stake...`);
23
+ this.log(` From: ${signerAddress}`);
24
+ if (options.operator) {
25
+ this.log(` Operator: ${options.operator}`);
26
+ }
27
+
28
+ const result = await client.validatorJoin({
29
+ amount,
30
+ operator: options.operator as Address | undefined,
31
+ });
32
+
33
+ const output = {
34
+ transactionHash: result.transactionHash,
35
+ validatorWallet: result.validatorWallet,
36
+ amount: result.amount,
37
+ operator: result.operator,
38
+ blockNumber: result.blockNumber.toString(),
39
+ gasUsed: result.gasUsed.toString(),
40
+ };
41
+
42
+ this.succeedSpinner("Validator created successfully!", output);
43
+ } catch (error: any) {
44
+ this.failSpinner("Failed to create validator", error.message || error);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,35 @@
1
+ import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+
4
+ export interface ValidatorPrimeOptions extends StakingConfig {
5
+ validator: string;
6
+ }
7
+
8
+ export class ValidatorPrimeAction extends StakingAction {
9
+ constructor() {
10
+ super();
11
+ }
12
+
13
+ async execute(options: ValidatorPrimeOptions): Promise<void> {
14
+ this.startSpinner("Priming validator...");
15
+
16
+ try {
17
+ const client = await this.getStakingClient(options);
18
+
19
+ this.setSpinnerText(`Priming validator ${options.validator}...`);
20
+
21
+ const result = await client.validatorPrime({validator: options.validator as Address});
22
+
23
+ const output = {
24
+ transactionHash: result.transactionHash,
25
+ validator: options.validator,
26
+ blockNumber: result.blockNumber.toString(),
27
+ gasUsed: result.gasUsed.toString(),
28
+ };
29
+
30
+ this.succeedSpinner("Validator primed for next epoch!", output);
31
+ } catch (error: any) {
32
+ this.failSpinner("Failed to prime validator", error.message || error);
33
+ }
34
+ }
35
+ }