Skip to content

fix: show delegated reward calls in account history - #757

Merged
rickstaa merged 1 commit into
mainfrom
fix/history-delegated-reward-calls
Aug 4, 2026
Merged

fix: show delegated reward calls in account history#757
rickstaa merged 1 commit into
mainfrom
fix/history-delegated-reward-calls

Conversation

@rickstaa

@rickstaa rickstaa commented Aug 3, 2026

Copy link
Copy Markdown
Member

Description

Since LIP-118 (livepeer/protocol#648, already live on Arbitrum) an orchestrator can call setRewardCaller to nominate an address that then calls rewardForTranscoder on its behalf.

Account history queries transactions(where: { from: $account }) — keyed on the transaction sender. A delegated reward is sent by the caller, so the whole transaction, and the RewardEvent inside it, vanishes from the orchestrator's history.

This adds a second query keyed on the orchestrator (rewardEvents(where: { delegate: $account })) and merges it into the timeline, mirroring how winningTicketRedeemedEvents is already handled.

Finding 2 of the LIP-118 audit. No subgraph changes needed — RewardEvent.delegate has always been the orchestrator.

Type of Change

  • fix: Bug fix

Related Issue(s)

Related: livepeer/subgraph#253

Changes Made

  • queries/transactions.graphql — new top-level rewardEvents(where: { delegate: $account })
  • components/HistoryView/index.tsx — merge the list in: window filter, mergedEvents,
    totalLoaded, updateQuery, empty state
  • Exclude RewardEvent from the transaction-path list — this is the dedupe. Self-called rewards
    arrive via both queries and there is no id-based dedupe in this component
  • reachedEnd now requires all lists exhausted, not just transactions — an orchestrator delegating
    every reward has few transactions but one reward per round, so paging stopped after one page
  • apollo/subgraph.ts — regenerated

Testing

  • Tested locally
  • Added/updated tests
  • All tests passing

pnpm lint, pnpm test (363), pnpm build, tsc --noEmit clean.

Verified against the first real delegated reward call on Arbitrum (0xc5377ebd…): it appears in the orchestrator's history on the preview and is absent on production. See the A/B links in the comment below.

Impact / Risk

Risk level: Low — UI only, account history view.

User impact: delegated reward calls appear in the orchestrator's history instead of vanishing.
Trade-off: they no longer appear in the reward caller's history, which is correct — the caller
receives nothing.

Rollback plan: PR revert. No schema, migration or config change.

Additional Notes

  • Delegated calls render with the existing copy. Labelling them "called by X" would mean comparing
    transaction.from, which is approximate (a multisig shows as the sender). The authoritative field
    is Transcoder.rewardCaller from feat: index RewardCallerSet reward caller delegation subgraph#253, not yet deployed — follow-up once it is.
  • Global TransactionsList needs no change; it already renders from event.delegate.id.
  • Conflicts with feat: add history events filter #720, which rewrites the same file. Whichever lands second resolves it.

Copilot AI review requested due to automatic review settings August 3, 2026 14:38
@rickstaa
rickstaa requested a review from ECWireless as a code owner August 3, 2026 14:38
@vercel

vercel Bot commented Aug 3, 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 Aug 4, 2026 10:22am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The transaction query now returns delegated reward events and supports contract-creation transactions. HistoryView merges reward events into history, preserves pagination across all event types, prevents duplicate rewards, and updates the empty state.

Changes

Reward history integration

Layer / File(s) Summary
Reward event query and types
apollo/subgraph.ts, queries/transactions.graphql
The query types allow nullable transaction recipients and expose account-specific rewardEvents with event, round, timestamp, and reward-token fields.
History merging and pagination
components/HistoryView/index.tsx
HistoryView merges reward events without duplicate self-called rewards, accumulates them across pages, checks all event collections for pagination completion, and includes rewards in empty-state checks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HistoryView
  participant TransactionsDocument
  participant Subgraph
  participant HistoryList
  HistoryView->>TransactionsDocument: request account history page
  TransactionsDocument->>Subgraph: query transactions, winning tickets, and rewardEvents
  Subgraph-->>TransactionsDocument: return account-matching event data
  TransactionsDocument-->>HistoryView: provide TransactionsQuery results
  HistoryView->>HistoryView: merge events and remove duplicate self-called rewards
  HistoryView->>HistoryList: render merged history
Loading

Possibly related PRs

  • livepeer/explorer#720: Both changes modify history event filtering and merging in components/HistoryView/index.tsx.

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 identifies the main fix: showing delegated reward calls in account history.
Description check ✅ Passed The description follows the template and clearly covers the problem, changes, testing, impact, risk, rollback, and follow-up notes.
✨ 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 fix/history-delegated-reward-calls

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 `@components/HistoryView/index.tsx`:
- Around line 178-180: Update the rewardEvents filter in HistoryView to use an
inclusive timestamp boundary so events with timestamps equal to
lastEventTimestamp are retained. Preserve the existing fallback behavior, and
add a regression test covering reward and transaction events sharing the
boundary timestamp.
🪄 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 Plus

Run ID: 764b3a83-99fe-44ce-a1c7-3c7df91deefe

📥 Commits

Reviewing files that changed from the base of the PR and between 72a24a1 and a2f15b9.

📒 Files selected for processing (3)
  • apollo/subgraph.ts
  • components/HistoryView/index.tsx
  • queries/transactions.graphql

Comment thread components/HistoryView/index.tsx

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

Fixes the account History view for orchestrators under LIP-118 by ensuring delegated reward() calls (submitted by a reward caller, not the orchestrator) still appear in the orchestrator’s timeline. This is done by querying RewardEvent entities directly by delegate and merging them into the history feed while avoiding duplicates.

Changes:

  • Add a delegate-keyed rewardEvents(where: { delegate: $account }) query alongside the existing sender-keyed transactions query.
  • Merge rewardEvents into HistoryView (and exclude tx-path RewardEvents) to ensure rewards render exactly once.
  • Update paging/end-of-list logic to require all parallel lists to be exhausted before stopping.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
queries/transactions.graphql Adds top-level rewardEvents query keyed by RewardEvent.delegate to capture delegated reward calls.
components/HistoryView/index.tsx Merges rewardEvents into the timeline and updates paging/end-of-list handling and empty state conditions.
apollo/subgraph.ts Regenerates GraphQL types/documents to include the new rewardEvents field and updated schema typing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread components/HistoryView/index.tsx Outdated
@rickstaa

rickstaa commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Reviewable in 30 seconds — real A/B on the same account

The first delegated reward call now exists on Arbitrum, so this can be verified live against the
production subgraph. Same account, same data, two builds:

Preview (this PR) https://explorer-arbitrum-one-git-fix-histor-41d683-livepeer-foundation.vercel.app/accounts/0x5BdEedCA9c6346B0CE6b17ffa8227A4DACE37039/history
Production (main) https://explorer.livepeer.org/accounts/0x5BdEedCA9c6346B0CE6b17ffa8227A4DACE37039/history

Look for Round #4,290 — 0xc537…ea40 — +358.37 LPT.

  • On the preview it is the top row.
  • On production it is absent entirely.

That transaction is 0xc5377ebd…2ea40: transcode.eth set 0x1B0c26FC…292F as its reward caller, and that caller submitted rewardForTranscoder. The Reward event's indexed transcoder is the orchestrator, but tx.from is the caller — so the sender-keyed query never sees it, and 358 LPT of the orchestrator's own earnings are missing from their history.

While you are on the preview, the dedupe is worth a glance too: the self-called rewards for the preceding rounds each appear exactly once. That is the regression risk in this PR, since rewards now arrive via a second query and there is no id-based dedupe in the component.

Account history is keyed on the transaction sender:

    transactions(where: { from: $account })

Since LIP-118 (livepeer/protocol#648) an orchestrator can nominate a
reward caller to submit reward() on its behalf. That transaction is sent
by the caller, so it drops out of the query and every event inside it -
including the RewardEvent - disappears from the orchestrator's history.

The event itself was always attributed correctly: RewardEvent.delegate is
the orchestrator regardless of who signed. The blind spot is purely in how
the Explorer asks for it. It cannot be fixed in place either, because
Transaction.events is typed as the Event interface, whose filter exposes
only id/timestamp/transaction/round - delegate lives on the concrete
RewardEvent type, so events_: { delegate: ... } does not exist.

So query the event entity directly, keyed on the orchestrator. This is the
same shape as winningTicketRedeemedEvents, already a separate role-keyed
query in this file for the same reason: a ticket's recipient is an event
param, so it survives being redeemed by a separate wallet.

Excluding RewardEvent from the transaction-path list is what keeps
self-called rewards, which appear in both queries, from rendering twice -
there is no id-based dedupe in this component.

Also require every list to be exhausted before paging stops. Keying that
on transactions alone cuts off exactly the accounts this fixes: an
orchestrator delegating every reward call has few transactions but one
reward event per round. This equally affects gateways with more tickets
than transactions, which 90ab4a1 fixed for totalLoaded but not reachedEnd.

Delegated calls render with the existing copy. Distinguishing them would
mean comparing transaction.from against the account, which is approximate
- a multisig or relayer shows as the sender. The authoritative answer is
Transcoder.rewardCaller, pending livepeer/subgraph#253.
Copilot AI review requested due to automatic review settings August 4, 2026 10:19
@rickstaa
rickstaa force-pushed the fix/history-delegated-reward-calls branch from a2f15b9 to 73c0d5b Compare August 4, 2026 10:19

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

components/HistoryView/index.tsx:181

  • The reward-events window filter uses a strict > comparison against lastEventTimestamp. Because many events share the same block timestamp, this can permanently exclude delegated RewardEvents whose transaction.timestamp equals the oldest loaded transaction timestamp (especially when there are no older transactions to load, so the boundary never moves). Using an inclusive boundary avoids stranding same-timestamp events at the paging edge.
      data?.rewardEvents?.filter(
        (e) => (e?.transaction?.timestamp ?? 0) > lastEventTimestamp
      ) ?? [],

@rickstaa
rickstaa merged commit 05e9253 into main Aug 4, 2026
9 checks passed
@rickstaa
rickstaa deleted the fix/history-delegated-reward-calls branch August 4, 2026 10:41
rickstaa added a commit that referenced this pull request Aug 5, 2026
Follow-up to #757. Render RewardCallerSetEvent on the home and transactions
pages and on the account history, including the unset variant (zero address).
Shown for any address: setRewardCaller has no registration check, and filtering
on current transcoder status would rewrite a historical event.

Also widen the reward query from #757, which was keyed on the delegate alone and
left a nominated caller's own history without the calls it made. It now matches
the sender too; those rows read "Called reward for <orchestrator>" with no LPT
amount, which is minted for the orchestrator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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