Skip to content

feat: ts v0.1.1 - #950

Merged
gummy789j merged 16 commits into
release_v4.9.8from
feat/ts-v0.1.1
Jul 16, 2026
Merged

feat: ts v0.1.1#950
gummy789j merged 16 commits into
release_v4.9.8from
feat/ts-v0.1.1

Conversation

@gummy789j

Copy link
Copy Markdown
Collaborator

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 one
breaking 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 24h
    withdraw-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 master
    password (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
--wait polling cap; --wait-timeout overrides per-invocation.

Secret policy: wallet secrets are TTY-only

import mnemonic / import private-key / change-password secrets are entered
interactively (hidden prompt) — the --mnemonic-stdin / --private-key-stdin
channels were removed. Only password, tx, and message remain stdin-backed.
No secret ever sits in argv, env, or the process table.

Refactors (no behavior change)

  • Family-keyed command contract — one ChainCommandDefinition per logical
    chain command: a service-free ChainSpec plus a family → FamilyBinding
    table. registerTronChainCommands replaces TronModule; commands/tron/*
    folded up into commands/*.
  • render/index.ts split by command domain (wallet / account / tx / vote /
    reward / chain / misc). Golden suite byte-identical.
  • First-class array flags — repeatable --for via yargs array: true
    (no preprocess/pipe patch).

⚠️ Breaking change

Stable command id dropped the tron. prefix: chain command ids are now
path.join(".") (e.g. tx.send, not tron.tx.send). The family travels in the
envelope's chain.family. The --json-schema catalog is otherwise identical
modulo this prefix removal.

Docs

ts/docs/typescript-wallet-cli-architecture-source-of-truth.md updated 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 · build clean; golden suite passing.

@gummy789j gummy789j changed the title Feat/ts v0.1.1 feat: ts v0.1.1 Jul 9, 2026

@fdinesh497-collab fdinesh497-collab left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R

@matrix-agent116 matrix-agent116 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 waitTimeoutMs when loading config.yaml, not only when it is written through ConfigService, 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it.

gummy789j added a commit that referenced this pull request Jul 15, 2026
…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>
gummy789j and others added 9 commits July 15, 2026 15:20
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>
…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>
gummy789j and others added 6 commits July 15, 2026 17:37
… 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>
@gummy789j
gummy789j merged commit e1fa2e4 into release_v4.9.8 Jul 16, 2026
@gummy789j gummy789j mentioned this pull request Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants