Skip to content

Use completed UTC buckets for position analytics#610

Open
antoncoding wants to merge 1 commit into
masterfrom
codex/completed-utc-position-periods
Open

Use completed UTC buckets for position analytics#610
antoncoding wants to merge 1 commit into
masterfrom
codex/completed-utc-position-periods

Conversation

@antoncoding

@antoncoding antoncoding commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep 24H rolling; calculate 7D, 30D, 3M, and 6M from completed UTC days.
  • Extend the existing PositionDailyFlow paginator to fetch sparse user flows and market-day states together, instead of keeping a second analytics pipeline.
  • Skip raw transaction-history downloads on archive-capable chains, reuse daily flows for completed-period charts, and resolve exact UTC boundary blocks.
  • Show the calculation range only on the selected period badge tooltip.

Cleanup

  • Deleted the separate daily-analytics data source, query builder, and calculation utility introduced in the first revision.
  • Removed tooltip/chart prop experiments that did not affect correctness.
  • Kept only the historical end snapshot handoff required to prevent completed-period charts from ending at the live balance.
  • Net result: 191 fewer lines than the first PR revision.

Verification

  • npx ultracite fix
  • npx ultracite check
  • pnpm check
  • pnpm build
  • Live indexer cursor test across two pages for both flow and market rows
  • High-activity 6M case (0xfe6509875528e7ea210127fad61800d1fd0d77bd, Optimism): 303 flow rows + 863 market-day rows, one analytics request, ~381 KB
  • Deterministic checks for deposit/withdraw earnings, time-weighted exposure, and negative earned values

Summary by CodeRabbit

  • New Features

    • Added more accurate earnings calculations for completed UTC day, week, month, and longer periods.
    • Improved historical position charts with end-of-period snapshots and daily analytics.
    • Added clearer earnings time-range details when viewing period settings.
    • Improved historical vault APY calculations across bounded date ranges.
  • Bug Fixes

    • Improved handling of sparse activity and market data for high-activity positions.
    • Added safeguards for analytics pagination and timestamp-based historical lookups.
  • Documentation

    • Updated technical guidance and validation checks for position analytics.

@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
monarch Ready Ready Preview, Comment Jul 20, 2026 6:15am

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Completed UTC periods now use daily position and market analytics with boundary snapshots, while rolling and custom periods retain bounded event handling. The change adds range utilities, paginated analytics retrieval, daily earnings calculation, historical APY boundaries, and updated chart/table wiring.

Changes

Completed analytics flow

Layer / File(s) Summary
Analytics ranges and paginated data
src/utils/earnings-period.ts, src/utils/blockEstimation.ts, src/graphql/envio-queries.ts, src/data-sources/monarch-api/*, src/hooks/queries/*, docs/*
Adds UTC range helpers, target-time block estimation, independent flow/market pagination, typed daily analytics, and validation/documentation updates.
Daily earnings computation and summary state
src/utils/interest.ts, src/hooks/usePositionsWithEarnings.ts, src/hooks/useUserPositionsSummaryData.ts
Calculates weighted earnings from daily analytics and integrates ranges, snapshots, transactions, loading, refetching, and per-chain results.
Range-aware APY and vault wiring
src/hooks/useVaultHistoricalApy.ts
Uses start and optional historical end snapshots for range-based APY calculations.
Range-aware charts and position details
src/features/position-detail/*, src/features/positions/*, src/features/vault/*
Threads end timestamps, end snapshots, earnings ranges, and completed-day bucketing through position and vault views.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PositionView
  participant useUserPositionsSummaryData
  participant usePositionDailyAnalyticsQuery
  participant fetchCompletedPositionDailyAnalytics
  participant usePositionsWithEarnings
  participant calculateEarningsFromDailyAnalytics
  PositionView->>useUserPositionsSummaryData: select completed UTC period
  useUserPositionsSummaryData->>usePositionDailyAnalyticsQuery: query positions and range
  usePositionDailyAnalyticsQuery->>fetchCompletedPositionDailyAnalytics: fetch chain analytics
  fetchCompletedPositionDailyAnalytics-->>usePositionDailyAnalyticsQuery: flows and market snapshots
  usePositionDailyAnalyticsQuery-->>useUserPositionsSummaryData: daily analytics by chain
  useUserPositionsSummaryData->>usePositionsWithEarnings: provide analytics and boundary snapshots
  usePositionsWithEarnings->>calculateEarningsFromDailyAnalytics: calculate weighted earnings
  calculateEarningsFromDailyAnalytics-->>usePositionsWithEarnings: earnings calculation
Loading

Possibly related PRs

Suggested labels: feature request, ui

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: completed UTC day buckets now drive position analytics for completed periods.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/completed-utc-position-periods

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@antoncoding
antoncoding force-pushed the codex/completed-utc-position-periods branch from 4bbedee to 32855b5 Compare July 14, 2026 16:47

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a daily analytics calculation mechanism for user positions, fetching daily flows and market snapshots via a new Envio GraphQL query to calculate earnings and APY without relying on heavy transaction history. The UI and hooks have been updated to support completed UTC days analytics periods. The review feedback highlights a potential bug in the daily analytics calculation where unaligned timestamps could break bucket lookups, and identifies type-safety issues where numeric chain IDs are implicitly converted to string keys via Object.fromEntries.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/utils/position-daily-analytics.ts Outdated
Comment on lines +94 to +120
for (let bucketStart = startTimestamp; bucketStart < endTimestamp; bucketStart += SECONDS_PER_DAY) {
const bucketEnd = bucketStart + SECONDS_PER_DAY;
const flow = flowByBucket.get(bucketStart);
const exposure = getCompletedFlowExposure(flow, currentShares, bucketEnd);
const endMarketSnapshot = marketSnapshotByBucket.get(bucketStart) ?? latestMarketSnapshot;
const startMarketSnapshot = latestMarketSnapshot ?? endMarketSnapshot;

if (exposure.weightedSharesSeconds > 0n) {
if (startMarketSnapshot && endMarketSnapshot) {
const startWeightedAssets = toWeightedAssetsSeconds(exposure.weightedSharesSeconds, startMarketSnapshot);
const endWeightedAssets = toWeightedAssetsSeconds(exposure.weightedSharesSeconds, endMarketSnapshot);
weightedSuppliedAssets += (startWeightedAssets + endWeightedAssets) / 2n;
} else {
weightedSuppliedAssets += getFallbackWeightedAssetsSeconds({
weightedSharesSeconds: exposure.weightedSharesSeconds,
startingBalance,
startingShares,
endingBalance,
endingShares,
});
}
}

effectiveTime += exposure.activeSeconds;
currentShares = exposure.closingShares;
latestMarketSnapshot = endMarketSnapshot;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The daily analytics calculation loops through days using bucketStart += SECONDS_PER_DAY starting from startTimestamp. If startTimestamp or endTimestamp are not perfectly aligned to UTC midnight (e.g., due to block timestamp estimation or clock drift), bucketStart will not match the keys in flowByBucket and marketSnapshotByBucket (which are strictly aligned to UTC midnight multiples of 86400). This will cause the lookups to return undefined and break the calculations.

Aligning startTimestamp and endTimestamp to UTC midnight boundaries inside the function makes the daily bucket lookups robust against unaligned inputs.

  const alignedStart = Math.floor(startTimestamp / SECONDS_PER_DAY) * SECONDS_PER_DAY;
  const alignedEnd = Math.floor(endTimestamp / SECONDS_PER_DAY) * SECONDS_PER_DAY;

  for (let bucketStart = alignedStart; bucketStart < alignedEnd; bucketStart += SECONDS_PER_DAY) {
    const bucketEnd = bucketStart + SECONDS_PER_DAY;
    const flow = flowByBucket.get(bucketStart);
    const exposure = getCompletedFlowExposure(flow, currentShares, bucketEnd);
    const endMarketSnapshot = marketSnapshotByBucket.get(bucketStart) ?? latestMarketSnapshot;
    const startMarketSnapshot = latestMarketSnapshot ?? endMarketSnapshot;

    if (exposure.weightedSharesSeconds > 0n) {
      if (startMarketSnapshot && endMarketSnapshot) {
        const startWeightedAssets = toWeightedAssetsSeconds(exposure.weightedSharesSeconds, startMarketSnapshot);
        const endWeightedAssets = toWeightedAssetsSeconds(exposure.weightedSharesSeconds, endMarketSnapshot);
        weightedSuppliedAssets += (startWeightedAssets + endWeightedAssets) / 2n;
      } else {
        weightedSuppliedAssets += getFallbackWeightedAssetsSeconds({
          weightedSharesSeconds: exposure.weightedSharesSeconds,
          startingBalance,
          startingShares,
          endingBalance,
          endingShares,
        });
      }
    }

    effectiveTime += exposure.activeSeconds;
    currentShares = exposure.closingShares;
    latestMarketSnapshot = endMarketSnapshot;
  }

Comment on lines +393 to +396
const endTimestampsByChain = useMemo(
() => Object.fromEntries(Object.entries(earningsRangesByChain).map(([chainId, range]) => [chainId, range.endTimestamp])),
[earningsRangesByChain],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using Object.fromEntries on Object.entries converts the numeric chain IDs into string keys in the resulting object. This can lead to implicit type conversions or TypeScript type-checking issues when passing endTimestampsByChain to functions expecting Record<number, number>. Constructing the record with explicit numeric keys is safer and cleaner.

  const endTimestampsByChain = useMemo(() => {
    const result: Record<number, number> = {};
    for (const [chainId, range] of Object.entries(earningsRangesByChain)) {
      result[Number(chainId)] = range.endTimestamp;
    }
    return result;
  }, [earningsRangesByChain]);

Comment thread src/features/vault/vault-view.tsx Outdated
Comment on lines 141 to 144
const endTimestampsByChain = useMemo(
() => Object.fromEntries(Object.entries(earningsRangesByChain).map(([rangeChainId, range]) => [rangeChainId, range.endTimestamp])),
[earningsRangesByChain],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using Object.fromEntries on Object.entries converts the numeric chain IDs into string keys in the resulting object. This can lead to implicit type conversions or TypeScript type-checking issues when passing endTimestampsByChain to functions expecting Record<number, number>. Constructing the record with explicit numeric keys is safer and cleaner.

  const endTimestampsByChain = useMemo(() => {
    const result: Record<number, number> = {};
    for (const [chainId, range] of Object.entries(earningsRangesByChain)) {
      result[Number(chainId)] = range.endTimestamp;
    }
    return result;
  }, [earningsRangesByChain]);

@antoncoding
antoncoding force-pushed the codex/completed-utc-position-periods branch from 1241b0d to cc3d755 Compare July 20, 2026 06:05
@coderabbitai coderabbitai Bot added feature request Specific feature ready to be implemented ui User interface labels Jul 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/hooks/queries/usePositionDailyAnalyticsQuery.ts (1)

35-48: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

One chain's analytics failure fails the whole query, discarding data for healthy chains.

Promise.all here means a single chain's fetchCompletedPositionDailyAnalytics rejection fails the entire query. Downstream, dailyAnalyticsQuery.isError then causes useUserPositionsSummaryData to fall back to raw-transaction earnings for every chain (and drop dailyAnalyticsByChain entirely), even if only one chain's fetch failed. Promise.allSettled would let healthy chains keep using completed-day analytics while only the failing chain falls back.

♻️ Proposed fix
-      const entries = await Promise.all(
-        marketsByChain.map(async ([chainId, marketIds]) => {
-          const analytics = await fetchCompletedPositionDailyAnalytics({
-            userAddress,
-            chainId,
-            marketIds,
-            startTimestamp: range.startTimestamp,
-            endTimestamp: range.endTimestamp,
-          });
-          return [chainId, analytics] as const;
-        }),
-      );
-
-      return Object.fromEntries(entries);
+      const results = await Promise.allSettled(
+        marketsByChain.map(async ([chainId, marketIds]) => {
+          const analytics = await fetchCompletedPositionDailyAnalytics({
+            userAddress,
+            chainId,
+            marketIds,
+            startTimestamp: range.startTimestamp,
+            endTimestamp: range.endTimestamp,
+          });
+          return [chainId, analytics] as const;
+        }),
+      );
+
+      return Object.fromEntries(
+        results.filter((r): r is PromiseFulfilledResult<readonly [number, PositionDailyAnalytics]> => r.status === 'fulfilled').map((r) => r.value),
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/queries/usePositionDailyAnalyticsQuery.ts` around lines 35 - 48,
Update the analytics aggregation in the query to use Promise.allSettled instead
of Promise.all, preserving successful chain results while handling rejected
fetchCompletedPositionDailyAnalytics calls as missing analytics for only those
chains. Ensure the returned Object.fromEntries input includes successful entries
without causing the entire query to error when one chain fails, so downstream
fallback remains chain-specific.
src/utils/earnings-period.ts (1)

41-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use hourCycle: 'h23' instead of hour12: false + string patch.

hour12: false doesn't guarantee midnight renders as 00:00; depending on locale/runtime it can use the h24 cycle and print 24:00 (confirmed via search: "Using hour12: false should give you 24-hour format... it might default to the h24 cycle (midnight = 24 00)"). The .replace('24:00', '00:00') patch is a fragile workaround for this — hourCycle: 'h23' forces 0–23 formatting directly and removes the need for the string hack.

♻️ Proposed fix
 const formatUtcTimestamp = (timestamp: number): string =>
   new Intl.DateTimeFormat('en-US', {
     timeZone: 'UTC',
     month: 'short',
     day: 'numeric',
     year: 'numeric',
     hour: '2-digit',
     minute: '2-digit',
-    hour12: false,
+    hourCycle: 'h23',
   })
-    .format(new Date(timestamp * 1000))
-    .replace('24:00', '00:00');
+    .format(new Date(timestamp * 1000));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/earnings-period.ts` around lines 41 - 52, Update formatUtcTimestamp
to use Intl.DateTimeFormat option hourCycle: 'h23' instead of hour12: false, and
remove the chained .replace('24:00', '00:00') workaround so midnight is
formatted directly as 00:00.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/queries/useBlockTimestamps.ts`:
- Around line 19-32: Remove latestBlocks from the queryKey in the
useBlockTimestamps query while retaining its use inside queryFn for latest-block
validation and findBlockAtTimestamp. Keep snapshotBlocks and targetTimestamp as
the cache key inputs.

In `@src/hooks/useVaultHistoricalApy.ts`:
- Around line 112-121: Update the historical APY processing near the
block/timestamp assignments to return early when requiresHistoricalEnd is true
and historicalEndBlockData is missing. Preserve the existing blockData guard and
fallback end timestamp behavior for cases that do not require a historical end
block.

---

Nitpick comments:
In `@src/hooks/queries/usePositionDailyAnalyticsQuery.ts`:
- Around line 35-48: Update the analytics aggregation in the query to use
Promise.allSettled instead of Promise.all, preserving successful chain results
while handling rejected fetchCompletedPositionDailyAnalytics calls as missing
analytics for only those chains. Ensure the returned Object.fromEntries input
includes successful entries without causing the entire query to error when one
chain fails, so downstream fallback remains chain-specific.

In `@src/utils/earnings-period.ts`:
- Around line 41-52: Update formatUtcTimestamp to use Intl.DateTimeFormat option
hourCycle: 'h23' instead of hour12: false, and remove the chained
.replace('24:00', '00:00') workaround so midnight is formatted directly as
00:00.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 441b5223-a7d5-40a9-9805-bbca366a3d3e

📥 Commits

Reviewing files that changed from the base of the PR and between b173698 and cc3d755.

📒 Files selected for processing (23)
  • docs/TECHNICAL_OVERVIEW.md
  • docs/VALIDATIONS.md
  • src/data-sources/monarch-api/index.ts
  • src/data-sources/monarch-api/position-daily-flows.ts
  • src/features/position-detail/components/history-tab.tsx
  • src/features/position-detail/position-view.tsx
  • src/features/positions/components/adapter-managed-exposure.tsx
  • src/features/positions/components/positions-period-settings.tsx
  • src/features/positions/components/supplied-markets-detail.tsx
  • src/features/positions/components/supplied-morpho-blue-grouped-table.tsx
  • src/features/positions/components/user-positions-chart.tsx
  • src/features/positions/positions-view.tsx
  • src/features/vault/components/vault-adapter-position-overview.tsx
  • src/features/vault/vault-view.tsx
  • src/graphql/envio-queries.ts
  • src/hooks/queries/useBlockTimestamps.ts
  • src/hooks/queries/usePositionDailyAnalyticsQuery.ts
  • src/hooks/usePositionsWithEarnings.ts
  • src/hooks/useUserPositionsSummaryData.ts
  • src/hooks/useVaultHistoricalApy.ts
  • src/utils/blockEstimation.ts
  • src/utils/earnings-period.ts
  • src/utils/interest.ts

Comment on lines +19 to +32
queryKey: ['block-timestamps', snapshotBlocks, targetTimestamp, latestBlocks],
queryFn: async () => {
const blockData: Record<number, { block: number; timestamp: number }> = {};

await Promise.all(
Object.entries(snapshotBlocks).map(async ([chainId, blockNum]) => {
try {
const client = getClient(Number(chainId) as SupportedNetworks, customRpcUrls[Number(chainId) as SupportedNetworks]);
const block = await client.getBlock({ blockNumber: BigInt(blockNum) });
const chainIdNumber = Number(chainId) as SupportedNetworks;
const latestBlock = latestBlocks?.[chainIdNumber];
if (latestBlock === undefined) {
throw new Error(`Missing latest block for chain ${chainId}`);
}
const blockDataAtTimestamp = await findBlockAtTimestamp(client, chainIdNumber, blockNum, targetTimestamp, latestBlock);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how frequently useCurrentBlocks refreshes latestBlocks
rg -n -A 30 'useCurrentBlocks' src/hooks --type=ts

Repository: antoncoding/monarch

Length of output: 11267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== useBlockTimestamps ==\n'
cat -n src/hooks/queries/useBlockTimestamps.ts

printf '\n== findBlockAtTimestamp / estimate helpers ==\n'
rg -n -A 80 -B 20 'function findBlockAtTimestamp|const findBlockAtTimestamp|estimateBlockAtTimestamp|currentBlocks|latestBlocks' src --type=ts

Repository: antoncoding/monarch

Length of output: 50375


Drop latestBlocks from the query key.

latestBlocks changes whenever useCurrentBlocks refreshes, so this query gets a new cache entry on each head update even when snapshotBlocks and targetTimestamp stay the same. Keep it in the queryFn, but don’t key the cache on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/queries/useBlockTimestamps.ts` around lines 19 - 32, Remove
latestBlocks from the queryKey in the useBlockTimestamps query while retaining
its use inside queryFn for latest-block validation and findBlockAtTimestamp.
Keep snapshotBlocks and targetTimestamp as the cache key inputs.

Comment on lines +112 to +121
const historicalEndBlockData = endBlockData?.[networkId];

if (!pastBlock || !blockData) {
if (!blockData) {
return;
}

const pastBlock = blockData.block;
const endBlock = historicalEndBlockData?.block;
const startTimestamp = blockData.timestamp;
const endTimestamp = historicalEndBlockData?.timestamp ?? fallbackEndTimestamp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files 'src/hooks/useVaultHistoricalApy.ts' 'src/**/useBlockTimestamps*' 'src/**/block*timestamp*' 'src/**/historical*' | sed 's#^`#-` #'

printf '\n== outline: src/hooks/useVaultHistoricalApy.ts ==\n'
ast-grep outline src/hooks/useVaultHistoricalApy.ts --view expanded || true

printf '\n== relevant excerpt: src/hooks/useVaultHistoricalApy.ts ==\n'
sed -n '1,220p' src/hooks/useVaultHistoricalApy.ts | cat -n

printf '\n== search for useBlockTimestamps and end-block gating ==\n'
rg -n "useBlockTimestamps|requiresHistoricalEnd|endBlockData|fallbackEndTimestamp|historicalEndBlockData|blockNumber" src -S

Repository: antoncoding/monarch

Length of output: 23168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== outline: src/hooks/queries/useBlockTimestamps.ts ==\n'
ast-grep outline src/hooks/queries/useBlockTimestamps.ts --view expanded || true

printf '\n== excerpt: src/hooks/queries/useBlockTimestamps.ts ==\n'
sed -n '1,220p' src/hooks/queries/useBlockTimestamps.ts | cat -n

printf '\n== excerpt: src/hooks/useUserPositionsSummaryData.ts (end block handling) ==\n'
sed -n '120,290p' src/hooks/useUserPositionsSummaryData.ts | cat -n

printf '\n== search for fallbackEndTimestamp usage ==\n'
rg -n "fallbackEndTimestamp|endBlockData\\?\\.\\[|!!endBlockData|endSnapshotBlocks" src/hooks src/utils -S

Repository: antoncoding/monarch

Length of output: 11498


Skip the network when the end block is missing.
endBlockData can exist while this chain is absent, so endBlock becomes undefined and the query falls back to the latest block plus Date.now(). For completed periods, that makes the chain end at the live balance instead of the day boundary. Return early when requiresHistoricalEnd && !historicalEndBlockData.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useVaultHistoricalApy.ts` around lines 112 - 121, Update the
historical APY processing near the block/timestamp assignments to return early
when requiresHistoricalEnd is true and historicalEndBlockData is missing.
Preserve the existing blockData guard and fallback end timestamp behavior for
cases that do not require a historical end block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request Specific feature ready to be implemented ui User interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant