feat: multi-rollup support — view/claim rewards across rollup versions#58
Open
gpevnev wants to merge 18 commits intoAztecProtocol:mainfrom
Open
feat: multi-rollup support — view/claim rewards across rollup versions#58gpevnev wants to merge 18 commits intoAztecProtocol:mainfrom
gpevnev wants to merge 18 commits intoAztecProtocol:mainfrom
Conversation
Discover all rollup versions at runtime from the Aztec governance Registry contract instead of hardcoding a single rollup via VITE_ROLLUP_ADDRESS. - Add RollupRegistry ABI (IRegistry: numberOfVersions, getVersion, getRollup, getCanonicalRollup) - Add useRollupRegistry hook that chains StakingRegistry.ROLLUP_REGISTRY() → Registry enumeration → builds rollup list with canonical detection - Export RollupInstance type and useRollupRegistry from rollup hooks barrel - Register rollupRegistry ABI in contracts/index.ts (address discovered at runtime, not configured statically)
Each rollup hook now accepts an optional rollupAddress parameter that defaults to contracts.rollup.address. This enables callers to target a specific rollup (e.g. an old rollup for stranded rewards/stakes) without changing the default behavior for existing consumers. Parameterized hooks: useSequencerRewards, useClaimSequencerRewards, useIsRewardsClaimable, useRollupData, useActivationThresholdFormatted, useEjectionThreshold, useAttesterView, useStakeHealth, useSequencerStatus, useWalletDirectStake, useWalletInitiateWithdraw, useFinalizeWithdraw, useApproveRollup.
Fan out reward reads across all discovered rollups so sequencers can see and claim rewards stranded on older rollups. - Add useCoinbaseRewardsAcrossRollups: multicalls getSequencerRewards across every (rollup x coinbase) pair, returns per-rollup breakdown - Rewrite useMultipleCoinbaseRewards as thin wrapper over the new fan-out hook - Add rollupAddress/rollupVersion to CoinbaseBreakdown and ClaimTask types - Thread rollupAddress through useClaimAllRewards engine (per-task targeting) - Thread rollupAddress through useClaimCoinbaseRewards - Add useIsRewardsClaimableAcrossRollups: multicall isRewardsClaimable() per rollup for per-row claim button gating - Add useAttesterStakeLocation: scans all rollups via getAttesterView to find where a stranded stake lives
…isclaimers UI components updated to surface multi-rollup data: Rewards: - CoinbaseAddressList: per-rollup rows with version badges, per-row claim targeting specific rollup, per-row claimability gating - ClaimSelfStakeRewardsModal: per-rollup balances with individual claim buttons - ClaimAllRewardsSummary: rollup version badges on coinbase rows - ClaimAllRewardsProgress: truncate long error messages to prevent overflow - ATPStakingOverviewClaimableRewards: gate Claim All on T&C acceptance Registration: - RegistrationStake: read activation threshold from canonical rollup - WalletDirectStakingFlow: deposit targets canonical rollup address Withdrawals: - WalletDirectStakeItem: resolve stake location via useAttesterStakeLocation - WalletWithdrawalActions: accept rollupAddress prop for stranded-stake withdrawals Disclaimers: - Add IndexerRollupDisclaimer component (shown when rollups.length > 1) - Add to StakingProvidersPage and StakingProviderDetailPage
End-to-end test setup that deploys real Aztec L1 contracts to local anvil with 2 rollup versions, seeds reward state, and verifies the dashboard's multi-rollup features. - deploy-multi-rollup.sh: orchestrates full L1 deploy (DeployAztecL1Contracts + DeployRollupForUpgrade), registers v2 via anvil_impersonateAccount, deploys MockStakingRegistry, deploys Multicall3, writes contract_addresses.json - seed-multi-rollup.ts: seeds sequencer rewards and isRewardsClaimable via anvil_setStorageAt using namespaced storage layout, mints fee tokens to rollups for claim payouts - MockStakingRegistry.sol: minimal mock (only contract not in aztec-packages) - README.md: full setup guide with architecture, gotchas, troubleshooting Requires AZTEC_PACKAGES_DIR env var (set automatically by .envrc).
The Positions Overview section (with Claimable Rewards) was gated only on having staked positions from the indexer. Users who added coinbase addresses for self-stake reward tracking but had no ATP staking events wouldn't see their rewards. Now also checks for saved coinbase addresses.
… timeout The completion effect created a setTimeout to advance to the next task and returned a cleanup function to clear it. But setTasks() inside the same effect changes `tasks` (a dependency), triggering an effect re-run whose cleanup clears the timeout before it fires. The engine permanently stalls after the first task. Fix: use a ref-based timeout (advanceTimeoutRef) that persists across effect re-runs. The handledCompletionRef guard prevents re-processing, so the effect re-running is harmless. Also store claim hooks in refs to remove hook identity from trigger effect deps — prevents premature firing during reset cycles.
onSuccess (which refetches rewards) was called as soon as isSuccess became true. The refetch zeroed out the rewards, causing hasStakedPositions to become false, unmounting the parent and the modal with it. Move onSuccess to handleDone so it fires when the user manually dismisses the success screen, not when the claims complete.
…he active one The CoinbaseAddressList shared a single useClaimCoinbaseRewards hook instance across all rows. When one row was claiming, isPending/isConfirming applied to every row's button, making them all show "Confirming..." simultaneously. Track which row is actively claiming via a claimingRowKey state. Only the active row shows the spinner; other rows are disabled but show "Claim Rewards".
Extend MockStakingRegistry with registerProvider() that emits ProviderRegistered events. The Ponder indexer watches for these events and populates the /api/providers endpoint. Add seed-providers.ts that registers the first 10 providers from the provider metadata (providers/*.json) on-chain so the providers list is populated during integration testing.
…irst claim Two bugs fixed: 1. handledCompletionRef stuck after single-task claim: the ref was set during completion handling but never cleared in the "all done" branch, startClaiming, cancelClaiming, or reset. After a successful claim, re-opening the modal would stall forever because the completion effect returned early on the guard check. Now reset in all exit paths. 2. ClaimSelfStakeRewardsModal auto-closes after first per-rollup claim: the success effect called onClose(), dismissing the modal and clearing the coinbase input. Users with rewards on multiple rollups had to reopen and re-enter the address for each rollup. Now resets the claim hook and re-checks rewards instead, so the remaining rollup rows stay visible.
…addresses stay visible P1: The claim engine previously stopped on the first error (e.g. a locked rollup), never reaching later claimable tasks. Now marks the failed task as errored and advances to the next one. Progress counts both completed and failed tasks. isSuccess fires when all tasks are processed. P2: useCoinbaseRewardsAcrossRollups now returns both allCoinbaseBreakdown (including zero-balance rows) and coinbaseBreakdown (non-zero only). The management UI uses allCoinbaseBreakdown so saved addresses with no rewards remain visible and removable. Claim UIs continue using the filtered version.
P0: ATPStakingOverview — the hasLoadedOnce guard no longer narrowed decimals/symbol/activationThreshold types. Add a second runtime guard for type narrowing after the initial-load shortcut. P0: ClaimSelfStakeRewardsModal — debouncedCheckRewards takes 0 args but was called with coinbaseAddress. Remove the argument. P1: ClaimAllRewardsModal — mixed-result runs (some succeeded, some failed) transitioned to the success phase, hiding the retry UI. Now only transitions to success when isSuccess && !isError. All three pass tsc --noEmit.
0d9be7e to
39c4172
Compare
- ClaimAllRewardsModal: destructure reset to stabilize the dependency - ClaimSelfStakeRewardsModal: add debouncedCheckRewards to deps with suppress - useClaimAllRewards: suppress derived-dep warning for currentTask
Replace `coinbaseAddress.length === 42 && coinbaseAddress.startsWith('0x')`
with `validateAddress(coinbaseAddress)` which uses viem's `isAddress` for
proper hex and checksum validation.
The inline reward row rendering (rollup badge, reward amount, claim button, loading/locked states) was deeply nested and hard to follow. Extract it into a focused RollupRewardRow component with clear props.
Replace 4 interleaved useEffects, 5 refs, and 6 state variables with an explicit state machine using useReducer. Phases: idle → ready_to_trigger → waiting_for_result → advancing → next Each phase has exactly one effect: 1. trigger: call claim function 2. result: watch hook isSuccess/isError 3. advance: setTimeout → reset hooks → dispatch ADVANCED 4. substep: update delegation sub-step display All task mutations happen in the pure reducer function. Effect cleanups are safe because phases don't change mid-timeout. No guard refs, no hasTriggeredClaim, no handledCompletionRef.
95071ae to
8a296d3
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Closes #57
The dashboard previously hardcoded a single rollup via
VITE_ROLLUP_ADDRESS. Sequencers could not view or claim rewards stranded on older rollups, and registration would target the wrong rollup after governance rotation.This PR adds runtime rollup discovery via the on-chain
IRegistrycontract and fans out reward reads/claims across all discovered rollups.What changed
useRollupRegistryhook readsStakingRegistry.ROLLUP_REGISTRY()→ enumerates all versions from the governance Registry. Returnsrollups[],canonical,configured,isStale.rollupAddress, defaulting tocontracts.rollup.address. No behavior change for existing callers.useCoinbaseRewardsAcrossRollupsmulticallsgetSequencerRewards(coinbase)across every(rollup × coinbase)pair. Per-rollup rows with version badges in the UI.claimSequencerRewards.useIsRewardsClaimableAcrossRollupsgates each row's button independently.RegistrationStakeandWalletDirectStakingFlowuseuseRollupRegistry().canonicalfor deposit/stake calls regardless ofVITE_ROLLUP_ADDRESS.useAttesterStakeLocationscans all rollups to find where an attester's stake lives, routes withdraw/finalize to the correct contract.rollups.length > 1.useReducerstate machine (idle → ready_to_trigger → waiting_for_result → advancing). Fixed sequential task execution, modal unmounting, per-row spinner state, T&C gate.Integration test
Includes a full integration test setup using real Aztec L1 contracts on local anvil:
scripts/multi-rollup-test/deploy-multi-rollup.sh— deploys 2 rollup versions + MockStakingRegistryscripts/multi-rollup-test/seed-multi-rollup.ts— seeds rewards viaanvil_setStorageAtscripts/multi-rollup-test/seed-providers.ts— registers test providers for populated provider listscripts/multi-rollup-test/README.md— setup guide with gotchasScreenshots
Per-rollup reward rows with version badges and individual claim buttons
Manage Reward Addresses — coinbase with rewards on two rollups
Providers list with IndexerRollupDisclaimer at the bottom
Claim All Rewards — sequential per-rollup task execution
Claim All progress — task 1 completed, task 2 in progress
Claim All success — both rollup tasks completed
Out of scope
Test plan
numberOfVersions() == 2)rollups.length > 1yarn type-checkpassesyarn buildpasses