feat: v4.13.0 (evm support & java standardCLI deprecate) - #990
Merged
Conversation
Code Smells for evm-extension
…asUsed union - eslint.config.js: the scripts/**/*.mjs block never applied — scripts/** is in the global ignores, so `eslint .` is byte-identical with and without it. - evm.ts: #send was a one-line pass-through to #request with a single caller; its doc comment moves to #request, which is what it describes. - TxInfoView.gasUsed: production only ever writes String(...), so the `number` half was kept alive solely by a stale test fixture. Fixture aligned to the shape production emits; the rendered output is unchanged (formatInt already handles decimal strings). Claude-Session: https://claude.ai/code/session_01Reu4W1oqGVqUWkMpAjR9Xo
…network API keys Consolidates the TRON and EVM client adapters onto a single HttpTransport (adapters/outbound/http) and builds the per-network credential pair on top of it. Transport - TronRpcClient and EvmRpcClient accept a NetworkDescriptor or an endpoint config; raw fetch calls, URL joining and timeout wiring now live in one place. - tronweb is handed explicit HttpProviders so it carries the same headers. - TronGridHistoryReader goes through the transport; its failures no longer echo the underlying message. Per-network API key - networks.<id>.apiKeyHeader / .apiKey are readable and writable via `config`. Both must be set before a header is sent, and the header name is validated as an RFC 9110 token so a hand-edited config.yaml cannot smuggle in a second header. - apiKey is write-only on every read surface and forces the 0600 check on config.yaml, which now looks inside `networks` rather than only at top level. - Credentialed requests refuse to follow redirects on both the fetch and the tronweb path, so a redirecting endpoint cannot collect the key. Config surface - A network renders as its configurable fields rather than a bare endpoint, so a new field shows up in every view at once. Listings still trim the endpoint to its host; naming one network reveals it in full. - `config` prints the document as a tree. Nested keys carry no trailing colon -- the ids at that level contain colons themselves. Fixes found while re-testing the above on Nile and Sepolia - EVM transport timeouts report `timeout`, not `rpc_error`, matching TRON and the documented meaning of the code. - --args/--verb/--group/--source are rejected as user-typed flags. They are yargs plumbing keys that must stay in the per-command allowlist, so they worked as undocumented aliases: `contract call --args <addr>` bound into a slot the command has not got and answered 0, a wrong result that reads like a real one. The check runs on the raw tokens, before yargs folds the two sources together, and a meta-test pins that no command declares a field by those names. Startup migration - The gate runs ahead of every surface and stops after upgrading rather than running the command that triggered it. Verified on Nile and Sepolia: reads across every transport path, TRX/TRC20 and ETH transfers confirmed on-chain, the credential observed on the wire on all three paths, and the redirect target receiving nothing once one is configured. Claude-Session: https://claude.ai/code/session_01JSxAgrttq54UacDxykm7gy
The source-of-truth doc it named was deleted in 7a33dcd, leaving the one instruction that tells a reader where the boundary rules live pointing at nothing. Split the reference across what actually holds each part now: the table below it for the boundaries, .dependency-cruiser.cjs for enforcement, machine-interface.md for the JSON contract, and docs/adr/ for the reasoning. Claude-Session: https://claude.ai/code/session_01JSxAgrttq54UacDxykm7gy
5389b00 pointed at ts/docs/adr/ after verifying it existed on disk, which is the wrong test: ts/.gitignore:9 ignores it, so it holds zero tracked files and is not there for anyone who clones. That replaced one dead reference with another. What remains is what the repo actually carries: the table, the dependency-cruiser config that enforces it, and machine-interface.md. Claude-Session: https://claude.ai/code/session_01JSxAgrttq54UacDxykm7gy
…sion feat: evm extension
Extracting `tronTxModeFields` out of the shared `txModeFields` moved --permission-id and --expiration behind an explicit opt-in. The commands that serve two families re-added them in their TRON binding (tx send, contract send/deploy) and the governance writes get them via `governanceTxModeFields`, but the 18 single-family TRON writes kept spreading `txModeFields` alone and silently lost both flags. The effect was not cosmetic: signing under a non-owner active permission group — the whole point of TRON multi-sig — became impossible on stake, vote, reward, asset, exchange and account activate/set, and there was no way to extend a transaction's expiry while collecting co-signatures. The same account could still do both through `tx send`, which is what makes this a gap rather than a decision. These commands are single-family, so the fields belong in `baseFields` directly. Not `governanceTxModeFields`: that also overrides --build-only's description, which would change help text on 18 commands as a side effect. Nothing below the CLI layer needed touching — the value already rides `...transactionMode(input)` into the pipeline's `prepare` hook, which is why the services never name it and why the break was invisible to them. Covered by transaction-options.test.ts: both flags present on all 18, identity-equal to the exported field objects so a second copy cannot drift, --build-only's shared description pinned, and argv coercion.
The EVM family went public on this branch — `FAMILY_REGISTRY` now carries `evmFamily`, and `networks` lists seven networks across two families — but ts/docs still described a TRON-only tool. Every page was checked against the running binary rather than against the diff: the current and the merge-base `--json-schema` catalogs were compared to find the 56 commands whose spec actually moved, and every example below was captured from a real run on tron:nile and evm:11155111. Command pages (91) now state, per command, which families serve it: - Portable commands document both families where they genuinely differ — the field SETS, not just the values: `account info` reports resources and permissions on TRON, nonce and code on EVM; `chain prices` answers in energy/bandwidth or in gas and shares no field between them; `tx info` nests `info` or `receipt`; `contract call` returns words or one blob. - TRON-only commands say so, and name `family_mismatch` — the error an EVM network returns before any node call. - Family-scoped flags are split into "TRON only" / "EVM only" tables, since using one on the other family is `invalid_option`. - `contract deploy` is rewritten for the artifact-based interface that replaced --bytecode/--params. - Account pages carry the per-family address model: `addresses` and `derivationPath` are maps now, and text listings show one family at a time while JSON carries every one. machine-interface.md gains the family rules, the discovery call, and a pointer to the published error-code index; the `chain` block note is corrected — `list` and `current` emit one without contacting a node. concepts/networks.md documents the evm-gas model beside tron-resource, and troubleshooting.md keys the new failure modes. Also corrected, pre-dating this branch: the stake info unfreezing tree, `Max delegatable` reporting TRX, the token book's official layer no longer being mainnet-only, and chain params claiming the CLI cannot create proposals. Verified: 0 broken links or anchors across 125 files, every documented flag matches the live JSON Schema, and all 91 pages keep the house layout.
Execution blueprint for removing org.tron.walletcli.cli from the Java implementation, measured against the tree rather than restated from the requirements doc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxauSno5mhxX4DmBr39iso
…ckage Standard CLI removal prep. CommandErrorException lives in org.tron.walletcli.cli but leaks into the main sources in 127 places, including REPL-reachable methods (getUSDTBalance, gasFreeTransferInternal). Deleting the cli/ package with the type still inside would not compile. Move it verbatim to org.tron.core.exception, alongside the exceptions signTransactionForCli already throws (CancelException). Package move only: same class name, same semantics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxauSno5mhxX4DmBr39iso
buildEvmUnsignedTx used to skip the pending-nonce read entirely whenever --nonce was given, so an explicit nonce ahead of the account's pending one produced no warning at all — a caller could leave a nonce gap and have no way to notice from the command's own output. Always read the pending nonce and compare against it, warning when the explicit nonce is above it. To keep the explicit-nonce path from becoming newly fragile, make that read best-effort in that case only: it exists solely to produce the warning, so a failed read now just skips the warning instead of failing a build that already has everything it needs. Without --nonce the same read remains a hard dependency, since there it IS the nonce.
build.gradle still declared version '1.0-SNAPSHOT', unrelated to the version the shell itself reports (Utils.VERSION). A release jar could therefore ship labeled 1.0-SNAPSHOT while the running CLI answers --version with something else entirely, and nothing caught the drift. Set the Gradle version to 4.13.0 and add VersionConsistencyTest, which pins Gradle's project.version (passed through as a system property) to Utils.VERSION and refuses a -SNAPSHOT suffix outright — so a release build carrying stale or placeholder versioning fails the test suite instead of shipping.
Comments cited ADRs (as `ADR-00XX` and as `docs/adr/000X` paths) and architecture-source-of-truth.md. Neither is in version control -- the ADR directory is gitignored and that second file does not exist anywhere -- so a maintainer reading these comments was pointed at nothing. Where the citation was decoration the sentence already stood on its own and the reference is simply gone. Where the ADR carried the reasoning, the reason is now inline: why `--path` is unimplemented, what makes the node-rejection allowlist narrow, which migration leans on the address re-encoding, and what the alias rule supersedes. No behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
The selection filtered on `evm:`, but production keys moved to `eip155:` with the CAIP-2 rename, so `evmNetworks` was empty and the three `it.each` blocks over it produced no cases at all. The file reported green while checking nothing: not the EIP-55 checksums, not the explicit decimals, not the duplicate-contract rule -- the checks that stand between a mistyped contract address and lost funds. Fixed the namespace and added a case asserting the selection is non-empty, so the next rename fails the suite instead of silently emptying it. Before: 4 tests. After: 13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
The one-hop fold walks the WHOLE alias book, builtin entries included, so it could reach backwards. A config keyed on `tron:3448148188` is accepted -- ids resolve before the book, so such an entry is merely dead -- but the fold followed it and rewrote the builtin `nile -> tron:3448148188` to that key's own target. `--network nile` would then build, sign and broadcast on MAINNET. A canonical id is now a terminal target, never a hop. Accepting the dead entry is left alone: that a canonical id can never be shadowed is deliberate and already covered by a test. Also carries this file's share of the ADR-comment cleanup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
…snapshot `plan()` reads outside any lock -- it has to, because the gate must know whether a password will be needed before it prompts -- so the document it captured travels through notice, consent and an interactive password prompt before `commit()` finally takes the lock. Writing that snapshot back erases anything another process committed in between. Verified with two real processes: A plans, B migrates and creates an account, A commits. Before this change B's account was gone from wallets.json and its encrypted key blob orphaned; after it, B's account survives. `commit()` now re-reads each file under the lock and migrates what is on disk. A version comparison is enough to detect interference -- the gate is absolute, so a second process cannot touch a stale file without migrating it, which moves the version. Already migrated: skip, writing nothing. Moved somewhere unexpected: refuse, leaving the file untouched. The pre-migration copy is taken from the fresh read too, so it can no longer be overwritten with the snapshot. `StaleFile.doc` is dropped: carrying the document is exactly the stale read the re-read exists to avoid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
`primaryType` says WHICH struct is being signed, and the EIP-712 JSON-RPC schema carries it precisely because the type map alone does not determine the root. A payload may declare types the chosen root never reaches -- one shared dictionary covering `Permit` / `Order` / `Cancel` is the ordinary case. Handing the whole map to the encoder made it see several roots and refuse, so a payload that passed our own validation failed at signing with `signing_rejected`: a code that reads as a key or device fault when nothing was wrong with either. `normalizeTypedData` now narrows `types` to the closure the declared root reaches. Being in the domain, this fixes all four signing paths at once -- EVM software, EVM Ledger, TRON software, TRON Ledger -- with no adapter change, and it removes the Ledger paths' latent disagreement between the root `hash()` infers and the `primaryType` `hashStruct()` is given. Narrowing cannot change an existing signature: every type a single-root payload declares is reachable from that root. Confirmed end to end -- a payload with an unrelated extra type now signs to the same digest as the same payload without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
…ked for Basis points express two decimal places, so a finer `--slippage` was rounded to the nearest one -- in either direction. On a predicted 1,000,000: 1.006% gave 989,900 where 989,940 was asked for, accepting 40 units less than the floor the user set; 1.004% gave 990,000 instead of 989,960, refusing a trade they would have taken. Under 0.005% the floor snapped to the prediction itself, and since that is an estimate the trade reverts on any last-unit disagreement -- asking for a very tight tolerance produced no tolerance at all. Millionths of a percent keep the arithmetic integral while making every precision a person types exact, and the tolerance now rounds DOWN rather than to nearest, so the floor can only ever come out stricter than requested. The comment always claimed that direction; the code did not implement it. Every percentage basis points could already express is unchanged, the spec's 1% example included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
An imported file chooses its own KDF cost and the KDF runs to completion BEFORE the MAC is checked, so the file's author decides how much work this process does without knowing any password. `dklen` had only a lower bound and pbkdf2's `c` had none: measured, c=10,000,000 takes 7.8s and scales linearly, so c=10^9 runs for ~13 minutes; dklen=2^34 did not finish at all. Both are now bounded before the KDF runs. scrypt's `n`, `r` and `p` are deliberately left to @noble/hashes, which refuses a non-power-of-two `n` and caps 128*r*(n+p+1) at a 1 GiB maxmem -- a tighter bound here would only risk refusing a file that library accepts. Those library refusals were also escaping as bare Errors, which surface as `internal_error` and send the reader off to debug the CLI over a file that simply names impossible parameters; they are now reported as `invalid_keystore` like every other malformed-file rejection. The costs real wallets write (our own export, MEW's 262,144) are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
`soliditynode.ip.list` is documented as optional, and every other consumer of that choice honours it -- ApiClient switches to local-create when it is absent. Network identification did not: it compared the value the fallback had just copied from `fullnode.ip.list`, so a config naming only Nile's full node could never match Nile's (fullnode, solidity) 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 applies an extra endpoint filter once the network is CUSTOM. Identification is now a testable `identifyNetwork`, comparing only the endpoints that were actually supplied; an omitted one says nothing about which network this is and is not compared. Connection behaviour and the fallback are unchanged. The config reference said the network is inferred by comparing both endpoints, which was never what a fullnode-only config would get -- corrected. Pre-existing defect, not introduced by this release. The unit tests cover the identification rule; a fullnode-only start against Nile still wants one manual run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRyKvkmEc4viwzrtM4SeYR
error.details.code collided in name with the outer error.code but meant something entirely different depending on the site: the TRON node's own rejection code (broadcastTransaction, tx broadcast --hex) or the TronLink collaboration service's response code (multisig list). A reader following error.details.code could easily assume it was the same taxonomy as the envelope's error.code. Rename to nodeCode (paired with the existing nodeMessage, both TRON node values) and providerCode (paired with the existing providerMessage, both TronLink values), so each pair's source is unambiguous at a glance. This is an external contract change: details.code no longer appears in the JSON envelope for these errors. Consumers must switch to details.nodeCode or details.providerCode.
A node that could not be reached came back as state "not_found" at exit 0. In the
four-state model that string means "keep polling", so a script waited forever on
an endpoint that was simply down — the same defect the EVM side fixed, on the
family that carries this project's production traffic.
tronweb throws "Transaction not found" for an unknown hash, so that rejection
cannot separate the two on its own. getTransactionInfoById can: it resolves ({}
on Nile) for an unknown hash and rejects only when the node failed. Letting it
propagate makes its resolution the evidence that the node answered, after which
the other rejection is a fact about the transaction. No message matching needed.
Classifying a rejection replaced the message with canned category text and moved the node's words to details.nodeMessage — which text mode never renders. So the same insufficient_balance showed the node's actual numbers through a dry run and a bare category through a broadcast. Every classified path now reads "<category>: <what the node said>", the shape the revert case already used. The category stays scannable and stable; the specifics stay visible to the person reading a terminal. details.nodeMessage is unchanged for machine readers, and TRON's redaction still runs before anything is folded in.
…the listing A wallets.json holding a source type this build does not know took `list` down with a redacted internal_error: SOURCE_KINDS returns undefined for it and enumerateAddresses reads fields it does not have. One unusable account made every usable one unreachable. The listing now skips what it cannot read and the command warns, naming the wallet ids, so the accounts that do work stay available and the ones that do not are visible rather than silently absent.
The same spelling was handled two opposite ways: `contract deploy --code 0X…` passed the local hex check and was left for ethers to refuse later as invalid_transaction at exit 1, while `--account 0X…` was rejected outright by a regex that insisted on a lowercase prefix. 0X is valid hex notation. Only our downstream library insists on 0x, and that is not something a caller should have to carry. Both paths now accept it and normalise. Note that isEvmAddress's checksum comparison had to normalise the prefix too, or a correctly-checksummed 0X address would have failed the very check that was meant to accept it.
A stretch of the v4.13.0 defect work was driven through briefs written in Chinese, and the comments that came out of it followed the brief's language rather than the file's. Chinese landed in the error-code dictionary, the help renderer, the token book, a test's expectations, and CLAUDE.md itself. This repository is English throughout — a comment only works if the next person to open the file can read it. Comments, commit messages, test names and docs stay English regardless of what language the work was discussed in. Comment text only; no behaviour changes.
v4.13.0 removed the standard CLI, and with it the only end-to-end harness the Java client had. The interactive shell is now the single entry point and nothing exercised it, so `./gradlew build` passing said nothing about whether it worked. qa-repl drives a real REPL over a pty with expect: wallet lifecycle, account queries, a signed 1 TRX transfer on Nile, and the not-logged-in guards. A second script pins the other half of the change -- the entry point takes only --version and --help, and points everything else at the TypeScript CLI with exit code 2. Secrets stay out of the repo: the key and mnemonic come from ts/.private/.env.test. Claude-Session: https://claude.ai/code/session_01JByiuXufMHVGvoF4pKYUdr
Skipping an account of an unknown source kind taught `list` to survive one, but every path that NAMES one still reached an accessor the kind cannot satisfy — SOURCE_KINDS[...] or enumerateAddresses — and answered with a redacted internal_error. `delete` was the costly one. It removed the wallet, wrote the file, and only then read SOURCE_KINDS[...].hasSecret to build its receipt, so the throw arrived after the write: the account was already gone from disk while the caller was told the command had failed, leaving an orphaned key blob behind. The address scan was the widest. It walked every wallet through enumerateAddresses, so a single unreadable account cost every OTHER account its address lookup — `use`, `backup` and `delete` by address all failed on a registry that `list` could still show. Both now follow the rule the listing already sets. The scan skips what it cannot read, so an address the caller can see keeps resolving. A command that names an unreadable account gets encoding_error naming the wallet, raised before the operation starts rather than while assembling its result.
`tx broadcast --dry-run` answered "would this go through?" by comparing the nonce against the mined count, but the build path — `tx send`, `contract send` and `contract deploy` — only ever warned about a nonce that was too HIGH. A dry run with a nonce the account had already used reported a fee plan for a transaction that could never be mined, and eth_estimateGas does not catch it because it ignores the nonce entirely. The check goes in tx-build, which all three share, so they inherit it from the same place the gap warning already comes from. Read at "latest", not "pending": a nonce below pending may be a deliberate replacement of a transaction still in the mempool, which is legitimate — only one already mined is spent. The read happens only for --dry-run with an explicit --nonce, since a derived nonce IS the pending count and cannot be behind, so no other mode pays the extra round trip; and it is best-effort, so a dry run that cannot reach the node still builds. Raised before onNonce and before the estimate: a doomed deploy should not hand back a predicted contract address it can never occupy, nor spend an estimate whose failure would report the wrong reason.
zerodevblock-cyber
approved these changes
Sep 3, 2026
`formatAmount` caps text amounts at six fractional digits, drops trailing zeros and reports a non-zero amount below that precision as `<0.000001`. `formatSun`/`formatWei`/`formatGwei` were wired to it; the account path never was, so an 18-decimal coin printed all eighteen digits and 1 wei read as a wall of zeros rather than "small but not nothing". `humanBalance` now scales through `formatAmount` (serving both `account balance` and `token balance`), and the portfolio column formats from the `rawBalance` + `decimals` that `holding()` already reports instead of reprinting the pre-scaled string. json is untouched: it keeps the exact base-unit integer. This also settles a disagreement the two commands had about the same number — `account info` already grouped it as `9,915.80311` while `account balance` printed `9915.80311`. Grouping now comes from the one formatter, and the doc examples are updated to what the renderer emits. The existing tests only exercised `formatAmount` itself, which is why a formatter that was never called still looked covered; the new ones assert rendered output.
`92695f03` wired `--out` into the EVM binding, but sign.md still told readers the option was accepted and ignored there, so the docs were steering people away from a feature that works. While correcting that, state the part the page only ever implied: `--out` adds a file, it does not remove the hex from the result. That is the already-shipped shape — the TRON example prints `hex` and `out` side by side — and a body that appeared or vanished with a flag would force every consumer to branch on it.
The case asserted that `base` does not resolve and called that "EVM is not currently exposed". No such gate exists — both families are registered unconditionally and every builtin EVM network and alias resolves, which the evm-gas and alias-book cases in this same file assert. `base` fails only because it is an L2, deliberately absent from both tables. Naming it for a gate that isn't there invites someone to "restore" one. The rule under test is that a name reaching neither table is rejected.
The skill file was deleted in 13325ab, leaving two dead links and a stale "skills" entry in the npm files list.
TronGrid's anonymous tier admits ~3 requests per second on account-scoped endpoints and rejects the rest with HTTP 429, identically whether they arrive concurrently or strictly sequentially. Serialising alone does not help; the spacing between request starts is what matters. RequestPacer is a FIFO queue that admits at most maxInFlight requests and starts them no closer together than minIntervalMs (measured: 300ms still drew 429s, 350ms ran clean, 400ms ships for margin). PacedHttpTransport decorates a transport so nothing reaching requestText can skip it, and TronRpcClient also installs the pacer on tronweb's own providers, which own a separate axios client that would otherwise bypass it entirely. Pacing is self-imposed, so it must not be charged against a caller's timeout: withDeadline installs the deadline before the work starts and keeps it reachable through AsyncLocalStorage, and the pacer credits every wait back before taking it. Credit reaches every enclosing deadline, since #wrap nests. Queue time behind in-flight requests is the one wait that cannot be credited up front; a request that loses its deadline there is dropped rather than sent, so an abandoned read does not spend node budget and an abandoned broadcast never reaches the node after the caller was told it timed out.
…vider swap tronweb installs a brand-new HttpProvider on setFullNode/setSolidityNode/ setEventServer — as do setHeader/setFullNodeHeader/setEventHeader, which delegate to those three — dropping the pacing patch held on the old instance. Nothing calls them today, so the failure mode was silent: requests keep succeeding, just unpaced, until the endpoint starts answering 429. Those three now re-pace whatever they installed, guarded by a WeakSet so a provider is never wrapped twice (which would make one request take two permits and deadlock the pacer). The pacer, deadline and client tests ran on real timers, which made them both slow and only able to assert "at least the interval, minus slack". On a virtual clock they assert the exact schedule instead — gaps of [400, 400], a fan-out finishing at exactly 3 x 400ms — and the three files went from 6.6s to 0.35s.
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.
No description provided.