import type { LogFn } from '@aztec/foundation/log'; import { Command, Option } from 'commander'; import { parseAztecAddress, parseEthereumAddress, parseHex, parseOptionalInteger } from '../../utils/commands.js'; import { defaultBlsPath } from './utils.js'; export function injectCommands(program: Command, log: LogFn) { const group = program .command('validator-keys') .aliases(['valKeys', 'valkeys']) .description('Manage validator keystores for node operators'); group .command('new') .summary('Generate a new validator keystore JSON') .description('Generates a new validator keystore with ETH secp256k1 accounts and optional BLS accounts') .option('--data-dir ', 'Directory to store keystore(s). Defaults to ~/.aztec/keystore') .option('--file ', 'Keystore file name. Defaults to key1.json (or keyN.json if key1.json exists)') .option('--count ', 'Number of validators to generate', parseOptionalInteger) .option('--publisher-count ', 'Number of publisher accounts per validator (default 0)', value => parseOptionalInteger(value, 0), ) .option('--publishers ', 'Comma-separated list of publisher private keys for all validators.', value => value.split(',').map((key: string) => key.trim()), ) .option('--mnemonic ', 'Mnemonic for ETH/BLS derivation') .option('--passphrase ', 'Optional passphrase for mnemonic') .option('--account-index ', 'Base account index for ETH/BLS derivation', parseOptionalInteger) .option('--address-index ', 'Base address index for ETH/BLS derivation', parseOptionalInteger) .option( '--coinbase
', 'Coinbase ETH address to use when proposing. Defaults to attester address.', parseEthereumAddress, ) .option( '--funding-account ', 'ETH funding account used to top up publisher EOAs. Provide a private key, or an address together with --remote-signer.', ) .option('--remote-signer ', 'Default remote signer URL for accounts in this file') .option('--ikm ', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32)) .option('--bls-path ', `EIP-2334 path (default ${defaultBlsPath})`) .addOption( new Option('--password ', 'Shared password for writing ETH JSON V3 and BLS EIP-2335 keystore files').env( 'AZTEC_KEYSTORE_PASSWORD', ), ) .option('--password-file ', 'File containing the shared password for writing keystore files') .addOption( new Option('--eth-password ', 'Password for writing ETH JSON V3 keystore files').env( 'AZTEC_ETH_KEYSTORE_PASSWORD', ), ) .option('--eth-password-file ', 'File containing the password for writing ETH JSON V3 keystore files') .addOption( new Option('--bls-password ', 'Password for writing BLS EIP-2335 keystore files').env( 'AZTEC_BLS_KEYSTORE_PASSWORD', ), ) .option('--bls-password-file ', 'File containing the password for writing BLS EIP-2335 keystore files') .option('--encrypted-keystore-dir ', 'Output directory for encrypted keystore file(s)') .option('--json', 'Echo resulting JSON to stdout') .option('--staker-output', 'Generate a single staker output JSON file with an array of validator entries') .option('--gse-address
', 'GSE contract address (required with --staker-output)', parseEthereumAddress) .option('--l1-rpc-urls ', 'L1 RPC URLs (comma-separated, required with --staker-output)', value => value.split(','), ) .option( '-c, --l1-chain-id ', 'L1 chain ID (required with --staker-output)', value => parseInt(value), 31337, ) .requiredOption('--fee-recipient
', 'Aztec address that will receive fees', parseAztecAddress) .action(async options => { const { newValidatorKeystore } = await import('./new.js'); await newValidatorKeystore(options, log); }); group .command('add') .summary('Augment an existing validator keystore JSON') .description('Adds attester/publisher/BLS entries to an existing keystore using the same flags as new') .argument('', 'Path to existing keystore JSON') .option('--data-dir ', 'Directory where keystore(s) live. (default: ~/.aztec/keystore)') .option('--file ', 'Override output file name. (default: key.json)') .option('--count ', 'Number of validators to add. (default: 1)', parseOptionalInteger) .option('--publisher-count ', 'Number of publisher accounts per validator (default 0)', value => parseOptionalInteger(value, 0), ) .option('--publishers ', 'Comma-separated list of publisher private keys for all validators.', value => value.split(',').map((key: string) => key.trim()), ) .option('--mnemonic ', 'Mnemonic for ETH/BLS derivation') .option('--passphrase ', 'Optional passphrase for mnemonic') .option('--account-index ', 'Base account index for ETH/BLS derivation', parseOptionalInteger) .option('--address-index ', 'Base address index for ETH/BLS derivation', parseOptionalInteger) .option( '--coinbase
', 'Coinbase ETH address to use when proposing. Defaults to attester address.', parseEthereumAddress, ) .option('--remote-signer ', 'Default remote signer URL for accounts in this file') .option('--ikm ', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32)) .option('--bls-path ', `EIP-2334 path (default ${defaultBlsPath})`) .option('--empty', 'Generate an empty skeleton without keys') .option( '--password ', 'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed', ) .option('--encrypted-keystore-dir ', 'Output directory for encrypted keystore file(s)') .option('--json', 'Echo resulting JSON to stdout') .requiredOption('--fee-recipient
', 'Aztec address that will receive fees', parseAztecAddress) .action(async (existing: string, options) => { const { addValidatorKeys } = await import('./add.js'); await addValidatorKeys(existing, options, log); }); group .command('set-funding-account') .summary('Set the funding account of an existing keystore') .description( 'Sets the keystore-level ETH funding account used to top up publisher EOAs, replacing any existing one', ) .argument('', 'Path to existing keystore JSON') .argument( '', 'Funding account: a private key, or an address (needs --remote-signer unless the keystore already defines one)', ) .option( '--remote-signer ', 'Remote signer URL for the funding account (required with an address unless the keystore already defines one)', ) .option( '--password ', 'Password for writing the funding key as an encrypted ETH JSON V3 file. Empty string allowed', ) .option('--encrypted-keystore-dir ', 'Output directory for the encrypted funding key file') .option('--json', 'Echo resulting JSON to stdout') .action(async (existing: string, fundingAccount: string, options) => { const { setFundingAccount } = await import('./set_funding_account.js'); await setFundingAccount(existing, fundingAccount, options, log); }); group .command('staker') .summary('Generate staking JSON from keystore') .description( 'Reads a validator keystore and outputs staking data with BLS public keys for each attester (skips mnemonics)', ) .requiredOption('--from ', 'Path to keystore JSON file') .option('--password ', 'Password for decrypting encrypted keystores (if not specified in keystore file)') .requiredOption('--gse-address
', 'GSE contract address', parseEthereumAddress) .option('--l1-rpc-urls ', 'L1 RPC URLs (comma-separated)', value => value.split(','), [ 'http://localhost:8545', ]) .option('-c, --l1-chain-id ', 'L1 chain ID', value => parseInt(value), 31337) .option('--output ', 'Output file path (if not specified, JSON is written to stdout)') .action(async options => { const { generateStakerJson } = await import('./staker.js'); await generateStakerJson(options, log); }); // top-level convenience: aztec generate-bls-keypair program .command('generate-bls-keypair') .description('Generate a BLS keypair with convenience flags') .option('--mnemonic ', 'Mnemonic for BLS derivation') .option('--ikm ', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32)) .option('--bls-path ', `EIP-2334 path (default ${defaultBlsPath})`) .option('--g2', 'Derive on G2 subgroup') .option('--compressed', 'Output compressed public key') .option('--json', 'Print JSON output to stdout') .option('--out ', 'Write output to file') .action(async options => { const { generateBlsKeypair } = await import('./generate_bls_keypair.js'); await generateBlsKeypair(options, log); }); return program; }