You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
app: rebuild the explorer from scratch as a portfolio-first app - #828
A from-scratch rebuild of the Livepeer Explorer. The portfolio is the home view, the pages use the Livepeer Design System (shared with the Console), and the old pages router, Stitches and Apollo are replaced by Next.js 16 App Router, Tailwind v4, TanStack Query and wagmi/RainbowKit.
Each commit covers one area and typechecks on its own (the first only removes the old app), so reviewing commit by commit is the easiest way through.
Changes Made
Portfolio: track any addresses (connected wallet, cold wallet, Safe, any ENS name) with stake, rewards, fees and network share per round, plus realised APR and a Needs attention list (missed reward calls, cut changes, ready withdrawals, Safe actions, open votes). Includes an earnings CSV priced at each reward call, and a prompt to delegate unstaked LPT.
Orchestrators: ranked by expected yield instead of stake, so new stake isn't steered to the biggest. Yield is rewards at today's cut plus fees, leaving out fees an orchestrator paid itself; raised cuts are flagged, newcomers are grouped separately, and ties go to the smaller orchestrator.
Staking: one dialog for delegate, move, unstake, restake and withdraw; acting for another address prompts you to connect it. Approve and bond go out as one batch inside the Safe app. Transfer stake (transferBond) sends delegated stake to another wallet without unbonding: one of the portfolio's, or any address or ENS name after confirming you control it. The contract's rules (where the stake lands, and the block until the receiver's orchestrator calls reward) are checked and explained before signing.
Governance: proposals and LIP polls with voting, plus creating polls and treasury proposals.
Also: gateway pages, a network overview with scrubbable KPIs, a searchable activity feed, ⌘K search, per-page share images, and redirects from old URLs.
Testing
Tested locally against the offline subgraph mock (scripts/mock/), with screenshots at desktop (light and dark) and mobile
All tests passing (91), plus lint, typecheck and production build
In dev (Web Analytics debug mode), checked the nav click, Orchestrators page view, orchestrator detail view (on a full page load too) and Earn entry point fire, with addresses redacted from URLs
Before merging (needs a real wallet and live data)
Send each staking and governance transaction, including transferring stake between your own wallets, with MetaMask, Rainbow and WalletConnect, and the Safe flows inside the Safe app (including a transfer into a Safe)
Compare the orchestrator APRs with on-chain pendingStake / pendingFees for 5–10 orchestrators
Check the new queries against the published subgraph, including the self-paid tickets query
Impact / Risk
Risk level: Low for this PR, which merges into next and only changes the beta site. High when next is merged into main, since that replaces the whole app.
Impacted areas: UI, data fetching, wallet and transactions, config
User impact: none on the live explorer until next is merged into main. On the beta site, a new home view and navigation; old URLs redirect to their new pages.
Rollback plan: revert this PR's commit on next. Production (main) isn't affected until next is merged into it.
Additional Notes
New env vars (see .env.example): NEXT_PUBLIC_SUBGRAPH_API_KEY (restrict it to your domains), optional NEXT_PUBLIC_SUBGRAPH_DEPLOYMENT, SITE_URL, NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID, PINATA_JWT (poll creation), optional GITHUB_ACCESS_TOKEN.
Analytics: the Vercel Web Analytics delegation funnel from feat: instrument the delegation funnel with Vercel Web Analytics #809, with the same event names so v1 and v2 can be compared by hostname in one Vercel project. The unstaked LPT prompt and onboarding links count as the plan's Earn entry point; delegation_review_opened stays unimplemented, since there's still no separate review step.
Build workaround: RainbowKit's Base Account wallet pulls in optional @x402/* modules, which Turbopack aliases to an empty stub.
Deletes the pages-router app: Stitches styles, Apollo and its codegen,
and the hand-written components, hooks and layouts. Repository tooling
(CI, templates, husky, license, contributing docs) stays. The contract
ABIs are kept and move to lib/abis.
This commit doesn't build on its own; the next ones put the new app in
place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
New package.json and lockfile, TypeScript, ESLint, Jest and PostCSS
config, and the Dockerfile. Old URLs such as /accounts/:a/delegating
and /transcoders redirect to their new pages. RainbowKit's Base Account wallet lazily pulls
in optional @x402/* modules, so Turbopack aliases them to an empty stub.
AGENTS.md and CLAUDE.md hold the Next.js notes for coding agents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Theme tokens for light, dark and system, the brand mark, buttons,
dialogs, tooltips, badges, sortable headers, page scaffolding and the
time-series chart, plus number and token formatting. Icons and the web
manifest are refreshed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
A plain GraphQL client over The Graph's gateway (Studio URL without a
key), cursor paging that handles events sharing a timestamp, active-set
status from activation rounds, and queries for the network, orchestrators,
gateways, votes and accounts, wrapped in TanStack Query hooks.
lib/portfolio/compute.ts rebuilds stake, rewards and fees per round from
the cumulative reward and fee factors, so bonding and moving stake never
count as earnings. Orchestrator stats include realised APR, the yield at
today's cut and reward-call reliability. Tests cover the maths, paging,
activity status and search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
The portfolio is a list of addresses kept in the browser; connecting a
wallet adds its address, and any address or ENS name can be added and
renamed. Acting for an address that isn't connected is one "Connect
<name>" step, and the action resumes once that account connects.
One staking dialog handles delegate, stake more, move, unstake, restake
and withdrawals, including approval. Inside the Safe app, approve and bond
go out as one batch (EIP-5792), queued transactions show as sent to the
Safe, and a Safe's queued Livepeer actions are decoded locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
A vertical sidebar with navigation, the wallet menu, theme switcher and
a round clock that celebrates when a new round starts; a top bar with a
drawer on phones; ⌘K search across orchestrators (by ENS name or
address), gateways, addresses, pages and theme; and error and not-found
pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Treasury proposals and LIP polls with quorum and quota thresholds, vote
tallies and voting. Creating a poll lists LIPs that are "Proposed" and
have no poll yet; the server pins the official LIP text to IPFS before
calling PollCreator.createPoll. A treasury proposal takes a markdown
description, a receiver (address or ENS) and an LPT amount checked
against the treasury and your voting power. Open votes feed the
portfolio's Needs attention.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
The home view charts stake, rewards, fees and network share across your
addresses, with realised APR, projected earnings and pending
withdrawals. Delegations adapt to the width they get, withdrawing fees
says what it will withdraw, and unstaked LPT gets a prompt that opens
the delegate flow pre-filled. Needs attention flags missed reward calls,
cut changes, inactive orchestrators, ready withdrawals, Safe actions and
open votes.
The earnings CSV has one row per wallet per round, valued in USD at the
hour of the reward call from Coinbase's hourly candles, served through a
cached route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
The list defaults to what a delegator joining now can expect, rather
than stake, so new stake isn't steered to whoever already has the most:
rewards over the last 30 rounds (lowered to today's cut when it was
raised, and flagged) plus 90 days of fees at today's fee share, valued at
today's ETH/LPT price, leaving out fees an orchestrator paid itself.
Newcomers get their own group and near-ties go to the smaller
orchestrator. Profiles show yield, stake and fee charts, the last 90
reward calls, cut history and delegators.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Fees paid, deposit and reserve, deposit runway, and payouts by
orchestrator, flagging a gateway that pays its own orchestrator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
A round card with reward calls, LPT minted and fees; KPI tiles with
30-day sparklines you can scrub; history charts; and a paginated,
filterable activity feed searchable by address, ENS or transaction hash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Orchestrator, gateway, poll and proposal pages draw their own share card
and title, e.g. "titan-node · 32% APR · 30/30 reward calls"; the rest of
the site uses a static image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
scripts/mock serves the queries from generated fixtures, with a
screenshot script for visual review. The README and .env.example cover
the new setup: the Graph API key, optional deployment pin, WalletConnect
and Pinata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Replaces the Conventional Commits guidance with an area-prefix style:
the area of the app, then a short imperative summary. PR titles follow
it too, since PRs are squash-merged and the title becomes the commit
on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adamsoffer
changed the title
Rebuild the explorer from scratch as a portfolio-first app
app: rebuild the explorer from scratch as a portfolio-first app
Sep 29, 2026
Brings over the previous explorer's Vercel Web Analytics funnel (#809),
with the same event names, so the two versions can be compared event
for event in one Vercel project, split by hostname.
In v2 the Delegating tab on your own account is the portfolio with a
connected wallet, and switching orchestrator counts as a redelegation,
like "Move Delegated Stake" did. The unstaked LPT prompt and the
onboarding links into choosing an orchestrator are the plan's Earn
entry point, which the old explorer had no UI for. There's still no
separate review step, so delegation_review_opened stays unimplemented.
Events tracked on mount could run before <Analytics /> set up its queue
and be dropped, so the helper sets up the queue itself, with the URL
redaction registered ahead of any event. Safe proposals aren't tracked
as transactions, since they have no on-chain hash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both orchestrator queries wait for the protocol, and a query that's
waiting reports isLoading: false. The pages read that as loaded and
empty, so the server HTML, and the client until the protocol arrived,
showed "No orchestrators match" on the list and "Not an orchestrator"
on every profile, and hydration failed on the list. isPending covers
the wait. A failed protocol read now shows as the page's error, rather
than leaving the skeleton up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The onboarding card ranked on realised APR alone, broke ties toward
more stake and counted 29 of 30 reward calls as reliable, so its top
five were the biggest of the tied orchestrators rather than the list's.
It now reuses rankByYield, expected yield with fees and ties to the
smaller orchestrator, with 30 of 30 calls, matching "Reliable only".
The median realised APR beside it uses the same set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was a card of its own between the stake chart and the figures,
weighing as much as the portfolio itself even for a little LPT. It's
now a short "N LPT unstaked" with a small outline Delegate button in
the Delegation(s) header, above the rows it would change, like the
Withdraw button on unclaimed fees. Several wallets get a menu, and a
wallet with no orchestrator gets "Choose orchestrator".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each wallet's share of the portfolio is written out ("70% of total")
instead of an unlabelled bar, which read like progress and looked
broken for a small wallet.
- The subtitle says "3 wallets", or "2 delegations · 3 wallets" when
some have none, rather than "3 across 3 wallets".
- Every account has a second line: its address under a label or name,
"Connected", or "Not connected", so rows line up and show status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 260px, a 900px-tall screen showed only the Delegations heading. At
200px its first row and the unstaked LPT prompt are in view before any
scrolling, and the chart still reads as a trend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A receipt only says a transaction was mined; its status says whether it
did anything. Governance, votes and staking all counted any receipt as
success, so a reverted vote or delegation showed as confirmed, toasted
and refreshed. Success now needs status "success", and a reverted one
shows an error instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Which LIPs already have a poll comes from the subgraph and each poll's
IPFS document. A failed or errored query, or an unreadable document, was
read as "no poll", so an already-polled LIP could be offered again and
polled twice. Those failures now throw, and poll creation reports that
LIPs couldn't be loaded until they read cleanly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oll vote
An orchestrator's voteStake still includes delegators who voted for
themselves; their stake is its nonVoteStake. Subtract it, as the
previous explorer does, so the orchestrator's weight, share and the
grouped totals aren't overstated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a subgraph key or endpoint the app falls back to the staging
subgraph, which suits next dev but would ship staging data as
production's. Production builds now fail in that case instead.
Docker builds also take SITE_URL, which the layout needs for absolute
share-image URLs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
String.fromCodePoint throws past U+10FFFF, so proposal text with an
entity like � crashed the page. Such entities now stay as
written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clears GHSA-wq5f-xc86-pv6w (high, CVE-2026-96889), a vulnerability in
sharp's bundled librsvg that fails the audit step in CI. sharp comes in
through next, whose range already allows 0.35.5; an override pins it so
the install updates only sharp and its platform packages, instead of
re-resolving unrelated ones as pnpm update does. The install also moved
a dev-only Lighthouse dependency, legacy-javascript, from 0.0.1 to 0.0.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Transfer stake is inaccessible for single-wallet portfolios, some legacy URLs lack redirects, and an indexing timeout edge case can strand refresh flows.
Light mode had grey cards on a white page, which reads as recessed
wells. Like dark mode and most apps (Linear, Vercel, Stripe), surfaces
now get lighter as they rise: an off-white page and sidebar, with white
cards that keep their hairline border and gain a faint shadow.
Elements that used the page background to mean white (the selected
segment, search box, active nav item, estimate field) now use the card
colour, so they stay white.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A vote that misses quorum fails however lopsided it is, so a meter under
the tally shows turnout towards quorum and how much more stake needs to
vote. Participation leaves the details, which keep quorum with quota as
the vote's rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no LIPs open, the page is its own empty view rather than a card:
centred under the header, with how to write a LIP, the repository, and
which commit of it was checked. The description gives the poll period
from PollCreator (57,600 L1 blocks, about 8 days), not 10 rounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The treasury pays on Arbitrum, so an ENS name's Arbitrum address
(ENSIP-11) is used when set, falling back to its mainnet one. A receiver
that's a contract on mainnet with no code at the same address on
Arbitrum, most likely a mainnet-only Safe, gets a warning: its owners
don't control that address on Arbitrum, and the LPT would be lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An orchestrator that never changed its cut since activation has an
update time of 0 in the subgraph, which read as "Updated 57 years ago".
It now reads "Unchanged since" its first round's date, in the stat and
the cut history, and a missing activation time is left out rather than
shown as 1970.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Vote queries can silently truncate governance data, production can still target staging, and contract addresses remain cached across registry upgrades.
"Add address" beside "Connect wallet" read as two ways to do the same
thing. "Watch address", with an eye, says it's read-only; the dialog
says so too. The portfolio's own Connect wallet is now phones-only, since
desktop has one in the header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A queued transaction is described only when it's a plain CALL, or a
DELEGATECALL to a canonical MultiSend whose batched calls are all CALLs;
any other delegatecall runs someone else's code as the Safe. A Livepeer
contract that hasn't resolved yet now matches nothing instead of
everything.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Poll and treasury votes, their events, and voters' delegates were capped
at 1,000 rows, so a large vote would list a subset and work out shares
against it. They're paged to completion, and a poll at the nested cap is
counted in full.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Controller can point a name at an upgraded contract; cached forever,
a tab left open kept sending to the old one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Submitting waits for the ENS lookups and both contract checks to finish,
and is blocked when the receiver is a mainnet-only contract, where the
LPT would be lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imeout
Transfer was hidden without a second saved wallet, though the picker
takes any address. The indexing wait could build a negative timeout,
which throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enforce the deadline during in-flight requests and retries
app/api/prices/history/route.ts:63
The 45-second deadline only prevents starting the next batch; it does not bound an in-flight readPage. Each page can perform seven 15-second requests plus exponential backoff, so a degraded Coinbase endpoint can run well beyond this route's 60-second limit and be terminated without a JSON response. Pass the remaining budget into each page request (including retries and abort timeouts), or race/abort the whole lookup at the deadline.
Avoid claiming ascending ID results are the most recent
app/gateways/[address]/page.tsx:144
This is not necessarily the “most recent” set: the capped query is ordered by id ascending, not by timestamp. When the cap is reached, this message misstates which tickets contributed to the displayed totals. Either implement timestamp-based cursor pagination or describe it as a capped set without claiming recency.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A from-scratch rebuild of the Livepeer Explorer. The portfolio is the home view, the pages use the Livepeer Design System (shared with the Console), and the old pages router, Stitches and Apollo are replaced by Next.js 16 App Router, Tailwind v4, TanStack Query and wagmi/RainbowKit.
Each commit covers one area and typechecks on its own (the first only removes the old app), so reviewing commit by commit is the easiest way through.
Changes Made
transferBond) sends delegated stake to another wallet without unbonding: one of the portfolio's, or any address or ENS name after confirming you control it. The contract's rules (where the stake lands, and the block until the receiver's orchestrator calls reward) are checked and explained before signing.Testing
scripts/mock/), with screenshots at desktop (light and dark) and mobileBefore merging (needs a real wallet and live data)
pendingStake/pendingFeesfor 5–10 orchestratorsImpact / Risk
Risk level: Low for this PR, which merges into
nextand only changes the beta site. High whennextis merged intomain, since that replaces the whole app.Impacted areas: UI, data fetching, wallet and transactions, config
User impact: none on the live explorer until
nextis merged intomain. On the beta site, a new home view and navigation; old URLs redirect to their new pages.Rollback plan: revert this PR's commit on
next. Production (main) isn't affected untilnextis merged into it.Additional Notes
.env.example):NEXT_PUBLIC_SUBGRAPH_API_KEY(restrict it to your domains), optionalNEXT_PUBLIC_SUBGRAPH_DEPLOYMENT,SITE_URL,NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID,PINATA_JWT(poll creation), optionalGITHUB_ACCESS_TOKEN.delegation_review_openedstays unimplemented, since there's still no separate review step.@x402/*modules, which Turbopack aliases to an empty stub.🤖 Generated with Claude Code
https://claude.ai/code/session_01Dh52qRhBrfkLKKAAGqPxYN
Generated by Claude Code