Skip to content

Adding heimdall commands to polycli#904

Draft
praetoriansentry wants to merge 49 commits intomainfrom
jhilliard/heimdall-command
Draft

Adding heimdall commands to polycli#904
praetoriansentry wants to merge 49 commits intomainfrom
jhilliard/heimdall-command

Conversation

@praetoriansentry
Copy link
Copy Markdown
Member

wip

Adds `cmd/heimdall/` package with the root `heimdall` cobra command
(aliased `h`) and embedded usage.md. Wires it into `NewPolycliCommand`.
No subcommands yet — those land in W1.

Refs: HEIMDALLCAST_PLAN.md §2.1
Adds internal/heimdall/config package. Resolves runtime config by
layering preset -> optional ~/.polycli/heimdall.toml -> env vars ->
flags, with --mainnet/--amoy/--network shortcuts. Registers persistent
flags on HeimdallCmd so every future subcommand inherits --rest-url /
--rpc-url / --chain-id / --json / --curl / --color / --raw etc.

The amoy preset falls back to the in-cluster test-node addresses from
HEIMDALLCAST_REQUIREMENTS.md §2; mainnet uses the community-documented
heimdall.polygon.technology endpoints. Both are marked with a TODO for
operator verification before the first user-facing release.

Refs: HEIMDALLCAST_PLAN.md §2.2
Adds internal/heimdall/client with:
- RESTClient.Get/Post returning raw bytes + status code
- RPCClient.Call issuing JSON-RPC 2.0 envelopes over HTTP
- Transport abstraction with HTTPTransport (wire) and CurlTransport
  (dumps equivalent curl one-liner instead of executing)
- Typed errors (HTTPError, NetworkError, UsageError, RPCError) plus
  ExitCode() mapper for cast-style exit codes (1/2/3/4)

Neither client decodes response bodies; callers choose between typed
decode and JSON passthrough. Insecure TLS and custom headers wired
through the constructors.

Refs: HEIMDALLCAST_PLAN.md §2.3
Adds internal/heimdall/render with:
- RenderKV (right-aligned key/value, mirrors requirements §4.1)
- RenderTable (column-aligned for list payloads)
- RenderJSON with bytes->0x-hex normalization (suppressed by --raw)
  and uint64-string preservation per requirements §4.2
- --field plucker with dot-path support; bare output for single field
- Watch() wrapper with timer.NewTimer + defer timer.Stop (CLAUDE.md
  timer-leak rule) and context-driven exit
- Hint catalogue for the known misleading responses in §4.5
- Timestamp annotation helper (cast-style "1776640801 (date, 2h ago)")

Color handling honours --color auto|always|never and NO_COLOR.

Refs: HEIMDALLCAST_PLAN.md §2.4
Adds scripts/capture-heimdall-fixtures.sh that hits the live test node
(172.19.0.2) for every REST path and CometBFT method referenced in the
requirements and writes pretty-printed JSON to
internal/heimdall/client/testdata/{rest,rpc}. Idempotent: rerun
overwrites in place.

Captured success-case fixtures for 33 REST endpoints and 11 RPC
methods. A handful are skipped because they need specific node state
this test node lacks (clerk latest-id + topup endpoints require
`eth_rpc_url`; tx_search times out on wide queries) — the script
reports those failures and continues.

TestFixturesAreValidJSON verifies every committed fixture parses and
RPC fixtures carry the expected envelope keys.

Refs: HEIMDALLCAST_PLAN.md §2.5
The internal/heimdall/config loader parses the optional
~/.polycli/heimdall.toml via pelletier/go-toml/v2. Move the dep from
indirect to direct since the heimdall package imports it.

Refs: HEIMDALLCAST_PLAN.md §2.2
Resolves the TODO placeholders with the confirmed public endpoints:
- mainnet REST:  https://heimdall-api.polygon.technology
- mainnet RPC:   https://tendermint-api.polygon.technology
- amoy REST:     https://heimdall-api-amoy.polygon.technology
- amoy RPC:      https://tendermint-api-amoy.polygon.technology

Verified each URL returns 200 on a representative endpoint.
Implement the seven cast-familiar CometBFT-facing subcommands of
`polycli heimdall` as specified in requirements section 3.1:
`block`, `block-number`, `age`, `find-block`, `chain-id`, `chain`,
`client`. All calls target the CometBFT JSON-RPC endpoint and share
the heimdall config resolver, RPC client, and renderer. `find-block`
does a context-cancellable binary search and accepts unix or RFC3339
timestamps. Height resolution honors `latest`/`earliest` and rejects
Ethereum-only tags with a usage hint.

Adds unit tests with fixtures (including a new block_earliest.json
capture from Amoy) and integration tests gated by the
`heimdall_integration` build tag.
Replace the placeholder /tx error fixture with a real successful
MsgTopupTx response captured from 172.19.0.2:26657, and add a
matching /tx_search fixture for two entries queried descendingly.
Used by the tx/receipt/logs unit tests in cmd/heimdall/tx.
Introduces the W1b tx-group read-only subcommands at the top level of
the heimdall command tree (mirroring cast's flat layout):

- tx <HASH> / t                 - CometBFT /tx, with --raw to preserve
                                   the base64 TxRaw body
- receipt <HASH> / re           - same /tx call but renders events and
                                   logs; --confirmations N polls /status
                                   until tip >= tx.height + N, honouring
                                   context cancellation
- logs <QUERY>                  - CometBFT /tx_search with --limit and
                                   --page pagination
- nonce <ADDRESS>               - REST /cosmos/auth/v1beta1/accounts/
                                   {addr}.sequence
- sequence <ADDRESS>            - synonym of nonce
- balance <ADDRESS> / b         - REST by-denom balance; default raw
                                   integer, --human formats with decimals
- rpc <METHOD> [KEY=VALUE...]   - raw CometBFT JSON-RPC passthrough with
                                   JSON-aware key=value argument parsing
- publish <TX>                  - broadcasts a base64/hex TxRaw via REST
                                   /cosmos/tx/v1beta1/txs; state-changing,
                                   so requires --yes (prints the equivalent
                                   wire payload and fails with usage error
                                   otherwise)

Hash handling tolerates the 0x prefix and base64-encodes for the
CometBFT JSON-RPC /tx endpoint (reflect-based RPC expects []byte as
base64, not hex).

Unit tests cover fixture-driven happy paths, argument validation,
--confirmations wait + cancel behaviour, and tx-bytes normalization;
integration tests (heimdall_integration build tag) resolve a recent
MsgTopupTx hash via tx_search on the live node and round-trip
tx/receipt/nonce/balance/rpc/logs end-to-end.

Run: make gen-doc regenerated the top-level heimdall doc and added
per-subcommand doc files for each new command.
…xtures

Add REST fixtures for checkpoint module tests: live prepare-next and
empty-buffer captures, plus synthetic code:13 (L1 unavailable) and
code:5 (not-set) envelopes used by unit tests to exercise the hint
and error paths without depending on node state.
Introduce the checkpoint umbrella (alias cp) wiring ten subcommands
against Heimdall v2's x/checkpoint REST endpoints: params, count,
latest, get, buffer, last-no-ack, next, list, signatures, overview.
Bare numeric args to checkpoint shortcut to get. root_hash is
rendered as 0x-hex by default and --raw preserves base64.

Human affordances: buffer prints 'empty' with a hint when the
proposer is the zero address or empty string; next maps gRPC
code 13 to an L1-not-configured hint on stderr; last-no-ack
annotates unix seconds with human age; list paginates via
Cosmos pagination.* params and reports next_key on stderr.

Unit tests exercise all subcommands against recorded fixtures
plus a hash-normalization helper and the buffer-empty / L1
detection predicates. Integration tests (build tag
heimdall_integration) run against a live Amoy node.
…ures

Add canned REST responses captured from a live Heimdall v2 node so the
span subcommand unit tests can exercise producer-votes-by-id, planned
downtime (populated), and planned downtime (not-found / gRPC code 5)
code paths without hitting the network.
Add a span umbrella command (alias sp) under polycli heimdall that
exposes the Heimdall v2 x/bor REST surface:

  - params, latest, get <ID> (and bare span <ID> shorthand)
  - list [--limit N] [--reverse] [--page KEY]
  - producers <ID> (derived from span's selected_producers)
  - seed <ID>
  - votes [VAL_ID]
  - downtime <PRODUCER_ID> (prints "none" on 404)
  - scores (sorted desc, numeric tiebreak)
  - find <BOR_BLOCK> (binary-searches the covering span, computes
    designated producer via (block-start)/sprint mod len(producers),
    prints a Veblop rotation caveat on stderr)

Unit tests cover find edge cases (at start_block, at end_block, sprint
boundary, mid-sprint, span 0, last span, before any span, after
latest, span with no producers). Integration tests (guarded by the
heimdall_integration build tag) exercise params, latest, find, scores,
votes, list and downtime against a live Heimdall v2 node. Docs are
regenerated via make gen-doc.
…ixtures

Captured from http://172.19.0.2:1317:

- milestones_out_of_range.json — HTTP 404 body for /milestones/0
  ("code: 5 milestone number out of range"); used to drive the
  valid-range hint in `milestone get`.
- milestones_by_number_one.json — milestone #1 on Amoy, whose
  `milestone_id` is a `uuid - 0x…` string (genesis artefact) and
  therefore cannot be confused with its URL-path sequence number.
  Drives the `number` vs `milestone_id` footgun rendering test.
Implements HEIMDALLCAST_REQUIREMENTS.md §3.2.3 under a new
`cmd/heimdall/milestone` package. Subcommands:

- `milestone params` → /milestones/params
- `milestone count` → /milestones/count (bare integer on stdout)
- `milestone latest` → /milestones/latest (hash rendered as 0x-hex)
- `milestone get <NUMBER>` and bare `milestone <NUMBER>` →
  /milestones/{number}; prints both the URL-path `number` and the
  response-body `milestone_id` (they are distinct — see footgun in
  §3.2.3).

On a 404 from /milestones/{number}, the command fetches
/milestones/count and, if the requested number is 0 or > count,
emits `hint: valid range is 1..<count>` on stderr. If the count
lookup itself fails the original 404 is returned unchanged.

Unit tests exercise all four subcommands against the captured
fixtures including the number-vs-milestone_id rendering and both
the "zero" and "far above count" out-of-range hint paths. An
integration test file (//go:build heimdall_integration) hits
172.19.0.2:1317 directly to confirm the same behaviour on real data.

make gen-doc re-run; docs regenerated.
Adds `polycli heimdall validator` (alias `val`) + the top-level
`validators` alias covering the Heimdall v2 x/stake module:

- `validator set` / `validators` (sorted power desc by default, with
  --sort power|id|signer and --limit)
- `validator total-power`
- `validator get <ID>` (plus bare `validator <ID>` shorthand)
- `validator signer <ADDR>` (tolerates missing 0x prefix)
- `validator status <ADDR>` (renames upstream is_old to is_current;
  emits a hint explaining the rename)
- `validator proposer`
- `validator proposers [N]` (N defaults to 1)
- `validator is-old-stake-tx <HASH> <LOG_INDEX>` (prints the
  L1-not-configured hint on gRPC code 13 / connection refused)

Includes unit tests with captured REST fixtures and integration tests
gated by the heimdall_integration build tag.
Output of make gen-doc after adding cmd/heimdall/validator and
the top-level validators alias.
Extend the clerk REST fixtures with cases the W1g state-sync tree
needs: a gRPC code-13 latest-id / sequence / is-old-tx response for
L1-less nodes, a HTTP-500 "not found" for event-record lookups (the
upstream returns code 13 rather than 404 for a missing id), the
page=0 HTTP-400 error from /clerk/event-records/list (upstream
rejects page 0 because the endpoint is page-based, not cosmos), and
a /clerk/time response with from_id + to_time + pagination.limit.
Implement the x/clerk umbrella per requirements §3.2.5 under the
canonical name state-sync, with aliases clerk and ss. Subcommands:

- count: bare integer from /clerk/event-records/count.
- latest-id: /clerk/event-records/latest-id, surfacing the
  L1-not-configured hint on gRPC code 13.
- get <ID> / bare state-sync <ID>: /clerk/event-records/{id}. The
  `data` field renders as 0x-hex by default; --base64 (or the
  global --raw) preserves the upstream base64.
- list [--page N] [--limit N]: /clerk/event-records/list. PAGE-
  BASED pagination (bare `page` + `limit` query params), not
  cosmos-pagination — the upstream rejects page=0 with HTTP 400,
  so --page defaults to 1. When --limit is omitted the
  pagination-limit hint is surfaced on stderr.
- range --from-id ID [--to-time T] [--limit N]: /clerk/time.
  Unlike /list, this endpoint goes through cosmos-pagination on
  the server side, so --limit is forwarded as `pagination.limit`.
  --from-id is required; the server rejects an empty query.
- sequence / is-old <TX_HASH> <LOG_INDEX>: /clerk/sequence and
  /clerk/is-old-tx respectively. Both fan out to L1 on the
  server; the L1-not-configured hint is surfaced on gRPC code 13
  or transport-level `dial tcp` / `connection refused`.

Register under cmd/heimdall/heimdall.go init() and regenerate
doc/polycli_heimdall_state-sync*.md.
Capture real responses from the Amoy heimdall-v2 node at 172.19.0.2:1317
for the x/topup endpoints: dividend-account-root (success),
dividend-account (not-found), account-proof (L1-unconfigured),
verify (false + bad-proof), sequence / is-old-tx (L1-unconfigured).
Success-case dividend-account / account-proof / sequence / is-old-tx
bodies are synthesized from the heimdall-v2 proto shapes since the
live test node lacks L1 RPC and has no dividend accounts.
Implements `polycli heimdall topup` with six subcommands targeting
Heimdall v2's x/topup module, per HEIMDALLCAST_REQUIREMENTS.md §3.2.6.
All routes are confirmed from heimdall-v2 proto/heimdallv2/topup/query.proto:

  - topup root     -> GET /topup/dividend-account-root
  - topup account  -> GET /topup/dividend-account/{address}
  - topup proof    -> GET /topup/account-proof/{address}
  - topup verify   -> GET /topup/account-proof/{address}/verify?proof=...
  - topup sequence -> GET /topup/sequence?tx_hash=...&log_index=...
  - topup is-old   -> GET /topup/is-old-tx?tx_hash=...&log_index=...

The proof/sequence/is-old endpoints fan out to L1 on the server side;
gRPC code 13 (or a connection-refused transport error) is surfaced as
the shared L1-not-configured hint on stderr before propagating the
error, matching the clerk umbrella's shape.

Default human output renders bytes as 0x-hex; --raw preserves the
upstream base64, and --json emits the raw server payload. Addresses,
tx hashes, and proofs are validated and normalized before the URL is
built.

The `verify` route uses GET (not POST) with the proof in the
`proof` query parameter — confirmed from the upstream gateway proto.
Unit tests (24): fixture-backed httptest server exercises each
subcommand's happy path, L1-unconfigured hint path, address/tx-hash
validation, proof query-param shape, --json / --raw flag behaviour,
and error propagation for not-found (404/500) responses.

Integration tests (8, build tag heimdall_integration) talk to the
live Heimdall v2 node at 172.19.0.2:1317. The proof / sequence /
is-old tests skip rather than fail if the node ever gains L1 RPC
connectivity. The root / verify tests exercise the non-L1 path end
to end.

`go test -race ./cmd/heimdall/topup/...` and
`go test -tags heimdall_integration -race ./cmd/heimdall/topup/...`
both pass.
Captured from the live Heimdall v2 Amoy node at 172.19.0.2:1317:

- chainmanager_params.json — GET /chainmanager/params success body with
  the chain_params envelope, all ten *_address fields, and the two tx
  confirmation depths.
- chainmanager_not_implemented.json — gRPC-gateway code 12 body used to
  exercise the 501 error path for unknown routes.
Implements `polycli heimdall chainmanager` (alias `cm`) targeting the
Heimdall v2 x/chainmanager module, per HEIMDALLCAST_REQUIREMENTS.md
§3.2.7.

Subcommands:

- `chainmanager params` — GET /chainmanager/params. Default KV output
  unwraps the `params` envelope; --json preserves it; --field uses
  dot-notation paths against the raw server shape.
- `chainmanager addresses` — derived view over the same response,
  surfacing the two chain ids and every `*_address` field from
  params.chain_params as `<name>=<value>` lines (alphabetized). Hides
  the tx confirmation depths that clutter the etherscan paste workflow.

Only one HTTP route exists upstream; confirmed from
heimdall-v2/proto/heimdallv2/chainmanager/query.proto. The package
directory is named `chainparams` to avoid colliding with the top-level
`chain` command (W1a).

Registered on the heimdall root via chainparams.Register.
- helpers_test.go — shared httptest fixture server + cobra runner,
  mirroring cmd/heimdall/topup/helpers_test.go.
- chainparams_test.go — 10 unit tests covering default KV output, --json
  passthrough, --field dot-path plucking over the envelope, the derived
  addresses view (including alphabetized ordering and
  confirmation-depth exclusion), malformed-body handling, and the `cm`
  alias.
- integration_test.go (build tag heimdall_integration) — 4 tests
  against the live Amoy node at 172.19.0.2:1317 exercising params
  shape, --field plucking, the addresses derived view, and alias
  routing end-to-end.

All tests pass with -race under both build modes.
Adds `polycli heimdall util` with four offline helpers: addr (hex↔bech32
address conversion using the default cosmos prefix, overridable via
--hrp), b64 (hex↔base64 with auto-direction detection and --to override),
version (polycli build metadata with optional --node /status lookup via
CometBFT RPC), and completions (bash, zsh, fish, powershell via cobra's
stock generators).

Bech32 encoding piggybacks on github.com/btcsuite/btcd/btcutil/bech32,
already in the module graph as an indirect dep, so no new top-level
dependencies are introduced. The `cosmos` prefix is confirmed from
heimdall-v2/API_REFERENCE.md and the absence of a
sdk.Config.SetBech32PrefixForAccount override in heimdalld's commands.
Covers addr (round-trip vectors for three known Heimdall addresses plus
a custom --hrp smoke test, plus invalid-input table), b64 (auto and
explicit conversion, URL-safe base64 acceptance, round-trip, invalid
inputs), version (plain + --json + --field paths, plus a
heimdall_integration-tagged test hitting 172.19.0.2:26657 for --node),
and completions smoke tests for all four supported shells that verify
cobra's output contains shell-specific markers.

22 unit test functions, 1 integration test function, 42 invocations
once subtests are expanded. go test -race ./cmd/heimdall/util/...
passes clean.
Capture real /status, /health, /net_info, /abci_info, /commit,
/validators, /num_unconfirmed_txs and /unconfirmed_txs responses
from the Amoy-backed node at 172.19.0.2:26657, plus the error
envelope that node returns for /dump_consensus_state (consensus
endpoints are disabled by default). Add a synthetic
dump_consensus_state.json so rendering logic remains
unit-testable on a fixture even when the live node refuses.
Introduce 'polycli heimdall ops' grouping the operator-facing
CometBFT JSON-RPC commands: status, health, peers, consensus,
tx-pool, abci-info, commit and validators-cometbft. All calls use
the existing RPCClient (with --curl and config resolution) and
render via the heimdall KV/JSON/table renderers.

Notes:
- consensus warns before the call, since dump_consensus_state is
  expensive and frequently disabled on production nodes.
- validators-cometbft prints a stderr hint directing operators to
  'heimdall validator' for the x/stake view so the two commands
  don't get confused.
- tx-pool --list decodes base64 mempool blobs to sha256 tx hashes
  (matches how CometBFT computes tx hashes over the wire payload).
Unit tests drive each subcommand against an httptest.Server backed
by the captured fixtures: summary rendering, --json passthrough,
--field plucking, error surfaces (health RPC error, consensus
endpoint disabled, bad height, bad --limit), and the base64
mempool hash helper.

Integration tests (behind the heimdall_integration build tag)
exercise every subcommand against the live 172.19.0.2:26657 node
and treat the consensus-endpoint-disabled response as a pass of
the error path rather than a failure.
Implements `polycli heimdall wallet` with 13 subcommands compatible with
Foundry's `cast wallet` v3 JSON keystore format: new, new-mnemonic,
address, derive, sign, verify, import, list, remove, public-key,
decrypt-keystore, change-password, private-key. Hardware wallets,
vanity, and sign-auth are rejected with helpful pointers to cast.

Keystore directory precedence: --keystore-dir flag > ETH_KEYSTORE env >
~/.foundry/keystores/ (if exists) > ~/.polycli/keystores/ (created on
demand). Signing defaults to EIP-191 personal_sign; --raw signs a
32-byte hash. Plaintext key export is gated by --i-understand-the-risks.

All operations are offline. Tests use t.TempDir() and never touch real
keystore directories. Coverage includes a known BIP-39 derivation
vector, sign/verify round-trip, foundry keystore fixture decryption,
and keystore precedence across all four levels.
Add a hand-rolled proto-encoding subset sufficient for the heimdall tx
builder: Any, Coin, PubKey wrapper, TxBody, AuthInfo, SignerInfo,
ModeInfo, Fee, TxRaw, SignDoc, and MsgWithdrawFeeTx. Uses
google.golang.org/protobuf/encoding/protowire (already a direct dep);
no cosmos-sdk / cometbft / go-ethereum replace directives required. See
internal/heimdall/proto/README.md for rationale and escape hatch to
buf generate if the surface grows.
Add the internal/heimdall/tx package implementing the shared builder
used by `mktx`, `send`, and `estimate` (W3). The builder fills a
cosmos.tx.v1beta1.TxRaw with one or more messages, resolves
account_number/sequence through a REST AccountFetcher (or explicit
overrides), and signs using PubKeySecp256k1eth (keccak256 digest,
r||s 64-byte sig). Direct and amino-JSON sign modes are both
supported; amino-JSON serializes a canonicalized StdSignDoc with
sorted keys.

Additional helpers:

- Broadcast via POST /cosmos/tx/v1beta1/txs in SYNC or ASYNC mode.
- WaitForInclusion polls CometBFT /tx until inclusion or ctx cancel.
- WaitForConfirmations polls /status until tip passes txHeight + N.
- Simulate via POST /cosmos/tx/v1beta1/simulate.
- RequireForce refuses L1-mirroring Msg types unless --force is set,
  with wording from HEIMDALLCAST_REQUIREMENTS.md §3.3.

Only MsgWithdrawFeeTx is wired in this wave (as the exemplar Msg);
W3/W4 add the remaining operator-reachable messages using the same
pattern.
Unit tests (go test -race) cover:

- Deterministic TxBody/AuthInfo bytes across builds with identical
  inputs, plus the derived 64-byte r||s signature length.
- Sign mode switch: SIGN_MODE_DIRECT and SIGN_MODE_LEGACY_AMINO_JSON
  produce different AuthInfo + signature bytes but share TxBody.
- Signature verifies against the signer's 65-byte uncompressed pubkey
  using keccak256 digest (PubKeySecp256k1eth).
- AccountFetcher integration: populates account_number/sequence,
  preserves explicit sequence overrides, propagates fetch errors.
- Builder input validation: missing messages, chain id, gas limit,
  or private key each fail with an explicit error.
- Broadcast happy path and non-zero-code surfacing.
- Simulate happy path returns gas_used / gas_wanted.
- WaitForInclusion honours context cancellation and deadline; polls
  until the tx appears.
- RequireForce flags the L1-mirroring Msg types and allows safe
  types, with the requirements-§3.3 wording.
- Proto encoder round-trips for Any, MsgWithdrawFeeTx, TxRaw, SignDoc
  and emits zero bytes for a zero-valued message.

Integration tests (//go:build heimdall_integration) against
172.19.0.2:1317 fetch a known signer's account and build a TxRaw
without broadcasting. Broadcasting is gated behind
HEIMDALL_TEST_ALLOW_BROADCAST=1.
Wire three top-level subcommands for the `polycli heimdall` tree:
`mktx` builds and prints a signed TxRaw without broadcasting; `send`
builds, signs, broadcasts, and (by default) waits for inclusion;
`estimate` simulates a tx against /cosmos/tx/v1beta1/simulate and
reports gas usage.

Child msg subcommands (e.g. `withdraw`) live under cmd/heimdall/tx/msgs
and register themselves into a package-level factory map via
RegisterFactory(name, factory). BuildChildren(mode, flags) produces a
fresh subtree per umbrella because cobra commands are single-parent.

Shared flag bag TxOpts plus RegisterFlags(cmd, opts, mode) apply the
wallet / gas+fee / account-override / sign+broadcast / force flags
per spec §3.3; Execute(cmd, opts, mode, plan) is the single call
point that resolves config, runs the L1-force guard, signs, and
dispatches per mode. All new files are scoped to cmd/heimdall/tx and
cmd/heimdall/tx/msgs -- nothing under internal/heimdall is touched.
Register a `withdraw` factory under mktx/send/estimate that emits a
MsgWithdrawFeeTx. The proposer field defaults to the signer's
Eth-style address (derived by resolving --from / --account /
--private-key / --mnemonic via ResolveSigningKey); callers can
override with --user. --amount defaults to "0", which Heimdall
interprets as "withdraw the full accumulated balance".

MsgWithdrawFeeTx is not L1-mirrored, so requireEthAddress is local
and RequireForce returns nil -- no --force prompt needed.
Add msgs_test.go (default build tag) covering:
- registry shape and BuildChildren safety
- mktx withdraw emits 0x-prefixed TxRaw hex and a stable JSON envelope
- body-bytes determinism across repeated invocations
- send --dry-run does not POST; non-dry-run POSTs /cosmos/tx/v1beta1/txs
- estimate withdraw round-trips simulate and computes fee from gas price
- L1-mirroring guard exempts MsgWithdrawFeeTx
- Sign-mode gating: DIRECT ok, AMINO_JSON ok, garbage rejected
- Flag validation (missing chain-id, missing signer, unknown msg)

Integration suite gated by `heimdall_integration` tag defaults to
the documented 172.19.0.2 compose node with overrides
(HEIMDALL_TEST_REST_URL / HEIMDALL_TEST_RPC_URL / HEIMDALL_TEST_CHAIN_ID).
Broadcasting test further gated by HEIMDALL_TEST_ALLOW_BROADCAST=1
so CI doesn't accidentally emit txs.
Adds hand-rolled proto encoders and decoders for the Heimdall v2 Msg
types needed by the per-Msg subcommands: MsgCheckpoint, MsgCpAck,
MsgCpNoAck, MsgProposeSpan, MsgBackfillSpans, MsgVoteProducers,
MsgSetProducerDowntime, MsgValidatorJoin, MsgStakeUpdate,
MsgSignerUpdate, MsgValidatorExit, MsgEventRecord, MsgTopupTx. Also
adds the sidetxs VoteExtension for `decode ve`.

Introduces internal/heimdall/proto/registry.go which exposes the set of
known type URLs and a single Decode(typeURL, bytes) entry point so
both the decode CLI and future broadcast flows can round-trip an Any.
Wraps each new proto Msg behind a wire-interface adapter exposed by
internal/heimdall/tx/msgs.go so the shared tx builder can marshal
them into the outer TxBody without per-module plumbing. Extends the
L1-mirroring guard list to cover MsgEventRecord, matching the W4
clerk-record subcommand which mirrors a bridge event recorded on L1.
Drops 13 per-Msg subcommands into cmd/heimdall/tx/msgs/ so every W4
Msg type gains a first-class CLI: checkpoint (gated behind
--i-am-a-validator), checkpoint-ack, checkpoint-noack, span-propose,
span-backfill, span-vote-producers, span-set-downtime, topup,
stake-join, stake-update, signer-update, stake-exit, and
clerk-record. L1-mirroring Msg types reuse the force guard wired in
the previous commit; checkpoint-ack additionally requires --l1-tx
since the Heimdall-side ack is only meaningful when the L1 ack
transaction hash is known.

Shared flag helpers (parseHexBytes, requireNonEmptyString,
lowerEthAddress) keep the per-command files small and consistent.
Adds cmd/heimdall/decode with four offline subcommands:

  * decode tx    - unmarshal a TxRaw, resolve each Any via the proto
                   registry, and print a human-readable or --json view
                   of body, auth_info, signatures, and the CometBFT
                   SHA-256 tx hash.
  * decode msg   - decode a single Any.value for a type URL; --list
                   prints every registered type URL for discovery.
  * decode hash-tx - compute the CometBFT SHA-256 hash of a raw tx.
  * decode ve    - decode heimdallv2.sidetxs.VoteExtension bytes from
                   CometBFT logs (hex by default, base64 accepted).

All decoders accept hex (0x-prefixed or bare) and base64 (std/url,
padded or raw) uniformly. The umbrella is created fresh per Register
call so tests can build throwaway roots without piling duplicate
subcommands onto a package-level variable.
Adds round-trip tests for every new proto Msg, asserts that every
type URL exposed by the per-Msg files appears in the registry, and
exercises the full Decode() lookup path for one Msg per module.

On the CLI side, covers subcommand registration, the L1-mirroring
force guard, and the validator-only gate on checkpoint. Happy-path
mktx assertions prove that span-propose and topup actually build a
transaction when all required flags are supplied; negative tests
confirm stake-join, topup, and checkpoint-ack refuse to build
without --force / --l1-tx.

decode_test.go round-trips a MsgWithdrawFeeTx through `decode msg`,
a TxRaw containing it through `decode tx` (both human and --json),
verifies the SHA-256 from `decode hash-tx` against a known digest,
and round-trips a VoteExtension through `decode ve`.
Introduce render.EnableWatch / render.EnableWatchTree helpers that
wrap a cobra command's RunE with a cancellable watch loop. Each
umbrella's Register() invokes EnableWatchTree on its freshly built
tree (or EnableWatch on individual leaves for tx/tx.go where
publish/rpc must stay one-shot) so every read-only subcommand now
accepts `--watch DURATION` without touching its RunE.

The watch loop renders into a bytes.Buffer per tick and clears the
screen with VT100 sequences when stdout is a TTY, so `polycli
heimdall tx <hash> --watch 2s` and friends behave like `cast` / `watch
-n`. Transient errors are printed to stderr and do not abort the
loop.
Walk the heimdall subtree at init-time and wrap every RunE so that
on failure the process exits with the cast-style code returned by
client.ExitCode: 1 node error, 2 network error, 3 usage error, 4
signing error. Operators scripting against polycli can now
distinguish the four failure classes instead of collapsing them onto
cobra's single rc=1.

Setting SilenceUsage + SilenceErrors on the subtree keeps cobra from
printing the usage blob and a duplicate error line; the wrapper
prints "Error: <msg>" itself before os.Exit so the familiar cast
output survives.
…/wallet

Move the duplicated BIP-39/BIP-32 derivation helpers
(DeriveFromMnemonic, ParseDerivationPath, DefaultDerivationPath) and
v3 keystore helpers (NewKeyStore, FindAccount,
AddressFromKeystoreFile, DecryptKeystoreAccount, ResolveKeystoreDir)
into a new internal/heimdall/wallet package shared by `polycli
heimdall wallet` and `polycli heimdall tx/mktx/send/estimate`.

ResolveKeystoreDir gains a createDefault bool so the wallet
management surface keeps its "materialise ~/.polycli/keystores on
first use" behaviour while the signing surface treats a missing
default dir as a clear "account not found" error instead of silently
creating empty dirs.

cmd/heimdall/wallet keeps thin wrappers so the local package does
not ripple through every call site; cmd/heimdall/tx/msgs/key.go now
calls the shared package directly. The stale cmd/heimdall/wallet/
json.go helper is dropped.
Tighten Short strings that significantly exceeded the ~50 char help
menu target so `polycli heimdall --help` columns stay readable on
80-wide terminals. No behavioural change; the L1-mirroring Short
suffixes on mktx/send msg subcommands are kept verbatim because the
marker is load-bearing for operators.
`make gen-doc` output after the watch-flag wiring, exit-code
wrapping, and Short description trims. Picks up the new
chainmanager/decode/estimate/mktx/send/ops/util/wallet subtrees and
the --watch flag rows.
Adds docs/heimdall.md covering:
- Quick-start invocations for the cast-familiar surface.
- Umbrella command list with one-line intent per umbrella.
- --watch DURATION, --curl, and exit-code tables.
- Keystore directory precedence (flag > ETH_KEYSTORE >
  ~/.foundry/keystores > ~/.polycli/keystores), with a note on
  createDefault behaviour across wallet vs signing surfaces.
- Verbatim L1-mirroring warning and the list of subcommands
  that carry it.

The per-subcommand reference remains under doc/ (generated by
`make gen-doc`); this file is the human-readable index.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
13.7% Duplication on New Code (required ≤ 3%)
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

case float64:
// json.Unmarshal yields float64 for numbers; render as int
// when integral to match cast output style.
if vv == float64(int64(vv)) {
// json.Unmarshal yields float64 for numbers; render as int
// when integral to match cast output style.
if vv == float64(int64(vv)) {
return fmt.Sprintf("%d", int64(vv))
// pure unsigned integer that does not also look like an address.
if n, err := strconv.ParseUint(identifier, 10, 32); err == nil {
list := ks.Accounts()
if int(n) >= len(list) {
Msg: fmt.Sprintf("keystore has %d accounts; index %d out of range", len(list), n),
}
}
return list[int(n)], nil
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.

2 participants