Skip to content

fix(template): align apyWindow options with api-served smoothing windows - #4

Open
alexbensimon wants to merge 2 commits into
mainfrom
feature/vau-1315-fixtemplate-align-apywindow-options-with-api-served
Open

fix(template): align apyWindow options with api-served smoothing windows#4
alexbensimon wants to merge 2 commits into
mainfrom
feature/vau-1315-fixtemplate-align-apywindow-options-with-api-served

Conversation

@alexbensimon

@alexbensimon alexbensimon commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Closes VAU-1315 — https://linear.app/morpho-labs/issue/VAU-1315

Problem

The template ships apyWindow: "7d", but VaultV2History.avgApy/avgNetApy(lookbackHours:) cap each plotted point's smoothing at 24h. A default deployment holding V2 vaults renders a headline APY labelled 7d next to a chart the app honestly relabels 1d.

Nothing was broken or dishonest — vaultV2HistoryLookbackHours clamps the request and vaultV2HistoryApyWindow relabels the series rather than claiming a smoothing the points don't have. The problem is that the public reference implementation walks into that mismatch out of the box, and didn't expose the option Anchorage uses to avoid it.

The fix adapts the configurable options to what the API actually serves, rather than working around the gap in app code.

Changes

Add "6h" to ApyWindow (config/types.ts) and APY_WINDOW_HOURS / APY_LOOKBACK_PERIOD (common/utils/timeframe.ts). The API serves SIX_HOURS on both VaultV1LookbackPeriod and VaultV2LookbackPeriod, the fork already carries "6h" | "1d" | "7d" | "30d" and runs on 6h, and this repo's data layer was already written for it — MarketSupplyApyWindows/MarketBorrowApyWindows already carry supplyApy6h/borrowApy6h, backed by MarketState.avg* fields the schema documents as genuine 6h averages. Only the config union was narrower: an oversight in the port, not a decision.

Default apyWindow"1d" (config/index.ts).

The ticket left 6h vs 1d open. 1d is the only window that plots at full fidelity on every surface, because the API's per-point smoothing is coarser than its current-state averages in both directions:

window vault V1 chart vault V2 chart market borrow chart
6h falls back to 1d native falls back to 1d
1d native native (= the 24h cap exactly) native
7d native capped to 24h native
30d native capped to 24h native

6h matches production Anchorage, but Anchorage is a vaults-only deployment. The template ships every surface, so 6h would trade the V2 vault mismatch for two new ones. 1d has none, and is the more conventional public default.

Document the gaps at the config site (config/types.ts), so an integrator learns which windows plot natively where before shipping rather than after.

Handle the windows the API doesn't serve per surface. marketHistoryApyWindow and vaultV1HistoryApyWindow floor the configured window at 1d for the two surfaces with no sub-daily averaged series, mirroring the pre-existing vaultV2HistoryApyWindow at the other end. Established rule throughout: the tab label names the configured window (the one the headline beside it genuinely uses), and the tab description discloses the window actually plotted, whenever they differ. On the default 1d nothing diverges anywhere.

Explicitly not done

  • The option set is not restricted to what V2 history serves. 7d/30d are genuinely served for headline numbers and for V1 vault and market charts. Removing them would strip real capability from V1-only deployments to work around a V2-only limitation.
  • The user-facing range selector is untouched. DATA_RANGES / CHART_RANGES already align with the API. The cap is on per-point smoothing only.

Review

Ran a 4-round adversarial review loop (three independent reviewers per round, fresh each round).

Round 1 caught a real defect in my first cut: widening the union activated a previously-dead branch that mapped the "6h" key onto VaultHistory.netApy. Per the schema that is the instantaneous series — VaultHistory's averaged family starts at dailyNetApy, there is no six-hour series. A 6h deployment would have plotted an instantaneous V1 series titled Net APY (6h), undisclosed, beside a genuinely-6h headline: precisely the mislabelling this PR exists to remove, on the one value it adds. Fixed in 8a9317c via vaultV1HistoryApyWindow, per-protocol series-window selection in the chart, and dropping the now-unused netApy selection from the V1 fragment (which is what invited the mismapping).

Rounds 2–4 found no further functional defects. Their remaining findings were all comments and doc-strings asserting things the code does not do — including my own new apyWindow doc block, which had the labelling rule backwards — and are fixed in the same commit. Final round scored 4/5, 5/5, 4/5 with no blockers.

One reviewer note left deliberately unfixed, flagged here for a human call: DataChart.tsx:71 falls back to the last plotted point when totalApy is falsy, and assembleApy coerces a null APY to 0. So a genuinely-0% headline renders the series' last value under a label naming a different window. The mechanism is pre-existing and untouched by this PR; on a 6h deployment the fallback can now disagree with the label where before it couldn't. Low impact (needs a zero/absent headline) and out of scope here — happy to open a follow-up.

Validation

pnpm codegen, pnpm check:types and pnpm check all pass clean.

pnpm test locally: the 6 env-independent test files pass (83 tests); the other 11 fail in viem's HTTP transport because they fork mainnet / read the Chainalysis oracle and this machine has no RPC URLs configured (smoke.test.ts asserts MAINNET_RPC_URL_1 is defined and fails first). The same set fails identically before and after this change — none of them touch apyWindow. The change has not been verified against a green suite; that has to come from CI.

CI note

This branch is cut from main, which predates #2 (ci/pin-action-shas). The org enforces sha_pinning_required and the current workflow uses tag refs, so no vitest check can run on this PR and it will show BLOCKED against the now-required vitest status. Expected — unblocks once #2 merges and this rebases onto it. No workflow files touched here.

🤖 Generated with Claude Code

@alexbensimon
alexbensimon marked this pull request as ready for review August 7, 2026 12:54
@alexbensimon
alexbensimon requested a review from spennyp as a code owner August 7, 2026 12:54
@alexbensimon

Copy link
Copy Markdown
Collaborator Author

@codex review

@alexbensimon alexbensimon self-assigned this Aug 7, 2026
alexbensimon and others added 2 commits August 7, 2026 18:04
The template shipped `apyWindow: "7d"`, but `VaultV2History.avgApy/avgNetApy`
cap each plotted point's smoothing at 24h. A default deployment holding V2
vaults therefore rendered a headline APY labelled 7d next to a chart the app
honestly relabels 1d.

Adapt the configurable options to what the API actually serves:

- Add "6h" to `ApyWindow` and `APY_WINDOW_HOURS`. The fork already carries
  `"6h" | "1d" | "7d" | "30d"` and runs on `6h`; the narrower union here was an
  oversight in the port — the vault history adapters already key their series
  by "6h" and market data already carries `supplyApy6h`/`borrowApy6h`.
- Default `apyWindow` to `1d`: the only window that plots at full fidelity on
  every surface (V2 history caps at 24h above it; market history has no
  sub-daily series below it).
- Document the V2 24h cap next to `apyWindow` in `config/types.ts`, so an
  integrator choosing 7d/30d learns about it before shipping.

`7d` and `30d` stay configurable — they are genuinely served for headline
numbers and for V1 vault charts. The user-facing range selector is untouched.

Allowing `6h` exposes the mirror-image gap on the borrow chart: market history
serves daily/weekly/monthly borrow APY series only. `marketHistoryApyWindow`
handles it with the same honesty rule already used for V2 vaults — the label
tracks the headline, the description discloses the series window when it
differs.

VAU-1315

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

Adversarial review of the `apyWindow` change found that widening the union to
include `6h` activated a previously-unreachable branch: the V1 history path
mapped the `"6h"` key onto `VaultHistory.netApy`, which the schema shows is the
instantaneous series — `VaultHistory`'s averaged family starts at
`dailyNetApy`, there is no six-hour series. A `6h` deployment would therefore
have plotted an instantaneous V1 series titled `Net APY (6h)` beside a
genuinely-6h headline, with no disclosure. That is exactly the mislabelling
this branch exists to remove, on the one value it adds.

Fixed the same way as market history: `vaultV1HistoryApyWindow` floors the
configured window at `1d`, the V1 path files the corresponding
daily/weekly/monthly series under the configured window, and
`VaultHistoricalApyChart` derives its series window per protocol so the
disclosure fires for V1 as well as V2. The `netApy` selection is dropped from
the V1 history fragment — no consumer remains, and its presence is what invited
the mismapping.

Also corrects several comments and doc-strings that asserted things the code
does not do, including the deployer-facing `apyWindow` block, which had the
labelling rule backwards: the tab label names the configured window (the one
the headline beside it genuinely uses) and the description discloses the window
actually plotted.

VAU-1315

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexbensimon
alexbensimon force-pushed the feature/vau-1315-fixtemplate-align-apywindow-options-with-api-served branch from 8a9317c to 57a090e Compare August 7, 2026 16:04
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
morpho-blueprint Ready Ready Preview Aug 7, 2026 4:05pm

Request Review

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