wallet-cli-4.13.0
Notice
Non-mandatory upgrade
New Features
Change
-
The TypeScript CLI now speaks EVM as well as TRON. A network belongs to one chain family,
tronorevm, and that is what decides which commands and flags apply. Twenty-one commands are bound forevm—account balance/info/portfolio,block,chain node/prices,tx send/sign/broadcast/status/info,token balance/info/add/list/remove,contract call/send/deploy, andmessage 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|ethereumchoosing which at import. Fees follow the chain: EIP-1559 gas estimation with--gas-limit/--nonceon EVM, bandwidth and energy on TRON. Calling a command on the wrong family fails withfamily_mismatchat exit2before any node call, using a flag from the other family isinvalid_option, and--helptags every family-scoped row(TRON only)/(EVM only).contract deploy --artifactreads 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) -
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:nileandtron:shastaremain permanent aliases, as do short spellings such asnile,sepoliaandbsc. Output is a different matter — the envelope'schain.network, thenetworkslisting'sidand theconfigkeys all now report the CAIP-2 id, so a consumer that string-matches or keys a map bytron:nilemust be updated; an alias resolves once at selection and can never appear in a result.chainIdgoes with it, on both the envelope'schainobject and thenetworkslisting, because it keeps mirroring the id's second segment: TRON's readablemainnet/nile/shastaspellings become728126428/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,--versionand--json-schema— checks the persisted wallet schema first and upgradeswallets.jsonandtokens.jsonbefore anything else runs. An upgrade asks for consent in a terminal, returns a singlemigrationenvelope at exit0withoriginalCommandExecuted: 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 returnsmigration_requiredat exit2unless 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 aswallets.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 upgradedwallets.json, but it scopes user-added tokens by the pre-CAIP-2 network id and would therefore list none of them. (#990, #994) -
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 barejava -jar wallet-cli.jar, which opens the prompt;--versionprints one plain-text line,--helpprints the shell usage plus a pointer to the TypeScript CLI, and any other argument prints that one-line pointer on stderr and exits2. 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-SNAPSHOTor drifted versioning. (#991, #990) -
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.retryanswers "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 carryerror.details.matches;ambiguous_accountjoinsambiguous_asset_namethere, listing the candidate accounts when--account <address>does not resolve to a single interchangeable signer for the family being acted on. (#990, #994) -
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/.apiKeyare readable and writable throughconfig: both must be set before a header is sent, the header name is validated as an RFC 9110 token so a hand-editedconfig.yamlcannot smuggle in a second one,apiKeyis write-only on every read surface and forces the0600permission 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 nodereports itsendpointas the host alone, as does the one thenetworkslisting now carries, andconfig networks.<id>.httpEndpointstays 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 asaccount portfolionow runs clean instead of collecting rejections. Pacing is self-imposed, so the time a request waits is credited back to--timeoutrather 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
-
An unreachable node was reported as a fact about the chain.
tx statusanswerednot_foundat exit0when 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'sgetTransactionInfoByIdresolves 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.blockhad the mirror-image defect: a height the node has no record of came back as{block: {}}at exit0, leaving callers to guess what an empty object meant. A real block always carriesblockID, so its absence is nownot_foundfor a specific height, orinvalid_node_responseforlatest— "the chain has no latest block" is not something a node can truthfully state. (#990) -
The wallet store could be taken down, or quietly lose a secret, by one unusual account. A
wallets.jsonholding a source kind this build does not know tooklistdown with a redactedinternal_error, and the address scan walked every wallet, so a single unreadable account cost every other account its lookup —use,backupanddeleteby address all failed on a registrylistcould still show.deletewas 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 returnedstatus: "existing"at exit0while the seed — the strictly more capable secret — was discarded, andderivethen failed on an import reported as successful. Import validation was hardened alongside: a private key outside secp256k1's valid range is nowinvalid_private_keyrather thaninternal_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 measuredc=10^9runs for about thirteen minutes). Contract reduction:import keystorenow delegates to exactly the pathimport private-keyuses, so theaccount_existserror code has no producer left and is removed. (#990, #996) -
exchange trade --slippagedid 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 — while1.004%refused a trade they would have taken. Below0.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) -
The Java shell mis-identified a network configured with only a full node.
soliditynode.ip.listis documented as optional and every other consumer honours it, but network identification compared the value the fallback had just copied fromfullnode.ip.list— so aconfig.confnaming only Nile's full node could never match Nile's endpoint pair and was reported asCUSTOM. 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) -
Two paper cuts in the TypeScript CLI's input and output.
tx sign --fileandtx broadcast --filefailed to parse any file produced by an editor,echo, orjq -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. Andaccount balancenever 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 inaccount infobut ungrouped inaccount 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:
- PUB: 1254 F859 D2B1 BD9F 66E7 107D F859 BCB4 4A28 290B
- UID: build@tron.network
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