From 8a88e5040fba1dd71be230846e1dcc0cdac12540 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 8 Jul 2026 10:47:35 +0100 Subject: [PATCH 1/4] feat(network): custom network profiles with deployment-file import genlayer network add --base [--deployment ] [--rpc ] [--consensus-main|--consensus-data|--staking|--fee-manager |--rounds-storage|--appeals ] [--chain-id ] [--deployment-key ] Profiles persist as base + address overrides only; resolveNetwork loads the base chain fresh from genlayer-js and applies overrides, so ABIs never go stale. network set/list/info/remove and StakingAction --network accept custom aliases. The consensus deployments.json shape is parsed by walking the tree for ContractName->address leaves (ConsensusMain, ConsensusData, GenStaking/Staking, FeeManager, Rounds/RoundsStorage, Appeals); flags take precedence over the file. Adds a prepare script so npm install from a git ref builds dist. Verified: 576 vitest tests, full manual smoke (add/list/set/info/remove with a deployment file). --- package.json | 1 + src/commands/account/index.ts | 2 +- src/commands/account/send.ts | 10 +- src/commands/account/show.ts | 2 +- src/commands/network/index.ts | 25 ++ src/commands/network/setNetwork.ts | 266 ++++++++++++++++-- src/commands/staking/StakingAction.ts | 8 +- src/commands/staking/index.ts | 38 +-- src/commands/staking/validatorHistory.ts | 14 +- src/commands/staking/validators.ts | 10 +- src/commands/staking/wizard.ts | 10 +- src/commands/vesting/VestingAction.ts | 8 +- src/commands/vesting/index.ts | 4 +- src/lib/actions/BaseAction.ts | 25 +- src/lib/networks/customNetworks.ts | 253 +++++++++++++++++ tests/actions/customNetworkProfiles.test.ts | 297 ++++++++++++++++++++ tests/commands/network.test.ts | 56 ++++ 17 files changed, 933 insertions(+), 96 deletions(-) create mode 100644 src/lib/networks/customNetworks.ts create mode 100644 tests/actions/customNetworkProfiles.test.ts diff --git a/package.json b/package.json index 64058f2d..405ab3d0 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:smoke": "vitest run --config vitest.smoke.config.ts", "dev": "cross-env NODE_ENV=development node esbuild.config.js", "build": "cross-env NODE_ENV=production node esbuild.config.js", + "prepare": "npm run build", "release": "./scripts/release.sh", "postinstall": "node ./scripts/postinstall.js", "docs:cli": "node scripts/generate-cli-docs.mjs" diff --git a/src/commands/account/index.ts b/src/commands/account/index.ts index 3fa23f15..88580d46 100644 --- a/src/commands/account/index.ts +++ b/src/commands/account/index.ts @@ -98,7 +98,7 @@ export function initializeAccountCommands(program: Command) { .command("send ") .description("Send GEN to an address") .option("--rpc ", "RPC URL for the network") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--account ", "Account to send from") .option("--password ", "Password to unlock account (skips interactive prompt)") .action(async (to: string, amount: string, options: {rpc?: string; network?: string; account?: string; password?: string}) => { diff --git a/src/commands/account/send.ts b/src/commands/account/send.ts index 716d577b..e6905b46 100644 --- a/src/commands/account/send.ts +++ b/src/commands/account/send.ts @@ -1,4 +1,4 @@ -import {BaseAction, BUILT_IN_NETWORKS, resolveNetwork} from "../../lib/actions/BaseAction"; +import {BaseAction, resolveNetwork} from "../../lib/actions/BaseAction"; import {parseEther, formatEther} from "viem"; import {createClient, createAccount} from "genlayer-js"; import type {GenLayerChain, Address, Hash} from "genlayer-js/types"; @@ -21,13 +21,9 @@ export class SendAction extends BaseAction { private getNetwork(networkOption?: string): GenLayerChain { if (networkOption) { - const network = BUILT_IN_NETWORKS[networkOption]; - if (!network) { - throw new Error(`Unknown network: ${networkOption}. Available: ${Object.keys(BUILT_IN_NETWORKS).join(", ")}`); - } - return network; + return resolveNetwork(networkOption, this.getCustomNetworks()); } - return resolveNetwork(this.getConfig().network); + return resolveNetwork(this.getConfig().network, this.getCustomNetworks()); } private parseAmount(amount: string): bigint { diff --git a/src/commands/account/show.ts b/src/commands/account/show.ts index 161bac41..ee74506f 100644 --- a/src/commands/account/show.ts +++ b/src/commands/account/show.ts @@ -15,7 +15,7 @@ export class ShowAccountAction extends BaseAction { } private getNetwork(): GenLayerChain { - return resolveNetwork(this.getConfig().network); + return resolveNetwork(this.getConfig().network, this.getCustomNetworks()); } async execute(options?: ShowAccountOptions): Promise { diff --git a/src/commands/network/index.ts b/src/commands/network/index.ts index a5f7ee27..4e786e0a 100644 --- a/src/commands/network/index.ts +++ b/src/commands/network/index.ts @@ -6,6 +6,24 @@ export function initializeNetworkCommands(program: Command) { const network = program.command("network").description("Network configuration"); + // genlayer network add + network + .command("add") + .description("Add a custom network profile") + .argument("", "Custom network alias") + .requiredOption("--base ", "Built-in base network alias") + .option("--deployment ", "Consensus deployments JSON file") + .option("--deployment-key ", "Deployment JSON sub-object to scan") + .option("--rpc ", "Node RPC URL override") + .option("--consensus-main ", "ConsensusMain contract address override") + .option("--consensus-data ", "ConsensusData contract address override") + .option("--staking ", "Staking contract address override") + .option("--fee-manager ", "FeeManager contract address override") + .option("--rounds-storage ", "RoundsStorage contract address override") + .option("--appeals ", "Appeals contract address override") + .option("--chain-id ", "Chain ID override") + .action((alias: string, options) => networkActions.addNetwork(alias, options)); + // genlayer network set [name] network .command("set") @@ -25,5 +43,12 @@ export function initializeNetworkCommands(program: Command) { .description("List available networks") .action(() => networkActions.listNetworks()); + // genlayer network remove + network + .command("remove") + .description("Remove a custom network profile") + .argument("", "Custom network alias to remove") + .action((alias: string) => networkActions.removeNetwork(alias)); + return program; } diff --git a/src/commands/network/setNetwork.ts b/src/commands/network/setNetwork.ts index 550e1fef..de771cb8 100644 --- a/src/commands/network/setNetwork.ts +++ b/src/commands/network/setNetwork.ts @@ -1,61 +1,113 @@ import {BaseAction, BUILT_IN_NETWORKS, resolveNetwork} from "../../lib/actions/BaseAction"; import inquirer, {DistinctQuestion} from "inquirer"; +import { + CONTRACT_OVERRIDES, + CUSTOM_NETWORKS_CONFIG_KEY, + getContractAddress, + isValidAddress, + normalizeCustomNetworks, + parseDeploymentFile, + type ContractOverrideKey, + type CustomNetworkOverrides, + type CustomNetworkProfile, + type CustomNetworksConfig, +} from "../../lib/networks/customNetworks"; +import type {Address, GenLayerChain} from "genlayer-js/types"; -const networks = Object.entries(BUILT_IN_NETWORKS).map(([alias, network]) => ({ +const builtInNetworks = Object.entries(BUILT_IN_NETWORKS).map(([alias, network]) => ({ name: network.name, alias, - value: network, + type: "built-in" as const, })); +const CONTRACT_FLAG_OPTIONS: Array<{ + optionKey: keyof AddNetworkOptions; + overrideKey: ContractOverrideKey; + label: string; +}> = [ + {optionKey: "consensusMain", overrideKey: "consensusMain", label: "--consensus-main"}, + {optionKey: "consensusData", overrideKey: "consensusData", label: "--consensus-data"}, + {optionKey: "staking", overrideKey: "staking", label: "--staking"}, + {optionKey: "feeManager", overrideKey: "feeManager", label: "--fee-manager"}, + {optionKey: "roundsStorage", overrideKey: "roundsStorage", label: "--rounds-storage"}, + {optionKey: "appeals", overrideKey: "appeals", label: "--appeals"}, +]; + +export interface AddNetworkOptions { + base: string; + deployment?: string; + deploymentKey?: string; + rpc?: string; + consensusMain?: string; + consensusData?: string; + staking?: string; + feeManager?: string; + roundsStorage?: string; + appeals?: string; + chainId?: string; +} + +type NetworkEntry = + | {alias: string; name: string; type: "built-in"} + | {alias: string; name: string; type: "custom"; base: string; profile: CustomNetworkProfile}; + export class NetworkActions extends BaseAction { constructor() { super(); } - async showInfo(): Promise { - const storedNetwork = this.getConfigByKey("network") || "localnet"; - const network = resolveNetwork(storedNetwork); - - const info: Record = { - alias: storedNetwork, - name: network.name, - chainId: network.id?.toString() || "unknown", - rpc: network.rpcUrls?.default?.http?.[0] || "unknown", - mainContract: network.consensusMainContract?.address || "not set", - stakingContract: network.stakingContract?.address || "not set", - }; + async addNetwork(alias: string, options: AddNetworkOptions): Promise { + try { + const customNetworks = this.readCustomNetworks(); + const profile = this.buildCustomNetworkProfile(alias, options, customNetworks); + customNetworks[alias] = profile; + this.writeConfig(CUSTOM_NETWORKS_CONFIG_KEY, customNetworks); - if (network.blockExplorers?.default?.url) { - info.explorer = network.blockExplorers.default.url; + const network = resolveNetwork(alias, customNetworks); + this.succeedSpinner("Custom network profile added", this.formatNetworkInfo(alias, network, profile)); + } catch (error: any) { + this.failSpinner("Failed to add custom network profile", error.message || error); } + } + + async showInfo(): Promise { + const storedNetwork = this.getConfigByKey("network") || "localnet"; + const customNetworks = this.readCustomNetworks(); + const network = resolveNetwork(storedNetwork, customNetworks); + const profile = customNetworks[storedNetwork]; - this.succeedSpinner("Current network", info); + this.succeedSpinner("Current network", this.formatNetworkInfo(storedNetwork, network, profile)); } async listNetworks(): Promise { const currentNetwork = this.getConfigByKey("network") || "localnet"; + const entries = this.getNetworkEntries(); console.log(""); - for (const net of networks) { + for (const net of entries) { const marker = net.alias === currentNetwork ? "*" : " "; - console.log(`${marker} ${net.alias.padEnd(16)} ${net.name}`); + if (net.type === "custom") { + console.log(`${marker} ${net.alias.padEnd(20)} custom base: ${net.base} ${net.name}`); + } else { + console.log(`${marker} ${net.alias.padEnd(20)} built-in ${net.name}`); + } } console.log(""); } async setNetwork(networkName?: string): Promise { + const entries = this.getNetworkEntries(); + if (networkName || networkName === "") { - if (!networks.some(n => n.name === networkName || n.alias === networkName)) { - this.failSpinner(`Network ${networkName} not found`); - return; - } - const selectedNetwork = networks.find(n => n.name === networkName || n.alias === networkName); + const selectedNetwork = entries.find(n => + n.alias === networkName || (n.type === "built-in" && n.name === networkName), + ); if (!selectedNetwork) { this.failSpinner(`Network ${networkName} not found`); return; } this.writeConfig("network", selectedNetwork.alias); - this.succeedSpinner(`Network successfully set to ${selectedNetwork.name}`); + this.succeedSpinner(`Network successfully set to ${this.getNetworkDisplayName(selectedNetwork)}`); return; } @@ -64,14 +116,172 @@ export class NetworkActions extends BaseAction { type: "list", name: "selectedNetwork", message: "Select which network do you want to use:", - choices: networks.map(n => ({name: n.name, value: n.alias})), + choices: entries.map(n => ({ + name: this.getNetworkChoiceName(n), + value: n.alias, + })), }, ]; const networkAnswer = await inquirer.prompt(networkQuestions); const selectedAlias = networkAnswer.selectedNetwork; - const selectedNetwork = networks.find(n => n.alias === selectedAlias)!; + const selectedNetwork = entries.find(n => n.alias === selectedAlias)!; this.writeConfig("network", selectedAlias); - this.succeedSpinner(`Network successfully set to ${selectedNetwork.name}`); + this.succeedSpinner(`Network successfully set to ${this.getNetworkDisplayName(selectedNetwork)}`); + } + + async removeNetwork(alias: string): Promise { + try { + if (BUILT_IN_NETWORKS[alias]) { + throw new Error(`Cannot remove built-in network: ${alias}`); + } + + const customNetworks = this.readCustomNetworks(); + if (!customNetworks[alias]) { + throw new Error(`Custom network ${alias} not found`); + } + + delete customNetworks[alias]; + this.writeConfig(CUSTOM_NETWORKS_CONFIG_KEY, customNetworks); + + if ((this.getConfigByKey("network") || "localnet") === alias) { + this.writeConfig("network", "localnet"); + this.logWarning(`Removed active network ${alias}; active network reset to localnet.`); + } + + this.succeedSpinner(`Custom network ${alias} removed`); + } catch (error: any) { + this.failSpinner("Failed to remove custom network profile", error.message || error); + } + } + + private buildCustomNetworkProfile( + alias: string, + options: AddNetworkOptions, + customNetworks: CustomNetworksConfig, + ): CustomNetworkProfile { + if (BUILT_IN_NETWORKS[alias]) { + throw new Error(`Custom network alias cannot collide with built-in network: ${alias}`); + } + if (customNetworks[alias]) { + throw new Error(`Custom network ${alias} already exists`); + } + if (!options.base || !BUILT_IN_NETWORKS[options.base]) { + throw new Error(`Base network must be one of: ${Object.keys(BUILT_IN_NETWORKS).join(", ")}`); + } + + const hasOverrideInput = Boolean( + options.deployment || + options.rpc || + options.chainId !== undefined || + CONTRACT_FLAG_OPTIONS.some(option => Boolean(options[option.optionKey])), + ); + if (!hasOverrideInput) { + throw new Error("Provide at least one override: --deployment, --rpc, --chain-id, or a contract address flag"); + } + + const overrides: CustomNetworkOverrides = {}; + if (options.deployment) { + const parsed = parseDeploymentFile(options.deployment, options.deploymentKey); + Object.assign(overrides, parsed.overrides); + for (const notice of parsed.notices) { + this.logInfo(notice); + } + } + + for (const option of CONTRACT_FLAG_OPTIONS) { + const address = options[option.optionKey]; + if (!address) continue; + if (!isValidAddress(address)) { + throw new Error(`Invalid address for ${option.label}: ${address}`); + } + overrides[option.overrideKey] = address as Address; + } + + if (options.rpc) { + overrides.rpcUrl = options.rpc; + } + + if (options.chainId !== undefined) { + const chainId = Number(options.chainId); + if (!Number.isInteger(chainId) || chainId <= 0) { + throw new Error(`Invalid --chain-id value: ${options.chainId}`); + } + overrides.chainId = chainId; + } + + return { + base: options.base, + overrides, + }; + } + + private formatNetworkInfo( + alias: string, + network: GenLayerChain, + profile?: CustomNetworkProfile, + ): Record { + const info: Record = { + alias, + type: profile ? "custom" : "built-in", + }; + + if (profile) { + info.base = profile.base; + } + + info.name = network.name; + info.chainId = this.formatInheritedValue(network.id?.toString() || "unknown", Boolean(profile?.overrides.chainId), profile); + info.rpc = this.formatInheritedValue(network.rpcUrls?.default?.http?.[0] || "unknown", Boolean(profile?.overrides.rpcUrl), profile); + + for (const contract of CONTRACT_OVERRIDES) { + const address = getContractAddress(network, contract.overrideKey) || "not set"; + info[contract.label] = this.formatInheritedValue(address, Boolean(profile?.overrides[contract.overrideKey]), profile); + } + + if (network.blockExplorers?.default?.url) { + info.explorer = network.blockExplorers.default.url; + } + + return info; + } + + private formatInheritedValue(value: string, overridden: boolean, profile?: CustomNetworkProfile): string { + if (!profile) return value; + return `${value} (${overridden ? "overridden" : "inherited"})`; + } + + private getNetworkEntries(): NetworkEntry[] { + const customNetworks = this.readCustomNetworks(); + const customEntries = Object.entries(customNetworks).map(([alias, profile]) => { + const network = resolveNetwork(alias, customNetworks); + return { + alias, + name: network.name, + type: "custom" as const, + base: profile.base, + profile, + }; + }); + + return [...builtInNetworks, ...customEntries]; + } + + private getNetworkChoiceName(entry: NetworkEntry): string { + if (entry.type === "custom") { + return `${entry.alias} (custom, base: ${entry.base})`; + } + return entry.name; + } + + private getNetworkDisplayName(entry: NetworkEntry): string { + if (entry.type === "custom") { + return `${entry.alias} (custom)`; + } + return entry.name; + } + + private readCustomNetworks(): CustomNetworksConfig { + return normalizeCustomNetworks(this.getConfigByKey(CUSTOM_NETWORKS_CONFIG_KEY)); } } diff --git a/src/commands/staking/StakingAction.ts b/src/commands/staking/StakingAction.ts index 5eb6b067..ab78e8c9 100644 --- a/src/commands/staking/StakingAction.ts +++ b/src/commands/staking/StakingAction.ts @@ -52,14 +52,10 @@ export class StakingAction extends BaseAction { private getNetwork(config: StakingConfig): GenLayerChain { // Priority: --network option > global config > localnet default if (config.network) { - const network = BUILT_IN_NETWORKS[config.network]; - if (!network) { - throw new Error(`Unknown network: ${config.network}. Available: ${Object.keys(BUILT_IN_NETWORKS).join(", ")}`); - } - return {...network}; + return {...resolveNetwork(config.network, this.getCustomNetworks())}; } - return resolveNetwork(this.getConfig().network); + return resolveNetwork(this.getConfig().network, this.getCustomNetworks()); } protected async getStakingClient(config: StakingConfig): Promise> { diff --git a/src/commands/staking/index.ts b/src/commands/staking/index.ts index c2252f30..b5b097eb 100644 --- a/src/commands/staking/index.ts +++ b/src/commands/staking/index.ts @@ -40,7 +40,7 @@ export function initializeStakingCommands(program: Command) { .option("--operator
", "Operator address (defaults to signer)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: ValidatorJoinOptions) => { @@ -55,7 +55,7 @@ export function initializeStakingCommands(program: Command) { .requiredOption("--amount ", "Amount to deposit (in wei or with 'eth'/'gen' suffix)") .option("--account ", "Account to use (must be validator owner)") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .action(async (validatorArg: string | undefined, options: ValidatorDepositOptions) => { const validator = validatorArg || options.validator; @@ -74,7 +74,7 @@ export function initializeStakingCommands(program: Command) { .requiredOption("--shares ", "Number of shares to withdraw") .option("--account ", "Account to use (must be validator owner)") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .action(async (validatorArg: string | undefined, options: ValidatorExitOptions) => { const validator = validatorArg || options.validator; @@ -92,7 +92,7 @@ export function initializeStakingCommands(program: Command) { .option("--validator
", "Validator wallet contract address (deprecated, use positional arg)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .action(async (validatorArg: string | undefined, options: ValidatorClaimOptions) => { const validator = validatorArg || options.validator; @@ -110,7 +110,7 @@ export function initializeStakingCommands(program: Command) { .option("--validator
", "Validator address to prime (deprecated, use positional arg)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: ValidatorPrimeOptions) => { @@ -128,7 +128,7 @@ export function initializeStakingCommands(program: Command) { .description("Prime all validators that need priming") .option("--account ", "Account to use (pays gas)") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: StakingConfig) => { @@ -143,7 +143,7 @@ export function initializeStakingCommands(program: Command) { .option("--operator
", "New operator address (deprecated, use positional arg)") .option("--account ", "Account to use (must be validator owner)") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .action(async (validatorArg: string | undefined, operatorArg: string | undefined, options: SetOperatorOptions) => { const validator = validatorArg || options.validator; @@ -171,7 +171,7 @@ export function initializeStakingCommands(program: Command) { .option("--extra-cid ", "Extra data as IPFS CID or hex bytes (0x...)") .option("--account ", "Account to use (must be validator operator)") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .action(async (validatorArg: string | undefined, options: SetIdentityOptions) => { const validator = validatorArg || options.validator; @@ -191,7 +191,7 @@ export function initializeStakingCommands(program: Command) { .requiredOption("--amount ", "Amount to stake (in wei or with 'eth'/'gen' suffix)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: DelegatorJoinOptions) => { @@ -211,7 +211,7 @@ export function initializeStakingCommands(program: Command) { .requiredOption("--shares ", "Number of shares to withdraw") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: DelegatorExitOptions) => { @@ -231,7 +231,7 @@ export function initializeStakingCommands(program: Command) { .option("--delegator
", "Delegator address (defaults to signer)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: DelegatorClaimOptions) => { @@ -250,7 +250,7 @@ export function initializeStakingCommands(program: Command) { .description("Get information about a validator") .option("--validator
", "Validator address (deprecated, use positional arg)") .option("--account ", "Account to use (for default validator address)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .option("--debug", "Show raw unfiltered pending deposits/withdrawals") @@ -266,7 +266,7 @@ export function initializeStakingCommands(program: Command) { .option("--validator
", "Validator address (deprecated, use positional arg)") .option("--delegator
", "Delegator address (defaults to signer)") .option("--account ", "Account to use (for default delegator address)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: StakingInfoOptions & {delegator?: string}) => { @@ -283,7 +283,7 @@ export function initializeStakingCommands(program: Command) { .command("epoch-info") .description("Get current epoch and staking parameters") .option("--epoch ", "Show data for specific epoch (current or previous only)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: StakingInfoOptions & {epoch?: string}) => { @@ -294,7 +294,7 @@ export function initializeStakingCommands(program: Command) { staking .command("active-validators") .description("List all active validators") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: StakingInfoOptions) => { @@ -305,7 +305,7 @@ export function initializeStakingCommands(program: Command) { staking .command("quarantined-validators") .description("List all quarantined validators") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: StakingInfoOptions) => { @@ -316,7 +316,7 @@ export function initializeStakingCommands(program: Command) { staking .command("banned-validators") .description("List all banned validators") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: StakingInfoOptions) => { @@ -331,7 +331,7 @@ export function initializeStakingCommands(program: Command) { .option("--json", "Output machine-readable JSON") .option("--sort-by ", "Sort validators by stake or uptime (default: stake)", "stake") .option("--explorer-url ", "Explorer backend or explorer base URL for optional performance enrichment") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (options: ValidatorsOptions) => { @@ -349,7 +349,7 @@ export function initializeStakingCommands(program: Command) { .option("--all", "Fetch complete history from genesis (slow)") .option("--limit ", "Maximum number of events to show (default: 50)") .option("--account ", "Account to use (for default validator address)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--staking-address
", "Staking contract address (overrides chain config)") .action(async (validatorArg: string | undefined, options: ValidatorHistoryOptions) => { diff --git a/src/commands/staking/validatorHistory.ts b/src/commands/staking/validatorHistory.ts index 6858daf5..aed86ad3 100644 --- a/src/commands/staking/validatorHistory.ts +++ b/src/commands/staking/validatorHistory.ts @@ -1,4 +1,5 @@ -import {StakingAction, StakingConfig, BUILT_IN_NETWORKS} from "./StakingAction"; +import {resolveNetwork} from "../../lib/actions/BaseAction"; +import {StakingAction, StakingConfig} from "./StakingAction"; import type {Address, GenLayerChain} from "genlayer-js/types"; import {createPublicClient, http, getContract} from "viem"; import Table from "cli-table3"; @@ -65,18 +66,11 @@ export class ValidatorHistoryAction extends StakingAction { private getNetworkForHistory(config: StakingConfig): GenLayerChain { if (config.network) { - const network = BUILT_IN_NETWORKS[config.network]; - if (!network) { - throw new Error(`Unknown network: ${config.network}`); - } - return network; + return resolveNetwork(config.network, this.getCustomNetworks()); } // Check global config const globalNetwork = this.getConfig().network; - if (globalNetwork && BUILT_IN_NETWORKS[globalNetwork]) { - return BUILT_IN_NETWORKS[globalNetwork]; - } - return BUILT_IN_NETWORKS["localnet"]; + return resolveNetwork(globalNetwork, this.getCustomNetworks()); } async execute(options: ValidatorHistoryOptions): Promise { diff --git a/src/commands/staking/validators.ts b/src/commands/staking/validators.ts index cfd8617e..e1da318e 100644 --- a/src/commands/staking/validators.ts +++ b/src/commands/staking/validators.ts @@ -1,5 +1,5 @@ import {resolveNetwork} from "../../lib/actions/BaseAction"; -import {StakingAction, StakingConfig, BUILT_IN_NETWORKS} from "./StakingAction"; +import {StakingAction, StakingConfig} from "./StakingAction"; import type {Address, GenLayerChain, ValidatorInfo} from "genlayer-js/types"; import Table from "cli-table3"; import chalk from "chalk"; @@ -318,14 +318,10 @@ export class ValidatorsAction extends StakingAction { private resolveExplorerNetwork(config: StakingConfig): GenLayerChain { if (config.network) { - const network = BUILT_IN_NETWORKS[config.network]; - if (!network) { - throw new Error(`Unknown network: ${config.network}. Available: ${Object.keys(BUILT_IN_NETWORKS).join(", ")}`); - } - return network; + return resolveNetwork(config.network, this.getCustomNetworks()); } - return resolveNetwork(this.getConfig().network); + return resolveNetwork(this.getConfig().network, this.getCustomNetworks()); } private getDefaultExplorerUrl(options: ValidatorsOptions): string | undefined { diff --git a/src/commands/staking/wizard.ts b/src/commands/staking/wizard.ts index 00452588..d7137b8a 100644 --- a/src/commands/staking/wizard.ts +++ b/src/commands/staking/wizard.ts @@ -1,4 +1,5 @@ import {StakingAction, StakingConfig, BUILT_IN_NETWORKS} from "./StakingAction"; +import {resolveNetwork} from "../../lib/actions/BaseAction"; import {CreateAccountAction} from "../account/create"; import {ExportAccountAction} from "../account/export"; import inquirer from "inquirer"; @@ -187,10 +188,7 @@ export class ValidatorWizardAction extends StakingAction { console.log("-------------------------\n"); if (options.network) { - const network = BUILT_IN_NETWORKS[options.network]; - if (!network) { - this.failSpinner(`Unknown network: ${options.network}`); - } + const network = resolveNetwork(options.network, this.getCustomNetworks()); state.networkAlias = options.network; this.writeConfig("network", options.network); console.log(`Using network: ${network.name}\n`); @@ -228,7 +226,7 @@ export class ValidatorWizardAction extends StakingAction { this.startSpinner("Checking balance and staking requirements..."); - const network = BUILT_IN_NETWORKS[state.networkAlias!]; + const network = resolveNetwork(state.networkAlias!, this.getCustomNetworks()); const client = createClient({ chain: network, account: state.accountAddress as Address, @@ -780,7 +778,7 @@ export class ValidatorWizardAction extends StakingAction { } console.log(` Staked Amount: ${state.stakeAmount}`); - console.log(` Network: ${BUILT_IN_NETWORKS[state.networkAlias].name}`); + console.log(` Network: ${resolveNetwork(state.networkAlias, this.getCustomNetworks()).name}`); if (state.identity) { console.log(` Identity:`); diff --git a/src/commands/vesting/VestingAction.ts b/src/commands/vesting/VestingAction.ts index ba0d1414..8e1627d0 100644 --- a/src/commands/vesting/VestingAction.ts +++ b/src/commands/vesting/VestingAction.ts @@ -27,14 +27,10 @@ export class VestingAction extends BaseAction { private getNetwork(config: VestingConfig): GenLayerChain { if (config.network) { - const network = BUILT_IN_NETWORKS[config.network]; - if (!network) { - throw new Error(`Unknown network: ${config.network}. Available: ${Object.keys(BUILT_IN_NETWORKS).join(", ")}`); - } - return {...network}; + return {...resolveNetwork(config.network, this.getCustomNetworks())}; } - return resolveNetwork(this.getConfig().network); + return resolveNetwork(this.getConfig().network, this.getCustomNetworks()); } protected async getVestingClient(config: VestingConfig): Promise { diff --git a/src/commands/vesting/index.ts b/src/commands/vesting/index.ts index 25aba95c..c73ef39f 100644 --- a/src/commands/vesting/index.ts +++ b/src/commands/vesting/index.ts @@ -20,7 +20,7 @@ import {VestingValidatorListAction, VestingValidatorListOptions} from "./validat function addReadOptions(command: Command): Command { return command .option("--account ", "Account to use (for default beneficiary address)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--factory
", "VestingFactory address (overrides AddressManager lookup)") .option("--address-manager
", "AddressManager address (overrides consensus lookup)"); @@ -31,7 +31,7 @@ function addWriteOptions(command: Command): Command { .option("--vesting
", "Vesting contract address (overrides beneficiary lookup)") .option("--account ", "Account to use") .option("--password ", "Password to unlock account (skips interactive prompt)") - .option("--network ", "Network to use (localnet, testnet-asimov)") + .option("--network ", "built-in or custom network alias (see: genlayer network list)") .option("--rpc ", "RPC URL for the network") .option("--factory
", "VestingFactory address (overrides AddressManager lookup)") .option("--address-manager
", "AddressManager address (overrides consensus lookup)"); diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index d555a6dd..92a0d665 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -7,6 +7,12 @@ import { inspect } from "util"; import {createClient, createAccount} from "genlayer-js"; import {localnet, studionet, testnetAsimov, testnetBradbury} from "genlayer-js/chains"; import type {GenLayerClient, GenLayerChain, Hash, Address, Account} from "genlayer-js/types"; +import { + applyCustomNetworkProfile, + CUSTOM_NETWORKS_CONFIG_KEY, + normalizeCustomNetworks, + type CustomNetworksConfig, +} from "../networks/customNetworks"; // Built-in networks - always resolve fresh from genlayer-js export const BUILT_IN_NETWORKS: Record = { @@ -20,7 +26,7 @@ export const BUILT_IN_NETWORKS: Record = { * Resolves a stored network config to a fresh chain object. * Handles both new format (alias string) and old format (JSON object) for backwards compat. */ -export function resolveNetwork(stored: string | undefined): GenLayerChain { +export function resolveNetwork(stored: string | undefined, customNetworks?: CustomNetworksConfig): GenLayerChain { if (!stored) return localnet; // Try as alias first (new format) @@ -28,6 +34,15 @@ export function resolveNetwork(stored: string | undefined): GenLayerChain { return BUILT_IN_NETWORKS[stored]; } + const customNetwork = customNetworks?.[stored]; + if (customNetwork) { + const baseNetwork = BUILT_IN_NETWORKS[customNetwork.base]; + if (!baseNetwork) { + throw new Error(`Custom network ${stored} references unknown base network: ${customNetwork.base}`); + } + return applyCustomNetworkProfile(baseNetwork, customNetwork); + } + // Backwards compat: try parsing as JSON (old format) try { const parsed = JSON.parse(stored); @@ -62,6 +77,10 @@ export class BaseAction extends ConfigFileManager { this.keychainManager = new KeychainManager(); } + protected getCustomNetworks(): CustomNetworksConfig { + return normalizeCustomNetworks(this.getConfigByKey(CUSTOM_NETWORKS_CONFIG_KEY)); + } + private async decryptKeystore(keystoreJson: string, attempt: number = 1): Promise { try { const message = attempt === 1 @@ -97,7 +116,7 @@ export class BaseAction extends ConfigFileManager { protected async getClient(rpcUrl?: string, readOnly: boolean = false): Promise> { if (!this._genlayerClient) { - const network = resolveNetwork(this.getConfig().network); + const network = resolveNetwork(this.getConfig().network, this.getCustomNetworks()); const account = await this.getAccount(readOnly); this._genlayerClient = createClient({ chain: network, @@ -298,4 +317,4 @@ export class BaseAction extends ConfigFileManager { protected setSpinnerText(message: string): void { this.spinner.text = chalk.blue(message); } -} \ No newline at end of file +} diff --git a/src/lib/networks/customNetworks.ts b/src/lib/networks/customNetworks.ts new file mode 100644 index 00000000..d3f815fb --- /dev/null +++ b/src/lib/networks/customNetworks.ts @@ -0,0 +1,253 @@ +import {readFileSync} from "fs"; +import type {Address, GenLayerChain} from "genlayer-js/types"; + +export const CUSTOM_NETWORKS_CONFIG_KEY = "customNetworks"; +export const ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/; + +export type ContractOverrideKey = + | "consensusMain" + | "consensusData" + | "staking" + | "feeManager" + | "roundsStorage" + | "appeals"; + +export interface CustomNetworkOverrides { + rpcUrl?: string; + chainId?: number; + consensusMain?: Address; + consensusData?: Address; + staking?: Address; + feeManager?: Address; + roundsStorage?: Address; + appeals?: Address; +} + +export interface CustomNetworkProfile { + base: string; + overrides: CustomNetworkOverrides; +} + +export type CustomNetworksConfig = Record; + +export const CONTRACT_OVERRIDES: Array<{ + overrideKey: ContractOverrideKey; + chainField: keyof GenLayerChain; + label: string; +}> = [ + {overrideKey: "consensusMain", chainField: "consensusMainContract", label: "consensusMain"}, + {overrideKey: "consensusData", chainField: "consensusDataContract", label: "consensusData"}, + {overrideKey: "staking", chainField: "stakingContract", label: "staking"}, + {overrideKey: "feeManager", chainField: "feeManagerContract", label: "feeManager"}, + {overrideKey: "roundsStorage", chainField: "roundsStorageContract", label: "roundsStorage"}, + {overrideKey: "appeals", chainField: "appealsContract", label: "appeals"}, +]; + +const DEPLOYMENT_KEY_TO_OVERRIDE: Record = { + ConsensusMain: "consensusMain", + ConsensusData: "consensusData", + GenStaking: "staking", + Staking: "staking", + FeeManager: "feeManager", + Rounds: "roundsStorage", + RoundsStorage: "roundsStorage", + Appeals: "appeals", +}; + +const CONSENSUS_MAIN_WITH_FEES = "ConsensusMainWithFees"; +const SCANNED_DEPLOYMENT_KEYS = new Set([ + ...Object.keys(DEPLOYMENT_KEY_TO_OVERRIDE), + CONSENSUS_MAIN_WITH_FEES, +]); + +interface FoundDeploymentAddress { + path: string; + address: string; +} + +export interface ParsedDeploymentOverrides { + overrides: Partial>; + notices: string[]; +} + +export function normalizeCustomNetworks(value: unknown): CustomNetworksConfig { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return {}; + } + return value as CustomNetworksConfig; +} + +export function isValidAddress(value: string): value is Address { + return ADDRESS_REGEX.test(value); +} + +export function parseDeploymentFile(filePath: string, deploymentKey?: string): ParsedDeploymentOverrides { + const content = readFileSync(filePath, "utf-8"); + const parsed = JSON.parse(content); + return parseDeploymentObject(parsed, deploymentKey); +} + +export function parseDeploymentObject(input: unknown, deploymentKey?: string): ParsedDeploymentOverrides { + const selected = deploymentKey ? selectDeploymentObject(input, deploymentKey) : input; + if (!selected || typeof selected !== "object" || Array.isArray(selected)) { + throw new Error("Deployment selection must be a JSON object"); + } + + const found: Record = {}; + walkDeploymentObject(selected, [], found); + + for (const [contractName, entries] of Object.entries(found)) { + if (entries.length > 1) { + const paths = entries.map(entry => entry.path).join(", "); + throw new Error( + `Ambiguous ${contractName} entries found at ${paths}. ` + + "Pass --deployment-key to select one deployment.", + ); + } + } + + const overrides: ParsedDeploymentOverrides["overrides"] = {}; + const fieldSources: Partial> = {}; + + for (const [contractName, entries] of Object.entries(found)) { + const entry = entries[0]; + if (!entry) continue; + if (!isValidAddress(entry.address)) { + throw new Error(`Invalid address for ${contractName} at ${entry.path}: ${entry.address}`); + } + if (contractName === CONSENSUS_MAIN_WITH_FEES) { + continue; + } + + const overrideKey = DEPLOYMENT_KEY_TO_OVERRIDE[contractName]; + if (!overrideKey) continue; + if (fieldSources[overrideKey]) { + throw new Error( + `Multiple deployment entries map to ${overrideKey}: ${fieldSources[overrideKey]} and ${contractName}. ` + + `Use an explicit --${toFlagName(overrideKey)} override.`, + ); + } + overrides[overrideKey] = entry.address as Address; + fieldSources[overrideKey] = contractName; + } + + const notices: string[] = []; + if (found.ConsensusMain?.length && found[CONSENSUS_MAIN_WITH_FEES]?.length) { + notices.push( + "ConsensusMainWithFees exists in the deployment file; using ConsensusMain. " + + "Use --consensus-main to choose ConsensusMainWithFees.", + ); + } + + return {overrides, notices}; +} + +export function applyCustomNetworkProfile( + baseChain: GenLayerChain, + profile: CustomNetworkProfile, +): GenLayerChain { + const chain = cloneChain(baseChain); + const overrides = profile.overrides || {}; + + if (overrides.chainId !== undefined) { + (chain as any).id = overrides.chainId; + } + + if (overrides.rpcUrl) { + const rpcUrls = (chain.rpcUrls || {}) as any; + const defaultRpc = rpcUrls.default || {}; + const currentHttp = Array.isArray(defaultRpc.http) ? defaultRpc.http : []; + (chain as any).rpcUrls = { + ...rpcUrls, + default: { + ...defaultRpc, + http: [overrides.rpcUrl, ...currentHttp.slice(1)], + }, + }; + } + + for (const contract of CONTRACT_OVERRIDES) { + const address = overrides[contract.overrideKey]; + if (!address) continue; + const current = (chain as any)[contract.chainField]; + (chain as any)[contract.chainField] = { + ...(current || {}), + address, + }; + } + + return chain; +} + +export function getContractAddress(chain: GenLayerChain, overrideKey: ContractOverrideKey): string | undefined { + const contract = CONTRACT_OVERRIDES.find(item => item.overrideKey === overrideKey); + if (!contract) return undefined; + return (chain as any)[contract.chainField]?.address; +} + +function selectDeploymentObject(input: unknown, deploymentKey: string): unknown { + const segments = deploymentKey.split(".").filter(Boolean); + if (!segments.length) { + throw new Error("--deployment-key must not be empty"); + } + + let selected = input as any; + for (const segment of segments) { + if (!selected || typeof selected !== "object" || Array.isArray(selected) || !(segment in selected)) { + throw new Error(`Deployment key not found: ${deploymentKey}`); + } + selected = selected[segment]; + } + + return selected; +} + +function walkDeploymentObject( + node: Record, + path: string[], + found: Record, +): void { + for (const [key, value] of Object.entries(node)) { + const nextPath = [...path, key]; + if (typeof value === "string" && SCANNED_DEPLOYMENT_KEYS.has(key)) { + if (!found[key]) found[key] = []; + found[key].push({path: nextPath.join("."), address: value}); + continue; + } + if (value && typeof value === "object" && !Array.isArray(value)) { + walkDeploymentObject(value as Record, nextPath, found); + } + } +} + +function cloneChain(baseChain: GenLayerChain): GenLayerChain { + const chain = {...baseChain} as any; + + if (baseChain.rpcUrls) { + chain.rpcUrls = {}; + for (const [key, value] of Object.entries(baseChain.rpcUrls as any)) { + chain.rpcUrls[key] = value && typeof value === "object" + ? { + ...value, + http: Array.isArray((value as any).http) ? [...(value as any).http] : (value as any).http, + } + : value; + } + } + + for (const contract of CONTRACT_OVERRIDES) { + const current = (baseChain as any)[contract.chainField]; + if (current) { + chain[contract.chainField] = {...current}; + } + } + + return chain as GenLayerChain; +} + +function toFlagName(overrideKey: ContractOverrideKey): string { + return overrideKey.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`); +} diff --git a/tests/actions/customNetworkProfiles.test.ts b/tests/actions/customNetworkProfiles.test.ts new file mode 100644 index 00000000..a9e6f0f8 --- /dev/null +++ b/tests/actions/customNetworkProfiles.test.ts @@ -0,0 +1,297 @@ +import {afterEach, beforeEach, describe, expect, test, vi} from "vitest"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import {tmpdir} from "os"; +import {NetworkActions} from "../../src/commands/network/setNetwork"; +import {resolveNetwork} from "../../src/lib/actions/BaseAction"; +import {parseDeploymentObject} from "../../src/lib/networks/customNetworks"; +import {testnetBradbury} from "genlayer-js/chains"; +import {StakingAction} from "../../src/commands/staking/StakingAction"; + +const ADDR_1 = "0x1111111111111111111111111111111111111111"; +const ADDR_2 = "0x2222222222222222222222222222222222222222"; +const ADDR_3 = "0x3333333333333333333333333333333333333333"; +const ADDR_4 = "0x4444444444444444444444444444444444444444"; +const ADDR_5 = "0x5555555555555555555555555555555555555555"; +const ADDR_6 = "0x6666666666666666666666666666666666666666"; +const ADDR_A = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + +describe("custom network profiles", () => { + let tempHome: string; + let action: NetworkActions; + let succeedSpy: any; + let failSpy: any; + let warningSpy: any; + let infoSpy: any; + let consoleSpy: any; + + beforeEach(() => { + tempHome = fs.mkdtempSync(path.join(tmpdir(), "genlayer-custom-network-")); + vi.spyOn(os, "homedir").mockReturnValue(tempHome); + action = new NetworkActions(); + succeedSpy = vi.spyOn(action as any, "succeedSpinner").mockImplementation(() => {}); + failSpy = vi.spyOn(action as any, "failSpinner").mockImplementation(() => {}); + warningSpy = vi.spyOn(action as any, "logWarning").mockImplementation(() => {}); + infoSpy = vi.spyOn(action as any, "logInfo").mockImplementation(() => {}); + consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + fs.rmSync(tempHome, {recursive: true, force: true}); + }); + + test("network add stores flags-only overrides", async () => { + await action.addNetwork("bradbury-clarke", { + base: "testnet-bradbury", + rpc: "http://localhost:9999", + consensusMain: ADDR_1, + chainId: "4222", + }); + + expect(failSpy).not.toHaveBeenCalled(); + expect(readConfig().customNetworks["bradbury-clarke"]).toEqual({ + base: "testnet-bradbury", + overrides: { + consensusMain: ADDR_1, + rpcUrl: "http://localhost:9999", + chainId: 4222, + }, + }); + expect(succeedSpy).toHaveBeenCalledWith( + "Custom network profile added", + expect.objectContaining({ + alias: "bradbury-clarke", + base: "testnet-bradbury", + consensusMain: `${ADDR_1} (overridden)`, + rpc: "http://localhost:9999 (overridden)", + }), + ); + }); + + test("network add sources overrides from a deployment file", async () => { + const deploymentPath = writeDeployment({ + genlayerTestnet: { + deployment_x: { + ConsensusMain: ADDR_1, + ConsensusData: ADDR_2, + GenStaking: ADDR_3, + FeeManager: ADDR_4, + Rounds: ADDR_5, + Appeals: ADDR_6, + }, + }, + }); + + await action.addNetwork("bradbury-deployment", { + base: "testnet-bradbury", + deployment: deploymentPath, + }); + + expect(failSpy).not.toHaveBeenCalled(); + expect(readConfig().customNetworks["bradbury-deployment"].overrides).toEqual({ + consensusMain: ADDR_1, + consensusData: ADDR_2, + staking: ADDR_3, + feeManager: ADDR_4, + roundsStorage: ADDR_5, + appeals: ADDR_6, + }); + }); + + test("network add gives address flags precedence over deployment file", async () => { + const deploymentPath = writeDeployment({ + deployment_x: { + ConsensusMain: ADDR_1, + ConsensusData: ADDR_2, + }, + }); + + await action.addNetwork("bradbury-precedence", { + base: "testnet-bradbury", + deployment: deploymentPath, + consensusMain: ADDR_A, + }); + + expect(failSpy).not.toHaveBeenCalled(); + expect(readConfig().customNetworks["bradbury-precedence"].overrides).toEqual({ + consensusMain: ADDR_A, + consensusData: ADDR_2, + }); + }); + + test("network add validates base, alias, addresses, and override presence", async () => { + await action.addNetwork("localnet", {base: "testnet-bradbury", rpc: "http://localhost:9999"}); + await action.addNetwork("bad-base", {base: "missing", rpc: "http://localhost:9999"}); + await action.addNetwork("bad-address", {base: "testnet-bradbury", consensusMain: "0x123"}); + await action.addNetwork("empty", {base: "testnet-bradbury"}); + + expect(failSpy).toHaveBeenNthCalledWith( + 1, + "Failed to add custom network profile", + "Custom network alias cannot collide with built-in network: localnet", + ); + expect(failSpy).toHaveBeenNthCalledWith( + 2, + "Failed to add custom network profile", + "Base network must be one of: localnet, studionet, testnet-asimov, testnet-bradbury", + ); + expect(failSpy).toHaveBeenNthCalledWith( + 3, + "Failed to add custom network profile", + "Invalid address for --consensus-main: 0x123", + ); + expect(failSpy).toHaveBeenNthCalledWith( + 4, + "Failed to add custom network profile", + "Provide at least one override: --deployment, --rpc, --chain-id, or a contract address flag", + ); + }); + + test("network add rejects ambiguous deployment contract names", async () => { + const deploymentPath = writeDeployment({ + net_a: {deployment: {ConsensusMain: ADDR_1}}, + net_b: {deployment: {ConsensusMain: ADDR_2}}, + }); + + await action.addNetwork("ambiguous", { + base: "testnet-bradbury", + deployment: deploymentPath, + }); + + expect(failSpy).toHaveBeenCalledWith( + "Failed to add custom network profile", + expect.stringContaining("Pass --deployment-key "), + ); + }); + + test("network add supports --deployment-key", async () => { + const deploymentPath = writeDeployment({ + net_a: {deployment: {ConsensusMain: ADDR_1}}, + net_b: {deployment: {ConsensusMain: ADDR_2}}, + }); + + await action.addNetwork("keyed", { + base: "testnet-bradbury", + deployment: deploymentPath, + deploymentKey: "net_b.deployment", + }); + + expect(failSpy).not.toHaveBeenCalled(); + expect(readConfig().customNetworks.keyed.overrides).toEqual({ + consensusMain: ADDR_2, + }); + }); + + test("network set, list, info, and remove handle custom profiles", async () => { + await action.addNetwork("bradbury-clarke", { + base: "testnet-bradbury", + rpc: "http://localhost:9999", + consensusMain: ADDR_1, + }); + succeedSpy.mockClear(); + + await action.setNetwork("bradbury-clarke"); + expect(readConfig().network).toBe("bradbury-clarke"); + expect(succeedSpy).toHaveBeenCalledWith("Network successfully set to bradbury-clarke (custom)"); + + await action.listNetworks(); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("bradbury-clarke")); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("custom base: testnet-bradbury")); + + succeedSpy.mockClear(); + await action.showInfo(); + expect(succeedSpy).toHaveBeenCalledWith( + "Current network", + expect.objectContaining({ + alias: "bradbury-clarke", + type: "custom", + base: "testnet-bradbury", + rpc: "http://localhost:9999 (overridden)", + consensusMain: `${ADDR_1} (overridden)`, + consensusData: expect.stringContaining("(inherited)"), + }), + ); + + await action.removeNetwork("bradbury-clarke"); + expect(warningSpy).toHaveBeenCalledWith("Removed active network bradbury-clarke; active network reset to localnet."); + expect(readConfig().network).toBe("localnet"); + expect(readConfig().customNetworks["bradbury-clarke"]).toBeUndefined(); + }); + + test("network remove refuses built-ins", async () => { + await action.removeNetwork("localnet"); + + expect(failSpy).toHaveBeenCalledWith( + "Failed to remove custom network profile", + "Cannot remove built-in network: localnet", + ); + }); + + test("deployment parser notices ConsensusMainWithFees when ConsensusMain is present", () => { + const parsed = parseDeploymentObject({ + deployment: { + ConsensusMain: ADDR_1, + ConsensusMainWithFees: ADDR_2, + }, + }); + + expect(parsed.overrides.consensusMain).toBe(ADDR_1); + expect(parsed.notices[0]).toContain("ConsensusMainWithFees exists"); + }); + + test("resolveNetwork applies custom overrides while retaining base ABI objects", () => { + const resolved = resolveNetwork("bradbury-clarke", { + "bradbury-clarke": { + base: "testnet-bradbury", + overrides: { + consensusMain: ADDR_1, + rpcUrl: "http://localhost:9999", + chainId: 4222, + }, + }, + }); + + const resolvedChain = resolved as any; + const baseChain = testnetBradbury as any; + expect(resolved).not.toBe(testnetBradbury); + expect(resolvedChain.id).toBe(4222); + expect(resolvedChain.rpcUrls.default.http[0]).toBe("http://localhost:9999"); + expect(resolvedChain.consensusMainContract.address).toBe(ADDR_1); + expect(resolvedChain.consensusMainContract.abi).toBe(baseChain.consensusMainContract.abi); + expect(resolvedChain.consensusDataContract.abi).toBe(baseChain.consensusDataContract.abi); + }); + + test("StakingAction.getNetwork accepts a custom alias", () => { + const stakingAction = new StakingAction(); + vi.spyOn(stakingAction as any, "getConfigByKey").mockImplementation((key: string) => { + if (key === "customNetworks") { + return { + "bradbury-clarke": { + base: "testnet-bradbury", + overrides: { + staking: ADDR_3, + }, + }, + }; + } + return null; + }); + + const network = (stakingAction as any).getNetwork({network: "bradbury-clarke"}); + + expect(network.stakingContract.address).toBe(ADDR_3); + expect(network.stakingContract.abi).toBe((testnetBradbury as any).stakingContract.abi); + }); + + function writeDeployment(content: unknown): string { + const deploymentPath = path.join(tempHome, "deployment.json"); + fs.writeFileSync(deploymentPath, JSON.stringify(content, null, 2)); + return deploymentPath; + } + + function readConfig(): Record { + return JSON.parse(fs.readFileSync(path.join(tempHome, ".genlayer", "genlayer-config.json"), "utf-8")); + } +}); diff --git a/tests/commands/network.test.ts b/tests/commands/network.test.ts index b738071f..90e4b8b4 100644 --- a/tests/commands/network.test.ts +++ b/tests/commands/network.test.ts @@ -57,4 +57,60 @@ describe("network commands", () => { expect(NetworkActions).toHaveBeenCalledTimes(1); expect(NetworkActions.prototype.showInfo).toHaveBeenCalled(); }); + + test("NetworkActions.addNetwork is called with add options", async () => { + program.parse([ + "node", + "test", + "network", + "add", + "bradbury-clarke", + "--base", + "testnet-bradbury", + "--deployment", + "/tmp/dep.json", + "--deployment-key", + "genlayerTestnet.deployment_x", + "--rpc", + "http://localhost:9999", + "--consensus-main", + "0x1111111111111111111111111111111111111111", + "--consensus-data", + "0x2222222222222222222222222222222222222222", + "--staking", + "0x3333333333333333333333333333333333333333", + "--fee-manager", + "0x4444444444444444444444444444444444444444", + "--rounds-storage", + "0x5555555555555555555555555555555555555555", + "--appeals", + "0x6666666666666666666666666666666666666666", + "--chain-id", + "4222", + ]); + + expect(NetworkActions).toHaveBeenCalledTimes(1); + expect(NetworkActions.prototype.addNetwork).toHaveBeenCalledWith( + "bradbury-clarke", + expect.objectContaining({ + base: "testnet-bradbury", + deployment: "/tmp/dep.json", + deploymentKey: "genlayerTestnet.deployment_x", + rpc: "http://localhost:9999", + consensusMain: "0x1111111111111111111111111111111111111111", + consensusData: "0x2222222222222222222222222222222222222222", + staking: "0x3333333333333333333333333333333333333333", + feeManager: "0x4444444444444444444444444444444444444444", + roundsStorage: "0x5555555555555555555555555555555555555555", + appeals: "0x6666666666666666666666666666666666666666", + chainId: "4222", + }), + ); + }); + + test("NetworkActions.removeNetwork is called for network remove", async () => { + program.parse(["node", "test", "network", "remove", "bradbury-clarke"]); + expect(NetworkActions).toHaveBeenCalledTimes(1); + expect(NetworkActions.prototype.removeNetwork).toHaveBeenCalledWith("bradbury-clarke"); + }); }); From dbb7b122e3a4354c04943f4844a8c7d0379fd4a7 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 8 Jul 2026 10:53:25 +0100 Subject: [PATCH 2/4] chore(deps): bump genlayer-js to v2-dev tip for vesting actions The locked v2-dev SHA (28e99fbc) predates the vesting client actions; vestingValidatorJoin and friends land at 666d1156. Verified live: vesting validator create succeeds against a #1162-branch consensus deployment. --- package-lock.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 25a9539d..a09354be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -325,6 +325,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -348,6 +349,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1646,6 +1648,7 @@ "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -2159,6 +2162,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2281,6 +2285,7 @@ "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", @@ -2917,6 +2922,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5582,7 +5588,7 @@ }, "node_modules/genlayer-js": { "version": "1.1.8", - "resolved": "git+ssh://git@github.com/genlayerlabs/genlayer-js.git#28e99fbc10ad962019e8314fb1b0d83d5a0e0938", + "resolved": "git+ssh://git@github.com/genlayerlabs/genlayer-js.git#666d1156c3c136bf79916c53b9764ff3a2a87a5c", "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.30.0", @@ -6821,6 +6827,7 @@ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -6849,6 +6856,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -8480,6 +8488,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@nodeutils/defaults-deep": "1.1.0", "@octokit/rest": "21.1.1", @@ -9514,6 +9523,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9811,6 +9821,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9909,6 +9920,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -10086,6 +10098,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -10199,6 +10212,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10650,6 +10664,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, From c566514eda91a2e5f5bcc9953335c2ce11322c77 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 8 Jul 2026 14:54:52 +0100 Subject: [PATCH 3/4] docs(cli): regenerate API references; fix option placeholder regex The docs generator's option regex only matched placeholders, so flags with dots or hyphens in the value name (--base , --deployment , --deployment-key ) were silently dropped from the options tables. Widen to <[^>]+> and regenerate: adds the network add/remove pages and the previously undocumented vesting command section (validator create/deposit/exit/claim, operator-transfer, set-identity, delegate/undelegate/claim/withdraw/list). --- docs/api-references/_meta.json | 3 +- docs/api-references/accounts/account/send.mdx | 2 +- docs/api-references/configuration/network.mdx | 4 ++- .../configuration/network/add.mdx | 32 +++++++++++++++++ .../configuration/network/remove.mdx | 21 +++++++++++ docs/api-references/contracts/call.mdx | 1 + docs/api-references/contracts/deploy.mdx | 23 ++++++------ docs/api-references/contracts/write.mdx | 21 +++++------ docs/api-references/estimate-fees.mdx | 21 +++++------ docs/api-references/index.mdx | 1 + .../localnet/validators/create-random.mdx | 4 ++- docs/api-references/staking/staking.mdx | 2 +- .../staking/staking/active-validators.mdx | 2 +- .../staking/staking/banned-validators.mdx | 2 +- .../staking/staking/delegation-info.mdx | 2 +- .../staking/staking/delegator-claim.mdx | 2 +- .../staking/staking/delegator-exit.mdx | 2 +- .../staking/staking/delegator-join.mdx | 2 +- .../staking/staking/epoch-info.mdx | 2 +- .../staking/staking/prime-all.mdx | 2 +- .../staking/quarantined-validators.mdx | 2 +- .../staking/staking/set-identity.mdx | 2 +- .../staking/staking/set-operator.mdx | 2 +- .../staking/staking/validator-claim.mdx | 2 +- .../staking/staking/validator-deposit.mdx | 2 +- .../staking/staking/validator-exit.mdx | 2 +- .../staking/staking/validator-history.mdx | 2 +- .../staking/staking/validator-info.mdx | 2 +- .../staking/staking/validator-join.mdx | 2 +- .../staking/staking/validator-prime.mdx | 2 +- .../staking/staking/validators.mdx | 7 ++-- docs/api-references/vesting.mdx | 28 +++++++++++++++ docs/api-references/vesting/claim.mdx | 27 ++++++++++++++ docs/api-references/vesting/delegate.mdx | 28 +++++++++++++++ docs/api-references/vesting/list.mdx | 21 +++++++++++ docs/api-references/vesting/undelegate.mdx | 27 ++++++++++++++ docs/api-references/vesting/validator.mdx | 31 ++++++++++++++++ .../vesting/validator/claim.mdx | 27 ++++++++++++++ .../vesting/validator/create.mdx | 28 +++++++++++++++ .../vesting/validator/deposit.mdx | 28 +++++++++++++++ .../api-references/vesting/validator/exit.mdx | 28 +++++++++++++++ .../api-references/vesting/validator/join.mdx | 28 +++++++++++++++ .../api-references/vesting/validator/list.mdx | 22 ++++++++++++ .../vesting/validator/operator-transfer.mdx | 25 +++++++++++++ .../validator/operator-transfer/cancel.mdx | 27 ++++++++++++++ .../validator/operator-transfer/complete.mdx | 27 ++++++++++++++ .../validator/operator-transfer/initiate.mdx | 29 +++++++++++++++ .../vesting/validator/set-identity.mdx | 36 +++++++++++++++++++ .../vesting/validator/status.mdx | 22 ++++++++++++ docs/api-references/vesting/withdraw.mdx | 23 ++++++++++++ scripts/generate-cli-docs.mjs | 2 +- 51 files changed, 635 insertions(+), 57 deletions(-) create mode 100644 docs/api-references/configuration/network/add.mdx create mode 100644 docs/api-references/configuration/network/remove.mdx create mode 100644 docs/api-references/vesting.mdx create mode 100644 docs/api-references/vesting/claim.mdx create mode 100644 docs/api-references/vesting/delegate.mdx create mode 100644 docs/api-references/vesting/list.mdx create mode 100644 docs/api-references/vesting/undelegate.mdx create mode 100644 docs/api-references/vesting/validator.mdx create mode 100644 docs/api-references/vesting/validator/claim.mdx create mode 100644 docs/api-references/vesting/validator/create.mdx create mode 100644 docs/api-references/vesting/validator/deposit.mdx create mode 100644 docs/api-references/vesting/validator/exit.mdx create mode 100644 docs/api-references/vesting/validator/join.mdx create mode 100644 docs/api-references/vesting/validator/list.mdx create mode 100644 docs/api-references/vesting/validator/operator-transfer.mdx create mode 100644 docs/api-references/vesting/validator/operator-transfer/cancel.mdx create mode 100644 docs/api-references/vesting/validator/operator-transfer/complete.mdx create mode 100644 docs/api-references/vesting/validator/operator-transfer/initiate.mdx create mode 100644 docs/api-references/vesting/validator/set-identity.mdx create mode 100644 docs/api-references/vesting/validator/status.mdx create mode 100644 docs/api-references/vesting/withdraw.mdx diff --git a/docs/api-references/_meta.json b/docs/api-references/_meta.json index 9b9b4046..bf2c81cb 100644 --- a/docs/api-references/_meta.json +++ b/docs/api-references/_meta.json @@ -9,5 +9,6 @@ "localnet": "Localnet", "estimate-fees": "estimate-fees", "finalize": "finalize", - "finalize-batch": "finalize-batch" + "finalize-batch": "finalize-batch", + "vesting": "vesting" } \ No newline at end of file diff --git a/docs/api-references/accounts/account/send.mdx b/docs/api-references/accounts/account/send.mdx index 86477235..42498133 100644 --- a/docs/api-references/accounts/account/send.mdx +++ b/docs/api-references/accounts/account/send.mdx @@ -18,7 +18,7 @@ Send GEN to an address | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --rpc <rpcUrl> | RPC URL for the network | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --account <name> | Account to send from | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/configuration/network.mdx b/docs/api-references/configuration/network.mdx index 5a1cec9d..c71d6c34 100644 --- a/docs/api-references/configuration/network.mdx +++ b/docs/api-references/configuration/network.mdx @@ -20,6 +20,8 @@ Network configuration ### Subcommands +- `genlayer add` — Add a custom network profile - `genlayer set` — Set the network to use -- `genlayer info` — Show current network configuration and contract addresses +- `genlayer info` — Show current network configuration and contract - `genlayer list` — List available networks +- `genlayer remove` — Remove a custom network profile diff --git a/docs/api-references/configuration/network/add.mdx b/docs/api-references/configuration/network/add.mdx new file mode 100644 index 00000000..a070fa9c --- /dev/null +++ b/docs/api-references/configuration/network/add.mdx @@ -0,0 +1,32 @@ +--- +title: network add +--- + +Add a custom network profile +Arguments: +alias Custom network alias + +### Usage + +`$ genlayer network add [options] ` + +### Arguments + +- `` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --base <built-in-alias> | Built-in base network alias | No | | +| | --deployment <path.json> | Consensus deployments JSON file | No | | +| | --deployment-key <dot.path> | Deployment JSON sub-object to scan | No | | +| | --rpc <url> | Node RPC URL override | No | | +| | --consensus-main <addr> | ConsensusMain contract address override | No | | +| | --consensus-data <addr> | ConsensusData contract address override | No | | +| | --staking <addr> | Staking contract address override | No | | +| | --fee-manager <addr> | FeeManager contract address override | No | | +| | --rounds-storage <addr> | RoundsStorage contract address override | No | | +| | --appeals <addr> | Appeals contract address override | No | | +| | --chain-id <n> | Chain ID override | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/configuration/network/remove.mdx b/docs/api-references/configuration/network/remove.mdx new file mode 100644 index 00000000..f9b3aeb8 --- /dev/null +++ b/docs/api-references/configuration/network/remove.mdx @@ -0,0 +1,21 @@ +--- +title: network remove +--- + +Remove a custom network profile +Arguments: +alias Custom network alias to remove + +### Usage + +`$ genlayer network remove [options] ` + +### Arguments + +- `` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/contracts/call.mdx b/docs/api-references/contracts/call.mdx index 02b21da6..d275fec6 100644 --- a/docs/api-references/contracts/call.mdx +++ b/docs/api-references/contracts/call.mdx @@ -18,4 +18,5 @@ Call a contract method without sending a transaction or changing the state | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --args <args...> | Contract arguments. Supported types: | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/contracts/deploy.mdx b/docs/api-references/contracts/deploy.mdx index dddbf07e..ca96ac0b 100644 --- a/docs/api-references/contracts/deploy.mdx +++ b/docs/api-references/contracts/deploy.mdx @@ -10,14 +10,15 @@ Deploy intelligent contracts ### Options -| Short | Long | Description | Required | Default | -| ----- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------- | -| | --contract <contractPath> | Path to the smart contract to deploy | No | | -| | --rpc <rpcUrl> | RPC URL for the network | No | | -| | --fees <json> | Transaction fee options JSON passed to genlayer-js. | No | | -| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | -| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | -| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | -| | --fee-value <wei> | Fee deposit value to send with the transaction | No | | -| | --valid-until <unixTimestamp> | Unix timestamp after which the transaction is invalid | No | | -| -h | --help | display help for command | No | | +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --contract <contractPath> | Path to the smart contract to deploy | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --fees <json> | Transaction fee options JSON passed to genlayer-js. | No | | +| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | +| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | +| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | +| | --fee-value <wei> | Fee deposit value to send with the transaction | No | | +| | --valid-until <unixTimestamp> | Unix timestamp after which the transaction is invalid | No | | +| | --args <args...> | Contract arguments. Supported types: | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/contracts/write.mdx b/docs/api-references/contracts/write.mdx index 0bb09d22..b4760e37 100644 --- a/docs/api-references/contracts/write.mdx +++ b/docs/api-references/contracts/write.mdx @@ -15,13 +15,14 @@ Sends a transaction to a contract method that modifies the state ### Options -| Short | Long | Description | Required | Default | -| ----- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------- | -| | --rpc <rpcUrl> | RPC URL for the network | No | | -| | --fees <json> | Transaction fee options JSON passed to genlayer-js. | No | | -| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | -| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | -| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | -| | --fee-value <wei> | Fee deposit value to send with the transaction | No | | -| | --valid-until <unixTimestamp> | Unix timestamp after which the transaction is invalid | No | | -| -h | --help | display help for command | No | | +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --fees <json> | Transaction fee options JSON passed to genlayer-js. | No | | +| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | +| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | +| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | +| | --fee-value <wei> | Fee deposit value to send with the transaction | No | | +| | --valid-until <unixTimestamp> | Unix timestamp after which the transaction is invalid | No | | +| | --args <args...> | Contract arguments. Supported types: | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/estimate-fees.mdx b/docs/api-references/estimate-fees.mdx index 88154ea6..217e9ae6 100644 --- a/docs/api-references/estimate-fees.mdx +++ b/docs/api-references/estimate-fees.mdx @@ -16,13 +16,14 @@ simulation ### Options -| Short | Long | Description | Required | Default | -| ----- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------- | -| | --rpc <rpcUrl> | RPC URL for the network | No | | -| | --fees <json> | Fee estimate options JSON passed to genlayer-js estimateTransactionFees. | No | | -| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | -| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | -| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | -| | --json | Print the fee estimate as JSON without spinner output | No | | -| | --include-report | Include simulation fee accounting/report in the generated estimate output | No | | -| -h | --help | display help for command | No | | +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --fees <json> | Fee estimate options JSON passed to genlayer-js estimateTransactionFees. | No | | +| | --fee-profile <path> | Path to a fee profile generated by gltest --fee-profile. Deploy uses the profile deploy entry; write and targeted estimate-fees use the matching method entry. --fees can still be provided to override profile values. | No | | +| | --fee-preset <preset> | Fee profile appeal posture: low, standard, or high | No | | +| | --appeal-rounds <count> | Override fee profile appeal rounds | No | | +| | --json | Print the fee estimate as JSON without spinner output | No | | +| | --include-report | Include simulation fee accounting/report in the generated estimate output | No | | +| | --args <args...> | Contract arguments. Supported types: | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/index.mdx b/docs/api-references/index.mdx index faf3b24f..dc97f6cb 100644 --- a/docs/api-references/index.mdx +++ b/docs/api-references/index.mdx @@ -32,6 +32,7 @@ Version: `0.39.1` - `genlayer finalize` — Finalize a transaction that is ready to be finalized (public call) - `genlayer finalize-batch` — Finalize a batch of idle transactions in a single call (public call) - `genlayer staking` — Staking operations for validators and delegators +- `genlayer vesting` — Vesting operations for beneficiaries --- diff --git a/docs/api-references/localnet/localnet/validators/create-random.mdx b/docs/api-references/localnet/localnet/validators/create-random.mdx index 4fb23b53..926480b0 100644 --- a/docs/api-references/localnet/localnet/validators/create-random.mdx +++ b/docs/api-references/localnet/localnet/validators/create-random.mdx @@ -12,5 +12,7 @@ Create random validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --count <count> | Number of validators to create openai ollama) gpt-4o) | No | `[]` | +| | --count <count> | Number of validators to create | No | `1` | +| | --providers <providers...> | Space-separated list of provider names (e.g., openai ollama) | No | `[]` | +| | --models <models...> | Space-separated list of model names (e.g., gpt-4 gpt-4o) | No | `[]` | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking.mdx b/docs/api-references/staking/staking.mdx index 17f18239..f1bd84ac 100644 --- a/docs/api-references/staking/staking.mdx +++ b/docs/api-references/staking/staking.mdx @@ -38,5 +38,5 @@ Staking operations for validators and delegators - `genlayer active-validators` — List all active validators - `genlayer quarantined-validators` — List all quarantined validators - `genlayer banned-validators` — List all banned validators -- `genlayer validators` — Show validator set with stake, status, and voting power +- `genlayer validators` — List validators with stake, status, and optional explorer performance - `genlayer validator-history` — Show slash and reward history for a validator (default: last 10 epochs) diff --git a/docs/api-references/staking/staking/active-validators.mdx b/docs/api-references/staking/staking/active-validators.mdx index 89a90a41..441eba20 100644 --- a/docs/api-references/staking/staking/active-validators.mdx +++ b/docs/api-references/staking/staking/active-validators.mdx @@ -12,7 +12,7 @@ List all active validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/banned-validators.mdx b/docs/api-references/staking/staking/banned-validators.mdx index 83b750aa..c3b33247 100644 --- a/docs/api-references/staking/staking/banned-validators.mdx +++ b/docs/api-references/staking/staking/banned-validators.mdx @@ -12,7 +12,7 @@ List all banned validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/delegation-info.mdx b/docs/api-references/staking/staking/delegation-info.mdx index b75fe6ce..447750c4 100644 --- a/docs/api-references/staking/staking/delegation-info.mdx +++ b/docs/api-references/staking/staking/delegation-info.mdx @@ -19,7 +19,7 @@ Get delegation info for a delegator with a validator | | --validator <address> | Validator address (deprecated, use positional arg) | No | | | | --delegator <address> | Delegator address (defaults to signer) | No | | | | --account <name> | Account to use (for default delegator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/delegator-claim.mdx b/docs/api-references/staking/staking/delegator-claim.mdx index 325eb379..25c71120 100644 --- a/docs/api-references/staking/staking/delegator-claim.mdx +++ b/docs/api-references/staking/staking/delegator-claim.mdx @@ -20,7 +20,7 @@ Claim delegator withdrawals after unbonding period | | --delegator <address> | Delegator address (defaults to signer) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/delegator-exit.mdx b/docs/api-references/staking/staking/delegator-exit.mdx index 1e8b5ad0..b4f8864d 100644 --- a/docs/api-references/staking/staking/delegator-exit.mdx +++ b/docs/api-references/staking/staking/delegator-exit.mdx @@ -20,7 +20,7 @@ Exit as a delegator by withdrawing shares from a validator | | --shares <shares> | Number of shares to withdraw | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/delegator-join.mdx b/docs/api-references/staking/staking/delegator-join.mdx index 3219e752..d213113a 100644 --- a/docs/api-references/staking/staking/delegator-join.mdx +++ b/docs/api-references/staking/staking/delegator-join.mdx @@ -20,7 +20,7 @@ Join as a delegator by staking with a validator | | --amount <amount> | Amount to stake (in wei or with 'eth'/'gen' suffix) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/epoch-info.mdx b/docs/api-references/staking/staking/epoch-info.mdx index 7f4cf570..97a50bca 100644 --- a/docs/api-references/staking/staking/epoch-info.mdx +++ b/docs/api-references/staking/staking/epoch-info.mdx @@ -13,7 +13,7 @@ Get current epoch and staking parameters | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --epoch <number> | Show data for specific epoch (current or previous only) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/prime-all.mdx b/docs/api-references/staking/staking/prime-all.mdx index 5d69dbea..be8d1a7c 100644 --- a/docs/api-references/staking/staking/prime-all.mdx +++ b/docs/api-references/staking/staking/prime-all.mdx @@ -14,7 +14,7 @@ Prime all validators that need priming | --- | --- | --- | :---: | --- | | | --account <name> | Account to use (pays gas) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/quarantined-validators.mdx b/docs/api-references/staking/staking/quarantined-validators.mdx index c6a55a01..b90d615e 100644 --- a/docs/api-references/staking/staking/quarantined-validators.mdx +++ b/docs/api-references/staking/staking/quarantined-validators.mdx @@ -12,7 +12,7 @@ List all quarantined validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/set-identity.mdx b/docs/api-references/staking/staking/set-identity.mdx index 9212e863..85046475 100644 --- a/docs/api-references/staking/staking/set-identity.mdx +++ b/docs/api-references/staking/staking/set-identity.mdx @@ -28,6 +28,6 @@ Set validator identity metadata (moniker, website, socials, etc.) | | --extra-cid <cid> | Extra data as IPFS CID or hex bytes (0x...) | No | | | | --account <name> | Account to use (must be validator operator) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/set-operator.mdx b/docs/api-references/staking/staking/set-operator.mdx index 5c5b2e9f..0d08a104 100644 --- a/docs/api-references/staking/staking/set-operator.mdx +++ b/docs/api-references/staking/staking/set-operator.mdx @@ -21,6 +21,6 @@ Change the operator address for a validator wallet | | --operator <address> | New operator address (deprecated, use positional arg) | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-claim.mdx b/docs/api-references/staking/staking/validator-claim.mdx index bddb4657..3085a3af 100644 --- a/docs/api-references/staking/staking/validator-claim.mdx +++ b/docs/api-references/staking/staking/validator-claim.mdx @@ -19,6 +19,6 @@ Claim validator withdrawals after unbonding period | | --validator <address> | Validator wallet contract address (deprecated, use positional arg) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-deposit.mdx b/docs/api-references/staking/staking/validator-deposit.mdx index 1f0dd6a6..daf32fa0 100644 --- a/docs/api-references/staking/staking/validator-deposit.mdx +++ b/docs/api-references/staking/staking/validator-deposit.mdx @@ -20,6 +20,6 @@ Make an additional deposit to a validator wallet | | --amount <amount> | Amount to deposit (in wei or with 'eth'/'gen' suffix) | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-exit.mdx b/docs/api-references/staking/staking/validator-exit.mdx index 3c8dd58c..c5489f03 100644 --- a/docs/api-references/staking/staking/validator-exit.mdx +++ b/docs/api-references/staking/staking/validator-exit.mdx @@ -20,6 +20,6 @@ Exit as a validator by withdrawing shares | | --shares <shares> | Number of shares to withdraw | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-history.mdx b/docs/api-references/staking/staking/validator-history.mdx index e2f29f85..d2279901 100644 --- a/docs/api-references/staking/staking/validator-history.mdx +++ b/docs/api-references/staking/staking/validator-history.mdx @@ -23,7 +23,7 @@ Show slash and reward history for a validator (default: last 10 epochs) | | --all | Fetch complete history from genesis (slow) | No | | | | --limit <count> | Maximum number of events to show | No | `50` | | | --account <name> | Account to use (for default validator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-info.mdx b/docs/api-references/staking/staking/validator-info.mdx index bc4e6fd8..f0088acf 100644 --- a/docs/api-references/staking/staking/validator-info.mdx +++ b/docs/api-references/staking/staking/validator-info.mdx @@ -18,7 +18,7 @@ Get information about a validator | --- | --- | --- | :---: | --- | | | --validator <address> | Validator address (deprecated, use positional arg) | No | | | | --account <name> | Account to use (for default validator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | | --debug | Show raw unfiltered pending deposits/withdrawals | No | | diff --git a/docs/api-references/staking/staking/validator-join.mdx b/docs/api-references/staking/staking/validator-join.mdx index 24e9ff3b..e2d56331 100644 --- a/docs/api-references/staking/staking/validator-join.mdx +++ b/docs/api-references/staking/staking/validator-join.mdx @@ -16,7 +16,7 @@ Join as a validator by staking tokens | | --operator <address> | Operator address (defaults to signer) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validator-prime.mdx b/docs/api-references/staking/staking/validator-prime.mdx index 33d335a7..769d53f4 100644 --- a/docs/api-references/staking/staking/validator-prime.mdx +++ b/docs/api-references/staking/staking/validator-prime.mdx @@ -19,7 +19,7 @@ Prime a validator to prepare their stake record for the next epoch | | --validator <address> | Validator address to prime (deprecated, use positional arg) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/staking/staking/validators.mdx b/docs/api-references/staking/staking/validators.mdx index 015b1518..e17e4965 100644 --- a/docs/api-references/staking/staking/validators.mdx +++ b/docs/api-references/staking/staking/validators.mdx @@ -2,7 +2,7 @@ title: staking validators --- -Show validator set with stake, status, and voting power +List validators with stake, status, and optional explorer performance ### Usage @@ -13,7 +13,10 @@ Show validator set with stake, status, and voting power | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --all | Include banned validators | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --json | Output machine-readable JSON | No | | +| | --sort-by <field> | Sort validators by stake or uptime (default: stake) | No | `stake` | +| | --explorer-url <url> | Explorer backend or explorer base URL for optional performance enrichment | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting.mdx b/docs/api-references/vesting.mdx new file mode 100644 index 00000000..1742df61 --- /dev/null +++ b/docs/api-references/vesting.mdx @@ -0,0 +1,28 @@ +--- +title: vesting +--- + +Vesting operations for beneficiaries + +### Usage + +`$ genlayer vesting [options] [command]` + +### Arguments + +- `[command]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| -h | --help | display help for command | No | | + +### Subcommands + +- `genlayer list` — List beneficiary vesting contracts and state +- `genlayer delegate` — Delegate vesting-held tokens to a validator +- `genlayer undelegate` — Undelegate all vesting delegation shares +- `genlayer claim` — Claim vesting delegation withdrawals after +- `genlayer withdraw` — Withdraw vested tokens to the beneficiary +- `genlayer validator` — Vesting-backed validator operations diff --git a/docs/api-references/vesting/claim.mdx b/docs/api-references/vesting/claim.mdx new file mode 100644 index 00000000..43d64d47 --- /dev/null +++ b/docs/api-references/vesting/claim.mdx @@ -0,0 +1,27 @@ +--- +title: vesting claim +--- + +Claim vesting delegation withdrawals after unbonding period + +### Usage + +`$ genlayer vesting claim [options] [validator]` + +### Arguments + +- `[validator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --validator <address> | Validator address to claim from (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/delegate.mdx b/docs/api-references/vesting/delegate.mdx new file mode 100644 index 00000000..c0523790 --- /dev/null +++ b/docs/api-references/vesting/delegate.mdx @@ -0,0 +1,28 @@ +--- +title: vesting delegate +--- + +Delegate vesting-held tokens to a validator + +### Usage + +`$ genlayer vesting delegate [options] [validator]` + +### Arguments + +- `[validator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --validator <address> | Validator address to delegate to (deprecated, use positional arg) | No | | +| | --amount <amount> | Amount to delegate (in wei or with 'eth'/'gen' suffix) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/list.mdx b/docs/api-references/vesting/list.mdx new file mode 100644 index 00000000..36131325 --- /dev/null +++ b/docs/api-references/vesting/list.mdx @@ -0,0 +1,21 @@ +--- +title: vesting list +--- + +List beneficiary vesting contracts and state + +### Usage + +`$ genlayer vesting list [options]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --beneficiary <address> | Beneficiary address (defaults to signer) | No | | +| | --account <name> | Account to use (for default beneficiary address) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/undelegate.mdx b/docs/api-references/vesting/undelegate.mdx new file mode 100644 index 00000000..3ab6c783 --- /dev/null +++ b/docs/api-references/vesting/undelegate.mdx @@ -0,0 +1,27 @@ +--- +title: vesting undelegate +--- + +Undelegate all vesting delegation shares from a validator + +### Usage + +`$ genlayer vesting undelegate [options] [validator]` + +### Arguments + +- `[validator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --validator <address> | Validator address to undelegate from (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator.mdx b/docs/api-references/vesting/validator.mdx new file mode 100644 index 00000000..265a5fea --- /dev/null +++ b/docs/api-references/vesting/validator.mdx @@ -0,0 +1,31 @@ +--- +title: vesting validator +--- + +Vesting-backed validator operations + +### Usage + +`$ genlayer vesting validator [options] [command]` + +### Arguments + +- `[command]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| -h | --help | display help for command | No | | + +### Subcommands + +- `genlayer create` — Create a vesting-backed validator +- `genlayer join` — Create a vesting-backed validator +- `genlayer deposit` — Deposit more vesting-held tokens to a +- `genlayer exit` — Exit vesting validator self-stake by +- `genlayer claim` — Claim vesting validator withdrawals after +- `genlayer operator-transfer` — Manage vesting validator operator transfers +- `genlayer set-identity` — Set vesting validator identity metadata +- `genlayer list` — List validator wallets owned by a vesting +- `genlayer status` — Show validator wallets owned by a vesting diff --git a/docs/api-references/vesting/validator/claim.mdx b/docs/api-references/vesting/validator/claim.mdx new file mode 100644 index 00000000..cf112d09 --- /dev/null +++ b/docs/api-references/vesting/validator/claim.mdx @@ -0,0 +1,27 @@ +--- +title: vesting validator claim +--- + +Claim vesting validator withdrawals after unbonding period + +### Usage + +`$ genlayer vesting validator claim [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/create.mdx b/docs/api-references/vesting/validator/create.mdx new file mode 100644 index 00000000..f159851b --- /dev/null +++ b/docs/api-references/vesting/validator/create.mdx @@ -0,0 +1,28 @@ +--- +title: vesting validator create +--- + +Create a vesting-backed validator + +### Usage + +`$ genlayer vesting validator create [options] [operator]` + +### Arguments + +- `[operator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --operator <address> | Operator address (deprecated, use positional arg) | No | | +| | --amount <amount> | Amount to self-stake (in wei or with 'eth'/'gen' suffix) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/deposit.mdx b/docs/api-references/vesting/validator/deposit.mdx new file mode 100644 index 00000000..13ead169 --- /dev/null +++ b/docs/api-references/vesting/validator/deposit.mdx @@ -0,0 +1,28 @@ +--- +title: vesting validator deposit +--- + +Deposit more vesting-held tokens to a validator wallet + +### Usage + +`$ genlayer vesting validator deposit [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --amount <amount> | Amount to deposit (in wei or with 'eth'/'gen' suffix) | No | | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/exit.mdx b/docs/api-references/vesting/validator/exit.mdx new file mode 100644 index 00000000..02a3541c --- /dev/null +++ b/docs/api-references/vesting/validator/exit.mdx @@ -0,0 +1,28 @@ +--- +title: vesting validator exit +--- + +Exit vesting validator self-stake by withdrawing shares + +### Usage + +`$ genlayer vesting validator exit [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --shares <shares> | Number of shares to withdraw | No | | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/join.mdx b/docs/api-references/vesting/validator/join.mdx new file mode 100644 index 00000000..e2bd862b --- /dev/null +++ b/docs/api-references/vesting/validator/join.mdx @@ -0,0 +1,28 @@ +--- +title: vesting validator join +--- + +Create a vesting-backed validator + +### Usage + +`$ genlayer vesting validator join [options] [operator]` + +### Arguments + +- `[operator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --operator <address> | Operator address (deprecated, use positional arg) | No | | +| | --amount <amount> | Amount to self-stake (in wei or with 'eth'/'gen' suffix) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/list.mdx b/docs/api-references/vesting/validator/list.mdx new file mode 100644 index 00000000..9cdc9e72 --- /dev/null +++ b/docs/api-references/vesting/validator/list.mdx @@ -0,0 +1,22 @@ +--- +title: vesting validator list +--- + +List validator wallets owned by a vesting contract + +### Usage + +`$ genlayer vesting validator list [options]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --beneficiary <address> | Beneficiary address (defaults to signer) | No | | +| | --account <name> | Account to use (for default beneficiary address) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/operator-transfer.mdx b/docs/api-references/vesting/validator/operator-transfer.mdx new file mode 100644 index 00000000..0210b4e4 --- /dev/null +++ b/docs/api-references/vesting/validator/operator-transfer.mdx @@ -0,0 +1,25 @@ +--- +title: vesting validator operator-transfer +--- + +Manage vesting validator operator transfers + +### Usage + +`$ genlayer vesting validator operator-transfer [options] [command]` + +### Arguments + +- `[command]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| -h | --help | display help for command | No | | + +### Subcommands + +- `genlayer initiate` — Initiate a vesting validator operator transfer +- `genlayer complete` — Complete a vesting validator operator transfer +- `genlayer cancel` — Cancel a vesting validator operator transfer diff --git a/docs/api-references/vesting/validator/operator-transfer/cancel.mdx b/docs/api-references/vesting/validator/operator-transfer/cancel.mdx new file mode 100644 index 00000000..13f88d5f --- /dev/null +++ b/docs/api-references/vesting/validator/operator-transfer/cancel.mdx @@ -0,0 +1,27 @@ +--- +title: vesting validator operator-transfer cancel +--- + +Cancel a vesting validator operator transfer + +### Usage + +`$ genlayer vesting validator operator-transfer cancel [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/operator-transfer/complete.mdx b/docs/api-references/vesting/validator/operator-transfer/complete.mdx new file mode 100644 index 00000000..47dda0c7 --- /dev/null +++ b/docs/api-references/vesting/validator/operator-transfer/complete.mdx @@ -0,0 +1,27 @@ +--- +title: vesting validator operator-transfer complete +--- + +Complete a vesting validator operator transfer + +### Usage + +`$ genlayer vesting validator operator-transfer complete [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/operator-transfer/initiate.mdx b/docs/api-references/vesting/validator/operator-transfer/initiate.mdx new file mode 100644 index 00000000..e3596106 --- /dev/null +++ b/docs/api-references/vesting/validator/operator-transfer/initiate.mdx @@ -0,0 +1,29 @@ +--- +title: vesting validator operator-transfer initiate +--- + +Initiate a vesting validator operator transfer + +### Usage + +`$ genlayer vesting validator operator-transfer initiate [options] [wallet] [newOperator]` + +### Arguments + +- `[wallet]` +- `[newOperator]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --new-operator <address> | New operator address (deprecated, use positional arg) | No | | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/set-identity.mdx b/docs/api-references/vesting/validator/set-identity.mdx new file mode 100644 index 00000000..463f37b8 --- /dev/null +++ b/docs/api-references/vesting/validator/set-identity.mdx @@ -0,0 +1,36 @@ +--- +title: vesting validator set-identity +--- + +Set vesting validator identity metadata + +### Usage + +`$ genlayer vesting validator set-identity [options] [wallet]` + +### Arguments + +- `[wallet]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --moniker <name> | Validator display name | No | | +| | --logo-uri <uri> | Logo URI | No | | +| | --website <url> | Website URL | No | | +| | --description <text> | Description | No | | +| | --email <email> | Contact email | No | | +| | --twitter <handle> | Twitter handle | No | | +| | --telegram <handle> | Telegram handle | No | | +| | --github <handle> | GitHub handle | No | | +| | --extra-cid <cid> | Extra data as IPFS CID or hex bytes (0x...) | No | | +| | --wallet <address> | Validator wallet address (deprecated, use positional arg) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/validator/status.mdx b/docs/api-references/vesting/validator/status.mdx new file mode 100644 index 00000000..8ce0d443 --- /dev/null +++ b/docs/api-references/vesting/validator/status.mdx @@ -0,0 +1,22 @@ +--- +title: vesting validator status +--- + +Show validator wallets owned by a vesting contract + +### Usage + +`$ genlayer vesting validator status [options]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --beneficiary <address> | Beneficiary address (defaults to signer) | No | | +| | --account <name> | Account to use (for default beneficiary address) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/docs/api-references/vesting/withdraw.mdx b/docs/api-references/vesting/withdraw.mdx new file mode 100644 index 00000000..294c87a0 --- /dev/null +++ b/docs/api-references/vesting/withdraw.mdx @@ -0,0 +1,23 @@ +--- +title: vesting withdraw +--- + +Withdraw vested tokens to the beneficiary + +### Usage + +`$ genlayer vesting withdraw [options]` + +### Options + +| Short | Long | Description | Required | Default | +| --- | --- | --- | :---: | --- | +| | --amount <amount> | Amount to withdraw (in wei or with 'eth'/'gen' suffix) | No | | +| | --vesting <address> | Vesting contract address (overrides beneficiary lookup) | No | | +| | --account <name> | Account to use | No | | +| | --password <password> | Password to unlock account (skips interactive prompt) | No | | +| | --network <network> | built-in or custom network alias (see: genlayer network list) | No | | +| | --rpc <rpcUrl> | RPC URL for the network | No | | +| | --factory <address> | VestingFactory address (overrides AddressManager lookup) | No | | +| | --address-manager <address> | AddressManager address (overrides consensus lookup) | No | | +| -h | --help | display help for command | No | | diff --git a/scripts/generate-cli-docs.mjs b/scripts/generate-cli-docs.mjs index 30474d0b..3bc6fb15 100644 --- a/scripts/generate-cli-docs.mjs +++ b/scripts/generate-cli-docs.mjs @@ -154,7 +154,7 @@ function parseHelp(text, programName, commandPath) { if (inOptions) { // e.g., " -V, --version output the version number" - const m = l.match(/^\s*(-\w)?,?\s*(--[\w-]+(?:\s+<\w+>)?)\s{2,}(.+)$/); + const m = l.match(/^\s*(-\w)?,?\s*(--[\w-]+(?:\s+<[^>]+>)?)\s{2,}(.+)$/); if (m) { const short = m[1] || ''; const long = m[2] || ''; From 1444e16ea39e3349d3ba40d2a1132a1a0010223b Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 8 Jul 2026 15:02:48 +0100 Subject: [PATCH 4/4] fix(vesting): resolve validator wallet address in create output The join receipt does not carry the new wallet address, so the output printed validatorWallet: undefined. Read getValidatorWallets from the vesting contract after the join and report the newest entry. Verified live against a #1162-branch deployment. --- src/commands/vesting/validatorCreate.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/commands/vesting/validatorCreate.ts b/src/commands/vesting/validatorCreate.ts index 94d481bf..0b76aaf0 100644 --- a/src/commands/vesting/validatorCreate.ts +++ b/src/commands/vesting/validatorCreate.ts @@ -27,10 +27,22 @@ export class VestingValidatorCreateAction extends VestingAction { amount, }); + // The join receipt does not carry the wallet address; the vesting + // contract tracks its wallets, so the newest entry is the one created. + let validatorWallet = result.validatorWallet || result.wallet; + if (!validatorWallet) { + try { + const wallets = await client.getValidatorWallets(vesting); + validatorWallet = wallets[wallets.length - 1]; + } catch { + validatorWallet = "(read getValidatorWallets to inspect)"; + } + } + const output = { transactionHash: result.transactionHash, vesting, - validatorWallet: result.validatorWallet || result.wallet, + validatorWallet, operator: result.operator || options.operator, amount: result.amount || this.formatAmount(amount), blockNumber: result.blockNumber.toString(),