Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export const ClaimSelfStakeRewardsModal = ({
rewards={row.rewards}
decimals={decimals ?? 18}
symbol={symbol ?? ""}
isClaimable={isClaimableForRollup(row.rollupAddress) === true}
isClaimable={isClaimableForRollup(row.rollupAddress)}
isInBatch={isInBatch}
onAddToBatch={() => handleAddToBatch(row.rollupAddress, row.rollupVersion, row.rewards)}
onOpenCart={openCart}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ export const CoinbaseAddressList = ({
return (
<div className="space-y-3">
{coinbaseBreakdown.map((item) => {
// Only enable the claim button when the rollup's claimability has been
// explicitly confirmed `true`. `undefined` (still loading, or the
// multicall reverted) is treated as not-claimable so the user doesn't
// sign a tx that's guaranteed to revert and waste gas.
const rowIsClaimable = isClaimableForRollup(item.rollupAddress) === true
// Lock a row only when its rollup explicitly reports rewards as not
// claimable (`isRewardsClaimable() === false`, the network-wide freeze on
// older rollups). A revert — e.g. the V5 rollup, which removed the
// function — or a still-loading read is treated as claimable, since
// claims on those rollups are live. See issue #111.
const rowIsClaimable = isClaimableForRollup(item.rollupAddress)
const rowKey = `${item.address}-${item.rollupAddress}`
const tx = buildClaimSequencerRewardsTx(item.address, item.rollupAddress)
const isInBatch = checkTransactionInQueue(tx)
Expand Down
10 changes: 9 additions & 1 deletion staking-dashboard/src/hooks/rollup/useIsRewardsClaimable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import { contracts } from "@/contracts"
/**
* Hook to check if rewards are claimable from a specific rollup contract.
*
* `isRewardsClaimable()` was a network-wide reward lock on older rollups; a
* `false` return means the protocol has frozen claims. The V5 rollup removed
* the function, so the call reverts there — but claims on V5 are always live.
* We fail OPEN: `isRewardsClaimable` is `false` only when the rollup explicitly
* returns `false`; a revert (function absent, e.g. V5) or a still-loading read
* reads as `true`, so a removed view can't silently disable claims or surface a
* spurious "rewards locked" banner. See issue #111.
*
* @param rollupAddress - Optional rollup contract to query. Defaults to the configured rollup.
*/
export function useIsRewardsClaimable(rollupAddress?: Address) {
Expand All @@ -16,7 +24,7 @@ export function useIsRewardsClaimable(rollupAddress?: Address) {
})

return {
isRewardsClaimable: query.data as boolean | undefined,
isRewardsClaimable: query.data !== false,
isLoading: query.isLoading,
error: query.error,
refetch: query.refetch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import { contracts } from "@/contracts"

/**
* Multicalls `isRewardsClaimable()` across a list of rollup contracts.
* Returns a map keyed by lowercased rollup address; `undefined` means the
* value is still loading (or the call reverted).
*
* `isRewardsClaimable()` was a network-wide reward lock on older rollups; a
* `false` return means the protocol has frozen claims on that rollup. The V5
* rollup removed the function entirely, so the call reverts there — but claims
* on V5 are always live. We therefore fail OPEN: a rollup is treated as locked
* only when it explicitly returns `false`. A revert (function absent, e.g. V5)
* or a still-loading read is treated as claimable, so a removed view can't
* silently disable claims. See issue #111.
*/
export function useIsRewardsClaimableAcrossRollups(rollupAddresses: Address[]) {
const uniqueAddresses = useMemo(() => {
Expand Down Expand Up @@ -50,8 +56,11 @@ export function useIsRewardsClaimableAcrossRollups(rollupAddresses: Address[]) {
return map
}, [data, uniqueAddresses])

const isClaimable = (rollupAddress: Address): boolean | undefined => {
return claimableByRollup.get(rollupAddress.toLowerCase())
// Fail open: locked only when the rollup explicitly returned `false`. A
// missing entry (revert on V5 where the function no longer exists, or the
// read hasn't resolved) is treated as claimable.
const isClaimable = (rollupAddress: Address): boolean => {
return claimableByRollup.get(rollupAddress.toLowerCase()) !== false
}

return {
Expand Down
Loading