Release v4.10.0 - #960
Merged
Merged
Conversation
Two standalone signing commands so the CLI can sign payloads built elsewhere: - `tx sign` — sign a transaction constructed outside the CLI, output the signed result for later `tx broadcast`. Pure signer (no owner/contract policy) but enforces payload integrity: txID must be sha256(raw_data_hex), and raw_data must re-encode to those bytes where the contract type is decodable. Verified end-to-end on Nile (confirmed on-chain) and offline. - `typed-data sign` — EIP-712/TIP-712 in a new top-level `typed-data` group. Byte-for-byte identical output to the agent-wallet SDK for the same key and payload. Verified on a Ledger via Speculos (real TRON app 0.7.6). Both support software and Ledger accounts. Integrity checking lives in a shared tron/tx-integrity module enforced by both the software strategy and the Ledger adapter, so a hardware account is never the weaker signer. Device signing preliminaries (precheck, awaiting_device, abort-on-timeout) are extracted into obtainSignature and reused by every signing path; the abort signal now actually closes the transport. Also in this change: - Map Ledger app-setting APDU errors (0x6a8b/c/d) to actionable messages instead of an opaque UNKNOWN_ERROR. - Remove the network "required" value: it resolved identically to "optional" (default-network fallback), so it only produced a false help line. - Fix the Requires help block to state whether a command prompts, and print full signatures in sign-only text receipts instead of a truncated txID. - Speed up the test suite ~13x by mocking scrypt in keystore-heavy specs (production crypto untouched; crypto.test.ts still uses real scrypt).
…aryType, and opaque Ledger errors Three independent fixes to the standalone `tx sign` / `typed-data sign` paths: - tx-integrity: add a contract-type binding (layer 1.5) between the txID hash check and tronweb's txCheck. It decodes raw_data_hex's outer protobuf envelope — which succeeds even for contract types tronweb cannot re-encode (Market, Shielded, legacy) — and asserts its contract type(s) equal what raw_data declares. Fails closed on undecodable protobuf, unknown type, or any type/count mismatch. Closes the disguise where a benign raw_data hides a different signed contract while layer 2 is skipped. Only same-type differing fields of an unencodable contract remain unverifiable (no encoder/decoder exists). Pinning tests guard the tronweb assumptions (the 6 unencodable families, the enum normalization). - typed-data: reject a supplied primaryType that is not the root struct (i.e. is referenced as a field by another type, including via array fields). ethers always signs the inferred root, so a non-root primaryType meant the receipt — and, worse, the Ledger hashStruct — disagreed with what was actually signed. Pure structural check in the domain layer, so both backends inherit it; omitted and root primaryType are unaffected. UsageError -> exit 2. - ledger: map APDU 0x6d00 (INS_NOT_SUPPORTED) to a generic, chain- and operation-agnostic ledger_unsupported error instead of a misleading auth_required. The app version lacks the instruction, or the wrong app is open. All TDD'd with real-behavior tests. Full suite, typecheck, and depcruise pass.
… withdraw Three QA-surfaced gaps where the CLI trusted a downstream default/empty response/node acceptance instead of validating at the source: - GLOB-006: an out-of-range global value flag (e.g. --timeout -1) was silently coerced to undefined and fell back to the default. coerceGlobalValue now returns a discriminated ok/reason result; the runner surfaces a rejected flag as invalid_value (exit 2) before any dispatch, uniformly for every value flag (--timeout, --output, --wait-timeout). - MGT-CONTRACT-014: `contract info` on a non-contract address returned an empty-but-successful contract. The adapter now treats an empty getContract response as not_found at the anti-corruption boundary. - MGT-STAKE-014: `stake withdraw` with nothing withdrawable broadcast a tx that never confirmed, leaving a phantom txid. A pre-flight check on the withdrawable amount now rejects with nothing_to_withdraw before build/broadcast (dry-run too).
feat(ts): add tx sign and typed-data sign commands
ops: update version
zerodevblock-cyber
approved these changes
Jul 27, 2026
matrix-agent116
approved these changes
Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New Features
Change
Add standalone signing commands to the TypeScript CLI:
tx signandtyped-data sign.tx signsigns a transaction built elsewhere and prints the signed payload without broadcasting, so the signed result can be handed totx broadcastlater; because it appends to an existingsignaturearray rather than replacing it, a partially signed transaction can be passed from signer to signer until a TRON multi-sig permission threshold is met.typed-data signsigns EIP-712 / TIP-712 structured data and returns the signature, the digest that was signed, and the resolved primary type; it accommodates real-world payloads by ignoringEIP712Domainintypes, acceptingvalueas an alias formessageand accepting TRON base58 addresses inaddressfields, while rejecting a declaredprimaryTypethat is not the message root, since a nested type can never be what gets signed. Both commands are offline for software accounts (--networkis optional) and both work with Ledger accounts. (#957)Enforce TRON transaction payload integrity before any signature is produced, in the software strategy and on Ledger alike. A TRON transaction states its content three times —
raw_data(what a caller reads),raw_data_hex(what the node executes) andtxID(the only thing actually signed) — and nothing in the format forces the three to agree, so a payload that displays a harmlessraw_datacan carry the hash and bytes of a different transaction. Signing now refuses (tx_integrity) unlesstxIDis the sha256 ofraw_data_hex, the contract types encoded inraw_data_hexmatch thoseraw_datadeclares, andraw_datare-encodes to exactly those bytes wherever the contract type can be encoded. The checks reject nothing a correct transaction builder produces. (#957)Make Ledger failures actionable rather than opaque. APDU statuses that previously surfaced as
UNKNOWN_ERROR (0x6a8c)are now mapped to typed errors that name the fix:ledger_setting_requiredfor the TRON app's "Sign by Hash", "Transactions data" and "Custom contracts" settings, andledger_unsupportedwhen the app version does not implement the instruction. Device signing also honours an abort signal, closing the transport immediately instead of holding the native handle until the timeout expires. (#957)Bug Fixes
Change
Fail closed on invalid global flag values in the TypeScript CLI. An out-of-range or non-matching value for a global flag (for example
--timeout 0or an unrecognized--output) silently fell back to the flag's default and the command ran under settings the caller never asked for. Such a value is now reported asinvalid_valuebefore any command dispatch, so no RPC is attempted. (#957)Reject operations that cannot succeed instead of returning misleading results.
contract infoon an address with no deployed contract returned a valid-but-empty contract normalized from TronWeb's empty response, and now fails as not found.stake withdrawwith nothing withdrawable passed the node's broadcast-time checks but never confirmed, leaving a phantom txid; the withdrawable amount is now queried up front and the command fails withnothing_to_withdraw. (#957)