Skip to content

Only include netAPR and netAPY in estimated APR response when present#353

Open
matheus1lva wants to merge 1 commit intomainfrom
fix/apy-hook
Open

Only include netAPR and netAPY in estimated APR response when present#353
matheus1lva wants to merge 1 commit intomainfrom
fix/apy-hook

Conversation

@matheus1lva
Copy link
Collaborator

@matheus1lva matheus1lva commented Feb 27, 2026

Summary

When netAPR or netAPY components are missing from the output table, the V3 estimated APR helper (getLatestEstimatedAprV3) was returning apr: 0 and apy: 0 via the ?? 0 fallback. This is misleading — a missing value is not the same as zero. Now apr and apy are only included in the response when the corresponding component actually exists in the data.

Additionally, netAPR and netAPY are removed from the components bag to avoid duplication, since they are already promoted to top-level apr/apy fields.

How to review

Single file change in packages/ingest/helpers/apy-apr.ts. Review the destructuring + conditional spread pattern on lines 30–36.

Test plan

  • Start dependencies
docker start redis 2>/dev/null || docker run -d --name redis -p 6379:6379 redis
# Ensure Postgres is running with the required env vars
  • Start the dev environment
make dev
  • In the terminal UI, select ingestfanout abis to trigger indexing

  • Find a vault that has netAPR/netAPY components in the output table

SELECT DISTINCT chain_id, address, label
FROM output
WHERE component IN ('netAPR', 'netAPY')
  AND label LIKE '%-estimated-apr'
  AND block_time > NOW() - INTERVAL '7 days'
LIMIT 5;
  • Check the snapshot hook result for that vault — apr/apy should be present with numeric values
SELECT chain_id, address, hook->'estimatedApr' AS estimated_apr
FROM snapshot
WHERE chain_id = <chain_id> AND address = '<address>';

Expected: {"type":"...","apr":<number>,"apy":<number>,"components":{...}}apr/apy present, and netAPR/netAPY NOT in components

  • Find a vault that does NOT have netAPR/netAPY
SELECT DISTINCT s.chain_id, s.address
FROM snapshot s
LEFT JOIN output o
  ON s.chain_id = o.chain_id AND s.address = o.address
  AND o.component IN ('netAPR', 'netAPY')
  AND o.label LIKE '%-estimated-apr'
  AND o.block_time > NOW() - INTERVAL '7 days'
WHERE o.address IS NULL
  AND s.hook->'estimatedApr' IS NOT NULL
LIMIT 5;
  • Check its snapshot — apr/apy keys should be absent (not zero)
SELECT chain_id, address, hook->'estimatedApr' AS estimated_apr
FROM snapshot
WHERE chain_id = <chain_id> AND address = '<address>';

Expected: {"type":"...","components":{...}} — no apr or apy keys

  • Shut down
make down

Risk / impact

Low risk. Only affects the V3 estimated APR path (getLatestEstimatedAprV3). Consumers that rely on apr/apy always being present will now need to handle the optional case — but this is the correct behavior since a missing value should not masquerade as zero.

@vercel
Copy link

vercel bot commented Feb 27, 2026

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

Project Deployment Actions Updated (UTC)
kong Ready Ready Preview, Comment Feb 27, 2026 4:54pm

Request Review

@matheus1lva
Copy link
Collaborator Author

matheus1lva commented Feb 27, 2026

Jesus christ even after 30000 loops claude couldnt generate a decent manual test, hang on.

Now that seems fine.

Copy link
Collaborator

@murderteeth murderteeth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Summary

The core fix is correct — missing netAPR/netAPY should not be coerced to 0. The destructuring + conditional spread pattern in the helper is clean. Removing netAPR/netAPY from the components bag to avoid duplication is a good cleanup.

However, downstream schemas still declare apr/apy as required fields, so returning an object without them will break consumers.

Issues

1. GraphQL schema — apr: Float! / apy: Float! are non-nullable
packages/web/app/api/gql/typeDefs/vault.ts:124-125

The EstimatedApr type marks both fields as Float!. When a V3 vault has no netAPR/netAPY data and a client queries estimated { apr apy }, GraphQL will return a field-level error. This won't surface if clients omit those fields from their selection set, but any client that does select them will break.

Fix: change to apr: Float / apy: Float (nullable).

2. REST Zod schema — apr / apy are required
packages/web/app/api/rest/list/db.ts:42-43

The estimated object is .nullish() at the top level, but when present, apr: z.number() and apy: z.number() are required. The REST list endpoint reads snapshot.hook->'performance' directly from the DB (line 140), so if the stored JSON has an estimated object without apr/apy, z.array(VaultListItemSchema).parse() at line 204 will throw — failing the entire list response, not just one vault.

Fix: change to apr: z.number().optional() / apy: z.number().optional().

3. Lib types — EstimatedAprSchema inconsistency
packages/lib/types.ts:445-446

Not directly used in the V3 path (only V2 calls .parse()), but the shared type now misrepresents what V3 actually returns. Worth updating for consistency: apr: z.number().optional() / apy: z.number().optional().

Test plan feedback

The current test plan has a few gaps:

  • Full index on blank DB: As written it implies running fanout abis against the entire config, which is heavy. Consider scoping to a few specific vaults via config/abis.local.yaml to keep the test fast and focused.
  • Redundant docker step: make dev already starts Redis/Postgres — the standalone docker start redis step can be dropped.
  • Vault selection is left to the reviewer: Instead of asking the reviewer to query for vaults with/without netAPR/netAPY, pick concrete test addresses and state what to expect. For example:
    • A V3 vault that has netAPR/netAPY components → expected: apr and apy present with numeric values, netAPR/netAPY absent from components
    • A V3 vault that does NOT have those components (e.g. a freshly deployed vault, or one using oracle APR only) → expected: no apr/apy keys at all in estimated, not 0

Nit

componentsNonAprAPY is a bit of a mouthful. A simpler name like rest or remainingComponents would read more naturally, though this is purely cosmetic.

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