Integrate the eris-dashboard as a workspace, driven by on-chain-derived run data (#63) - #65
Merged
Merged
Conversation
Plain copy of the standalone eris-dashboard repo (Vite + React 19 + Tailwind v4, seed-data driven) into dashboard/, per issue #63 Phase 0: - add dashboard to root npm workspaces; drop bun artifacts (bun.lockb, packageManager field) — installs and runs under npm - root script: `npm run dashboard` starts the dev server on :5173 - README: document the dashboard as an optional workspace, still on seed data until the runsProvider lands (Phase 1) Verified: workspace typecheck + production build pass, dev server serves, root typecheck / check:boundaries / tests (508) unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Phase 2) Add core/src/realtime/marketSeries.ts: after the run ends, the same blockNumber-pinned Multicall3 machinery that reconstructs the value series derives a dashboard-facing market artifact and writes runs/<id>/market.json — zero reads added to the per-block coordinator loop, and a failure degrades the artifact, never the run (independent try/catch, market_series_reconstructed / _failed events). Per sampled block (config.scoreEvery stride, fromBlock/toBlock pinned): - per-venue executable two-sided quotes (uniswap slot0 mid +/- fee, curve get_dy probes, balancer querySwap probes — the adapters' probe discipline) plus pool depth in USD - GMX open interest long/short + saved funding factor (DataStore keys derived per Keys.sol; unit-tested against cast-computed vectors) - Aave reserve totals (aToken / variableDebtToken supplies, utilization) - multi-asset fair prices (PriceFeed latestAnswer / answerOf) Plus end-of-run per-agent GMX positions and Aave account data, and per-tx USD notionals decoded from receipts (Transfer logs vs the tx sender; unknown tokens are counted, never silently priced). failedReads/failedReadTargets are tracked the same way as the value series. Note: notionals read receipts directly instead of the Blockscout token-transfers API the issue sketches — the receipt decoder must exist as the explorer-down fallback anyway, and it yields the same data with one less moving part. Blockscout remains the deep-dive UI. Measured (calm, 60-block backtest): 59 cross-sections x 3 venues x 2 bases, failedReads 0, +755ms post-run; 347/924 txs decoded to notionals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the seed-data provider as the default with runsProvider, which builds
all five snapshots from runs/<id>/ files only: summary.json (standings —
score = M9 scaled to bps/epoch, PnL%, Sharpe, max drawdown from the epoch
series), events.jsonl (reconstructed observations -> price/portfolio series,
typed event tape), blocks.csv (explorer blocks/txs, methods joined from the
agents' submitted-tx self-reports), agents/<id>.jsonl (decision logs), and
market.json when present (per-venue quote series for the cross-venue arb
chart with real buy/sell markers, MarketStats volume/OI/funding/liquidity,
GMX positions table, decoded tx amounts, multi-asset tickers and closing
prices). Runs without market.json degrade to the Phase 1 rendering.
- vite.config.ts: dev-server plugin serving /runs/index.json + artifacts from
the sibling runs/ dir; /blockscout proxy to :3100
- run picker in the sidebar (localStorage-persisted; hooks refetch on change)
- Blockscout deep links for tx/block/address, feature-flagged on a one-shot
availability probe — links vanish when the explorer is down, nothing else
- keep localProvider behind VITE_DATA_PROVIDER=seed for UI development
- fix seed-era podium formatting ("+-13.1%" on negative PnL)
Fix found while wiring: reconstructed observations serialize blockNumber as a
string, so the price series must Number() it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A run in progress (no summary.json yet, events.jsonl still moving) now shows up in the run picker as "(live)" and every view refreshes in place every few seconds, flipping to the archived rendering automatically when the run completes. Live data comes from three places, none of them the coordinator: - file tails: a /runs/<id>/tail/<file>?offset=N dev-server endpoint streams appended bytes of events.jsonl (run meta, tape, tx_submitted attribution) and agents/<id>.jsonl (live decision log) - JSON-RPC: the anvil endpoint is discovered from the agents' own runtime_start log lines; the browser reads chain height, recent blocks, and the on-chain PriceFeed (a live fair ticker accumulates while the page watches) - Blockscout, optionally: its indexed height renders next to the RPC height so the indexer lag stays visible Each live panel carries the block height it reflects. Scores, portfolio curves, per-venue series and notionals are post-run artifacts by design and appear on completion. liveRun.ts assembles a synthetic LoadedRun so the ordinary snapshot builders render both modes; a shared useSnapshot hook refreshes live data without flashing the loading state. Note one divergence from the issue sketch: blocks.csv cannot be tailed live — the coordinator writes it after the run (the per-block write was deliberately removed from the hot loop), so the explorer-down fallback for live blocks/txs is direct RPC instead. Verified against a live backtest (calm#202, 120 blocks): live detection, countdown, RPC blocks/fair ticker, streamed decision log, and the automatic live -> archived flip with real scores, all without reload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seed build shipped UI that described a product which does not exist. This replaces or removes every piece the acceptance criterion "no seed data on screen" still caught: - order book -> AMM depth panel: every venue is an AMM, so the right column now shows per-venue pool depth (series + current, from market.json) with the executable two-sided quote, instead of fictional asks/bids - rank move redefined as the rank change over the run's final epoch on cumulative value gain — a single run has no previous round, but it has epochs, the same unit the score is computed over - orders tab renders only when a run actually produced trigger orders (no current strategy uses them) - market pair selector driven by the run's actual bases (WETH/WBTC/...), and switching pairs now really switches the candles, stats, depth, positions and arb view; the decorative timeframe chips are gone - top-page info tabs rewritten as real documentation of the simulator (overview / environment / scoring / artifacts), sourced from the README and ADRs instead of invented seasons, prizes and sponsors - honest chrome: ASCON -> ERIS everywhere, "Round 11 of 13 · live" -> actual run number and status with the countdown only while live, leaderboard caption states the real metric (mean − λ·std of epoch log returns) - unreachable fictional pages deleted (/rules with invented regulations, /register with a fake signup countdown) The seed provider stays for UI development; its market snapshot now synthesizes venue depths and survives old IndexedDB blobs by attaching the new fields at read time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…63) Found watching a real run end on screen: the coordinator's teardown (bulk blocks.csv recording, then the reconstruction sweeps) leaves events.jsonl silent for tens of seconds while summary.json does not exist yet. The run index judged liveness by events.jsonl mtime alone with a 30s window, so the run vanished from the index mid-teardown — the dashboard flipped to the neighboring run and its refresh loop stopped there, stranded on the wrong run's data. - judge liveness on the newest of events.jsonl and blocks.csv, with a 120s window that bridges the quiet stretches - useSnapshot: one grace refresh after a live -> not-live transition, so a transitional read settles on the next poll instead of freezing the screen Re-verified end to end against a live run (calm#404): the run stays selected through teardown and flips in place to its own archived rendering — real scores, and the Phase 4 epoch move column firing on real data (venue-arb +1 / multi-arb -1 over the final epoch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ten findings from an external review of the #63 work, each verified against the code before fixing: core (market.json producer): - summarizeTransfers records baseUnits and a per-unit priceUsd when a tx really exchanged both legs; the missing counter leg is what tells an Aave supply or GMX collateral move apart from a swap - fairLookup takes the last sample at or before the tx's block — with a thinned series the nearest sample could sit in the tx's future, pricing a notional across a crash edge off information the tx never had - a failed GMX funding read leaves fundingPerHourBps absent instead of printing 0.00bps (issue #44's no-silent-zero discipline) - tokenUsd prices the fork pools' USDC.e/USDT legs at par, so fork runs keep their balancer/curve depth series - document that the uniswap quote is slot0 mid +/- fee (the no-arb monitor's approximation), not a tick-walk dashboard: - LiveRunState.refresh() shares one in-flight promise: the tail offsets are mutable shared state and StrictMode's double-mounted effects were folding the same chunk twice on every page load - live fair samples pin eth_call to the height they are stamped with, not "latest" read seconds later - Trades tab shows base quantity and per-unit price (the "Price" column was showing the tx's total USD notional); volume counts only both-leg trades and the label says "Volume · run", not "24h volume" - agent-detail GMX positions mark against their own base's final fair (a WBTC perp was priced off the WETH fair) - the archive view of a live run keeps polling until the final artifacts land instead of freezing on a partial snapshot - runs middleware: realpath check closes symlink escape; tail responses are chunk-capped so a first tail of a large log doesn't buffer it whole Not changed on purpose: the uniswap quote approximation itself, and the SAVED_FUNDING_FACTOR funding source — both documented instead. Verified against a fresh backtest: swaps decode with priceUsd/baseUnits, one-sided transfers carry none, WETH volume corrects from $228,699 to $203,589, and the Trades tab renders quantity x unit price on screen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #63.
Imports the eris-dashboard UI as the
dashboard/workspace and drives every view from real run data, per the issue's guiding principle: the chain is the source of truth — numeric series are derived from on-chain reads by the post-run reconstruction, logs supply only reasoning, intent and identity.What's in here (one commit per phase)
npm run dashboardcore/src/realtime/marketSeries.ts: after the run, the same blockNumber-pinned Multicall3 machinery that reconstructs the value series derivesruns/<id>/market.json— per-venue two-sided quotes + pool depth, GMX OI/funding (DataStore keys per Keys.sol, unit-tested against cast-computed vectors), Aave reserve totals, multi-asset fair series, end-of-run GMX positions / Aave accounts, and per-tx USD notionals decoded from receipts. Zero reads added to the per-block coordinator loop; failures degrade the artifact, never the runrunsProviderbuilds all five snapshots fromsummary.json/events.jsonl/blocks.csv/agents/*.jsonl/market.json; run picker in the sidebar; Blockscout deep links behind a one-shot availability probe;VITE_DATA_PROVIDER=seedkeeps the seed provider for UI development(live)and every view refreshes in place. Data comes from incremental file tails (a dev-server/runs/<id>/tailendpoint), JSON-RPC reads of the chain (endpoint discovered from the agents' ownruntime_startlog lines), and optionally Blockscout's indexed height so the indexer lag stays visible. Views flip to the archived rendering automatically on completionmoveredefined as the rank change over the run's final epoch, orders tab hidden until a run produces trigger orders, pair selector driven by the run's actual bases (switching really switches), info tabs rewritten as accurate documentation, ASCON → ERIS, fictional /rules and /register pages deletedDeviations from the issue sketch (deliberate, documented in the commits)
blocks.csvtailing: the coordinator writesblocks.csvafter the run (the per-block write was deliberately removed from the hot loop), so there is nothing to tail liveAcceptance criteria
npm install→npm run sim:realtime→npm run dashboardrenders the completed run with no seed data on screen — verified against a realsim:realtimerun (example.yaml roster: self-improving agents + frozen control, lst/liquity venues)sim:realtimecan be watched live — verified end-to-end twice, including the automatic live → archived flip with real scores and the epoch move column firing on real data🤖 Generated with Claude Code