From 059a68d293dfb62181aa5002b8df2ac1d1922b6b Mon Sep 17 00:00:00 2001 From: Francisco Tobar Date: Tue, 10 Dec 2024 22:41:06 -0600 Subject: [PATCH 1/4] feat: format currency, symbol and metrics --- .../components/AllocationAmount.tsx | 4 +- .../components/AllocationMetrics.tsx | 8 +- .../components/BuilderAllocation.tsx | 6 +- .../leaderboard/BuildersLeaderBoardTable.tsx | 17 +- .../metrics/AllTimeRewardsMetrics.tsx | 5 +- .../metrics/TotalAllocationsMetrics.tsx | 8 +- .../rewards/backers/BackerRewardsTable.tsx | 21 +- .../rewards/backers/ClaimableRewards.tsx | 8 +- .../rewards/backers/RewardsCard.tsx | 5 +- .../backers/hooks/useGetBackerRewards.ts | 63 ++---- .../rewards/builders/AllTimeRewards.tsx | 4 +- .../rewards/builders/ClaimableRewards.tsx | 8 +- .../rewards/builders/EstimatedRewards.tsx | 11 +- .../rewards/builders/LastCycleRewards.tsx | 4 +- .../builders/hooks/useGetBuildersRewards.ts | 65 +++--- .../hooks/useGetBackerRewardPercentage.ts | 17 +- .../hooks/useGetBackersRewardPercentage.ts | 17 +- src/app/collective-rewards/rewards/types.ts | 15 +- .../rewards/utils/formatMetrics.test.ts | 64 ------ .../rewards/utils/formatMetrics.ts | 16 -- .../rewards/utils/formatter.test.ts | 204 ++++++++++++++++++ .../rewards/utils/formatter.ts | 80 +++++++ .../utils/getBackerRewardPercentage.ts | 15 ++ .../rewards/utils/getPercentageData.ts | 29 --- .../collective-rewards/rewards/utils/index.ts | 5 +- .../rewards/utils/toPercentage.ts | 3 - .../shared/components/Table/TableCells.tsx | 66 +++--- .../utils/getCombinedFiatAmount.ts | 9 + src/app/collective-rewards/utils/index.ts | 1 + src/components/Footer/Footer.tsx | 2 +- 30 files changed, 484 insertions(+), 296 deletions(-) delete mode 100644 src/app/collective-rewards/rewards/utils/formatMetrics.test.ts delete mode 100644 src/app/collective-rewards/rewards/utils/formatMetrics.ts create mode 100644 src/app/collective-rewards/rewards/utils/formatter.test.ts create mode 100644 src/app/collective-rewards/rewards/utils/formatter.ts create mode 100644 src/app/collective-rewards/rewards/utils/getBackerRewardPercentage.ts delete mode 100644 src/app/collective-rewards/rewards/utils/getPercentageData.ts delete mode 100644 src/app/collective-rewards/rewards/utils/toPercentage.ts create mode 100644 src/app/collective-rewards/utils/getCombinedFiatAmount.ts diff --git a/src/app/collective-rewards/allocations/components/AllocationAmount.tsx b/src/app/collective-rewards/allocations/components/AllocationAmount.tsx index 7162aee7..47869b86 100644 --- a/src/app/collective-rewards/allocations/components/AllocationAmount.tsx +++ b/src/app/collective-rewards/allocations/components/AllocationAmount.tsx @@ -1,7 +1,7 @@ 'use client' import { Allocations, AllocationsContext } from '@/app/collective-rewards/allocations/context' -import { formatOnchainFraction } from '@/app/collective-rewards/rewards' +import { formatSymbol } from '@/app/collective-rewards/rewards' import { Button, ButtonProps } from '@/components/Button' import { Input } from '@/components/Input' import { cn } from '@/lib/utils' @@ -63,7 +63,7 @@ export const AllocationAmount = () => { name="allocated-amount" fullWidth onChange={handleOnChange} - value={formatOnchainFraction(amountToAllocate)} + value={formatSymbol(amountToAllocate, 'stRIF')} errorMessage={ cumulativeAllocation > amountToAllocate && cumulativeAllocation < balance ? ALLOCATION_EXCEED_AMOUNT_ERROR diff --git a/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx index fc0f0e21..d611ef7b 100644 --- a/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx +++ b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx @@ -1,7 +1,7 @@ 'use client' import { AllocationsContext } from '@/app/collective-rewards/allocations/context' -import { formatOnchainFraction } from '@/app/collective-rewards/rewards' +import { formatSymbol } from '@/app/collective-rewards/rewards' import { Paragraph } from '@/components/Typography' import { useContext } from 'react' @@ -45,11 +45,11 @@ export const AllocationMetrics = () => { }, } = useContext(AllocationsContext) - const balanceValue = `${formatOnchainFraction(balance)} stRIF` + const balanceValue = `${formatSymbol(balance, 'stRIF')} stRIF` - const allocatedAmountValue = `${formatOnchainFraction(amountToAllocate)} stRIF` + const allocatedAmountValue = `${formatSymbol(amountToAllocate, 'stRIF')} stRIF` - const unallocatedAmount = formatOnchainFraction(balance - amountToAllocate) + const unallocatedAmount = formatSymbol(balance - amountToAllocate, 'stRIF') const unallocatedAmountValue = `${unallocatedAmount} stRIF` return ( diff --git a/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx index 5d1e87e2..00008bbb 100644 --- a/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx +++ b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx @@ -1,5 +1,5 @@ import { AllocationsContext } from '@/app/collective-rewards/allocations/context' -import { formatOnchainFraction } from '@/app/collective-rewards/rewards' +import { formatSymbol } from '@/app/collective-rewards/rewards' import { weiToPercentage } from '@/app/collective-rewards/settings' import { Builder } from '@/app/collective-rewards/types' import { Input } from '@/components/Input' @@ -41,9 +41,9 @@ export const BuilderAllocation = (builder: BuilderAllocationProps) => { 0 ? formatOnchainFraction(allocationLeft) : '0'} stRIF`} + hint={`Allocation left ${formatSymbol(allocationLeft, 'stRIF')} stRIF`} onChange={onInputChange} - value={formatOnchainFraction(currentAllocation || 0n)} + value={formatSymbol(currentAllocation || 0n, 'stRIF')} /> { rewardPercentage: (a: IRewardData, b: IRewardData) => Number(a.rewardPercentage.current - b.rewardPercentage.current), lastCycleRewards: (a: IRewardData, b: IRewardData) => { - const aValue = a.lastCycleReward.rif.crypto.value + a.lastCycleReward.rbtc.crypto.value - const bValue = b.lastCycleReward.rif.crypto.value + b.lastCycleReward.rbtc.crypto.value + const aValue = getCombinedFiatAmount([a.lastCycleRewards.rif.amount, a.lastCycleRewards.rbtc.amount]) + const bValue = getCombinedFiatAmount([b.lastCycleRewards.rif.amount, b.lastCycleRewards.rbtc.amount]) return aValue - bValue }, estimatedRewards: (a: IRewardData, b: IRewardData) => { - const aValue = a.estimatedReward.rif.crypto.value + a.estimatedReward.rbtc.crypto.value - const bValue = b.estimatedReward.rif.crypto.value + b.estimatedReward.rbtc.crypto.value + const aValue = getCombinedFiatAmount([a.estimatedRewards.rif.amount, a.estimatedRewards.rbtc.amount]) + const bValue = getCombinedFiatAmount([b.estimatedRewards.rif.amount, b.estimatedRewards.rbtc.amount]) return aValue - bValue }, totalAllocationPercentage: (a: IRewardData, b: IRewardData) => @@ -124,8 +125,8 @@ export const BuildersLeaderBoardTable: FC = () => { address, builderName, stateFlags, - lastCycleReward, - estimatedReward, + lastCycleRewards, + estimatedRewards, totalAllocationPercentage, rewardPercentage, }) => ( @@ -139,11 +140,11 @@ export const BuildersLeaderBoardTable: FC = () => { diff --git a/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx b/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx index 079c0f82..a9f49e27 100644 --- a/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx +++ b/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx @@ -10,7 +10,6 @@ import { import { FC } from 'react' import { useHandleErrors } from '@/app/collective-rewards/utils' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { usePricesContext } from '@/shared/context/PricesContext' type TokenRewardsMetricsProps = { @@ -40,10 +39,8 @@ const TokenRewardsMetrics: FC = ({ 0n, ) - const totalRewardsInHuman = Number(formatBalanceToHuman(totalRewards)) const price = prices[symbol]?.price ?? 0 - - const { amount, fiatAmount } = formatMetrics(totalRewardsInHuman, price, symbol, currency) + const { amount, fiatAmount } = formatMetrics(totalRewards, price, symbol, currency) return withSpinner( TokenMetricsCardRow, diff --git a/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx b/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx index f3bfba49..b32bce1b 100644 --- a/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx +++ b/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx @@ -2,13 +2,12 @@ import { useGaugesGetFunction } from '@/app/collective-rewards/shared/hooks' import { Address } from 'viem' import { FC } from 'react' import { usePricesContext } from '@/shared/context/PricesContext' -import { formatCurrency } from '@/lib/utils' import { MetricsCard, MetricsCardTitle, TokenMetricsCardRow, Token, - formatOnchainFraction, + formatMetrics, } from '@/app/collective-rewards/rewards' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { useHandleErrors } from '@/app/collective-rewards/utils' @@ -31,8 +30,7 @@ export const TotalAllocationsMetrics: FC = ({ const price = prices[symbol]?.price ?? 0 const totalAllocations = Object.values(data).reduce((acc, allocation) => acc + allocation, 0n) - const totalAllocationsInHuman = Number(formatOnchainFraction(totalAllocations)) - const fiatAmount = `= ${currency} ${formatCurrency(totalAllocationsInHuman * price, currency)}` + const { amount, fiatAmount } = formatMetrics(totalAllocations, price, symbol, currency) return ( @@ -47,7 +45,7 @@ export const TotalAllocationsMetrics: FC = ({ TokenMetricsCardRow, 'min-h-0 grow-0', )({ - amount: `${totalAllocationsInHuman} STRIF`, + amount, fiatAmount, isLoading, })} diff --git a/src/app/collective-rewards/rewards/backers/BackerRewardsTable.tsx b/src/app/collective-rewards/rewards/backers/BackerRewardsTable.tsx index 3f7a4324..f38d677d 100644 --- a/src/app/collective-rewards/rewards/backers/BackerRewardsTable.tsx +++ b/src/app/collective-rewards/rewards/backers/BackerRewardsTable.tsx @@ -4,7 +4,6 @@ import { BackerRewardsContextProvider, useGetBackerRewards, } from '@/app/collective-rewards/rewards' -import { BuilderContextProviderWithPrices } from '@/app/collective-rewards/user' import { ISortConfig, TableHeader, @@ -15,7 +14,7 @@ import { TotalAllocationCell, } from '@/app/collective-rewards/shared' import { TableBody, TableCore, TableHead, TableRow } from '@/components/Table' -import { useHandleErrors } from '@/app/collective-rewards/utils' +import { getCombinedFiatAmount, useHandleErrors } from '@/app/collective-rewards/utils' import { LoadingSpinner } from '@/components/LoadingSpinner' import { useBasicPaginationUi } from '@/shared/hooks/usePaginationUi' import { CycleContextProvider } from '@/app/collective-rewards/metrics' @@ -66,14 +65,22 @@ const RewardsTable: FC = ({ builder, gauges, tokens }) => { rewardPercentage: (a: IRewardData, b: IRewardData) => Number(a.rewardPercentage.current - b.rewardPercentage.current), estimatedRewards: (a: IRewardData, b: IRewardData) => { - return Number(a.estimatedRewards.rif.crypto.value - b.estimatedRewards.rif.crypto.value) + const aValue = getCombinedFiatAmount([a.estimatedRewards.rif.amount, a.estimatedRewards.rbtc.amount]) + const bValue = getCombinedFiatAmount([b.estimatedRewards.rif.amount, b.estimatedRewards.rbtc.amount]) + return aValue - bValue }, totalAllocationPercentage: (a: IRewardData, b: IRewardData) => Number(a.totalAllocationPercentage - b.totalAllocationPercentage), - claimableRewards: (a: IRewardData, b: IRewardData) => - Number(a.claimableRewards.rif.crypto.value - b.claimableRewards.rif.crypto.value), - allTimeRewards: (a: IRewardData, b: IRewardData) => - Number(a.allTimeRewards.rif.crypto.value - b.allTimeRewards.rif.crypto.value), + claimableRewards: (a: IRewardData, b: IRewardData) => { + const aValue = getCombinedFiatAmount([a.claimableRewards.rif.amount, a.claimableRewards.rbtc.amount]) + const bValue = getCombinedFiatAmount([b.claimableRewards.rif.amount, b.claimableRewards.rbtc.amount]) + return aValue - bValue + }, + allTimeRewards: (a: IRewardData, b: IRewardData) => { + const aValue = getCombinedFiatAmount([a.allTimeRewards.rif.amount, a.allTimeRewards.rbtc.amount]) + const bValue = getCombinedFiatAmount([b.allTimeRewards.rif.amount, b.allTimeRewards.rbtc.amount]) + return aValue - bValue + }, } return Object.values(rewardsData).toSorted((a: IRewardData, b: IRewardData) => { const { key, direction } = sortConfig diff --git a/src/app/collective-rewards/rewards/backers/ClaimableRewards.tsx b/src/app/collective-rewards/rewards/backers/ClaimableRewards.tsx index 5dc5523d..532422f6 100644 --- a/src/app/collective-rewards/rewards/backers/ClaimableRewards.tsx +++ b/src/app/collective-rewards/rewards/backers/ClaimableRewards.tsx @@ -12,7 +12,6 @@ import { useBackerRewardsContext, ClaimYourRewardsButton, } from '@/app/collective-rewards/rewards' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { useHandleErrors } from '@/app/collective-rewards/utils' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' @@ -35,12 +34,7 @@ const TokenRewardsMetrics: FC = ({ const tokenPrice = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics( - Number(formatBalanceToHuman(earnedRewards)), - tokenPrice, - symbol, - currency, - ) + const { amount, fiatAmount } = formatMetrics(earnedRewards, tokenPrice, symbol, currency) const { claimRewards, isClaimable } = useClaimBackerRewards(address) diff --git a/src/app/collective-rewards/rewards/backers/RewardsCard.tsx b/src/app/collective-rewards/rewards/backers/RewardsCard.tsx index 70e8e297..09af4dd1 100644 --- a/src/app/collective-rewards/rewards/backers/RewardsCard.tsx +++ b/src/app/collective-rewards/rewards/backers/RewardsCard.tsx @@ -9,7 +9,6 @@ import { TooltipProps, } from '@/app/collective-rewards/rewards' import { useHandleErrors } from '@/app/collective-rewards/utils' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { usePricesContext } from '@/shared/context/PricesContext' import { FC } from 'react' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' @@ -45,11 +44,9 @@ const TokenRewardsMetrics: FC = ({ useHandleErrors({ error: error, title: 'Error loading all time rewards' }) const { prices } = usePricesContext() - - const totalRewardsInHuman = Number(formatBalanceToHuman(totalRewards)) const price = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics(totalRewardsInHuman, price, symbol, currency) + const { amount, fiatAmount } = formatMetrics(totalRewards, price, symbol, currency) return withSpinner( TokenMetricsCardRow, diff --git a/src/app/collective-rewards/rewards/backers/hooks/useGetBackerRewards.ts b/src/app/collective-rewards/rewards/backers/hooks/useGetBackerRewards.ts index 4066d5fa..086a0237 100644 --- a/src/app/collective-rewards/rewards/backers/hooks/useGetBackerRewards.ts +++ b/src/app/collective-rewards/rewards/backers/hooks/useGetBackerRewards.ts @@ -5,13 +5,12 @@ import { useGetBackersRewardPercentage, RifSvg, RbtcSvg, - BuilderRewardPercentage, TokenRewards, + BackerRewardPercentage, } from '@/app/collective-rewards/rewards' import { useGaugesGetFunction } from '@/app/collective-rewards/shared' import { Address } from 'viem' import { usePricesContext } from '@/shared/context/PricesContext' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { useGetBuildersByState } from '@/app/collective-rewards//user' import { Builder, BuilderStateFlags } from '@/app/collective-rewards/types' import { useMemo } from 'react' @@ -21,20 +20,16 @@ export type BackerRewards = { builderName: string stateFlags: BuilderStateFlags totalAllocationPercentage: bigint - rewardPercentage: BuilderRewardPercentage + rewardPercentage: BackerRewardPercentage estimatedRewards: TokenRewards claimableRewards: TokenRewards allTimeRewards: TokenRewards } const tokenRewardsMetrics = (tokenRewards: TokenBackerRewards, gauge: Address) => { - const estimatedRewards = Number(formatBalanceToHuman(tokenRewards.estimated[gauge] ?? 0n)) - const earned = Number(formatBalanceToHuman(tokenRewards.earned[gauge] ?? 0n)) - const claimed = Number( - formatBalanceToHuman( - tokenRewards.claimed[gauge]?.reduce((acc, value) => acc + value.args.amount_, 0n) ?? 0n, - ), - ) + const estimatedRewards = tokenRewards.estimated[gauge] ?? 0n + const earned = tokenRewards.earned[gauge] ?? 0n + const claimed = tokenRewards.claimed[gauge]?.reduce((acc, value) => acc + value.args.amount_, 0n) ?? 0n return { claimableRewards: earned, @@ -121,72 +116,60 @@ export const useGetBackerRewards = ( rewardPercentage, estimatedRewards: { rif: { - crypto: { + amount: { value: rifRewards.estimatedRewards, symbol: rif.symbol, - }, - fiat: { - value: rifPrice * rifRewards.estimatedRewards, - symbol: currency, + price: rifPrice, + currency, }, logo: RifSvg(), }, rbtc: { - crypto: { + amount: { value: rbtcRewards.estimatedRewards, symbol: rbtc.symbol, - }, - fiat: { - value: rbtcPrice * rbtcRewards.estimatedRewards, - symbol: currency, + price: rbtcPrice, + currency, }, logo: RbtcSvg(), }, }, claimableRewards: { rif: { - crypto: { + amount: { value: rifRewards.claimableRewards, symbol: rif.symbol, - }, - fiat: { - value: rifPrice * rifRewards.claimableRewards, - symbol: currency, + price: rifPrice, + currency, }, logo: RifSvg(), }, rbtc: { - crypto: { + amount: { value: rbtcRewards.claimableRewards, symbol: rbtc.symbol, - }, - fiat: { - value: rbtcPrice * rbtcRewards.claimableRewards, - symbol: currency, + price: rbtcPrice, + currency, }, logo: RbtcSvg(), }, }, allTimeRewards: { rif: { - crypto: { + amount: { value: rifRewards.allTimeRewards, symbol: rif.symbol, - }, - fiat: { - value: rifPrice * rifRewards.allTimeRewards, - symbol: currency, + price: rifPrice, + currency, }, logo: RifSvg(), }, rbtc: { - crypto: { + amount: { value: rbtcRewards.allTimeRewards, symbol: rbtc.symbol, - }, - fiat: { - value: rbtcPrice * rbtcRewards.allTimeRewards, - symbol: currency, + price: rbtcPrice, + currency, }, logo: RbtcSvg(), }, diff --git a/src/app/collective-rewards/rewards/builders/AllTimeRewards.tsx b/src/app/collective-rewards/rewards/builders/AllTimeRewards.tsx index 308c770e..39a71c06 100644 --- a/src/app/collective-rewards/rewards/builders/AllTimeRewards.tsx +++ b/src/app/collective-rewards/rewards/builders/AllTimeRewards.tsx @@ -9,7 +9,6 @@ import { BuilderRewardDetails, } from '@/app/collective-rewards/rewards' import { useHandleErrors } from '@/app/collective-rewards/utils' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { usePricesContext } from '@/shared/context/PricesContext' import { FC } from 'react' @@ -49,10 +48,9 @@ const TokenRewardsMetrics: FC = ({ }, 0n) ?? 0n const totalRewards = totalClaimedRewards + (claimableRewards ?? 0n) - const totalRewardsInHuman = Number(formatBalanceToHuman(totalRewards)) const price = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics(totalRewardsInHuman, price, symbol, currency) + const { amount, fiatAmount } = formatMetrics(totalRewards, price, symbol, currency) return withSpinner( TokenMetricsCardRow, diff --git a/src/app/collective-rewards/rewards/builders/ClaimableRewards.tsx b/src/app/collective-rewards/rewards/builders/ClaimableRewards.tsx index 3e5f2d84..589582ff 100644 --- a/src/app/collective-rewards/rewards/builders/ClaimableRewards.tsx +++ b/src/app/collective-rewards/rewards/builders/ClaimableRewards.tsx @@ -10,7 +10,6 @@ import { BuilderRewardDetails, } from '@/app/collective-rewards/rewards' import { useHandleErrors } from '@/app/collective-rewards/utils' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { usePricesContext } from '@/shared/context/PricesContext' import { FC } from 'react' @@ -40,12 +39,7 @@ const TokenRewardsMetrics: FC = ({ const tokenPrice = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics( - Number(formatBalanceToHuman(rewards ?? 0n)), - tokenPrice, - symbol, - currency, - ) + const { amount, fiatAmount } = formatMetrics(rewards ?? 0n, tokenPrice, symbol, currency) const { isClaimable, claimRewards, isPaused } = useClaimBuilderRewardsPerToken(builder, gauge, address) const content = isPaused ? 'You cannot be paused to claim rewards' : undefined diff --git a/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx b/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx index f860dbfa..dc284c90 100644 --- a/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx +++ b/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx @@ -11,10 +11,9 @@ import { useGetBackerRewardPercentage, } from '@/app/collective-rewards/rewards' import { useHandleErrors } from '@/app/collective-rewards/utils' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { usePricesContext } from '@/shared/context/PricesContext' import { FC, useEffect, useState } from 'react' -import { Address } from 'viem' +import { Address, parseUnits } from 'viem' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { useCycleContext } from '@/app/collective-rewards/metrics/context/CycleContext' @@ -64,7 +63,7 @@ const TokenRewards: FC = ({ builder, gauge, token: { id, symb error: backerRewardsPctError, } = useGetBackerRewardPercentage(builder, cycleNext.toSeconds()) - const rewardPercentageToApply = backerRewardsPct?.current ?? 0 + const rewardPercentageToApply = backerRewardsPct.current const error = rewardsError ?? totalPotentialRewardsError ?? rewardSharesError ?? backerRewardsPctError ?? cycleError @@ -75,10 +74,10 @@ const TokenRewards: FC = ({ builder, gauge, token: { id, symb const rewardsAmount = rewardShares && totalPotentialRewards ? (rewards * rewardShares) / totalPotentialRewards : 0n // The complement of the reward percentage is applied to the estimated rewards since are from the builder's perspective - const estimatedRewardsInHuman = - Number(formatBalanceToHuman(rewardsAmount)) * (1 - rewardPercentageToApply / 100) + const weiPerEther = parseUnits('1', 18) + const estimatedRewards = (rewardsAmount * (weiPerEther - rewardPercentageToApply)) / weiPerEther const price = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics(estimatedRewardsInHuman, price, symbol, currency) + const { amount, fiatAmount } = formatMetrics(estimatedRewards, price, symbol, currency) return withSpinner( TokenMetricsCardRow, diff --git a/src/app/collective-rewards/rewards/builders/LastCycleRewards.tsx b/src/app/collective-rewards/rewards/builders/LastCycleRewards.tsx index 2f14eb74..bf57076d 100644 --- a/src/app/collective-rewards/rewards/builders/LastCycleRewards.tsx +++ b/src/app/collective-rewards/rewards/builders/LastCycleRewards.tsx @@ -10,7 +10,6 @@ import { BuilderRewardDetails, } from '@/app/collective-rewards/rewards' import { useHandleErrors } from '@/app/collective-rewards/utils' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { usePricesContext } from '@/shared/context/PricesContext' import { FC } from 'react' @@ -40,9 +39,8 @@ const TokenRewardsMetrics: FC = ({ const { prices } = usePricesContext() const lastCycleRewards = getLastCycleRewards(cycle, rewardsPerToken[address]) - const lastCycleRewardsInHuman = Number(formatBalanceToHuman(lastCycleRewards.builderAmount)) const price = prices[symbol]?.price ?? 0 - const { amount, fiatAmount } = formatMetrics(lastCycleRewardsInHuman, price, symbol, currency) + const { amount, fiatAmount } = formatMetrics(lastCycleRewards.builderAmount, price, symbol, currency) return withSpinner( TokenMetricsCardRow, diff --git a/src/app/collective-rewards/rewards/builders/hooks/useGetBuildersRewards.ts b/src/app/collective-rewards/rewards/builders/hooks/useGetBuildersRewards.ts index a199f38e..a3793e40 100644 --- a/src/app/collective-rewards/rewards/builders/hooks/useGetBuildersRewards.ts +++ b/src/app/collective-rewards/rewards/builders/hooks/useGetBuildersRewards.ts @@ -9,15 +9,14 @@ import { useGetTotalPotentialReward, RifSvg, RbtcSvg, - BuilderRewardPercentage, TokenRewards, + BackerRewardPercentage, } from '@/app/collective-rewards/rewards' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { usePricesContext } from '@/shared/context/PricesContext' import { useGaugesGetFunction } from '@/app/collective-rewards/shared' import { Builder, BuilderStateFlags } from '@/app/collective-rewards/types' import { useGetBuildersByState } from '@/app/collective-rewards/user' -import { Address } from 'viem' +import { Address, parseUnits } from 'viem' import { Allocations, AllocationsContext } from '@/app/collective-rewards/allocations/context' import { useContext, useMemo } from 'react' @@ -37,9 +36,9 @@ export type BuildersRewards = { builderName: string stateFlags: BuilderStateFlags totalAllocationPercentage: bigint - rewardPercentage: BuilderRewardPercentage - lastCycleReward: TokenRewards - estimatedReward: TokenRewards + rewardPercentage: BackerRewardPercentage + lastCycleRewards: TokenRewards + estimatedRewards: TokenRewards } export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token }, currency = 'USD') => { @@ -152,30 +151,30 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token }, const builderRewardShares = rewardShares[gauge] ?? 0n const rewardPercentage = backersRewardsPct[address] ?? null - const rewardPercentageToApply = rewardPercentage?.current ?? 0 + const rewardPercentageToApply = rewardPercentage?.current ?? 0n + + const weiPerEther = parseUnits('1', 18) // calculate rif estimated rewards const rewardRif = rewardsERC20 ?? 0n const rewardsAmountRif = totalPotentialRewards ? (rewardRif * builderRewardShares) / totalPotentialRewards : 0n - const estimatedRifInHuman = - Number(formatBalanceToHuman(rewardsAmountRif)) * (rewardPercentageToApply / 100) + const estimatedRifAmount = (rewardsAmountRif * rewardPercentageToApply) / weiPerEther // calculate rbtc estimated rewards const rewardRbtc = rewardsCoinbase ?? 0n const rewardsAmountRbtc = totalPotentialRewards ? (rewardRbtc * builderRewardShares) / totalPotentialRewards : 0n - const estimatedRbtcInHuman = - Number(formatBalanceToHuman(rewardsAmountRbtc)) * (rewardPercentageToApply / 100) + const estimatedRbtcAmount = (rewardsAmountRbtc * rewardPercentageToApply) / weiPerEther const totalAllocationPercentage = sumTotalAllocation ? (totalAllocation[gauge] * 100n) / sumTotalAllocation : 0n - const rifBuilderRewardsAmount = Number(formatBalanceToHuman(rifBuildersRewardsAmount[gauge] ?? 0n)) - const rbtcBuilderRewardsAmount = Number(formatBalanceToHuman(rbtcBuildersRewardsAmount[gauge] ?? 0n)) + const rifLastCycleRewardsAmount = rifBuildersRewardsAmount[gauge] ?? 0n + const rbtcLastCycleRewardsAmount = rbtcBuildersRewardsAmount[gauge] ?? 0n return [ ...acc, @@ -185,38 +184,42 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token }, stateFlags, totalAllocationPercentage, rewardPercentage, - lastCycleReward: { + lastCycleRewards: { rif: { - crypto: { value: rifBuilderRewardsAmount, symbol: rif.symbol }, - fiat: { - value: rifPrice * rifBuilderRewardsAmount, - symbol: currency, + amount: { + value: rifLastCycleRewardsAmount, + price: rifPrice, + symbol: rif.symbol, + currency, }, logo: RifSvg(), }, rbtc: { - crypto: { value: rbtcBuilderRewardsAmount, symbol: rbtc.symbol }, - fiat: { - value: rbtcPrice * rbtcBuilderRewardsAmount, - symbol: currency, + amount: { + value: rbtcLastCycleRewardsAmount, + price: rbtcPrice, + symbol: rbtc.symbol, + currency, }, logo: RbtcSvg(), }, }, - estimatedReward: { + estimatedRewards: { rif: { - crypto: { value: estimatedRifInHuman, symbol: rif.symbol }, - fiat: { - value: rifPrice * estimatedRifInHuman, - symbol: currency, + amount: { + value: estimatedRifAmount, + price: rifPrice, + symbol: rif.symbol, + currency, }, logo: RifSvg(), }, rbtc: { - crypto: { value: estimatedRbtcInHuman, symbol: rbtc.symbol }, - fiat: { - value: rbtcPrice * estimatedRbtcInHuman, - symbol: currency, + amount: { + value: estimatedRbtcAmount, + price: rbtcPrice, + symbol: rbtc.symbol, + currency, }, logo: RbtcSvg(), }, diff --git a/src/app/collective-rewards/rewards/hooks/useGetBackerRewardPercentage.ts b/src/app/collective-rewards/rewards/hooks/useGetBackerRewardPercentage.ts index e56051c3..2466ea52 100644 --- a/src/app/collective-rewards/rewards/hooks/useGetBackerRewardPercentage.ts +++ b/src/app/collective-rewards/rewards/hooks/useGetBackerRewardPercentage.ts @@ -1,13 +1,12 @@ -import { BuilderRewardPercentage, getPercentageData } from '@/app/collective-rewards/rewards/utils' import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi' import { AVERAGE_BLOCKTIME } from '@/lib/constants' import { BackersManagerAddress } from '@/lib/contracts' -import { useEffect, useState } from 'react' +import { useMemo } from 'react' import { Address } from 'viem' import { useReadContract } from 'wagmi' +import { getBackerRewardPercentage } from '../utils' export const useGetBackerRewardPercentage = (builder: Address, timestampInSeconds?: number) => { - const [rewardPercentageData, setRewardPercentageData] = useState() const { data, isLoading, error } = useReadContract({ address: BackersManagerAddress, abi: BackersManagerAbi, @@ -18,18 +17,14 @@ export const useGetBackerRewardPercentage = (builder: Address, timestampInSecond }, }) - useEffect(() => { - if (!data) return + const backerRewardPercentage = useMemo(() => { + const [previous, next, cooldownEndTime] = data || [0n, 0n, 0n] - const [previous, next, cooldownEndTime] = data - - const percentageData = getPercentageData(previous, next, cooldownEndTime, timestampInSeconds) - - setRewardPercentageData(percentageData) + return getBackerRewardPercentage(previous, next, cooldownEndTime, timestampInSeconds) }, [data, timestampInSeconds]) return { - data: rewardPercentageData, + data: backerRewardPercentage, isLoading, error, } diff --git a/src/app/collective-rewards/rewards/hooks/useGetBackersRewardPercentage.ts b/src/app/collective-rewards/rewards/hooks/useGetBackersRewardPercentage.ts index 42f63327..889c84db 100644 --- a/src/app/collective-rewards/rewards/hooks/useGetBackersRewardPercentage.ts +++ b/src/app/collective-rewards/rewards/hooks/useGetBackersRewardPercentage.ts @@ -1,10 +1,11 @@ -import { BuilderRewardPercentage, getPercentageData } from '@/app/collective-rewards/rewards/utils' import { BuilderRegistryAbi } from '@/lib/abis/v2/BuilderRegistryAbi' import { AVERAGE_BLOCKTIME } from '@/lib/constants' import { BackersManagerAddress } from '@/lib/contracts' import { useMemo } from 'react' import { Address } from 'viem' import { useReadContracts } from 'wagmi' +import { BackerRewardPercentage } from '../types' +import { getBackerRewardPercentage } from '../utils' export const useGetBackersRewardPercentage = (builders: Address[], timestampInSeconds?: number) => { const contractCalls = builders?.map( @@ -28,7 +29,7 @@ export const useGetBackersRewardPercentage = (builders: Address[], timestampInSe }, }) - type ReturnType = BuilderRewardPercentage + type ReturnType = BackerRewardPercentage const buildersMap = useMemo( () => builders.reduce>((acc, gauge, index) => { @@ -36,13 +37,19 @@ export const useGetBackersRewardPercentage = (builders: Address[], timestampInSe return {} as Record } - const [current, next, cooldownEndTime] = (contractResults[index].result || [0n, 0n, 0n]) as [ + const [previous, next, cooldownEndTime] = (contractResults[index].result || [0n, 0n, 0n]) as [ bigint, bigint, bigint, ] - const result = getPercentageData(current, next, cooldownEndTime, timestampInSeconds) - acc[gauge] = result + const backerRewardPercentage = getBackerRewardPercentage( + previous, + next, + cooldownEndTime, + timestampInSeconds, + ) + + acc[gauge] = backerRewardPercentage return acc }, {}), diff --git a/src/app/collective-rewards/rewards/types.ts b/src/app/collective-rewards/rewards/types.ts index 65248771..c7f0c8f6 100644 --- a/src/app/collective-rewards/rewards/types.ts +++ b/src/app/collective-rewards/rewards/types.ts @@ -5,14 +5,15 @@ export type Token = { address: Address } -export type Currency = { - value: number +export type RewardAmount = { + value: bigint + price: number symbol: string + currency: string } export type Reward = { - crypto: Currency - fiat: Currency + amount: RewardAmount logo?: JSX.Element } @@ -30,3 +31,9 @@ export type RewardDetails = { } export type BuilderRewardDetails = RewardDetails & { gauge: Address } + +export type BackerRewardPercentage = { + current: bigint + next: bigint + cooldownEndTime: bigint +} diff --git a/src/app/collective-rewards/rewards/utils/formatMetrics.test.ts b/src/app/collective-rewards/rewards/utils/formatMetrics.test.ts deleted file mode 100644 index 66e7768c..00000000 --- a/src/app/collective-rewards/rewards/utils/formatMetrics.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { formatMetrics, formatOnchainFraction } from './formatMetrics' - -describe('formatOnchainFraction', () => { - it('should format onchain fraction correctly with default decimals', () => { - const amount = BigInt('1000000000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('1') - }) - - it('should format onchain fraction correctly with custom display decimals', () => { - const amount = BigInt('1234567890000000000') - const result = formatOnchainFraction(amount, 3) - expect(result).toBe('1.234') - }) - - it('should round to the lower number', () => { - const amount = BigInt('1237567890000000000') - const result = formatOnchainFraction(amount, 2) - expect(result).toBe('1.23') - }) - - it('should format onchain fraction correctly with custom decimals', () => { - const amount = BigInt('123456789') - const result = formatOnchainFraction(amount, 2, 9) - expect(result).toBe('0.12') - }) - - it('should handle zero amount correctly', () => { - const amount = BigInt('0') - const result = formatOnchainFraction(amount) - expect(result).toBe('0') - }) - - it('should handle numbers without decimals', () => { - const amount = BigInt('9000000000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('9') - }) - - it('should handle numbers without decimals and more than 1 character', () => { - const amount = BigInt('90000000000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('90') - }) - - it('should handle numbers with decimals and 3 characters', () => { - const amount = BigInt('900561000000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('900.56') - }) - - it('should handle numbers with decimals and 3 characters', () => { - const amount = BigInt('9000127000000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('9000.12') - }) - - it('should handle very big numbers', () => { - const amount = BigInt('987654321123456789012300000000000000') - const result = formatOnchainFraction(amount) - expect(result).toBe('987654321123456789.01') - }) -}) diff --git a/src/app/collective-rewards/rewards/utils/formatMetrics.ts b/src/app/collective-rewards/rewards/utils/formatMetrics.ts deleted file mode 100644 index 648fd1b8..00000000 --- a/src/app/collective-rewards/rewards/utils/formatMetrics.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { formatCurrency, toFixed } from '@/lib/utils' -import { formatUnits } from 'viem' - -export const formatMetrics = (amount: number, price: number, symbol: string, currency: string) => ({ - amount: `${toFixed(amount)} ${symbol}`, - fiatAmount: `= ${currency} ${formatCurrency(amount * price, currency)}`, -}) - -export const formatOnchainFraction = (amount: bigint, displayDecimals = 2, decimals = 18) => { - const formattedAmount = formatUnits(amount, decimals) - const length = - formattedAmount.indexOf('.') >= 0 - ? formattedAmount.indexOf('.') + displayDecimals + 1 - : formattedAmount.length - return formattedAmount.slice(0, length) -} diff --git a/src/app/collective-rewards/rewards/utils/formatter.test.ts b/src/app/collective-rewards/rewards/utils/formatter.test.ts new file mode 100644 index 00000000..53acd373 --- /dev/null +++ b/src/app/collective-rewards/rewards/utils/formatter.test.ts @@ -0,0 +1,204 @@ +import { describe, it, expect, test } from 'vitest' +import { formatMetrics, formatCurrency, formatSymbol, getFiatAmount } from './formatter' +import { parseEther } from 'viem' + +const formatFiatAmount = (amount: string, currency: string, currencySymbol = '') => + `= ${currency} ${currencySymbol}${amount}` + +describe('formatter', () => { + const oneEther = parseEther('1') + const halfEther = parseEther('.5') + const oneWei = 1n + + describe('formatMetrics', () => { + let symbol = 'RIF' + let currency = 'USD' + let currencySymbol = '$' + test.each([ + { + amount: oneEther, + price: 10, + symbol, + currency, + expected: { amount: `1 ${symbol}`, fiatAmount: formatFiatAmount('10.00', currency, currencySymbol) }, + }, + { + amount: halfEther, + price: 10, + symbol, + currency, + expected: { amount: `<1 ${symbol}`, fiatAmount: formatFiatAmount('5.00', currency, currencySymbol) }, + }, + { + amount: oneWei, + price: 10, + symbol, + currency, + expected: { amount: `<0 ${symbol}`, fiatAmount: formatFiatAmount('<0.01', currency) }, + }, + ])( + 'formatMetrics($amount, $price, $symbol, $currency) -> $expected.amount, $expected.fiatAmount', + ({ amount, price, symbol, currency, expected }) => { + expect(formatMetrics(amount, price, symbol, currency)).toEqual(expected) + }, + ) + + symbol = 'RBTC' + currency = 'USD' + currencySymbol = '$' + test.each([ + { + amount: oneEther, + price: 10, + symbol, + currency, + expected: { + amount: `1.00000 ${symbol}`, + fiatAmount: formatFiatAmount('10.00', currency, currencySymbol), + }, + }, + { + amount: halfEther, + price: 10, + symbol, + currency, + expected: { + amount: `0.50000 ${symbol}`, + fiatAmount: formatFiatAmount('5.00', currency, currencySymbol), + }, + }, + { + amount: oneWei, + price: 10, + symbol, + currency, + expected: { amount: `<0.00001 ${symbol}`, fiatAmount: formatFiatAmount('<0.01', currency) }, + }, + ])( + 'formatMetrics($amount, $price, $symbol, $currency) -> $expected.amount, $expected.fiatAmount', + ({ amount, price, symbol, currency, expected }) => { + expect(formatMetrics(amount, price, symbol, currency)).toEqual(expected) + }, + ) + + symbol = 'RIF' + currency = 'EUR' + currencySymbol = '€' + test.each([ + { + amount: oneEther, + price: 10, + symbol, + currency, + expected: { amount: `1 ${symbol}`, fiatAmount: formatFiatAmount('10.00', currency, currencySymbol) }, + }, + { + amount: halfEther, + price: 10, + symbol, + currency, + expected: { amount: `<1 ${symbol}`, fiatAmount: formatFiatAmount('5.00', currency, currencySymbol) }, + }, + { + amount: oneWei, + price: 10, + symbol, + currency, + expected: { amount: `<0 ${symbol}`, fiatAmount: formatFiatAmount('<0.01', currency) }, + }, + ])( + 'formatMetrics($amount, $price, $symbol, $currency) -> $expected.amount, $expected.fiatAmount', + ({ amount, price, symbol, currency, expected }) => { + expect(formatMetrics(amount, price, symbol, currency)).toEqual(expected) + }, + ) + }) + describe('getFiatAmount', () => { + test.each([ + { amount: 0n, price: 0, expected: 0 }, + { amount: oneEther, price: 10, expected: 10 }, + { amount: halfEther, price: 10, expected: 5 }, + { amount: oneWei, price: 10, expected: 0.00000000000000001 }, + ])('getFiatAmount($amount, $price) -> $expected', ({ amount, price, expected }) => { + expect(getFiatAmount(amount, price)).toBe(expected) + }) + }) + describe('formatCurrency', () => { + test.each([ + { value: 0, expected: '$0.00' }, + { currency: 'USD', value: 0, expected: '$0.00' }, + { currency: 'EUR', value: 0, expected: '€0.00' }, + ])('should format $currency properly with $value', ({ currency, value, expected }) => { + expect(formatCurrency(value, currency)).toBe(expected) + }) + + test.each([ + { value: 5, expected: '$5.00' }, + { currency: 'USD', value: 5, expected: '$5.00' }, + { currency: 'EUR', value: 5, expected: '€5.00' }, + ])('should format $currency properly with $value', ({ currency, value, expected }) => { + expect(formatCurrency(value, currency)).toBe(expected) + }) + + test.each([ + { value: 5.00001, expected: '$5.00' }, + { currency: 'USD', value: 5.00001, expected: '$5.00' }, + { currency: 'EUR', value: 5.00001, expected: '€5.00' }, + ])('should format $currency properly with $value', ({ currency, value, expected }) => { + expect(formatCurrency(value, currency)).toBe(expected) + }) + + test.each([ + { value: 5.01, expected: '$5.01' }, + { currency: 'USD', value: 5.01, expected: '$5.01' }, + { currency: 'EUR', value: 5.01, expected: '€5.01' }, + ])('should format $currency properly with $value', ({ currency, value, expected }) => { + expect(formatCurrency(value, currency)).toBe(expected) + }) + + test.each([ + { value: 0.001, expected: '<0.01' }, + { currency: 'USD', value: 0.001, expected: '<0.01' }, + { currency: 'EUR', value: 0.001, expected: '<0.01' }, + ])('should format $currency properly with $value', ({ currency, value, expected }) => { + expect(formatCurrency(value, currency)).toBe(expected) + }) + }) + describe('formatSymbol', () => { + test.each([ + { symbol: 'Symbol', value: 0n, expected: '0' }, + { symbol: 'RIF', value: 0n, expected: '0' }, + { symbol: 'RBTC', value: 0n, expected: '0' }, + { symbol: 'stRIF', value: 0n, expected: '0' }, + ])('should format $symbol properly with $value', ({ symbol, value, expected }) => { + expect(formatSymbol(value, symbol)).toBe(expected) + }) + + test.each([ + { symbol: 'Symbol', value: oneEther, expected: '1.00' }, + { symbol: 'RIF', value: oneEther, expected: '1' }, + { symbol: 'RBTC', value: oneEther, expected: '1.00000' }, + { symbol: 'stRIF', value: oneEther, expected: '1' }, + ])('should format $symbol properly with $value', ({ symbol, value, expected }) => { + expect(formatSymbol(value, symbol)).toBe(expected) + }) + + test.each([ + { symbol: 'Symbol', value: halfEther, expected: '0.50' }, + { symbol: 'RIF', value: halfEther, expected: '<1' }, + { symbol: 'RBTC', value: halfEther, expected: '0.50000' }, + { symbol: 'stRIF', value: halfEther, expected: '<1' }, + ])('should format $symbol properly with $value', ({ symbol, value, expected }) => { + expect(formatSymbol(value, symbol)).toBe(expected) + }) + + test.each([ + { symbol: 'Symbol', value: oneWei, expected: '<0.01' }, + { symbol: 'RIF', value: oneWei, expected: '<0' }, + { symbol: 'RBTC', value: oneWei, expected: '<0.00001' }, + { symbol: 'stRIF', value: oneWei, expected: '<0' }, + ])('should format $symbol properly with $value', ({ symbol, value, expected }) => { + expect(formatSymbol(value, symbol)).toBe(expected) + }) + }) +}) diff --git a/src/app/collective-rewards/rewards/utils/formatter.ts b/src/app/collective-rewards/rewards/utils/formatter.ts new file mode 100644 index 00000000..c81ebf6a --- /dev/null +++ b/src/app/collective-rewards/rewards/utils/formatter.ts @@ -0,0 +1,80 @@ +import { formatUnits, formatEther } from 'viem' + +export const formatMetrics = (amount: bigint, price: number, symbol: string, currency: string) => { + const fiatAmount = getFiatAmount(amount, price) + + return { + amount: `${formatSymbol(amount, symbol)} ${symbol}`, + fiatAmount: `= ${currency} ${formatCurrency(fiatAmount, currency)}`, + } +} + +export const getFiatAmount = (amount: bigint, price: number) => { + const amountInEther = Number(formatEther(amount)) + return amountInEther * price +} + +export const formatCurrency = (value: number, currency = 'USD'): string => { + if (value > 0 && value < 0.01) { + return '<0.01' + } + + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency, + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(value) +} + +type SymbolFormatOptions = { + decimals: number + displayDecimals: number +} + +const strif = { + decimals: 18, + displayDecimals: 0, +} + +const rif = { + decimals: 18, + displayDecimals: 0, +} + +const rbtc = { + decimals: 18, + displayDecimals: 5, +} + +const symbols: { [key: string]: SymbolFormatOptions } = { + rif, + rbtc, + strif, +} + +export const formatSymbol = (value: bigint, symbol: string) => { + if (!value) { + return '0' + } + const { decimals, displayDecimals } = symbols[symbol.toLocaleLowerCase()] ?? { + decimals: 18, + displayDecimals: 2, + } + const amount = Number(formatUnits(value, decimals)) + const minimumAmount = 1 / Math.pow(10, decimals) + + if (amount > 0 && amount <= minimumAmount) { + return `<0${displayDecimals > 0 ? '.'.padEnd(displayDecimals, '0') + '1' : ''}` + } + + if (amount > 0 && amount < 1 && displayDecimals === 0) { + return '<1' + } + + return new Intl.NumberFormat('en-US', { + minimumFractionDigits: displayDecimals, + maximumFractionDigits: displayDecimals, + roundingMode: 'floor', + }).format(amount) +} diff --git a/src/app/collective-rewards/rewards/utils/getBackerRewardPercentage.ts b/src/app/collective-rewards/rewards/utils/getBackerRewardPercentage.ts new file mode 100644 index 00000000..f6ccab5c --- /dev/null +++ b/src/app/collective-rewards/rewards/utils/getBackerRewardPercentage.ts @@ -0,0 +1,15 @@ +export const getBackerRewardPercentage = ( + previous: bigint, + next: bigint, + cooldownEndTime: bigint, + timestampInSeconds?: number, +) => { + const currentTimestamp = timestampInSeconds ?? Math.floor(Date.now() / 1000) + const current = currentTimestamp < cooldownEndTime ? previous : next + + return { + current, + next, + cooldownEndTime, + } +} diff --git a/src/app/collective-rewards/rewards/utils/getPercentageData.ts b/src/app/collective-rewards/rewards/utils/getPercentageData.ts deleted file mode 100644 index 47435f5b..00000000 --- a/src/app/collective-rewards/rewards/utils/getPercentageData.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { toPercentage } from '@/app/collective-rewards/rewards' - -// TODO: this is a duplicate of BackerRewardPercentage -export type BuilderRewardPercentage = { - current: number - next: number - cooldownEndTime: bigint -} - -// TODO: get the percentage in bigint -export const getPercentageData = ( - previous: bigint, - next: bigint, - cooldownEndTime: bigint, - timestampInSeconds?: number, -) => { - const currentTimestamp = timestampInSeconds ?? Math.floor(Date.now() / 1000) - const previousPercentage = toPercentage(previous) - const nextPercentage = toPercentage(next) - let currentPercentage = currentTimestamp < cooldownEndTime ? previousPercentage : nextPercentage - currentPercentage = Math.round(currentPercentage * 100) / 100 - - const percentageData: BuilderRewardPercentage = { - current: currentPercentage, - next: nextPercentage, - cooldownEndTime, - } - return percentageData -} diff --git a/src/app/collective-rewards/rewards/utils/index.ts b/src/app/collective-rewards/rewards/utils/index.ts index 77dfca72..044a139a 100644 --- a/src/app/collective-rewards/rewards/utils/index.ts +++ b/src/app/collective-rewards/rewards/utils/index.ts @@ -1,5 +1,4 @@ -export * from './formatMetrics' +export * from './formatter' export * from './getLastCycleRewards' export * from './getNotifyRewardAmount' -export * from './getPercentageData' -export * from './toPercentage' +export * from './getBackerRewardPercentage' diff --git a/src/app/collective-rewards/rewards/utils/toPercentage.ts b/src/app/collective-rewards/rewards/utils/toPercentage.ts deleted file mode 100644 index 1f316dac..00000000 --- a/src/app/collective-rewards/rewards/utils/toPercentage.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { parseEther } from 'viem' - -export const toPercentage = (value: bigint) => Number((value * 100n) / parseEther('1')) diff --git a/src/app/collective-rewards/shared/components/Table/TableCells.tsx b/src/app/collective-rewards/shared/components/Table/TableCells.tsx index 9f1c90e6..e4b4ac85 100644 --- a/src/app/collective-rewards/shared/components/Table/TableCells.tsx +++ b/src/app/collective-rewards/shared/components/Table/TableCells.tsx @@ -1,5 +1,5 @@ import { AllocationsContext } from '@/app/collective-rewards/allocations/context' -import { BuilderRewardPercentage, Reward } from '@/app/collective-rewards/rewards' +import { BackerRewardPercentage, formatMetrics, Reward } from '@/app/collective-rewards/rewards' import { TableHeader } from '@/app/collective-rewards/shared' import { BuilderStateFlags } from '@/app/collective-rewards/types' import { @@ -17,43 +17,53 @@ import { Label, Typography } from '@/components/Typography' import { cn, formatCurrency, shortAddress, toFixed } from '@/lib/utils' import { FC, memo, useContext, useMemo } from 'react' import { FaArrowDown, FaArrowUp, FaCircle } from 'react-icons/fa' -import { Address, isAddress } from 'viem' +import { Address, isAddress, parseEther, parseUnits } from 'viem' -export function getFormattedCurrency(value: number, symbol: string) { - const formattedCurrency = formatCurrency(value, symbol) - return `${formattedCurrency.substring(0, 1)}${symbol} ${formattedCurrency.substring(1)}` +type RewardCellValueProps = { + reward: Reward +} + +const RewardCellValue: FC = ({ reward }) => { + const { + amount: { value, price, symbol, currency }, + logo, + } = reward + + const { amount, fiatAmount } = formatMetrics(value, price, symbol, currency) + + return ( +
+
+ +
{logo}
+
+ +
+ ) } type RewardCellProps = { tableHeader: TableHeader rewards: Reward[] } - export const RewardCell: FC = ({ tableHeader: { className }, rewards }) => (
- {rewards && - rewards.map(({ crypto: { value, symbol }, fiat: { value: fiatValue, symbol: fiatSymbol }, logo }) => ( -
- {/* TODO: if the value is very small, should we show it in Gwei/wei? */} - -
- -
{logo}
-
- -
- ))} + {rewards && rewards.map((reward, index) => )}
) export const LazyRewardCell = memo(RewardCell, ({ rewards: prevReward }, { rewards: nextReward }) => - prevReward.every((reward, key) => reward.fiat.value === nextReward[key].fiat.value), + prevReward.every( + (reward, key) => + reward.amount.value === nextReward[key].amount.value && + reward.amount.price === nextReward[key].amount.price, + ), ) type BuilderStatusFlagProps = { @@ -131,9 +141,10 @@ export const BuilderNameCell: FC = ({ type BackerRewardsPercentageProps = { tableHeader: TableHeader - percentage: BuilderRewardPercentage | null + percentage: BackerRewardPercentage | null } +const toPercentage = (value: bigint) => Number((value * 100n) / parseEther('1')) export const BackerRewardsPercentage: FC = ({ tableHeader: { className }, percentage, @@ -141,7 +152,10 @@ export const BackerRewardsPercentage: FC = ({ const renderDelta = useMemo(() => { if (!percentage) return null - const deltaPercentage = percentage.next - percentage.current + const current = toPercentage(percentage.current) + const next = toPercentage(percentage.next) + + const deltaPercentage = next - current if (deltaPercentage > 0) { const colorGreen = '#1bc47d' return ( @@ -165,7 +179,7 @@ export const BackerRewardsPercentage: FC = ({ return (
-
{percentage?.current}
+
{percentage ? toPercentage(percentage.current) : ''}
{renderDelta}
diff --git a/src/app/collective-rewards/utils/getCombinedFiatAmount.ts b/src/app/collective-rewards/utils/getCombinedFiatAmount.ts new file mode 100644 index 00000000..7e59fae9 --- /dev/null +++ b/src/app/collective-rewards/utils/getCombinedFiatAmount.ts @@ -0,0 +1,9 @@ +import { formatEther } from 'viem' +import { RewardAmount } from '../rewards' + +export const getCombinedFiatAmount = (values: Array): number => { + return values.reduce((acc, { value, price }) => { + const amountInEther = Number(formatEther(value)) + return acc + amountInEther * price + }, 0) +} diff --git a/src/app/collective-rewards/utils/index.ts b/src/app/collective-rewards/utils/index.ts index eae5a322..b7241961 100644 --- a/src/app/collective-rewards/utils/index.ts +++ b/src/app/collective-rewards/utils/index.ts @@ -5,3 +5,4 @@ export * from './getMostAdvancedProposal' export * from './handleErrors' export * from './removeBrackets' export * from './isBuilderOperational' +export * from './getCombinedFiatAmount' diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index ad7d8a30..b057ea3c 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -17,7 +17,7 @@ const VARIANTS = { } export const Footer = ({ variant = 'login' }: Props) => ( -