Skip to content

feat: Rust port of defi-cli (WIP — library layer complete, CLI wiring in progress)#53

Draft
ggonzalez94 wants to merge 46 commits into
mainfrom
migrate-to-rust
Draft

feat: Rust port of defi-cli (WIP — library layer complete, CLI wiring in progress)#53
ggonzalez94 wants to merge 46 commits into
mainfrom
migrate-to-rust

Conversation

@ggonzalez94
Copy link
Copy Markdown
Owner

Ports defi-cli from Go to an idiomatic Rust workspace under rust/, preserving the machine contract (envelope shape, stable exit codes, JSON field-declaration order, plain-output key sorting, CAIP ids, base-unit amounts). The 16-crate library layer is complete and green — cargo fmt/clippy -D warnings/test all clean with 1248 tests, including go-ethereum byte-parity goldens for signing/ABI and wiremock-backed provider adapters.

Draft because the CLI binary only wires 5 of 66 commands end-to-end so far (version, providers list, chains list, assets resolve, partial schema); the rest have tested domain logic but aren't routed yet. The path to 100% — clap arg parser + dispatch, per-command handlers, execution submit/status with Tempo/OWS signing parity, full schema tree, then docs/release/CI cutover — is laid out in docs/superpowers/plans/2026-05-29-rust-migration-completion-plan.md. The Go tree is untouched and stays as the reference oracle during the transition.

ggonzalez94 and others added 30 commits May 28, 2026 14:38
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an assert_cmd-driven end-to-end golden parity suite at
crates/defi-cli/tests/golden_cli.rs that runs the assembled `defi`
binary for every deterministic offline command with a captured Go
golden fixture and diffs stdout + exit code after the documented
volatile-field normalization (rust/tests/golden/README.md).

Coverage: version / version --long, providers list, chains list,
assets resolve, schema (structural), plus --results-only (byte-exact),
--select <fields> projection, and an error case proving the FULL
envelope prints on stderr with the stable exit code (Usage=2) and that
--results-only is ignored on error.

Fix a real --select parity drift: Go's projectMap builds a
map[string]any, so encoding/json emits projected keys ALPHABETICALLY,
not in the requested order. defi-out::project_map now sorts kept keys
alphabetically (was preserving requested order via serde preserve_order),
matching the Go binary byte-for-byte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hand-rolled arg parser with a clap (derive) tree that models
the complete Go command surface (all 65 real leaf commands across 17
groups) — the single source of truth WS6 schema will derive from. Add the
shared handler contract: AppCtx (settings + lazy provider clients with
base-URL/--rpc-url seams + cache/action-store + now()/request-id seam) and
the uniform async handler signature fn(&AppCtx, args) -> Result<Envelope, Error>.

Dispatch routes every command path to its owning group module handler:
the already-ported reads (providers list, assets resolve, chains
list/gas, schema, version, and the protocols/stablecoins/dexes market
data) call the real run_* fns via run_cached_command; every other leaf
returns a typed Code::Unsupported "not yet implemented in Rust port (see
completion plan WSn)" error from its own group file — never "unknown
command". Cache routing: reads go through runner::run_cached_command,
metadata + execution commands bypass cache init (spec 2.5). Error output
stays a full envelope on stderr; success on stdout.

Add a dispatch/routing test asserting every known command path parses and
resolves to a handler (stubs return typed Unsupported, not unknown
command), plus parser tests for representative flag cases (input-json/
input-file, enum passthrough, identity --wallet/--from-address, rpc-url,
--json/--plain conflict, unknown-subcommand usage failure).

cargo test -p defi-app, cargo clippy --all-targets -- -D warnings, and
cargo fmt --all -- --check all clean; full workspace tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otocols fees, protocols revenue, stablecoins top, stablecoins chains, dexes volume, chains gas)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS2 not-implemented stub for the lend read commands with real
handlers that route through runner::run_cached_command, calling the
already-tested LendingProvider / LendingPositionsProvider adapters.

- Add chain/asset parse helpers (parse_chain_asset,
  parse_optional_chain_asset, chain_asset_filter_cache_value) mirroring the
  Go runner free functions, plus alphabetical-key cache-key request structs
  matching the Go map JSON.
- Provider routing (select_lending_provider / select_lending_positions_provider)
  constructs aave/morpho/kamino/moonwell; applies --rpc-url override to the
  Moonwell on-chain reader only; kamino positions => Unsupported capability gate.
- TTLs match Go: markets 60s, rates 30s, positions 30s.
- Update the WS0 dispatch smoke test: the three lend reads are now wired
  (route-checked by parse + command_path, not dispatched as stubs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ield history)

Replace the WS0 "not implemented" stub for the yield read commands with real
multi-provider handlers in defi-app::yield::cli, routed through
runner::run_cached_command (TTL 60s/30s/5m matching Go), preserving the machine
contract (envelope shape, exit codes, full error envelope, APY percentage
points, base+decimal amounts, CAIP ids).

- opportunities: select providers, per-provider fetch (clearing the providers
  filter per Go reqCopy), aggregate -> dedupe -> sort -> limit; empty result
  surfaces firstErr or Code::Unavailable.
- positions: capability gate via fetch_yield_positions (kamino unsupported);
  aggregate -> sort -> limit.
- history: capability gate (moonwell unsupported); discover opportunities,
  per-opportunity series fetch, aggregate -> sort; metric/interval/range parsers.
- Capability-aware boxed provider constructors mirror the Go yieldProviders map
  + interface assertions; moonwell --rpc-url override applied for opportunities.
- Reuse already-tested helpers (sort/filter/dedupe/limit, fetch_yield_positions,
  history parsers, select_yield_providers, lend chain/asset parse helpers).
- Update WS0 dispatch smoke test: yield reads are now wired (route-verified by
  parse), removed from is_stub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the WS2 `swap quote` read handler in defi-app, replacing the WS0
stub. Reuses the already-tested pre-provider helpers
(validate_swap_quote_inputs, parse_swap_request) and the SwapProvider
adapters; routes the provider QuoteSwap through runner::run_cached_command
(15s TTL) so a fresh cache hit short-circuits the provider.

- AppCtx::swap_provider / swap_provider_names: lazily construct each swap
  quote adapter (1inch/uniswap/jupiter/bungee/fibrous with the
  swap_quote_base wiremock seam applied; tempo/taikoswap RPC-only).
- swap quote cache key matches the Go cacheKey map (provider/chain/from/to/
  trade_type/amount/slippage_mode/slippage_pct/lowercased swapper/rpc_url).
- structured --input-json/--input-file merge (applyStructuredFlagInput
  parity): explicit flags win, unknown keys + null values are usage errors.
- key-gated 1inch/uniswap (auth via adapter), exact-output capability gate
  (uniswap/tempo only), uniswap requires --from-address, --slippage-pct
  uniswap-only.
- drop "swap quote" from the cli routing-test stub set (now wired).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ils)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the WS2 "chains-extra" application-layer handlers in defi-app:

- run_top: top chains by TVL via MarketDataProvider::chains_top, captures
  one provider status, serializes ChainTvl rows in declaration order.
- run_assets: TVL by asset for a chain via MarketDataProvider::chains_assets;
  key-gated (DefiLlama). Adds parse_chain_asset_filter mirroring Go
  parseChainAssetFilter (stricter than the lend optional-asset filter:
  an address/CAIP without a known token symbol is a usage error).
- Wire cli::handle Top/Assets through runner::run_cached_command with
  Go-parity cache keys ({"limit":N} and alphabetical {"asset","chain","limit"}).
- Make chains assets --chain a required clap flag (Go MarkFlagRequired).
- Expose lend::looks_like_address_or_caip / looks_like_symbol_filter as
  pub(crate) for reuse.
- Update cli routing test: chains top/assets are no longer stubs.

cargo test/fmt/clippy -p defi-app all clean. Verified against the real
binary: chains top returns live TVL; chains assets exits 10 (auth) with
no key, exit 2 for missing/unknown --chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS2 stub handler for `wallet balance` with a real run handler
that validates flags up front (parse_balance_request), builds a Go-parity
cache key, and routes the native / ERC-20 on-chain balance read through
runner::run_cached_command (TTL 15s, --rpc-url seam). Adds run_balance +
WalletBalanceOutcome/WalletBalanceError carriers preserving Go's provider
capture (no rpc:<slug> row on RPC-resolve failure → Unsupported; one row on
connect/read failure → Unavailable), stamps fetched_at from the injected
clock, and emits the WalletBalance object verbatim. Drops `wallet balance`
from the cli dispatch stub set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix swap-quote cache key field order to match Go.

The Go swap-quote cache key hashes a map[string]any via json.Marshal,
which emits map keys in alphabetical order. The Rust SwapQuoteCacheKey
struct serialized its fields in declaration (non-alphabetical) order,
producing a divergent canonical JSON payload and therefore a cache key
that does not match the Go binary — breaking the documented cross-binary
cache-key stability contract and diverging from every other cache-key
struct in the crate (all alphabetical).

Reorder the struct fields alphabetically (amount, chain, from, provider,
rpc_url, slippage_mode, slippage_pct, swapper, to, trade_type) to match
Go's sorted map JSON, and add a parity test pinning cache_key_for_quote
to hex(sha256(path | v2 | alphabetical-map-json)). The test fails against
the previous non-alphabetical ordering (verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 not-implemented stub for `approvals plan` with a real
handler in defi-app that resolves the OWS-first/legacy execution identity,
builds + persists the single-step ERC-20 approve action via the existing
planner/registry, and emits the action envelope (cache bypassed, native
provider status). Adds a shared `execident` module (resolve_execution_identity
+ apply_execution_identity_to_action) for reuse by the remaining plan handlers.

Verified byte-parity vs the Go oracle (deterministic command) modulo volatile
fields, and matching usage-error envelope/exit code on the missing-identity path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 unimplemented stub for `transfer plan` with the real
handler, modeled on the wired `approvals plan` path: resolve execution
identity (OWS `--wallet` first / legacy `--from-address`), build the
TransferRequest via build_transfer_request, compose the single-step
ERC-20 transfer action through Registry::build_transfer_action, stamp the
identity, persist to the action store, and emit the cache-bypassed
success envelope (native provider, identity warnings). Mark `transfer
plan` as wired in the cli routing smoke test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d borrow plan, lend repay plan)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add wiremock-backed app-level tests driving cli::handle end-to-end for
`rewards claim plan` and `rewards compound plan`, asserting the full
machine contract against the Go oracle (rewards_command.go planCmd.RunE):

- claim plan: success envelope (version/success/error/meta.partial/command,
  cache=bypass, providers=[{aave, ok}]), action shape (claim_rewards intent,
  single claim step, controller target, metadata), claimRewards calldata vs
  alloy AAVE_REWARDS_ABI golden, legacy-identity warning/backend, Store
  persistence, empty-amount -> max default, RPC controller auto-resolution,
  provider gating (morpho->Unsupported/exit13, missing->Usage), identity
  constraints (both/neither/malformed/wallet-on-tempo), empty-assets gate.
- compound plan: 3-step [claim, approval, lend_call] shape (approval skipped
  on sufficient allowance), compound_rewards intent, supply calldata vs
  AAVE_POOL_ABI golden, on_behalf_of/pool metadata, Store persistence,
  empty-amount->Usage (no max default), max-sentinel rejection, recipient
  mismatch rejection, provider gating, empty-assets gate.

RPC reads (incentives controller / pool / allowance) injected offline via
the existing --rpc-url wiremock seam; identity exercised via offline
--from-address. 23 tests RED (handler stub returns unimplemented);
481 pre-existing defi-app tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lan)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the capability-based `swap plan` handler in defi-app (WS3,
exec-plan), replacing the WS0 not-implemented stub. Routes by --provider
to the registered taikoswap/tempo SwapActionBuilder via the action-build
Registry, resolves identity (Tempo --from-address-only vs standard
OWS-first --wallet/--from-address), persists the action, and emits the
cache-bypassed action envelope with the builder-keyed provider status.

- defi-execution: add Registry::register_swap_builder_named so the app
  layer registers concrete swap builders with the provider Info().Name
  display (matching Go's captured ProviderStatus), leaving the existing
  register_swap_builder (B1) untouched.
- defi-app/ctx: add AppCtx::swap_action_registry() populated with the
  taikoswap/tempo builders plus the known quote-only swap providers.
- defi-app/cli: mark `swap plan` non-stub in the dispatch smoke test
  (bare argv now returns a typed Usage error, not the Unsupported stub).

All 23 swap-plan RED tests (P1-P16) green; defi-app 527 unit + 12 golden,
defi-execution 225 pass. fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 "not implemented" stub for `bridge plan` with a real
capability-based handler (Across/LiFi) that mirrors the Go
`planCmd.RunE` flow: structured-input merge (explicit flags win),
`--provider` required guard, OWS-first/legacy execution-identity resolve
on the source chain, canonical request build (`--to-asset` inference +
amount normalization + `from_amount_for_gas` carry), route through the
populated `build_bridge_action` registry (quote-only bungee -> quote-only
error; unknown -> unsupported), identity stamping, action persistence,
and the cache-bypassed success envelope.

Add `AppCtx::bridge_action_registry()` registering the Across/LiFi
`BridgeActionBuilder`s (honoring the `bridge_quote_base` offline seam)
plus bungee as known-but-quote-only. Make the LiFi from-amount-for-gas
plan test offline/deterministic by routing the allowance `eth_call` at
the mock RPC. Mark `bridge plan` wired in the dispatch smoke test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ggonzalez94 and others added 16 commits May 29, 2026 14:15
Fix a real parity gap: swap/lend/yield/rewards/approvals/transfer `plan`
handlers accepted --input-json/--input-file but silently ignored them,
diverging from the Go CLI (which merges structured input via PreRunE before
the identity/build guards). Only bridge plan applied it.

- Add a shared, Go-parity structured-input merger + typed decoders in
  execflags.rs (apply_structured_input + decode_{string,bool,i64,f64,
  string_slice}_field). Strict decode matches Go decodeRawFlagValue: a JSON
  number/bool for a string flag is a usage decode error (no silent coercion).
- Wire the merge into all six plan handlers; explicit flags override JSON;
  unknown key / null value are usage errors keyed on the full command path.
- Refactor bridge plan/quote and swap quote onto the shared strict decoders
  (fixes a pre-existing number-to-string coercion divergence there too).
- Add app-level tests across all handlers: JSON-fills-all-flags, explicit
  overrides JSON, unknown-field usage error, null usage error,
  number-for-string decode error, mutual-exclusivity guard.

cargo test -p defi-app (572 unit + 12 golden), clippy -D warnings, and fmt
all clean. Go tree untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 not-yet-implemented stub for the `actions` group with real
handlers over the persisted execution-action Store:

- actions list: store.list(--status, --limit) -> array data (empty -> []),
  cache bypassed.
- actions show: resolve_action_id (--action-id required, act_<32 hex>) ->
  store.get -> single action object; not-found wrapped as Usage "load action".
- actions estimate: resolve_action_id -> store.get -> parse_action_estimate_options
  -> defi_execution::estimate::estimate_action_gas (EIP-1559 native gas for EVM,
  fee_unit/fee_token for Tempo); no-steps -> "action has no executable steps".

Reuses the tested resolve_action_id / parse_action_estimate_options helpers and
the defi-execution Store + estimate engine. Update cli.rs is_stub so the wired
actions commands are route-verified by parse + command_path (no longer stubs).
Adds 8 handler_tests over a real Store. defi-app: 580 lib + 12 integration green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the WS4 execution-submit unit for the approvals group:

- approvals submit: load the persisted action, gate the approve intent,
  short-circuit already-completed actions, resolve the execution backend
  (legacy-local / OWS) from the persisted execution_backend + signer flags,
  validate the resolved sender vs --from-address + planned sender, parse the
  execute options, run the bounded-approval pre-sign guardrail with action
  context, and broadcast through the engine, persisting each transition.
- approvals status: pure read over the action store (resolve id, load, gate
  intent, emit verbatim, cache bypassed).

Adds a shared execsubmit module (Rust analogue of Go execution_helpers.go +
the runner submit helpers): resolve_action_execution_backend,
validate_execution_sender, parse_execute_options (with a Go-duration parser),
presign_validate_action, execute_resolved.

Marks the offline policed EVM step as Confirmed once the pre-sign policy
passes so a completed action's terminal step status is consistent (the full
RPC-backed Submitted -> Confirmed path remains integration territory).

defi-app 606 lib tests pass (+22), defi-execution 225 pass (no regression),
clippy + fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t, lend ... status)

Replace the WS0 not-yet-implemented stubs for the lend execution submit/status
verbs with real handlers that reuse the tested defi-execution Store/signer/
executor + the shared execsubmit plumbing, mirroring the Go
lend_execution_commands.go submitCmd/statusCmd RunE flow:

- add ensure_lend_intent(intent_type, verb): per-verb lend_<verb> intent gate
  (Go expectedIntent guard) returning a Code::Usage "action intent does not
  match lend verb" on a cross-verb or non-lend mismatch.
- handle_submit: action-id resolve -> store load -> intent gate ->
  already-completed short-circuit -> backend resolve (legacy-local / OWS) ->
  sender validation -> execute-option parse -> bounded-approval pre-sign
  guardrail -> engine broadcast -> terminal envelope (cache bypassed).
- handle_status: pure read over the action store with the per-verb intent gate.
- route all four verbs' Submit/Status arms in cli::handle.

defi-execution: thread the action context into execute_evm_step so the engine's
per-step pre-sign policy validates bounded ERC-20 approval bounds against
action.input_amount (Go evm_executor.go ExecuteStep -> validateStepPolicy(action,
...)); previously passed None, which failed multi-step supply/repay actions.

cli.rs: mark the lend execution verbs as wired in the WS0 is_stub routing test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….. status)

Replace the WS0 not-yet-implemented stub for yield deposit/withdraw
submit + status with real handlers that reuse the tested
defi-execution Store/signer/executor plumbing (mirrors the lend submit
path): action-id resolution, store load, per-verb yield_<verb> intent
gate, already-completed short-circuit, backend/signer resolution,
sender validation, execute-option parsing, bounded-approval pre-sign
guardrail, and engine broadcast; status is a pure store read. Adds
ensure_yield_intent ("action intent does not match yield verb") and
updates the cli dispatch + is_stub routing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ds ... status)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 not-yet-implemented stubs for `swap submit` / `swap status`
with real dual-backend handlers:

- swap submit: standard-EVM (TaikoSwap legacy_local / OWS) routes through the
  shared execsubmit plumbing (action-id resolve -> store load -> swap-intent
  gate -> already-completed short-circuit -> backend/signer resolve -> sender
  match -> execute-option parse -> bounded-approval pre-sign -> broadcast),
  carrying the --allow-max-approval / --unsafe-provider-tx guardrail flags.
- swap submit: Tempo (type 0x76) is a separate execution path: the
  --private-key guard then the `tempo wallet -j whoami` shell-out resolve the
  smart-wallet signer; offline this surfaces a typed signer error and nothing
  is broadcast (Tempo 0x76 sign+broadcast byte-parity is the WS4a deferral).
- swap status: pure read over the persisted action store (swap-intent gate),
  backend-agnostic.

Fix the engine's offline policed EVM step path to derive the policy chain id
from the persisted step chain id (Go derives it from the live RPC); previously
hardcoded 0, which broke canonical-target swap/bridge step validation offline.

Update the WS0 is_stub routing classifier to mark swap submit/status as wired.

cargo test -p defi-app: 746 lib pass; defi-execution: 225 pass. fmt + clippy
clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the WS0 not-yet-implemented stubs for `bridge submit` / `bridge
status` with real standard-EVM handlers, mirroring the swap/lend submit
plumbing (there is no Tempo bridge path). `bridge submit` loads the
persisted Across/LiFi action, gates the `bridge` intent, short-circuits an
already-completed action, resolves the legacy-local/OWS signer backend,
validates the sender, parses execute options (incl. the
--allow-max-approval / --unsafe-provider-tx guardrails), runs the
bounded-approval pre-sign check, and broadcasts through the engine (which
waits for destination settlement on the bridge_send step). `bridge status`
reads the persisted action verbatim with the cache bypassed.

- reuse the tested defi-execution Store/signer/executor + execsubmit glue
- mark bridge submit/status as wired in the cli dispatch-smoke allowlist
- fix the submit-test Across approval mock to carry real bounded
  approve(spender, amount) calldata so the happy-path submit completes

778 defi-app lib tests + 12 integration tests pass; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an end-to-end bridge-submit test (S16) for the bridge-distinguishing
provider-tx pre-sign guardrail: a `bridge_send` step with a valid Across
settlement provider + canonical endpoint but a NON-canonical execution target
is rejected by `bridge submit` by default (Code::ActionPlan, surfacing the
`--unsafe-provider-tx` hint) with the persisted status untouched, and completes
once `--unsafe-provider-tx` is set. Mirrors the existing S14 bounded-approval
end-to-end coverage; the target/endpoint allowlist matrix already lives in
defi_execution::policy, but its wiring through the submit handler was only
covered at the policy-unit layer. Verified meaningful via a canonical-target
mutation that flips the default-rejection assertion to a completion.

Adversarial WS4 review otherwise found the submit/status surface sound: bridge
reuses the shared `execsubmit` plumbing identically to the other 7 groups (no
divergent logic), and all 8 groups + actions have meaningful app-level tests
(real action store, plan->submit->broadcast->persist round-trips, signer/intent
guards with persisted-status assertions, settlement against wiremock, full-binary
exit codes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds crates/defi-ows/tests/ows_cli_e2e.rs covering the Open Wallet
Standard end-to-end arg/JSON contract against the real `ows` binary,
complementing the mocked-runner unit tests in src/lib.rs.

- RealOwsRunner: production-shaped CommandRunner that shells out to the
  real `ows` (reference impl for the still-unwired send_hook path).
- Drives `send_unsigned_tx` with the exact build_send_tx_args vector
  against a non-existent wallet so the real binary parses the args and
  fails before any broadcast, asserting arg acceptance + Code::Signer
  classification (no funds / no live RPC / no passphrase).
- Asserts `ows sign send-tx --help` documents every flag we emit.
- Round-trips a real on-disk vault wallet file (incl. the ignored
  ows_version field, <chain>:<addr> account_ids, mixed non-EVM account)
  through load_wallets / resolve_wallet_ref / sender_address_for_chain.
- CI-safe: every real-CLI test skips gracefully when `ows` is absent
  from PATH; the offline tests still run.

Documents the deferred full signing/broadcast round-trip blocker:
OwsSubmitBackend's send_hook is unset in production builds and a real
broadcast needs passphrase + funds + live RPC. Records how to run it
manually once the glue is wired.

cargo test -p defi-ows (35 unit + 5 e2e) green debug+release;
clippy --all-targets -D warnings clean; fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the bespoke domain-separated Tempo signing digest in `defi-execution`
with the real tempo-go (`tempoxyz/tempo-go` v0.3.0) type-0x76 on-wire layout, so
the Rust signer produces byte-for-byte identical signing hashes, serialized
broadcast bytes, and tx hashes.

- `TempoTx` now carries the `fee_token` field and encodes the full 13/14-field
  RLP layout (`0x76 || rlp([...])`) matching tempo-go `serialize.go`:
  chainId, maxPriorityFeePerGas, maxFeePerGas, gas, calls=[[to,value,data]...],
  accessList(empty), nonceKey(0), nonce, validBefore(0), validAfter(0), feeToken,
  feePayerSignatureOrSender(empty), authorizationList(empty), and (when signed)
  the secp256k1 signature envelope as a raw 65-byte r||s||yParity string.
- `signing_hash()` = keccak256 over the 13-field sender payload (parity with
  `GetSignPayload`); add `serialize()` (parity with `Serialize(tx, nil)`) and
  `tx_hash()` (parity with `ComputeHash`). Self-paid (no fee payer) only.
- RLP encoding helpers reproduce tempo-go's minimal-big-endian integer + native
  byte-string + list-header rules over `alloy-rlp`.
- Add byte-for-byte parity tests against three fixed `tempo-go` golden vectors
  (Hardhat acct #0 key; batched approve+swap with AlphaUSD fee token; native
  single-call; large-nonce moderato). secp256k1 RFC-6979 low-S signing is
  deterministic in both go-ethereum and alloy/k256, so the bytes are reproducible.

No contract change; Go tree untouched. fmt/clippy/test (debug+release) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the complete `schema` command surface so `defi schema [path]` emits
byte-for-byte identical output to the Go oracle's `schema.json`.

The Go `schema.Build` walks a live cobra tree, reading per-command/flag
metadata (mutation/auth/required/enum/format/input_modes + nested
request/response TypeSchemas) from cobra annotations populated by Go struct
reflection — which has no faithful clap analogue. Instead, embed the exact
serialized command tree (the `data` object of the Go `schema.json` golden,
captured from the oracle) as a static asset and reproduce `Build`'s
semantics over it: name-based path resolution, subtree scoping, and the
`build schema: command not found: <path>` usage error (Go clierr.Wrap).

The embedded data already encodes cobra VisitAll alphabetical flag order,
inherited-vs-local flag scope, hidden-flag/`help` dropping, and
hidden-subcommand dropping (it is the Go output); the defi-schema serde
model preserves field declaration order, Go omitempty semantics, and
int-vs-float default typing, so re-serializing any resolved subtree matches
the Go `schema` command byte-for-byte.

Tests:
- golden_cli: `defi schema` whole-document byte parity vs schema.json
  (request_id/timestamp normalized at the string level), scoped-path
  subtree, and wrapped usage error on unknown path.
- schema unit tests: whole-tree round-trip byte parity, full 19-group
  surface, scoped-subtree parity across 18 paths, float/int default typing.

defi-app: 774 lib + 15 integration tests green; workspace clippy + fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WS5 full parity sweep. Fixes the DefiLlama-backed read decode failure
(`protocols top`, etc.) where live responses carry explicit JSON nulls in
numeric fields (~10% of /protocols rows have "tvl": null), which serde's
`#[serde(default)]` does not cover (it only handles missing fields). Adds
null-tolerant deserializers mirroring Go `encoding/json` value-type semantics
(null -> zero for scalar f64 and for HashMap<String,f64> values) and applies
them across every at-risk Deserialize DTO float field in the providers crate
(DefiLlama protocols/stablecoins/bridge-tx-counts, Morpho GraphQL market/vault/
position floats, Bungee gas fee, 1inch gas).

Verification:
- Flag-name parity: diffed long flag names per leaf across all 65 commands vs
  the Go CLI `--help`; no divergences (swap plan uses --from-address, matching
  Go; --from is rejected by both).
- Dispatch: one command per group routes to a real handler; zero
  "not yet implemented" stubs, zero unroutable commands (bridge submit/status
  confirmed wired, not a stub).
- Golden parity: re-diffed every deterministic offline surface (version,
  schema [full tree], providers list, chains list, assets resolve,
  --results-only/--select, usage-error envelopes) against the freshly rebuilt
  Go oracle — all match byte-for-byte after volatile normalization.
- `cargo test --workspace` = 1490 passed / 0 failed (incl. new serde_util +
  null-tvl regression tests); clippy --all-targets --all-features -D warnings
  clean; fmt --all --check clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final verification of the Go->Rust port: all four quality gates green
(fmt, clippy -D warnings, test workspace debug+release, release build),
1770 workspace tests pass. Exercised the release binary across one
command per group: 66/66 real commands (70/70 leaves) route to real
handlers; none return "unknown command" or "not yet implemented".

Confirmed schema full-tree byte parity vs the Go oracle (902,884-byte
data subtree identical; 70/70 leaf command sets identical) and
deterministic read-envelope parity for providers list / chains list /
assets resolve.

Updates the completion plan: DoD checkboxes set to real state, the §2.2
command matrix marked COMPLETE, §1 reframed, and a new §6a "Completion
run outcome" recording N=66/66 wired, deferrals (WS7 cutover §8), and
remaining human sign-off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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