Skip to content

wallet-cli-4.13.0

Choose a tag to compare

@gummy789j gummy789j released this 07 Sep 15:10
· 74 commits to master since this release
3960316

Notice

Non-mandatory upgrade

New Features

Change

  1. The TypeScript CLI now speaks EVM as well as TRON. A network belongs to one chain family, tron or evm, and that is what decides which commands and flags apply. Twenty-one commands are bound for evm — account balance / info / portfolio, block, chain node / prices, tx send / sign / broadcast / status / info, token balance / info / add / list / remove, contract call / send / deploy, and message sign / typed-data sign — against TRON's full surface, which keeps staking, permissions, proposals, TRC10, GasFree and TronLink multi-sig to itself. Four EVM networks ship built in (Ethereum, Sepolia, BNB Smart Chain and its testnet) alongside the three TRON ones. One seed produces a TRON address and a different EVM address, and a key-backed account serves both families; a watch-only or Ledger account holds one address and therefore one family, with --app tron|ethereum choosing which at import. Fees follow the chain: EIP-1559 gas estimation with --gas-limit / --nonce on EVM, bandwidth and energy on TRON. Calling a command on the wrong family fails with family_mismatch at exit 2 before any node call, using a flag from the other family is invalid_option, and --help tags every family-scoped row (TRON only) / (EVM only). contract deploy --artifact reads a Foundry / Hardhat / TronBox compiler artifact for both bytecode and ABI, so the same deploy command works on either family with the constructor's types coming from the compiler rather than from you. (#982, #984, #990)

  2. Canonical network ids are now CAIP-2, and persisted wallet data upgrades itself at startup. A network is identified by namespace:reference — tron:728126428, tron:3448148188, eip155:56 — the scheme WalletConnect, SIWE and the wider agent tooling already speak; the TRON references are the decimal genesis-hash prefix. Input is unaffected: tron:mainnet, tron:nile and tron:shasta remain permanent aliases, as do short spellings such as nile, sepolia and bsc. Output is a different matter — the envelope's chain.network, the networks listing's id and the config keys all now report the CAIP-2 id, so a consumer that string-matches or keys a map by tron:nile must be updated; an alias resolves once at selection and can never appear in a result. chainId goes with it, on both the envelope's chain object and the networks listing, because it keeps mirroring the id's second segment: TRON's readable mainnet / nile / shasta spellings become 728126428 / 3448148188 / 2494104990, the same decimal genesis-hash prefixes. One rule for both families beats a display string, and the alias column is where readability now lives. Because the rename would otherwise orphan stored data, every invocation — including --help, --version and --json-schema — checks the persisted wallet schema first and upgrades wallets.json and tokens.json before anything else runs. An upgrade asks for consent in a terminal, returns a single migration envelope at exit 0 with originalCommandExecuted: false, and deliberately does not run the command that triggered it, so nothing continues across an unexpected durable-state change; declining is a successful cancellation, and a non-interactive run that needs the master password returns migration_required at exit 2 unless it is piped in with --password-stdin. The commit re-reads each file under the lock, so a concurrent process's account cannot be erased by a stale snapshot. Each file's pre-upgrade copy rides inside the same transaction and is left beside it as wallets.json.v1.bak / tokens.json.v1.bak; nothing removes it later, so it is yours to keep as the rollback or to delete once you are satisfied — it holds the same wallet state as the file it shadows. Going back is a one-way trip in one respect: 4.13.0 leaves a file newer than itself alone and 4.12.0 never read the version field at all, so an older build still reads an upgraded wallets.json, but it scopes user-added tokens by the pre-CAIP-2 network id and would therefore list none of them. (#990, #994)

  3. The Java implementation is now the interactive shell and nothing else. The Standard CLI — its command registry, option parser, output formatter, alias book, non-interactive Ledger signer, and their tests and resources — is deleted, together with the qa/ harness that only ever drove it. The only supported invocation is a bare java -jar wallet-cli.jar, which opens the prompt; --version prints one plain-text line, --help prints the shell usage plus a pointer to the TypeScript CLI, and any other argument prints that one-line pointer on stderr and exits 2. The hint is deliberately not a JSON envelope, so a script cannot mistake it for the removed contract. Non-interactive, scriptable and CI use belongs to the TypeScript CLI, which reached feature parity in 4.12.0. In its place, java/qa-repl/ drives the real shell over a pty against a funded Nile account — wallet lifecycle, account queries, a signed transfer, and the not-logged-in guards — and the Gradle build version is now pinned to the version string the shell itself reports, with a test that fails a release build carrying -SNAPSHOT or drifted versioning. (#991, #990)

  4. The error-code index is published and machine-readable. wallet-cli --json-schema | jq '.errorCodes' returns one entry per code — {"rpc_error": {"exit": 1, "retry": "same", "meaning": "the node answered with an error"}} — so an agent no longer has to scrape prose to learn what a failure means. retry answers "now what": same (resend immediately), later (a lock-up or rate limit — back off first), changed (raise the fee, rebuild the nonce), never. Exit status is now derived from the error code itself rather than from the class that raised it, and a test holds every throw site to its code's declared exit, which settled four codes that had been thrown at both classes. Errors that name a choice rather than a dead end carry error.details.matches; ambiguous_account joins ambiguous_asset_name there, listing the candidate accounts when --account <address> does not resolve to a single interchangeable signer for the family being acted on. (#990, #994)

  5. Every chain HTTP call now goes through one transport, with per-network API keys and request pacing. The TRON and EVM clients share a single transport, and networks.<id>.apiKeyHeader / .apiKey are readable and writable through config: both must be set before a header is sent, the header name is validated as an RFC 9110 token so a hand-edited config.yaml cannot smuggle in a second one, apiKey is write-only on every read surface and forces the 0600 permission check, and a credentialed request refuses to follow redirects so a redirecting endpoint cannot collect the key. A key can also sit in the endpoint's own path, so a result no longer echoes the endpoint back in full: chain node reports its endpoint as the host alone, as does the one the networks listing now carries, and config networks.<id>.httpEndpoint stays the way to read the URL you configured. On top of it, requests to one endpoint are paced — a FIFO queue that starts them no closer together than 400ms — because TronGrid's anonymous tier admits about three requests per second on account-scoped endpoints and answers the rest with HTTP 429, identically whether they arrive concurrently or strictly sequentially. A wide fan-out such as account portfolio now runs clean instead of collecting rejections. Pacing is self-imposed, so the time a request waits is credited back to --timeout rather than charged against it; a request that loses its deadline while queued is dropped rather than sent, so an abandoned broadcast never reaches the node after the caller was told it timed out. (#990, #996)

Bug Fixes

Change

  1. An unreachable node was reported as a fact about the chain. tx status answered not_found at exit 0 when the endpoint simply could not be reached, and in the four-state model that string means "keep polling" — so a script waited forever on a node that was down. TRON's getTransactionInfoById resolves for an unknown hash and rejects only when the node failed, so its resolution is now the evidence that the node answered, after which "not found" is a fact about the transaction. block had the mirror-image defect: a height the node has no record of came back as {block: {}} at exit 0, leaving callers to guess what an empty object meant. A real block always carries blockID, so its absence is now not_found for a specific height, or invalid_node_response for latest — "the chain has no latest block" is not something a node can truthfully state. (#990)

  2. The wallet store could be taken down, or quietly lose a secret, by one unusual account. A wallets.json holding a source kind this build does not know took list down with a redacted internal_error, and the address scan walked every wallet, so a single unreadable account cost every other account its lookup — use, backup and delete by address all failed on a registry list could still show. delete was the costly one: it removed the wallet and wrote the file, then threw while building its receipt, so the account was gone from disk while the caller was told the command had failed. Separately, import dedup matched across source kinds, so importing a mnemonic whose account #0 already existed as a raw private key returned status: "existing" at exit 0 while the seed — the strictly more capable secret — was discarded, and derive then failed on an import reported as successful. Import validation was hardened alongside: a private key outside secp256k1's valid range is now invalid_private_key rather than internal_error, and an imported Web3 keystore's KDF cost is bounded before it runs, since the file's author otherwise chose how much work this process does before any password is checked (a measured c=10^9 runs for about thirteen minutes). Contract reduction: import keystore now delegates to exactly the path import private-key uses, so the account_exists error code has no producer left and is removed. (#990, #996)

  3. exchange trade --slippage did not honour the tolerance that was typed. The floor was derived in basis points, which express two decimal places, so a finer value was rounded to the nearest one in either direction: on a predicted 1,000,000, 1.006% produced a floor of 989,900 where 989,940 was asked for — accepting 40 units less than the user set — while 1.004% refused a trade they would have taken. Below 0.005% the floor snapped to the prediction itself, and since that is an estimate, asking for a very tight tolerance produced no tolerance at all. The arithmetic now runs in millionths of a percent and rounds down, so the floor can only ever come out stricter than requested; every percentage basis points could already express is unchanged. (#990)

  4. The Java shell mis-identified a network configured with only a full node. soliditynode.ip.list is documented as optional and every other consumer honours it, but network identification compared the value the fallback had just copied from fullnode.ip.list — so a config.conf naming only Nile's full node could never match Nile's endpoint pair and was reported as CUSTOM. That is not cosmetic: the GasFree commands refuse any network other than MAIN or NILE, so a user on Nile was told GasFree does not support their network, and the transaction history applied an extra endpoint filter. Identification now compares only the endpoints the config actually supplied; connection behaviour and the fallback are unchanged. (#990)

  5. Two paper cuts in the TypeScript CLI's input and output. tx sign --file and tx broadcast --file failed to parse any file produced by an editor, echo, or jq -r, because the trailing newline every one of them writes was passed through verbatim; surrounding whitespace is now trimmed once at the read site, while internal whitespace still fails downstream rather than silently joining two tokens. And account balance never went through the shared amount formatter, so an 18-decimal coin printed all eighteen fractional digits, 1 wei read as a wall of zeros instead of <0.000001, and the same number appeared grouped in account info but ungrouped in account balance. Text output now follows one rule everywhere; JSON is untouched and keeps the exact base-unit integer. (#990, #994)

Integrity Check

All jar files available in this release are signed via this GPG key:

From the download listings below you should see links to the downloadable jar files as well as sig signature files. To verify the authenticity of any jar file, grab the jar and sig files with the same prefix name and then execute the verification process: GPG signature verification