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/README.md b/README.md index e074bb382..541204100 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

wallet-cli

- A command-line wallet for the TRON network — interactive in Java, agent-first in TypeScript + A command-line wallet for TRON and selected EVM networks — interactive in Java, agent-first in TypeScript

@@ -17,7 +17,7 @@ This repository holds **two independent implementations** that share the same pu - **[Java](java/README.md)** — the original, full-featured reference CLI. An interactive prompt (REPL) you drive by hand. - **[TypeScript](ts/README.md)** — an agent-first rewrite for automation. Standard subcommands with a stable JSON envelope, built for scripts, CI, and AI agents. -Both manage the same kind of wallet on the same networks — your address is identical regardless of which you use. They cover the same TRON feature surface and differ in how you install and drive them. Pick one and read its own README for depth; this page gives you the basics of each so you can choose. +Both manage TRON wallets, but they are independent implementations rather than interchangeable account stores. Do not assume every derived account has the same address across implementations: check the recorded BIP44 path when migrating. The TypeScript implementation additionally supports selected EVM networks. ## At a glance @@ -25,12 +25,12 @@ Both manage the same kind of wallet on the same networks — your address is ide | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **What it is** | The mature, full-feature reference CLI. | A newer rewrite focused on programmatic integration. | | **Runtime** | JVM — built with Gradle, run as a `.jar`. Uses the [Trident](https://github.com/tronprotocol/trident) SDK. | [Node.js](https://nodejs.org) **20+**. | -| **Install** | `git clone` + `./gradlew build` (see [Setup](java/README.md#setup)) | `npm install -g @tron-walletcli/wallet-cli` | +| **Install** | `git clone` + `cd wallet-cli/java && ./gradlew build` (see [Setup](java/README.md#setup)) | `npm install -g @tron-walletcli/wallet-cli` | | **How you drive it** | An **interactive prompt only** — start it, then type commands at `>`. | **One-shot subcommands** — `wallet-cli ` from your shell. Interactive prompts only for secret input. | | **Command style** | PascalCase verbs: `RegisterWallet`, `SendCoin`, `GetBalance`. Amounts in **SUN** (1 TRX = 1,000,000 SUN). | Noun-verb subcommands: `create`, `tx send`, `account balance`, with `--flags`. | | **Output for scripts** | Human-readable text. | Stable JSON via `-o json` ([`wallet-cli.result.v1`](ts/docs/machine-interface.md)) + fixed exit codes (`0`/`1`/`2`). | -| **Config / networks** | `config.conf` (net type + full node), or `SwitchNetwork` at runtime. Mainnet · Nile · Shasta · custom. | `--network` flag / `config` command. `tron:mainnet` · `tron:nile` · `tron:shasta`. | -| **Signing** | Software keystore · Ledger. | Encrypted local keystore · Ledger. Secrets never via argv/env. | +| **Config / networks** | `config.conf` endpoints, or `SwitchNetwork` at runtime. Mainnet · Nile · Shasta · custom. | `--network` flag / `config` command. Three TRON networks plus Ethereum, Sepolia, BNB Smart Chain, and its testnet. | +| **Signing** | Software keystore · Ledger. | Encrypted local keystore · Ledger. Secrets enter via stdin/TTY, never argv or dedicated secret environment variables. | | **Feature scope** | **The full surface** — wallets and transfers, staking, voting and rewards, governance, contracts, TRC10, and the on-chain exchange. | **The full surface** — HD wallets, TRX/TRC20/TRC10 transfers, staking & delegation, voting & rewards, governance proposals & super-representative operation, contract call/deploy/governance, TRC10 issuance, the on-chain Bancor exchange, multi-sig, GasFree transfers, message signing, and on-chain queries. | | **Best for** | People at a terminal who want every TRON capability. | Scripting, CI pipelines, and AI agents. | | **Full docs** | [java/README.md](java/README.md) | [ts/README.md](ts/README.md) | @@ -41,9 +41,9 @@ Interactive only. Build it, start the prompt, then type commands: ```console $ git clone https://github.com/tronprotocol/wallet-cli.git -$ cd wallet-cli && ./gradlew build && cd build/libs +$ cd wallet-cli/java && ./gradlew build && cd build/libs $ java -jar wallet-cli.jar # opens the interactive prompt -> RegisterWallet 123456 # create a keystore (password 123456) +> RegisterWallet # prompts twice for the password, then for mnemonic length > Login # unlock it > GetAddress # your TRON address > GetBalance # TRX balance @@ -58,7 +58,7 @@ Install from npm, then run subcommands directly from your shell: ```console $ npm install -g @tron-walletcli/wallet-cli $ wallet-cli create --label main # prompts for a master password -$ wallet-cli account balance --network tron:nile +$ wallet-cli account balance --network tron:3448148188 $ wallet-cli account balance -o json # one wallet-cli.result.v1 JSON frame ``` 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/README.md b/java/README.md index 46462fe76..64c954e48 100644 --- a/java/README.md +++ b/java/README.md @@ -18,7 +18,7 @@ git clone https://github.com/tronprotocol/wallet-cli.git ### Configuration -A minimal `config.conf` only needs a network type and a full node to talk to: +A minimal `config.conf` needs a full-node endpoint. `net.type` does not select the network; it only controls whether `grpc.mainnet.apiKey` is applied. The startup network is inferred from the configured node endpoints. ``` net { @@ -40,13 +40,13 @@ You can also switch networks at runtime with the [`SwitchNetwork`](docs/commands - **Compile and run**: ```console - $ cd wallet-cli + $ cd wallet-cli/java $ ./gradlew build $ cd build/libs $ java -jar wallet-cli.jar ``` -wallet-cli connects to java-tron via the gRPC protocol, which can be deployed locally or remotely. Configure the java-tron node IP and port in `src/main/resources/config.conf`, or use `SwitchNetwork` to switch among mainnet, testnets (Nile and Shasta), and custom networks. +wallet-cli connects to java-tron via gRPC. At startup it first looks for `config.conf` in the current working directory, then falls back to the bundled classpath resource. Use `SwitchNetwork` to switch among mainnet, testnets (Nile and Shasta), and custom networks. ## Quickstart @@ -55,13 +55,13 @@ Build, create an account, and send your first transfer — all from the interact ```console # 1. Build $ git clone https://github.com/tronprotocol/wallet-cli.git -$ cd wallet-cli && ./gradlew build && cd build/libs +$ cd wallet-cli/java && ./gradlew build && cd build/libs # 2. Start the interactive wallet $ java -jar wallet-cli.jar # 3. In the wallet prompt: create an account (or ImportWallet), unlock, and inspect it -> RegisterWallet 123456 # create a keystore with password 123456 +> RegisterWallet # prompts twice for the password, then for mnemonic length > Login # unlock the account > GetAddress # show your address > GetBalance # TRX balance 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/commands/contract.md b/java/docs/commands/contract.md index 224143f31..6b47145fb 100644 --- a/java/docs/commands/contract.md +++ b/java/docs/commands/contract.md @@ -111,18 +111,20 @@ Example: ## TriggerConstantContract ```console -> TriggerConstantContract [ownerAddress] contractAddress method args isHex fee_limit value token_value token_id +> TriggerConstantContract ownerAddress contractAddress method args isHex [value token_value token_id] ``` -- `OwnerAddress` — the address of the account that initiated the transaction, optional, default is the address of the login account. +- `ownerAddress` — required. Pass a base58 address, or `#` to use the logged-in account. - `contractAddress` — smart contract address. - `method` — the name of the function and parameters; refer to the example. - `args` — parameter value; if you want to call `receive`, pass `#` instead. - `isHex` — the format of the parameters `method` and `args`; hex string or not. -- `fee_limit` — the most TRX allowed for consumption. +- `value` — optional call value in SUN; when supplied, `token_value` and `token_id` are required too. - `token_value` — number of TRC10. - `token_id` — TRC10 id; if not, use `#` instead. +The command accepts exactly five parameters without value/token fields, or eight parameters with all three optional fields. It does not take `fee_limit`. + Example: ```console diff --git a/java/docs/commands/multisig.md b/java/docs/commands/multisig.md index 2a514d2f1..69e9ade41 100644 --- a/java/docs/commands/multisig.md +++ b/java/docs/commands/multisig.md @@ -2,6 +2,8 @@ Configure account permissions, co-sign transactions, inspect signature weight, and use TronLink multi-sign. For the underlying permission model, see [concepts/multisig](../concepts/multisig.md). +Many REPL write commands accept `-m` only as their final token. That switch routes the operation through the interactive multi-sign flow instead of the normal single-signer broadcast. Support is command-specific; use the command's built-in usage text before appending it. + ## How to use the multi-signature feature of wallet-cli Multi-signature allows other users to access the account in order to better manage it. There are three types of access: diff --git a/java/docs/commands/stake-v1-legacy.md b/java/docs/commands/stake-v1-legacy.md index aab565883..c2ab6e215 100644 --- a/java/docs/commands/stake-v1-legacy.md +++ b/java/docs/commands/stake-v1-legacy.md @@ -11,12 +11,13 @@ After the funds are frozen, the corresponding number of shares and bandwidth wil **Freeze operation is as follows:** ```console -> freezeBalance [OwnerAddress] frozen_balance frozen_duration [ResourceCode:0 BANDWIDTH, 1 ENERGY] [receiverAddress] +> freezeBalance [OwnerAddress] frozen_balance frozen_duration [ResourceCode:0 BANDWIDTH, 1 ENERGY, 2 TRON_POWER] [receiverAddress] ``` - `OwnerAddress` — the address of the account that initiated the transaction, optional, default is the address of the login account. - `frozen_balance` — the amount of frozen funds, the unit is Sun. The minimum value is **1000000 Sun (1 TRX)**. - `frozen_duration` — freeze time, this value is currently only allowed for **3 days**. +- `ResourceCode` — `0` BANDWIDTH; `1` ENERGY; `2` TRON_POWER only when `getAllowNewResourceModel` is enabled. TRON_POWER cannot be delegated, so omit `receiverAddress` when using `2`. For example: @@ -33,7 +34,7 @@ After the freezing time expires, funds can be unfrozen. **Unfreeze operation is as follows:** ```console -> unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH, 1 CPU) [receiverAddress] +> unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH, 1 ENERGY, 2 TRON_POWER) [receiverAddress] ``` ## How to delegate resource @@ -55,7 +56,7 @@ The latter two parameters are optional. If not set, the TRX is frozen to obtain ### UnfreezeBalance (undelegate) ```console -> unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH, 1 CPU) [receiverAddress] +> unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH, 1 ENERGY) [receiverAddress] ``` The latter two parameters are optional. If they are not set, the BANDWIDTH resource is unfrozen by default; when the `receiverAddress` is set, the delegated resources are unfrozen. diff --git a/java/docs/commands/stake-v2.md b/java/docs/commands/stake-v2.md index 42debeb2f..40e46b545 100644 --- a/java/docs/commands/stake-v2.md +++ b/java/docs/commands/stake-v2.md @@ -12,7 +12,7 @@ FreezeV2-based staking, resource delegation, and unfreeze withdrawal — the cur - `OwnerAddress` — the address of the account that initiated the transaction, optional, default is the address of the login account. - `frozen_balance` — the amount of frozen, the unit is the smallest unit (Sun), the minimum is 1000000 sun. -- `ResourceCode` — 0 BANDWIDTH; 1 ENERGY. +- `ResourceCode` — `0` BANDWIDTH; `1` ENERGY; `2` TRON_POWER only when `getAllowNewResourceModel` is enabled. Example: @@ -60,7 +60,7 @@ wallet> GetTransactionById 82244829971b4235d98a9f09ba67ddb09690ac2f879ad93e09ba - `OwnerAddress` — the address of the account that initiated the transaction, optional, default is the address of the login account. - `unfreezeBalance` — the amount of unfreeze, the unit is the smallest unit (Sun). -- `ResourceCode` — 0 BANDWIDTH; 1 ENERGY. +- `ResourceCode` — `0` BANDWIDTH; `1` ENERGY; `2` TRON_POWER only when `getAllowNewResourceModel` is enabled. Example: diff --git a/java/docs/commands/transfer-trc10.md b/java/docs/commands/transfer-trc10.md index 3492cafbe..078476850 100644 --- a/java/docs/commands/transfer-trc10.md +++ b/java/docs/commands/transfer-trc10.md @@ -170,7 +170,7 @@ assetV2 Query the list of all the tokens by pagination. Returns a list of tokens that succeed the token located at offset. ```console -> ListAssetIssuePaginated address code salt +> ListAssetIssuePaginated offset limit ``` Example: diff --git a/java/docs/commands/vote-reward.md b/java/docs/commands/vote-reward.md index 38ff86fff..beec5ec9a 100644 --- a/java/docs/commands/vote-reward.md +++ b/java/docs/commands/vote-reward.md @@ -14,7 +14,7 @@ Voting requires share. Share can be obtained by freezing funds. For example: ```console -> freezeBalance 100000000 3 1 address # Freeze 10TRX and acquire 10 units of shares +> freezeBalance 10000000 3 1 address # Freeze 10 TRX and acquire 10 units of shares > votewitness 123455 witness1 4 witness2 6 # Cast 4 votes for witness1 and 6 votes for witness2 at the same time @@ -97,7 +97,7 @@ Apply to become a super representative candidate. ``` ```console -> CreateWitness TEDapYSVvAZ3aYH7w8N9tMEEFKaNKUD5Bp 007570646174654e616d6531353330363038383733343633 +> CreateWitness TEDapYSVvAZ3aYH7w8N9tMEEFKaNKUD5Bp https://sr.example.com ``` ### UpdateWitness @@ -105,7 +105,7 @@ Apply to become a super representative candidate. Edit the URL of the SR's official website. ```console -> UpdateWitness TEDapYSVvAZ3aYH7w8N9tMEEFKaNKUD5Bp 007570646174654e616d6531353330363038383733343633 +> UpdateWitness TEDapYSVvAZ3aYH7w8N9tMEEFKaNKUD5Bp https://sr.example.com/v2 ``` ## ListWitnesses diff --git a/java/docs/commands/wallet.md b/java/docs/commands/wallet.md index b7a8b4e1f..f24ab3bfc 100644 --- a/java/docs/commands/wallet.md +++ b/java/docs/commands/wallet.md @@ -73,7 +73,7 @@ Import a derived account from a Ledger device into wallet-cli. ```console wallet> ImportWalletByLedger -((Note:This will pair Ledger to user your hardward wallet) +(Note:This will pair Ledger to user your hardware wallet) Only one Ledger device is supported. If you have multiple devices, please ensure only one is connected. Ledger device found: Nano X Please input password. diff --git a/java/docs/guide/command-flow.md b/java/docs/guide/command-flow.md index 8fa03a444..63defc19c 100644 --- a/java/docs/guide/command-flow.md +++ b/java/docs/guide/command-flow.md @@ -3,14 +3,14 @@ A worked end-to-end example of an interactive session: build and run, register, back up, inspect, issue an asset, and transfer it. ```console -$ cd wallet-cli +$ cd wallet-cli/java $ ./gradlew build $ ./gradlew run -> RegisterWallet 123456 (password = 123456) -> login 123456 +> RegisterWallet (prompts twice for the password, then for mnemonic length) +> login (prompts for the password) > getAddress address = TRfwwLDpr4excH4V4QzghLEsdYwkapTxnm' # backup it! -> BackupWallet 123456 +> BackupWallet (prompts for the password) priKey = 1234567890123456789012345678901234567890123456789012345678901234 # backup it!!! (BackupWallet2Base64 option) > getbalance Balance = 0 diff --git a/java/docs/guide/getting-started.md b/java/docs/guide/getting-started.md index c1866ed0e..cd4fc3cdb 100644 --- a/java/docs/guide/getting-started.md +++ b/java/docs/guide/getting-started.md @@ -9,13 +9,13 @@ Build, create an account, and send your first transfer — all from the interact ```console # 1. Build $ git clone https://github.com/tronprotocol/wallet-cli.git -$ cd wallet-cli && ./gradlew build && cd build/libs +$ cd wallet-cli/java && ./gradlew build && cd build/libs # 2. Start the interactive wallet $ java -jar wallet-cli.jar # 3. In the wallet prompt: create an account (or ImportWallet), unlock, and inspect it -> RegisterWallet 123456 # create a keystore with password 123456 +> RegisterWallet # prompts twice for the password, then for mnemonic length > Login # unlock the account > GetAddress # show your address > GetBalance # TRX balance diff --git a/java/docs/reference/config.md b/java/docs/reference/config.md index f2b4622bd..0b33975cb 100644 --- a/java/docs/reference/config.md +++ b/java/docs/reference/config.md @@ -1,10 +1,10 @@ # Configuration reference -Full reference for `config.conf`. wallet-cli reads the node config from `src/main/resources/config.conf`. You can also switch networks at runtime with the [`SwitchNetwork`](../commands/network.md) command, so editing `config.conf` is only needed for a custom node or the advanced features below. +Full reference for `config.conf`. At startup, wallet-cli first reads `./config.conf` from the process working directory. If that file does not exist, it loads the bundled classpath resource (`src/main/resources/config.conf` in a source checkout). You can also switch networks at runtime with [`SwitchNetwork`](../commands/network.md). ## Minimal config -A minimal `config.conf` only needs a network type and a full node to talk to: +A minimal `config.conf` needs a full-node endpoint. Keeping `net.type` is useful when configuring a mainnet API key, but it does not select the active network: ``` net { @@ -99,7 +99,7 @@ tronlink = { | Field | Purpose | |---|---| -| `net.type` | Network type (e.g. `mainnet`). | +| `net.type` | Controls whether `grpc.mainnet.apiKey` is loaded. It does not select the active network. | | `fullnode.ip.list` | Full node endpoint(s) `ip : port`. | | `soliditynode.ip.list` | Optional Solidity node endpoint(s). | | `ledger_debug` | Enable Ledger debug output. | @@ -111,7 +111,9 @@ tronlink = { ## Connecting to Java-tron -wallet-cli connects to Java-tron via the gRPC protocol, which can be deployed locally or remotely. Configure the Java-tron node IP and port in `src/main/resources/config.conf` so wallet-cli can talk to the node. You can also use `SwitchNetwork` to switch among mainnet, testnets (Nile and Shasta), and custom networks — see [commands/network](../commands/network.md). +wallet-cli connects to Java-tron via gRPC. The startup network is inferred by comparing `fullnode.ip.list` and `soliditynode.ip.list` with the built-in Mainnet, Nile, and Shasta endpoints; any other pair is `CUSTOM`. Consequently, `net.type = mainnet` with Nile endpoints still starts on Nile. Check the endpoints themselves before sending funds. + +To override the bundled file without rebuilding the jar, place `config.conf` in the directory from which you launch `java -jar`. You can also use `SwitchNetwork` to switch among mainnet, Nile, Shasta, and custom endpoints at runtime — see [commands/network](../commands/network.md). ## See also 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