fix: temporarily show unbonding locks the subgraph has not indexed#748
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a bounded BondingManager-backed unbonding-lock API, a typed SWR hook, and degraded-subgraph handling in ChangesUnbonding lock fallback flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant StakeTransactions
participant useUnbondingLocksData
participant UnbondingLocksAPI
participant BondingManager
StakeTransactions->>useUnbondingLocksData: Request missing locks when subgraph is degraded
useUnbondingLocksData->>UnbondingLocksAPI: Fetch address and starting lock ID
UnbondingLocksAPI->>BondingManager: Read delegator and lock details
BondingManager-->>UnbondingLocksAPI: Return on-chain lock data
UnbondingLocksAPI-->>useUnbondingLocksData: Return formatted locks
useUnbondingLocksData-->>StakeTransactions: Merge locks and classify by round ID
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@pages/api/unbonding-locks/`[address].tsx:
- Around line 39-62: Cap the lock ID range used by the unbonding-locks handler
to a maximum of 1000 entries before constructing lockIds and calling
l2PublicClient.multicall. When nextUnbondingLockId exceeds the cap, select only
the most recent IDs while preserving the existing result mapping and lock
identifiers for the selected range.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5e104a63-fbc5-4145-9112-d165fa50f371
📒 Files selected for processing (6)
components/DelegatingView/index.tsxcomponents/WithdrawableLocks/index.tsxhooks/useSwr.tsxlib/api/types/get-unbonding-locks.tspages/api/subgraph-health.tsxpages/api/unbonding-locks/[address].tsx
There was a problem hiding this comment.
Pull request overview
This PR adds an on-chain fallback path so users can still discover and withdraw mature unbonding locks even when the subgraph is unavailable, and tightens the “subgraph degraded” signal so outages trigger the fallback.
Changes:
- Add
/api/unbonding-locks/[address]to scan delegator unbonding locks viagetDelegator+ multicallgetDelegatorUnbondingLock. - Add
WithdrawableLocksUI to render withdrawable locks based on chain data, and surface it inDelegatingViewwhen the subgraph is degraded for the connected account. - Update
/api/subgraph-healthto treat an unprobeable subgraph as degraded (instead of failing open).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pages/api/unbonding-locks/[address].tsx | New API endpoint to enumerate open unbonding locks from the chain via multicall. |
| pages/api/subgraph-health.tsx | Changes degraded detection to fail closed when the subgraph probe fails. |
| lib/api/types/get-unbonding-locks.ts | Adds API response types for the new unbonding-locks endpoint. |
| hooks/useSwr.tsx | Adds useUnbondingLocksData SWR hook for the new endpoint. |
| components/WithdrawableLocks/index.tsx | New UI component to display unbonding locks and expose withdrawals without subgraph data. |
| components/DelegatingView/index.tsx | Uses useSubgraphDegraded() to render WithdrawableLocks instead of the empty state during subgraph degradation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Stopgap while the subgraph resyncs from scratch (livepeer/subgraph#247). The withdrawal list comes from the subgraph, so locks created after indexing stalled are missing from it: an unbond appears to do nothing, and a user with no acknowledgement may reasonably unbond again, moving more stake out for a second full unbonding period. Once such a lock matures it is also withdrawable on-chain while remaining invisible in the UI. Lock ids are sequential and never reused, so anything the subgraph has not seen sits above its highest known id. Add /api/unbonding-locks/[address], which reads that tail from the contract, and append it to the subgraph's list rather than replacing it. The round comes from /api/current-round with it, since a stale round files a matured lock as pending and hides its withdraw button. Only fetched when the subgraph reports degraded and the user is viewing their own account, so a healthy subgraph behaves exactly as before, with no extra request. The scan is bounded to 200 ids so a direct call cannot ask for an unbounded read. This is a bandaid, not a feature: it duplicates state the subgraph owns and should be reverted once indexing has recovered. It also disables itself as that happens, since the ids it fetches are those above what the subgraph reports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: CodeRabbit <coderabbitai[bot]@users.noreply.github.com>
d13f4ca to
798adc0
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@hooks/useSwr.tsx`:
- Around line 185-195: Add JSDoc immediately above useUnbondingLocksData
describing that it fetches unbonding-lock data for the address, uses from to
skip existing subgraph IDs, and returns null when no address is provided or data
is unavailable. Keep the existing hook behavior unchanged.
In `@pages/api/unbonding-locks/`[address].tsx:
- Around line 54-59: Update the from-parameter validation in the handler around
requested and start to accept only finite integer values, rejecting non-integer
input through the existing 400 validation path before it reaches BigInt in the
scan loop. Preserve the current fallback and MAX_SCAN lower-bound behavior for
valid requests.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4b396ae2-d965-4255-9a92-c563f47d4aa1
📒 Files selected for processing (4)
components/StakeTransactions/index.tsxhooks/useSwr.tsxlib/api/types/get-unbonding-locks.tspages/api/unbonding-locks/[address].tsx
Number.isFinite accepts 5.5, which reached BigInt() and threw a RangeError, turning a malformed query into a 500. The route already answers 400 for a malformed address, so treat a non-integer `from` as absent instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: CodeRabbit <coderabbitai[bot]@users.noreply.github.com>
Warning
Bandaid — revert once the subgraph has reindexed.
Only exists because the subgraph is resyncing from scratch
(livepeer/subgraph#247). It duplicates state the subgraph owns.
Problem
The withdrawal list reads
delegator.unbondingLocks, so a lock created after indexing stalled is absent: the unbond looks like it never happened, and once it matures the stake is withdrawable on-chain but invisible in the UI.Change
Lock ids are sequential and never reused — only
unbond()allocates one — so anything the subgraph has not seen sits above its highest known id.GET /api/unbonding-locks/[address]?from=<id>reads that tail fromBondingManagerandStakeTransactionsappends it to the subgraph's list. The round comes from/api/current-roundtoo, or a stale round hides a matured lock's withdraw button.Only fetched when the subgraph reports degraded and you are on your own account — otherwise the SWR key is
nulland no request is made. The scan is capped at 200 ids.Not covered: a subgraph serving no entities.
DelegatingView:123returns early on a nulldelegator, so this never mounts.Verified
Against a live account (
nextUnbondingLockId: 61):Rebonding three locks moved open 8 → 5 while the counter stayed at 61, confirming freed slots do not advance it.
Review comments
Unbounded allocation (CodeRabbit) — fixed by the cap.
uint256→number— the crash half is gone with the cap; the precision half needs ~9×10¹⁵ unbonds.subgraph-healthandWithdrawableLockscomments refer to changes since dropped. "days" vs rounds is pre-existing onmain, tracked in #751.Revert
Revert once the subgraph serves current locks. It also degrades to a no-op by itself first: as indexing catches up,
max(known id) + 1converges onnextUnbondingLockIdand nothing is fetched.🤖 Generated with Claude Code