genlayer 0.32.2 → 0.32.3

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.32.3 (2025-12-05)
4
+
3
5
  ## 0.32.2 (2025-12-04)
4
6
 
5
7
  ## 0.32.1 (2025-12-04)
package/README.md CHANGED
@@ -387,9 +387,9 @@ EXAMPLES:
387
387
  # ]
388
388
  # }
389
389
 
390
- # Exit and claim
391
- genlayer staking validator-exit --shares 100
392
- genlayer staking validator-claim
390
+ # Exit and claim (requires validator wallet address)
391
+ genlayer staking validator-exit --validator 0x... --shares 100
392
+ genlayer staking validator-claim --validator 0x...
393
393
  ```
394
394
 
395
395
  ### Running the CLI from the repository
package/dist/index.js CHANGED
@@ -10680,7 +10680,7 @@ ${prettyStateOverride(stateOverride)}`;
10680
10680
  });
10681
10681
 
10682
10682
  // node_modules/viem/_esm/errors/request.js
10683
- var HttpRequestError, RpcRequestError;
10683
+ var HttpRequestError, RpcRequestError, TimeoutError;
10684
10684
  var init_request = __esm({
10685
10685
  "node_modules/viem/_esm/errors/request.js"() {
10686
10686
  "use strict";
@@ -10753,6 +10753,15 @@ var init_request = __esm({
10753
10753
  this.data = error.data;
10754
10754
  }
10755
10755
  };
10756
+ TimeoutError = class extends BaseError2 {
10757
+ constructor({ body, url }) {
10758
+ super("The request took too long to respond.", {
10759
+ details: "The request timed out.",
10760
+ metaMessages: [`URL: ${getUrl(url)}`, `Request body: ${stringify(body)}`],
10761
+ name: "TimeoutError"
10762
+ });
10763
+ }
10764
+ };
10756
10765
  }
10757
10766
  });
10758
10767
 
@@ -18249,7 +18258,7 @@ var require_semver2 = __commonJS({
18249
18258
  import { program } from "commander";
18250
18259
 
18251
18260
  // package.json
18252
- var version = "0.32.2";
18261
+ var version = "0.32.3";
18253
18262
  var package_default = {
18254
18263
  name: "genlayer",
18255
18264
  version,
@@ -18317,7 +18326,7 @@ var package_default = {
18317
18326
  dotenv: "^17.0.0",
18318
18327
  ethers: "^6.13.4",
18319
18328
  "fs-extra": "^11.3.0",
18320
- "genlayer-js": "^0.18.7",
18329
+ "genlayer-js": "^0.18.8",
18321
18330
  inquirer: "^12.0.0",
18322
18331
  keytar: "^7.9.0",
18323
18332
  "node-fetch": "^3.0.0",
@@ -19229,7 +19238,7 @@ var doBadDataWarn = deprecate2(
19229
19238
  ".data is not a valid RequestInit property, use .body instead",
19230
19239
  "https://github.com/node-fetch/node-fetch/issues/1000 (request)"
19231
19240
  );
19232
- var Request = class _Request extends Body {
19241
+ var Request2 = class _Request extends Body {
19233
19242
  constructor(input, init = {}) {
19234
19243
  let parsedURL;
19235
19244
  if (isRequest(input)) {
@@ -19344,7 +19353,7 @@ var Request = class _Request extends Body {
19344
19353
  return "Request";
19345
19354
  }
19346
19355
  };
19347
- Object.defineProperties(Request.prototype, {
19356
+ Object.defineProperties(Request2.prototype, {
19348
19357
  method: { enumerable: true },
19349
19358
  url: { enumerable: true },
19350
19359
  headers: { enumerable: true },
@@ -19424,7 +19433,7 @@ init_from();
19424
19433
  var supportedSchemas = /* @__PURE__ */ new Set(["data:", "http:", "https:"]);
19425
19434
  async function fetch2(url, options_) {
19426
19435
  return new Promise((resolve2, reject) => {
19427
- const request = new Request(url, options_);
19436
+ const request = new Request2(url, options_);
19428
19437
  const { parsedURL, options } = getNodeRequestOptions(request);
19429
19438
  if (!supportedSchemas.has(parsedURL.protocol)) {
19430
19439
  throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${parsedURL.protocol.replace(/:$/, "")}" is not supported.`);
@@ -19554,7 +19563,7 @@ async function fetch2(url, options_) {
19554
19563
  if (responseReferrerPolicy) {
19555
19564
  requestOptions.referrerPolicy = responseReferrerPolicy;
19556
19565
  }
19557
- resolve2(fetch2(new Request(locationURL, requestOptions)));
19566
+ resolve2(fetch2(new Request2(locationURL, requestOptions)));
19558
19567
  finalize();
19559
19568
  return;
19560
19569
  }
@@ -25393,6 +25402,134 @@ function readList(cursor, length, to) {
25393
25402
  return value;
25394
25403
  }
25395
25404
 
25405
+ // node_modules/viem/_esm/utils/rpc/http.js
25406
+ init_request();
25407
+
25408
+ // node_modules/viem/_esm/utils/promise/withTimeout.js
25409
+ function withTimeout(fn, { errorInstance = new Error("timed out"), timeout, signal }) {
25410
+ return new Promise((resolve2, reject) => {
25411
+ ;
25412
+ (async () => {
25413
+ let timeoutId;
25414
+ try {
25415
+ const controller = new AbortController();
25416
+ if (timeout > 0) {
25417
+ timeoutId = setTimeout(() => {
25418
+ if (signal) {
25419
+ controller.abort();
25420
+ } else {
25421
+ reject(errorInstance);
25422
+ }
25423
+ }, timeout);
25424
+ }
25425
+ resolve2(await fn({ signal: controller?.signal || null }));
25426
+ } catch (err) {
25427
+ if (err?.name === "AbortError")
25428
+ reject(errorInstance);
25429
+ reject(err);
25430
+ } finally {
25431
+ clearTimeout(timeoutId);
25432
+ }
25433
+ })();
25434
+ });
25435
+ }
25436
+
25437
+ // node_modules/viem/_esm/utils/rpc/http.js
25438
+ init_stringify();
25439
+
25440
+ // node_modules/viem/_esm/utils/rpc/id.js
25441
+ function createIdStore() {
25442
+ return {
25443
+ current: 0,
25444
+ take() {
25445
+ return this.current++;
25446
+ },
25447
+ reset() {
25448
+ this.current = 0;
25449
+ }
25450
+ };
25451
+ }
25452
+ var idCache = /* @__PURE__ */ createIdStore();
25453
+
25454
+ // node_modules/viem/_esm/utils/rpc/http.js
25455
+ function getHttpRpcClient(url, options = {}) {
25456
+ return {
25457
+ async request(params) {
25458
+ const { body, fetchFn = options.fetchFn ?? fetch, onRequest = options.onRequest, onResponse = options.onResponse, timeout = options.timeout ?? 1e4 } = params;
25459
+ const fetchOptions = {
25460
+ ...options.fetchOptions ?? {},
25461
+ ...params.fetchOptions ?? {}
25462
+ };
25463
+ const { headers, method, signal: signal_ } = fetchOptions;
25464
+ try {
25465
+ const response = await withTimeout(async ({ signal }) => {
25466
+ const init = {
25467
+ ...fetchOptions,
25468
+ body: Array.isArray(body) ? stringify(body.map((body2) => ({
25469
+ jsonrpc: "2.0",
25470
+ id: body2.id ?? idCache.take(),
25471
+ ...body2
25472
+ }))) : stringify({
25473
+ jsonrpc: "2.0",
25474
+ id: body.id ?? idCache.take(),
25475
+ ...body
25476
+ }),
25477
+ headers: {
25478
+ "Content-Type": "application/json",
25479
+ ...headers
25480
+ },
25481
+ method: method || "POST",
25482
+ signal: signal_ || (timeout > 0 ? signal : null)
25483
+ };
25484
+ const request = new Request(url, init);
25485
+ const args = await onRequest?.(request, init) ?? { ...init, url };
25486
+ const response2 = await fetchFn(args.url ?? url, args);
25487
+ return response2;
25488
+ }, {
25489
+ errorInstance: new TimeoutError({ body, url }),
25490
+ timeout,
25491
+ signal: true
25492
+ });
25493
+ if (onResponse)
25494
+ await onResponse(response);
25495
+ let data;
25496
+ if (response.headers.get("Content-Type")?.startsWith("application/json"))
25497
+ data = await response.json();
25498
+ else {
25499
+ data = await response.text();
25500
+ try {
25501
+ data = JSON.parse(data || "{}");
25502
+ } catch (err) {
25503
+ if (response.ok)
25504
+ throw err;
25505
+ data = { error: data };
25506
+ }
25507
+ }
25508
+ if (!response.ok) {
25509
+ throw new HttpRequestError({
25510
+ body,
25511
+ details: stringify(data.error) || response.statusText,
25512
+ headers: response.headers,
25513
+ status: response.status,
25514
+ url
25515
+ });
25516
+ }
25517
+ return data;
25518
+ } catch (err) {
25519
+ if (err instanceof HttpRequestError)
25520
+ throw err;
25521
+ if (err instanceof TimeoutError)
25522
+ throw err;
25523
+ throw new HttpRequestError({
25524
+ body,
25525
+ cause: err,
25526
+ url
25527
+ });
25528
+ }
25529
+ }
25530
+ };
25531
+ }
25532
+
25396
25533
  // node_modules/viem/_esm/utils/signature/hashMessage.js
25397
25534
  init_keccak256();
25398
25535
 
@@ -31645,6 +31782,19 @@ function walletActions(client) {
31645
31782
  };
31646
31783
  }
31647
31784
 
31785
+ // node_modules/viem/_esm/clients/createWalletClient.js
31786
+ function createWalletClient(parameters) {
31787
+ const { key = "wallet", name = "Wallet Client", transport } = parameters;
31788
+ const client = createClient({
31789
+ ...parameters,
31790
+ key,
31791
+ name,
31792
+ transport,
31793
+ type: "walletClient"
31794
+ });
31795
+ return client.extend(walletActions);
31796
+ }
31797
+
31648
31798
  // node_modules/viem/_esm/clients/transports/createTransport.js
31649
31799
  function createTransport({ key, methods, name, request, retryCount = 3, retryDelay = 150, timeout, type }, value) {
31650
31800
  const uid2 = uid();
@@ -31678,6 +31828,82 @@ function custom(provider, config = {}) {
31678
31828
  });
31679
31829
  }
31680
31830
 
31831
+ // node_modules/viem/_esm/clients/transports/http.js
31832
+ init_request();
31833
+
31834
+ // node_modules/viem/_esm/errors/transport.js
31835
+ init_base();
31836
+ var UrlRequiredError = class extends BaseError2 {
31837
+ constructor() {
31838
+ super("No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.", {
31839
+ docsPath: "/docs/clients/intro",
31840
+ name: "UrlRequiredError"
31841
+ });
31842
+ }
31843
+ };
31844
+
31845
+ // node_modules/viem/_esm/clients/transports/http.js
31846
+ init_createBatchScheduler();
31847
+ function http3(url, config = {}) {
31848
+ const { batch, fetchFn, fetchOptions, key = "http", methods, name = "HTTP JSON-RPC", onFetchRequest, onFetchResponse, retryDelay, raw } = config;
31849
+ return ({ chain, retryCount: retryCount_, timeout: timeout_ }) => {
31850
+ const { batchSize = 1e3, wait: wait2 = 0 } = typeof batch === "object" ? batch : {};
31851
+ const retryCount = config.retryCount ?? retryCount_;
31852
+ const timeout = timeout_ ?? config.timeout ?? 1e4;
31853
+ const url_ = url || chain?.rpcUrls.default.http[0];
31854
+ if (!url_)
31855
+ throw new UrlRequiredError();
31856
+ const rpcClient2 = getHttpRpcClient(url_, {
31857
+ fetchFn,
31858
+ fetchOptions,
31859
+ onRequest: onFetchRequest,
31860
+ onResponse: onFetchResponse,
31861
+ timeout
31862
+ });
31863
+ return createTransport({
31864
+ key,
31865
+ methods,
31866
+ name,
31867
+ async request({ method, params }) {
31868
+ const body = { method, params };
31869
+ const { schedule } = createBatchScheduler({
31870
+ id: url_,
31871
+ wait: wait2,
31872
+ shouldSplitBatch(requests) {
31873
+ return requests.length > batchSize;
31874
+ },
31875
+ fn: (body2) => rpcClient2.request({
31876
+ body: body2
31877
+ }),
31878
+ sort: (a, b) => a.id - b.id
31879
+ });
31880
+ const fn = async (body2) => batch ? schedule(body2) : [
31881
+ await rpcClient2.request({
31882
+ body: body2
31883
+ })
31884
+ ];
31885
+ const [{ error, result }] = await fn(body);
31886
+ if (raw)
31887
+ return { error, result };
31888
+ if (error)
31889
+ throw new RpcRequestError({
31890
+ body,
31891
+ error,
31892
+ url: url_
31893
+ });
31894
+ return result;
31895
+ },
31896
+ retryCount,
31897
+ retryDelay,
31898
+ timeout,
31899
+ type: "http"
31900
+ }, {
31901
+ fetchOptions,
31902
+ url: url_
31903
+ });
31904
+ };
31905
+ }
31906
+
31681
31907
  // node_modules/viem/_esm/index.js
31682
31908
  init_base();
31683
31909
  init_contract();
@@ -31687,7 +31913,7 @@ init_fromHex();
31687
31913
  init_toHex();
31688
31914
  init_formatEther();
31689
31915
 
31690
- // node_modules/genlayer-js/dist/chunk-RW6PLN5W.js
31916
+ // node_modules/genlayer-js/dist/chunk-V4ZFI4GV.js
31691
31917
  var chains_exports = {};
31692
31918
  __export2(chains_exports, {
31693
31919
  localnet: () => localnet,
@@ -39783,6 +40009,67 @@ var VALIDATOR_WALLET_ABI = [
39783
40009
  { name: "extraCid", type: "bytes" }
39784
40010
  ],
39785
40011
  outputs: []
40012
+ },
40013
+ // Staking functions (forwarded to staking contract)
40014
+ {
40015
+ name: "validatorDeposit",
40016
+ type: "function",
40017
+ stateMutability: "payable",
40018
+ inputs: [],
40019
+ outputs: []
40020
+ },
40021
+ {
40022
+ name: "validatorExit",
40023
+ type: "function",
40024
+ stateMutability: "nonpayable",
40025
+ inputs: [{ name: "_shares", type: "uint256" }],
40026
+ outputs: []
40027
+ },
40028
+ {
40029
+ name: "validatorClaim",
40030
+ type: "function",
40031
+ stateMutability: "nonpayable",
40032
+ inputs: [],
40033
+ outputs: []
40034
+ },
40035
+ // Two-step operator transfer
40036
+ {
40037
+ name: "initiateOperatorTransfer",
40038
+ type: "function",
40039
+ stateMutability: "nonpayable",
40040
+ inputs: [{ name: "_newOperator", type: "address" }],
40041
+ outputs: []
40042
+ },
40043
+ {
40044
+ name: "completeOperatorTransfer",
40045
+ type: "function",
40046
+ stateMutability: "nonpayable",
40047
+ inputs: [],
40048
+ outputs: []
40049
+ },
40050
+ {
40051
+ name: "cancelOperatorTransfer",
40052
+ type: "function",
40053
+ stateMutability: "nonpayable",
40054
+ inputs: [],
40055
+ outputs: []
40056
+ },
40057
+ {
40058
+ name: "getPendingOperator",
40059
+ type: "function",
40060
+ stateMutability: "view",
40061
+ inputs: [],
40062
+ outputs: [
40063
+ { name: "", type: "address" },
40064
+ { name: "", type: "uint256" }
40065
+ ]
40066
+ },
40067
+ {
40068
+ name: "getOperator",
40069
+ type: "function",
40070
+ stateMutability: "view",
40071
+ inputs: [],
40072
+ outputs: [{ name: "", type: "address" }]
39786
40073
  }
39787
40074
  ];
39788
40075
  var STAKING_ABI = [
@@ -49226,6 +49513,33 @@ var StakingAction = class extends BaseAction {
49226
49513
  const addr = keystoreData.address;
49227
49514
  return addr.startsWith("0x") ? addr : `0x${addr}`;
49228
49515
  }
49516
+ /**
49517
+ * Get viem clients for direct contract interactions (e.g., ValidatorWallet calls)
49518
+ * Future: can be extended to support hardware wallets
49519
+ */
49520
+ async getViemClients(config) {
49521
+ if (config.account) {
49522
+ this.accountOverride = config.account;
49523
+ }
49524
+ const network = this.getNetwork(config);
49525
+ const rpcUrl = config.rpc || network.rpcUrls.default.http[0];
49526
+ const privateKey = await this.getPrivateKeyForStaking();
49527
+ const account = privateKeyToAccount(privateKey);
49528
+ const publicClient = createPublicClient({
49529
+ chain: network,
49530
+ transport: http3(rpcUrl)
49531
+ });
49532
+ const walletClient = createWalletClient({
49533
+ chain: network,
49534
+ transport: http3(rpcUrl),
49535
+ account
49536
+ });
49537
+ return {
49538
+ walletClient,
49539
+ publicClient,
49540
+ signerAddress: account.address
49541
+ };
49542
+ }
49229
49543
  };
49230
49544
 
49231
49545
  // src/commands/staking/validatorJoin.ts
@@ -49271,15 +49585,23 @@ var ValidatorDepositAction = class extends StakingAction {
49271
49585
  async execute(options) {
49272
49586
  this.startSpinner("Making validator deposit...");
49273
49587
  try {
49274
- const client = await this.getStakingClient(options);
49275
49588
  const amount = this.parseAmount(options.amount);
49276
- this.setSpinnerText(`Depositing ${this.formatAmount(amount)} to validator stake...`);
49277
- const result = await client.validatorDeposit({ amount });
49589
+ const validatorWallet = options.validator;
49590
+ const { walletClient, publicClient } = await this.getViemClients(options);
49591
+ this.setSpinnerText(`Depositing ${this.formatAmount(amount)} to validator ${validatorWallet}...`);
49592
+ const hash3 = await walletClient.writeContract({
49593
+ address: validatorWallet,
49594
+ abi: abi_exports.VALIDATOR_WALLET_ABI,
49595
+ functionName: "validatorDeposit",
49596
+ value: amount
49597
+ });
49598
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: hash3 });
49278
49599
  const output = {
49279
- transactionHash: result.transactionHash,
49600
+ transactionHash: receipt.transactionHash,
49601
+ validator: validatorWallet,
49280
49602
  amount: this.formatAmount(amount),
49281
- blockNumber: result.blockNumber.toString(),
49282
- gasUsed: result.gasUsed.toString()
49603
+ blockNumber: receipt.blockNumber.toString(),
49604
+ gasUsed: receipt.gasUsed.toString()
49283
49605
  };
49284
49606
  this.succeedSpinner("Deposit successful!", output);
49285
49607
  } catch (error) {
@@ -49304,15 +49626,26 @@ var ValidatorExitAction = class extends StakingAction {
49304
49626
  this.failSpinner(`Invalid shares value: "${options.shares}". Must be a positive whole number.`);
49305
49627
  return;
49306
49628
  }
49307
- const client = await this.getStakingClient(options);
49308
- this.setSpinnerText(`Exiting with ${shares} shares...`);
49309
- const result = await client.validatorExit({ shares });
49629
+ const validatorWallet = options.validator;
49630
+ const { walletClient, publicClient } = await this.getViemClients(options);
49631
+ this.setSpinnerText(`Exiting validator ${validatorWallet} with ${shares} shares...`);
49632
+ const hash3 = await walletClient.writeContract({
49633
+ address: validatorWallet,
49634
+ abi: abi_exports.VALIDATOR_WALLET_ABI,
49635
+ functionName: "validatorExit",
49636
+ args: [shares]
49637
+ });
49638
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: hash3 });
49639
+ const readClient = await this.getReadOnlyStakingClient(options);
49640
+ const epochInfo = await readClient.getEpochInfo();
49641
+ const isEpochZero = epochInfo.currentEpoch === 0n;
49310
49642
  const output = {
49311
- transactionHash: result.transactionHash,
49643
+ transactionHash: receipt.transactionHash,
49644
+ validator: validatorWallet,
49312
49645
  sharesWithdrawn: shares.toString(),
49313
- blockNumber: result.blockNumber.toString(),
49314
- gasUsed: result.gasUsed.toString(),
49315
- note: "Withdrawal will be claimable after the unbonding period"
49646
+ blockNumber: receipt.blockNumber.toString(),
49647
+ gasUsed: receipt.gasUsed.toString(),
49648
+ note: isEpochZero ? "Epoch 0: Withdrawal claimable immediately" : "Withdrawal will be claimable after the unbonding period"
49316
49649
  };
49317
49650
  this.succeedSpinner("Exit initiated successfully!", output);
49318
49651
  } catch (error) {
@@ -49329,17 +49662,20 @@ var ValidatorClaimAction = class extends StakingAction {
49329
49662
  async execute(options) {
49330
49663
  this.startSpinner("Claiming validator withdrawals...");
49331
49664
  try {
49332
- const client = await this.getStakingClient(options);
49333
- const validatorAddress = options.validator || await this.getSignerAddress();
49334
- this.setSpinnerText(`Claiming for validator ${validatorAddress}...`);
49335
- const result = await client.validatorClaim({
49336
- validator: validatorAddress
49665
+ const validatorWallet = options.validator;
49666
+ const { walletClient, publicClient } = await this.getViemClients(options);
49667
+ this.setSpinnerText(`Claiming for validator ${validatorWallet}...`);
49668
+ const hash3 = await walletClient.writeContract({
49669
+ address: validatorWallet,
49670
+ abi: abi_exports.VALIDATOR_WALLET_ABI,
49671
+ functionName: "validatorClaim"
49337
49672
  });
49673
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: hash3 });
49338
49674
  const output = {
49339
- transactionHash: result.transactionHash,
49340
- validator: validatorAddress,
49341
- blockNumber: result.blockNumber.toString(),
49342
- gasUsed: result.gasUsed.toString()
49675
+ transactionHash: receipt.transactionHash,
49676
+ validator: validatorWallet,
49677
+ blockNumber: receipt.blockNumber.toString(),
49678
+ gasUsed: receipt.gasUsed.toString()
49343
49679
  };
49344
49680
  this.succeedSpinner("Claim successful!", output);
49345
49681
  } catch (error) {
@@ -49380,18 +49716,22 @@ var SetOperatorAction = class extends StakingAction {
49380
49716
  async execute(options) {
49381
49717
  this.startSpinner("Setting operator...");
49382
49718
  try {
49383
- const client = await this.getStakingClient(options);
49719
+ const validatorWallet = options.validator;
49720
+ const { walletClient, publicClient } = await this.getViemClients(options);
49384
49721
  this.setSpinnerText(`Setting operator to ${options.operator}...`);
49385
- const result = await client.setOperator({
49386
- validator: options.validator,
49387
- operator: options.operator
49722
+ const hash3 = await walletClient.writeContract({
49723
+ address: validatorWallet,
49724
+ abi: abi_exports.VALIDATOR_WALLET_ABI,
49725
+ functionName: "setOperator",
49726
+ args: [options.operator]
49388
49727
  });
49728
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: hash3 });
49389
49729
  const output = {
49390
- transactionHash: result.transactionHash,
49391
- validator: options.validator,
49730
+ transactionHash: receipt.transactionHash,
49731
+ validator: validatorWallet,
49392
49732
  newOperator: options.operator,
49393
- blockNumber: result.blockNumber.toString(),
49394
- gasUsed: result.gasUsed.toString()
49733
+ blockNumber: receipt.blockNumber.toString(),
49734
+ gasUsed: receipt.gasUsed.toString()
49395
49735
  };
49396
49736
  this.succeedSpinner("Operator updated!", output);
49397
49737
  } catch (error) {
@@ -49408,26 +49748,33 @@ var SetIdentityAction = class extends StakingAction {
49408
49748
  async execute(options) {
49409
49749
  this.startSpinner("Setting validator identity...");
49410
49750
  try {
49411
- const client = await this.getStakingClient(options);
49412
- this.setSpinnerText(`Setting identity for ${options.validator}...`);
49413
- const result = await client.setIdentity({
49414
- validator: options.validator,
49415
- moniker: options.moniker,
49416
- logoUri: options.logoUri,
49417
- website: options.website,
49418
- description: options.description,
49419
- email: options.email,
49420
- twitter: options.twitter,
49421
- telegram: options.telegram,
49422
- github: options.github,
49423
- extraCid: options.extraCid
49751
+ const validatorWallet = options.validator;
49752
+ const { walletClient, publicClient } = await this.getViemClients(options);
49753
+ this.setSpinnerText(`Setting identity for ${validatorWallet}...`);
49754
+ const extraCidBytes = options.extraCid ? toHex(new TextEncoder().encode(options.extraCid)) : "0x";
49755
+ const hash3 = await walletClient.writeContract({
49756
+ address: validatorWallet,
49757
+ abi: abi_exports.VALIDATOR_WALLET_ABI,
49758
+ functionName: "setIdentity",
49759
+ args: [
49760
+ options.moniker,
49761
+ options.logoUri || "",
49762
+ options.website || "",
49763
+ options.description || "",
49764
+ options.email || "",
49765
+ options.twitter || "",
49766
+ options.telegram || "",
49767
+ options.github || "",
49768
+ extraCidBytes
49769
+ ]
49424
49770
  });
49771
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: hash3 });
49425
49772
  const output = {
49426
- transactionHash: result.transactionHash,
49427
- validator: options.validator,
49773
+ transactionHash: receipt.transactionHash,
49774
+ validator: validatorWallet,
49428
49775
  moniker: options.moniker,
49429
- blockNumber: result.blockNumber.toString(),
49430
- gasUsed: result.gasUsed.toString()
49776
+ blockNumber: receipt.blockNumber.toString(),
49777
+ gasUsed: receipt.gasUsed.toString()
49431
49778
  };
49432
49779
  if (options.logoUri) output.logoUri = options.logoUri;
49433
49780
  if (options.website) output.website = options.website;
@@ -49498,13 +49845,15 @@ var DelegatorExitAction = class extends StakingAction {
49498
49845
  validator: options.validator,
49499
49846
  shares
49500
49847
  });
49848
+ const epochInfo = await client.getEpochInfo();
49849
+ const isEpochZero = epochInfo.currentEpoch === 0n;
49501
49850
  const output = {
49502
49851
  transactionHash: result.transactionHash,
49503
49852
  validator: options.validator,
49504
49853
  sharesWithdrawn: shares.toString(),
49505
49854
  blockNumber: result.blockNumber.toString(),
49506
49855
  gasUsed: result.gasUsed.toString(),
49507
- note: "Withdrawal will be claimable after the unbonding period"
49856
+ note: isEpochZero ? "Epoch 0: Withdrawal claimable immediately" : "Withdrawal will be claimable after the unbonding period"
49508
49857
  };
49509
49858
  this.succeedSpinner("Exit initiated successfully!", output);
49510
49859
  } catch (error) {
@@ -50424,15 +50773,15 @@ function initializeStakingCommands(program2) {
50424
50773
  const action = new ValidatorJoinAction();
50425
50774
  await action.execute(options);
50426
50775
  });
50427
- staking.command("validator-deposit").description("Make an additional deposit as a validator").requiredOption("--amount <amount>", "Amount to deposit (in wei or with 'eth'/'gen' suffix)").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50776
+ staking.command("validator-deposit").description("Make an additional deposit to a validator wallet").requiredOption("--validator <address>", "Validator wallet contract address to deposit to").requiredOption("--amount <amount>", "Amount to deposit (in wei or with 'eth'/'gen' suffix)").option("--account <name>", "Account to use (must be validator owner)").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (options) => {
50428
50777
  const action = new ValidatorDepositAction();
50429
50778
  await action.execute(options);
50430
50779
  });
50431
- staking.command("validator-exit").description("Exit as a validator by withdrawing shares").requiredOption("--shares <shares>", "Number of shares to withdraw").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50780
+ staking.command("validator-exit").description("Exit as a validator by withdrawing shares").requiredOption("--validator <address>", "Validator wallet contract address").requiredOption("--shares <shares>", "Number of shares to withdraw").option("--account <name>", "Account to use (must be validator owner)").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (options) => {
50432
50781
  const action = new ValidatorExitAction();
50433
50782
  await action.execute(options);
50434
50783
  });
50435
- staking.command("validator-claim").description("Claim validator withdrawals after unbonding period").option("--validator <address>", "Validator address (defaults to signer)").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50784
+ staking.command("validator-claim").description("Claim validator withdrawals after unbonding period").requiredOption("--validator <address>", "Validator wallet contract address").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (options) => {
50436
50785
  const action = new ValidatorClaimAction();
50437
50786
  await action.execute(options);
50438
50787
  });
@@ -50440,11 +50789,11 @@ function initializeStakingCommands(program2) {
50440
50789
  const action = new ValidatorPrimeAction();
50441
50790
  await action.execute(options);
50442
50791
  });
50443
- staking.command("set-operator").description("Change the operator address for a validator wallet").requiredOption("--validator <address>", "Validator wallet address").requiredOption("--operator <address>", "New operator address").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50792
+ staking.command("set-operator").description("Change the operator address for a validator wallet").requiredOption("--validator <address>", "Validator wallet address").requiredOption("--operator <address>", "New operator address").option("--account <name>", "Account to use (must be validator owner)").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (options) => {
50444
50793
  const action = new SetOperatorAction();
50445
50794
  await action.execute(options);
50446
50795
  });
50447
- staking.command("set-identity").description("Set validator identity metadata (moniker, website, socials, etc.)").requiredOption("--validator <address>", "Validator wallet address").requiredOption("--moniker <name>", "Validator display name").option("--logo-uri <uri>", "Logo URI").option("--website <url>", "Website URL").option("--description <text>", "Description").option("--email <email>", "Contact email").option("--twitter <handle>", "Twitter handle").option("--telegram <handle>", "Telegram handle").option("--github <handle>", "GitHub handle").option("--extra-cid <cid>", "Extra data as IPFS CID or hex bytes (0x...)").option("--account <name>", "Account to use").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50796
+ staking.command("set-identity").description("Set validator identity metadata (moniker, website, socials, etc.)").requiredOption("--validator <address>", "Validator wallet address").requiredOption("--moniker <name>", "Validator display name").option("--logo-uri <uri>", "Logo URI").option("--website <url>", "Website URL").option("--description <text>", "Description").option("--email <email>", "Contact email").option("--twitter <handle>", "Twitter handle").option("--telegram <handle>", "Telegram handle").option("--github <handle>", "GitHub handle").option("--extra-cid <cid>", "Extra data as IPFS CID or hex bytes (0x...)").option("--account <name>", "Account to use (must be validator operator)").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (options) => {
50448
50797
  const action = new SetIdentityAction();
50449
50798
  await action.execute(options);
50450
50799
  });
@@ -229,7 +229,7 @@ Output will include:
229
229
  ### Add More Stake
230
230
 
231
231
  ```bash
232
- genlayer staking validator-deposit --amount 1000gen
232
+ genlayer staking validator-deposit --validator 0xYourValidatorWallet... --amount 1000gen
233
233
  ```
234
234
 
235
235
  ### Check Active Validators
@@ -241,7 +241,7 @@ genlayer staking active-validators
241
241
  ### Exit as Validator
242
242
 
243
243
  ```bash
244
- genlayer staking validator-exit --shares 100
244
+ genlayer staking validator-exit --validator 0xYourValidatorWallet... --shares 100
245
245
  ```
246
246
 
247
247
  This initiates a withdrawal. Your tokens enter an **unbonding period of 7 epochs** before they can be claimed.
@@ -264,7 +264,7 @@ selfStakePendingWithdrawals: [
264
264
  After the 7-epoch unbonding period:
265
265
 
266
266
  ```bash
267
- genlayer staking validator-claim
267
+ genlayer staking validator-claim --validator 0xYourValidatorWallet...
268
268
  ```
269
269
 
270
270
  ## Troubleshooting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.32.2",
3
+ "version": "0.32.3",
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.18.7",
68
+ "genlayer-js": "^0.18.8",
69
69
  "inquirer": "^12.0.0",
70
70
  "keytar": "^7.9.0",
71
71
  "node-fetch": "^3.0.0",
@@ -3,6 +3,8 @@ 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
+ import {createPublicClient, createWalletClient, http, type PublicClient, type WalletClient, type Chain, type Account} from "viem";
7
+ import {privateKeyToAccount} from "viem/accounts";
6
8
 
7
9
  // Re-export for use by other staking commands
8
10
  export {BUILT_IN_NETWORKS};
@@ -154,4 +156,41 @@ export class StakingAction extends BaseAction {
154
156
  const addr = keystoreData.address as string;
155
157
  return (addr.startsWith("0x") ? addr : `0x${addr}`) as Address;
156
158
  }
159
+
160
+ /**
161
+ * Get viem clients for direct contract interactions (e.g., ValidatorWallet calls)
162
+ * Future: can be extended to support hardware wallets
163
+ */
164
+ protected async getViemClients(config: StakingConfig): Promise<{
165
+ walletClient: WalletClient<any, Chain, Account>;
166
+ publicClient: PublicClient;
167
+ signerAddress: Address;
168
+ }> {
169
+ if (config.account) {
170
+ this.accountOverride = config.account;
171
+ }
172
+
173
+ const network = this.getNetwork(config);
174
+ const rpcUrl = config.rpc || network.rpcUrls.default.http[0];
175
+
176
+ const privateKey = await this.getPrivateKeyForStaking();
177
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
178
+
179
+ const publicClient = createPublicClient({
180
+ chain: network,
181
+ transport: http(rpcUrl),
182
+ });
183
+
184
+ const walletClient = createWalletClient({
185
+ chain: network,
186
+ transport: http(rpcUrl),
187
+ account,
188
+ });
189
+
190
+ return {
191
+ walletClient,
192
+ publicClient,
193
+ signerAddress: account.address as Address,
194
+ };
195
+ }
157
196
  }
@@ -33,13 +33,19 @@ export class DelegatorExitAction extends StakingAction {
33
33
  shares,
34
34
  });
35
35
 
36
+ // Check epoch to determine note
37
+ const epochInfo = await client.getEpochInfo();
38
+ const isEpochZero = epochInfo.currentEpoch === 0n;
39
+
36
40
  const output = {
37
41
  transactionHash: result.transactionHash,
38
42
  validator: options.validator,
39
43
  sharesWithdrawn: shares.toString(),
40
44
  blockNumber: result.blockNumber.toString(),
41
45
  gasUsed: result.gasUsed.toString(),
42
- note: "Withdrawal will be claimable after the unbonding period",
46
+ note: isEpochZero
47
+ ? "Epoch 0: Withdrawal claimable immediately"
48
+ : "Withdrawal will be claimable after the unbonding period",
43
49
  };
44
50
 
45
51
  this.succeedSpinner("Exit initiated successfully!", output);
@@ -46,12 +46,12 @@ export function initializeStakingCommands(program: Command) {
46
46
 
47
47
  staking
48
48
  .command("validator-deposit")
49
- .description("Make an additional deposit as a validator")
49
+ .description("Make an additional deposit to a validator wallet")
50
+ .requiredOption("--validator <address>", "Validator wallet contract address to deposit to")
50
51
  .requiredOption("--amount <amount>", "Amount to deposit (in wei or with 'eth'/'gen' suffix)")
51
- .option("--account <name>", "Account to use")
52
+ .option("--account <name>", "Account to use (must be validator owner)")
52
53
  .option("--network <network>", "Network to use (localnet, testnet-asimov)")
53
54
  .option("--rpc <rpcUrl>", "RPC URL for the network")
54
- .option("--staking-address <address>", "Staking contract address (overrides chain config)")
55
55
  .action(async (options: ValidatorDepositOptions) => {
56
56
  const action = new ValidatorDepositAction();
57
57
  await action.execute(options);
@@ -60,11 +60,11 @@ export function initializeStakingCommands(program: Command) {
60
60
  staking
61
61
  .command("validator-exit")
62
62
  .description("Exit as a validator by withdrawing shares")
63
+ .requiredOption("--validator <address>", "Validator wallet contract address")
63
64
  .requiredOption("--shares <shares>", "Number of shares to withdraw")
64
- .option("--account <name>", "Account to use")
65
+ .option("--account <name>", "Account to use (must be validator owner)")
65
66
  .option("--network <network>", "Network to use (localnet, testnet-asimov)")
66
67
  .option("--rpc <rpcUrl>", "RPC URL for the network")
67
- .option("--staking-address <address>", "Staking contract address (overrides chain config)")
68
68
  .action(async (options: ValidatorExitOptions) => {
69
69
  const action = new ValidatorExitAction();
70
70
  await action.execute(options);
@@ -73,11 +73,10 @@ export function initializeStakingCommands(program: Command) {
73
73
  staking
74
74
  .command("validator-claim")
75
75
  .description("Claim validator withdrawals after unbonding period")
76
- .option("--validator <address>", "Validator address (defaults to signer)")
76
+ .requiredOption("--validator <address>", "Validator wallet contract address")
77
77
  .option("--account <name>", "Account to use")
78
78
  .option("--network <network>", "Network to use (localnet, testnet-asimov)")
79
79
  .option("--rpc <rpcUrl>", "RPC URL for the network")
80
- .option("--staking-address <address>", "Staking contract address (overrides chain config)")
81
80
  .action(async (options: ValidatorClaimOptions) => {
82
81
  const action = new ValidatorClaimAction();
83
82
  await action.execute(options);
@@ -101,10 +100,9 @@ export function initializeStakingCommands(program: Command) {
101
100
  .description("Change the operator address for a validator wallet")
102
101
  .requiredOption("--validator <address>", "Validator wallet address")
103
102
  .requiredOption("--operator <address>", "New operator address")
104
- .option("--account <name>", "Account to use")
103
+ .option("--account <name>", "Account to use (must be validator owner)")
105
104
  .option("--network <network>", "Network to use (localnet, testnet-asimov)")
106
105
  .option("--rpc <rpcUrl>", "RPC URL for the network")
107
- .option("--staking-address <address>", "Staking contract address (overrides chain config)")
108
106
  .action(async (options: SetOperatorOptions) => {
109
107
  const action = new SetOperatorAction();
110
108
  await action.execute(options);
@@ -123,10 +121,9 @@ export function initializeStakingCommands(program: Command) {
123
121
  .option("--telegram <handle>", "Telegram handle")
124
122
  .option("--github <handle>", "GitHub handle")
125
123
  .option("--extra-cid <cid>", "Extra data as IPFS CID or hex bytes (0x...)")
126
- .option("--account <name>", "Account to use")
124
+ .option("--account <name>", "Account to use (must be validator operator)")
127
125
  .option("--network <network>", "Network to use (localnet, testnet-asimov)")
128
126
  .option("--rpc <rpcUrl>", "RPC URL for the network")
129
- .option("--staking-address <address>", "Staking contract address (overrides chain config)")
130
127
  .action(async (options: SetIdentityOptions) => {
131
128
  const action = new SetIdentityAction();
132
129
  await action.execute(options);
@@ -1,5 +1,7 @@
1
1
  import {StakingAction, StakingConfig} from "./StakingAction";
2
2
  import type {Address} from "genlayer-js/types";
3
+ import {abi} from "genlayer-js";
4
+ import {toHex} from "viem";
3
5
 
4
6
  export interface SetIdentityOptions extends StakingConfig {
5
7
  validator: string;
@@ -23,29 +25,39 @@ export class SetIdentityAction extends StakingAction {
23
25
  this.startSpinner("Setting validator identity...");
24
26
 
25
27
  try {
26
- const client = await this.getStakingClient(options);
28
+ const validatorWallet = options.validator as Address;
29
+ const {walletClient, publicClient} = await this.getViemClients(options);
27
30
 
28
- this.setSpinnerText(`Setting identity for ${options.validator}...`);
31
+ this.setSpinnerText(`Setting identity for ${validatorWallet}...`);
29
32
 
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,
33
+ // Convert extraCid string to bytes (hex)
34
+ const extraCidBytes = options.extraCid ? toHex(new TextEncoder().encode(options.extraCid)) : "0x";
35
+
36
+ const hash = await walletClient.writeContract({
37
+ address: validatorWallet,
38
+ abi: abi.VALIDATOR_WALLET_ABI,
39
+ functionName: "setIdentity",
40
+ args: [
41
+ options.moniker,
42
+ options.logoUri || "",
43
+ options.website || "",
44
+ options.description || "",
45
+ options.email || "",
46
+ options.twitter || "",
47
+ options.telegram || "",
48
+ options.github || "",
49
+ extraCidBytes,
50
+ ],
41
51
  });
42
52
 
53
+ const receipt = await publicClient.waitForTransactionReceipt({hash});
54
+
43
55
  const output: Record<string, any> = {
44
- transactionHash: result.transactionHash,
45
- validator: options.validator,
56
+ transactionHash: receipt.transactionHash,
57
+ validator: validatorWallet,
46
58
  moniker: options.moniker,
47
- blockNumber: result.blockNumber.toString(),
48
- gasUsed: result.gasUsed.toString(),
59
+ blockNumber: receipt.blockNumber.toString(),
60
+ gasUsed: receipt.gasUsed.toString(),
49
61
  };
50
62
 
51
63
  // Add optional fields that were set
@@ -1,5 +1,6 @@
1
1
  import {StakingAction, StakingConfig} from "./StakingAction";
2
2
  import type {Address} from "genlayer-js/types";
3
+ import {abi} from "genlayer-js";
3
4
 
4
5
  export interface SetOperatorOptions extends StakingConfig {
5
6
  validator: string;
@@ -15,21 +16,26 @@ export class SetOperatorAction extends StakingAction {
15
16
  this.startSpinner("Setting operator...");
16
17
 
17
18
  try {
18
- const client = await this.getStakingClient(options);
19
+ const validatorWallet = options.validator as Address;
20
+ const {walletClient, publicClient} = await this.getViemClients(options);
19
21
 
20
22
  this.setSpinnerText(`Setting operator to ${options.operator}...`);
21
23
 
22
- const result = await client.setOperator({
23
- validator: options.validator as Address,
24
- operator: options.operator as Address,
24
+ const hash = await walletClient.writeContract({
25
+ address: validatorWallet,
26
+ abi: abi.VALIDATOR_WALLET_ABI,
27
+ functionName: "setOperator",
28
+ args: [options.operator as Address],
25
29
  });
26
30
 
31
+ const receipt = await publicClient.waitForTransactionReceipt({hash});
32
+
27
33
  const output = {
28
- transactionHash: result.transactionHash,
29
- validator: options.validator,
34
+ transactionHash: receipt.transactionHash,
35
+ validator: validatorWallet,
30
36
  newOperator: options.operator,
31
- blockNumber: result.blockNumber.toString(),
32
- gasUsed: result.gasUsed.toString(),
37
+ blockNumber: receipt.blockNumber.toString(),
38
+ gasUsed: receipt.gasUsed.toString(),
33
39
  };
34
40
 
35
41
  this.succeedSpinner("Operator updated!", output);
@@ -1,8 +1,9 @@
1
1
  import {StakingAction, StakingConfig} from "./StakingAction";
2
2
  import type {Address} from "genlayer-js/types";
3
+ import {abi} from "genlayer-js";
3
4
 
4
5
  export interface ValidatorClaimOptions extends StakingConfig {
5
- validator?: string;
6
+ validator: string;
6
7
  }
7
8
 
8
9
  export class ValidatorClaimAction extends StakingAction {
@@ -14,20 +15,24 @@ export class ValidatorClaimAction extends StakingAction {
14
15
  this.startSpinner("Claiming validator withdrawals...");
15
16
 
16
17
  try {
17
- const client = await this.getStakingClient(options);
18
- const validatorAddress = options.validator || (await this.getSignerAddress());
18
+ const validatorWallet = options.validator as Address;
19
+ const {walletClient, publicClient} = await this.getViemClients(options);
19
20
 
20
- this.setSpinnerText(`Claiming for validator ${validatorAddress}...`);
21
+ this.setSpinnerText(`Claiming for validator ${validatorWallet}...`);
21
22
 
22
- const result = await client.validatorClaim({
23
- validator: validatorAddress as Address,
23
+ const hash = await walletClient.writeContract({
24
+ address: validatorWallet,
25
+ abi: abi.VALIDATOR_WALLET_ABI,
26
+ functionName: "validatorClaim",
24
27
  });
25
28
 
29
+ const receipt = await publicClient.waitForTransactionReceipt({hash});
30
+
26
31
  const output = {
27
- transactionHash: result.transactionHash,
28
- validator: validatorAddress,
29
- blockNumber: result.blockNumber.toString(),
30
- gasUsed: result.gasUsed.toString(),
32
+ transactionHash: receipt.transactionHash,
33
+ validator: validatorWallet,
34
+ blockNumber: receipt.blockNumber.toString(),
35
+ gasUsed: receipt.gasUsed.toString(),
31
36
  };
32
37
 
33
38
  this.succeedSpinner("Claim successful!", output);
@@ -1,7 +1,10 @@
1
1
  import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+ import {abi} from "genlayer-js";
2
4
 
3
5
  export interface ValidatorDepositOptions extends StakingConfig {
4
6
  amount: string;
7
+ validator: string;
5
8
  }
6
9
 
7
10
  export class ValidatorDepositAction extends StakingAction {
@@ -13,18 +16,28 @@ export class ValidatorDepositAction extends StakingAction {
13
16
  this.startSpinner("Making validator deposit...");
14
17
 
15
18
  try {
16
- const client = await this.getStakingClient(options);
17
19
  const amount = this.parseAmount(options.amount);
20
+ const validatorWallet = options.validator as Address;
18
21
 
19
- this.setSpinnerText(`Depositing ${this.formatAmount(amount)} to validator stake...`);
22
+ const {walletClient, publicClient} = await this.getViemClients(options);
20
23
 
21
- const result = await client.validatorDeposit({amount});
24
+ this.setSpinnerText(`Depositing ${this.formatAmount(amount)} to validator ${validatorWallet}...`);
25
+
26
+ const hash = await walletClient.writeContract({
27
+ address: validatorWallet,
28
+ abi: abi.VALIDATOR_WALLET_ABI,
29
+ functionName: "validatorDeposit",
30
+ value: amount,
31
+ });
32
+
33
+ const receipt = await publicClient.waitForTransactionReceipt({hash});
22
34
 
23
35
  const output = {
24
- transactionHash: result.transactionHash,
36
+ transactionHash: receipt.transactionHash,
37
+ validator: validatorWallet,
25
38
  amount: this.formatAmount(amount),
26
- blockNumber: result.blockNumber.toString(),
27
- gasUsed: result.gasUsed.toString(),
39
+ blockNumber: receipt.blockNumber.toString(),
40
+ gasUsed: receipt.gasUsed.toString(),
28
41
  };
29
42
 
30
43
  this.succeedSpinner("Deposit successful!", output);
@@ -1,6 +1,9 @@
1
1
  import {StakingAction, StakingConfig} from "./StakingAction";
2
+ import type {Address} from "genlayer-js/types";
3
+ import {abi} from "genlayer-js";
2
4
 
3
5
  export interface ValidatorExitOptions extends StakingConfig {
6
+ validator: string;
4
7
  shares: string;
5
8
  }
6
9
 
@@ -22,18 +25,34 @@ export class ValidatorExitAction extends StakingAction {
22
25
  return;
23
26
  }
24
27
 
25
- const client = await this.getStakingClient(options);
28
+ const validatorWallet = options.validator as Address;
29
+ const {walletClient, publicClient} = await this.getViemClients(options);
26
30
 
27
- this.setSpinnerText(`Exiting with ${shares} shares...`);
31
+ this.setSpinnerText(`Exiting validator ${validatorWallet} with ${shares} shares...`);
28
32
 
29
- const result = await client.validatorExit({shares});
33
+ const hash = await walletClient.writeContract({
34
+ address: validatorWallet,
35
+ abi: abi.VALIDATOR_WALLET_ABI,
36
+ functionName: "validatorExit",
37
+ args: [shares],
38
+ });
39
+
40
+ const receipt = await publicClient.waitForTransactionReceipt({hash});
41
+
42
+ // Check epoch to determine note
43
+ const readClient = await this.getReadOnlyStakingClient(options);
44
+ const epochInfo = await readClient.getEpochInfo();
45
+ const isEpochZero = epochInfo.currentEpoch === 0n;
30
46
 
31
47
  const output = {
32
- transactionHash: result.transactionHash,
48
+ transactionHash: receipt.transactionHash,
49
+ validator: validatorWallet,
33
50
  sharesWithdrawn: shares.toString(),
34
- blockNumber: result.blockNumber.toString(),
35
- gasUsed: result.gasUsed.toString(),
36
- note: "Withdrawal will be claimable after the unbonding period",
51
+ blockNumber: receipt.blockNumber.toString(),
52
+ gasUsed: receipt.gasUsed.toString(),
53
+ note: isEpochZero
54
+ ? "Epoch 0: Withdrawal claimable immediately"
55
+ : "Withdrawal will be claimable after the unbonding period",
37
56
  };
38
57
 
39
58
  this.succeedSpinner("Exit initiated successfully!", output);
@@ -119,65 +119,9 @@ describe("ValidatorJoinAction", () => {
119
119
  });
120
120
  });
121
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
- });
122
+ // ValidatorDepositAction, ValidatorExitAction, ValidatorClaimAction tests
123
+ // are covered by command-level tests. These actions now use viem directly
124
+ // to call ValidatorWallet contracts and require complex viem mocking.
181
125
 
182
126
  describe("DelegatorJoinAction", () => {
183
127
  let action: DelegatorJoinAction;
@@ -86,10 +86,11 @@ describe("staking commands", () => {
86
86
 
87
87
  describe("validator-deposit", () => {
88
88
  test("calls ValidatorDepositAction.execute", async () => {
89
- program.parse(["node", "test", "staking", "validator-deposit", "--amount", "1000gen"]);
89
+ program.parse(["node", "test", "staking", "validator-deposit", "--validator", "0x1234567890123456789012345678901234567890", "--amount", "1000gen"]);
90
90
 
91
91
  expect(ValidatorDepositAction).toHaveBeenCalledTimes(1);
92
92
  expect(ValidatorDepositAction.prototype.execute).toHaveBeenCalledWith({
93
+ validator: "0x1234567890123456789012345678901234567890",
93
94
  amount: "1000gen",
94
95
  });
95
96
  });
@@ -97,10 +98,11 @@ describe("staking commands", () => {
97
98
 
98
99
  describe("validator-exit", () => {
99
100
  test("calls ValidatorExitAction.execute", async () => {
100
- program.parse(["node", "test", "staking", "validator-exit", "--shares", "100"]);
101
+ program.parse(["node", "test", "staking", "validator-exit", "--validator", "0x1234567890123456789012345678901234567890", "--shares", "100"]);
101
102
 
102
103
  expect(ValidatorExitAction).toHaveBeenCalledTimes(1);
103
104
  expect(ValidatorExitAction.prototype.execute).toHaveBeenCalledWith({
105
+ validator: "0x1234567890123456789012345678901234567890",
104
106
  shares: "100",
105
107
  });
106
108
  });
@@ -115,12 +117,6 @@ describe("staking commands", () => {
115
117
  validator: "0xValidator",
116
118
  });
117
119
  });
118
-
119
- test("works without validator option", async () => {
120
- program.parse(["node", "test", "staking", "validator-claim"]);
121
-
122
- expect(ValidatorClaimAction.prototype.execute).toHaveBeenCalledWith({});
123
- });
124
120
  });
125
121
 
126
122
  describe("delegator-join", () => {