Skip to content

fix: temporarily show unbonding locks the subgraph has not indexed#748

Merged
rickstaa merged 2 commits into
mainfrom
fix/withdraw-without-subgraph
Jul 21, 2026
Merged

fix: temporarily show unbonding locks the subgraph has not indexed#748
rickstaa merged 2 commits into
mainfrom
fix/withdraw-without-subgraph

Conversation

@rickstaa

@rickstaa rickstaa commented Jul 21, 2026

Copy link
Copy Markdown
Member

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 from BondingManager and StakeTransactions appends it to the subgraph's list. The round comes from /api/current-round too, 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 null and no request is made. The scan is capped at 200 ids.

Not covered: a subgraph serving no entities. DelegatingView:123 returns early on a null delegator, so this never mounts.

Verified

Against a live account (nextUnbondingLockId: 61):

from=61  →  0 ids  → []                  ← caught up, nothing fetched
from=56  →  5 ids  → [56,57,58,59,60]
from=0   → 61 ids  → [56,57,58,59,60]    ← capped at 200 on large accounts

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. uint256number — the crash half is gone with the cap; the precision half needs ~9×10¹⁵ unbonds. subgraph-health and WithdrawableLocks comments refer to changes since dropped. "days" vs rounds is pre-existing on main, 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) + 1 converges on nextUnbondingLockId and nothing is fetched.

🤖 Generated with Claude Code

@rickstaa
rickstaa requested a review from ECWireless as a code owner July 21, 2026 14:55
Copilot AI review requested due to automatic review settings July 21, 2026 14:55
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
explorer-arbitrum-one Ready Ready Preview, Comment Jul 21, 2026 7:27pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rickstaa, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ae043f23-09de-4603-9e17-5f51b3d45bbd

📥 Commits

Reviewing files that changed from the base of the PR and between 798adc0 and 7b46cf0.

📒 Files selected for processing (1)
  • pages/api/unbonding-locks/[address].tsx
📝 Walkthrough

Walkthrough

Adds a bounded BondingManager-backed unbonding-lock API, a typed SWR hook, and degraded-subgraph handling in StakeTransactions. Missing locks are fetched and merged, while pending and available states use the on-chain round ID.

Changes

Unbonding lock fallback flow

Layer / File(s) Summary
Unbonding lock API and data contract
lib/api/types/get-unbonding-locks.ts, pages/api/unbonding-locks/[address].tsx
Defines the unbonding-lock payload and implements a GET route that validates addresses, scans bounded lock IDs, formats non-zero locks, and maps errors to API responses.
SWR access to unbonding locks
hooks/useSwr.tsx
Adds useUnbondingLocksData, which conditionally fetches unbonding locks for an address and starting lock ID.
Degraded-subgraph stake classification
components/StakeTransactions/index.tsx
Detects degraded subgraph data, merges fetched locks, and uses the on-chain round ID for pending, completed, and countdown calculations.

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
Loading

Possibly related PRs

Suggested reviewers: ecwireless

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly states the main user-facing change: showing unbonding locks the subgraph has not indexed.
Description check ✅ Passed The description is detailed and covers problem, behavior, verification, and revert guidance, though it does not follow the template sections exactly.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/withdraw-without-subgraph

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f2a4a1 and a5a5b52.

📒 Files selected for processing (6)
  • components/DelegatingView/index.tsx
  • components/WithdrawableLocks/index.tsx
  • hooks/useSwr.tsx
  • lib/api/types/get-unbonding-locks.ts
  • pages/api/subgraph-health.tsx
  • pages/api/unbonding-locks/[address].tsx

Comment thread pages/api/unbonding-locks/[address].tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 via getDelegator + multicall getDelegatorUnbondingLock.
  • Add WithdrawableLocks UI to render withdrawable locks based on chain data, and surface it in DelegatingView when the subgraph is degraded for the connected account.
  • Update /api/subgraph-health to 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.

Comment thread pages/api/subgraph-health.tsx Outdated
Comment thread components/WithdrawableLocks/index.tsx Outdated
Comment thread pages/api/unbonding-locks/[address].tsx Outdated
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>
@rickstaa
rickstaa force-pushed the fix/withdraw-without-subgraph branch from d13f4ca to 798adc0 Compare July 21, 2026 18:47
@rickstaa rickstaa changed the title fix: keep withdrawals reachable when the subgraph is unavailable fix: temporarily show unbonding locks the subgraph has not indexed Jul 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between a5a5b52 and 798adc0.

📒 Files selected for processing (4)
  • components/StakeTransactions/index.tsx
  • hooks/useSwr.tsx
  • lib/api/types/get-unbonding-locks.ts
  • pages/api/unbonding-locks/[address].tsx

Comment thread hooks/useSwr.tsx
Comment thread pages/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>
@rickstaa
rickstaa merged commit 081de4b into main Jul 21, 2026
8 of 9 checks passed
@rickstaa
rickstaa deleted the fix/withdraw-without-subgraph branch July 21, 2026 19:33
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