Release v4.11.0 - #971
Merged
Merged
Conversation
Rebase V4.10.1
Backfill reference pages for every command added since release_v4.11.0, verified against the command registry rather than the drafted command doc: all 66 registered commands now have a page. New pages: permission/ (index, show, update) multi-sig permission structure gasfree/ (index, info, transfer, trace) contact/ (index, add, list, remove) address/ (index, generate) encoding/ (index, convert) tx/approvals, tx/multisig account/activate, account/set Updated: commands/index, account/index, tx/index (multi-sig lifecycle), tx/broadcast (--hex/--file/--dry-run, threshold validation), tx/send (contact names), current (--qr), config (TronLink/GasFree credentials and secret masking), README, and the agent SKILL command map. Examples and error codes were taken from actual command output and the domain rules, so contact limits, the permission operation bitmaps, and the config masking behaviour match the implementation. Also included in this commit: - bump version to 4.11.0 (ts package, runner, java Utils) - name TRON contract types 3/20/32/51 and require unnamed operation bits to be declared via unknownOperationIds - add USDD to mainnet and seed the Nile builtin token book
Positional arguments: yargs inferred a type for the anonymous group tail, so a number-shaped value (0xdeadbeef, 12345) reached its string-typed field as a number and was rejected — `encoding convert 0x...` could not take the EVM address its own help example shows. Type the tail as strings so the raw token survives parsing (coercing afterwards is lossy: 1e5 would already be 100000), and route single-segment leaves through the same `args..` capture so one binding path serves every positional command. tx sign: replace --check with --offline and verify online by default. A co-signer outside the transaction's permission group, or one signing twice, now fails before any signature is produced, instead of emitting a hex that only `tx broadcast` rejects — possibly after it has been passed to further signers. The default receipt carries the documented permission and weight-progress block; --offline keeps air-gapped signing and says plainly that approval state was not checked. The 4.10.0 --transaction JSON route is untouched. account activate --dry-run: render the derived account-creation fee as its total rather than "[object Object]", and stop an unrecognised fee object from reaching the scalar fallback that stringified it. BREAKING CHANGE: a field exposed as a positional no longer accepts its `--<field>` spelling. This affects `config --key/--value`, `block --number`, `contact add --name/--address`, `contact remove --name`, `encoding convert --input` and `gasfree trace --trace-id`. The positional form is unchanged, and the global --account flag still works on `use`/`rename`/`delete`/`backup`.
…round trip Text receipts carried machine-readable values that belong in json: the contract-type enum sat next to the human operation name on every Type row, and `permission show` printed the raw operations bitmap. Both are now json-only; the enum remains the fallback when a contract type has no human name. `tx broadcast` resolves the full approval state to decide broadcastability but projected none of it into text: --dry-run printed a header and a fee line while json carried permission, weight progress and approved signers. Reuse renderApproval for it, which also exposed two smaller faults — the multi-sign fee was printed twice whenever it was non-zero, and the Tx row was always empty because the broadcast view names the field `transaction`, not `tx`. renderApproval moves to its own module so tx.ts can use it without a cycle back into multisig.ts. `permission update` rejected the workflow its own documentation recommends. `permission show` always emits unknownOperationIds, almost always `[]`, and an empty list forced operationsHex to be supplied alongside it — so a structure exported by show could not be fed back after editing `operations`. An empty list declares "no unnamed bits", exactly what re-encoding `operations` produces, so it now needs no bitmap; a non-empty list still does, leaving the review guarantee intact. The remaining mismatch error now names the fix instead of only stating the disagreement. Eight fields across four commands shipped with no help description, five of them positionals rendered as bare names under Args. All are filled in, and a registry-wide test now fails if a command ships an undocumented input.
Resolves the 15 findings raised on the PR. Verified each against the code before changing anything; several turned out to differ from the report. Correctness - contract deploy reported an address derived from the pre-prepare txID. --permission-id / --expiration rewrite raw_data, moving the txID and with it the deployed address. Derived in refreshTransactionIdentity now, and captured from the prepared transaction. The default path was never wrong. - Post-checks that run after an already-confirmed, already-paid transaction no longer convert it into a command failure (account activate / set, permission update). New warnOnPostCheck degrades both the unreadable-read and the mismatch paths to warnings, keeping the txid. - GasFree polling no longer discards the accepted receipt: transport flakiness retries inside the deadline, other non-integrity errors return submitted with the traceId, and integrity errors now carry it in details. - getTokenInfo no longer swallows transport failures. Measured against a live node: a view method the contract lacks returns result:true with an empty payload, so the catch was only ever hiding node outages, which GasFree then escalated to gasfree_integrity and tx send to exit 2. - Keypair writer removes its own unfinished file, so a failed write no longer blocks every retry of the same path via O_EXCL. - TronLink signer rosters are bound to the on-chain permission, not just the signed subset; weights render from chain. A stale snapshot after a permission update was displayed as chain-validated. Contract and output - Bootstrap failures produce a v1 error envelope and the right exit code instead of a bare fatal: line; config read/parse failures classify as invalid_config without echoing file content. - Exclusive-output conflicts use one code, class and exit status (output_exists / UsageError / 2) across backup and address generate. - Text output escapes bidi and zero-width characters rather than passing them through, so a chain-controlled name cannot reorder what is printed beside it. JSON stays byte-exact. - Shielded transfers are named in the codec table so the refusal is actionable; four contract types cannot be decoded, not the three the docs claimed. Architecture - TransactionArtifactWriter port added; the inbound command no longer names the outbound implementation. Pinned by a boundary test, since depcruise cannot see type-only edges without failing on pre-existing type-only cycles. Docs - meta.warnings declared as the mixed type it actually is, with guidance for consumers. - --wait receipts documented as reporting the outcome in data.stage, never in success. - The 11 leaf pages that had fallen behind the shared transaction options now document --build-only / --permission-id / --expiration, and no longer claim every mode needs the master password. Guarded by a test. - Corrected claims that were never implemented (vote status warning code), no longer reachable (account set provider_error), or absent (config file mode requirement and its Windows exemption).
…t build depcruise only saw the graph that survives compilation, so a boundary violation carrying nothing but a type was invisible to it — which is how an inbound command came to name an outbound implementation directly. Turning on tsPreCompilationDeps fixes that, but it also closes four pre-existing type-only cycles, so those had to go first. Every cycle ran through one pivot: ChainFamily was defined in the family registry, and the registry depends on the address codec at runtime. Any type that named a family therefore had to reach through the registry, closing types -> family -> address -> types. The identity now lives in its own dependency-free module, re-exported from the registry, so both public import paths are unchanged. The price cycle was simpler: coingecko imported its own port type back through the barrel that re-exports it, rather than from the port. With the cycles gone, tsPreCompilationDeps is on and all four boundary rules cover type-only edges. That subsumes boundary.test.ts, added as a stopgap for the inbound->outbound case, so it is removed: the guarantee moves from one hand-written string check to the build, and from one edge to four. Verified the replacement is real by pointing the inbound command back at the outbound class: depcruise reports inbound-does-not-know-outbound.
--create was ported from MultiSignService.createTransaction(), which has no call site anywhere in the Java tree — dead code whose request shape had never been validated by the service. Two things were wrong as a result. The POST signature covered permission_name and tx_id, but the service signs only sign_version/channel/secret_id/ts/uuid/address, so every --create was rejected with 4000 Authentication failed. And the shape itself does not exist: the service has no empty collection, and derives the starting weight from the signature the transaction arrives with. Uploading unsigned raw_data returns 20004 Error param. --create now signs the transaction locally and submits it, which opens the collection at 1 of N. The CLI surface is unchanged — it still takes the unsigned hex a --build-only run produces — but it needs the master password now, and the originator no longer signs a second time. signChecked() already covers expiry, permission membership, and repeat signatures, so the manual getSignWeight/approvals preamble is gone, along with the port's create() and TronLinkCreateRequest. Two related fixes: - Service rejections carried only a numeric code, which cannot separate a bad signature (4000) from a bad parameter (20004) or a stale one (20305). The service's own wording now reaches error.details.providerMessage, stripped of control characters and bounded at 200 chars. - One record that no longer reconciles with the chain used to fail the whole listing, which is the normal state of any account whose permission changed while an old transaction sat queued. Listing now marks such a record unverified, says why, and shows the rest of the page. It is never presented as chain-validated, its state column reads unverified rather than what the service claimed, and awaitingMySignature is forced false. Acting on one still refuses. "Could not check" is kept distinct from "checked and disagreed": a node outage propagates instead of reading as a clean page. Also: once the threshold is met the service broadcasts the transaction itself, so the receipt now says to confirm with tx info before broadcasting by hand. Verified end to end on Nile: --create -> --sign -> on chain (a480c8f6…5797, block #69,754,801). New tests are mutation-checked; the ten mutants covering the routing, receipts, degradation boundary, and message handling all fail the suite. Bundles an unrelated in-flight refactor: KeypairWriter now takes a {out}|{name} target so filesystem layout stays out of AddressService.
The v0.1.2 help audit found that a command's --help routinely omitted what the command actually requires, so a reader could only learn the rule by failing a run. Fix that at the source: declare the constraints, render them, and cover each with a test. Mutually exclusive options (A-1) Options that are jointly required each rendered "[optional]" — individually true, but read together it says the whole set may be omitted, which is exactly what Zod rejects. Add a declarative ExclusiveGroup on the spec, rendered as a labelled block and emitted in the --json-schema catalog so agents see it too. Two flavours: "exactly-one" drops the misleading tag, "at-most-one" keeps it (tx send's asset selector really is optional — omit all three and you send native TRX). Eight groups, three of which the audit did not list: tx send x2 and message sign, whose --message-stdin never appeared under Options at all. Values and limits (A-2, A-3, A-4, A-5) --permission-id now names its groups (0=owner, 1=witness, 2-9=active). --expiration states the 24h cap it enforces and the ~60s node default when omitted. --build-only names both multi-sig routes it feeds, not just the TronLink one. address generate --out names the default path a private key lands on, which mattered before the run, not only in the receipt. Irreversible and costly operations (A-6, A-8, A-9, A-11) account set says each field can be set once and never changed, and is not `rename`. permission update says where the input shape comes from and that the lockout warning does not block the submission. The permission group page explains the model and names the update fee; the gasfree group page says fees come out of the token being sent. Wording that was false (B, root) --wait claimed "after broadcast ... returns the submitted txid", shown verbatim on gasfree transfer, which broadcasts nothing and returns a trace id. Reword globally rather than add a per-command override — a second copy is how this drift started. --timeout enumerated only RPC and device, leaving out the service APIs it equally bounds. chain was the one family-scoped group missing its (tron) tag. Also: permission update carried its own copies of three shared txModeFields descriptions; they now reuse the shared source, and a test keeps them there. Fourteen reference pages get the same --permission-id / --expiration wording, with the sync test tightened from "flag is present" to "semantics match", reading the expected text out of the schema instead of restating it. Ledger reach (separate thread, folded in) backup checks exportability before demanding a master password: a Ledger-only keystore may have no password sentinel, so the old order trapped the user on a prompt no answer satisfies. account activate and account set --id now require a software signer up front — the TRON app has no parser for AccountCreateContract or SetAccountIdContract and answers 0x6a80, which is now classified with wording covering both an unsupported contract type and a malformed payload. Tests 792 -> 833. Two defects the tests caught that review would not: an exclusive group whose member was named in camelCase resolved to nothing and vanished silently (the renderer now throws), and each shared-description edit left a stale duplicate behind in permission update.
TronLink renders `resource` as its enum value (`1`), while TronWeb's encoder only understands the name and drops an unrecognized value silently. The re-encoded bytes then disagreed with the provider's own raw_data_hex, so every freeze / delegate record read as forged. Against Nile, 7 of one account's 20 records failed; because the failure was thrown from the per-record projection, one such historical record hid the entire queue and `tx multisig` exited 1. Map the value back to its name before encoding. The raw_data_hex / txID equality checks remain the arbiter of whether the result is accepted, so this widens what can be read, not what can be trusted. Two conditions around it, kept distinct: a record this client cannot reconstruct now costs its own row (counted in `unreadable`, reported below the table) rather than the page, while a record that decodes and then disagrees still fails the command as before. The error names the codec's reason instead of blaming the service for bytes we could not rebuild. Verified against the reported account on Nile: 7 failures -> 0.
…e node `permission update` submits through the shared pipeline, whose preflight only checked that the signer belongs to the transaction's permission group and has not already signed. Nothing compared the accumulated signature weight against the permission threshold, so an owner holding one key of a 2-of-2 group signed locally and broadcast a transaction the node was always going to reject — while the same transaction handed to `tx broadcast` failed locally with `not_authorized`, never touching the network. Preflight now returns a `SignerAuthorization` gate that the pipeline consults after signing and before broadcasting, in broadcast mode only: sign-only and build-only still return a partial signature, which is how a multi-signature transaction legitimately reaches its threshold. The weight is projected from data the preflight already fetched, so the check costs no extra RPC, and commands that pass no preflight — ordinary transfers — are untouched. Both entry points now raise the shortfall through one `assertThresholdReached`, so their error code and message are identical by construction. Closes PER-005 / DEFECT-005. Validated on Nile: an under-threshold `permission update --broadcast` is refused three times out of three with no txid and no fee burned, and the same update lands once both owners sign.
Feat/ts v4.11.0
matrix-agent116
approved these changes
Aug 5, 2026
zerodevblock-cyber
approved these changes
Aug 5, 2026
|
Ok
…On Wed, 5 Aug Reiwa 8 at 22:08, Steven Lin ***@***.***> wrote:
Merged #971 <#971> into
master.
—
Reply to this email directly, view it on GitHub
<#971?email_source=notifications&email_token=CBJNDRTRBNH7M5ZMLEPBRAL5INEYLA5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJQGE3TCNBVG43DBJTSMVQXG33OVJZXKYTTMNZGSYTFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#event-29017145760>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/CBJNDRVKLL75TV3JVKXL6NL5INEYLAVCNFSNUABFKJSXA33TNF2G64TZHMYTCOJWHA4DGMJVHNEXG43VMU5TKMBWHE2DAOBWGA32C5QC>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
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
Multi-sig is now a first-class workflow in the TypeScript CLI.
permission showandpermission updateread and replace an account's permission structure on chain (the latter burns 100 TRX and warns before an owner lockout); every broadcast command gained--permission-id <n>,--expiration <ms>, and a new--build-onlyearly-exit mode alongside--dry-run/--sign-only.tx signnow takes a transaction hex via--hex/--file, appends exactly one signature while preserving prior ones, and reports how far the accumulated weight is from the threshold — refusing before a key is ever decrypted if the account is not in the permission group (not_authorized) or has already approved (already_signed);--offlinekeeps the air-gapped path.tx approvalsis the read-only companion that shows the permission group, threshold, accumulated weight, approved signers, and expiration without signing. (#963)tx multisigadds optional collaboration through the TronLink multi-sig service. Where the on-chain path passes a hex from person to person, the service holds the transaction, accumulates signatures, and pushes notifications: the default mode lists transactions awaiting this account,--createsigns an unsigned hex and opens a collection at1 / N,--sign <txId>co-signs a pending one, and--watchkeeps a WebSocket open and reports only the count awaiting your signature (never transaction content). Once the threshold is reached the service broadcasts. Credentials (tronlinkSecretId/tronlinkSecretKey/tronlinkChannel) are set withconfigand are per-environment; without them the command fails withtronlink_credentials_missing. (#963)gasfreebrings gas-less token transfers to the TypeScript CLI.gasfree inforeports your GasFree address, activation status, nonce, and fee schedule;gasfree transfersigns an EIP-712 structured-data transfer and submits it to the GasFree provider, which puts it on chain and charges the fee in the transferred token itself, so no TRX is required;gasfree trace <traceId>follows the provider'sWAITING → INPROGRESS → CONFIRMING → SUCCEED / FAILEDstates. Because submission goes to a provider rather than a node, the receipt carries atraceIdinstead of atxId— follow it with--waitorgasfree trace, nottx status. Credentials aregasfreeApiKey/gasfreeApiSecretviaconfig. (#963)New account-lifecycle and local-utility commands.
account activatecreates a not-yet-existing address on chain without transferring any asset, with the payer covering the creation fee;account setwrites the on-chain name or account id (permanent on mainnet, so it is deliberately one-at-a-time). A local contact book —contact add/list/remove, stored0600in the config directory — lets a saved name be used wherever a recipient is expected (tx send --to,gasfree transfer --to).encoding convert <input>auto-detects an input and prints every equivalent representation (TRON base58 / TRON hex / EVM / public key, or hex / Base64 / Base58Check), rejecting secrets outright.address generateproduces a throwaway keypair offline, writing the private key to a0600file rather than the terminal unless--print-secretis passed.current --qrrenders a scannable receive-address QR in text mode without unlocking or touching the network. (#963)The Java documentation was split out of
java/README.mdinto a browsablejava/docs/tree. The README shrank from a single ~2,700-line page to an overview with quick links; commands now live underdocs/commands/by domain (wallet, account, transfers, staking, multisig, GasFree, DEX, proposals, contracts, …), withdocs/concepts/,docs/guide/, and a field-by-fielddocs/reference/config.md. Stale planning and QA scratch documents were removed. No Java behavior changed in this release apart from the version constant. (#963)Bug Fixes
Change
A transaction the node rejected could be reported as submitted. TronWeb does not throw on rejection — it hands back the node's response verbatim, and a rejected
/wallet/broadcasttransactioncarries noresultfield at all, so the previousresult === falsecheck never fired and the CLI returned a success envelope with a txId for a transaction that was never accepted. Acceptance is now white-listed (result !== trueis a rejection) on both the object and--hexbroadcast paths, surfacing the node's reason astransaction_rejected. (#963)blocklost precision on large int64 fields. Block responses were parsed through JavaScript numbers, so protobuf int64/uint64 values beyondNumber.MAX_SAFE_INTEGERcame back rounded. Blocks are now fetched and parsed losslessly, with out-of-range integers preserved as exact decimal strings. (#963)--dry-runreported doomed staking requests as fine.stake freezeandstake unfreezeare rejected by the node at broadcast time when the amount exceeds the available balance or the amount actually staked for that resource, but--dry-runnever reaches the node — so a request that could not possibly succeed previewed cleanly. Both now pre-flight against the chain and fail withinsufficient_balance/insufficient_stake. Relatedly,contract sendnow warns when the supplied--fee-limitis below the energy estimate at the current energy price, and the staked-amount computation was corrected for the node's habit of omitting the defaultBANDWIDTHenum infrozenV2. (#963)Chain-controlled text could repaint the terminal. Permission names, token names and symbols, and co-signer labels are written by whoever put them on chain. Terminal control bytes were already stripped, but bidirectional and zero-width formatting characters passed straight through —
U+202Ereverses the display order of everything after it, letting a crafted name change how the address or weight beside it appears, and zero-width characters can make two different names render identically. Text mode now escapes them visibly (<U+202E>) rather than dropping them, so legitimate right-to-left text still displays normally and tampering is obvious. JSON output is never rewritten. (#963)Filesystem failures surfaced as raw OS errors.
backupand the atomic config/keystore writers let NodeErrnoExceptions escape, producinginternal_errorand, on a failed write, leaving a truncated or empty file at the destination. Failures are now typed asio_errorwith the partial artifact removed, and the multi-file atomic write reports a rolled-back failure distinctly from one that committed but could not confirm durability. Two new config-file conditions are also reported explicitly:invalid_configwhenconfig.yamlcannot be parsed (the parser detail is withheld, since it quotes the offending line and may carry a credential) andinsecure_configwhen the file holds service credentials but is a symlink or group/world-readable. (#963)Positional arguments rejected number-shaped values. Yargs inferred a type for the anonymous positional tail, so a value such as
0xdeadbeefor12345arrived at its string-typed field as a number and was rejected. The tail is now typed as strings so the raw token survives parsing. As a consequence, a field exposed as a positional no longer also accepts its--<field>spelling — this affects the undocumentedconfig --key/--valueandblock --numberforms; the documented positional form is unchanged, and the global--accountflag still works onuse/rename/delete/backup. (#963)