feat: ts v0.1.1 - #950
Conversation
matrix-agent116
left a comment
There was a problem hiding this comment.
Summary
This PR adds substantial TypeScript CLI functionality and a broad command-contract refactor. However, the new password-rotation path can leave the keystore partially migrated if a rename fails during the multi-file commit, so it is not safe to merge yet.
- Blocker: Make password rotation recoverable across the entire set of encrypted blobs and the verifier. The current sequential renames are only atomic per file; add rollback, journaling, or an equivalent recovery mechanism, plus a test that fails after at least one rename has succeeded.
- Suggestion: Validate
waitTimeoutMswhen loadingconfig.yaml, not only when it is written throughConfigService, so manually supplied invalid values cannot enter the effective configuration.
| for (const { tmp } of staged) { try { unlinkSync(tmp); } catch { /* best-effort */ } } | ||
| throw e; | ||
| } | ||
| for (const { tmp, path } of staged) renameSync(tmp, path); // atomic per file |
There was a problem hiding this comment.
[blocker] These sequential renames are atomic only per file. If a later renameSync fails, earlier keystore blobs have already been replaced while the remaining blobs—and possibly the verifier—still use the old password. changePassword then reports a rollback even though the vault may require two passwords or become unusable. Please make the commit recoverable across all files and add a test that injects a failure after one rename succeeds.
There was a problem hiding this comment.
Totally agree with this issue. This has been resolved in the latest commit.
| } | ||
| if (raw.defaultOutput === "json" || raw.defaultOutput === "text") defaultOutput = raw.defaultOutput; | ||
| if (typeof raw.timeoutMs === "number") timeoutMs = raw.timeoutMs; | ||
| if (typeof raw.waitTimeoutMs === "number") waitTimeoutMs = raw.waitTimeoutMs; |
There was a problem hiding this comment.
[suggestion] Please apply the documented non-negative-integer validation here as well. A manually edited config.yaml currently accepts negative or fractional waitTimeoutMs values even though ConfigService rejects them, allowing an invalid effective configuration.
02c1d4a to
b1df797
Compare
…Ms on load Address the two review comments on PR #950. [blocker] change-password could leave the keystore half-migrated: writeJsonAll staged all temps then renamed each into place, atomic per file only. A rename failing mid-commit left some blobs (and possibly the verifier) on the new password and others on the old, while changePassword still reported a clean rollback. Make the commit recoverable — back up each existing target, install the new blob, and on any failure restore every already-committed target from its backup. If automatic restore itself fails, throw io_error("manual recovery needed") and leave the backups on disk instead of falsely claiming rollback. changePassword now lets that explicit error propagate unmasked. [suggestion] ConfigLoader accepted negative/fractional waitTimeoutMs from a hand-edited config.yaml even though ConfigService rejects them on write. Apply the same non-negative-integer rule on load so the effective config stays valid. Tests: fault-injection coverage for the mid-commit and failed-restore paths (fs.test.ts) via a commitRename seam; waitTimeoutMs load-validation cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One ChainCommandDefinition per logical chain command: a neutral,
service-free ChainSpec (path, policy, baseFields, shared formatText)
plus a family->FamilyBinding table (run + delta fields/refine).
Registry assembles same-path bindings; shell dispatches by resolving
the network first, then the binding via net.family
(network_family_mismatch when absent), merging baseFields with the
binding's delta and composing base->family refines.
- command id = path.join(".") — drop the tron. prefix; the family
travels in the envelope's chain.family
- help/catalog render one entry per chain command with families: [...]
and a merged input schema
- registerTronChainCommands(registry, deps) replaces TronModule;
commands/tron/* folded up into commands/*
- remove dead legacy paths: resolveCandidates/resolveForFamily/tree(),
CommandDefinition.family, and the Networked/Networkless union —
CommandDefinition is now neutral-and-networkless by type
User-facing surface is unchanged: --json-schema catalog is identical
modulo the tron. prefix removal; all describe()/summary strings are
byte-identical; golden suite 360 passed / 2 skipped; tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure moves, no output change (golden suite byte-identical): family.ts — FAMILY_RENDER hook table + renderFamily wallet.ts — wallet receipts + list tree view account.ts — account/token queries + history/portfolio rows tx.ts — signing receipts + tx status/info misc.ts — config/networks/contract/message/block index.ts stays the barrel: reassembles TextFormatters (spread) and keeps renderGenericText, so no importer changes. methodName moves to scalars.ts (shared by tx receipts and contract call). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s, vote/reward Steven's v0.1.1 slice plus Leon's vote/reward (#949), rectified to repo conventions: - config: waitTimeoutMs (configurable --wait-timeout default) - gateway: TRON stake/chain read RPCs; reads + tx confirmation via the full node (fast --wait ~3s instead of ~60s solidity lag; fresh, consistent reads) - stake: info / delegated queries + public votingPower() - chain: params / prices / node - change-password + import mnemonic/private-key: TTY-only secret entry (no --*-stdin) - vote: cast / list / status ; reward: balance / withdraw (rectified: reuse stake.votingPower via injected TronStakeService, scope.warn instead of a __walletCliWarnings data side-channel, shared scalars.formatAtWithRelative, bounded/cached brokerage fan-out, pct & receipt-summary cleanups) - arity: first-class array flags (--for) via yargs array:true — no preprocess/pipe patch - render: preserve fractional TRX; chain untagged (generic, not TRON-exclusive) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional multi-line `description` field to command specs so leaf help (`<cmd> --help`) can carry the doc's user-value semantics (overwrite behavior, TP math, per-call limits, warnings) instead of collapsing to the one-line `summary` used in the parent group's listing. Populate it for vote/reward/ stake/chain/import/change-password commands, extend the group description for vote, and render the stake-info unfreezing list as an aligned tree (├─/└─). Add golden + formatter tests covering the new help copy and layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SignerResolver.assertCanSign — a cheap capability gate (no RPC, no keystore decrypt) that rejects watch-only accounts before any business validation. Call it as the first line of every write entry (send, stake, vote cast, reward withdraw, contract call/deploy) via a thin TxPipeline delegate, so "watch-only can't sign" wins over rule errors like insufficient_voting_power and even --dry-run is refused for watch accounts. Also rename backup()'s error from watch_only_no_signer to not_exportable: that branch covers watch and ledger accounts and is an export failure, not a signing one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…egory & ledger specific not-allow command
…Ms on load Address the two review comments on PR #950. [blocker] change-password could leave the keystore half-migrated: writeJsonAll staged all temps then renamed each into place, atomic per file only. A rename failing mid-commit left some blobs (and possibly the verifier) on the new password and others on the old, while changePassword still reported a clean rollback. Make the commit recoverable — back up each existing target, install the new blob, and on any failure restore every already-committed target from its backup. If automatic restore itself fails, throw io_error("manual recovery needed") and leave the backups on disk instead of falsely claiming rollback. changePassword now lets that explicit error propagate unmasked. [suggestion] ConfigLoader accepted negative/fractional waitTimeoutMs from a hand-edited config.yaml even though ConfigService rejects them on write. Apply the same non-negative-integer rule on load so the effective config stays valid. Tests: fault-injection coverage for the mid-commit and failed-restore paths (fs.test.ts) via a commitRename seam; waitTimeoutMs load-validation cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74adfd4 to
33c0a61
Compare
33c0a61 to
f3a4a86
Compare
… errors - writeJsonAll clean-rollback now fsyncs affected dirs before rethrowing, so a power loss right after rollback can't resurrect a half-committed state - fsyncDir no longer swallows all errors: skip on Windows (can't open a dir handle), tolerate only POSIX not-applicable codes (EINVAL/ENOTSUP/ EOPNOTSUPP/ENOSYS), and propagate real faults (EIO/ENOSPC/EACCES/EBADF) - split raw syscall into overridable rawFsyncDir seam for fault injection - add tests: rollback dir-fsync, real-error propagation, unsupported-code swallow Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Making fsyncDir propagate real errors exposed two paths where writeJsonAll
threw a bare error that changePassword then mislabeled as "rolled back":
- post-commit success path: the new blobs are already installed and readable,
so a dir-fsync failure there is NOT a rollback. Throw a distinct io_error
("committed but durability unconfirmed; new state in effect") and retain the
backups instead of cleaning them up, so callers stop reporting it as reverted.
- clean-rollback path: restores already succeeded, but their fsync failing let
a bare error escape and masked the original write failure. Throw a precise
io_error ("rolled back, durability of restore unconfirmed") preserving the
original cause.
Both now surface as ExecutionError, which changePassword passes through
unmasked, so the error message always matches the real on-disk state.
Add tests for both paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s in npm package Docs showed family-qualified command ids (tron.account.balance) but the runtime emits the unprefixed logical id (account.balance); the family travels only in the envelope's chain.family. Rewrote all 35 user-facing reference/guide docs to match actual output. Historical superpowers specs left untouched. Also add docs/ and skills/ to package.json files[] so README-linked reference pages and the agent skill actually ship (tarball 9 -> 93 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to 0b410ed: the message namespace was not in the previous replacement set, so tron.message.sign remained. Now unprefixed like the rest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These two were the only non-interactive commands lacking a JSON output
example (change-password stays exampleless — it produces no structured
output by design). Shapes derived from the actual use-case returns:
block -> {block: <raw node block>} (large fields elided as … per the
tx.info convention); import.ledger -> the shared account descriptor with
type "ledger" and the device path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(ts): TypeScript Wallet CLI v0.1.1
Stake/chain queries, governance (vote/reward), a configurable confirmation
timeout, TTY-only secret entry, and a family-keyed command contract — layered
on top of
release_v4.9.8. User-facing command surface is additive; the onebreaking change is the command-id scheme (see below).
Highlights
New commands
vote cast | list | status— cast/replace your full SR vote allocation,list super representatives & candidates (with brokerage → reward-ratio), and
show current votes + voting power + reward overview.
reward balance | withdraw— claimable voting/block reward and 24hwithdraw-window status; withdraw accrued rewards.
chain params | prices | node— on-chain governance parameters,energy/bandwidth unit prices + memo fee, and connected-node sync status.
stake info | delegated— staked amounts, voting power, resource usage,unfreeze schedule; and V2 delegation records (both directions).
change-password— re-encrypt every software keystore under a new masterpassword (TTY-only, confirmed).
Faster, fresher reads (
--wait)TRON reads and transaction confirmation now hit the full node's unconfirmed
view instead of the solidified node. Confirmation resolves ~one block after
inclusion (~3s) rather than after solidification (~19 blocks / ~60s), and all
reads are fresh and mutually consistent.
Configurable confirmation cap
New config key
waitTimeoutMs(built-in 60000 ms) supplies the default--waitpolling cap;--wait-timeoutoverrides per-invocation.Secret policy: wallet secrets are TTY-only
import mnemonic/import private-key/change-passwordsecrets are enteredinteractively (hidden prompt) — the
--mnemonic-stdin/--private-key-stdinchannels were removed. Only
password,tx, andmessageremain stdin-backed.No secret ever sits in argv, env, or the process table.
Refactors (no behavior change)
ChainCommandDefinitionper logicalchain command: a service-free
ChainSpecplus afamily → FamilyBindingtable.
registerTronChainCommandsreplacesTronModule;commands/tron/*folded up into
commands/*.render/index.tssplit by command domain (wallet / account / tx / vote /reward / chain / misc). Golden suite byte-identical.
--forvia yargsarray: true(no preprocess/pipe patch).
Stable command id dropped the
tron.prefix: chain command ids are nowpath.join(".")(e.g.tx.send, nottron.tx.send). The family travels in theenvelope's
chain.family. The--json-schemacatalog is otherwise identicalmodulo this prefix removal.
Docs
ts/docs/typescript-wallet-cli-architecture-source-of-truth.mdupdated to match:command surface, command contract, ports/use-cases, capability list, full-node
read model, secret policy, config keys, and arity.
Verification
typecheck·depcruise·test·buildclean; golden suite passing.