diff --git a/CLAUDE.md b/CLAUDE.md index c579946e7..86cd3ee2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,9 +33,10 @@ npm run depcruise # dependency-cruiser — enforces the architecture rules ### Architecture (hexagonal / ports & adapters) -Dependencies point inward. The source of truth is -`ts/docs/typescript-wallet-cli-architecture-source-of-truth.md` — read it before changing -boundaries, ports, command routing, or the JSON contract. `depcruise` enforces these rules in CI. +Dependencies point inward, and the table below is the rule — read it before changing boundaries, +ports, or command routing. `depcruise` enforces it in CI (`ts/.dependency-cruiser.cjs`). +`ts/docs/machine-interface.md` is the source of truth for the JSON contract (envelope, exit codes, +stdout/stderr discipline). | Area (`ts/src/…`) | Role | May depend on | Must NOT depend on | |---|---|---|---| @@ -73,14 +74,10 @@ Key points: # Build fat JAR (output: build/libs/wallet-cli.jar) ./gradlew shadowJar -# Run in REPL 交互模式 (human-friendly, interactive prompts) +# Run the interactive shell (the only way to run it) ./gradlew run # Or after building: java -jar build/libs/wallet-cli.jar -# Run in standard CLI mode (non-interactive, scriptable) -java -jar build/libs/wallet-cli.jar --network nile get-account --address TXyz... -java -jar build/libs/wallet-cli.jar --output json --network nile get-account --address TXyz... - # Run tests ./gradlew test @@ -93,74 +90,46 @@ java -jar build/libs/wallet-cli.jar --output json --network nile get-account --a Java 8 source/target compatibility. Protobuf sources are in `src/main/protos/` and generate into `src/main/gen/` — this directory is git-tracked but rebuilt on `clean`. -## QA Verification - -The `qa/` directory contains shell-based parity tests that compare interactive REPL output vs standard CLI (text and JSON modes). Requires a funded Nile testnet account. - -```bash -# Run QA verification (needs TRON_TEST_PRIVATE_KEY env var for private key) -TRON_TEST_PRIVATE_KEY= bash qa/run.sh verify +## End-to-end coverage -# QA config is in qa/config.sh; test commands are in qa/commands/*.sh -# MASTER_PASSWORD env var is used for keystore auto-login (default: testpassword123A) -``` +There is none, and there never was: the `qa/` harness that used to live here +only ever drove the standard CLI, which was removed in v4.13.0. `./gradlew build` +passing does **not** mean the interactive shell still works — changes that touch +shared helpers must be walked through by hand against a funded Nile account. ## Architecture This is a **TRON blockchain CLI wallet** built on the [Trident SDK](https://github.com/tronprotocol/trident). It communicates with TRON nodes via gRPC. -### Two CLI Modes - -1. **REPL 交互模式** (human-friendly) — `Client` class with JCommander `@Parameters` inner classes. Entry point: `org.tron.walletcli.Client`. Features tab completion, interactive prompts, and conversational output. This is the largest file (~4900 lines). Best for manual exploration and day-to-day wallet management by humans. -2. **Standard CLI 模式** (AI-agent-friendly) — `StandardCliRunner` with `CommandRegistry`/`CommandDefinition` pattern in `org.tron.walletcli.cli.*`. Supports `--output json`, `--network`, `--quiet` flags. Commands are registered in `cli/commands/` classes (e.g., `WalletCommands`, `TransactionCommands`, `QueryCommands`). Designed for automation: deterministic exit codes, structured JSON output, no interactive prompts, and env-var-based authentication — ideal for AI agents, scripts, and CI/CD pipelines. +### One CLI Mode -The standard CLI suppresses all stray stdout/stderr in JSON mode to ensure machine-parseable output. Authentication is automatic via `MASTER_PASSWORD` env var + keystore files in `Wallet/`. +**REPL 交互模式** — `Client` class with JCommander `@Parameters` inner classes. Entry point: +`org.tron.walletcli.Client`. Features tab completion, interactive prompts, and conversational +output. This is the largest file (~4800 lines). -### Standard CLI Contract - -Before changing parser behavior, auth flow, JSON output, command success/failure semantics, or `qa/` expectations for -the standard CLI, read: - -- `java/docs/standard-cli-contract-spec.md` - -Treat that file as the source of truth for the standard CLI contract unless the repository owner explicitly decides to -revise it. +The shell is started one way only: a bare `java -jar wallet-cli.jar`. The entry point recognises +`--version` and `--help` and nothing else; any other argument prints a one-line pointer to the +TypeScript CLI on stderr and exits 2. Non-interactive, scriptable and CI use belongs to `ts/` +(npm `@tron-walletcli/wallet-cli`). ### Request Flow ``` -# Standard CLI mode: -User Input → GlobalOptions → StandardCliRunner → CommandRegistry → CommandHandler → WalletApiWrapper → WalletApi → Trident SDK → gRPC → TRON Node - -# Interactive REPL mode: User Input → Client (JCommander) → WalletApiWrapper → WalletApi → Trident SDK → gRPC → TRON Node ``` ### Key Classes -- **`org.tron.walletcli.Client`** — Legacy REPL entry point and CLI command dispatcher. Each command is a JCommander `@Parameters` inner class. -- **`org.tron.walletcli.cli.StandardCliRunner`** — New standard CLI executor. Handles network init, auto-authentication, JSON stream suppression, and command dispatch. -- **`org.tron.walletcli.cli.CommandRegistry`** — Maps command names/aliases to `CommandDefinition` instances. Supports fuzzy suggestion on typos. -- **`org.tron.walletcli.cli.CommandDefinition`** — Immutable command metadata (name, aliases, options, handler). Built via fluent `Builder` API. -- **`org.tron.walletcli.cli.OutputFormatter`** — Formats output as text or JSON. In JSON mode, wraps results in `{"success":true,"data":...}` envelope. +- **`org.tron.walletcli.Client`** — REPL entry point and command dispatcher. Each command is a JCommander `@Parameters` inner class. - **`org.tron.walletcli.WalletApiWrapper`** — Orchestration layer between CLI and core wallet logic. Handles transaction construction, signing, and broadcasting. - **`org.tron.walletserver.WalletApi`** — Core wallet operations: account management, transaction creation, proposals, asset operations. Delegates gRPC calls to Trident. - **`org.tron.walletcli.ApiClientFactory`** — Creates gRPC client instances for different networks (mainnet, Nile testnet, Shasta testnet, custom). -### Adding a New Standard CLI Command - -1. Create or extend a class in `cli/commands/` (e.g., `TransactionCommands.java`) -2. Build a `CommandDefinition` via `CommandDefinition.builder()` with name, aliases, options, and handler -3. Register it in the appropriate `register(CommandRegistry)` method -4. The handler receives `(ParsedOptions, WalletApiWrapper, OutputFormatter)` — use `formatter.success()/error()` for output - ### Package Organization | Package | Purpose | |---------|---------| -| `walletcli` | CLI entry points, API wrapper | -| `walletcli.cli` | Standard CLI framework: registry, definitions, options, formatter | -| `walletcli.cli.commands` | Standard CLI command implementations by domain | +| `walletcli` | REPL entry point, API wrapper | | `walletserver` | Core wallet API and gRPC communication | | `common` | Crypto utilities, encoding, enums, shared helpers | | `core` | Configuration, data converters, DAOs, exceptions, managers | diff --git a/java/.gitignore b/java/.gitignore index 74ed38120..0d85c316e 100644 --- a/java/.gitignore +++ b/java/.gitignore @@ -18,10 +18,4 @@ Wallet/ Mnemonic/ wallet_data/ -# QA runtime output -qa/results/ -qa/runtime/ -qa/report.txt -qa/.verify.lock/ - docs/superpowers diff --git a/java/build.gradle b/java/build.gradle index b6cb8f48c..26107091b 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -150,19 +150,3 @@ shadowJar { version = null mergeServiceFiles() // https://github.com/grpc/grpc-java/issues/10853 } - -task qaJar(type: Jar, dependsOn: [shadowJar, testClasses]) { - from zipTree(shadowJar.archiveFile) - from sourceSets.test.output - archiveBaseName.set('wallet-cli-qa') - archiveClassifier.set('') - archiveVersion.set('') - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -task qaRun(type: JavaExec) { - classpath = sourceSets.test.runtimeClasspath - mainClass = 'org.tron.qa.QARunner' - args = project.hasProperty('qaArgs') ? project.property('qaArgs').split(' ') : ['list'] - standardInput = System.in -} diff --git a/java/docs/standard-cli-contract-spec.md b/java/docs/standard-cli-contract-spec.md deleted file mode 100644 index 02a4f9e46..000000000 --- a/java/docs/standard-cli-contract-spec.md +++ /dev/null @@ -1,1092 +0,0 @@ -# Standard CLI Contract Spec - -This document defines the intended contract for the standard CLI path under `org.tron.walletcli.cli.*`. -When changing parser behavior, command execution, machine-readable output, authentication, or QA coverage, -follow this spec unless the repository owner explicitly decides to revise it. - -## Scope - -This spec applies to: - -- `GlobalOptions` -- `StandardCliRunner` -- `CommandDefinition` / `ParsedOptions` -- `OutputFormatter` -- `cli/commands/*` -- `WalletApiWrapper` behavior as exposed through the standard CLI -- `qa/` verification for the standard CLI - -This spec does not require the legacy REPL path in `Client` to match the same UX or parsing behavior exactly. - -## Design Goals - -The standard CLI is intended to be: - -- deterministic -- scriptable -- machine-readable in JSON mode -- strict about malformed input -- explicit about authentication behavior - -The standard CLI should not rely on "best effort" guessing for ambiguous user input. - -## Request Flow - -The standard CLI request flow is: - -1. Parse global options -2. Resolve the command name -3. Parse command-local options -4. Apply runner-level setup such as network and auth policy -5. Execute the command handler -6. Emit a single structured success or error outcome - -## Option Layering - -The standard CLI has two distinct option layers: - -- global options -- command-local options - -### Global Options - -Global options affect the overall execution environment for the whole command run. - -Examples: - -- output mode -- network selection -- wallet override -- grpc endpoint override -- quiet / verbose behavior -- global help / version handling - -Global options are parsed by `GlobalOptions.parse(String[] args)`. Execution modifiers may appear either before or -after the command token. Top-level mode selectors are pre-command only, except for command-help handling described -below. - -### Command-Local Options - -Command-local options belong to a specific command and are meaningful only after the command has been resolved. - -Examples: - -- `get-balance --address T...` -- `send-coin --to T... --amount 1` -- `trigger-constant-contract --contract T... --method "balanceOf(address)"` - -Command-local options are parsed by `CommandDefinition.parseArgs(String[] args)`. - -### Layer Boundary - -- global execution modifiers configure how the CLI run is executed -- top-level mode selectors choose an alternate program mode before command execution -- command-local options configure what the chosen command does -- global parsing happens first and extracts known global options from the full argument list -- command-local parsing happens only after the command token is known -- a token must not be interpreted as both a global option and a command-local option in the same pass - -## Contract 1: Global Option Parsing - -`GlobalOptions.parse(String[] args)` is responsible only for parsing known options that affect the whole run. - -### Parsing Model - -Global option parsing is a left-to-right first-pass scan across the full argument list. - -The parser consumes tokens until one of these happens: - -1. it reaches the end of input -2. it encounters a malformed known global option and fails with a usage error - -This pass is intentionally narrow. It extracts only known global options and must not reinterpret unknown -post-command options that belong to command-local parsing. - -### Supported Syntax - -Supported global options are: - -- valueless flags: - - `--interactive` - - `--help` - - `-h` - - `--version` - - `--quiet` - - `--verbose` -- valued options: - - `--output ` - - `--network ` - - `--wallet ` - - `--grpc-endpoint ` - -Long options may be provided in either of these equivalent forms when the option accepts a value: - -- `--name value` -- `--name=value` - -For Contract 1, this applies to valued global options only. - -### Boundary Rules - -- Execution modifier global options are recognized before and after the command token. -- Top-level mode selectors `--version` and `--interactive` are recognized only before the command token. -- The first token before command resolution that does not begin with `-` is the command token. -- The command token is normalized to lowercase for registry lookup. -- After the command token is found, execution modifier global options are extracted and all other tokens are passed - through unchanged as command arguments. -- Unknown options after the command token are never reinterpreted as global options. -- Post-command `--help` and `-h` are reserved for command help and are passed through as command arguments. -- Post-command `--version` and `--interactive` are command-local tokens and are passed through as command arguments. - -Examples: - -- `wallet-cli --output json --network nile get-balance --address T...` - - global options: `--output json`, `--network nile` - - command: `get-balance` - - command args: `--address T...` -- `wallet-cli --output=json --network=nile get-balance --address T...` - - global options: `--output=json`, `--network=nile` - - command: `get-balance` - - command args: `--address T...` -- `wallet-cli get-balance --network nile` - - global options: `--network nile` - - command: `get-balance` - - command args: none -- `wallet-cli get-balance --network=nile` - - global options: `--network=nile` - - command: `get-balance` - - command args: none -- `wallet-cli get-balance --address T... --network nile` - - global options: `--network nile` - - command: `get-balance` - - command args: `--address T...` -- `wallet-cli get-balance --help` - - command: `get-balance` - - command args: `--help` - - `--help` is not treated as global help because it appears after the command token -- `wallet-cli get-balance --version` - - command: `get-balance` - - command args: `--version` - - `--version` is not treated as global version because it appears after the command token -- `wallet-cli get-balance --interactive` - - command: `get-balance` - - command args: `--interactive` - - `--interactive` is not treated as global interactive mode because it appears after the command token - -### No-Command Cases - -The global parser may produce no command token when the invocation is global-only, such as: - -- `wallet-cli --help` -- `wallet-cli -h` -- `wallet-cli --version` -- `wallet-cli --interactive` - -In those cases, the parser succeeds and leaves handling to the runner / entrypoint. - -If no command is present and the invocation is not a supported global-only mode, the runner should treat that as a -usage error. - -### Help Flag Semantics - -`--help` and `-h` are reserved for help behavior. - -Rules: - -- before the command token, `--help` and `-h` request global help -- after the command token, `--help` and `-h` are passed through for command help handling -- `-h` is not available for unrelated global short-option meanings -- the global parser must not reinterpret `-h` as a command token -- standard CLI commands should treat trailing `--help` and `-h` as command-help requests, not as normal business - options - -Examples: - -- `wallet-cli -h` - - global help -- `wallet-cli --help` - - global help -- `wallet-cli get-balance -h` - - `get-balance` command help -- `wallet-cli get-balance --help` - - `get-balance` command help - -### Error Rules - -The following are usage errors at the global parsing layer: - -- unknown global option before the command token -- missing value for a valued global option -- invalid value for a constrained global option -- malformed syntax for a known global option - -Specific expectations: - -- `--outputt json get-balance` - - usage error: unknown global option `--outputt` -- `--outputt=json get-balance` - - usage error: unknown global option `--outputt` -- `--network get-balance` - - usage error: invalid value for `--network` -- `--network=get-balance` - - usage error: invalid value for `--network` -- `--network --output json` - - usage error: missing value for `--network` -- `--grpc-endpoint` - - usage error: missing value for `--grpc-endpoint` -- `--output=` - - usage error: missing or empty value for `--output` -- `--quiet=true` - - usage error: option `--quiet` does not take a value -- `get-balance --network beta` - - usage error: invalid value for `--network` -- `get-balance --unknown value` - - not a global parsing error; `--unknown value` remains command-local input - -Error classification for valued global options: - -- if the next token does not exist: missing value -- if the next token is another long option token such as `--output`: missing value -- if the next token exists but is not allowed for a constrained option: invalid value -- if the option is provided as `--name=` with an empty value: missing or empty value -- if a valueless flag is provided as `--flag=value`: option does not take a value - -### Valued Option Rules - -For valued global options: - -- the value may be the next token or may be provided inline as `--name=value` -- the value must not itself be another long option token such as `--output` -- constrained options must validate their allowed values during global parsing, not later - -For valueless global flags: - -- the presence of the flag means `true` -- `--flag=value` is not allowed -- `--flag=` is not allowed - -Examples: - -- `--output=json` is valid -- `--network=nile` is valid -- `--wallet=/tmp/keystore.json` is valid -- `--output=` is invalid -- `--quiet=true` is invalid - -For now, the standard CLI contract does not require support for: - -- combined short flags -- `--` as a special end-of-options sentinel - -These may be added later only if the spec is updated explicitly. - -### Global and Command Parser Consistency - -The standard CLI should not support `--name=value` in one parsing layer but reject it in the other. - -If long-option inline-value syntax is supported for global parsing, it should also be supported consistently for -command-local parsing where the command option accepts a value. - -### Value-Detection Heuristic - -The global parser and command parser use intentionally different heuristics to detect whether the next token is a -missing value or a legitimate option value: - -- the global parser treats any token starting with `-` as a non-value token (rejects `-anything` as a value) -- the command parser treats only tokens starting with `--` as a non-value token (allows `-anything` as a value) - -This difference is deliberate. Command-local options include numeric types (`LONG`) where negative values such as -`-5` are legitimate. Global valued options (`--output`, `--network`, `--wallet`, `--grpc-endpoint`) never accept -negative numbers or dash-prefixed values, so the stricter heuristic is safe and catches more user errors early. - -### Repetition Rules - -Repeated global options must behave deterministically. - -- repeated valued global options are a usage error - - example: `--network nile --network main` is an error - - example: `--output json --output text` is an error - - example: `--wallet a.json --wallet b.json` is an error -- repeated boolean flags are idempotent when they do not conflict - - example: `--verbose --verbose` is equivalent to `--verbose` -- conflicting boolean flags are a usage error - - example: `--quiet --verbose` is an error - -### Explicit Non-Goals - -- Do not silently reinterpret unknown global options as command arguments. -- Do not allow a malformed global option to fall through into downstream command execution errors. -- Do not support "guessing" whether a token was meant to be a global option or a command option. -- Do not let downstream command parsing decide whether a pre-command token was a valid global option. - -## Contract 2: Command Option Parsing - -`CommandDefinition.parseArgs(String[] args)` parses only command-local options using the schema declared by the -command definition. - -### Scope - -`CommandDefinition.parseArgs(String[] args)` is responsible only for command-local syntax parsing. - -It is responsible for: - -- recognizing declared command options -- extracting option values -- performing parser-level validation of token shape and option syntax -- producing `ParsedOptions` - -It is not responsible for: - -- global option parsing -- auth, network, or output-mode policy -- command execution -- domain validation beyond parser-level type and shape checks - -### Supported Syntax - -Command-local options should support: - -- `--name value` -- `--name=value` -- boolean options as valueless flags, such as `--multi` -- boolean options with explicit values: - - `--multi=true` - - `--multi=false` - - `--multi=1` - - `--multi=0` - - `--multi=yes` - - `--multi=no` -- `-m` only for commands that explicitly declare the `multi` option - -Command-local parsing does not support: - -- arbitrary short-option aliases -- combined short flags -- positional arguments -- unknown short flags - -### Boundary Rules - -- command parsing starts only after the runner has already resolved the command token -- command parsing operates only on command arguments -- command parsing does not reinterpret tokens as global options -- command parsing does not guess whether a token was intended as a global option -- if the runner reserves `--help` or `-h` for command-help handling, those tokens should not be treated as normal - business options by generic command parsing -- unexpected bare tokens are usage errors unless positional arguments are explicitly introduced by a future spec - -### Error Rules - -The following are usage errors at the command parsing layer: - -- unknown command option -- non-boolean option missing value -- non-boolean option empty value -- boolean option with an invalid explicit value -- option provided in an unsupported form -- option that does not take a value being provided as `--name=value` -- empty option name such as `--` -- unexpected bare token - -Specific expectations: - -- a non-boolean option must never fall back to `"true"` when its value is missing -- malformed input should fail in the parser layer, not later during command execution -- `--contract --method balanceOf(address)` must fail as a parser usage error -- `--contract=` must fail as a parser usage error -- `--multi=maybe` must fail as a parser usage error - -### Repetition Rules - -Repeated command options must behave deterministically and strictly. - -- repeated valued options are a usage error -- repeated boolean options with the same effective meaning are idempotent -- repeated boolean options with conflicting explicit values are a usage error - -Examples: - -- `--amount 1 --amount 2` - - usage error -- `--multi --multi` - - valid, equivalent to `--multi` -- `--multi=true --multi=false` - - usage error - -### Parser vs Semantic Validation - -Parser-level validation and semantic validation are distinct. - -Parser-level validation is responsible for: - -- whether an option name exists -- whether an option requires a value -- whether a token is in a supported parser form -- whether a boolean literal is valid -- whether the basic shape of the argument list is valid - -Semantic validation is responsible for: - -- address validity -- numeric range checks -- command-specific business rules -- relationships between multiple options that depend on command semantics - -Examples: - -- `--contract --method balanceOf(address)` - - parser error -- `--contract=` - - parser error -- `--address abc` - - semantic validation error unless a stricter command-specific parser rule is added -- malformed vote counts or domain-specific numeric constraints - - semantic or command-specific pre-execution validation - -Command-specific validation that runs immediately after parsing may still surface as `usage_error` when the problem -is malformed user input rather than runtime execution failure. - -Staking resource-code validation is **network-aware and fail-open**: for freeze/unfreeze commands the code `2` -(TRON_POWER) is accepted only when the chain parameter `getAllowNewResourceModel` is enabled, and when that -parameter cannot be fetched (offline/timeout) the client-side pre-check is skipped so the node's `validate()` -remains the single source of truth at broadcast. Delegation commands reject `2` unconditionally (BANDWIDTH/ENERGY -only), matching the node actuator. - -### Rationale - -This contract prevents malformed input such as `--contract --method balanceOf(address)` from being treated as -`contract=true` and then failing later with a misleading execution error. - -## Contract 3: Authentication Policy - -`StandardCliRunner` controls whether automatic authentication is attempted for a command. - -### Rules - -- Auto-auth is a runner policy decision, not a side effect of arbitrary wrapper calls. -- Commands that do not require a logged-in wallet must not fail purely because wallet auto-auth setup is absent. -- Commands that require a wallet should authenticate deterministically or fail with an explicit execution error. -- `--wallet` must be honored as an explicit override even if the local `Wallet/` directory is absent or empty. -- Authentication skip conditions should be surfaced as text-mode info messages when appropriate. - -### Policy Shape - -Each command should have one clear auth policy: - -- never auto-auth -- require auto-auth -- conditional auto-auth based on specific options - -Avoid ad hoc checks spread across handlers. - -### Decision Point - -Auth policy is resolved only after: - -1. global options have been parsed -2. the command has been resolved -3. command-local options have been parsed - -The runner then decides whether the command instance for this invocation is: - -- `NEVER` -- `REQUIRE` - -If a command has conditional auth behavior, that condition must be evaluated from parsed command options and must -resolve deterministically to either `NEVER` or `REQUIRE` before handler execution begins. - -### Policy Semantics - -`NEVER` means: - -- the runner must not attempt wallet discovery -- the runner must not read active-wallet state for authentication -- the runner must not load or decrypt a keystore for authentication -- the runner must not require `MASTER_PASSWORD` -- the command must be allowed to execute even if wallet auth setup is absent or broken - -`REQUIRE` means: - -- the runner must resolve an authentication target before handler execution -- the runner must successfully complete keystore loading and password verification before handler execution -- if authentication cannot be completed, the handler must not run -- failure to authenticate is an execution error, not a silent skip - -### Resolution Order - -When auth policy resolves to `REQUIRE`, the runner must resolve the target wallet in this order: - -1. explicit `--wallet` override -2. active wallet selection - -`--wallet` is authoritative for that invocation and takes precedence over active-wallet state. - -### `--wallet` Override Rules - -When `--wallet` is present, the runner must try to resolve it as: - -1. an explicit filesystem path -2. a file entry under local `Wallet/` -3. a wallet name - -Specific rules: - -- an explicit filesystem path must be honored even if the local `Wallet/` directory does not exist -- `--wallet` must not be ignored merely because the current working directory has no keystore directory -- if `--wallet` is ambiguous or does not resolve to a keystore, the command must fail with an explicit execution error - -### Active Wallet Rules - -If `--wallet` is absent and auth policy resolves to `REQUIRE`, active-wallet resolution is authoritative. - -Rules: - -- unreadable or malformed active-wallet state must not be silently treated as "unset" -- an active wallet pointing to a missing keystore must not silently fall back to another wallet -- wallet-required commands must fail explicitly when the selected active wallet cannot be used - -For commands whose auth policy resolves to `NEVER`, active-wallet problems must not block execution. - -Commands whose purpose is wallet inspection or recovery may intentionally use a more lenient read of active-wallet -state, but that behavior must be explicit in command design rather than an accidental side effect of runner auth. - -### Skip vs Fail - -For invocations whose resolved auth policy is `NEVER`: - -- auth may be skipped without error -- text-mode info messages may explain why auth was skipped -- JSON mode does not need to surface skip info as a top-level result -- the runner may still pass through the already-parsed global `--wallet` value as an explicit target selector for - command-specific business logic, as long as that does not trigger runner-managed authentication semantics - -For invocations whose resolved auth policy is `REQUIRE`: - -- missing wallet directory is an execution error unless an explicit `--wallet` path resolves successfully -- missing keystore is an execution error -- a missing master password (no `MASTER_PASSWORD` env var and no `--password-stdin` input) is an execution error -- invalid password is an execution error -- unreadable wallet metadata or keystore content is an execution error -- the standard CLI must not fall back to interactive password prompts, wallet-selection prompts, permission prompts, - confirmation prompts, or other interactive auth flows -- "skip auto-login and let the handler fail later" is not allowed - -### Password Source Resolution - -The standard CLI accepts the master password from two explicit sources. Both are non-interactive — neither involves -prompting the user for input. - -Sources, in precedence order: - -1. `--password-stdin` global flag — the runner reads the entire `System.in` once, strips a single trailing `\r?\n`, - and uses the result as the password. Internal whitespace is preserved verbatim. -2. `MASTER_PASSWORD` environment variable. - -Rules: - -- when both sources are present, `--password-stdin` wins; the runner may emit a text-mode info notice that the env - var was overridden, but JSON mode behavior is unaffected -- `--password-stdin` reads `System.in` exactly once; the read result is cached so wallet-authenticated commands that - consult the password more than once observe a stable value -- `--password-stdin` with empty input is an execution error with a message that explicitly identifies the empty-stdin - cause (distinct from "neither source set") -- `--password-stdin` invoked when stdin is detected to be an interactive terminal (TTY) is a usage error — reading - `System.in` would block on a prompt, which violates the non-interactive contract; this detection is best-effort, - may produce false negatives, and must not be relied on by callers as a hard guarantee -- `--password-stdin` is recognized regardless of position relative to the command token, consistent with other known - globals -- `--password-stdin` must not introduce a third source by way of fallback (e.g. reading `System.in` opportunistically - when the flag was not passed); the flag is the only trigger -- neither source may be substituted by an interactive prompt, a keychain lookup, or any other implicit channel that is - not declared by this contract - -### Ledger Hardware Wallet Sign Outcomes - -When the selected wallet is a Ledger keystore, the standard CLI signs through a connected Ledger device. The keystore -auth flow above applies unchanged — the password unlocks the BIP44 path metadata stored in the keystore, not the -device. The device is a separate auth boundary requiring on-device user confirmation; the funds are protected by the -device, not the password. - -Rules: - -- a Ledger sign must be non-interactive on the CLI side: no prompts on stdin or stdout, no menus, no `selectDevice` - call paths -- exactly one stderr notice may be emitted before the sign blocks on the device, indicating which address the user - must confirm; this notice must not be suppressed by JSON mode (it is the only signal that a human action is - required) but may be suppressed by `--quiet` -- prints from shared Ledger code (listener, HID wrapper) must not reach stdout in standard CLI mode; the runner is - responsible for capturing those during the sign -- the device discovery, sign request, and result polling must all complete without re-prompting the user; the selected - device must derive the keystore address at the stored path -- Ledger-specific failures must surface as execution errors with one of the documented `ledger_*` codes: - `ledger_not_connected`, `ledger_app_not_open`, `ledger_sign_by_hash_disabled`, - `ledger_unsupported_contract`, `ledger_already_signing`, `ledger_user_rejected`, `ledger_timeout`, - `ledger_sign_failed` -- all Ledger-specific error codes share the `ledger_` prefix for stable programmatic matching by agents -- the standard CLI must not introduce a Ledger sign path that bypasses keystore auth (e.g. a `--ledger-path` direct - mode); if such a path is added in the future it requires its own contract subsection - -### Handler Boundary - -- handlers must not re-decide runner auth policy ad hoc -- handlers may still perform command-specific checks about whether a logged-in wallet is acceptable for the requested - operation -- handlers must be able to rely on the runner guarantee that `REQUIRE` commands either start authenticated or do not - start at all -- when a `NEVER` command intentionally uses global `--wallet` as a business-level target selector, that behavior - must be explicit in command design and must not implicitly fall back into runner-style auth or `MASTER_PASSWORD` - requirements - -### Standard CLI vs REPL - -This contract applies only to the standard CLI path. - -Rules: - -- changes made to satisfy this auth contract must not silently change legacy REPL auth behavior -- REPL prompt flow, password prompting, and legacy interactive recovery behavior remain separate compatibility concerns -- if standard CLI needs stricter auth handling than the REPL, isolate that behavior in `StandardCliRunner` or other - standard-CLI-only code paths rather than changing shared legacy behavior by accident - -## Contract 4: Result and Error Model - -The standard CLI must have one clear source of truth for success, failure, and exit code behavior. - -### Rules - -- Usage mistakes map to a usage error and exit code `2`. -- Execution failures map to an execution error and exit code `1`. -- Success maps to exit code `0`. -- A command must not report success if the underlying operation merely printed a warning and returned early. -- The standard CLI path must not depend on legacy direct `System.out.println(...)` calls to signal success or failure. - -### Recommended Responsibility Split - -- Command handlers decide the public CLI outcome. -- `WalletApiWrapper` should return structured results or throw explicit exceptions for failure cases. -- `OutputFormatter` is the only place that should emit standard CLI envelopes and terminal-facing error formatting. - -### Outcome Ownership - -For the standard CLI path, the public outcome of a command is determined by the standard CLI layer, not by legacy -printing side effects. - -Rules: - -- a command invocation must end in exactly one public outcome: - - success - - usage error - - execution error -- a handler must not rely on legacy stdout/stderr text to imply success -- a handler must not report success merely because no exception was thrown -- if an underlying operation returns early, reports failure, or leaves the command without a real result, the handler - must map that to a non-success outcome - -### Usage Error vs Execution Error - -`usage_error` covers malformed CLI input, including: - -- invalid option syntax -- missing required arguments -- parser-level invalid values -- command-specific malformed user input detected before execution - -`execution_error` covers failures after the invocation is otherwise well-formed, including: - -- authentication failure -- keystore resolution or loading failure -- RPC or network failure -- chain rejection -- wrapper-level operational failure -- command execution that cannot produce the promised result - -The standard CLI must prefer early `usage_error` classification for malformed input rather than letting parser problems -leak into downstream execution failures. - -### Single-Outcome Rule - -The standard CLI invocation model is single-outcome. - -Rules: - -- one invocation must not emit multiple competing terminal outcomes -- once a command has emitted a terminal success or error outcome, no later layer should reinterpret that invocation -- handlers must not emit success and then continue into a later failure path -- if a handler returns without establishing a valid outcome for the requested operation, that is a standard CLI - contract violation and should surface as an execution error rather than silent success - -### Legacy Integration Boundary - -The standard CLI may call shared legacy layers such as `WalletApiWrapper`, but legacy behavior is not the public -result contract for the standard CLI. - -Rules: - -- direct prints from shared legacy code are compatibility details, not the standard CLI source of truth -- if a shared wrapper method only prints warnings or status text, the standard CLI must not treat that as sufficient - success signaling -- if a shared wrapper method cannot express failure cleanly enough for the standard CLI contract, the standard CLI - should adapt it explicitly rather than inheriting ambiguous behavior - -### Additive Change Preference - -Because `WalletApiWrapper` and related utilities are shared with the legacy REPL path, changes to shared layers should -prefer additive evolution. - -Rules: - -- prefer adding new structured return paths, helper methods, adapters, or standard-CLI-specific wrappers -- avoid silently changing the meaning of existing shared methods that the REPL already depends on -- do not require an immediate rewrite of legacy REPL-oriented wrapper behavior just to satisfy the standard CLI - contract -- if a non-additive shared-layer change becomes necessary, it must be evaluated explicitly for REPL compatibility and - called out in the spec or PR discussion - -### Standard CLI vs REPL - -This result contract applies to the standard CLI public surface. - -Rules: - -- tightening result semantics for the standard CLI must not silently change legacy REPL success/failure behavior -- if stricter standard CLI outcome handling is needed, isolate that logic in handlers, adapters, runner code, or - formatter-controlled paths before changing shared REPL behavior - -## Contract 5: Machine-Readable Output - -JSON mode exists to provide a stable machine-readable contract. - -### Rules - -- JSON mode must emit one structured success envelope or one structured error envelope. -- Commands participating in standard CLI mode must not bypass `OutputFormatter` for their public result. -- Stray legacy stdout/stderr may be suppressed in JSON mode, but this is only a containment mechanism, not the - primary success path. -- Command behavior in JSON mode must not rely on legacy printing side effects being visible to the caller. - -### Formal Interface - -For the standard CLI path, `--output json` is a formal machine-readable interface, not an incidental alternate -display format. - -Rules: - -- if `--output` is omitted, the standard CLI defaults to text mode -- text mode is the formal human-facing interface -- new standard CLI commands must treat JSON output as a maintained contract -- changes to existing standard CLI commands must preserve JSON-mode contract compatibility unless the spec is revised -- machine consumers that require a stable structured contract must explicitly request `--output json` -- callers must be able to determine success or failure from the JSON envelope itself without inspecting suppressed - legacy output - -### Single JSON Output Rule - -Each standard CLI invocation in JSON mode must emit exactly one top-level JSON envelope to `stdout`. - -Rules: - -- `stdout` must contain one terminal JSON result -- JSON mode must not emit multiple competing top-level JSON outcomes -- empty `stdout` in JSON mode is never a valid success outcome -- legacy or debug output that would otherwise pollute `stdout` is a bug to contain, not part of the public contract - -### Output Ownership - -For the standard CLI path, `OutputFormatter` is the only valid public JSON emitter. - -Rules: - -- command handlers must route their public outcome through `OutputFormatter` -- shared legacy code may still print internally, but those prints are not part of the JSON contract -- suppressing stray legacy output does not convert it into valid JSON output -- if a shared legacy method cannot produce a standard-CLI-safe result directly, the standard CLI should adapt it - explicitly -- a command that bypasses `OutputFormatter` is outside the standard CLI JSON contract until adapted - -### Envelope Shape - -The top-level JSON envelope is stable and must not vary by command. - -Success: - -```json -{ - "success": true, - "data": {} -} -``` - -Error: - -```json -{ - "success": false, - "error": "usage_error|execution_error|domain_specific_code", - "message": "human-readable explanation" -} -``` - -### Field Stability - -The JSON contract distinguishes stable machine-readable fields from human-oriented fields. - -Rules: - -- `success` is the stable top-level discriminator -- `error` is a stable machine-readable error code when `success` is `false` -- `message` is human-readable explanation text and must not be the only machine contract -- command-specific machine-readable payload belongs under `data` -- future command output expansion should prefer additive changes inside `data` rather than changing the top-level - envelope shape - -### Broadcast Command Payload - -On-chain broadcast commands (send-coin, transfer-asset, trigger-contract, deploy-contract, -freeze-balance, vote-witness, etc.) include transaction-specific fields under `data` when the -broadcast succeeds. - -Rules: - -- a successful single-sign broadcast must include `"txid"` under `data` -- a successful multi-sign submission must not include `"txid"` under `data` because multi-sign - submits a partially-signed transaction to a coordinator, not directly to the network; no txid - is generated at broadcast time -- deploy-contract must additionally include `"contract_address"` (base58Check) under `data` -- callers that require a txid must check that `data.txid` is present; its absence means the - transaction was submitted for multi-sign coordination - -### Text and JSON Consistency - -Text mode and JSON mode are two renderings of the same command outcome. - -Rules: - -- text mode and JSON mode must not represent different success/failure semantics for the same invocation -- a command must not succeed in text mode but produce empty output in JSON mode -- a command must not fail in text mode while reporting JSON success for the same underlying outcome -- machine-readable payload richness may differ from text formatting, but outcome classification must remain aligned - -### Command Participation - -A command is considered compliant with the standard CLI JSON contract only if all of the following are true: - -- it emits its public result through `OutputFormatter` -- it produces one valid JSON envelope in JSON mode -- it does not require callers to inspect suppressed legacy output to determine success or failure - -If a command cannot yet satisfy these rules cleanly, it should be adapted explicitly or treated as outside the -guaranteed JSON contract until that work is completed. - -Such commands being temporarily outside the guaranteed JSON contract does not relax the standard CLI JSON contract -itself. It means those commands are not yet compliant and must not be used to redefine the contract for compliant -commands. - -## Contract 6: Standard CLI vs Legacy Interactive Behavior - -The standard CLI is not a thin alias for the legacy REPL path. - -### Rules - -- Standard CLI behavior takes precedence over legacy prompt-oriented behavior when the two conflict. -- Interactive viewers, prompts, and direct prints from legacy code are compatibility hazards in the standard CLI path. -- If a legacy path cannot satisfy the standard CLI contract cleanly, either adapt it explicitly or exclude it from - the standard CLI guarantees. -- Hidden stdin scripting, prompt auto-confirmation, or injected prompt answers are not allowed as standard CLI - behavior. Explicit, opt-in stdin consumption that is part of a documented contract (e.g. `--password-stdin`) is - not "hidden" and is allowed. - -### Interface Identity - -The standard CLI and the legacy REPL are separate public interfaces with different operating models. - -Rules: - -- the standard CLI is a non-interactive command interface intended for deterministic terminal use, scripts, agents, - and automation -- the legacy REPL is an interactive prompt-oriented interface intended for human-driven sessions -- shared implementation is allowed, but shared UX and behavioral contract must not be assumed automatically - -### Contract Precedence - -When standard CLI requirements conflict with inherited REPL behavior, the standard CLI contract wins for the standard -CLI path. - -Rules: - -- standard CLI parser rules are not constrained by legacy REPL parsing quirks -- standard CLI auth behavior is not defined by legacy prompt flow -- standard CLI result and exit-code behavior are not defined by legacy print side effects -- standard CLI JSON behavior is not defined by what the legacy interactive path happens to print - -### Interactive Boundary - -Interactive behavior is not part of the intended standard CLI contract unless explicitly designed into a future spec -revision. - -Rules: - -- prompts, menus, viewer UIs, and multi-step confirmations from legacy code must not be treated as standard CLI - contract behavior -- if a command capability currently depends on interactive legacy flow, it should be adapted explicitly before being - considered fully standard-CLI-compliant -- hidden stdin feeding, prompt auto-confirmation, prompt suppression, or interactive fallback are not valid standard - CLI implementation techniques; explicit opt-in stdin consumption declared by a contract (e.g. `--password-stdin`) - is exempt from this prohibition - -### Adaptation Strategy - -When standard CLI needs capabilities that currently exist only in legacy interactive form, prefer explicit adaptation -over behavioral inheritance. - -Rules: - -- prefer additive adapters, standard-CLI-safe wrapper methods, or dedicated handler logic -- do not force standard CLI callers to emulate REPL interactions as part of the public contract -- do not silently redefine REPL behavior just to satisfy standard CLI requirements - -### Temporary Compatibility Mechanisms - -Some temporary containment techniques may be necessary while migrating legacy functionality. - -Rules: - -- temporary shims must be understood as migration aids, not as the target design -- code comments, tests, and PR descriptions should avoid presenting temporary compatibility hacks as the intended - long-term contract -- when a command still depends on such a shim, that command is not standard-CLI-compliant and the dependency should be - treated as technical debt to retire - -## Contract 7: QA Verification Scope - -The purpose of `qa/` for the standard CLI is to validate the public CLI contract, not to preserve incidental -legacy side effects. - -### Rules - -- QA should primarily validate observable CLI outcomes: - - exit code behavior - - text-mode success/failure semantics - - JSON envelope correctness - - meaningful parity between text and JSON where appropriate -- QA should not claim coverage for flows that are known stale, retired, or outside the supported standard CLI path. -- If a QA path no longer represents real supported behavior, retire it instead of keeping a misleading green check. - -### QA Purpose - -Standard CLI QA exists to validate contract behavior at the CLI boundary. - -Rules: - -- QA should verify what callers can observe from the standard CLI interface -- QA should not treat incidental legacy stdout/stderr behavior as the thing being preserved -- QA should not elevate temporary migration shims into the supported contract merely because they currently exist - -### Priority Coverage Areas - -QA for the standard CLI should prioritize coverage for: - -- global option parsing behavior -- command-local parsing behavior -- authentication policy behavior -- success/failure classification -- exit code behavior -- JSON envelope correctness -- text/JSON outcome consistency -- help and other public top-level CLI flows - -### Contract-Level Bias - -Contract-level verification is more valuable than reproducing isolated implementation details. - -Rules: - -- prefer tests that validate user-visible contract guarantees at the CLI boundary -- when a bug is fixed, add regression coverage at the contract boundary if feasible -- avoid overfitting QA to temporary implementation quirks that are not part of the intended contract - -### Text and JSON Verification - -Text and JSON outputs should be validated according to their roles. - -Rules: - -- JSON-mode QA should validate both envelope shape and command outcome semantics -- text-mode QA should validate human-facing success/failure behavior without requiring brittle formatting snapshots -- text/JSON parity should primarily mean semantic outcome parity, not exact string equality -- if text and JSON intentionally differ in presentation detail, QA should still ensure they agree on success/failure - and core public meaning - -### Coverage Integrity - -QA reporting must reflect real supported coverage. - -Rules: - -- do not represent stale, broken, or unsupported flows as validated simply because a script still runs -- if a command is not yet fully compliant with the standard CLI contract, QA should either mark that gap clearly or - exclude the command from compliance claims -- a passing QA check must not rely on output that actually indicates an internal failure path - -### Shared Layer vs CLI Boundary - -Tests at different layers serve different purposes. - -Rules: - -- shared-layer unit tests are useful but do not replace CLI-boundary contract tests -- standard CLI QA should not assume that a passing wrapper or utility test proves public CLI correctness -- REPL-oriented tests do not count as standard CLI contract coverage unless they explicitly exercise the standard CLI - surface - -### Maintenance Expectations - -QA should evolve with the contract. - -Rules: - -- when behavior covered by this spec changes, QA should be updated in the same change -- new standard CLI commands should add or extend contract-relevant coverage -- if a QA path becomes misleading, retire or rewrite it rather than keeping nominal coverage - -## Reviewer Expectations Captured by This Spec - -Review feedback in this area has consistently pushed toward: - -- early parser failures instead of downstream surprises -- explicit auth policy instead of implicit side effects -- one reliable machine-readable output path -- one reliable success/failure model -- QA that validates supported behavior instead of historical implementation details - -## Unified Address Book - -Standard CLI supports a per-network alias book layered over built-in token aliases. - -Rules: - -- built-in aliases are token-only and are loaded before user aliases -- user alias files live at `Wallet/aliases/.json` -- built-in names cannot be overridden or removed by standard CLI commands -- if a user manually edits a file to collide with a built-in name, the built-in entry remains authoritative -- account-position options resolve only account aliases -- contract-position options resolve token aliases, including `--contract` and `get-contract* --address` -- raw Base58Check and hex TRON addresses are accepted directly and are not recorded as alias resolutions -- alias hits are auditable: text mode emits a stderr resolution line, JSON mode includes `meta.resolved` -- aliases are not resolved inside packed string arguments such as `vote-witness --votes "address count ..."` - -Commands: - -- `alias-add --name --type --address
[--decimals n] [--note text]` -- `alias-remove --name ` -- `alias-list [--type ]` -- `alias-resolve --name [--type ]` - -## Change Management - -When changing behavior covered by this spec: - -1. Update this document first or in the same change. -2. Update tests to reflect the intended contract. -3. Prefer contract-level tests over narrowly reproducing only the latest reported bug. - -If a change intentionally violates this spec, document the reason in the PR and revise this file in the same patch. diff --git a/java/qa/commands/alias.sh b/java/qa/commands/alias.sh deleted file mode 100755 index 4583eaa53..000000000 --- a/java/qa/commands/alias.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$(cd "$SCRIPT_DIR/../.." && pwd)" - -CLI="${CLI:-./gradlew -q run}" - -run_cli() { - # shellcheck disable=SC2206 - local cli_parts=($CLI) - "${cli_parts[@]}" --args="$*" -} - -run_cli "--output json --network main alias-resolve --name USDT --type TOKEN" \ - | grep -q '"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"' - -run_cli "--network main alias-list --type TOKEN" \ - | grep -q 'USDT' - -if run_cli "--network main alias-add --name USDT --type TOKEN --address TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" >/tmp/wallet-cli-alias.out 2>&1; then - echo "expected built-in alias override to fail" >&2 - exit 1 -fi -grep -q 'built in' /tmp/wallet-cli-alias.out - -echo "alias QA passed" diff --git a/java/qa/config.sh b/java/qa/config.sh deleted file mode 100644 index 38c83821d..000000000 --- a/java/qa/config.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -NETWORK="${TRON_NETWORK:-nile}" -PRIVATE_KEY="${TRON_TEST_PRIVATE_KEY:-}" -MNEMONIC="${TRON_TEST_MNEMONIC:-}" -TRON_PRIVATE_KEY="${TRON_PRIVATE_KEY:-$PRIVATE_KEY}" -TRON_MNEMONIC="${TRON_MNEMONIC:-$MNEMONIC}" -MASTER_PASSWORD="${MASTER_PASSWORD:-testpassword123A}" -ALT_PASSWORD="${QA_ALT_PASSWORD:-TempPass123!B}" -GASFREE_API_KEY="${GASFREE_API_KEY:-}" -GASFREE_API_SECRET="${GASFREE_API_SECRET:-}" - -WALLET_JAR="$PROJECT_DIR/build/libs/wallet-cli.jar" -QA_JAR="$PROJECT_DIR/build/libs/wallet-cli-qa.jar" -RESULTS_DIR="$PROJECT_DIR/qa/results" -RUNTIME_DIR="$PROJECT_DIR/qa/runtime" -REPORT_FILE="$PROJECT_DIR/qa/report.txt" -MANIFEST_FILE="$PROJECT_DIR/qa/manifest.tsv" -CONTRACTS_FILE="$PROJECT_DIR/qa/contracts.tsv" - -TARGET_ADDR="${TRON_QA_TARGET_ADDR:-TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL}" -USDT_NILE="${TRON_QA_USDT_NILE:-TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf}" -FAKE_ID_64="0000000000000000000000000000000000000000000000000000000000000001" - -export NETWORK PRIVATE_KEY MNEMONIC MASTER_PASSWORD ALT_PASSWORD -export TRON_PRIVATE_KEY TRON_MNEMONIC -export WALLET_JAR QA_JAR RESULTS_DIR RUNTIME_DIR REPORT_FILE MANIFEST_FILE CONTRACTS_FILE -export TARGET_ADDR USDT_NILE FAKE_ID_64 PROJECT_DIR -export GASFREE_API_KEY GASFREE_API_SECRET - -qa_has_private_key() { - [ -n "$PRIVATE_KEY" ] -} - -qa_has_mnemonic() { - [ -n "$MNEMONIC" ] -} - -qa_requires_available() { - case "$1" in - none|"") - return 0 - ;; - private_key) - qa_has_private_key - ;; - mnemonic) - qa_has_mnemonic - ;; - *) - return 1 - ;; - esac -} diff --git a/java/qa/contracts.tsv b/java/qa/contracts.tsv deleted file mode 100644 index 5bd735695..000000000 --- a/java/qa/contracts.tsv +++ /dev/null @@ -1,49 +0,0 @@ -# label|template|requires|env_mode|stream_mode|expectation|args_json|json_path_exists|json_path_absent|error_code|text_contains|text_absent|preflight|workspace_path_exists|workspace_path_absent -global-help|empty|none|default|dual|help_dual|["--help"]|data.help|||Usage:,TRON Wallet CLI|Error:| -version-flag|empty|none|default|text|success|["--version"]||||wallet-cli|| -missing-command|empty|none|default|json|usage|["--output","json"]|||usage_error||| -unknown-global-option|empty|none|default|json|usage|["--output","json","--outputt","json","get-balance"]|||usage_error||| -unknown-command|empty|none|default|text|usage|["sendkon"]||||Did you mean|| -contract__global_network_separate|empty|none|default|text|success|["--network","nile","current-network"]||||Current network:|| -contract__global_network_inline_json|empty|none|default|json|success|["--output","json","--network=nile","current-network"]|||||| -contract__global_missing_network_value_json|empty|none|default|json|usage|["--output","json","--network"]|||usage_error||| -contract__global_empty_output_value_json|empty|none|default|text|usage|["--output="]||||Missing or empty value for --output|| -contract__global_invalid_network_value_json|empty|none|default|json|usage|["--output","json","--network=beta","current-network"]|||usage_error||| -contract__global_flag_with_value_json|empty|none|default|json|usage|["--output","json","--quiet=true","current-network"]|||usage_error||| -contract__global_repeated_network_json|empty|none|default|json|usage|["--output","json","--network","nile","--network","main","current-network"]|||usage_error||| -contract__global_quiet_verbose_conflict_json|empty|none|default|json|usage|["--output","json","--quiet","--verbose","current-network"]|||usage_error||| -contract__post_command_global_network_text|empty|none|default|text|success|["current-network","--network","nile"]||||Current network:|| -contract__post_command_unknown_option_text|empty|none|default|text|usage|["current-network","--unknown","value"]||||Unknown option: --unknown|| -contract__post_command_version_text|empty|none|default|text|usage|["current-network","--version"]||||Unknown option: --version|| -contract__post_command_interactive_text|empty|none|default|text|usage|["current-network","--interactive"]||||Unknown option: --interactive|| -contract__command_missing_required_value_text|empty|none|default|text|usage|["get-block-by-latest-num","--count"]||||Missing value for --count|| -contract__command_empty_required_value_json|empty|none|default|json|usage|["--output","json","get-block-by-latest-num","--count="]|||usage_error||| -contract__command_boolean_flag_exec|empty|none|default|text|execution|["freeze-balance-v2","--amount","1","--multi"]|||execution_error|Error:|| -contract__command_boolean_true_exec|empty|none|default|json|execution|["--output","json","freeze-balance-v2","--amount","1","--multi=true"]|||execution_error||| -contract__command_boolean_false_exec|empty|none|default|json|execution|["--output","json","freeze-balance-v2","--amount","1","--multi=false"]|||execution_error||| -contract__command_boolean_invalid_text|empty|none|default|text|usage|["freeze-balance-v2","--amount","1","--multi=maybe"]||||requires a boolean value|| -contract__command_repeated_value_text|empty|none|default|text|usage|["freeze-balance-v2","--amount","1","--amount","2"]||||Repeated option: --amount|| -contract__command_repeated_boolean_same_exec|empty|none|default|json|execution|["--output","json","freeze-balance-v2","--amount","1","--multi","--multi"]|||execution_error||| -contract__command_repeated_boolean_conflict_text|empty|none|default|text|usage|["freeze-balance-v2","--amount","1","--multi=true","--multi=false"]||||Conflicting values for option: --multi|| -contract__command_unknown_option_text|empty|none|default|text|usage|["get-block","--wat"]||||Unknown option: --wat|| -contract__command_unexpected_argument_text|empty|none|default|text|usage|["get-block","oops"]||||Unexpected argument: oops|| -contract__command_help_short_text|empty|none|default|text|help_text|["get-balance","-h"]||||Usage:,wallet-cli get-balance|Error:| -contract__command_help_short_json|empty|none|default|json|help_json|["--output","json","get-balance","-h"]|data.help||||| -contract__command_help_not_option_value_text|empty|none|default|text|help_text|["get-balance","--address","--help"]||||Usage:,wallet-cli get-balance|Error:| -contract__auth_never_without_wallet_text|empty|none|default|text|success|["current-network"]||||Current network:|| -contract__auth_require_without_wallet_json|empty|none|default|json|execution|["--output","json","get-address"]|||execution_error||| -contract__auth_require_no_password_text|auth|private_key|no-password|text|execution|["get-address"]||||MASTER_PASSWORD is required|| -contract__auth_wallet_override_without_active_text|auth-no-active|private_key|default|text|success|["--network","nile","--wallet","mywallet","get-address"]||||GetAddress successful,address =|| -contract__auth_wallet_override_missing_text|auth-no-active|private_key|default|text|execution|["--network","nile","--wallet","missing-wallet","get-address"]||||No wallet found for --wallet|| -contract__auth_wallet_bad_active_text|auth-bad-active|private_key|default|text|execution|["--network","nile","get-address"]||||Could not read active wallet config|| -contract__clear_wallet_external_target_text|auth-external-wallet|private_key|default|text|success|["--network","nile","--wallet","./External/{{FIRST_WALLET_FILE}}","clear-wallet-keystore","--force"]||||ClearWalletKeystore successful !!|||Wallet/.active-wallet|External/{{FIRST_WALLET_FILE}} -contract__auth_get_usdt_balance_with_address_json|empty|none|default|json|success|["--output","json","--network","nile","get-usdt-balance","--address","{{TARGET_ADDR}}"]|||||| -contract__auth_get_usdt_balance_without_address_json|empty|none|default|json|execution|["--output","json","--network","nile","get-usdt-balance"]|||execution_error||| -contract__auth_trigger_constant_with_owner_json|empty|none|default|json|success|["--output","json","--network","nile","trigger-constant-contract","--contract","{{USDT_NILE}}","--method","balanceOf(address)","--params","\"{{TARGET_ADDR}}\"","--owner","{{TARGET_ADDR}}"]|||||| -contract__auth_trigger_constant_without_owner_json|empty|none|default|json|success|["--output","json","--network","nile","trigger-constant-contract","--contract","{{USDT_NILE}}","--method","balanceOf(address)","--params","\"{{TARGET_ADDR}}\""]|||||| -contract__auth_estimate_energy_with_owner_json|empty|none|default|json|success|["--output","json","--network","nile","estimate-energy","--contract","{{USDT_NILE}}","--method","transfer(address,uint256)","--params","\"{{TARGET_ADDR}}\",1","--owner","{{TARGET_ADDR}}"]|||||| -contract__auth_estimate_energy_without_owner_json|empty|none|default|json|execution|["--output","json","--network","nile","estimate-energy","--contract","{{USDT_NILE}}","--method","transfer(address,uint256)","--params","\"{{TARGET_ADDR}}\",1"]|||query_failed||| -# ForCli migration: verify create-account, vote-witness reach chain via ForCli path and return proper execution_error -# asset-issue is omitted: valid params can succeed on a fresh account (non-idempotent), making it unreliable as expected-execution-error -contract__create_account_forcli_exec|auth|private_key|default|dual|execution|["--network","{{NETWORK}}","create-account","--address","{{TARGET_ADDR}}"]|||execution_error|Error:|| -contract__vote_witness_forcli_exec|auth|private_key|default|dual|execution|["--network","{{NETWORK}}","vote-witness","--votes","{{TARGET_ADDR}} 1"]|||execution_error|Error:|| diff --git a/java/qa/lib/case_resolver.py b/java/qa/lib/case_resolver.py deleted file mode 100644 index b24536ef9..000000000 --- a/java/qa/lib/case_resolver.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import shlex -import sys - - -def read_table(path): - rows = [] - if not os.path.exists(path): - return rows - with open(path, "r", encoding="utf-8") as fh: - for raw_line in fh: - line = raw_line.rstrip("\n") - if not line or line.lstrip().startswith("#"): - continue - rows.append(line.split("|")) - return rows - - -def find_row(rows, label): - for row in rows: - if row and row[0] == label: - return row - return None - - -def load_seeds(path): - result = {} - if not path or not os.path.exists(path): - return result - with open(path, "r", encoding="utf-8") as fh: - for raw_line in fh: - line = raw_line.strip() - if not line or "=" not in line: - continue - key, value = line.split("=", 1) - try: - tokens = shlex.split(value) - result[key] = tokens[0] if tokens else "" - except Exception: - result[key] = value - return result - - -def substitute_token(token, values): - updated = token - for key, value in values.items(): - updated = updated.replace("{{%s}}" % key, value) - return updated - - -def placeholder_tokens(token): - found = [] - start = 0 - while True: - open_idx = token.find("{{", start) - if open_idx < 0: - break - close_idx = token.find("}}", open_idx + 2) - if close_idx < 0: - break - found.append(token[open_idx:close_idx + 2]) - start = close_idx + 2 - return found - - -def unresolved_tokens(tokens): - missing = [] - for token in tokens: - missing.extend(placeholder_tokens(token)) - return missing - - -def missing_placeholders(tokens, values): - missing = [] - seen = set() - for token in tokens: - for placeholder in placeholder_tokens(token): - key = placeholder[2:-2] - if values.get(key, "") == "" and placeholder not in seen: - missing.append(placeholder) - seen.add(placeholder) - return missing - - -def split_csv(value): - if not value: - return [] - return [item.strip() for item in value.split(",") if item.strip()] - - -def smoke_expectation(case_type): - mapping = { - "offline-success": "success", - "noauth-success": "success", - "noauth-help": "help_dual", - "auth-success": "success", - "stateful-success": "success", - "stateful-replay-execution": "stateful_replay_execution", - "expected-usage-error": "usage", - "expected-execution-error": "execution", - "excluded-interactive": "skip", - } - return mapping.get(case_type, "invalid") - - -def build_smoke_case(label, row, values): - case_type = row[1] if len(row) > 1 else "" - template = row[2] if len(row) > 2 else "empty" - requires = row[3] if len(row) > 3 else "none" - args_string = row[4] if len(row) > 4 else "" - json_path_exists = split_csv(row[5] if len(row) > 5 else "") - json_path_absent = split_csv(row[6] if len(row) > 6 else "") - error_code = row[7] if len(row) > 7 else "" - text_contains = split_csv(row[8] if len(row) > 8 else "") - preflight = split_csv(row[9] if len(row) > 9 else "") - stream_mode = row[10] if len(row) > 10 and row[10] else "dual" - if stream_mode not in ("text", "json", "dual"): - raise SystemExit("Invalid stream mode for manifest case %s: %s" % (label, stream_mode)) - - raw_tokens = shlex.split(args_string) if args_string else [] - unresolved = missing_placeholders(raw_tokens, values) - resolved_tokens = [substitute_token(token, values) for token in raw_tokens] - unresolved.extend(token for token in unresolved_tokens(resolved_tokens) if token not in unresolved) - - command_tokens = [label] + resolved_tokens - text_argv = [] - json_argv = [] - if stream_mode in ("text", "dual"): - text_argv = ["--network", values["NETWORK"]] + command_tokens - if stream_mode in ("json", "dual"): - json_argv = ["--output", "json", "--network", values["NETWORK"]] + command_tokens - return { - "label": label, - "suite": "stateful" if case_type.startswith("stateful-") else "smoke", - "template": template or "empty", - "requires": requires or "none", - "env_mode": "default", - "expectation": smoke_expectation(case_type), - "text_argv": text_argv, - "json_argv": json_argv, - "json_path_exists": json_path_exists, - "json_path_absent": json_path_absent, - "error_code": error_code, - "text_contains": text_contains, - "text_absent": ["Error:", "Usage:"] if smoke_expectation(case_type) == "success" - else ["Error:"] if smoke_expectation(case_type) == "help_dual" else [], - "preflight": preflight, - "unresolved_placeholders": unresolved, - "excluded": case_type == "excluded-interactive", - } - - -def build_help_case(label, values): - return { - "label": "help__%s" % label, - "suite": "help", - "template": "empty", - "requires": "none", - "env_mode": "default", - "expectation": "help_dual", - "text_argv": ["--network", values["NETWORK"], label, "--help"], - "json_argv": ["--output", "json", "--network", values["NETWORK"], label, "--help"], - "json_path_exists": ["data.help"], - "json_path_absent": [], - "error_code": "", - "text_contains": ["Usage:", "wallet-cli %s" % label], - "text_absent": ["Error:"], - "preflight": [], - "unresolved_placeholders": [], - "excluded": False, - } - - -def build_contract_case(label, row, values): - template = row[1] if len(row) > 1 else "empty" - requires = row[2] if len(row) > 2 else "none" - env_mode = row[3] if len(row) > 3 else "default" - stream_mode = row[4] if len(row) > 4 else "text" - expectation = row[5] if len(row) > 5 else "success" - args_json = row[6] if len(row) > 6 else "[]" - json_path_exists = split_csv(row[7] if len(row) > 7 else "") - json_path_absent = split_csv(row[8] if len(row) > 8 else "") - error_code = row[9] if len(row) > 9 else "" - text_contains = split_csv(row[10] if len(row) > 10 else "") - text_absent = split_csv(row[11] if len(row) > 11 else "") - preflight = split_csv(row[12] if len(row) > 12 else "") - workspace_path_exists = split_csv(row[13] if len(row) > 13 else "") - workspace_path_absent = split_csv(row[14] if len(row) > 14 else "") - - raw_tokens = json.loads(args_json) - if not isinstance(raw_tokens, list) or not all(isinstance(item, str) for item in raw_tokens): - raise SystemExit("Contract args_json must be an array of strings: %s" % label) - unresolved = missing_placeholders(raw_tokens, values) - resolved_tokens = [substitute_token(token, values) for token in raw_tokens] - unresolved.extend(token for token in unresolved_tokens(resolved_tokens) if token not in unresolved) - - text_argv = None - json_argv = None - if stream_mode in ("text", "dual"): - text_argv = resolved_tokens - if stream_mode in ("json", "dual"): - if stream_mode == "dual" and "--output" not in resolved_tokens and not any( - token.startswith("--output=") for token in resolved_tokens): - json_argv = ["--output", "json"] + resolved_tokens - else: - json_argv = list(resolved_tokens) - - return { - "label": label, - "suite": "contract", - "template": template or "empty", - "requires": requires or "none", - "env_mode": env_mode or "default", - "expectation": expectation, - "text_argv": text_argv, - "json_argv": json_argv, - "json_path_exists": json_path_exists, - "json_path_absent": json_path_absent, - "error_code": error_code, - "text_contains": text_contains, - "text_absent": text_absent, - "preflight": preflight, - "workspace_path_exists": workspace_path_exists, - "workspace_path_absent": workspace_path_absent, - "unresolved_placeholders": unresolved, - "excluded": False, - } - - -def main(): - if len(sys.argv) != 3: - raise SystemExit("Usage: case_resolver.py