paritytech / paritytech/polkadot-cli

Architecture: deepening opportunities backlog

Open
#206 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
10
Forks
2
Avg merge
12h 35m
Merged PRs (30d)
4

Description

Backlog from an architecture review. Each item is a deepening opportunity — a refactor that turns shallow modules into deeper ones (more behaviour behind a smaller interface), aimed at testability and AI-navigability. Each one passes the deletion test: removing it would concentrate complexity, not relocate it.

Vocabulary used below: Module (interface + implementation), Interface (everything callers must know), Seam (where an interface lives), Adapter (concrete satisfier of an interface), Locality (changes concentrate in one place), Leverage (callers get a lot per unit of interface).


1. Unified "command intent" Module at the dispatch seam

Files: src/cli.ts, src/utils/parse-dot-path.ts, src/utils/parse-target.ts, src/core/file-loader.ts, every src/commands/*.ts

Problem: No single Module represents "what the user asked for." Intent is held as a ParsedDotPath, a separate ParsedFileCommand for file input, a raw target: string, and a 15-field opts blob — handed to handlers, which re-split target on "." and re-resolve pallet/item from metadata. The chain-prefix-vs---chain-flag reconciliation appears in cli.ts:206, parse-target.ts:81, complete.ts:317. The category→item-filter switch (tx→calls, query→storage, …) appears 4+ times across cli.ts, focused-inspect.ts, complete.ts, and the per-category command files.

Solution: One Module that takes raw input (target, args, opts, optional file path) plus chain config + metadata and returns a discriminated union — one variant per shape of request (list pallets in category, list items in pallet, query storage, encode tx, dry-run tx, submit tx, decode hex, raw-hex tx, item-level help, …). Handlers receive a fully-resolved variant and stop re-parsing.

Benefits: Locality — every "is this a chain or a pallet?" decision lives at one seam. Leverage — handlers shrink to "given a resolved intent, do the side effect." Tests — the parser is exercised directly (no RPC); handlers become testable from fabricated intents instead of CLI integration tests.


2. Categorized pallet view inside metadata.ts

Files: src/core/metadata.ts, plus cli.ts, commands/{tx,query,const,events,errors,apis,extensions}.ts, commands/focused-inspect.ts, completions/complete.ts

Problem: metadata.ts exposes the raw polkadot-api MetadataBundle (lookup, builder, unified.pallets). Each consumer walks that structure independently and re-implements "for category X give me the items in pallet P." Inside metadata.ts, describeCallArgs / describeEventFields / describeType are three nearly-identical formatters (metadata.ts:340–474). The Interface is wide (10+ exports) and polkadot-api codec types leak into every command file (tx.ts:12–22 imports 10 of them).

Solution: Deepen metadata.ts to a small set of category-aware queries — list pallets with items in category C, list items in pallet P with type signatures, describe item P.I in category C — hiding Lookup/DynamicBuilder/MetadataBundle. Callers receive plain data. The three describe* formatters collapse to one.

Benefits: Locality — polkadot-api type knowledge stops leaking. Leverage — pays back across 7 categories × 4 surfaces (dispatch, focused-inspect, completions, command handlers). Tests — single unit-test surface replaces integration-style tests through the CLI.


3. Tx lifecycle Module separate from output rendering

Files: src/commands/tx.ts:1659–1719 (watchTransaction), 1721–1767 (watchTransactionJson), 437–491 (dry-run-signed output), 655–714 (signed-submission output)

Problem: The lifecycle state machine (signed → broadcasted → bestBlock → finalized, with --wait short-circuits) is implemented twice — once for spinner/text, once for NDJSON. The two functions are ~98% identical. One level up, dry-run output and post-submission output duplicate the chain/from/call/decoded/tx/status/events/explorer block in two parallel JSON+text branches.

Solution: A TxLifecycle Module that consumes the polkadot-api submission observable and emits typed events plus a final result. Two thin Adapters render — interactive (spinner + ANSI) and NDJSON. The submission "block" becomes a single TxReport data shape with one renderer per output mode.

Benefits: Locality — wait-level rules and event shape live at one seam. Leverage — --json parity is automatic. Tests — lifecycle tested by feeding fabricated observables (no chain), renderers via snapshot; today these paths are integration-only.


4. One type-directed argument pipeline (CLI strings + YAML/JSON file input)

Files: src/commands/tx.ts:1271–1417 (parseTypedArg), 1092–1242 (normalizeValue), 949–1024 (parseCallArgs), 1251–1269 (fileArgsToStrings)

Problem: Two parallel pipelines walk the same metadata type tree. parseTypedArg converts CLI strings to typed values; normalizeValue converts pre-parsed YAML/JSON to typed values. Both handle enum variants, structs, Option<T>, sized byte arrays, XCM single-element wrapping. fileArgsToStrings literally re-serializes file values back to strings to feed them through parseTypedArg — a tell that the seam is in the wrong place.

Solution: Inner Module: a type-directed walk over a metadata type, parameterized by a source-adapter (raw strings vs. already-parsed JSON values) — one dispatch table per metadata kind. Outer layer: each entry point supplies its adapter and calls the inner Module once.

Benefits: Locality — enum-shorthand, Option<T> literals, sized byte arrays, XCM unwrapping each live once. Leverage — file-based commands and CLI args share semantics by construction. Tests — each metadata-kind branch tested against a typed input source rather than two parallel suites that can drift.


5. Signed-extension policy Module

Files: src/commands/tx.ts:1445–1516 (parseExtOption, buildCustomSignedExtensions, autoDefaultForType), 1525–1578 (unsignedDefaultForType), 1587–1653 (buildGeneralTx v5 assembly), 319–334 (--asset pipeline)

Problem: Per-extension knowledge (CheckMortality, CheckNonce, ChargeTransactionPayment, ChargeAssetTxPayment) is scattered across tx.ts: which extensions polkadot-api fills automatically, unsigned defaults for each, how --asset overrides ChargeAssetTxPayment, the v5 general-transaction byte layout. The --asset detour bypasses a polkadot-api compatibility check and is wired inline. The dot <chain>.extensions inspector lives in a separate command but knows the same domain.

Solution: A signed-extension-policy Module concentrating each extension's behaviour — auto-filled flag, --unsigned default, mapping from --ext/--asset/--tip/--mortality/--nonce to extension values, and v5 layout. The extensions inspector consults the same Module.

Benefits: Locality — adding a new chain-specific extension is one place. Leverage — --ext validation, the --asset shortcut, the --unsigned defaulter, and the extensions inspector share one source of truth. Tests — currently only the parsing helpers are unit-tested; the "right thing for this chain's extensions" path is integration-only. A policy Module with metadata-as-input becomes unit-testable end-to-end.


6. One tokenizer for runtime parsing and completions

Files: src/utils/parse-dot-path.ts, src/utils/parse-target.ts, src/completions/complete.ts:16–64, 244–408

Problem: Three independent parsers cover the same chain/category/pallet/item domain. parse-dot-path and parse-target overlap on chain-vs-pallet disambiguation. complete.ts re-implements CATEGORY_ALIASES, case-insensitive chain matching, and the category→pallet-filter switch independently because it walks partial input. Each has its own tests; none exercises the others.

Solution: One tokenizer Module that turns a (possibly partial) segment sequence into a structured "what kind is each segment" shape. The runtime parser fails on incomplete input; the completion engine reads the partial structure to decide what to suggest next.

Benefits: Locality — chain-name matching, category aliases, and segment-position rules defined once. Leverage — completions stay in sync with dispatch by construction. Tests — partial vs. complete input is one test surface. (Likely folds into #1 if that ships first.)


Suggested ordering

#1 and #2 have the broadest payoff (touch the most modules). #3 and #4 are the most effective at shrinking tx.ts (currently 1789 lines). #6 collapses naturally if #1 ships. #5 is the most chain-specific and the highest leverage for adding new runtimes.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This is a six-part architecture backlog spanning the named files under src/cli.ts, src/core/, src/commands/, src/utils/, and src/completions/. Start by reading the suggested ordering and the specific problem statement for one selected opportunity, then inspect its listed entry points and tests. Done is not defined for the backlog as a whole; the selected refactor would need a scoped completion criterion.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, tooling
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.