Skip to content

refactor: migrate CLI to NestJS architecture with test suite and UX improvements#13

Open
carlosfebres wants to merge 98 commits intomainfrom
improvements
Open

refactor: migrate CLI to NestJS architecture with test suite and UX improvements#13
carlosfebres wants to merge 98 commits intomainfrom
improvements

Conversation

@carlosfebres
Copy link
Collaborator

@carlosfebres carlosfebres commented Feb 26, 2026

Summary

A comprehensive refactor of the Routes CLI — migrating from a monolithic Commander-based structure to a modular, injectable NestJS application. The goal is improved maintainability, testability, and extensibility.

Key Changes

  • Architecture: Replaced standalone Commander setup with NestJS + nestjs-commander. All concerns are now injectable services (DisplayService, PromptService, QuoteService, RpcService, ChainsService, PublisherFactory, etc.)
  • Configuration: Global ConfigModule with Zod-validated environment schema replaces scattered env reads and global state mutation
  • Security: KeyHandle abstraction for safe private key zeroization via useAsync()
  • Reliability: RPC fallback strategy with exponential backoff; typed error hierarchy; runtime Zod validation
  • Test suite: Unit, integration, and E2E tests added across all core modules
  • UX: CLI flags for portal/prover/private keys, opt-in fulfillment watching (--watch), quote summary table, friendly error messages, and help on bare invocation
  • Tooling: ESLint and TypeScript compiler settings tightened; CI + security scan workflows; ncc bundle for npm publish; TypeDoc + Changesets

Test plan

  • pnpm install && pnpm build completes without errors
  • pnpm dev (no subcommand) shows help and exits 0
  • pnpm test passes all unit and integration tests
  • Interactive publish flow works end-to-end on a testnet

- tronweb 6.0.4 → 6.2.0 (fixes axios DoS + validator vulns)
- @coral-xyz/anchor 0.31.1 → 0.32.1
- jest 30.1.3 → 30.2.0, ts-jest 29.4.1 → 29.4.6
- @typescript-eslint/{plugin,parser} 8.43.0 → 8.56.0
- eslint 9.35.0 → 10.0.0 (fixes minimatch ReDoS via transitive dep)
- viem range changed from ~2.40.1 to ^2.40.1
- pnpm.overrides: axios >=1.13.5, minimatch >=10.2.1
- pnpm.auditConfig.ignoreCves: bigint-buffer has no upstream fix (patched versions: <0.0.0)
Enable strictPropertyInitialization, noUnusedLocals, noUnusedParameters,
noImplicitOverride; set skipLibCheck/skipDefaultLibCheck to false.
Exclude src/scripts (has own node_modules) from main build.
Fix four noUnusedLocals violations by prefixing unused params with _.
- Define QuoteRequestPayload interface in quote.ts (replaces request: any)
- Type logger.table() with ConstructorParameters<typeof Table>[0] from cli-table3
- Replace Program/AnchorProvider/Transaction any aliases in svm-types.ts
- Change SvmError.details and DecodedEvent.data from any to unknown
- Define RawIntentPublishedData interface for safe event data transformation
- Convert all catch (error: any) to catch (error: unknown) with type narrowing
- Fix handleError(error: any) in SvmPublisher with object narrowing for Solana-specific fields
- Change decodedData?: any to decodedData?: unknown in PublishResult
- Remove unused PortalIdl import from svm-transaction.ts
- Replace jest.config.js with jest.config.ts (typed Config, coverageThreshold)
- Add tests/ directory structure: blockchain/, config/, integration/, e2e/
- Add pass-through mocks for viem, tronweb, @solana/web3.js and stub for ora
- Add test:unit and test:integration scripts to package.json
- Exclude tests/ from root tsconfig.json to prevent dist/ contamination
- Add allowJs:true and override exclude in tests/tsconfig.json
- Use projectService:true in ESLint for multi-tsconfig project support

pnpm test exits 0 (38/38 pass); pnpm build passes.
Add stricter rules: no-explicit-any (error), explicit-function-return-type
(error, allowExpressions), no-floating-promises (error), require-await
(error), no-console (error, allow warn/error), no-unsafe-assignment (warn).

Fix all 46 resulting violations: add return type annotations to 10 functions,
remove unnecessary async from 7 config functions, add eslint-disable to
logger.ts (the legitimate console abstraction), update pre-commit to run
pnpm typecheck, exclude src/scripts from ESLint (not in tsconfig).
…SK-022)

- Add ValidationResult interface and abstract validate() to BasePublisher
- Add protected handleError() and runSafely() to eliminate duplicate try-catch
- Implement validate() on EvmPublisher, TvmPublisher, SvmPublisher
- Add override keyword to publish/getBalance/validate on all publishers
- Wrap all publish() implementations in runSafely()
- Convert SvmPublisher private handleError to protected override
Create solana-client.ts (Connection + Anchor setup), pda-manager.ts
(PDA derivations), transaction-builder.ts (replaces svm-transaction.ts).
svm-publisher.ts now imports only 4 local modules.
svm-client-factory.ts kept as barrel re-export for backward compat.
Bug 1 — TVM token loop: replace hardcoded reward.tokens[0] approval with
a loop over all reward.tokens, matching EVM behavior. Multi-token intents
on TVM previously silently skipped every token after the first.

Bug 2 — SVM proverAddress: add proverAddress as 7th param to
SvmPublisher.publish() matching the BasePublisher signature. Thread it
through PublishContext and use it as override in buildFundingTransaction.

Bug 3 — TVM key cleanup: wrap TVM publish body in try/finally so that
tronWeb.setPrivateKey('') always executes regardless of success or error.
Key material no longer persists on the TronWeb instance after publish.

Bug 4 — override keyword: already complete from TASK-022.
…peline (TASK-036)

Covers: full flow (quote → encode → publish), quote failure manual fallback,
invalid address error propagation, insufficient balance validation, and
publisher factory dispatch by chain type.
…ntent lifecycle, publisher pattern, chain registry, and quote service (TASK-040)
Add @param, @returns, and @example JSDoc to all exported types, interfaces,
and functions across the seven priority files: commands/publish.ts,
config/chains.ts, config/tokens.ts, config/env.ts, and all three publishers.

Includes field-level docs on ChainConfig, TokenConfig, and EnvConfig —
notably explaining why TokenConfig.addresses uses string keys (bigint cannot
be a JS object key) and documenting each private-key format for EnvConfig.
Replace EnvSchema.parse with safeParse so Zod issues are caught and
printed as human-readable lines before process.exit(1) fires. This
ensures validation errors are visible instead of being swallowed by
NestJS's disabled internal logger.
… types

- Fix funder sent to quote API being the raw private key instead of the
  wallet address
- Default recipient to the configured wallet on the destination chain
- Add deriveAddress() helper supporting EVM (viem), TVM (TronWeb static),
  and SVM (base58/byte-array/JSON-array key formats)
- Add ConfigService.getKeyForChainType() to select the correct key per chain
- Fix rawKey selection always using EVM key regardless of source chain type
- Filter token list to only show tokens deployed on the selected chain
When the zero address is used as a token, buildReward() sets
nativeAmount = amount with tokens = [], and buildManualRoute()
builds a native call with target = recipient, data = '0x',
value = amount, matching solver PR #475 expectations.

Also replaces the broken inputManualPortal fallback in publish.command.ts
with a structured buildManualRoute() call that prompts for route amount.
Adds displayQuote() to DisplayService that renders a formatted table
with source/destination token amounts, portal, prover, deadline, and
estimated fulfillment time after a quote is successfully received.
Replace reference-first structure with progressive disclosure layout:
hero tagline, terminal demo, 3-step quick start, intent explainer,
chains/tokens tables, command reference, env config, and troubleshooting.
- Add @vercel/ncc to bundle the CLI into bundle/ at build time,
  resolving all @/ path aliases so the package works standalone
- Update bin to ./bundle/index.js, files to ["bundle", ...]
- Add #!/usr/bin/env node shebang to src/main.ts
- Update start script and clean to cover bundle/ dir
- Add bundle/ to .gitignore
The quote API can return a portalAddress different from the one in config.
publish.command.ts correctly overrides portalAddress on the watchChain object,
but publisher.getStatus was discarding it by re-looking up the chain from the
registry using only the chain ID.

Pass the full ChainConfig through so the EVM publisher uses chain.portalAddress
directly, preserving any runtime portal override from the quote.
…ucceed/fail/warn

Removes the DisplayService spinner started before publisher.publish() which ran
concurrently with the publisher's own ora spinners, causing console.log output
to merge onto the active spinner line.

Adds explicit fallback branches to succeed(), fail(), and warn() so they always
print even when no spinner is active, matching ora's visual style.
…nnet

Replace the single cached PublicClient (always using chains.mainnet) with
a per-chain Map keyed by chain.id. getPublicClient() now requires a Chain
argument and caches by chain.id.

publish(), getStatus(), getBalance(), and validate() all resolve the viem
Chain from the source/destination chain ID and pass it through, so balance
checks, token reads, waitForTransactionReceipt, and event queries hit the
correct chain descriptor.

Also removes two debug console.log statements from getStatus().
Remove hardcoded portal addresses from chain configs and require them to
be supplied at publish time. Priority order: quote service → --portal-address
/ --prover-address CLI arg → interactive prompt.

- Remove portalAddress from Base, Base Sepolia, Optimism Sepolia, Plasma
  Testnet, and Sepolia chain configs
- Add --portal-address and --prover-address options to publish command
- Add inputManualProver() prompt to PromptService (mirrors inputManualPortal)
- Update getStatus signature in base/evm/tvm/svm publishers to accept
  optional portalAddress parameter
- Remove unimplemented PORTAL_ADDRESS_* env var section from .env.example
@carlosfebres carlosfebres changed the title fix(ci): make CI workflow pass end-to-end refactor: migrate CLI to NestJS architecture with test suite and UX improvements Feb 26, 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.

1 participant