From 3ec201a3b71635f3c5dea91b24c5b3df877e06fc Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Tue, 21 Jul 2026 21:34:22 +0200 Subject: [PATCH] =?UTF-8?q?Revert=20"fix:=20temporarily=20show=20unbonding?= =?UTF-8?q?=20locks=20the=20subgraph=20has=20not=20indexed=20(#=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 081de4be4a4e21fc694dd018da4a938d029644d0. --- components/StakeTransactions/index.tsx | 70 ++++------------ hooks/useSwr.tsx | 13 --- lib/api/types/get-unbonding-locks.ts | 15 ---- pages/api/unbonding-locks/[address].tsx | 102 ------------------------ 4 files changed, 16 insertions(+), 184 deletions(-) delete mode 100644 lib/api/types/get-unbonding-locks.ts delete mode 100644 pages/api/unbonding-locks/[address].tsx diff --git a/components/StakeTransactions/index.tsx b/components/StakeTransactions/index.tsx index 264c48c8..1b4dd409 100644 --- a/components/StakeTransactions/index.tsx +++ b/components/StakeTransactions/index.tsx @@ -2,11 +2,6 @@ import { Box, Card, Flex, Heading, Text } from "@livepeer/design-system"; import { formatLPT } from "@utils/numberFormatters"; import { formatAddress } from "@utils/web3"; import { UnbondingLock } from "apollo"; -import { - useCurrentRoundData, - useSubgraphDegraded, - useUnbondingLocksData, -} from "hooks"; import { useMemo } from "react"; import { parseEther } from "viem"; @@ -18,54 +13,20 @@ import WithdrawStake from "../WithdrawStake"; const Index = ({ delegator, transcoders, currentRound, isMyAccount }) => { const isBonded = !!delegator.delegate; - // TEMPORARY, with the subgraph outage: locks created after indexing stopped - // are missing here, so an unbond looks like it never happened. Append the - // ones above the subgraph's highest known id, read from the chain. The round - // comes from the chain too, or a stale one leaves a matured lock stuck in - // "Pending" with no withdraw button. Remove once indexing has recovered. - const degraded = useSubgraphDegraded(); - - const nextLockId = useMemo( - () => - delegator.unbondingLocks.reduce( - (next: number, lock: UnbondingLock) => - Math.max(next, lock.unbondingLockId + 1), - 0 - ), - [delegator.unbondingLocks] - ); - const missing = useUnbondingLocksData( - degraded && isMyAccount ? delegator.id : null, - nextLockId - ); - - const locks = useMemo( - () => - missing?.locks.length - ? [...delegator.unbondingLocks, ...missing.locks] - : delegator.unbondingLocks, - [delegator.unbondingLocks, missing] - ); - - const onchainRound = useCurrentRoundData(); - const roundId = Number(onchainRound?.id ?? currentRound.id); - - const pendingStakeTransactions = useMemo( - () => - locks.filter( - (item: UnbondingLock) => - item.withdrawRound && +item.withdrawRound > roundId - ), - [locks, roundId] - ); - const completedStakeTransactions = useMemo( - () => - locks.filter( - (item: UnbondingLock) => - item.withdrawRound && +item.withdrawRound <= roundId - ), - [locks, roundId] - ); + const pendingStakeTransactions = useMemo(() => { + const roundId = parseInt(currentRound.id, 10); + return delegator.unbondingLocks.filter( + (item: UnbondingLock) => + item.withdrawRound && +item.withdrawRound > roundId + ); + }, [delegator.unbondingLocks, currentRound.id]); + const completedStakeTransactions = useMemo(() => { + const roundId = parseInt(currentRound.id, 10); + return delegator.unbondingLocks.filter( + (item: UnbondingLock) => + item.withdrawRound && +item.withdrawRound <= roundId + ); + }, [delegator.unbondingLocks, currentRound.id]); return ( @@ -114,7 +75,8 @@ const Index = ({ delegator, transcoders, currentRound, isMyAccount }) => { Tokens will be available for withdrawal in approximately{" "} - {+lock.withdrawRound - roundId} days. + {+lock.withdrawRound - parseInt(currentRound.id, 10)}{" "} + days. { return data ?? null; }; -export const useUnbondingLocksData = ( - address: string | undefined | null, - from: number -) => { - // `from` skips the ids the subgraph already has. See /api/unbonding-locks. - const { data } = useSWR( - address ? `/unbonding-locks/${address.toLowerCase()}?from=${from}` : null - ); - - return data ?? null; -}; - export const useL1DelegatorData = (address: string | undefined | null) => { const { data } = useSWR( address ? `/l1-delegator/${address.toLowerCase()}` : null diff --git a/lib/api/types/get-unbonding-locks.ts b/lib/api/types/get-unbonding-locks.ts deleted file mode 100644 index d5d9cf38..00000000 --- a/lib/api/types/get-unbonding-locks.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type UnbondingLockInfo = { - id: string; - unbondingLockId: number; - amount: string; - withdrawRound: string; - /** - * The contract does not record which orchestrator a lock was unbonded from, - * so this is the delegator's current delegate. - */ - delegate: { id: string }; -}; - -export type UnbondingLocks = { - locks: UnbondingLockInfo[]; -}; diff --git a/pages/api/unbonding-locks/[address].tsx b/pages/api/unbonding-locks/[address].tsx deleted file mode 100644 index b00ae040..00000000 --- a/pages/api/unbonding-locks/[address].tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { getCacheControlHeader } from "@lib/api"; -import { bondingManager } from "@lib/api/abis/main/BondingManager"; -import { getBondingManagerAddress } from "@lib/api/contracts"; -import { badRequest, internalError, methodNotAllowed } from "@lib/api/errors"; -import { UnbondingLocks } from "@lib/api/types/get-unbonding-locks"; -import { l2PublicClient } from "@lib/chains"; -import { NextApiRequest, NextApiResponse } from "next"; -import { formatEther, isAddress } from "viem"; - -/** Lock ids read per request, so a caller cannot ask for an unbounded scan. */ -const MAX_SCAN = 200; - -/** - * TEMPORARY stopgap, paired with the subgraph outage: returns the delegator's - * unbonding locks from `from` onwards, so the UI can show locks created after - * the subgraph stopped indexing. Everything the subgraph already knows keeps - * coming from the subgraph. - * - * Remove this route along with its caller once indexing has recovered. - */ -const handler = async ( - req: NextApiRequest, - res: NextApiResponse -) => { - try { - const method = req.method; - - if (method === "GET") { - res.setHeader("Cache-Control", getCacheControlHeader("revalidate")); - - const { address, from } = req.query; - - if (!!address && !Array.isArray(address) && isAddress(address)) { - const bondingManagerAddress = await getBondingManagerAddress(); - - const contract = { - address: bondingManagerAddress, - abi: bondingManager, - } as const; - - // [bondedAmount, fees, delegateAddress, delegatedAmount, startRound, - // lastClaimRound, nextUnbondingLockId] - const delegator = await l2PublicClient.readContract({ - ...contract, - functionName: "getDelegator", - args: [address], - }); - const delegateAddress = delegator[2]; - const end = Number(delegator[6]); - - // Ids are sequential and never reused, so anything the subgraph has not - // seen sits above its highest known id. Always cover the newest ids, - // whatever `from` asks for, and never read more than MAX_SCAN of them. - const requested = Number(Array.isArray(from) ? from[0] : from); - const start = Math.max( - 0, - Number.isInteger(requested) ? requested : 0, - end - MAX_SCAN - ); - - const results = await l2PublicClient.multicall({ - allowFailure: false, - contracts: Array.from( - { length: Math.max(0, end - start) }, - (_, i) => ({ - ...contract, - functionName: "getDelegatorUnbondingLock" as const, - args: [address, BigInt(start + i)] as const, - }) - ), - }); - - // Withdrawing and rebonding both delete the lock, so a non-zero amount - // is what makes one open. - const locks = results - .map(([amount, withdrawRound], i) => ({ - amount, - withdrawRound, - id: start + i, - })) - .filter(({ amount }) => amount > 0n) - .map(({ amount, withdrawRound, id }) => ({ - id: `${address.toLowerCase()}-${id}`, - unbondingLockId: id, - amount: formatEther(amount), - withdrawRound: withdrawRound.toString(), - delegate: { id: delegateAddress.toLowerCase() }, - })); - - return res.status(200).json({ locks }); - } else { - return badRequest(res, "Invalid address format"); - } - } - - return methodNotAllowed(res, method ?? "unknown", ["GET"]); - } catch (err) { - return internalError(res, err); - } -}; - -export default handler;