diff --git a/apps/hub/src/app/validators/components/cutting-board-configuration.tsx b/apps/hub/src/app/validators/components/cutting-board-configuration.tsx deleted file mode 100644 index 4aedc0a3c..000000000 --- a/apps/hub/src/app/validators/components/cutting-board-configuration.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { useMemo, useState } from "react"; -import { usePollGaugesData } from "@bera/berajs"; -import { Combobox } from "@bera/shared-ui"; -import { BeraChart } from "@bera/ui/bera-chart"; -import { Button } from "@bera/ui/button"; -import { Icons } from "@bera/ui/icons"; -import { Input } from "@bera/ui/input"; - -export const CuttingBoardConfiguration = () => { - const [gauges, setGauges] = useState< - { address: string; distribution: number; color: number }[] - >([{ address: "", distribution: 0, color: Math.random() * 360 }]); - const { data, isLoading } = usePollGaugesData(); - - const colourPalette = [0, 240, 120, 300, 60, 180, 270]; - - const gaugesData = useMemo(() => { - return data?.vaults?.map((gauge: any) => ({ - vaultAddress: gauge.vaultAddress, - stakingTokenAddress: gauge.stakingToken.stakingTokenAddress, - symbol: gauge.stakingToken.symbol, - name: gauge.stakingToken.name, - })); - }, [data]); - - const handleAddGauge = () => { - setGauges([ - ...gauges, - { address: "", distribution: 0, color: Math.random() * 360 }, - ]); - }; - - const handleDeleteGauge = (index: number) => { - setGauges(gauges.filter((_, i) => i !== index)); - }; - - const handleGaugeChange = ( - index: number, - field: "address" | "distribution", - value: string | number, - ) => { - const updatedGauges = gauges.map((gauge, i) => { - if (i === index) { - return { ...gauge, [field]: value }; - } - return gauge; - }); - setGauges(updatedGauges); - }; - - const totalDistribution = gauges.reduce( - (sum, gauge) => sum + gauge.distribution, - 0, - ); - - const pieChartData = useMemo(() => { - return { - labels: gauges.map((gauge) => gauge.address || "Unassigned"), - datasets: [ - { - data: gauges.map((gauge) => gauge.distribution), - hoverBorderWidth: 5, - borderRadius: 8, - spacing: 5, - borderWidth: 0, - backgroundColor: gauges.map( - (gauge, index) => - `hsl(${ - colourPalette[index] - ? colourPalette[index] - : Math.random() * 360 - }, 70%, 40%)`, - ), - hoverBorderColor: gauges.map( - (gauge, index) => - `hsl(${ - colourPalette[index] - ? colourPalette[index] - : Math.random() * 360 - }, 70%, 40%)`, - ), - // borderColor: "colours", - }, - ], - }; - }, [gauges]); - - const pieChartOptions = { - responsive: true, - cutout: "70%", - radius: "95%", - plugins: { - legend: { - display: false, - }, - tooltip: { - callbacks: { - label: (context: any) => { - const label = context.label || ""; - const value = context.parsed || 0; - return `${label}: ${value}%`; - }, - }, - }, - }, - }; - - return ( -
-
- {gauges.map((gauge, index) => ( -
- ({ - value: gaugeData.vaultAddress, - label: `${gaugeData.symbol} - ${gaugeData.name}`, - })) || [] - } - onSelect={(selectedValue) => - handleGaugeChange(index, "address", selectedValue) - } - /> - { - const value = e.target.value; - const [integerPart, decimalPart] = value.split("."); - const formattedValue = decimalPart - ? `${integerPart}.${decimalPart.slice(0, 2)}` - : value; - handleGaugeChange( - index, - "distribution", - parseFloat(formattedValue), - ); - }} - placeholder="Distribution" - outerClassName="w-fit" - className="w-48 text-right" - onKeyDown={(e) => - (e.key === "-" || e.key === "e" || e.key === "E") && - e.preventDefault() - } - maxLength={3} - min={0} - max={100} - // endAdornment={ - // - // % - // - // } - /> - -
- ))} -
- - -
-
-
-
- -
-
- Total -
-
- {totalDistribution}% -
-
-
-
- Total Distribution: {totalDistribution}% - {totalDistribution !== 100 && ( - Total must equal 100% - )} -
-
-
- ); -}; diff --git a/apps/hub/src/app/validators/components/general-settings.tsx b/apps/hub/src/app/validators/components/general-settings.tsx new file mode 100644 index 000000000..5150753f8 --- /dev/null +++ b/apps/hub/src/app/validators/components/general-settings.tsx @@ -0,0 +1,192 @@ +import { useCallback, useMemo, useState } from "react"; +import { + TransactionActionType, + beaconDepositAbi, + useBeraJs, + useValidatorOperatorAddress, + useValidatorQueuedOperatorAddress, +} from "@bera/berajs"; +import { depositContractAddress } from "@bera/config"; +import { useTxn } from "@bera/shared-ui"; +import { Button } from "@bera/ui/button"; +import { Card, CardContent, CardFooter } from "@bera/ui/card"; +import { Checkbox } from "@bera/ui/checkbox"; +import { Icons } from "@bera/ui/icons"; +import { Input } from "@bera/ui/input"; +import { Address, zeroAddress } from "viem"; + +export const GeneralSettings = ({ + validatorPublicKey, +}: { + validatorPublicKey: Address; +}) => { + const [confirmed, setConfirmed] = useState(false); + const { account } = useBeraJs(); + const { + data: operatorAddress, + isLoading: isOperatorAddressLoading, + refresh: refreshOperatorAddress, + } = useValidatorOperatorAddress(validatorPublicKey); + const { + data: queuedOperatorAddress, + isLoading: isQueuedOperatorAddressLoading, + refresh: refreshQueuedOperatorAddress, + } = useValidatorQueuedOperatorAddress(validatorPublicKey); + const [operatorInput, setOperatorInput] = useState(account || ""); + + const isQueuedOperatorAddress = useMemo(() => { + if ( + !queuedOperatorAddress || + !operatorAddress || + isQueuedOperatorAddressLoading || + isOperatorAddressLoading + ) + return true; + return ( + queuedOperatorAddress[1] !== operatorAddress && + queuedOperatorAddress[1] !== zeroAddress + ); + }, [ + queuedOperatorAddress, + operatorAddress, + isQueuedOperatorAddressLoading, + isOperatorAddressLoading, + ]); + + const timeRemaining = useMemo(() => { + if (!queuedOperatorAddress) return 0; + const currentBlockTimestamp = Number(queuedOperatorAddress[0]) * 1000; // Convert seconds to milliseconds + const twentyFourHoursInMs = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + return currentBlockTimestamp + twentyFourHoursInMs; + }, [queuedOperatorAddress]); + + const canQueueOperatorChange = useMemo(() => { + if (!queuedOperatorAddress) return false; + return ( + queuedOperatorAddress[1] !== operatorAddress && Date.now() > timeRemaining + ); + }, [queuedOperatorAddress, operatorAddress, timeRemaining]); + + const { + write, + isLoading: isApplyingOperatorChange, + ModalPortal, + } = useTxn({ + message: "Applying Operator Change", + actionType: TransactionActionType.APPLYING_OPERATOR_CHANGE, + onSuccess: () => { + refreshOperatorAddress(); + refreshQueuedOperatorAddress(); + }, + }); + + const handleSaveSettings = useCallback(() => { + write({ + address: depositContractAddress, + abi: beaconDepositAbi, + functionName: "requestOperatorChange", + params: [validatorPublicKey, operatorInput as Address], + }); + }, [operatorInput, validatorPublicKey]); + + const handleConfirmQueuedOperatorChange = useCallback(() => { + write({ + address: depositContractAddress, + abi: beaconDepositAbi, + functionName: "acceptOperatorChange", + params: [validatorPublicKey], + }); + }, [validatorPublicKey]); + + const handleCancelQueuedOperatorChange = useCallback(() => { + write({ + address: depositContractAddress, + abi: beaconDepositAbi, + functionName: "cancelOperatorChange", + params: [validatorPublicKey], + }); + }, [validatorPublicKey]); + + return ( +
+ {isQueuedOperatorAddress && ( + + +
+
+ + {canQueueOperatorChange + ? "Confirm Operator Address Change" + : "Queued Operator Address Change"} +
+ + {`Operator Address is currently queued to change to ${ + queuedOperatorAddress?.[1] + }. You'll be able to apply the change at ${new Date( + timeRemaining, + ).toLocaleString()}`} + +
+
+ + +
+
+
+ )} + + {ModalPortal} + + General Settings + + Configure your operator address + + Operator Address + setOperatorInput(e.target.value)} + /> + + +
+ setConfirmed(!confirmed)} + /> + + I understand that changing operator address is equivalent to + handing over ownership rights to the validator + +
+ +
+
+
+ ); +}; diff --git a/apps/hub/src/app/validators/components/queued-reward-allocation-configuration.tsx b/apps/hub/src/app/validators/components/queued-reward-allocation-configuration.tsx new file mode 100644 index 000000000..de23dbb8f --- /dev/null +++ b/apps/hub/src/app/validators/components/queued-reward-allocation-configuration.tsx @@ -0,0 +1,241 @@ +import { useMemo } from "react"; +import { + usePollGaugesData, + useValidatorQueuedRewardAllocation, + useBlockToTimestamp, +} from "@bera/berajs"; +import { SimpleTable, useAsyncTable } from "@bera/shared-ui"; +import { BeraChart } from "@bera/ui/bera-chart"; +import { Card, CardContent } from "@bera/ui/card"; +import { Skeleton } from "@bera/ui/skeleton"; +import { type ColumnDef } from "@tanstack/react-table"; +import { v4 as uuidv4 } from "uuid"; +import { useBlockNumber } from "wagmi"; +import { Address } from "viem"; +type VaultData = { + vaultAddress: string; + stakingTokenAddress: string | undefined; + symbol: string; + name: string; +}; + +type VaultWeight = { + address: string; + distribution: number; + id: string; + name: string; +}; + +const dateFormatter = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", +}); + +export const QueuedRewardAllocationConfiguration = ({ + validatorPublicKey, +}: { validatorPublicKey: Address }) => { + const { + data: queuedRewardAllocation, + isLoading: queuedRewardAllocationLoading, + } = useValidatorQueuedRewardAllocation(validatorPublicKey); + const { data, isLoading: allVaultsLoading } = usePollGaugesData(); + + const vaultsData = useMemo(() => { + return data?.vaults?.map((vault) => ({ + vaultAddress: vault.vaultAddress.toLowerCase(), + stakingTokenAddress: vault.stakingToken?.address?.toLowerCase(), + symbol: vault.stakingToken.symbol, + name: vault.stakingToken.name, + })); + }, [data]); + + const { data: blockNumber } = useBlockNumber(); + + const vaults = useMemo(() => { + return queuedRewardAllocation?.weights.map((weight) => ({ + address: weight.receiver.toLowerCase(), + distribution: Number(weight.percentageNumerator) / 100, + id: uuidv4(), + name: `${ + vaultsData?.find( + (item) => item.vaultAddress === weight.receiver.toLowerCase(), + )?.symbol + } - ${ + vaultsData?.find( + (item) => item.vaultAddress === weight.receiver.toLowerCase(), + )?.name + }`, + })); + }, [queuedRewardAllocation, vaultsData]); + + const startBlockTimestamp = useBlockToTimestamp( + queuedRewardAllocation?.startBlock ?? 0, + ); + + const rgbColorPalette = [ + "rgb(248 113 113)", // bg-red-400 + "rgb(96 165 250)", // bg-blue-400 + "rgb(251 146 60)", // bg-orange-400 + "rgb(167 139 250)", // bg-violet-400 + "rgb(250 204 21)", // bg-yellow-400 + "rgb(74 222 128)", // bg-green-400 + "rgb(56 189 248)", // bg-sky-400 + "rgb(45 212 191)", // bg-teal-400 + "rgb(255 210 204)", // bg-pink-400, + "rgb(156 163 175)", // bg-gray-400 + ]; + + const queuedTableColumns = useMemo< + ColumnDef[] + >(() => { + return [ + { + header: "Name", + accessorKey: "name", + cell: ({ row }) => ( +
{row.original?.name}
+ ), + enableSorting: false, + }, + { + header: "Vault Address", + accessorKey: "address", + cell: ({ row }) => ( +
{row.original?.address}
+ ), + enableSorting: false, + }, + { + header: "Distribution", + accessorKey: "distribution", + enableSorting: false, + cell: ({ row }) => ( +
{row.original?.distribution}%
+ ), + minSize: 100, + size: 100, + }, + ]; + }, []); + + const queuedTable = useAsyncTable({ + fetchData: async () => {}, + columns: queuedTableColumns, + data: vaults || [], + }); + + const pieChartData = useMemo(() => { + if (!vaults || !vaultsData) return { labels: [], datasets: [] }; + return { + labels: vaults?.map( + (vault) => + vaultsData?.find((item) => item.vaultAddress === vault.address) + ?.name || "Unassigned", + ), + datasets: [ + { + data: vaults?.map((vault) => vault.distribution), + hoverBorderWidth: 5, + borderRadius: 8, + spacing: 5, + borderWidth: 0, + backgroundColor: vaults?.map( + (vault, index) => rgbColorPalette[index], + ), + hoverBorderColor: vaults?.map( + (vault, index) => rgbColorPalette[index], + ), + }, + ], + }; + }, [vaults]); + + const pieChartOptions = { + responsive: true, + cutout: "70%", + radius: "95%", + plugins: { + legend: { + display: false, + }, + tooltip: { + backgroundColor: "rgba(0, 0, 0, 1)", + cornerRadius: 6, + interaction: { + intersect: true, + }, + callbacks: { + label: (context: any) => { + const label = context.label || ""; + const value = context.parsed || 0; + return `${label}: ${value}%`; + }, + }, + }, + }, + }; + + return ( + <> + {queuedRewardAllocationLoading || allVaultsLoading ? ( + + ) : vaults && vaults.length > 0 ? ( + + +
+ {/* Vaults Weight Chart */} +
+
+ Queued Distribution +
+
+ +
+
+ + {`${ + Number(queuedRewardAllocation?.startBlock) - + Number(blockNumber) + }`}{" "} + Blocks Remaining + + + {startBlockTimestamp + ? dateFormatter.format( + typeof startBlockTimestamp === "number" + ? startBlockTimestamp * 1000 + : startBlockTimestamp, + ) + : "--"} + +
+
+
+
+ +
+
+
+
+
+ ) : null} + + ); +}; diff --git a/apps/hub/src/app/validators/components/reward-allocation-configuration.tsx b/apps/hub/src/app/validators/components/reward-allocation-configuration.tsx new file mode 100644 index 000000000..84b924d56 --- /dev/null +++ b/apps/hub/src/app/validators/components/reward-allocation-configuration.tsx @@ -0,0 +1,415 @@ +import { useEffect, useMemo, useState } from "react"; +import { + BERA_CHEF_ABI, + TransactionActionType, + useDefaultRewardAllocation, + usePollGaugesData, + useRewardAllocationBlockDelay, + useValidatorQueuedRewardAllocation, + useValidatorRewardAllocation, +} from "@bera/berajs"; +import { beraChefAddress } from "@bera/config"; +import { Combobox, useTxn } from "@bera/shared-ui"; +import { cn } from "@bera/ui"; +import { BeraChart } from "@bera/ui/bera-chart"; +import { Button } from "@bera/ui/button"; +import { Card, CardContent, CardFooter } from "@bera/ui/card"; +import { Icons } from "@bera/ui/icons"; +import { Input } from "@bera/ui/input"; +import { Skeleton } from "@bera/ui/skeleton"; +import BigNumber from "bignumber.js"; +import { v4 as uuidv4 } from "uuid"; +import { Address } from "viem"; +import { useBlockNumber } from "wagmi"; + +const USER_BLOCK_DELAY = 100n; + +export const RewardAllocationConfiguration = ({ + validatorPublicKey, +}: { validatorPublicKey: Address }) => { + const [vaults, setVaults] = useState< + { address: string; distribution: number; id: string }[] + >([{ address: "", distribution: 0, id: uuidv4() }]); + const { data: rewardVaults, isLoading } = usePollGaugesData(); + const { data: blockNumber } = useBlockNumber({ + query: { refetchInterval: 10000 }, + }); + const { data: rewardAllocationBlockDelay } = useRewardAllocationBlockDelay(); + const { data: defaultRewardAllocation } = useDefaultRewardAllocation(); + + const { + data: queuedRewardAllocation, + isLoading: queuedRewardAllocationLoading, + refresh: refreshQueuedRewardAllocation, + } = useValidatorQueuedRewardAllocation(validatorPublicKey); + + const isQueuedRewardAllocation = useMemo( + () => + queuedRewardAllocation?.weights && + queuedRewardAllocation?.weights.length > 0 && + !queuedRewardAllocationLoading, + [queuedRewardAllocation, queuedRewardAllocationLoading], + ); + + const { + write, + isLoading: isApplyingQueuedRewardAllocation, + ModalPortal, + } = useTxn({ + message: "Applying Reward Allocation", + actionType: TransactionActionType.APPLYING_REWARD_ALLOCATION, + onSuccess: () => { + refreshQueuedRewardAllocation(); + }, + }); + + const { data: rewardAllocation, isLoading: rewardAllocationLoading } = + useValidatorRewardAllocation(validatorPublicKey, { + opts: { revalidateOnMount: true }, + }); + + const [error, setError] = useState(""); + const rgbColorPalette = [ + "rgb(248 113 113)", // bg-red-400 + "rgb(96 165 250)", // bg-blue-400 + "rgb(251 146 60)", // bg-orange-400 + "rgb(167 139 250)", // bg-violet-400 + "rgb(250 204 21)", // bg-yellow-400 + "rgb(74 222 128)", // bg-green-400 + "rgb(56 189 248)", // bg-sky-400 + "rgb(45 212 191)", // bg-teal-400 + "rgb(255 210 204)", // bg-pink-400 + "rgb(156 163 175)", // bg-gray-400 + ]; + + useEffect(() => { + if (rewardAllocation?.weights) { + setVaults( + rewardAllocation.weights.map((weight) => ({ + address: weight.receiver.toLowerCase(), + distribution: Number(weight.percentageNumerator) / 100, + id: uuidv4(), + })), + ); + } + }, [rewardAllocation]); + + const vaultsData = useMemo(() => { + return rewardVaults?.vaults?.map((vault) => ({ + vaultAddress: vault.vaultAddress.toLowerCase(), + stakingTokenAddress: vault.stakingToken?.address?.toLowerCase(), + symbol: vault.stakingToken.symbol, + name: vault.stakingToken.name, + })); + }, [rewardVaults]); + + const handleAddVault = () => { + setVaults([...vaults, { address: "", distribution: 0, id: uuidv4() }]); + }; + + const handleDeleteVault = (index: number) => { + if (vaults.length === 1) return; + setVaults(vaults.filter((_, i) => i !== index)); + }; + + const handleVaultChange = ( + index: number, + field: "address" | "distribution", + value: string | number, + ) => { + const updatedVaults = vaults.map((vault, i) => { + if (i === index) { + return { ...vault, [field]: value }; + } + return vault; + }); + setVaults(updatedVaults); + }; + + const totalDistributionBN = vaults.reduce((sum, vault) => { + return BigNumber(vault.distribution).plus(sum); + }, BigNumber(0)); + const totalDistribution = totalDistributionBN.isNaN() + ? "" + : totalDistributionBN.toNumber(); + + const handleDistributeVaults = () => { + const baseDistribution = Math.floor((100 * 100) / vaults.length) / 100; // Get base with 2 decimal places + const remainder = 100 - baseDistribution * vaults.length; + + const updatedVaults = vaults.map((vault, index) => { + // Add the remainder to the first vault to ensure total is exactly 100 + const distribution = + index === 0 ? baseDistribution + remainder : baseDistribution; + return { ...vault, distribution: parseFloat(distribution.toFixed(2)) }; + }); + + setVaults(updatedVaults); + }; + + const handleQueue = () => { + const formattedVaults = vaults.map((vault) => ({ + receiver: vault.address as Address, + percentageNumerator: BigInt(vault.distribution * 100), + })); + if (!blockNumber || !rewardAllocationBlockDelay) return; + write({ + address: beraChefAddress, + abi: BERA_CHEF_ABI, + functionName: "queueNewRewardAllocation", + params: [ + validatorPublicKey, + (blockNumber || 0n) + + (rewardAllocationBlockDelay || 0n) + + USER_BLOCK_DELAY, + formattedVaults, + ], + }); + }; + + const handleResetToDefault = () => { + setVaults( + defaultRewardAllocation?.weights.map((weight) => ({ + address: weight.receiver.toLowerCase(), + distribution: Number(weight.percentageNumerator) / 100, + id: uuidv4(), + })) || [], + ); + }; + + const pieChartData = useMemo(() => { + return { + labels: vaults.map( + (vault) => + vaultsData?.find((item) => item.vaultAddress === vault.address) + ?.name || "Unassigned", + ), + datasets: [ + { + data: vaults.map((vault) => vault.distribution), + hoverBorderWidth: 5, + borderRadius: 8, + spacing: 5, + borderWidth: 0, + backgroundColor: vaults.map((vault, index) => rgbColorPalette[index]), + hoverBorderColor: vaults.map( + (vault, index) => rgbColorPalette[index], + ), + }, + ], + }; + }, [vaults, vaultsData]); + + const pieChartOptions = { + responsive: true, + cutout: "70%", + radius: "95%", + plugins: { + legend: { + display: false, + }, + tooltip: { + backgroundColor: "rgba(0, 0, 0, 1)", + cornerRadius: 6, + interaction: { + intersect: true, + }, + callbacks: { + label: (context: any) => { + const label = context.label || ""; + const value = context.parsed || 0; + return `${label}: ${value}%`; + }, + }, + }, + }, + }; + + useEffect(() => { + const hasEmptyAddress = vaults.some((vault) => vault.address === ""); + const hasEmptyDistribution = vaults.some( + (vault) => vault.distribution === 0, + ); + if (totalDistribution !== 100) { + setError("Total distribution must equal 100%"); + } else if (hasEmptyAddress) { + setError("All vaults must have an address"); + } else if (hasEmptyDistribution) { + setError("All vaults must have a distribution"); + } else { + setError(""); + } + }, [totalDistribution, vaults, setError]); + + return ( + + {ModalPortal} + + Berachef Weight + + Configure your reward vaults distribution weighting + +
+ {/* Vaults Addresses */} +
+ {vaults.map((vault, index) => ( +
+
+
+ Reward Vault Address +
+ {isLoading || rewardAllocationLoading ? ( + + ) : ( + ({ + value: vaultData.vaultAddress, + label: `${vaultData.symbol} - ${vaultData.name}`, + })) || [] + } + value={vaults[index].address} + selectedItems={vaults.map((vault) => vault.address)} + disabled={isQueuedRewardAllocation} + onSelect={(selectedValue) => + handleVaultChange(index, "address", selectedValue) + } + /> + )} +
+
+
Distribution
+ { + let value = e.target.value; + if (value.includes(".")) { + const [integerPart, decimalPart] = value.split("."); + value = `${integerPart}.${decimalPart.slice(0, 2)}`; + } + handleVaultChange( + index, + "distribution", + parseFloat(value), + ); + }} + placeholder="Distribution" + outerClassName="w-fit h-[42px]" + className={cn( + "h-[42px] w-32 text-right xl:w-48", + vault.distribution > 100 && "text-destructive-foreground", + )} + onKeyDown={(e) => + (e.key === "-" || e.key === "e" || e.key === "E") && + e.preventDefault() + } + maxLength={3} + min={0} + max={100} + endAdornment={ + + % + + } + /> +
+ +
+ ))} +
+
+ + +
+
+ {/* Vaults Weight Chart */} +
+
+ Current Vaults Weight +
+
+ +
+
+
+ Total +
+
+ {totalDistribution}% +
+
+
+ Total Distribution: {totalDistribution}% + {error && {error}} +
+
+
+ + + {isQueuedRewardAllocation && ( + + Unable to be modified when there is a current queued reward + allocation + + )} + + + + + ); +}; diff --git a/apps/hub/src/app/validators/components/validator-banner.tsx b/apps/hub/src/app/validators/components/validator-banner.tsx index 07a580c68..16b4aa532 100644 --- a/apps/hub/src/app/validators/components/validator-banner.tsx +++ b/apps/hub/src/app/validators/components/validator-banner.tsx @@ -1,23 +1,34 @@ import Link from "next/link"; -import { truncateHash, useBeraJs, useSelectedValidator } from "@bera/berajs"; +import { truncateHash, useBeraJs, useValidatorByOperator } from "@bera/berajs"; import { ValidatorIcon } from "@bera/shared-ui"; import { getHubValidatorPath } from "@bera/shared-ui"; import { Icons } from "@bera/ui/icons"; export const ValidatorBanner = () => { const { account, isReady } = useBeraJs(); - const { data: validator, isLoading } = useSelectedValidator(account ?? "0x"); - if (!isReady || !account || isLoading || !validator) return <>; + const { data: validator, isLoading } = useValidatorByOperator( + account ?? "0x", + ); + if ( + !isReady || + !account || + isLoading || + !validator || + validator?.validators.length === 0 + ) + return <>; return ( -
+
{" "} Connected as {" "} - {validator?.metadata?.name} ({truncateHash(account)}) + {"Public Key:"}{" "} + {truncateHash(validator?.validators[0]?.publicKey ?? "0x")}{" "} + {" | Operator: "} ({truncateHash(account)})
Manage as a validator diff --git a/apps/hub/src/app/validators/components/validator-configuration.tsx b/apps/hub/src/app/validators/components/validator-configuration.tsx index e2c7efdc9..ebc6e5e91 100644 --- a/apps/hub/src/app/validators/components/validator-configuration.tsx +++ b/apps/hub/src/app/validators/components/validator-configuration.tsx @@ -1,72 +1,32 @@ -import { useCallback, useState } from "react"; -import { cn } from "@bera/ui"; -import { Button } from "@bera/ui/button"; +import { useCallback } from "react"; import { Card } from "@bera/ui/card"; -import { Icons } from "@bera/ui/icons"; -import { Input } from "@bera/ui/input"; import { Address } from "viem"; -import { CuttingBoardConfiguration } from "./cutting-board-configuration"; - +import { QueuedRewardAllocationConfiguration } from "./queued-reward-allocation-configuration"; +import { RewardAllocationConfiguration } from "./reward-allocation-configuration"; +import { GeneralSettings } from "./general-settings"; export const ValidatorConfiguration = ({ - validatorAddress, + validatorPublicKey, }: { - validatorAddress: Address; + validatorPublicKey: Address; }) => { - const [operatorAddress, setOperatorAddress] = useState(""); - const [commission, setCommission] = useState("0"); - - const handleSaveSettings = useCallback(() => { - console.log("changing settings", validatorAddress); - }, []); - const handleUpdateMetadata = useCallback(() => { - console.log("updating metadata", validatorAddress); + console.log("updating metadata", validatorPublicKey); }, []); return (
- - Berachef Weight - - Configure your reward vaults distribution weighting - - - - - General Settings - - Configure your operator address & commission - - Operator Address - setOperatorAddress(e.target.value)} - /> - Commission - setCommission(e.target.value)} - /> -
- - + + + Update your metadata Configure and modify your validator metadata
{"Coming Soon"} diff --git a/apps/hub/src/app/validators/components/validator-portal-status.tsx b/apps/hub/src/app/validators/components/validator-portal-status.tsx index 12990889e..fbb260d50 100644 --- a/apps/hub/src/app/validators/components/validator-portal-status.tsx +++ b/apps/hub/src/app/validators/components/validator-portal-status.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { truncateHash, useBeraJs, useSelectedValidator } from "@bera/berajs"; +import { truncateHash, useBeraJs, useValidatorByOperator } from "@bera/berajs"; import { ActionButton } from "@bera/shared-ui"; import { Icons } from "@bera/ui/icons"; export const ValidatorPortalStatus = () => { const { account, isReady } = useBeraJs(); - const { data: validator } = useSelectedValidator(account ?? "0x"); - if (validator) return <>; + const { data: validator } = useValidatorByOperator(account ?? "0x"); + if (validator?.validators[0]?.publicKey) return <>; return (
diff --git a/apps/hub/src/app/validators/components/validator-tabs.tsx b/apps/hub/src/app/validators/components/validator-tabs.tsx index 327275a7a..5b5e7c11f 100644 --- a/apps/hub/src/app/validators/components/validator-tabs.tsx +++ b/apps/hub/src/app/validators/components/validator-tabs.tsx @@ -1,5 +1,9 @@ -import { useState } from "react"; -import { useBeraJs, type Validator } from "@bera/berajs"; +import { useEffect, useState } from "react"; +import { + useBeraJs, + useValidatorByOperator, + type Validator, +} from "@bera/berajs"; import { Select, SelectContent, @@ -13,28 +17,48 @@ import { ValidatorOverview } from "../validator/validator-overview"; import { ValidatorPolData } from "../validator/validator-pol-data"; import { ValidatorAnalytics } from "./validator-analytics"; import { ValidatorConfiguration } from "./validator-configuration"; -import { ValidatorEvents } from "./validator-events"; + +// import { ValidatorEvents } from "./validator-events"; + +type ValidatorTabValue = "overview" | "configuration" | "analytics" | "events"; export const ValidatorTabs = ({ validator }: { validator: Validator }) => { const { account } = useBeraJs(); + const { data } = useValidatorByOperator(account ?? "0x"); const isValidatorWallet = - account?.toLowerCase() === validator.operator.toLowerCase(); + data?.validators[0]?.publicKey === validator.coinbase; + const [dayRange, setDayRange] = useState("30"); + const [activeTab, setActiveTab] = useState("overview"); + + useEffect(() => { + if (activeTab === "configuration" && !isValidatorWallet) { + setActiveTab("overview"); + } + }, [isValidatorWallet, activeTab]); return ( - + + setActiveTab(value as ValidatorTabValue) + } + defaultValue="overview" + >
Overview - {/* TODO: Uncomment this when we have a working contract for configuration */} - {/* {isValidatorWallet && ( + {isValidatorWallet && ( Configuration - )} */} + )} Analytics {/* Events */} - setDayRange(value)} + > @@ -67,7 +91,9 @@ export const ValidatorTabs = ({ validator }: { validator: Validator }) => { - + { const { data: tokenHoneyPrices } = useTokenHoneyPrices({ tokenAddresses: activeIncentivesTokens.map( - (t: Token) => t.address, + (t: Token) => t?.address, ) as Address[], }); const returnPerBgt: number = activeIncentivesArray?.reduce( (acc: number, ab: ActiveIncentiveWithVault) => { const tokenPrice = parseFloat( - tokenHoneyPrices?.[ab.token.address] ?? "0", + tokenHoneyPrices?.[ab?.token.address] ?? "0", ); - return acc + ab.incentiveRate * tokenPrice; + return acc + ab?.incentiveRate * tokenPrice; }, 0, ); diff --git a/apps/hub/src/app/validators/validator/validator.tsx b/apps/hub/src/app/validators/validator/validator.tsx index f60de91d5..58c75a858 100755 --- a/apps/hub/src/app/validators/validator/validator.tsx +++ b/apps/hub/src/app/validators/validator/validator.tsx @@ -2,7 +2,7 @@ import React from "react"; import { notFound } from "next/navigation"; -import { useSelectedValidator, useValidator } from "@bera/berajs"; +import { useValidator } from "@bera/berajs"; import { type Address } from "viem"; import { ValidatorTabs } from "../components/validator-tabs"; diff --git a/packages/berajs/src/abi/pol/BeaconDeposit.ts b/packages/berajs/src/abi/pol/BeaconDeposit.ts index 4bf940371..bf97c4764 100644 --- a/packages/berajs/src/abi/pol/BeaconDeposit.ts +++ b/packages/berajs/src/abi/pol/BeaconDeposit.ts @@ -1,257 +1,595 @@ export const beaconDepositAbi = [ - { inputs: [], type: "error", name: "AlreadyInitialized" }, - { inputs: [], type: "error", name: "AmountLessThanMinIncentiveRate" }, - { inputs: [], type: "error", name: "CannotRecoverIncentiveToken" }, - { inputs: [], type: "error", name: "CannotRecoverRewardToken" }, - { inputs: [], type: "error", name: "CannotRecoverStakingToken" }, - { inputs: [], type: "error", name: "DepositNotMultipleOfGwei" }, - { inputs: [], type: "error", name: "DepositValueTooHigh" }, - { inputs: [], type: "error", name: "DonateAmountLessThanPayoutAmount" }, - { inputs: [], type: "error", name: "IncentiveRateTooHigh" }, - { inputs: [], type: "error", name: "IndexOutOfRange" }, - { inputs: [], type: "error", name: "InsolventReward" }, - { inputs: [], type: "error", name: "InsufficientDelegateStake" }, - { inputs: [], type: "error", name: "InsufficientDeposit" }, - { inputs: [], type: "error", name: "InsufficientSelfStake" }, - { inputs: [], type: "error", name: "InsufficientStake" }, - { inputs: [], type: "error", name: "InvalidActivateBoostDelay" }, - { inputs: [], type: "error", name: "InvalidBaseRate" }, - { inputs: [], type: "error", name: "InvalidBoostMultiplier" }, - { inputs: [], type: "error", name: "InvalidCredentialsLength" }, - { inputs: [], type: "error", name: "InvalidDropBoostDelay" }, - { inputs: [], type: "error", name: "InvalidMaxIncentiveTokensCount" }, - { inputs: [], type: "error", name: "InvalidMinBoostedRewardRate" }, - { inputs: [], type: "error", name: "InvalidProof" }, - { inputs: [], type: "error", name: "InvalidPubKeyLength" }, - { inputs: [], type: "error", name: "InvalidRewardAllocationWeights" }, - { inputs: [], type: "error", name: "InvalidRewardConvexity" }, - { inputs: [], type: "error", name: "InvalidRewardRate" }, - { inputs: [], type: "error", name: "InvalidRewardVaultAddress" }, - { inputs: [], type: "error", name: "InvalidSignatureLength" }, - { inputs: [], type: "error", name: "InvalidStartBlock" }, { inputs: [], + name: "AlreadyInitialized", type: "error", + }, + { + inputs: [], + name: "AmountLessThanMinIncentiveRate", + type: "error", + }, + { + inputs: [], + name: "CannotRecoverIncentiveToken", + type: "error", + }, + { + inputs: [], + name: "CannotRecoverRewardToken", + type: "error", + }, + { + inputs: [], + name: "CannotRecoverStakingToken", + type: "error", + }, + { + inputs: [], + name: "DepositNotMultipleOfGwei", + type: "error", + }, + { + inputs: [], + name: "DepositValueTooHigh", + type: "error", + }, + { + inputs: [], + name: "DonateAmountLessThanPayoutAmount", + type: "error", + }, + { + inputs: [], + name: "IncentiveRateTooHigh", + type: "error", + }, + { + inputs: [], + name: "IndexOutOfRange", + type: "error", + }, + { + inputs: [], + name: "InsolventReward", + type: "error", + }, + { + inputs: [], + name: "InsufficientDelegateStake", + type: "error", + }, + { + inputs: [], + name: "InsufficientDeposit", + type: "error", + }, + { + inputs: [], + name: "InsufficientSelfStake", + type: "error", + }, + { + inputs: [], + name: "InsufficientStake", + type: "error", + }, + { + inputs: [], + name: "InvalidActivateBoostDelay", + type: "error", + }, + { + inputs: [], + name: "InvalidBaseRate", + type: "error", + }, + { + inputs: [], + name: "InvalidBoostMultiplier", + type: "error", + }, + { + inputs: [], + name: "InvalidCredentialsLength", + type: "error", + }, + { + inputs: [], + name: "InvalidDropBoostDelay", + type: "error", + }, + { + inputs: [], + name: "InvalidMaxIncentiveTokensCount", + type: "error", + }, + { + inputs: [], + name: "InvalidMinBoostedRewardRate", + type: "error", + }, + { + inputs: [], + name: "InvalidProof", + type: "error", + }, + { + inputs: [], + name: "InvalidPubKeyLength", + type: "error", + }, + { + inputs: [], + name: "InvalidRewardAllocationWeights", + type: "error", + }, + { + inputs: [], + name: "InvalidRewardConvexity", + type: "error", + }, + { + inputs: [], + name: "InvalidRewardRate", + type: "error", + }, + { + inputs: [], + name: "InvalidRewardVaultAddress", + type: "error", + }, + { + inputs: [], + name: "InvalidSignatureLength", + type: "error", + }, + { + inputs: [], + name: "InvalidStartBlock", + type: "error", + }, + { + inputs: [], name: "InvalidateDefaultRewardAllocation", + type: "error", }, - { inputs: [], type: "error", name: "InvariantCheckFailed" }, { inputs: [], + name: "InvariantCheckFailed", type: "error", + }, + { + inputs: [], name: "MaxNumWeightsPerRewardAllocationIsZero", + type: "error", + }, + { + inputs: [], + name: "MinIncentiveRateIsZero", + type: "error", + }, + { + inputs: [], + name: "NotAContract", + type: "error", + }, + { + inputs: [], + name: "NotApprovedSender", + type: "error", + }, + { + inputs: [], + name: "NotBGT", + type: "error", + }, + { + inputs: [], + name: "NotBlockRewardController", + type: "error", + }, + { + inputs: [], + name: "NotDelegate", + type: "error", + }, + { + inputs: [], + name: "NotDistributor", + type: "error", + }, + { + inputs: [], + name: "NotEnoughBalance", + type: "error", + }, + { + inputs: [], + name: "NotEnoughBoostedBalance", + type: "error", + }, + { + inputs: [], + name: "NotEnoughTime", + type: "error", + }, + { + inputs: [], + name: "NotFactoryVault", + type: "error", + }, + { + inputs: [], + name: "NotFeeCollector", + type: "error", + }, + { + inputs: [], + name: "NotGovernance", + type: "error", + }, + { + inputs: [], + name: "NotIncentiveManager", + type: "error", + }, + { + inputs: [], + name: "NotNewOperator", + type: "error", + }, + { + inputs: [], + name: "NotOperator", + type: "error", + }, + { + inputs: [], + name: "NotRootFollower", + type: "error", + }, + { + inputs: [], + name: "NotWhitelistedVault", + type: "error", + }, + { + inputs: [], + name: "OperatorAlreadySet", + type: "error", }, - { inputs: [], type: "error", name: "MaxTokensPerAccountReached" }, - { inputs: [], type: "error", name: "MinIncentiveRateIsZero" }, - { inputs: [], type: "error", name: "NotAContract" }, - { inputs: [], type: "error", name: "NotApprovedSender" }, - { inputs: [], type: "error", name: "NotBGT" }, - { inputs: [], type: "error", name: "NotBlockRewardController" }, - { inputs: [], type: "error", name: "NotDelegate" }, - { inputs: [], type: "error", name: "NotDistributor" }, - { inputs: [], type: "error", name: "NotEnoughBalance" }, - { inputs: [], type: "error", name: "NotEnoughBoostedBalance" }, - { inputs: [], type: "error", name: "NotEnoughTime" }, - { inputs: [], type: "error", name: "NotFactoryVault" }, - { inputs: [], type: "error", name: "NotFeeCollector" }, - { inputs: [], type: "error", name: "NotGovernance" }, - { inputs: [], type: "error", name: "NotIncentiveManager" }, - { inputs: [], type: "error", name: "NotNewOperator" }, - { inputs: [], type: "error", name: "NotOperator" }, - { inputs: [], type: "error", name: "NotRootFollower" }, - { inputs: [], type: "error", name: "NotWhitelistedVault" }, - { inputs: [], type: "error", name: "OperatorAlreadySet" }, - { inputs: [], type: "error", name: "PayoutAmountIsZero" }, - { inputs: [], type: "error", name: "RewardAllocationAlreadyQueued" }, { inputs: [], + name: "PayoutAmountIsZero", type: "error", + }, + { + inputs: [], + name: "RewardAllocationAlreadyQueued", + type: "error", + }, + { + inputs: [], name: "RewardAllocationBlockDelayTooLarge", + type: "error", + }, + { + inputs: [], + name: "RewardCycleNotEnded", + type: "error", }, - { inputs: [], type: "error", name: "RewardCycleNotEnded" }, - { inputs: [], type: "error", name: "RewardsDurationIsZero" }, - { inputs: [], type: "error", name: "StakeAmountIsZero" }, - { inputs: [], type: "error", name: "TimestampAlreadyProcessed" }, { inputs: [], + name: "RewardsDurationIsZero", type: "error", + }, + { + inputs: [], + name: "StakeAmountIsZero", + type: "error", + }, + { + inputs: [], + name: "TimestampAlreadyProcessed", + type: "error", + }, + { + inputs: [], name: "TokenAlreadyWhitelistedOrLimitReached", + type: "error", + }, + { + inputs: [], + name: "TokenNotWhitelisted", + type: "error", + }, + { + inputs: [], + name: "TooManyWeights", + type: "error", + }, + { + inputs: [], + name: "TotalSupplyOverflow", + type: "error", + }, + { + inputs: [], + name: "VaultAlreadyExists", + type: "error", + }, + { + inputs: [], + name: "WithdrawAmountIsZero", + type: "error", + }, + { + inputs: [], + name: "ZeroAddress", + type: "error", + }, + { + inputs: [], + name: "ZeroOperatorOnFirstDeposit", + type: "error", }, - { inputs: [], type: "error", name: "TokenNotInList" }, - { inputs: [], type: "error", name: "TokenNotWhitelisted" }, - { inputs: [], type: "error", name: "TooManyWeights" }, - { inputs: [], type: "error", name: "TotalSupplyOverflow" }, - { inputs: [], type: "error", name: "VaultAlreadyExists" }, - { inputs: [], type: "error", name: "WithdrawAmountIsZero" }, - { inputs: [], type: "error", name: "ZeroAddress" }, - { inputs: [], type: "error", name: "ZeroOperatorOnFirstDeposit" }, - { inputs: [], type: "error", name: "ZeroPercentageWeight" }, { + inputs: [], + name: "ZeroPercentageWeight", + type: "error", + }, + { + anonymous: false, inputs: [ { + indexed: false, internalType: "bytes", name: "pubkey", type: "bytes", - indexed: false, }, { + indexed: false, internalType: "bytes", name: "credentials", type: "bytes", - indexed: false, }, { + indexed: false, internalType: "uint64", name: "amount", type: "uint64", - indexed: false, }, { + indexed: false, internalType: "bytes", name: "signature", type: "bytes", - indexed: false, }, { + indexed: false, internalType: "uint64", name: "index", type: "uint64", - indexed: false, }, ], - type: "event", name: "Deposit", - anonymous: false, + type: "event", }, { + anonymous: false, inputs: [ { + indexed: true, internalType: "bytes", name: "pubkey", type: "bytes", - indexed: true, }, ], - type: "event", name: "OperatorChangeCancelled", - anonymous: false, + type: "event", }, { + anonymous: false, inputs: [ { + indexed: true, internalType: "bytes", name: "pubkey", type: "bytes", - indexed: true, }, { + indexed: false, internalType: "address", name: "queuedOperator", type: "address", - indexed: false, }, { + indexed: false, internalType: "address", name: "currentOperator", type: "address", - indexed: false, }, { + indexed: false, internalType: "uint256", name: "queuedTimestamp", type: "uint256", - indexed: false, }, ], - type: "event", name: "OperatorChangeQueued", - anonymous: false, + type: "event", }, { + anonymous: false, inputs: [ { + indexed: true, internalType: "bytes", name: "pubkey", type: "bytes", - indexed: true, }, { + indexed: false, internalType: "address", name: "newOperator", type: "address", - indexed: false, }, { + indexed: false, internalType: "address", name: "previousOperator", type: "address", - indexed: false, }, ], - type: "event", name: "OperatorUpdated", - anonymous: false, + type: "event", }, { - inputs: [{ internalType: "bytes", name: "pubkey", type: "bytes" }], + inputs: [ + { + internalType: "bytes", + name: "pubkey", + type: "bytes", + }, + ], + name: "acceptOperatorChange", + outputs: [], stateMutability: "nonpayable", type: "function", - name: "acceptOperatorChange", }, { - inputs: [{ internalType: "bytes", name: "pubkey", type: "bytes" }], + inputs: [ + { + internalType: "bytes", + name: "pubkey", + type: "bytes", + }, + ], + name: "cancelOperatorChange", + outputs: [], stateMutability: "nonpayable", type: "function", - name: "cancelOperatorChange", }, { inputs: [ - { internalType: "bytes", name: "pubkey", type: "bytes" }, - { internalType: "bytes", name: "credentials", type: "bytes" }, - { internalType: "bytes", name: "signature", type: "bytes" }, - { internalType: "address", name: "operator", type: "address" }, + { + internalType: "bytes", + name: "pubkey", + type: "bytes", + }, + { + internalType: "bytes", + name: "credentials", + type: "bytes", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + { + internalType: "address", + name: "operator", + type: "address", + }, ], + name: "deposit", + outputs: [], stateMutability: "payable", type: "function", - name: "deposit", }, { inputs: [], + name: "depositCount", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], stateMutability: "view", type: "function", - name: "depositCount", - outputs: [{ internalType: "uint64", name: "", type: "uint64" }], }, { - inputs: [{ internalType: "bytes", name: "pubkey", type: "bytes" }], + inputs: [ + { + internalType: "bytes", + name: "pubkey", + type: "bytes", + }, + ], + name: "getOperator", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], stateMutability: "view", type: "function", - name: "getOperator", - outputs: [{ internalType: "address", name: "", type: "address" }], }, { - inputs: [{ internalType: "bytes", name: "", type: "bytes" }], - stateMutability: "view", - type: "function", + inputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], name: "queuedOperator", outputs: [ - { internalType: "uint96", name: "queuedTimestamp", type: "uint96" }, - { internalType: "address", name: "newOperator", type: "address" }, + { + internalType: "uint96", + name: "queuedTimestamp", + type: "uint96", + }, + { + internalType: "address", + name: "newOperator", + type: "address", + }, ], + stateMutability: "view", + type: "function", }, { inputs: [ - { internalType: "bytes", name: "pubkey", type: "bytes" }, - { internalType: "address", name: "newOperator", type: "address" }, + { + internalType: "bytes", + name: "pubkey", + type: "bytes", + }, + { + internalType: "address", + name: "newOperator", + type: "address", + }, ], + name: "requestOperatorChange", + outputs: [], stateMutability: "nonpayable", type: "function", - name: "requestOperatorChange", }, { - inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], stateMutability: "pure", type: "function", - name: "supportsInterface", - outputs: [{ internalType: "bool", name: "", type: "bool" }], }, ] as const; diff --git a/packages/berajs/src/actions/pol/getDefaultRewardAllocation.ts b/packages/berajs/src/actions/pol/getDefaultRewardAllocation.ts new file mode 100644 index 000000000..31866c0e3 --- /dev/null +++ b/packages/berajs/src/actions/pol/getDefaultRewardAllocation.ts @@ -0,0 +1,34 @@ +import { beraChefAddress } from "@bera/config"; +import { PublicClient } from "viem"; + +import { BERA_CHEF_ABI } from "~/abi"; +import { BeraConfig } from "~/types"; + +export interface DefaultRewardAllocation { + startBlock: bigint; + weights: readonly { receiver: `0x${string}`; percentageNumerator: bigint }[]; +} + +export const getDefaultRewardAllocation = async ({ + client, + config, +}: { + client: PublicClient; + config: BeraConfig; +}): Promise => { + try { + if (!beraChefAddress) + throw new Error("missing contract address beraChefAddress"); + + const result = await client.readContract({ + address: beraChefAddress, + abi: BERA_CHEF_ABI, + functionName: "getDefaultRewardAllocation", + args: [], + }); + return result; + } catch (e) { + console.log("getRewardAllocationBlockDelay:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getRewardAllocationBlockDelay.ts b/packages/berajs/src/actions/pol/getRewardAllocationBlockDelay.ts new file mode 100644 index 000000000..8a93cedb4 --- /dev/null +++ b/packages/berajs/src/actions/pol/getRewardAllocationBlockDelay.ts @@ -0,0 +1,28 @@ +import { PublicClient } from "viem"; +import { beraChefAddress } from "@bera/config"; +import { BERA_CHEF_ABI } from "~/abi"; +import { BeraConfig } from "~/types"; + +export const getRewardAllocationBlockDelay = async ({ + client, + config, +}: { + client: PublicClient; + config: BeraConfig; +}): Promise => { + try { + if (!beraChefAddress) + throw new Error("missing contract address beraChefAddress"); + + const result = await client.readContract({ + address: beraChefAddress, + abi: BERA_CHEF_ABI, + functionName: "rewardAllocationBlockDelay", + args: [], + }); + return result; + } catch (e) { + console.log("getRewardAllocationBlockDelay:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getValidatorByOperator.ts b/packages/berajs/src/actions/pol/getValidatorByOperator.ts new file mode 100644 index 000000000..40797151f --- /dev/null +++ b/packages/berajs/src/actions/pol/getValidatorByOperator.ts @@ -0,0 +1,30 @@ +import { bgtClient } from "@bera/graphql"; +import { + GetValidatorByOperator, + type GetValidatorByOperatorQuery, +} from "@bera/graphql/pol"; +import { Address } from "viem"; + +import { type BeraConfig } from "~/types"; + +export const getValidatorByOperator = async ({ + config, + address, +}: { + config: BeraConfig; + address: Address; +}): Promise => { + try { + if (!config.subgraphs?.polSubgraph) { + throw new Error("pol subgraph uri is not found in config"); + } + const result = await bgtClient.query({ + query: GetValidatorByOperator, + variables: { operator: address.toLowerCase() }, + }); + return result.data; + } catch (e) { + console.error("getValidatorByOperator:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getValidatorOperatorAddress.ts b/packages/berajs/src/actions/pol/getValidatorOperatorAddress.ts new file mode 100644 index 000000000..d3f96357f --- /dev/null +++ b/packages/berajs/src/actions/pol/getValidatorOperatorAddress.ts @@ -0,0 +1,32 @@ +import { Address, PublicClient } from "viem"; +import { depositContractAddress } from "@bera/config"; +import { beaconDepositAbi } from "~/abi"; +import { BeraConfig } from "~/types"; + +export type ValidatorOperatorAddress = Address; + +export const getValidatorOperatorAddress = async ({ + client, + config, + pubKey, +}: { + client: PublicClient; + config: BeraConfig; + pubKey: Address; +}): Promise => { + try { + if (!depositContractAddress) + throw new Error("missing contract address depositContractAddress"); + + const result = await client.readContract({ + address: depositContractAddress, + abi: beaconDepositAbi, + functionName: "getOperator", + args: [pubKey], + }); + return result as ValidatorOperatorAddress; + } catch (e) { + console.log("getValidatorOperatorAddress:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getValidatorQueuedOperatorAddress.ts b/packages/berajs/src/actions/pol/getValidatorQueuedOperatorAddress.ts new file mode 100644 index 000000000..d92ad911c --- /dev/null +++ b/packages/berajs/src/actions/pol/getValidatorQueuedOperatorAddress.ts @@ -0,0 +1,32 @@ +import { Address, PublicClient } from "viem"; +import { depositContractAddress } from "@bera/config"; +import { beaconDepositAbi } from "~/abi"; +import { BeraConfig } from "~/types"; + +export type ValidatorQueuedOperatorAddress = [bigint, string]; + +export const getValidatorQueuedOperatorAddress = async ({ + client, + config, + pubKey, +}: { + client: PublicClient; + config: BeraConfig; + pubKey: Address; +}): Promise => { + try { + if (!depositContractAddress) + throw new Error("missing contract address depositContractAddress"); + + const result = await client.readContract({ + address: depositContractAddress, + abi: beaconDepositAbi, + functionName: "queuedOperator", + args: [pubKey], + }); + return result as ValidatorQueuedOperatorAddress; + } catch (e) { + console.log("getValidatorQueuedOperatorAddress:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getValidatorQueuedRewardAllocation.ts b/packages/berajs/src/actions/pol/getValidatorQueuedRewardAllocation.ts new file mode 100644 index 000000000..40572869d --- /dev/null +++ b/packages/berajs/src/actions/pol/getValidatorQueuedRewardAllocation.ts @@ -0,0 +1,38 @@ +import { Address, PublicClient } from "viem"; +import { beraChefAddress } from "@bera/config"; +import { BERA_CHEF_ABI } from "~/abi"; +import { BeraConfig } from "~/types"; + +export interface ValidatorQueuedRewardAllocation { + startBlock: bigint; + weights: readonly { + receiver: `0x${string}`; + percentageNumerator: bigint; + }[]; +} + +export const getValidatorQueuedRewardAllocation = async ({ + client, + config, + pubKey, +}: { + client: PublicClient; + config: BeraConfig; + pubKey: Address; +}): Promise => { + try { + if (!beraChefAddress) + throw new Error("missing contract address beraChefAddress"); + + const result = await client.readContract({ + address: beraChefAddress, + abi: BERA_CHEF_ABI, + functionName: "getQueuedRewardAllocation", + args: [pubKey], + }); + return result; + } catch (e) { + console.log("getValidatorQueuedRewardAllocation:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/getValidatorRewardAllocation.ts b/packages/berajs/src/actions/pol/getValidatorRewardAllocation.ts new file mode 100644 index 000000000..06394fb7c --- /dev/null +++ b/packages/berajs/src/actions/pol/getValidatorRewardAllocation.ts @@ -0,0 +1,38 @@ +import { Address, PublicClient } from "viem"; +import { beraChefAddress } from "@bera/config"; +import { BERA_CHEF_ABI } from "~/abi"; +import { BeraConfig } from "~/types"; + +export interface ValidatorRewardAllocation { + startBlock: bigint; + weights: readonly { + receiver: `0x${string}`; + percentageNumerator: bigint; + }[]; +} + +export const getValidatorRewardAllocation = async ({ + client, + config, + pubKey, +}: { + client: PublicClient; + config: BeraConfig; + pubKey: Address; +}): Promise => { + try { + if (!beraChefAddress) + throw new Error("missing contract address beraChefAddress"); + + const result = await client.readContract({ + address: beraChefAddress, + abi: BERA_CHEF_ABI, + functionName: "getActiveRewardAllocation", + args: [pubKey], + }); + return result; + } catch (e) { + console.log("getValidatorRewardAllocation:", e); + throw e; + } +}; diff --git a/packages/berajs/src/actions/pol/index.ts b/packages/berajs/src/actions/pol/index.ts index 7345dcdb2..2497d4cb9 100644 --- a/packages/berajs/src/actions/pol/index.ts +++ b/packages/berajs/src/actions/pol/index.ts @@ -9,3 +9,11 @@ export * from "./get-validator-block-stats"; export * from "./get-validator-token-rewards"; export * from "./get-all-validators"; export * from "./get-reward-vault"; + +export * from "./getValidatorRewardAllocation"; +export * from "./getRewardAllocationBlockDelay"; +export * from "./getDefaultRewardAllocation"; +export * from "./getValidatorQueuedRewardAllocation"; +export * from "./getValidatorByOperator"; +export * from "./getValidatorQueuedOperatorAddress"; +export * from "./getValidatorOperatorAddress"; diff --git a/packages/berajs/src/enum/txnEnum.ts b/packages/berajs/src/enum/txnEnum.ts index 697be4598..ca5a38ddf 100755 --- a/packages/berajs/src/enum/txnEnum.ts +++ b/packages/berajs/src/enum/txnEnum.ts @@ -51,4 +51,6 @@ export enum TransactionActionType { // POL CREATE_REWARDS_VAULT = "Create Reward Vault", + APPLYING_REWARD_ALLOCATION = "Applying Reward Allocation", + APPLYING_OPERATOR_CHANGE = "Applying Operator Change", } diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useDefaultRewardAllocation.ts b/packages/berajs/src/hooks/modules/pol/hooks/useDefaultRewardAllocation.ts new file mode 100644 index 000000000..97041c008 --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useDefaultRewardAllocation.ts @@ -0,0 +1,39 @@ +import useSWR, { mutate } from "swr"; +import { usePublicClient } from "wagmi"; + +import { + DefaultRewardAllocation, + getDefaultRewardAllocation, +} from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useDefaultRewardAllocation = ( + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config ? ["useDefaultRewardAllocation"] : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + if (!publicClient) throw new Error("publicClient is not defined"); + if (!config) throw new Error("missing beraConfig"); + return await getDefaultRewardAllocation({ + config, + client: publicClient, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useRewardAllocationBlockDelay.ts b/packages/berajs/src/hooks/modules/pol/hooks/useRewardAllocationBlockDelay.ts new file mode 100644 index 000000000..727e71fee --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useRewardAllocationBlockDelay.ts @@ -0,0 +1,35 @@ +import useSWR, { mutate } from "swr"; +import { PublicClient } from "viem"; +import { usePublicClient } from "wagmi"; + +import { getRewardAllocationBlockDelay } from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useRewardAllocationBlockDelay = ( + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config ? ["useRewardAllocationBlockDelay"] : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + return await getRewardAllocationBlockDelay({ + config, + client: publicClient as PublicClient, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useValidatorByOperator.ts b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorByOperator.ts new file mode 100644 index 000000000..ea12a96bb --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorByOperator.ts @@ -0,0 +1,33 @@ +import { type GetValidatorByOperatorQuery } from "@bera/graphql/pol"; +import { mutate } from "swr"; +import useSWRImmutable from "swr/immutable"; +import { Address } from "viem"; + +import { getValidatorByOperator } from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useValidatorByOperator = ( + address: Address, + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + address && config ? ["useValidatorByOperator", address] : null; + + const swrResponse = useSWRImmutable( + QUERY_KEY, + async () => { + return await getValidatorByOperator({ config, address: address }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useValidatorOperatorAddress.ts b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorOperatorAddress.ts new file mode 100644 index 000000000..9e82cfdb2 --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorOperatorAddress.ts @@ -0,0 +1,42 @@ +import useSWR, { mutate } from "swr"; +import { Address, PublicClient } from "viem"; +import { usePublicClient } from "wagmi"; + +import { + ValidatorOperatorAddress, + getValidatorOperatorAddress, +} from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useValidatorOperatorAddress = ( + pubKey: Address, + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config && pubKey + ? ["useValidatorOperatorAddress", pubKey] + : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + return await getValidatorOperatorAddress({ + config, + client: publicClient as PublicClient, + pubKey: pubKey, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedOperatorAddress.ts b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedOperatorAddress.ts new file mode 100644 index 000000000..d92ec1784 --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedOperatorAddress.ts @@ -0,0 +1,42 @@ +import useSWR, { mutate } from "swr"; +import { Address, PublicClient } from "viem"; +import { usePublicClient } from "wagmi"; + +import { + ValidatorQueuedOperatorAddress, + getValidatorQueuedOperatorAddress, +} from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useValidatorQueuedOperatorAddress = ( + pubKey: Address, + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config && pubKey + ? ["useValidatorQueuedOperatorAddress", pubKey] + : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + return await getValidatorQueuedOperatorAddress({ + config, + client: publicClient as PublicClient, + pubKey: pubKey, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedRewardAllocation.ts b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedRewardAllocation.ts new file mode 100644 index 000000000..1317d7caf --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorQueuedRewardAllocation.ts @@ -0,0 +1,42 @@ +import useSWR, { mutate } from "swr"; +import { Address, PublicClient } from "viem"; +import { usePublicClient } from "wagmi"; + +import { + ValidatorQueuedRewardAllocation, + getValidatorQueuedRewardAllocation, +} from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useValidatorQueuedRewardAllocation = ( + pubKey: Address, + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config && pubKey + ? ["useValidatorQueuedRewardAllocation", pubKey] + : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + return await getValidatorQueuedRewardAllocation({ + config, + client: publicClient as PublicClient, + pubKey: pubKey, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/hooks/useValidatorRewardAllocation.ts b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorRewardAllocation.ts new file mode 100644 index 000000000..679224568 --- /dev/null +++ b/packages/berajs/src/hooks/modules/pol/hooks/useValidatorRewardAllocation.ts @@ -0,0 +1,42 @@ +import useSWR, { mutate } from "swr"; +import { Address, PublicClient } from "viem"; +import { usePublicClient } from "wagmi"; + +import { + ValidatorRewardAllocation, + getValidatorRewardAllocation, +} from "~/actions/pol"; +import { useBeraJs } from "~/contexts"; +import { DefaultHookOptions, DefaultHookReturnType } from "~/types"; + +export const useValidatorRewardAllocation = ( + pubKey: Address, + options?: DefaultHookOptions, +): DefaultHookReturnType => { + const { config: beraConfig } = useBeraJs(); + const publicClient = usePublicClient(); + const config = options?.beraConfigOverride ?? beraConfig; + const QUERY_KEY = + publicClient && config && pubKey + ? ["useValidatorRewardAllocation", pubKey] + : null; + + const swrResponse = useSWR( + QUERY_KEY, + async () => { + return await getValidatorRewardAllocation({ + config, + client: publicClient as PublicClient, + pubKey: pubKey, + }); + }, + { + ...options?.opts, + }, + ); + + return { + ...swrResponse, + refresh: () => mutate(QUERY_KEY), + }; +}; diff --git a/packages/berajs/src/hooks/modules/pol/index.ts b/packages/berajs/src/hooks/modules/pol/index.ts index b10b88c74..d72383db4 100644 --- a/packages/berajs/src/hooks/modules/pol/index.ts +++ b/packages/berajs/src/hooks/modules/pol/index.ts @@ -14,3 +14,11 @@ export * from "./hooks/useRewardVaultBalanceFromStakingToken"; export { useRewardVaultFromToken } from "./hooks/useRewardVaultFromToken"; export { usePollRewardVault } from "./hooks/poll-reward-vault"; export { useSubgraphUserValidators } from "./hooks/useSubgraphUserValidators"; + +export { useValidatorRewardAllocation } from "./hooks/useValidatorRewardAllocation"; +export { useRewardAllocationBlockDelay } from "./hooks/useRewardAllocationBlockDelay"; +export { useDefaultRewardAllocation } from "./hooks/useDefaultRewardAllocation"; +export { useValidatorQueuedRewardAllocation } from "./hooks/useValidatorQueuedRewardAllocation"; +export { useValidatorByOperator } from "./hooks/useValidatorByOperator"; +export { useValidatorQueuedOperatorAddress } from "./hooks/useValidatorQueuedOperatorAddress"; +export { useValidatorOperatorAddress } from "./hooks/useValidatorOperatorAddress"; diff --git a/packages/graphql/src/modules/pol/pol.graphql b/packages/graphql/src/modules/pol/pol.graphql index d8126a4dd..1f8e6cf33 100644 --- a/packages/graphql/src/modules/pol/pol.graphql +++ b/packages/graphql/src/modules/pol/pol.graphql @@ -192,7 +192,7 @@ query GetGauges { globalRewardAllocations(first: 1) { ...GlobalRewardAllocation } - vaults(first: 1000, where: { stakingTokenAmount_gt: "0" }) { + vaults(first: 1000, where: { isWhitelisted: true }) { ...VaultWithStakingToken } } @@ -219,3 +219,9 @@ query GetRewardVault($stakingToken: String) { ...VaultWithStakingToken } } + +query GetValidatorByOperator($operator: Bytes = "") { + validators(where: {operator: $operator}) { + publicKey + } +} \ No newline at end of file diff --git a/packages/shared-ui/src/combo-box.tsx b/packages/shared-ui/src/combo-box.tsx index 1eda98faa..2901f9ad4 100644 --- a/packages/shared-ui/src/combo-box.tsx +++ b/packages/shared-ui/src/combo-box.tsx @@ -16,15 +16,20 @@ import { Popover, PopoverContent, PopoverTrigger } from "@bera/ui/popover"; export function Combobox({ items, + selectedItems, onSelect, className, + value, + disabled, }: { items: { value: string; label: string }[]; + selectedItems: string[]; onSelect: (value: string) => void; className?: string; + value: string; + disabled?: boolean; }) { const [open, setOpen] = React.useState(false); - const [value, setValue] = React.useState(""); const [buttonWidth, setButtonWidth] = React.useState(0); const buttonRef = React.useRef(null); @@ -41,6 +46,7 @@ export function Combobox({ }, [updateButtonWidth]); const handleOpenChange = (newOpen: boolean) => { + if (disabled) return; if (newOpen) { updateButtonWidth(); } @@ -56,16 +62,21 @@ export function Combobox({ aria-expanded={open} size="sm" ref={buttonRef} + disabled={disabled} className={cn( - "w-full justify-between rounded-md border border-border p-3 font-semibold text-foreground", + "w-full min-w-[100px] justify-between rounded-sm text-sm border border-border p-2 text-foreground font-medium", className, )} > - - {value - ? items.find((item) => item.value === value)?.label - : "Select item..."} - + {value ? ( + + {items.find((item) => item.value === value)?.label} + + ) : ( + + Select Item... + + )} @@ -79,8 +90,10 @@ export function Combobox({ { - setValue(currentValue === value ? "" : currentValue); setOpen(false); onSelect(currentValue); // Call the onSelect function }} diff --git a/packages/shared-ui/src/table/v2/lib/components/core/table-row.tsx b/packages/shared-ui/src/table/v2/lib/components/core/table-row.tsx index 4e44a4dcf..5c2f99b02 100755 --- a/packages/shared-ui/src/table/v2/lib/components/core/table-row.tsx +++ b/packages/shared-ui/src/table/v2/lib/components/core/table-row.tsx @@ -10,6 +10,8 @@ export interface TableRowProps { flexTable?: boolean; onRowClick?: (row: any) => void; onRowHover?: (row: any) => void; + lastRowBorder?: boolean; + lastRow?: boolean; } export const TableRow: ( @@ -22,11 +24,16 @@ export const TableRow: ( className, children, flexTable, + lastRowBorder, + lastRow, }) => { return ( onRowClick?.(row as any)} onMouseOver={() => onRowHover?.(row as any)} data-state={row.getIsSelected() && "selected"} diff --git a/packages/shared-ui/src/table/v2/lib/simple-table/simple-table.tsx b/packages/shared-ui/src/table/v2/lib/simple-table/simple-table.tsx index 8f45502be..57102d50d 100755 --- a/packages/shared-ui/src/table/v2/lib/simple-table/simple-table.tsx +++ b/packages/shared-ui/src/table/v2/lib/simple-table/simple-table.tsx @@ -26,6 +26,7 @@ export type SimpleTableProps = TableBodyProps & { mutedBackgroundOnHead?: boolean; minWidth?: number; variant?: string; + lastRowBorder?: boolean; }; export function SimpleTable({ @@ -42,6 +43,7 @@ export function SimpleTable({ onRowClick, onRowHover, variant = "", + lastRowBorder = true, ...props }: SimpleTableProps) { const minWidth = @@ -99,7 +101,7 @@ export function SimpleTable({ tableBodyRef={tableBodyRef} > {rows.length > 0 ? ( - rows.map((row) => { + rows.map((row, index) => { return ( ({ flexTable={flexTable} onRowHover={onRowHover} onRowClick={onRowClick} + lastRowBorder={lastRowBorder} + lastRow={index === rows.length - 1} > {row.getVisibleCells().map((cell) => ( =6'} - cmdk@0.2.1: - resolution: {integrity: sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==} + cmdk@1.0.4: + resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} @@ -12058,16 +11975,6 @@ packages: '@types/react': optional: true - react-remove-scroll@2.5.4: - resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - react-remove-scroll@2.5.5: resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} engines: {node: '>=10'} @@ -18805,10 +18712,6 @@ snapshots: dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/primitive@1.0.0': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive@1.0.1': dependencies: '@babel/runtime': 7.24.7 @@ -18984,11 +18887,6 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 18.3.0 - '@radix-ui/react-compose-refs@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - react: 18.2.0 - '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19008,11 +18906,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-context@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - react: 18.2.0 - '@radix-ui/react-context@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19044,28 +18937,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-dialog@1.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-context': 1.0.0(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.0(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-id': 1.0.0(react@18.2.0) - '@radix-ui/react-portal': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-presence': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-slot': 1.0.0(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0) - aria-hidden: 1.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.4(@types/react@18.3.3)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.6 @@ -19089,6 +18960,28 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.6.0(@types/react@18.3.3)(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -19130,17 +19023,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-dismissable-layer@1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19169,6 +19051,19 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -19198,14 +19093,15 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-guards@1.0.0(react@18.2.0)': + '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 - '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.3)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.7 react: 18.2.0 optionalDependencies: '@types/react': 18.3.3 @@ -19216,15 +19112,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-focus-scope@1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - '@radix-ui/react-focus-scope@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19249,6 +19136,17 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) @@ -19282,12 +19180,6 @@ snapshots: dependencies: react: 19.0.0 - '@radix-ui/react-id@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - react: 18.2.0 - '@radix-ui/react-id@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19504,14 +19396,17 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-portal@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -19521,10 +19416,10 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) optionalDependencies: @@ -19541,14 +19436,6 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19580,13 +19467,6 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@1.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-slot': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19774,12 +19654,6 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 - '@radix-ui/react-slot@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - react: 18.2.0 - '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.6 @@ -19937,11 +19811,6 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 - '@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - react: 18.2.0 - '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19961,12 +19830,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-use-controllable-state@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - react: 18.2.0 - '@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19989,16 +19852,17 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-use-escape-keydown@1.0.0(react@18.2.0)': + '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.2.0) react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 - '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) react: 18.2.0 optionalDependencies: '@types/react': 18.3.3 @@ -20010,11 +19874,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0)': - dependencies: - '@babel/runtime': 7.24.7 - react: 18.2.0 - '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -24236,13 +24095,17 @@ snapshots: clsx@2.1.1: {} - cmdk@0.2.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + cmdk@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - '@radix-ui/react-dialog': 1.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + use-sync-external-store: 1.2.2(react@18.2.0) transitivePeerDependencies: - '@types/react' + - '@types/react-dom' co@4.6.0: {} @@ -29577,7 +29440,7 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - react-remove-scroll@2.5.4(@types/react@18.3.3)(react@18.2.0): + react-remove-scroll@2.5.5(@types/react@18.3.3)(react@18.2.0): dependencies: react: 18.2.0 react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.2.0) @@ -29588,7 +29451,7 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 - react-remove-scroll@2.5.5(@types/react@18.3.3)(react@18.2.0): + react-remove-scroll@2.6.0(@types/react@18.3.3)(react@18.2.0): dependencies: react: 18.2.0 react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.2.0)