diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx
index 91b733f1a0..8afbc1fe54 100644
--- a/ui/block/BlockDetails.tsx
+++ b/ui/block/BlockDetails.tsx
@@ -40,6 +40,7 @@ import Utilization from 'ui/shared/Utilization/Utilization';
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo';
+import KDABlockRewards from './BlockRewards';
import BlockDetailsBaseFeeCelo from './details/BlockDetailsBaseFeeCelo';
import BlockDetailsBlobInfo from './details/BlockDetailsBlobInfo';
import BlockDetailsZilliqaQuorumCertificate from './details/BlockDetailsZilliqaQuorumCertificate';
@@ -73,7 +74,7 @@ const BlockDetails = ({ query }: Props) => {
return null;
}
- const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data);
+ const { totalReward, burntFees, txFees } = getBlockReward(data);
const validatorTitle = getNetworkValidatorTitle();
@@ -82,33 +83,7 @@ const BlockDetails = ({ query }: Props) => {
return null;
}
- if (isPlaceholderData) {
- return ;
- }
-
- return (
-
-
- { staticReward.dividedBy(WEI).toFixed() }
-
- { !txFees.isEqualTo(ZERO) && (
- <>
- { space }+{ space }
-
- { txFees.dividedBy(WEI).toFixed() }
-
- >
- ) }
- { !burntFees.isEqualTo(ZERO) && (
- <>
- { space }-{ space }
-
- { burntFees.dividedBy(WEI).toFixed() }
-
- >
- ) }
-
- );
+ return ;
})();
const verificationTitle = `${ capitalize(getNetworkValidationActionText()) } by`;
@@ -382,10 +357,9 @@ const BlockDetails = ({ query }: Props) => {
Block reward
-
- { totalReward.dividedBy(WEI).toFixed() } { currencyUnits.ether }
+
+ { rewardBreakDown }
- { rewardBreakDown }
>
) }
diff --git a/ui/block/BlockRewards.tsx b/ui/block/BlockRewards.tsx
new file mode 100644
index 0000000000..6c5a7e6926
--- /dev/null
+++ b/ui/block/BlockRewards.tsx
@@ -0,0 +1,360 @@
+import { chakra, Text } from '@chakra-ui/react';
+import BigNumber from 'bignumber.js';
+import React, { useCallback, useMemo } from 'react';
+
+import useApiQuery from 'lib/api/useApiQuery';
+import { currencyUnits } from 'lib/units';
+import { Skeleton } from 'toolkit/chakra/skeleton';
+import { Tooltip } from 'toolkit/chakra/tooltip';
+import { Hint } from 'toolkit/components/Hint/Hint';
+import { WEI, ZERO } from 'toolkit/utils/consts';
+import { space } from 'toolkit/utils/htmlEntities';
+
+import getBlockReward from '../../lib/block/getBlockReward';
+import RawDataSnippet from '../shared/RawDataSnippet';
+import type { BlockQuery } from './useBlockQuery';
+
+interface Props {
+ query: BlockQuery;
+}
+
+type MouseEnterLeaveHandler = (event: React.MouseEvent) => void;
+type MouseEnterLeaveLabelHandler = (event: React.MouseEvent) => void;
+
+type TKDABlockRewardsData = {
+ isLoading: boolean;
+ loading: {
+ queryIsLoading: boolean;
+ withdrawalsIsLoading: boolean;
+ };
+ data: {
+ blockData?: BlockQuery['data'];
+ withdrawalsData?: { items: Array<{ amount?: string }> };
+ };
+ calculatedValues: {
+ rewardAmount: BigNumber;
+ rewardBaseFee: BigNumber;
+ txFees: BigNumber;
+ burntFees: BigNumber;
+ hasRewardBaseFee: boolean;
+ hasTxFees: boolean;
+ hasBurntFees: boolean;
+ } | null;
+ formattedValues: {
+ rewardAmount?: string;
+ rewardBaseFee?: string;
+ txFees?: string;
+ burntFees?: string;
+ };
+};
+
+interface TKDABreakdownItemProps {
+ isLoading: boolean;
+ onMouseEnter?: MouseEnterLeaveHandler;
+ onMouseLeave?: MouseEnterLeaveHandler;
+ children: React.ReactNode;
+}
+
+const KDABreakdownItem = React.forwardRef(({
+ children,
+ isLoading,
+ onMouseEnter,
+ onMouseLeave,
+}, ref) => {
+ return (
+
+
+
+ );
+});
+
+KDABreakdownItem.displayName = 'KDABreakdownItem';
+
+interface BreakdownLabelProps {
+ onMouseEnterValues: MouseEnterLeaveLabelHandler;
+ onMouseLeaveValues: MouseEnterLeaveLabelHandler;
+ children: React.ReactNode;
+}
+
+const BreakdownLabel = React.forwardRef(({
+ onMouseEnterValues,
+ onMouseLeaveValues,
+ children,
+}, ref) => (
+
+ { children }
+
+));
+
+BreakdownLabel.displayName = 'BreakdownLabel';
+
+export const KDABlockRewardsData = (query: BlockQuery): TKDABlockRewardsData => {
+ const { data, isLoading: queryIsLoading } = query;
+
+ const { data: withdrawalsData, isLoading: withdrawalsIsLoading } = useApiQuery('general:block_withdrawals', {
+ queryParams: { height_or_hash: `${ data?.height }` },
+ });
+ const { totalReward, burntFees, txFees } = data ? getBlockReward(data) : { totalReward: ZERO, burntFees: ZERO, txFees: ZERO };
+
+ const calculatedValues = useMemo(() => {
+ if (!withdrawalsData?.items?.length) {
+ return null;
+ }
+
+ const [ reward ] = withdrawalsData.items;
+ const rewardAmount = BigNumber(reward.amount ?? 0);
+ const rewardBaseFee = BigNumber(rewardAmount.toNumber() - totalReward.toNumber() + burntFees.toNumber());
+
+ return {
+ rewardAmount: rewardAmount.dividedBy(WEI),
+ rewardBaseFee: rewardBaseFee.dividedBy(WEI),
+ txFees: txFees.dividedBy(WEI),
+ burntFees: burntFees.dividedBy(WEI),
+ hasRewardBaseFee: !rewardBaseFee.isEqualTo(ZERO),
+ hasTxFees: !txFees.isEqualTo(ZERO),
+ hasBurntFees: !burntFees.isEqualTo(ZERO),
+ };
+ }, [ withdrawalsData, totalReward, burntFees, txFees ]);
+
+ return {
+ isLoading: withdrawalsIsLoading || queryIsLoading,
+ loading: {
+ queryIsLoading,
+ withdrawalsIsLoading,
+ },
+ data: {
+ blockData: data,
+ withdrawalsData,
+ },
+ calculatedValues,
+ formattedValues: {
+ rewardAmount: calculatedValues?.rewardAmount ? BigNumber(calculatedValues.rewardAmount).toFixed() : undefined,
+ rewardBaseFee: calculatedValues?.rewardBaseFee ? BigNumber(calculatedValues.rewardBaseFee).toFixed() : undefined,
+ txFees: calculatedValues?.txFees ? BigNumber(calculatedValues.txFees).toFixed() : undefined,
+ burntFees: calculatedValues?.burntFees ? BigNumber(calculatedValues.burntFees).toFixed() : undefined,
+ },
+ };
+};
+
+const KDABlockRewards = ({ query /* data, txFees, burntFees, totalReward */ }: Props) => {
+ const blockRewardValuesRef = React.useRef(null);
+ const blockRewardLabelRef = React.useRef(null);
+ const txFeesValuesRef = React.useRef(null);
+ const txFeesLabelRef = React.useRef(null);
+ const burntFeesValuesRef = React.useRef(null);
+ const burntFeesLabelRef = React.useRef(null);
+
+ const {
+ isLoading,
+ calculatedValues,
+ formattedValues: {
+ rewardAmount,
+ rewardBaseFee,
+ txFees,
+ burntFees,
+ },
+ } = KDABlockRewardsData(query);
+ const {
+ hasRewardBaseFee,
+ hasTxFees,
+ hasBurntFees,
+ } = calculatedValues ?? {};
+
+ const onMouseEnterValues = useCallback((ref: React.RefObject) => {
+ const element = ref.current?.querySelector('.chakra-skeleton') as HTMLElement;
+
+ if (element) {
+ element.style.backgroundColor = 'var(--kda-explorer-alert-background-semantic-info) !important';
+ }
+ }, []);
+
+ const onMouseLeaveValues = useCallback((ref: React.RefObject) => {
+ const element = ref.current?.querySelector('.chakra-skeleton') as HTMLElement;
+
+ if (element) {
+ element.style.backgroundColor = '';
+ }
+ }, []);
+
+ const onMouseEnterLabels = useCallback((ref: React.RefObject) => {
+ if (ref.current) {
+ ref.current.style.color = 'var(--kda-color-accent-blue)';
+ }
+ }, []);
+
+ const onMouseLeaveLabels = useCallback((ref: React.RefObject) => {
+ if (ref.current) {
+ ref.current.style.color = '';
+ }
+ }, []);
+
+ const handleBlockRewardMouseEnter = useCallback(() => onMouseEnterLabels(blockRewardLabelRef), [ onMouseEnterLabels ]);
+ const handleBlockRewardMouseLeave = useCallback(() => onMouseLeaveLabels(blockRewardLabelRef), [ onMouseLeaveLabels ]);
+
+ const handleTxFeesMouseEnter = useCallback(() => onMouseEnterLabels(txFeesLabelRef), [ onMouseEnterLabels ]);
+ const handleTxFeesMouseLeave = useCallback(() => onMouseLeaveLabels(txFeesLabelRef), [ onMouseLeaveLabels ]);
+
+ const handleBurntFeesMouseEnter = useCallback(() => onMouseEnterLabels(burntFeesLabelRef), [ onMouseEnterLabels ]);
+ const handleBurntFeesMouseLeave = useCallback(() => onMouseLeaveLabels(burntFeesLabelRef), [ onMouseLeaveLabels ]);
+
+ const handleBlockRewardValuesMouseEnter = useCallback(() => onMouseEnterValues(blockRewardValuesRef), [ onMouseEnterValues ]);
+ const handleBlockRewardValuesMouseLeave = useCallback(() => onMouseLeaveValues(blockRewardValuesRef), [ onMouseLeaveValues ]);
+
+ const handleTxFeesValuesMouseEnter = useCallback(() => onMouseEnterValues(txFeesValuesRef), [ onMouseEnterValues ]);
+ const handleTxFeesValuesMouseLeave = useCallback(() => onMouseLeaveValues(txFeesValuesRef), [ onMouseLeaveValues ]);
+
+ const handleBurntFeesValuesMouseEnter = useCallback(() => onMouseEnterValues(burntFeesValuesRef), [ onMouseEnterValues ]);
+ const handleBurntFeesValuesMouseLeave = useCallback(() => onMouseLeaveValues(burntFeesValuesRef), [ onMouseLeaveValues ]);
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (!calculatedValues) {
+ return null;
+ }
+
+ return (
+
+
+ { rewardAmount } { currencyUnits.ether }
+
+
+
+ Breakdown{ space }
+
+
+
+
+
+
+ Block reward
+
+
+
+ +
+
+
+
+
+ Transaction fees
+
+
+
+ -
+
+
+
+
+ Burnt fees
+
+
+
+
+
+ { hasRewardBaseFee && (
+
+
+
+ Reward fee:
+
+ { rewardBaseFee }
+
+
+ )
+ }
+ { space }+{ space }
+ { hasTxFees && (
+
+
+
+ Transaction fees:
+
+ { txFees }
+
+
+ ) }
+ { space }-{ space }
+ { hasBurntFees && (
+
+
+
+ Burnt fees:{ ' ' }
+
+ { burntFees }
+
+
+ ) }
+
+ ) }
+ isLoading={ isLoading }
+ />
+
+
+ );
+};
+
+KDABlockRewards.displayName = 'KDABlockRewards';
+
+export default KDABlockRewards;