feat: add delegators view#719
Conversation
Adds a Delegators tab to the account layout listing an orchestrator's delegators, backed by a new orchestratorDelegators subgraph query.
|
@moudi-network is attempting to deploy a commit to the Livepeer Foundation Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds a delegator list to the orchestrator account flow and a new logs query surface. The change includes generated Apollo types and hooks, a paginated delegator table, account layout wiring, and a dedicated delegators page. ChangesIndexing logs query
Orchestrator delegators list
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AccountLayout
participant DelegatorsView
participant useOrchestratorDelegatorsQuery
participant Subgraph
participant DelegatorList
AccountLayout->>DelegatorsView: render delegators view
DelegatorsView->>useOrchestratorDelegatorsQuery: query transcoder id + pagination
useOrchestratorDelegatorsQuery->>Subgraph: orchestratorDelegators(...)
Subgraph-->>useOrchestratorDelegatorsQuery: transcoder.delegators
useOrchestratorDelegatorsQuery-->>DelegatorsView: data and fetchMore results
DelegatorsView->>DelegatorList: delegators array
DelegatorList-->>AccountLayout: table or empty state
Related issue: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@components/DelegatorsView/index.tsx`:
- Around line 15-23: The delegators list is being silently truncated by the
hard-coded `first: 1000` in `useOrchestratorDelegatorsQuery`, while the table
only paginates the fetched data client-side. Update `DelegatorsView` to drive
pagination from table state and fetch additional rows from the server using
`first`/`skip` (or cursors) instead of a fixed limit. Use the existing
`useOrchestratorDelegatorsQuery` setup and the table’s pagination controls to
wire the requested page size and offset through the query variables.
- Around line 15-27: The `DelegatorsView` render path is treating both in-flight
and failed `useOrchestratorDelegatorsQuery` results as an empty list, so
`DelegatorList` shows a false “No delegators found” state. Update
`DelegatorsView` to read `loading` and `error` from
`useOrchestratorDelegatorsQuery`, render a loading or error state before
`DelegatorList`, and only pass through to the empty-state logic after a
successful query with zero delegators.
In `@pages/accounts/`[account]/delegators.tsx:
- Around line 59-88: The delegators page currently allows any valid account
through `getStaticProps`, but this route should only serve orchestrator
accounts. In `getStaticProps`, after `getAccount(client, accountId)` and before
fetching orchestrators, check `account.data.transcoder`; if it is missing,
return `notFound: true` or redirect to `/delegating` instead of building
`PageProps`. Keep the existing `getAccount`, `getSortedOrchestrators`, and
`PageProps` flow only for accounts that have a transcoder.
🪄 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: 70cf5959-3344-4ea1-be5d-effb16836fa5
📒 Files selected for processing (7)
apollo/subgraph.tscomponents/DelegatorList/index.tsxcomponents/DelegatorsView/index.tsxcomponents/Table/index.tsxlayouts/account.tsxpages/accounts/[account]/delegators.tsxqueries/orchestratorDelegators.graphql
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 `@components/DelegatorsView/index.tsx`:
- Around line 38-69: The `fetchNext` pagination path in `DelegatorsView` only
logs `fetchMore` failures, which can leave the list silently incomplete after a
later page load fails. Update `fetchNext` so it sets a visible failure state
(for example `pageFetchFailed`) when `fetchMore` throws, and keep the state
reset in `finally` so retries remain possible. Then use that state in
`DelegatorsView` to render a small banner/notice near the table, and optionally
provide a retry action, so users know paging stopped early.
- Around line 93-94: The DelegatorsView render path is performing a side effect
by calling console.error(error) directly inside the component body when error &&
!delegators is true. Move this logging into a useEffect in DelegatorsView keyed
on error (and any other needed state) so the log only runs after render and
avoids duplicate or discarded render side effects.
🪄 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: f80ae7c1-d6b1-4b3f-9281-a72a2621c274
📒 Files selected for processing (2)
components/DelegatorsView/index.tsxpages/accounts/[account]/delegators.tsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
components/DelegatorsView/index.tsx (2)
21-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a brief description for this non-trivial component.
DelegatorsViewimplements query wiring, cursor-less pagination, retry/failure handling, and multiple render states, but has no descriptive comment explaining its behavior/contract for future maintainers.As per path instructions, "at minimum, include a general description for new functions/components that are not self-explanatory."
🤖 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 `@components/DelegatorsView/index.tsx` around lines 21 - 33, DelegatorsView is a non-trivial component and needs a brief descriptive comment explaining its purpose and contract for maintainers. Add a short component-level description near the DelegatorsView definition that summarizes its query wiring via useOrchestratorDelegatorsQuery, pagination/fetchMore behavior, and the loading/error/empty/content render states so the intent is clear without reading the full implementation.Source: Path instructions
42-85: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winStop auto-paging once the last page is shorter than
PAGE_SIZE.
delegators.length % PAGE_SIZE === 0treats an exact multiple as “more data,” so the final empty page can keep re-triggeringfetchNextwith the sameskipand spam the subgraph. Track ahasMoreflag from the last response instead of inferring it from the current length.🤖 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 `@components/DelegatorsView/index.tsx` around lines 42 - 85, The auto-paging logic in fetchNext/useEffect is inferring pagination from delegators.length % PAGE_SIZE, which can keep requesting the same skip value after the last full page. Update the DelegatorsView paging flow to track an explicit hasMore flag from the latest fetchMore response and use that in the useEffect guard instead of relying on the current list length; make sure fetchNext clears hasMore when fetchMoreResult.transcoder.delegators is empty or shorter than PAGE_SIZE.
🤖 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.
Outside diff comments:
In `@components/DelegatorsView/index.tsx`:
- Around line 21-33: DelegatorsView is a non-trivial component and needs a brief
descriptive comment explaining its purpose and contract for maintainers. Add a
short component-level description near the DelegatorsView definition that
summarizes its query wiring via useOrchestratorDelegatorsQuery,
pagination/fetchMore behavior, and the loading/error/empty/content render states
so the intent is clear without reading the full implementation.
- Around line 42-85: The auto-paging logic in fetchNext/useEffect is inferring
pagination from delegators.length % PAGE_SIZE, which can keep requesting the
same skip value after the last full page. Update the DelegatorsView paging flow
to track an explicit hasMore flag from the latest fetchMore response and use
that in the useEffect guard instead of relying on the current list length; make
sure fetchNext clears hasMore when fetchMoreResult.transcoder.delegators is
empty or shorter than PAGE_SIZE.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b22ee49f-1b03-4b4a-86f3-a6bf8a034fd7
📒 Files selected for processing (1)
components/DelegatorsView/index.tsx
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@moudi-network thanks for the contribution. @mehrdadmms did an initial review, and everything looks good so far. The implementation works, and the code quality is high enough for the bounty to be paid out. I may request a few minor code and UI changes after Adam has reviewed it. I will try to merge this in in the comming weeks when I have more bandwith. |
Description
Adds a Delegators tab to the orchestrator account page, listing every account that has delegated stake to the orchestrator. Each row shows the delegator's Stake (subgraph
bondedAmount) and Pending Rewards (live on-chainpendingStake − bondedAmount), so a row's total matches what each delegator sees on their own profile.Built entirely on existing infrastructure: the subgraph
Transcoder.delegatorsfield (no subgraph/indexer change) and the existing/api/pending-stakeendpoint (no new server code). Pending Rewards is fetched per visible row via the existingusePendingFeesAndStakeDatahook — the same per-cell pattern already used for ENS data. Only the current page (~10 rows) are fetched.Type of Change
Related Issue(s)
Related: #711
Closes: #106
Changes Made
Delegators List
isOrchestrator || isMyDelegate), positioned after Delegatingqueries/orchestratorDelegators.graphqlquerying the existingTranscoder.delegatorsfield (regenerated Apollo types viapnpm codegen)components/DelegatorList(react-table: columns, customnumericSort, client pagination, empty state) andcomponents/DelegatorsViewwrapperpages/accounts/[account]/delegators.tsx(mirrorsorchestrating.tsx)bondedAmount; Pending Rewards column computed live per row asmax(0, pendingStake/1e18 − bondedAmount)via/api/pending-stakeTesting
How to test (optional unless test is not trivial)
Delegators List
0x1416…orchestrator → delegator0xa69e…3db3: 131.43K + 28.72K = 160.15K (matches profile).pnpm codegen,pnpm typecheck,pnpm exec eslint … --max-warnings 0,prettierall clean.Impact / Risk
Risk level: Low
Impacted areas: UI (new tab/route + right-rail copy), read-only API reuse (
/api/pending-stake). No DB, infra, config, or new server code.User impact: Orchestrator account pages gain a new Delegators tab. No change for plain delegator accounts. Purely additive. Nothing existing is altered.
Rollback plan: Plain PR revert. The change is additive (new route + tab branch + new components), so reverting removes the tab with no migration or config cleanup.
Screenshots / Recordings (if applicable)
Additional Notes
/api/pending-stake) to keep the PR minimal. No new API route/hook/type. Trade-off: the column is not globally sortable (the value only exists for on-screen rows). A batched-endpoint alternative (sortable column, 1 request) can be implemented if wanted later.Delegator.feesis lifetime-cumulative across all orchestrators and event-lagged, so it isn't attributable to this orchestrator.first: 1000delegators sorted by stake, paginated client-side (10/page); fine for current orchestrator sizes.Summary by CodeRabbit