genlayer 0.32.0 → 0.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/dist/index.js +1425 -222
  4. package/docs/delegator-guide.md +6 -6
  5. package/docs/validator-guide.md +51 -18
  6. package/package.json +2 -2
  7. package/src/commands/account/create.ts +10 -4
  8. package/src/commands/account/export.ts +106 -0
  9. package/src/commands/account/import.ts +85 -31
  10. package/src/commands/account/index.ts +77 -18
  11. package/src/commands/account/list.ts +34 -0
  12. package/src/commands/account/lock.ts +16 -7
  13. package/src/commands/account/remove.ts +30 -0
  14. package/src/commands/account/send.ts +14 -8
  15. package/src/commands/account/show.ts +22 -8
  16. package/src/commands/account/unlock.ts +20 -10
  17. package/src/commands/account/use.ts +21 -0
  18. package/src/commands/network/index.ts +18 -3
  19. package/src/commands/network/setNetwork.ts +38 -22
  20. package/src/commands/staking/StakingAction.ts +51 -19
  21. package/src/commands/staking/delegatorJoin.ts +2 -0
  22. package/src/commands/staking/index.ts +29 -2
  23. package/src/commands/staking/setIdentity.ts +5 -0
  24. package/src/commands/staking/stakingInfo.ts +29 -21
  25. package/src/commands/staking/wizard.ts +809 -0
  26. package/src/lib/actions/BaseAction.ts +71 -45
  27. package/src/lib/config/ConfigFileManager.ts +143 -0
  28. package/src/lib/config/KeychainManager.ts +68 -8
  29. package/tests/actions/create.test.ts +30 -10
  30. package/tests/actions/deploy.test.ts +7 -0
  31. package/tests/actions/lock.test.ts +28 -8
  32. package/tests/actions/unlock.test.ts +44 -26
  33. package/tests/commands/account.test.ts +43 -18
  34. package/tests/commands/network.test.ts +10 -10
  35. package/tests/commands/staking.test.ts +122 -0
  36. package/tests/libs/baseAction.test.ts +64 -41
  37. package/tests/libs/configFileManager.test.ts +8 -1
  38. package/tests/libs/keychainManager.test.ts +62 -18
  39. package/src/lib/interfaces/KeystoreData.ts +0 -5
package/dist/index.js CHANGED
@@ -18249,7 +18249,7 @@ var require_semver2 = __commonJS({
18249
18249
  import { program } from "commander";
18250
18250
 
18251
18251
  // package.json
18252
- var version = "0.32.0";
18252
+ var version = "0.32.2";
18253
18253
  var package_default = {
18254
18254
  name: "genlayer",
18255
18255
  version,
@@ -18317,7 +18317,7 @@ var package_default = {
18317
18317
  dotenv: "^17.0.0",
18318
18318
  ethers: "^6.13.4",
18319
18319
  "fs-extra": "^11.3.0",
18320
- "genlayer-js": "^0.18.5",
18320
+ "genlayer-js": "^0.18.7",
18321
18321
  inquirer: "^12.0.0",
18322
18322
  keytar: "^7.9.0",
18323
18323
  "node-fetch": "^3.0.0",
@@ -19771,21 +19771,79 @@ var ConfigFileManager = class {
19771
19771
  constructor(baseFolder = ".genlayer/", configFileName = "genlayer-config.json") {
19772
19772
  __publicField(this, "folderPath");
19773
19773
  __publicField(this, "configFilePath");
19774
+ __publicField(this, "keystoresPath");
19774
19775
  this.folderPath = path.resolve(os.homedir(), baseFolder);
19775
19776
  this.configFilePath = path.resolve(this.folderPath, configFileName);
19777
+ this.keystoresPath = path.resolve(this.folderPath, "keystores");
19776
19778
  this.ensureFolderExists();
19779
+ this.ensureKeystoresDirExists();
19777
19780
  this.ensureConfigFileExists();
19781
+ this.migrateOldConfig();
19782
+ this.migrateKeystoreFormats();
19778
19783
  }
19779
19784
  ensureFolderExists() {
19780
19785
  if (!fs2.existsSync(this.folderPath)) {
19781
19786
  fs2.mkdirSync(this.folderPath, { recursive: true });
19782
19787
  }
19783
19788
  }
19789
+ ensureKeystoresDirExists() {
19790
+ if (!fs2.existsSync(this.keystoresPath)) {
19791
+ fs2.mkdirSync(this.keystoresPath, { recursive: true });
19792
+ }
19793
+ }
19784
19794
  ensureConfigFileExists() {
19785
19795
  if (!fs2.existsSync(this.configFilePath)) {
19786
19796
  fs2.writeFileSync(this.configFilePath, JSON.stringify({}, null, 2));
19787
19797
  }
19788
19798
  }
19799
+ migrateOldConfig() {
19800
+ const config = this.getConfig();
19801
+ if (config.keyPairPath && !config.activeAccount) {
19802
+ const oldPath = config.keyPairPath;
19803
+ if (fs2.existsSync(oldPath)) {
19804
+ const newPath = this.getKeystorePath("default");
19805
+ const content = fs2.readFileSync(oldPath, "utf-8");
19806
+ const web3Content = this.convertToWeb3Format(content);
19807
+ fs2.writeFileSync(newPath, web3Content);
19808
+ delete config.keyPairPath;
19809
+ config.activeAccount = "default";
19810
+ fs2.writeFileSync(this.configFilePath, JSON.stringify(config, null, 2));
19811
+ }
19812
+ }
19813
+ }
19814
+ migrateKeystoreFormats() {
19815
+ if (!fs2.existsSync(this.keystoresPath)) {
19816
+ return;
19817
+ }
19818
+ const files = fs2.readdirSync(this.keystoresPath);
19819
+ if (!Array.isArray(files)) {
19820
+ return;
19821
+ }
19822
+ for (const file of files) {
19823
+ if (!file.endsWith(".json")) continue;
19824
+ const filePath = path.resolve(this.keystoresPath, file);
19825
+ try {
19826
+ const content = fs2.readFileSync(filePath, "utf-8");
19827
+ const parsed = JSON.parse(content);
19828
+ if (parsed.encrypted && typeof parsed.encrypted === "string") {
19829
+ const web3Content = this.convertToWeb3Format(content);
19830
+ fs2.writeFileSync(filePath, web3Content);
19831
+ }
19832
+ } catch {
19833
+ }
19834
+ }
19835
+ }
19836
+ convertToWeb3Format(content) {
19837
+ try {
19838
+ const parsed = JSON.parse(content);
19839
+ if (parsed.encrypted && typeof parsed.encrypted === "string") {
19840
+ return parsed.encrypted;
19841
+ }
19842
+ return content;
19843
+ } catch {
19844
+ return content;
19845
+ }
19846
+ }
19789
19847
  getFolderPath() {
19790
19848
  return this.folderPath;
19791
19849
  }
@@ -19805,33 +19863,140 @@ var ConfigFileManager = class {
19805
19863
  config[key] = value;
19806
19864
  fs2.writeFileSync(this.configFilePath, JSON.stringify(config, null, 2));
19807
19865
  }
19866
+ removeConfig(key) {
19867
+ const config = this.getConfig();
19868
+ delete config[key];
19869
+ fs2.writeFileSync(this.configFilePath, JSON.stringify(config, null, 2));
19870
+ }
19871
+ getKeystoresPath() {
19872
+ return this.keystoresPath;
19873
+ }
19874
+ getKeystorePath(name) {
19875
+ return path.resolve(this.keystoresPath, `${name}.json`);
19876
+ }
19877
+ accountExists(name) {
19878
+ return fs2.existsSync(this.getKeystorePath(name));
19879
+ }
19880
+ getActiveAccount() {
19881
+ return this.getConfigByKey("activeAccount");
19882
+ }
19883
+ setActiveAccount(name) {
19884
+ if (!this.accountExists(name)) {
19885
+ throw new Error(`Account '${name}' does not exist`);
19886
+ }
19887
+ this.writeConfig("activeAccount", name);
19888
+ }
19889
+ listAccounts() {
19890
+ if (!fs2.existsSync(this.keystoresPath)) {
19891
+ return [];
19892
+ }
19893
+ const files = fs2.readdirSync(this.keystoresPath);
19894
+ const accounts = [];
19895
+ for (const file of files) {
19896
+ if (!file.endsWith(".json")) continue;
19897
+ const name = file.replace(".json", "");
19898
+ const filePath = this.getKeystorePath(name);
19899
+ try {
19900
+ const content = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
19901
+ const addr = content.address || "unknown";
19902
+ accounts.push({
19903
+ name,
19904
+ address: addr.startsWith("0x") ? addr : `0x${addr}`,
19905
+ path: filePath
19906
+ });
19907
+ } catch {
19908
+ }
19909
+ }
19910
+ return accounts;
19911
+ }
19912
+ removeAccount(name) {
19913
+ const keystorePath = this.getKeystorePath(name);
19914
+ if (!fs2.existsSync(keystorePath)) {
19915
+ throw new Error(`Account '${name}' does not exist`);
19916
+ }
19917
+ fs2.unlinkSync(keystorePath);
19918
+ if (this.getActiveAccount() === name) {
19919
+ this.removeConfig("activeAccount");
19920
+ }
19921
+ }
19808
19922
  };
19809
19923
 
19810
19924
  // src/lib/config/KeychainManager.ts
19811
- import { default as keytar } from "keytar";
19925
+ var keytarModule = null;
19926
+ var keytarLoadAttempted = false;
19927
+ async function getKeytar() {
19928
+ if (keytarLoadAttempted) return keytarModule;
19929
+ keytarLoadAttempted = true;
19930
+ try {
19931
+ const mod3 = await import("keytar");
19932
+ keytarModule = mod3.default ?? mod3;
19933
+ return keytarModule;
19934
+ } catch {
19935
+ return null;
19936
+ }
19937
+ }
19812
19938
  var _KeychainManager = class _KeychainManager {
19813
19939
  constructor() {
19814
19940
  }
19941
+ getKeychainAccount(accountName) {
19942
+ return `account:${accountName}`;
19943
+ }
19815
19944
  async isKeychainAvailable() {
19816
19945
  try {
19946
+ const keytar = await getKeytar();
19947
+ if (!keytar) return false;
19817
19948
  await keytar.findCredentials("test-service");
19818
19949
  return true;
19819
19950
  } catch {
19820
19951
  return false;
19821
19952
  }
19822
19953
  }
19823
- async storePrivateKey(privateKey) {
19824
- return await keytar.setPassword(_KeychainManager.SERVICE, _KeychainManager.ACCOUNT, privateKey);
19954
+ async storePrivateKey(accountName, privateKey) {
19955
+ const keytar = await getKeytar();
19956
+ if (!keytar) throw new Error("Keychain not available. Install libsecret-1-dev on Linux.");
19957
+ try {
19958
+ return await keytar.setPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName), privateKey);
19959
+ } catch (error) {
19960
+ if (error?.message?.includes("org.freedesktop.secrets")) {
19961
+ throw new Error("Keychain service not running. Install and start gnome-keyring or another secrets service.");
19962
+ }
19963
+ throw error;
19964
+ }
19825
19965
  }
19826
- async getPrivateKey() {
19827
- return await keytar.getPassword(_KeychainManager.SERVICE, _KeychainManager.ACCOUNT);
19966
+ async getPrivateKey(accountName) {
19967
+ const keytar = await getKeytar();
19968
+ if (!keytar) return null;
19969
+ try {
19970
+ return await keytar.getPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19971
+ } catch {
19972
+ return null;
19973
+ }
19974
+ }
19975
+ async removePrivateKey(accountName) {
19976
+ const keytar = await getKeytar();
19977
+ if (!keytar) return false;
19978
+ try {
19979
+ return await keytar.deletePassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19980
+ } catch {
19981
+ return false;
19982
+ }
19828
19983
  }
19829
- async removePrivateKey() {
19830
- return await keytar.deletePassword(_KeychainManager.SERVICE, _KeychainManager.ACCOUNT);
19984
+ async listUnlockedAccounts() {
19985
+ const keytar = await getKeytar();
19986
+ if (!keytar) return [];
19987
+ try {
19988
+ const credentials = await keytar.findCredentials(_KeychainManager.SERVICE);
19989
+ return credentials.map((c) => c.account).filter((a) => a.startsWith("account:")).map((a) => a.replace("account:", ""));
19990
+ } catch {
19991
+ return [];
19992
+ }
19993
+ }
19994
+ async isAccountUnlocked(accountName) {
19995
+ const key = await this.getPrivateKey(accountName);
19996
+ return key !== null;
19831
19997
  }
19832
19998
  };
19833
19999
  __publicField(_KeychainManager, "SERVICE", "genlayer-cli");
19834
- __publicField(_KeychainManager, "ACCOUNT", "default-user");
19835
20000
  var KeychainManager = _KeychainManager;
19836
20001
 
19837
20002
  // node_modules/ora/index.js
@@ -31516,12 +31681,13 @@ function custom(provider, config = {}) {
31516
31681
  // node_modules/viem/_esm/index.js
31517
31682
  init_base();
31518
31683
  init_contract();
31684
+ init_decodeErrorResult();
31519
31685
  init_encodeFunctionData();
31520
31686
  init_fromHex();
31521
31687
  init_toHex();
31522
31688
  init_formatEther();
31523
31689
 
31524
- // node_modules/genlayer-js/dist/chunk-NZI52PRP.js
31690
+ // node_modules/genlayer-js/dist/chunk-RW6PLN5W.js
31525
31691
  var chains_exports = {};
31526
31692
  __export2(chains_exports, {
31527
31693
  localnet: () => localnet,
@@ -39550,6 +39716,13 @@ var studionet = defineChain({
39550
39716
  defaultConsensusMaxRotations: 3
39551
39717
  });
39552
39718
  var VALIDATOR_WALLET_ABI = [
39719
+ // Custom errors
39720
+ { name: "NotOperator", type: "error", inputs: [] },
39721
+ { name: "InvalidAddress", type: "error", inputs: [] },
39722
+ { name: "TransferFailed", type: "error", inputs: [] },
39723
+ { name: "OperatorTransferNotReady", type: "error", inputs: [] },
39724
+ { name: "NoPendingOperator", type: "error", inputs: [] },
39725
+ // Functions
39553
39726
  {
39554
39727
  name: "operator",
39555
39728
  type: "function",
@@ -39613,19 +39786,51 @@ var VALIDATOR_WALLET_ABI = [
39613
39786
  }
39614
39787
  ];
39615
39788
  var STAKING_ABI = [
39616
- // Custom errors
39617
- { name: "BelowMinStake", type: "error", inputs: [] },
39618
- { name: "AlreadyValidator", type: "error", inputs: [] },
39619
- { name: "NotValidator", type: "error", inputs: [] },
39620
- { name: "NotOwner", type: "error", inputs: [] },
39621
- { name: "NotOperator", type: "error", inputs: [] },
39622
- { name: "ValidatorBanned", type: "error", inputs: [] },
39623
- { name: "ValidatorQuarantined", type: "error", inputs: [] },
39624
- { name: "InsufficientShares", type: "error", inputs: [] },
39625
- { name: "NothingToClaim", type: "error", inputs: [] },
39626
- { name: "NotYetClaimable", type: "error", inputs: [] },
39627
- { name: "ZeroAmount", type: "error", inputs: [] },
39628
- { name: "InvalidOperator", type: "error", inputs: [] },
39789
+ // Custom errors from IGenLayerStaking
39790
+ { name: "OnlyGEN", type: "error", inputs: [] },
39791
+ { name: "OnlyTribunal", type: "error", inputs: [] },
39792
+ { name: "OnlyIdleness", type: "error", inputs: [] },
39793
+ { name: "OnlyTransactions", type: "error", inputs: [] },
39794
+ { name: "OnlyIdlenessOrTribunal", type: "error", inputs: [] },
39795
+ { name: "OnlyTransactionsOrTribunal", type: "error", inputs: [] },
39796
+ { name: "InvalidAtEpoch", type: "error", inputs: [] },
39797
+ { name: "MaxValidatorsCannotBeZero", type: "error", inputs: [] },
39798
+ { name: "ValidatorExitExceedsShares", type: "error", inputs: [] },
39799
+ { name: "DelegatorExitExceedsShares", type: "error", inputs: [] },
39800
+ { name: "DelegatorMayNotJoinWithZeroValue", type: "error", inputs: [] },
39801
+ { name: "DelegatorMayNotJoinTwoValidatorsSimultaneously", type: "error", inputs: [] },
39802
+ { name: "DelegatorBelowMinimumStake", type: "error", inputs: [] },
39803
+ { name: "DelegatorExitWouldBeBelowMinimum", type: "error", inputs: [] },
39804
+ { name: "ValidatorNotActive", type: "error", inputs: [] },
39805
+ { name: "ValidatorMayNotBeDelegator", type: "error", inputs: [] },
39806
+ { name: "ValidatorMustNotBeDelegator", type: "error", inputs: [] },
39807
+ { name: "ValidatorMayNotJoinWithZeroValue", type: "error", inputs: [] },
39808
+ { name: "ValidatorMayNotDepositZeroValue", type: "error", inputs: [] },
39809
+ { name: "ValidatorWithdrawalExceedsStake", type: "error", inputs: [] },
39810
+ { name: "ValidatorAlreadyJoined", type: "error", inputs: [] },
39811
+ { name: "ValidatorNotJoined", type: "error", inputs: [] },
39812
+ { name: "ValidatorBelowMinimumStake", type: "error", inputs: [] },
39813
+ { name: "OperatorAlreadyAssigned", type: "error", inputs: [] },
39814
+ { name: "InvalidOperatorAddress", type: "error", inputs: [] },
39815
+ { name: "MaxNumberOfValidatorsReached", type: "error", inputs: [] },
39816
+ { name: "ValidatorsConsumed", type: "error", inputs: [] },
39817
+ { name: "ValidatorsUnavailable", type: "error", inputs: [] },
39818
+ { name: "EpochNotFinished", type: "error", inputs: [] },
39819
+ { name: "EpochNotFinalized", type: "error", inputs: [] },
39820
+ { name: "InflationInvalidAmount", type: "error", inputs: [] },
39821
+ { name: "InflationAlreadyReceived", type: "error", inputs: [] },
39822
+ { name: "InflationAlreadyInitialized", type: "error", inputs: [] },
39823
+ { name: "EpochAlreadyFinalized", type: "error", inputs: [] },
39824
+ { name: "PendingTribunals", type: "error", inputs: [{ name: "epoch", type: "uint256" }] },
39825
+ { name: "FailedTransfer", type: "error", inputs: [{ name: "validator", type: "address" }] },
39826
+ { name: "NFTMinterCallFailed", type: "error", inputs: [] },
39827
+ { name: "DeepthoughtCallFailed", type: "error", inputs: [] },
39828
+ { name: "NFTMinterNotConfigured", type: "error", inputs: [] },
39829
+ { name: "NumberOfValidatorsExceedsAvailable", type: "error", inputs: [] },
39830
+ { name: "EpochAdvanceNotReady", type: "error", inputs: [] },
39831
+ { name: "PreviousEpochNotFinalizable", type: "error", inputs: [] },
39832
+ { name: "NoBurning", type: "error", inputs: [] },
39833
+ { name: "ReductionFactorCannotBeZero", type: "error", inputs: [] },
39629
39834
  // Validator functions
39630
39835
  {
39631
39836
  name: "validatorJoin",
@@ -39841,6 +40046,13 @@ var STAKING_ABI = [
39841
40046
  inputs: [],
39842
40047
  outputs: [{ name: "", type: "uint256" }]
39843
40048
  },
40049
+ {
40050
+ name: "epochZeroMinDuration",
40051
+ type: "function",
40052
+ stateMutability: "view",
40053
+ inputs: [],
40054
+ outputs: [{ name: "", type: "uint256" }]
40055
+ },
39844
40056
  {
39845
40057
  name: "getQuarantinedValidators",
39846
40058
  type: "function",
@@ -45214,19 +45426,19 @@ var decodeTransaction = (tx) => {
45214
45426
  return decodedTx;
45215
45427
  };
45216
45428
  var simplifyTransactionReceipt = (tx) => {
45217
- const simplifyObject = (obj, path6 = "") => {
45429
+ const simplifyObject = (obj, path8 = "") => {
45218
45430
  if (obj === null || obj === void 0) return obj;
45219
45431
  if (Array.isArray(obj)) {
45220
- return obj.map((item) => simplifyObject(item, path6)).filter((item) => item !== void 0);
45432
+ return obj.map((item) => simplifyObject(item, path8)).filter((item) => item !== void 0);
45221
45433
  }
45222
45434
  if (typeof obj === "object") {
45223
45435
  const result = {};
45224
45436
  for (const [key, value] of Object.entries(obj)) {
45225
- const currentPath = path6 ? `${path6}.${key}` : key;
45437
+ const currentPath = path8 ? `${path8}.${key}` : key;
45226
45438
  if (FIELDS_TO_REMOVE.includes(key)) {
45227
45439
  continue;
45228
45440
  }
45229
- if (key === "node_config" && !path6.includes("consensus_data")) {
45441
+ if (key === "node_config" && !path8.includes("consensus_data")) {
45230
45442
  continue;
45231
45443
  }
45232
45444
  if (key === "consensus_data" && typeof value === "object" && value !== null) {
@@ -45514,11 +45726,45 @@ function formatStakingAmount(amount) {
45514
45726
  }
45515
45727
  var FALLBACK_GAS = 1000000n;
45516
45728
  var GAS_BUFFER_MULTIPLIER = 2n;
45729
+ var COMBINED_ERROR_ABI = [...STAKING_ABI, ...VALIDATOR_WALLET_ABI];
45517
45730
  function extractRevertReason(err) {
45518
45731
  if (err instanceof BaseError2) {
45732
+ const rawError = err.walk((e2) => e2 instanceof RawContractError);
45733
+ if (rawError instanceof RawContractError && rawError.data && typeof rawError.data === "string") {
45734
+ try {
45735
+ const decoded = decodeErrorResult({
45736
+ abi: COMBINED_ERROR_ABI,
45737
+ data: rawError.data
45738
+ });
45739
+ return decoded.errorName;
45740
+ } catch {
45741
+ }
45742
+ }
45743
+ let current = err;
45744
+ while (current) {
45745
+ if (current && typeof current === "object") {
45746
+ const obj = current;
45747
+ if (obj.data && typeof obj.data === "string" && obj.data.startsWith("0x")) {
45748
+ try {
45749
+ const decoded = decodeErrorResult({
45750
+ abi: COMBINED_ERROR_ABI,
45751
+ data: obj.data
45752
+ });
45753
+ return decoded.errorName;
45754
+ } catch {
45755
+ }
45756
+ }
45757
+ current = obj.cause;
45758
+ } else {
45759
+ break;
45760
+ }
45761
+ }
45519
45762
  const revertError = err.walk((e2) => e2 instanceof ContractFunctionRevertedError);
45520
45763
  if (revertError instanceof ContractFunctionRevertedError) {
45521
- return revertError.data?.errorName || revertError.reason || "Unknown reason";
45764
+ if (revertError.data?.errorName) {
45765
+ return revertError.data.errorName;
45766
+ }
45767
+ return revertError.reason || "Unknown reason";
45522
45768
  }
45523
45769
  if (err.shortMessage) return err.shortMessage;
45524
45770
  }
@@ -45913,12 +46159,22 @@ var stakingActions = (client, publicClient) => {
45913
46159
  },
45914
46160
  getEpochInfo: async () => {
45915
46161
  const contract = getReadOnlyStakingContract();
45916
- const [epoch, validatorMinStake, delegatorMinStake, activeCount, epochMinDuration, epochOdd, epochEven] = await Promise.all([
46162
+ const [
46163
+ epoch,
46164
+ validatorMinStake,
46165
+ delegatorMinStake,
46166
+ activeCount,
46167
+ epochMinDuration,
46168
+ epochZeroMinDuration,
46169
+ epochOdd,
46170
+ epochEven
46171
+ ] = await Promise.all([
45917
46172
  contract.read.epoch(),
45918
46173
  contract.read.validatorMinStake(),
45919
46174
  contract.read.delegatorMinStake(),
45920
46175
  contract.read.activeValidatorsCount(),
45921
46176
  contract.read.epochMinDuration(),
46177
+ contract.read.epochZeroMinDuration(),
45922
46178
  contract.read.epochOdd(),
45923
46179
  contract.read.epochEven()
45924
46180
  ]);
@@ -45927,7 +46183,8 @@ var stakingActions = (client, publicClient) => {
45927
46183
  const currentEpochEnd = currentEpochData.end > 0n ? new Date(Number(currentEpochData.end) * 1e3) : null;
45928
46184
  let nextEpochEstimate = null;
45929
46185
  if (!currentEpochEnd) {
45930
- const estimatedEndMs = Number(currentEpochData.start + epochMinDuration) * 1e3;
46186
+ const duration = epoch === 0n ? epochZeroMinDuration : epochMinDuration;
46187
+ const estimatedEndMs = Number(currentEpochData.start + duration) * 1e3;
45931
46188
  nextEpochEstimate = new Date(estimatedEndMs);
45932
46189
  }
45933
46190
  return {
@@ -46136,25 +46393,26 @@ var _BaseAction = class _BaseAction extends ConfigFileManager {
46136
46393
  __publicField(this, "spinner");
46137
46394
  __publicField(this, "_genlayerClient", null);
46138
46395
  __publicField(this, "keychainManager");
46396
+ __publicField(this, "accountOverride", null);
46139
46397
  this.spinner = ora({ text: "", spinner: "dots" });
46140
46398
  this.keychainManager = new KeychainManager();
46141
46399
  }
46142
- async decryptKeystore(keystoreData, attempt = 1) {
46400
+ async decryptKeystore(keystoreJson, attempt = 1) {
46143
46401
  try {
46144
46402
  const message = attempt === 1 ? "Enter password to decrypt keystore:" : `Invalid password. Attempt ${attempt}/${_BaseAction.MAX_PASSWORD_ATTEMPTS} - Enter password to decrypt keystore:`;
46145
46403
  const password = await this.promptPassword(message);
46146
- const wallet = await ethers.Wallet.fromEncryptedJson(keystoreData.encrypted, password);
46404
+ const wallet = await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
46147
46405
  return wallet.privateKey;
46148
46406
  } catch (error) {
46149
46407
  if (attempt >= _BaseAction.MAX_PASSWORD_ATTEMPTS) {
46150
46408
  this.failSpinner(`Maximum password attempts exceeded (${_BaseAction.MAX_PASSWORD_ATTEMPTS}/${_BaseAction.MAX_PASSWORD_ATTEMPTS}).`);
46151
46409
  }
46152
- return await this.decryptKeystore(keystoreData, attempt + 1);
46410
+ return await this.decryptKeystore(keystoreJson, attempt + 1);
46153
46411
  }
46154
46412
  }
46155
46413
  isValidKeystoreFormat(data) {
46156
46414
  return Boolean(
46157
- data && data.version === 1 && typeof data.encrypted === "string" && typeof data.address === "string"
46415
+ data && (data.crypto || data.Crypto) && typeof data.address === "string"
46158
46416
  );
46159
46417
  }
46160
46418
  formatOutput(data) {
@@ -46175,40 +46433,64 @@ var _BaseAction = class _BaseAction extends ConfigFileManager {
46175
46433
  }
46176
46434
  return this._genlayerClient;
46177
46435
  }
46436
+ resolveAccountName() {
46437
+ if (this.accountOverride) {
46438
+ return this.accountOverride;
46439
+ }
46440
+ const activeAccount = this.getActiveAccount();
46441
+ if (activeAccount) {
46442
+ return activeAccount;
46443
+ }
46444
+ return _BaseAction.DEFAULT_ACCOUNT_NAME;
46445
+ }
46178
46446
  async getAccount(readOnly = false) {
46179
- let keypairPath = this.getConfigByKey("keyPairPath");
46447
+ const accountName = this.resolveAccountName();
46448
+ const keystorePath = this.getKeystorePath(accountName);
46180
46449
  let decryptedPrivateKey;
46450
+ let keystoreJson;
46181
46451
  let keystoreData;
46182
- if (!keypairPath || !existsSync(keypairPath)) {
46183
- await this.confirmPrompt("Keypair file not found. Would you like to create a new keypair?");
46184
- decryptedPrivateKey = await this.createKeypair(_BaseAction.DEFAULT_KEYSTORE_PATH, false);
46185
- keypairPath = this.getConfigByKey("keyPairPath");
46452
+ if (!existsSync(keystorePath)) {
46453
+ await this.confirmPrompt(`Account '${accountName}' not found. Would you like to create it?`);
46454
+ decryptedPrivateKey = await this.createKeypairByName(accountName, false);
46186
46455
  }
46187
- keystoreData = JSON.parse(readFileSync(keypairPath, "utf-8"));
46456
+ keystoreJson = readFileSync(keystorePath, "utf-8");
46457
+ keystoreData = JSON.parse(keystoreJson);
46188
46458
  if (!this.isValidKeystoreFormat(keystoreData)) {
46189
46459
  this.failSpinner("Invalid keystore format. Expected encrypted keystore file.", void 0, false);
46190
- await this.confirmPrompt("Would you like to create a new keypair?");
46191
- decryptedPrivateKey = await this.createKeypair(_BaseAction.DEFAULT_KEYSTORE_PATH, true);
46192
- keypairPath = this.getConfigByKey("keyPairPath");
46193
- keystoreData = JSON.parse(readFileSync(keypairPath, "utf-8"));
46460
+ await this.confirmPrompt(`Would you like to recreate account '${accountName}'?`);
46461
+ decryptedPrivateKey = await this.createKeypairByName(accountName, true);
46462
+ keystoreJson = readFileSync(keystorePath, "utf-8");
46463
+ keystoreData = JSON.parse(keystoreJson);
46194
46464
  }
46195
46465
  if (readOnly) {
46196
46466
  return this.getAddress(keystoreData);
46197
46467
  }
46198
46468
  if (!decryptedPrivateKey) {
46199
- const cachedKey = await this.keychainManager.getPrivateKey();
46200
- decryptedPrivateKey = cachedKey ? cachedKey : await this.decryptKeystore(keystoreData);
46469
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
46470
+ if (cachedKey) {
46471
+ const tempAccount = createAccount(cachedKey);
46472
+ const cachedAddress = tempAccount.address.toLowerCase();
46473
+ const keystoreAddress = `0x${keystoreData.address.toLowerCase().replace(/^0x/, "")}`;
46474
+ if (cachedAddress === keystoreAddress) {
46475
+ decryptedPrivateKey = cachedKey;
46476
+ } else {
46477
+ await this.keychainManager.removePrivateKey(accountName);
46478
+ decryptedPrivateKey = await this.decryptKeystore(keystoreJson);
46479
+ }
46480
+ } else {
46481
+ decryptedPrivateKey = await this.decryptKeystore(keystoreJson);
46482
+ }
46201
46483
  }
46202
46484
  return createAccount(decryptedPrivateKey);
46203
46485
  }
46204
46486
  getAddress(keystoreData) {
46205
46487
  return keystoreData.address;
46206
46488
  }
46207
- async createKeypair(outputPath, overwrite) {
46208
- const finalOutputPath = this.getFilePath(outputPath);
46489
+ async createKeypairByName(accountName, overwrite) {
46490
+ const keystorePath = this.getKeystorePath(accountName);
46209
46491
  this.stopSpinner();
46210
- if (existsSync(finalOutputPath) && !overwrite) {
46211
- this.failSpinner(`The file at ${finalOutputPath} already exists. Use the '--overwrite' option to replace it.`);
46492
+ if (existsSync(keystorePath) && !overwrite) {
46493
+ this.failSpinner(`Account '${accountName}' already exists. Use '--overwrite' to replace it.`);
46212
46494
  }
46213
46495
  const wallet = ethers.Wallet.createRandom();
46214
46496
  const password = await this.promptPassword("Enter a password to encrypt your keystore (minimum 8 characters):");
@@ -46220,14 +46502,11 @@ var _BaseAction = class _BaseAction extends ConfigFileManager {
46220
46502
  this.failSpinner(`Password must be at least ${_BaseAction.MIN_PASSWORD_LENGTH} characters long`);
46221
46503
  }
46222
46504
  const encryptedJson = await wallet.encrypt(password);
46223
- const keystoreData = {
46224
- version: 1,
46225
- encrypted: encryptedJson,
46226
- address: wallet.address
46227
- };
46228
- writeFileSync(finalOutputPath, JSON.stringify(keystoreData, null, 2));
46229
- this.writeConfig("keyPairPath", finalOutputPath);
46230
- await this.keychainManager.removePrivateKey();
46505
+ writeFileSync(keystorePath, encryptedJson);
46506
+ if (!this.getActiveAccount()) {
46507
+ this.setActiveAccount(accountName);
46508
+ }
46509
+ await this.keychainManager.removePrivateKey(accountName);
46231
46510
  return wallet.privateKey;
46232
46511
  }
46233
46512
  async promptPassword(message) {
@@ -46310,7 +46589,7 @@ ${message}`));
46310
46589
  this.spinner.text = source_default.blue(message);
46311
46590
  }
46312
46591
  };
46313
- __publicField(_BaseAction, "DEFAULT_KEYSTORE_PATH", "./keypair.json");
46592
+ __publicField(_BaseAction, "DEFAULT_ACCOUNT_NAME", "default");
46314
46593
  __publicField(_BaseAction, "MAX_PASSWORD_ATTEMPTS", 3);
46315
46594
  __publicField(_BaseAction, "MIN_PASSWORD_LENGTH", 8);
46316
46595
  var BaseAction = _BaseAction;
@@ -47492,33 +47771,42 @@ var ShowAccountAction = class extends BaseAction {
47492
47771
  getNetwork() {
47493
47772
  return resolveNetwork(this.getConfig().network);
47494
47773
  }
47495
- async execute() {
47774
+ async execute(options) {
47496
47775
  this.startSpinner("Fetching account info...");
47497
47776
  try {
47498
- const keypairPath = this.getConfigByKey("keyPairPath");
47499
- if (!keypairPath || !existsSync2(keypairPath)) {
47500
- this.failSpinner("No account found. Run 'genlayer account create' first.");
47777
+ if (options?.account) {
47778
+ this.accountOverride = options.account;
47779
+ }
47780
+ const accountName = this.resolveAccountName();
47781
+ const keystorePath = this.getKeystorePath(accountName);
47782
+ if (!existsSync2(keystorePath)) {
47783
+ this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
47501
47784
  return;
47502
47785
  }
47503
- const keystoreData = JSON.parse(readFileSync3(keypairPath, "utf-8"));
47786
+ const keystoreData = JSON.parse(readFileSync3(keystorePath, "utf-8"));
47504
47787
  if (!this.isValidKeystoreFormat(keystoreData)) {
47505
47788
  this.failSpinner("Invalid keystore format.");
47506
47789
  return;
47507
47790
  }
47508
- const address = keystoreData.address;
47791
+ const rawAddr = keystoreData.address;
47792
+ const address = rawAddr.startsWith("0x") ? rawAddr : `0x${rawAddr}`;
47509
47793
  const network = this.getNetwork();
47510
47794
  const client = createClient2({
47511
47795
  chain: network,
47512
- account: address
47796
+ account: address,
47797
+ endpoint: options?.rpc
47513
47798
  });
47514
47799
  const balance = await client.getBalance({ address });
47515
47800
  const formattedBalance = formatEther(balance);
47516
- const isUnlocked = await this.keychainManager.getPrivateKey();
47801
+ const isUnlocked = await this.keychainManager.isAccountUnlocked(accountName);
47802
+ const isActive = this.getActiveAccount() === accountName;
47517
47803
  const result = {
47804
+ name: accountName,
47518
47805
  address,
47519
47806
  balance: `${formattedBalance} GEN`,
47520
47807
  network: network.name || "localnet",
47521
- status: isUnlocked ? "unlocked" : "locked"
47808
+ status: isUnlocked ? "unlocked" : "locked",
47809
+ active: isActive
47522
47810
  };
47523
47811
  this.succeedSpinner("Account info", result);
47524
47812
  } catch (error) {
@@ -47534,9 +47822,13 @@ var CreateAccountAction = class extends BaseAction {
47534
47822
  }
47535
47823
  async execute(options) {
47536
47824
  try {
47537
- this.startSpinner("Creating encrypted keystore...");
47538
- await this.createKeypair(options.output, options.overwrite);
47539
- this.succeedSpinner(`Account created and saved to: ${options.output}`);
47825
+ this.startSpinner(`Creating account '${options.name}'...`);
47826
+ await this.createKeypairByName(options.name, options.overwrite);
47827
+ if (options.setActive !== false) {
47828
+ this.setActiveAccount(options.name);
47829
+ }
47830
+ const keystorePath = this.getKeystorePath(options.name);
47831
+ this.succeedSpinner(`Account '${options.name}' created at: ${keystorePath}`);
47540
47832
  } catch (error) {
47541
47833
  this.failSpinner("Failed to create account", error);
47542
47834
  }
@@ -47545,45 +47837,86 @@ var CreateAccountAction = class extends BaseAction {
47545
47837
 
47546
47838
  // src/commands/account/import.ts
47547
47839
  import { ethers as ethers2 } from "ethers";
47548
- import { writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
47549
- var _ImportAccountAction = class _ImportAccountAction extends BaseAction {
47840
+ import { writeFileSync as writeFileSync3, existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
47841
+ var ImportAccountAction = class extends BaseAction {
47550
47842
  constructor() {
47551
47843
  super();
47552
47844
  }
47553
47845
  async execute(options) {
47554
47846
  try {
47555
- const privateKey = options.privateKey || await this.promptPrivateKey();
47556
- const normalizedKey = this.normalizePrivateKey(privateKey);
47557
- this.validatePrivateKey(normalizedKey);
47558
- const finalOutputPath = this.getFilePath(options.output);
47559
- if (existsSync3(finalOutputPath) && !options.overwrite) {
47560
- this.failSpinner(`File at ${finalOutputPath} already exists. Use '--overwrite' to replace.`);
47561
- }
47562
- const wallet = new ethers2.Wallet(normalizedKey);
47563
- const password = await this.promptPassword("Enter a password to encrypt your keystore (minimum 8 characters):");
47564
- const confirmPassword = await this.promptPassword("Confirm password:");
47565
- if (password !== confirmPassword) {
47566
- this.failSpinner("Passwords do not match");
47847
+ const keystorePath = this.getKeystorePath(options.name);
47848
+ if (existsSync3(keystorePath) && !options.overwrite) {
47849
+ this.failSpinner(`Account '${options.name}' already exists. Use '--overwrite' to replace.`);
47567
47850
  }
47568
- if (password.length < _ImportAccountAction.MIN_PASSWORD_LENGTH) {
47569
- this.failSpinner(`Password must be at least ${_ImportAccountAction.MIN_PASSWORD_LENGTH} characters long`);
47851
+ let privateKey;
47852
+ if (options.keystore) {
47853
+ privateKey = await this.importFromKeystore(options.keystore, options.sourcePassword);
47854
+ } else if (options.privateKey) {
47855
+ const normalizedKey = this.normalizePrivateKey(options.privateKey);
47856
+ this.validatePrivateKey(normalizedKey);
47857
+ privateKey = normalizedKey;
47858
+ } else {
47859
+ const inputKey = await this.promptPrivateKey();
47860
+ const normalizedKey = this.normalizePrivateKey(inputKey);
47861
+ this.validatePrivateKey(normalizedKey);
47862
+ privateKey = normalizedKey;
47863
+ }
47864
+ const wallet = new ethers2.Wallet(privateKey);
47865
+ let password;
47866
+ if (options.password) {
47867
+ password = options.password;
47868
+ } else {
47869
+ password = await this.promptPassword("Enter a password to encrypt your keystore (minimum 8 characters):");
47870
+ const confirmPassword = await this.promptPassword("Confirm password:");
47871
+ if (password !== confirmPassword) {
47872
+ this.failSpinner("Passwords do not match");
47873
+ }
47570
47874
  }
47571
- this.startSpinner("Encrypting and saving keystore...");
47875
+ if (password.length < BaseAction.MIN_PASSWORD_LENGTH) {
47876
+ this.failSpinner(`Password must be at least ${BaseAction.MIN_PASSWORD_LENGTH} characters long`);
47877
+ }
47878
+ this.startSpinner(`Importing account '${options.name}'...`);
47572
47879
  const encryptedJson = await wallet.encrypt(password);
47573
- const keystoreData = {
47574
- version: 1,
47575
- encrypted: encryptedJson,
47576
- address: wallet.address
47577
- };
47578
- writeFileSync3(finalOutputPath, JSON.stringify(keystoreData, null, 2));
47579
- this.writeConfig("keyPairPath", finalOutputPath);
47580
- await this.keychainManager.removePrivateKey();
47581
- this.succeedSpinner(`Account imported and saved to: ${finalOutputPath}`);
47880
+ writeFileSync3(keystorePath, encryptedJson);
47881
+ if (options.setActive !== false) {
47882
+ this.setActiveAccount(options.name);
47883
+ }
47884
+ await this.keychainManager.removePrivateKey(options.name);
47885
+ this.succeedSpinner(`Account '${options.name}' imported to: ${keystorePath}`);
47582
47886
  this.logInfo(`Address: ${wallet.address}`);
47583
47887
  } catch (error) {
47584
47888
  this.failSpinner("Failed to import account", error);
47585
47889
  }
47586
47890
  }
47891
+ async importFromKeystore(keystorePath, sourcePassword) {
47892
+ if (!existsSync3(keystorePath)) {
47893
+ this.failSpinner(`Keystore file not found: ${keystorePath}`);
47894
+ }
47895
+ const fileContent = readFileSync4(keystorePath, "utf-8");
47896
+ let encryptedJson;
47897
+ try {
47898
+ const parsed = JSON.parse(fileContent);
47899
+ if (parsed.encrypted) {
47900
+ encryptedJson = parsed.encrypted;
47901
+ } else if (parsed.crypto || parsed.Crypto) {
47902
+ encryptedJson = fileContent;
47903
+ } else {
47904
+ this.failSpinner("Invalid keystore format. Expected encrypted keystore file.");
47905
+ }
47906
+ } catch {
47907
+ this.failSpinner("Invalid keystore file. Could not parse JSON.");
47908
+ }
47909
+ const password = sourcePassword || await this.promptPassword("Enter password to decrypt keystore:");
47910
+ this.startSpinner("Decrypting keystore...");
47911
+ try {
47912
+ const wallet = await ethers2.Wallet.fromEncryptedJson(encryptedJson, password);
47913
+ this.stopSpinner();
47914
+ return wallet.privateKey;
47915
+ } catch {
47916
+ this.failSpinner("Failed to decrypt keystore. Wrong password?");
47917
+ }
47918
+ throw new Error("Unreachable");
47919
+ }
47587
47920
  async promptPrivateKey() {
47588
47921
  return this.promptPassword("Enter private key to import:");
47589
47922
  }
@@ -47597,37 +47930,110 @@ var _ImportAccountAction = class _ImportAccountAction extends BaseAction {
47597
47930
  }
47598
47931
  }
47599
47932
  };
47600
- __publicField(_ImportAccountAction, "MIN_PASSWORD_LENGTH", 8);
47601
- var ImportAccountAction = _ImportAccountAction;
47602
47933
 
47603
- // src/commands/account/unlock.ts
47604
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
47934
+ // src/commands/account/export.ts
47605
47935
  import { ethers as ethers3 } from "ethers";
47936
+ import { writeFileSync as writeFileSync4, existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
47937
+ import path4 from "path";
47938
+ var ExportAccountAction = class extends BaseAction {
47939
+ constructor() {
47940
+ super();
47941
+ }
47942
+ async execute(options) {
47943
+ try {
47944
+ if (options.account) {
47945
+ this.accountOverride = options.account;
47946
+ }
47947
+ const accountName = this.resolveAccountName();
47948
+ const keystorePath = this.getKeystorePath(accountName);
47949
+ if (!existsSync4(keystorePath)) {
47950
+ this.failSpinner(`Account '${accountName}' not found.`);
47951
+ }
47952
+ const outputPath = path4.resolve(options.output);
47953
+ if (existsSync4(outputPath) && !options.overwrite) {
47954
+ this.failSpinner(`Output file already exists: ${outputPath}`);
47955
+ }
47956
+ const privateKey = await this.getPrivateKeyForExport(accountName, keystorePath, options.sourcePassword);
47957
+ let password;
47958
+ if (options.password) {
47959
+ password = options.password;
47960
+ } else {
47961
+ password = await this.promptPassword("Enter password for exported keystore (minimum 8 characters):");
47962
+ const confirmPassword = await this.promptPassword("Confirm password:");
47963
+ if (password !== confirmPassword) {
47964
+ this.failSpinner("Passwords do not match");
47965
+ }
47966
+ }
47967
+ if (password.length < BaseAction.MIN_PASSWORD_LENGTH) {
47968
+ this.failSpinner(`Password must be at least ${BaseAction.MIN_PASSWORD_LENGTH} characters long`);
47969
+ }
47970
+ this.startSpinner(`Exporting account '${accountName}'...`);
47971
+ const wallet = new ethers3.Wallet(privateKey);
47972
+ const encryptedJson = await wallet.encrypt(password);
47973
+ writeFileSync4(outputPath, encryptedJson);
47974
+ this.succeedSpinner(`Account '${accountName}' exported to: ${outputPath}`);
47975
+ this.logInfo(`Address: ${wallet.address}`);
47976
+ } catch (error) {
47977
+ this.failSpinner("Failed to export account", error);
47978
+ }
47979
+ }
47980
+ async getPrivateKeyForExport(accountName, keystorePath, sourcePassword) {
47981
+ const isAvailable = await this.keychainManager.isKeychainAvailable();
47982
+ if (isAvailable) {
47983
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
47984
+ if (cachedKey) {
47985
+ return cachedKey;
47986
+ }
47987
+ }
47988
+ const fileContent = readFileSync5(keystorePath, "utf-8");
47989
+ const parsed = JSON.parse(fileContent);
47990
+ const encryptedJson = parsed.encrypted || fileContent;
47991
+ const password = sourcePassword || await this.promptPassword(`Enter password to unlock '${accountName}':`);
47992
+ this.startSpinner("Decrypting keystore...");
47993
+ try {
47994
+ const wallet = await ethers3.Wallet.fromEncryptedJson(encryptedJson, password);
47995
+ this.stopSpinner();
47996
+ return wallet.privateKey;
47997
+ } catch {
47998
+ this.failSpinner("Failed to decrypt keystore. Wrong password?");
47999
+ }
48000
+ throw new Error("Unreachable");
48001
+ }
48002
+ };
48003
+
48004
+ // src/commands/account/unlock.ts
48005
+ import { readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
48006
+ import { ethers as ethers4 } from "ethers";
47606
48007
  var UnlockAccountAction = class extends BaseAction {
47607
- async execute() {
48008
+ async execute(options) {
47608
48009
  this.startSpinner("Checking keychain availability...");
47609
48010
  const keychainAvailable = await this.keychainManager.isKeychainAvailable();
47610
48011
  if (!keychainAvailable) {
47611
48012
  this.failSpinner("OS keychain is not available. This command requires a supported keychain (e.g. macOS Keychain, Windows Credential Manager, or GNOME Keyring).");
47612
48013
  return;
47613
48014
  }
47614
- this.setSpinnerText("Checking for existing account...");
47615
- const keypairPath = this.getConfigByKey("keyPairPath");
47616
- if (!keypairPath || !existsSync4(keypairPath)) {
47617
- this.failSpinner("No account found. Run 'genlayer account create' first.");
48015
+ if (options?.account) {
48016
+ this.accountOverride = options.account;
48017
+ }
48018
+ const accountName = this.resolveAccountName();
48019
+ this.setSpinnerText(`Checking for account '${accountName}'...`);
48020
+ const keystorePath = this.getKeystorePath(accountName);
48021
+ if (!existsSync5(keystorePath)) {
48022
+ this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
47618
48023
  return;
47619
48024
  }
47620
- const keystoreData = JSON.parse(readFileSync4(keypairPath, "utf-8"));
48025
+ const keystoreJson = readFileSync6(keystorePath, "utf-8");
48026
+ const keystoreData = JSON.parse(keystoreJson);
47621
48027
  if (!this.isValidKeystoreFormat(keystoreData)) {
47622
48028
  this.failSpinner("Invalid keystore format.");
47623
48029
  return;
47624
48030
  }
47625
48031
  this.stopSpinner();
47626
48032
  try {
47627
- const password = await this.promptPassword("Enter password to unlock account:");
47628
- const wallet = await ethers3.Wallet.fromEncryptedJson(keystoreData.encrypted, password);
47629
- await this.keychainManager.storePrivateKey(wallet.privateKey);
47630
- this.succeedSpinner("Account unlocked! Private key cached in OS keychain.");
48033
+ const password = await this.promptPassword(`Enter password to unlock '${accountName}':`);
48034
+ const wallet = await ethers4.Wallet.fromEncryptedJson(keystoreJson, password);
48035
+ await this.keychainManager.storePrivateKey(accountName, wallet.privateKey);
48036
+ this.succeedSpinner(`Account '${accountName}' unlocked! Private key cached in OS keychain.`);
47631
48037
  } catch (error) {
47632
48038
  this.failSpinner("Failed to unlock account.", error);
47633
48039
  }
@@ -47636,23 +48042,27 @@ var UnlockAccountAction = class extends BaseAction {
47636
48042
 
47637
48043
  // src/commands/account/lock.ts
47638
48044
  var LockAccountAction = class extends BaseAction {
47639
- async execute() {
48045
+ async execute(options) {
47640
48046
  this.startSpinner("Checking keychain availability...");
47641
48047
  const keychainAvailable = await this.keychainManager.isKeychainAvailable();
47642
48048
  if (!keychainAvailable) {
47643
48049
  this.failSpinner("OS keychain is not available. This command requires a supported keychain (e.g. macOS Keychain, Windows Credential Manager, or GNOME Keyring).");
47644
48050
  return;
47645
48051
  }
47646
- this.setSpinnerText("Checking for cached private key...");
47647
- const hasCachedKey = await this.keychainManager.getPrivateKey();
48052
+ if (options?.account) {
48053
+ this.accountOverride = options.account;
48054
+ }
48055
+ const accountName = this.resolveAccountName();
48056
+ this.setSpinnerText(`Checking for cached private key for '${accountName}'...`);
48057
+ const hasCachedKey = await this.keychainManager.getPrivateKey(accountName);
47648
48058
  if (!hasCachedKey) {
47649
- this.succeedSpinner("Account is already locked.");
48059
+ this.succeedSpinner(`Account '${accountName}' is already locked.`);
47650
48060
  return;
47651
48061
  }
47652
- this.setSpinnerText("Removing private key from OS keychain...");
48062
+ this.setSpinnerText(`Removing private key for '${accountName}' from OS keychain...`);
47653
48063
  try {
47654
- await this.keychainManager.removePrivateKey();
47655
- this.succeedSpinner("Account locked! Private key removed from OS keychain.");
48064
+ await this.keychainManager.removePrivateKey(accountName);
48065
+ this.succeedSpinner(`Account '${accountName}' locked! Private key removed from OS keychain.`);
47656
48066
  } catch (error) {
47657
48067
  this.failSpinner("Failed to lock account.", error);
47658
48068
  }
@@ -47660,8 +48070,8 @@ var LockAccountAction = class extends BaseAction {
47660
48070
  };
47661
48071
 
47662
48072
  // src/commands/account/send.ts
47663
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
47664
- import { ethers as ethers4 } from "ethers";
48073
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
48074
+ import { ethers as ethers5 } from "ethers";
47665
48075
  var SendAction = class extends BaseAction {
47666
48076
  constructor() {
47667
48077
  super();
@@ -47690,25 +48100,30 @@ var SendAction = class extends BaseAction {
47690
48100
  async execute(options) {
47691
48101
  this.startSpinner("Preparing transfer...");
47692
48102
  try {
47693
- const keypairPath = this.getConfigByKey("keyPairPath");
47694
- if (!keypairPath || !existsSync5(keypairPath)) {
47695
- this.failSpinner("No account found. Run 'genlayer account create' first.");
48103
+ if (options.account) {
48104
+ this.accountOverride = options.account;
48105
+ }
48106
+ const accountName = this.resolveAccountName();
48107
+ const keystorePath = this.getKeystorePath(accountName);
48108
+ if (!existsSync6(keystorePath)) {
48109
+ this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
47696
48110
  return;
47697
48111
  }
47698
- const keystoreData = JSON.parse(readFileSync5(keypairPath, "utf-8"));
48112
+ const keystoreJson = readFileSync7(keystorePath, "utf-8");
48113
+ const keystoreData = JSON.parse(keystoreJson);
47699
48114
  if (!this.isValidKeystoreFormat(keystoreData)) {
47700
48115
  this.failSpinner("Invalid keystore format.");
47701
48116
  return;
47702
48117
  }
47703
- const cachedKey = await this.keychainManager.getPrivateKey();
48118
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
47704
48119
  let privateKey;
47705
48120
  if (cachedKey) {
47706
48121
  privateKey = cachedKey;
47707
48122
  } else {
47708
48123
  this.stopSpinner();
47709
- const password = await this.promptPassword("Enter password to unlock account:");
48124
+ const password = await this.promptPassword(`Enter password to unlock account '${accountName}':`);
47710
48125
  this.startSpinner("Preparing transfer...");
47711
- const wallet = await ethers4.Wallet.fromEncryptedJson(keystoreData.encrypted, password);
48126
+ const wallet = await ethers5.Wallet.fromEncryptedJson(keystoreJson, password);
47712
48127
  privateKey = wallet.privateKey;
47713
48128
  }
47714
48129
  const network = this.getNetwork(options.network);
@@ -47769,44 +48184,136 @@ Waiting for confirmation...`);
47769
48184
  }
47770
48185
  };
47771
48186
 
48187
+ // src/commands/account/list.ts
48188
+ var ListAccountsAction = class extends BaseAction {
48189
+ constructor() {
48190
+ super();
48191
+ }
48192
+ async execute() {
48193
+ try {
48194
+ const accounts = this.listAccounts();
48195
+ const activeAccount = this.getActiveAccount();
48196
+ const unlockedAccounts = await this.keychainManager.listUnlockedAccounts();
48197
+ if (accounts.length === 0) {
48198
+ this.logInfo("No accounts found. Run 'genlayer account create --name <name>' to create one.");
48199
+ return;
48200
+ }
48201
+ console.log("");
48202
+ for (const account of accounts) {
48203
+ const isActive = account.name === activeAccount;
48204
+ const isUnlocked = unlockedAccounts.includes(account.name);
48205
+ const marker = isActive ? "*" : " ";
48206
+ const status = isUnlocked ? "(unlocked)" : "";
48207
+ const activeLabel = isActive ? "(active)" : "";
48208
+ console.log(`${marker} ${account.name.padEnd(16)} ${account.address} ${activeLabel} ${status}`.trim());
48209
+ }
48210
+ console.log("");
48211
+ } catch (error) {
48212
+ this.failSpinner("Failed to list accounts", error);
48213
+ }
48214
+ }
48215
+ };
48216
+
48217
+ // src/commands/account/use.ts
48218
+ var UseAccountAction = class extends BaseAction {
48219
+ constructor() {
48220
+ super();
48221
+ }
48222
+ async execute(name) {
48223
+ try {
48224
+ if (!this.accountExists(name)) {
48225
+ this.failSpinner(`Account '${name}' does not exist. Run 'genlayer account list' to see available accounts.`);
48226
+ return;
48227
+ }
48228
+ this.setActiveAccount(name);
48229
+ this.logSuccess(`Active account set to '${name}'`);
48230
+ } catch (error) {
48231
+ this.failSpinner("Failed to set active account", error);
48232
+ }
48233
+ }
48234
+ };
48235
+
48236
+ // src/commands/account/remove.ts
48237
+ var RemoveAccountAction = class extends BaseAction {
48238
+ constructor() {
48239
+ super();
48240
+ }
48241
+ async execute(name, options) {
48242
+ try {
48243
+ if (!this.accountExists(name)) {
48244
+ this.failSpinner(`Account '${name}' does not exist.`);
48245
+ return;
48246
+ }
48247
+ if (!options.force) {
48248
+ await this.confirmPrompt(`Are you sure you want to remove account '${name}'? This cannot be undone.`);
48249
+ }
48250
+ await this.keychainManager.removePrivateKey(name);
48251
+ this.removeAccount(name);
48252
+ this.logSuccess(`Account '${name}' removed`);
48253
+ } catch (error) {
48254
+ this.failSpinner("Failed to remove account", error);
48255
+ }
48256
+ }
48257
+ };
48258
+
47772
48259
  // src/commands/account/index.ts
47773
48260
  function initializeAccountCommands(program2) {
47774
- const accountCommand = program2.command("account").description("Manage your account (address, balance, keys)").action(async () => {
48261
+ const accountCommand = program2.command("account").description("Manage your accounts (address, balance, keys)").action(async () => {
48262
+ const showAction = new ShowAccountAction();
48263
+ await showAction.execute({});
48264
+ });
48265
+ accountCommand.command("list").description("List all accounts").action(async () => {
48266
+ const listAction = new ListAccountsAction();
48267
+ await listAction.execute();
48268
+ });
48269
+ accountCommand.command("show").description("Show account details (address, balance)").option("--rpc <rpcUrl>", "RPC URL for the network").option("--account <name>", "Account to show").action(async (options) => {
47775
48270
  const showAction = new ShowAccountAction();
47776
- await showAction.execute();
48271
+ await showAction.execute(options);
47777
48272
  });
47778
- accountCommand.command("create").description("Create a new account with encrypted keystore").option("--output <path>", "Path to save the keystore", "./keypair.json").option("--overwrite", "Overwrite existing file", false).action(async (options) => {
48273
+ accountCommand.command("create").description("Create a new account with encrypted keystore").requiredOption("--name <name>", "Name for the account").option("--overwrite", "Overwrite existing account", false).option("--no-set-active", "Do not set as active account").action(async (options) => {
47779
48274
  const createAction = new CreateAccountAction();
47780
48275
  await createAction.execute(options);
47781
48276
  });
47782
- accountCommand.command("import").description("Import an account from a private key").option("--private-key <key>", "Private key to import (will prompt if not provided)").option("--output <path>", "Path to save the keystore", "./keypair.json").option("--overwrite", "Overwrite existing file", false).action(async (options) => {
48277
+ accountCommand.command("import").description("Import an account from a private key or keystore file").requiredOption("--name <name>", "Name for the account").option("--private-key <key>", "Private key to import").option("--keystore <path>", "Path to keystore file to import (geth, foundry, etc.)").option("--password <password>", "Password for the new keystore (skips confirmation prompt)").option("--source-password <password>", "Password to decrypt source keystore (with --keystore)").option("--overwrite", "Overwrite existing account", false).option("--no-set-active", "Do not set as active account").action(async (options) => {
47783
48278
  const importAction = new ImportAccountAction();
47784
48279
  await importAction.execute(options);
47785
48280
  });
47786
- accountCommand.command("send <to> <amount>").description("Send GEN to an address").option("--rpc <rpcUrl>", "RPC URL for the network").option("--network <network>", "Network to use (localnet, testnet-asimov)").action(async (to, amount, options) => {
48281
+ accountCommand.command("export").description("Export an account to a keystore file (web3/geth/foundry compatible)").requiredOption("--output <path>", "Output path for the keystore file").option("--account <name>", "Account to export (defaults to active account)").option("--password <password>", "Password for exported keystore (skips confirmation)").option("--source-password <password>", "Password to decrypt account (if not unlocked)").action(async (options) => {
48282
+ const exportAction = new ExportAccountAction();
48283
+ await exportAction.execute(options);
48284
+ });
48285
+ accountCommand.command("use <name>").description("Set the active account").action(async (name) => {
48286
+ const useAction = new UseAccountAction();
48287
+ await useAction.execute(name);
48288
+ });
48289
+ accountCommand.command("remove <name>").description("Remove an account").option("--force", "Skip confirmation prompt", false).action(async (name, options) => {
48290
+ const removeAction = new RemoveAccountAction();
48291
+ await removeAction.execute(name, options);
48292
+ });
48293
+ accountCommand.command("send <to> <amount>").description("Send GEN to an address").option("--rpc <rpcUrl>", "RPC URL for the network").option("--network <network>", "Network to use (localnet, testnet-asimov)").option("--account <name>", "Account to send from").action(async (to, amount, options) => {
47787
48294
  const sendAction = new SendAction();
47788
- await sendAction.execute({ to, amount, rpc: options.rpc, network: options.network });
48295
+ await sendAction.execute({ to, amount, rpc: options.rpc, network: options.network, account: options.account });
47789
48296
  });
47790
- accountCommand.command("unlock").description("Unlock account by caching private key in OS keychain").action(async () => {
48297
+ accountCommand.command("unlock").description("Unlock account by caching private key in OS keychain").option("--account <name>", "Account to unlock").action(async (options) => {
47791
48298
  const unlockAction = new UnlockAccountAction();
47792
- await unlockAction.execute();
48299
+ await unlockAction.execute(options);
47793
48300
  });
47794
- accountCommand.command("lock").description("Lock account by removing private key from OS keychain").action(async () => {
48301
+ accountCommand.command("lock").description("Lock account by removing private key from OS keychain").option("--account <name>", "Account to lock").action(async (options) => {
47795
48302
  const lockAction = new LockAccountAction();
47796
- await lockAction.execute();
48303
+ await lockAction.execute(options);
47797
48304
  });
47798
48305
  return program2;
47799
48306
  }
47800
48307
 
47801
48308
  // src/commands/contracts/deploy.ts
47802
48309
  import fs9 from "fs";
47803
- import path4 from "path";
48310
+ import path5 from "path";
47804
48311
  import { pathToFileURL } from "url";
47805
48312
  import { buildSync } from "esbuild";
47806
48313
  var DeployAction = class extends BaseAction {
47807
48314
  constructor() {
47808
48315
  super();
47809
- __publicField(this, "deployDir", path4.resolve(process.cwd(), "deploy"));
48316
+ __publicField(this, "deployDir", path5.resolve(process.cwd(), "deploy"));
47810
48317
  }
47811
48318
  readContractCode(contractPath) {
47812
48319
  if (!fs9.existsSync(contractPath)) {
@@ -47869,7 +48376,7 @@ var DeployAction = class extends BaseAction {
47869
48376
  }
47870
48377
  this.setSpinnerText(`Found ${files.length} deploy scripts. Executing...`);
47871
48378
  for (const file of files) {
47872
- const filePath = path4.resolve(this.deployDir, file);
48379
+ const filePath = path5.resolve(this.deployDir, file);
47873
48380
  this.setSpinnerText(`Executing script: ${filePath}`);
47874
48381
  try {
47875
48382
  if (file.endsWith(".ts")) {
@@ -48388,18 +48895,18 @@ function initializeUpdateCommands(program2) {
48388
48895
 
48389
48896
  // src/commands/scaffold/new.ts
48390
48897
  import fs10 from "fs-extra";
48391
- import path5 from "path";
48898
+ import path6 from "path";
48392
48899
  import { fileURLToPath as fileURLToPath3 } from "url";
48393
48900
  var NewAction = class extends BaseAction {
48394
48901
  constructor() {
48395
48902
  super();
48396
48903
  __publicField(this, "templatePath");
48397
48904
  const __filename = fileURLToPath3(import.meta.url);
48398
- const basePath = path5.resolve(path5.dirname(__filename), "..");
48399
- this.templatePath = path5.join(basePath, "templates", "default");
48905
+ const basePath = path6.resolve(path6.dirname(__filename), "..");
48906
+ this.templatePath = path6.join(basePath, "templates", "default");
48400
48907
  }
48401
48908
  async createProject(projectName, options) {
48402
- const targetPath = path5.resolve(options.path, projectName);
48909
+ const targetPath = path6.resolve(options.path, projectName);
48403
48910
  if (fs10.existsSync(targetPath) && !options.overwrite) {
48404
48911
  return this.failSpinner(
48405
48912
  `Project directory "${targetPath}" already exists. Use --overwrite to replace it.`
@@ -48426,27 +48933,40 @@ function initializeScaffoldCommands(program2) {
48426
48933
 
48427
48934
  // src/commands/network/setNetwork.ts
48428
48935
  import inquirer5 from "inquirer";
48429
- var networks2 = [
48430
- {
48431
- name: localnet.name,
48432
- alias: "localnet",
48433
- value: localnet
48434
- },
48435
- {
48436
- name: studionet.name,
48437
- alias: "studionet",
48438
- value: studionet
48439
- },
48440
- {
48441
- name: testnetAsimov.name,
48442
- alias: "testnet-asimov",
48443
- value: testnetAsimov
48444
- }
48445
- ];
48936
+ var networks2 = Object.entries(BUILT_IN_NETWORKS).map(([alias, network]) => ({
48937
+ name: network.name,
48938
+ alias,
48939
+ value: network
48940
+ }));
48446
48941
  var NetworkActions = class extends BaseAction {
48447
48942
  constructor() {
48448
48943
  super();
48449
48944
  }
48945
+ async showInfo() {
48946
+ const storedNetwork = this.getConfigByKey("network") || "localnet";
48947
+ const network = resolveNetwork(storedNetwork);
48948
+ const info = {
48949
+ alias: storedNetwork,
48950
+ name: network.name,
48951
+ chainId: network.id?.toString() || "unknown",
48952
+ rpc: network.rpcUrls?.default?.http?.[0] || "unknown",
48953
+ mainContract: network.consensusMainContract?.address || "not set",
48954
+ stakingContract: network.stakingContract?.address || "not set"
48955
+ };
48956
+ if (network.blockExplorers?.default?.url) {
48957
+ info.explorer = network.blockExplorers.default.url;
48958
+ }
48959
+ this.succeedSpinner("Current network", info);
48960
+ }
48961
+ async listNetworks() {
48962
+ const currentNetwork = this.getConfigByKey("network") || "localnet";
48963
+ console.log("");
48964
+ for (const net of networks2) {
48965
+ const marker = net.alias === currentNetwork ? "*" : " ";
48966
+ console.log(`${marker} ${net.alias.padEnd(16)} ${net.name}`);
48967
+ }
48968
+ console.log("");
48969
+ }
48450
48970
  async setNetwork(networkName) {
48451
48971
  if (networkName || networkName === "") {
48452
48972
  if (!networks2.some((n) => n.name === networkName || n.alias === networkName)) {
@@ -48481,7 +49001,10 @@ var NetworkActions = class extends BaseAction {
48481
49001
  // src/commands/network/index.ts
48482
49002
  function initializeNetworkCommands(program2) {
48483
49003
  const networkActions = new NetworkActions();
48484
- program2.command("network").description("Set the network to use").argument("[network]", "The network to use").action((networkName) => networkActions.setNetwork(networkName));
49004
+ const network = program2.command("network").description("Network configuration");
49005
+ network.command("set").description("Set the network to use").argument("[network]", "The network to set").action((networkName) => networkActions.setNetwork(networkName));
49006
+ network.command("info").description("Show current network configuration and contract addresses").action(() => networkActions.showInfo());
49007
+ network.command("list").description("List available networks").action(() => networkActions.listNetworks());
48485
49008
  return program2;
48486
49009
  }
48487
49010
 
@@ -48595,8 +49118,8 @@ function initializeTransactionsCommands(program2) {
48595
49118
  }
48596
49119
 
48597
49120
  // src/commands/staking/StakingAction.ts
48598
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
48599
- import { ethers as ethers5 } from "ethers";
49121
+ import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
49122
+ import { ethers as ethers6 } from "ethers";
48600
49123
  var StakingAction = class extends BaseAction {
48601
49124
  constructor() {
48602
49125
  super();
@@ -48614,6 +49137,9 @@ var StakingAction = class extends BaseAction {
48614
49137
  }
48615
49138
  async getStakingClient(config) {
48616
49139
  if (!this._stakingClient) {
49140
+ if (config.account) {
49141
+ this.accountOverride = config.account;
49142
+ }
48617
49143
  const network = this.getNetwork(config);
48618
49144
  if (config.stakingAddress) {
48619
49145
  network.stakingContract = {
@@ -48632,6 +49158,9 @@ var StakingAction = class extends BaseAction {
48632
49158
  return this._stakingClient;
48633
49159
  }
48634
49160
  async getReadOnlyStakingClient(config) {
49161
+ if (config.account) {
49162
+ this.accountOverride = config.account;
49163
+ }
48635
49164
  const network = this.getNetwork(config);
48636
49165
  if (config.stakingAddress) {
48637
49166
  network.stakingContract = {
@@ -48639,34 +49168,46 @@ var StakingAction = class extends BaseAction {
48639
49168
  abi: abi_exports.STAKING_ABI
48640
49169
  };
48641
49170
  }
48642
- const keypairPath = this.getConfigByKey("keyPairPath");
48643
- if (!keypairPath || !existsSync6(keypairPath)) {
48644
- throw new Error("No account found. Run 'genlayer account create' first.");
49171
+ const accountName = this.resolveAccountName();
49172
+ const keystorePath = this.getKeystorePath(accountName);
49173
+ if (!existsSync7(keystorePath)) {
49174
+ throw new Error(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
48645
49175
  }
48646
- const keystoreData = JSON.parse(readFileSync6(keypairPath, "utf-8"));
49176
+ const keystoreData = JSON.parse(readFileSync8(keystorePath, "utf-8"));
49177
+ const addr = keystoreData.address;
49178
+ const normalizedAddress = addr.startsWith("0x") ? addr : `0x${addr}`;
48647
49179
  return createClient2({
48648
49180
  chain: network,
48649
49181
  endpoint: config.rpc,
48650
- account: keystoreData.address
49182
+ account: normalizedAddress
48651
49183
  });
48652
49184
  }
48653
49185
  async getPrivateKeyForStaking() {
48654
- const keypairPath = this.getConfigByKey("keyPairPath");
48655
- if (!keypairPath || !existsSync6(keypairPath)) {
48656
- throw new Error("No account found. Run 'genlayer account create' first.");
49186
+ const accountName = this.resolveAccountName();
49187
+ const keystorePath = this.getKeystorePath(accountName);
49188
+ if (!existsSync7(keystorePath)) {
49189
+ throw new Error(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`);
48657
49190
  }
48658
- const keystoreData = JSON.parse(readFileSync6(keypairPath, "utf-8"));
49191
+ const keystoreJson = readFileSync8(keystorePath, "utf-8");
49192
+ const keystoreData = JSON.parse(keystoreJson);
48659
49193
  if (!this.isValidKeystoreFormat(keystoreData)) {
48660
49194
  throw new Error("Invalid keystore format.");
48661
49195
  }
48662
- const cachedKey = await this.keychainManager.getPrivateKey();
49196
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
48663
49197
  if (cachedKey) {
48664
- return cachedKey;
49198
+ const tempAccount = createAccount(cachedKey);
49199
+ const cachedAddress = tempAccount.address.toLowerCase();
49200
+ const keystoreAddress = `0x${keystoreData.address.toLowerCase().replace(/^0x/, "")}`;
49201
+ if (cachedAddress !== keystoreAddress) {
49202
+ await this.keychainManager.removePrivateKey(accountName);
49203
+ } else {
49204
+ return cachedKey;
49205
+ }
48665
49206
  }
48666
49207
  this.stopSpinner();
48667
- const password = await this.promptPassword("Enter password to unlock account:");
49208
+ const password = await this.promptPassword(`Enter password to unlock account '${accountName}':`);
48668
49209
  this.startSpinner("Continuing...");
48669
- const wallet = await ethers5.Wallet.fromEncryptedJson(keystoreData.encrypted, password);
49210
+ const wallet = await ethers6.Wallet.fromEncryptedJson(keystoreJson, password);
48670
49211
  return wallet.privateKey;
48671
49212
  }
48672
49213
  parseAmount(amount) {
@@ -48676,12 +49217,14 @@ var StakingAction = class extends BaseAction {
48676
49217
  return formatStakingAmount(amount);
48677
49218
  }
48678
49219
  async getSignerAddress() {
48679
- const keypairPath = this.getConfigByKey("keyPairPath");
48680
- if (!keypairPath || !existsSync6(keypairPath)) {
48681
- throw new Error("Keypair file not found.");
49220
+ const accountName = this.resolveAccountName();
49221
+ const keystorePath = this.getKeystorePath(accountName);
49222
+ if (!existsSync7(keystorePath)) {
49223
+ throw new Error(`Account '${accountName}' not found.`);
48682
49224
  }
48683
- const keystoreData = JSON.parse(readFileSync6(keypairPath, "utf-8"));
48684
- return keystoreData.address;
49225
+ const keystoreData = JSON.parse(readFileSync8(keystorePath, "utf-8"));
49226
+ const addr = keystoreData.address;
49227
+ return addr.startsWith("0x") ? addr : `0x${addr}`;
48685
49228
  }
48686
49229
  };
48687
49230
 
@@ -48886,9 +49429,14 @@ var SetIdentityAction = class extends StakingAction {
48886
49429
  blockNumber: result.blockNumber.toString(),
48887
49430
  gasUsed: result.gasUsed.toString()
48888
49431
  };
49432
+ if (options.logoUri) output.logoUri = options.logoUri;
48889
49433
  if (options.website) output.website = options.website;
49434
+ if (options.description) output.description = options.description;
49435
+ if (options.email) output.email = options.email;
48890
49436
  if (options.twitter) output.twitter = options.twitter;
49437
+ if (options.telegram) output.telegram = options.telegram;
48891
49438
  if (options.github) output.github = options.github;
49439
+ if (options.extraCid) output.extraCid = options.extraCid;
48892
49440
  this.succeedSpinner("Validator identity set!", output);
48893
49441
  } catch (error) {
48894
49442
  this.failSpinner("Failed to set identity", error.message || error);
@@ -48920,6 +49468,8 @@ var DelegatorJoinAction = class extends StakingAction {
48920
49468
  gasUsed: result.gasUsed.toString()
48921
49469
  };
48922
49470
  this.succeedSpinner("Successfully joined as delegator!", output);
49471
+ console.log(source_default.dim(`
49472
+ To view your delegation: genlayer staking delegation-info --validator ${options.validator}`));
48923
49473
  } catch (error) {
48924
49474
  this.failSpinner("Failed to join as delegator", error.message || error);
48925
49475
  }
@@ -48993,6 +49543,8 @@ var DelegatorClaimAction = class extends StakingAction {
48993
49543
  };
48994
49544
 
48995
49545
  // src/commands/staking/stakingInfo.ts
49546
+ var ACTIVATION_DELAY_EPOCHS = 2n;
49547
+ var UNBONDING_PERIOD_EPOCHS = 7n;
48996
49548
  var StakingInfoAction = class extends StakingAction {
48997
49549
  constructor() {
48998
49550
  super();
@@ -49026,21 +49578,24 @@ var StakingInfoAction = class extends StakingAction {
49026
49578
  needsPriming: info.needsPriming,
49027
49579
  live: info.live,
49028
49580
  banned: info.banned ? info.bannedEpoch?.toString() : "Not banned",
49029
- selfStakePendingDeposits: info.pendingDeposits.length > 0 ? info.pendingDeposits.map((d) => {
49030
- const depositEpoch = d.epoch;
49031
- const activationEpoch = depositEpoch + 2n;
49032
- const epochsUntilActive = activationEpoch - currentEpoch;
49033
- return {
49034
- epoch: depositEpoch.toString(),
49035
- stake: d.stake,
49036
- shares: d.shares.toString(),
49037
- activatesAtEpoch: activationEpoch.toString(),
49038
- status: epochsUntilActive <= 0n ? "Active" : `Pending (${epochsUntilActive} epoch${epochsUntilActive > 1n ? "s" : ""} remaining)`
49039
- };
49040
- }) : "None",
49581
+ selfStakePendingDeposits: (() => {
49582
+ const pending = info.pendingDeposits.filter((d) => d.epoch + ACTIVATION_DELAY_EPOCHS > currentEpoch);
49583
+ return pending.length > 0 ? pending.map((d) => {
49584
+ const depositEpoch = d.epoch;
49585
+ const activationEpoch = depositEpoch + ACTIVATION_DELAY_EPOCHS;
49586
+ const epochsUntilActive = activationEpoch - currentEpoch;
49587
+ return {
49588
+ epoch: depositEpoch.toString(),
49589
+ stake: d.stake,
49590
+ shares: d.shares.toString(),
49591
+ activatesAtEpoch: activationEpoch.toString(),
49592
+ epochsRemaining: epochsUntilActive.toString()
49593
+ };
49594
+ }) : "None";
49595
+ })(),
49041
49596
  selfStakePendingWithdrawals: info.pendingWithdrawals.length > 0 ? info.pendingWithdrawals.map((w) => {
49042
49597
  const exitEpoch = w.epoch;
49043
- const claimableEpoch = exitEpoch + 7n;
49598
+ const claimableEpoch = exitEpoch + UNBONDING_PERIOD_EPOCHS;
49044
49599
  const epochsUntilClaimable = claimableEpoch - currentEpoch;
49045
49600
  return {
49046
49601
  epoch: exitEpoch.toString(),
@@ -49073,6 +49628,8 @@ var StakingInfoAction = class extends StakingAction {
49073
49628
  try {
49074
49629
  const client = await this.getReadOnlyStakingClient(options);
49075
49630
  const delegatorAddress = options.delegator || await this.getSignerAddress();
49631
+ const isOwnDelegation = !options.delegator;
49632
+ this.setSpinnerText(`Fetching delegation info for ${delegatorAddress}...`);
49076
49633
  if (!options.validator) {
49077
49634
  this.failSpinner("Validator address is required");
49078
49635
  return;
@@ -49095,21 +49652,24 @@ var StakingInfoAction = class extends StakingAction {
49095
49652
  shares: info.shares.toString(),
49096
49653
  stake: info.stake,
49097
49654
  projectedReward,
49098
- pendingDeposits: info.pendingDeposits.length > 0 ? info.pendingDeposits.map((d) => {
49099
- const depositEpoch = d.epoch;
49100
- const activationEpoch = depositEpoch + 2n;
49101
- const epochsUntilActive = activationEpoch - currentEpoch;
49102
- return {
49103
- epoch: depositEpoch.toString(),
49104
- stake: d.stake,
49105
- shares: d.shares.toString(),
49106
- activatesAtEpoch: activationEpoch.toString(),
49107
- status: epochsUntilActive <= 0n ? "Active" : `Pending (${epochsUntilActive} epoch${epochsUntilActive > 1n ? "s" : ""} remaining)`
49108
- };
49109
- }) : "None",
49655
+ pendingDeposits: (() => {
49656
+ const pending = info.pendingDeposits.filter((d) => d.epoch + ACTIVATION_DELAY_EPOCHS > currentEpoch);
49657
+ return pending.length > 0 ? pending.map((d) => {
49658
+ const depositEpoch = d.epoch;
49659
+ const activationEpoch = depositEpoch + ACTIVATION_DELAY_EPOCHS;
49660
+ const epochsUntilActive = activationEpoch - currentEpoch;
49661
+ return {
49662
+ epoch: depositEpoch.toString(),
49663
+ stake: d.stake,
49664
+ shares: d.shares.toString(),
49665
+ activatesAtEpoch: activationEpoch.toString(),
49666
+ epochsRemaining: epochsUntilActive.toString()
49667
+ };
49668
+ }) : "None";
49669
+ })(),
49110
49670
  pendingWithdrawals: info.pendingWithdrawals.length > 0 ? info.pendingWithdrawals.map((w) => {
49111
49671
  const exitEpoch = w.epoch;
49112
- const claimableEpoch = exitEpoch + 7n;
49672
+ const claimableEpoch = exitEpoch + UNBONDING_PERIOD_EPOCHS;
49113
49673
  const epochsUntilClaimable = claimableEpoch - currentEpoch;
49114
49674
  return {
49115
49675
  epoch: exitEpoch.toString(),
@@ -49120,7 +49680,8 @@ var StakingInfoAction = class extends StakingAction {
49120
49680
  };
49121
49681
  }) : "None"
49122
49682
  };
49123
- this.succeedSpinner("Stake info retrieved", result);
49683
+ const msg = isOwnDelegation ? "Your delegation info" : `Delegation info for ${delegatorAddress}`;
49684
+ this.succeedSpinner(msg, result);
49124
49685
  } catch (error) {
49125
49686
  this.failSpinner("Failed to get stake info", error.message || error);
49126
49687
  }
@@ -49214,54 +49775,696 @@ var StakingInfoAction = class extends StakingAction {
49214
49775
  }
49215
49776
  };
49216
49777
 
49778
+ // src/commands/staking/wizard.ts
49779
+ import inquirer6 from "inquirer";
49780
+ import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
49781
+ import path7 from "path";
49782
+ function ensureHexPrefix(address) {
49783
+ if (!address) return address;
49784
+ return address.startsWith("0x") ? address : `0x${address}`;
49785
+ }
49786
+ var ValidatorWizardAction = class extends StakingAction {
49787
+ constructor() {
49788
+ super();
49789
+ }
49790
+ async execute(options) {
49791
+ console.log("\n========================================");
49792
+ console.log(" GenLayer Validator Setup Wizard");
49793
+ console.log("========================================\n");
49794
+ const state = {};
49795
+ try {
49796
+ await this.stepAccountSetup(state, options);
49797
+ await this.stepNetworkSelection(state, options);
49798
+ await this.stepBalanceCheck(state, options);
49799
+ await this.stepOperatorSetup(state);
49800
+ await this.stepStakeAmount(state);
49801
+ await this.stepJoinValidator(state, options);
49802
+ if (!options.skipIdentity) {
49803
+ await this.stepIdentitySetup(state, options);
49804
+ }
49805
+ this.showSummary(state);
49806
+ } catch (error) {
49807
+ if (error.message === "WIZARD_ABORTED") {
49808
+ this.logError("Wizard aborted.");
49809
+ return;
49810
+ }
49811
+ this.failSpinner("Wizard failed", error.message || error);
49812
+ }
49813
+ }
49814
+ async stepAccountSetup(state, options) {
49815
+ console.log("Step 1: Account Setup");
49816
+ console.log("---------------------\n");
49817
+ if (options.account) {
49818
+ const keystorePath = this.getKeystorePath(options.account);
49819
+ if (!this.accountExists(options.account)) {
49820
+ this.failSpinner(`Account '${options.account}' not found.`);
49821
+ }
49822
+ state.accountName = options.account;
49823
+ this.accountOverride = options.account;
49824
+ const address = await this.getSignerAddress();
49825
+ state.accountAddress = ensureHexPrefix(address);
49826
+ console.log(`Using account: ${options.account} (${state.accountAddress})
49827
+ `);
49828
+ return;
49829
+ }
49830
+ const accounts = this.listAccounts();
49831
+ if (accounts.length === 0) {
49832
+ console.log("No accounts found. Let's create one.\n");
49833
+ const { accountName } = await inquirer6.prompt([
49834
+ {
49835
+ type: "input",
49836
+ name: "accountName",
49837
+ message: "Enter a name for your validator account:",
49838
+ default: "validator",
49839
+ validate: (input) => input.length > 0 || "Name cannot be empty"
49840
+ }
49841
+ ]);
49842
+ const createAction = new CreateAccountAction();
49843
+ await createAction.execute({ name: accountName, overwrite: false, setActive: true });
49844
+ state.accountName = accountName;
49845
+ this.accountOverride = accountName;
49846
+ const address = await this.getSignerAddress();
49847
+ state.accountAddress = ensureHexPrefix(address);
49848
+ } else {
49849
+ const choices = [
49850
+ ...accounts.map((a) => ({
49851
+ name: `${a.name} (${a.address})`,
49852
+ value: a.name
49853
+ })),
49854
+ { name: "Create new account", value: "__create_new__" }
49855
+ ];
49856
+ const { selectedAccount } = await inquirer6.prompt([
49857
+ {
49858
+ type: "list",
49859
+ name: "selectedAccount",
49860
+ message: "Select an account that will be the owner of the validator:",
49861
+ choices
49862
+ }
49863
+ ]);
49864
+ if (selectedAccount === "__create_new__") {
49865
+ const { accountName } = await inquirer6.prompt([
49866
+ {
49867
+ type: "input",
49868
+ name: "accountName",
49869
+ message: "Enter a name for your validator account:",
49870
+ default: "validator",
49871
+ validate: (input) => {
49872
+ if (input.length === 0) return "Name cannot be empty";
49873
+ if (accounts.find((a) => a.name === input)) return "Account with this name already exists";
49874
+ return true;
49875
+ }
49876
+ }
49877
+ ]);
49878
+ const createAction = new CreateAccountAction();
49879
+ await createAction.execute({ name: accountName, overwrite: false, setActive: true });
49880
+ state.accountName = accountName;
49881
+ this.accountOverride = accountName;
49882
+ const address = await this.getSignerAddress();
49883
+ state.accountAddress = ensureHexPrefix(address);
49884
+ } else {
49885
+ state.accountName = selectedAccount;
49886
+ this.accountOverride = selectedAccount;
49887
+ this.setActiveAccount(selectedAccount);
49888
+ const address = await this.getSignerAddress();
49889
+ state.accountAddress = ensureHexPrefix(address);
49890
+ console.log(`
49891
+ Using account: ${selectedAccount} (${state.accountAddress})`);
49892
+ }
49893
+ }
49894
+ console.log("");
49895
+ }
49896
+ async stepNetworkSelection(state, options) {
49897
+ console.log("Step 2: Network Selection");
49898
+ console.log("-------------------------\n");
49899
+ if (options.network) {
49900
+ const network = BUILT_IN_NETWORKS[options.network];
49901
+ if (!network) {
49902
+ this.failSpinner(`Unknown network: ${options.network}`);
49903
+ }
49904
+ state.networkAlias = options.network;
49905
+ this.writeConfig("network", options.network);
49906
+ console.log(`Using network: ${network.name}
49907
+ `);
49908
+ return;
49909
+ }
49910
+ const currentNetwork = this.getConfigByKey("network");
49911
+ const excludedNetworks = ["studionet"];
49912
+ const networks3 = Object.entries(BUILT_IN_NETWORKS).filter(([alias]) => !excludedNetworks.includes(alias)).map(([alias, chain]) => ({
49913
+ name: chain.name,
49914
+ value: alias
49915
+ }));
49916
+ const { selectedNetwork } = await inquirer6.prompt([
49917
+ {
49918
+ type: "list",
49919
+ name: "selectedNetwork",
49920
+ message: "Select network:",
49921
+ choices: networks3,
49922
+ default: currentNetwork || "testnet-asimov"
49923
+ }
49924
+ ]);
49925
+ state.networkAlias = selectedNetwork;
49926
+ this.writeConfig("network", selectedNetwork);
49927
+ console.log(`
49928
+ Network set to: ${BUILT_IN_NETWORKS[selectedNetwork].name}
49929
+ `);
49930
+ }
49931
+ async stepBalanceCheck(state, options) {
49932
+ console.log("Step 3: Balance Check");
49933
+ console.log("---------------------\n");
49934
+ this.startSpinner("Checking balance and staking requirements...");
49935
+ const network = BUILT_IN_NETWORKS[state.networkAlias];
49936
+ const client = createClient2({
49937
+ chain: network,
49938
+ account: state.accountAddress,
49939
+ endpoint: options.rpc
49940
+ });
49941
+ const [balance, epochInfo] = await Promise.all([
49942
+ client.getBalance({ address: state.accountAddress }),
49943
+ client.getEpochInfo()
49944
+ ]);
49945
+ this.stopSpinner();
49946
+ const balanceFormatted = formatEther(balance);
49947
+ const minStakeRaw = epochInfo.validatorMinStakeRaw;
49948
+ const minStakeFormatted = epochInfo.validatorMinStake;
49949
+ const currentEpoch = epochInfo.currentEpoch;
49950
+ const MIN_GAS_BUFFER = parseEther("0.01");
49951
+ console.log(`Balance: ${balanceFormatted} GEN`);
49952
+ console.log(`Minimum stake required: ${minStakeFormatted}`);
49953
+ if (currentEpoch === 0n) {
49954
+ console.log("(Epoch 0: minimum stake not enforced, but gas fees still required)");
49955
+ console.log(`Note: Validator won't become active until self-stake reaches ${minStakeFormatted}`);
49956
+ }
49957
+ const minRequired = currentEpoch === 0n ? MIN_GAS_BUFFER : minStakeRaw + MIN_GAS_BUFFER;
49958
+ if (balance < minRequired) {
49959
+ console.log("");
49960
+ const minFormatted = currentEpoch === 0n ? "0.01 GEN (for gas)" : `${minStakeFormatted} + gas`;
49961
+ this.failSpinner(
49962
+ `Insufficient balance. You need at least ${minFormatted} to become a validator.
49963
+ Fund your account (${state.accountAddress}) and run the wizard again.`
49964
+ );
49965
+ }
49966
+ state.balance = balance;
49967
+ state.minStake = currentEpoch === 0n ? 0n : minStakeRaw;
49968
+ console.log("Balance sufficient!\n");
49969
+ }
49970
+ async stepOperatorSetup(state) {
49971
+ console.log("Step 4: Operator Setup");
49972
+ console.log("----------------------\n");
49973
+ console.log("Using a separate operator address is recommended for security:");
49974
+ console.log("- Owner account: holds staked funds (keep secure)");
49975
+ console.log("- Operator account: signs blocks (hot wallet on validator server)\n");
49976
+ const { useOperator } = await inquirer6.prompt([
49977
+ {
49978
+ type: "confirm",
49979
+ name: "useOperator",
49980
+ message: "Do you want to use a separate operator address?",
49981
+ default: true
49982
+ }
49983
+ ]);
49984
+ if (!useOperator) {
49985
+ state.operatorAddress = ensureHexPrefix(state.accountAddress);
49986
+ state.operatorAccountName = state.accountName;
49987
+ console.log("\nOperator will be the same as owner address.\n");
49988
+ return;
49989
+ }
49990
+ const accounts = this.listAccounts();
49991
+ const otherAccounts = accounts.filter((a) => a.name !== state.accountName);
49992
+ const choices = [
49993
+ { name: "Create new operator account", value: "create" },
49994
+ ...otherAccounts.length > 0 ? [{ name: "Select from my accounts", value: "select" }] : [],
49995
+ { name: "Enter existing operator address", value: "existing" }
49996
+ ];
49997
+ const { operatorChoice } = await inquirer6.prompt([
49998
+ {
49999
+ type: "list",
50000
+ name: "operatorChoice",
50001
+ message: "How would you like to set up the operator?",
50002
+ choices
50003
+ }
50004
+ ]);
50005
+ if (operatorChoice === "existing") {
50006
+ const { operatorAddress } = await inquirer6.prompt([
50007
+ {
50008
+ type: "input",
50009
+ name: "operatorAddress",
50010
+ message: "Enter operator address (0x...):",
50011
+ validate: (input) => {
50012
+ if (!input.match(/^0x[a-fA-F0-9]{40}$/)) {
50013
+ return "Invalid address format. Expected 0x followed by 40 hex characters.";
50014
+ }
50015
+ return true;
50016
+ }
50017
+ }
50018
+ ]);
50019
+ state.operatorAddress = ensureHexPrefix(operatorAddress);
50020
+ console.log("");
50021
+ return;
50022
+ }
50023
+ if (operatorChoice === "select") {
50024
+ const { selectedOperator } = await inquirer6.prompt([
50025
+ {
50026
+ type: "list",
50027
+ name: "selectedOperator",
50028
+ message: "Select an account to use as operator:",
50029
+ choices: otherAccounts.map((a) => ({
50030
+ name: `${a.name} (${a.address})`,
50031
+ value: a.name
50032
+ }))
50033
+ }
50034
+ ]);
50035
+ const operatorKeystorePath2 = this.getKeystorePath(selectedOperator);
50036
+ const operatorData2 = JSON.parse(readFileSync9(operatorKeystorePath2, "utf-8"));
50037
+ state.operatorAddress = ensureHexPrefix(operatorData2.address);
50038
+ state.operatorAccountName = selectedOperator;
50039
+ const defaultFilename2 = `${selectedOperator}-keystore.json`;
50040
+ const { outputFilename: outputFilename2 } = await inquirer6.prompt([
50041
+ {
50042
+ type: "input",
50043
+ name: "outputFilename",
50044
+ message: "Export keystore filename:",
50045
+ default: defaultFilename2
50046
+ }
50047
+ ]);
50048
+ let outputPath2 = path7.resolve(`./${outputFilename2}`);
50049
+ if (existsSync8(outputPath2)) {
50050
+ const { overwrite } = await inquirer6.prompt([
50051
+ {
50052
+ type: "confirm",
50053
+ name: "overwrite",
50054
+ message: `File ${outputFilename2} already exists. Overwrite?`,
50055
+ default: false
50056
+ }
50057
+ ]);
50058
+ if (!overwrite) {
50059
+ const { newFilename } = await inquirer6.prompt([
50060
+ {
50061
+ type: "input",
50062
+ name: "newFilename",
50063
+ message: "Enter new filename:"
50064
+ }
50065
+ ]);
50066
+ outputPath2 = path7.resolve(`./${newFilename}`);
50067
+ }
50068
+ }
50069
+ const { exportPassword: exportPassword2 } = await inquirer6.prompt([
50070
+ {
50071
+ type: "password",
50072
+ name: "exportPassword",
50073
+ message: "Enter password for exported keystore (needed to import in node):",
50074
+ mask: "*",
50075
+ validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
50076
+ }
50077
+ ]);
50078
+ const { confirmPassword: confirmPassword2 } = await inquirer6.prompt([
50079
+ {
50080
+ type: "password",
50081
+ name: "confirmPassword",
50082
+ message: "Confirm password:",
50083
+ mask: "*"
50084
+ }
50085
+ ]);
50086
+ if (exportPassword2 !== confirmPassword2) {
50087
+ this.failSpinner("Passwords do not match");
50088
+ }
50089
+ const exportAction2 = new ExportAccountAction();
50090
+ await exportAction2.execute({
50091
+ account: selectedOperator,
50092
+ output: outputPath2,
50093
+ password: exportPassword2,
50094
+ overwrite: true
50095
+ });
50096
+ state.operatorKeystorePath = outputPath2;
50097
+ console.log("\n========================================");
50098
+ console.log(" IMPORTANT: Transfer operator keystore");
50099
+ console.log("========================================");
50100
+ console.log(`File: ${outputPath2}`);
50101
+ console.log("Transfer this file to your validator server and import it");
50102
+ console.log("into your validator node software.");
50103
+ console.log("========================================\n");
50104
+ return;
50105
+ }
50106
+ const { operatorName } = await inquirer6.prompt([
50107
+ {
50108
+ type: "input",
50109
+ name: "operatorName",
50110
+ message: "Enter a name for the operator account:",
50111
+ default: "operator",
50112
+ validate: (input) => {
50113
+ if (input.length === 0) return "Name cannot be empty";
50114
+ if (accounts.find((a) => a.name === input)) return "Account with this name already exists";
50115
+ return true;
50116
+ }
50117
+ }
50118
+ ]);
50119
+ console.log("");
50120
+ const createAction = new CreateAccountAction();
50121
+ await createAction.execute({ name: operatorName, overwrite: false, setActive: false });
50122
+ const operatorKeystorePath = this.getKeystorePath(operatorName);
50123
+ const operatorData = JSON.parse(readFileSync9(operatorKeystorePath, "utf-8"));
50124
+ state.operatorAddress = ensureHexPrefix(operatorData.address);
50125
+ state.operatorAccountName = operatorName;
50126
+ const defaultFilename = `${operatorName}-keystore.json`;
50127
+ const { outputFilename } = await inquirer6.prompt([
50128
+ {
50129
+ type: "input",
50130
+ name: "outputFilename",
50131
+ message: "Export keystore filename:",
50132
+ default: defaultFilename
50133
+ }
50134
+ ]);
50135
+ let outputPath = path7.resolve(`./${outputFilename}`);
50136
+ if (existsSync8(outputPath)) {
50137
+ const { overwrite } = await inquirer6.prompt([
50138
+ {
50139
+ type: "confirm",
50140
+ name: "overwrite",
50141
+ message: `File ${outputFilename} already exists. Overwrite?`,
50142
+ default: false
50143
+ }
50144
+ ]);
50145
+ if (!overwrite) {
50146
+ const { newFilename } = await inquirer6.prompt([
50147
+ {
50148
+ type: "input",
50149
+ name: "newFilename",
50150
+ message: "Enter new filename:"
50151
+ }
50152
+ ]);
50153
+ outputPath = path7.resolve(`./${newFilename}`);
50154
+ }
50155
+ }
50156
+ const { exportPassword } = await inquirer6.prompt([
50157
+ {
50158
+ type: "password",
50159
+ name: "exportPassword",
50160
+ message: "Enter password for exported keystore (needed to import in node):",
50161
+ mask: "*",
50162
+ validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
50163
+ }
50164
+ ]);
50165
+ const { confirmPassword } = await inquirer6.prompt([
50166
+ {
50167
+ type: "password",
50168
+ name: "confirmPassword",
50169
+ message: "Confirm password:",
50170
+ mask: "*"
50171
+ }
50172
+ ]);
50173
+ if (exportPassword !== confirmPassword) {
50174
+ this.failSpinner("Passwords do not match");
50175
+ }
50176
+ const exportAction = new ExportAccountAction();
50177
+ await exportAction.execute({
50178
+ account: operatorName,
50179
+ output: outputPath,
50180
+ password: exportPassword,
50181
+ overwrite: true
50182
+ });
50183
+ state.operatorKeystorePath = outputPath;
50184
+ console.log("\n========================================");
50185
+ console.log(" IMPORTANT: Transfer operator keystore");
50186
+ console.log("========================================");
50187
+ console.log(`File: ${outputPath}`);
50188
+ console.log("Transfer this file to your validator server and import it");
50189
+ console.log("into your validator node software.");
50190
+ console.log("========================================\n");
50191
+ }
50192
+ async stepStakeAmount(state) {
50193
+ console.log("Step 5: Stake Amount");
50194
+ console.log("--------------------\n");
50195
+ const balanceGEN = formatEther(state.balance);
50196
+ const minStakeGEN = formatEther(state.minStake);
50197
+ const hasMinStake = state.minStake > 0n;
50198
+ const { stakeAmount } = await inquirer6.prompt([
50199
+ {
50200
+ type: "input",
50201
+ name: "stakeAmount",
50202
+ message: hasMinStake ? `Enter stake amount (min: ${minStakeGEN}, max: ${balanceGEN} GEN):` : `Enter stake amount (max: ${balanceGEN} GEN):`,
50203
+ default: hasMinStake ? minStakeGEN : "1",
50204
+ validate: (input) => {
50205
+ const cleaned = input.toLowerCase().replace("gen", "").trim();
50206
+ const num2 = parseFloat(cleaned);
50207
+ if (isNaN(num2) || num2 <= 0) {
50208
+ return "Please enter a valid positive number";
50209
+ }
50210
+ const amountWei = BigInt(Math.floor(num2 * 1e18));
50211
+ if (hasMinStake && amountWei < state.minStake) {
50212
+ return `Amount must be at least ${minStakeGEN} GEN`;
50213
+ }
50214
+ if (amountWei > state.balance) {
50215
+ return `Amount exceeds balance (${balanceGEN} GEN)`;
50216
+ }
50217
+ return true;
50218
+ }
50219
+ }
50220
+ ]);
50221
+ const normalizedAmount = stakeAmount.toLowerCase().endsWith("gen") ? stakeAmount : `${stakeAmount}gen`;
50222
+ state.stakeAmount = normalizedAmount;
50223
+ const { confirm } = await inquirer6.prompt([
50224
+ {
50225
+ type: "confirm",
50226
+ name: "confirm",
50227
+ message: `You will stake ${stakeAmount}. Continue?`,
50228
+ default: true
50229
+ }
50230
+ ]);
50231
+ if (!confirm) {
50232
+ throw new Error("WIZARD_ABORTED");
50233
+ }
50234
+ console.log("");
50235
+ }
50236
+ async stepJoinValidator(state, options) {
50237
+ console.log("Step 6: Join as Validator");
50238
+ console.log("-------------------------\n");
50239
+ this.startSpinner("Creating validator...");
50240
+ try {
50241
+ const client = await this.getStakingClient({
50242
+ ...options,
50243
+ account: state.accountName,
50244
+ network: state.networkAlias
50245
+ });
50246
+ const amount = this.parseAmount(state.stakeAmount);
50247
+ this.setSpinnerText(`Creating validator with ${this.formatAmount(amount)} stake...`);
50248
+ const result = await client.validatorJoin({
50249
+ amount,
50250
+ operator: state.operatorAddress
50251
+ });
50252
+ state.validatorWalletAddress = ensureHexPrefix(result.validatorWallet);
50253
+ this.succeedSpinner("Validator created successfully!", {
50254
+ transactionHash: result.transactionHash,
50255
+ validatorWallet: state.validatorWalletAddress,
50256
+ amount: result.amount,
50257
+ operator: result.operator,
50258
+ blockNumber: result.blockNumber.toString()
50259
+ });
50260
+ console.log("");
50261
+ } catch (error) {
50262
+ this.failSpinner("Failed to create validator", error.message || error);
50263
+ }
50264
+ }
50265
+ async stepIdentitySetup(state, options) {
50266
+ console.log("Step 7: Identity Setup");
50267
+ console.log("----------------------\n");
50268
+ const { setupIdentity } = await inquirer6.prompt([
50269
+ {
50270
+ type: "confirm",
50271
+ name: "setupIdentity",
50272
+ message: "Would you like to set up your validator identity now?",
50273
+ default: true
50274
+ }
50275
+ ]);
50276
+ if (!setupIdentity) {
50277
+ console.log("\nYou can set up identity later with: genlayer staking set-identity\n");
50278
+ return;
50279
+ }
50280
+ const { moniker } = await inquirer6.prompt([
50281
+ {
50282
+ type: "input",
50283
+ name: "moniker",
50284
+ message: "Enter validator display name (moniker):",
50285
+ validate: (input) => input.length > 0 || "Moniker is required"
50286
+ }
50287
+ ]);
50288
+ const { logoUri } = await inquirer6.prompt([
50289
+ {
50290
+ type: "input",
50291
+ name: "logoUri",
50292
+ message: "Enter logo URL (optional):"
50293
+ }
50294
+ ]);
50295
+ const { website } = await inquirer6.prompt([
50296
+ {
50297
+ type: "input",
50298
+ name: "website",
50299
+ message: "Enter website URL (optional):"
50300
+ }
50301
+ ]);
50302
+ const { description } = await inquirer6.prompt([
50303
+ {
50304
+ type: "input",
50305
+ name: "description",
50306
+ message: "Enter description (optional):"
50307
+ }
50308
+ ]);
50309
+ const { email } = await inquirer6.prompt([
50310
+ {
50311
+ type: "input",
50312
+ name: "email",
50313
+ message: "Enter contact email (optional):"
50314
+ }
50315
+ ]);
50316
+ const { twitter } = await inquirer6.prompt([
50317
+ {
50318
+ type: "input",
50319
+ name: "twitter",
50320
+ message: "Enter Twitter handle (optional):"
50321
+ }
50322
+ ]);
50323
+ const { telegram } = await inquirer6.prompt([
50324
+ {
50325
+ type: "input",
50326
+ name: "telegram",
50327
+ message: "Enter Telegram handle (optional):"
50328
+ }
50329
+ ]);
50330
+ const { github } = await inquirer6.prompt([
50331
+ {
50332
+ type: "input",
50333
+ name: "github",
50334
+ message: "Enter GitHub handle (optional):"
50335
+ }
50336
+ ]);
50337
+ state.identity = {
50338
+ moniker,
50339
+ logoUri: logoUri || void 0,
50340
+ website: website || void 0,
50341
+ description: description || void 0,
50342
+ email: email || void 0,
50343
+ twitter: twitter || void 0,
50344
+ telegram: telegram || void 0,
50345
+ github: github || void 0
50346
+ };
50347
+ this.startSpinner("Setting validator identity...");
50348
+ try {
50349
+ const client = await this.getStakingClient({
50350
+ ...options,
50351
+ account: state.accountName,
50352
+ network: state.networkAlias
50353
+ });
50354
+ const validatorAddress = state.validatorWalletAddress || state.accountAddress;
50355
+ await client.setIdentity({
50356
+ validator: ensureHexPrefix(validatorAddress),
50357
+ moniker,
50358
+ logoUri: logoUri || void 0,
50359
+ website: website || void 0,
50360
+ description: description || void 0,
50361
+ email: email || void 0,
50362
+ twitter: twitter || void 0,
50363
+ telegram: telegram || void 0,
50364
+ github: github || void 0
50365
+ });
50366
+ this.succeedSpinner("Validator identity set!");
50367
+ console.log("");
50368
+ } catch (error) {
50369
+ this.stopSpinner();
50370
+ this.logWarning(`Failed to set identity: ${error.message || error}`);
50371
+ console.log("You can try again later with: genlayer staking set-identity\n");
50372
+ }
50373
+ }
50374
+ showSummary(state) {
50375
+ console.log("\n========================================");
50376
+ console.log(" Validator Setup Complete!");
50377
+ console.log("========================================\n");
50378
+ const validatorWallet = ensureHexPrefix(state.validatorWalletAddress || state.accountAddress);
50379
+ const ownerAddress = ensureHexPrefix(state.accountAddress);
50380
+ const operatorAddress = ensureHexPrefix(state.operatorAddress || "");
50381
+ console.log("Summary:");
50382
+ console.log(` Validator Wallet: ${validatorWallet}`);
50383
+ console.log(` Owner: ${ownerAddress} (${state.accountName})`);
50384
+ if (state.operatorAccountName) {
50385
+ console.log(` Operator: ${operatorAddress} (${state.operatorAccountName})`);
50386
+ } else {
50387
+ console.log(` Operator: ${operatorAddress}`);
50388
+ }
50389
+ console.log(` Staked Amount: ${state.stakeAmount}`);
50390
+ console.log(` Network: ${BUILT_IN_NETWORKS[state.networkAlias].name}`);
50391
+ if (state.identity) {
50392
+ console.log(` Identity:`);
50393
+ console.log(` Moniker: ${state.identity.moniker}`);
50394
+ if (state.identity.logoUri) console.log(` Logo: ${state.identity.logoUri}`);
50395
+ if (state.identity.website) console.log(` Website: ${state.identity.website}`);
50396
+ if (state.identity.description) console.log(` Description: ${state.identity.description}`);
50397
+ if (state.identity.email) console.log(` Email: ${state.identity.email}`);
50398
+ if (state.identity.twitter) console.log(` Twitter: ${state.identity.twitter}`);
50399
+ if (state.identity.telegram) console.log(` Telegram: ${state.identity.telegram}`);
50400
+ if (state.identity.github) console.log(` GitHub: ${state.identity.github}`);
50401
+ }
50402
+ console.log("\nNext Steps:");
50403
+ let step = 1;
50404
+ if (state.operatorKeystorePath) {
50405
+ console.log(` ${step++}. Transfer operator keystore to your validator server:`);
50406
+ console.log(` ${state.operatorKeystorePath}`);
50407
+ console.log(` ${step++}. Import it into your validator node software`);
50408
+ }
50409
+ console.log(` ${step++}. Monitor your validator:`);
50410
+ console.log(` genlayer staking validator-info --validator ${validatorWallet}`);
50411
+ console.log(` ${step++}. Lock your account when done: genlayer account lock`);
50412
+ console.log("\n========================================\n");
50413
+ }
50414
+ };
50415
+
49217
50416
  // src/commands/staking/index.ts
49218
50417
  function initializeStakingCommands(program2) {
49219
50418
  const staking = program2.command("staking").description("Staking operations for validators and delegators");
49220
- staking.command("validator-join").description("Join as a validator by staking tokens").requiredOption("--amount <amount>", "Amount to stake (in wei or with 'eth'/'gen' suffix, e.g., '42000gen')").option("--operator <address>", "Operator address (defaults to signer)").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) => {
50419
+ staking.command("wizard").description("Interactive wizard to become a validator").option("--account <name>", "Account to use (skip selection)").option("--network <network>", "Network to use (skip selection)").option("--skip-identity", "Skip identity setup step").option("--rpc <rpcUrl>", "RPC URL for the network").option("--staking-address <address>", "Staking contract address (overrides chain config)").action(async (options) => {
50420
+ const wizard = new ValidatorWizardAction();
50421
+ await wizard.execute(options);
50422
+ });
50423
+ staking.command("validator-join").description("Join as a validator by staking tokens").requiredOption("--amount <amount>", "Amount to stake (in wei or with 'eth'/'gen' suffix, e.g., '42000gen')").option("--operator <address>", "Operator 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) => {
49221
50424
  const action = new ValidatorJoinAction();
49222
50425
  await action.execute(options);
49223
50426
  });
49224
- 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("--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) => {
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) => {
49225
50428
  const action = new ValidatorDepositAction();
49226
50429
  await action.execute(options);
49227
50430
  });
49228
- staking.command("validator-exit").description("Exit as a validator by withdrawing shares").requiredOption("--shares <shares>", "Number of shares to withdraw").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) => {
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) => {
49229
50432
  const action = new ValidatorExitAction();
49230
50433
  await action.execute(options);
49231
50434
  });
49232
- staking.command("validator-claim").description("Claim validator withdrawals after unbonding period").option("--validator <address>", "Validator address (defaults to signer)").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) => {
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) => {
49233
50436
  const action = new ValidatorClaimAction();
49234
50437
  await action.execute(options);
49235
50438
  });
49236
- staking.command("validator-prime").description("Prime a validator to prepare their stake record for the next epoch").requiredOption("--validator <address>", "Validator address to prime").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) => {
50439
+ staking.command("validator-prime").description("Prime a validator to prepare their stake record for the next epoch").requiredOption("--validator <address>", "Validator address to prime").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) => {
49237
50440
  const action = new ValidatorPrimeAction();
49238
50441
  await action.execute(options);
49239
50442
  });
49240
- 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("--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) => {
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) => {
49241
50444
  const action = new SetOperatorAction();
49242
50445
  await action.execute(options);
49243
50446
  });
49244
- 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("--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) => {
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) => {
49245
50448
  const action = new SetIdentityAction();
49246
50449
  await action.execute(options);
49247
50450
  });
49248
- staking.command("delegator-join").description("Join as a delegator by staking with a validator").requiredOption("--validator <address>", "Validator address to delegate to").requiredOption("--amount <amount>", "Amount to stake (in wei or with 'eth'/'gen' suffix)").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) => {
50451
+ staking.command("delegator-join").description("Join as a delegator by staking with a validator").requiredOption("--validator <address>", "Validator address to delegate to").requiredOption("--amount <amount>", "Amount to stake (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) => {
49249
50452
  const action = new DelegatorJoinAction();
49250
50453
  await action.execute(options);
49251
50454
  });
49252
- staking.command("delegator-exit").description("Exit as a delegator by withdrawing shares from a validator").requiredOption("--validator <address>", "Validator address to exit from").requiredOption("--shares <shares>", "Number of shares to withdraw").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) => {
50455
+ staking.command("delegator-exit").description("Exit as a delegator by withdrawing shares from a validator").requiredOption("--validator <address>", "Validator address to exit from").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) => {
49253
50456
  const action = new DelegatorExitAction();
49254
50457
  await action.execute(options);
49255
50458
  });
49256
- staking.command("delegator-claim").description("Claim delegator withdrawals after unbonding period").requiredOption("--validator <address>", "Validator address").option("--delegator <address>", "Delegator address (defaults to signer)").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) => {
50459
+ staking.command("delegator-claim").description("Claim delegator withdrawals after unbonding period").requiredOption("--validator <address>", "Validator address").option("--delegator <address>", "Delegator 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) => {
49257
50460
  const action = new DelegatorClaimAction();
49258
50461
  await action.execute(options);
49259
50462
  });
49260
- staking.command("validator-info").description("Get information about a validator").option("--validator <address>", "Validator address (defaults to signer)").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) => {
50463
+ staking.command("validator-info").description("Get information about a validator").option("--validator <address>", "Validator address (defaults to signer)").option("--account <name>", "Account to use (for default validator address)").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) => {
49261
50464
  const action = new StakingInfoAction();
49262
50465
  await action.getValidatorInfo(options);
49263
50466
  });
49264
- staking.command("stake-info").description("Get stake info for a delegator with a validator").requiredOption("--validator <address>", "Validator address").option("--delegator <address>", "Delegator address (defaults to signer)").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) => {
50467
+ staking.command("delegation-info").description("Get delegation info for a delegator with a validator").requiredOption("--validator <address>", "Validator address").option("--delegator <address>", "Delegator address (defaults to signer)").option("--account <name>", "Account to use (for default delegator address)").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) => {
49265
50468
  const action = new StakingInfoAction();
49266
50469
  await action.getStakeInfo(options);
49267
50470
  });