From 6d2d4f1eecf9bc6e0eeace2ec77024beef68945c Mon Sep 17 00:00:00 2001 From: rAskVAL Date: Thu, 26 Jun 2025 13:32:22 +0300 Subject: [PATCH] SD-102: Refactor shielding fees --- .../definitions/chains/alephZero/alephZero.ts | 2 + .../definitions/chains/arbitrum/arbitrum.ts | 2 + .../utils/definitions/chains/base/base.ts | 1 + .../chains/utils/definitions/definitions.ts | 1 + src/domains/misc/utils/getQueryKey.ts | 36 +++- src/domains/shielder/components/FeeRows.tsx | 167 --------------- .../TokenList/Modals/ConfirmPage.tsx | 80 +++++++- .../Modals/SendModal/SelectAmountPage.tsx | 90 +++++++-- .../TokenList/Modals/SendModal/SendModal.tsx | 44 +--- .../Modals/ShieldModal/SelectAmountPage.tsx | 95 +++++++-- .../Modals/ShieldModal/ShieldModal.tsx | 35 ++-- .../ActivityDetailsModal.tsx | 112 +++++++--- .../shielder/stores/getShielderIndexedDB.ts | 51 ++--- .../utils/useEstimateAllowanceFee.tsx | 99 +++++++++ .../shielder/utils/useFeeBreakdownModal.tsx | 191 ++++++++++++++++++ src/domains/shielder/utils/useSendingFees.tsx | 76 +++++++ src/domains/shielder/utils/useShield.tsx | 29 +-- .../shielder/utils/useShielderFees.tsx | 56 +++-- .../shielder/utils/useShieldingFees.tsx | 104 ++++++++++ src/domains/shielder/utils/useWithdraw.tsx | 4 - 20 files changed, 900 insertions(+), 375 deletions(-) delete mode 100644 src/domains/shielder/components/FeeRows.tsx create mode 100644 src/domains/shielder/utils/useEstimateAllowanceFee.tsx create mode 100644 src/domains/shielder/utils/useFeeBreakdownModal.tsx create mode 100644 src/domains/shielder/utils/useSendingFees.tsx create mode 100644 src/domains/shielder/utils/useShieldingFees.tsx diff --git a/src/domains/chains/utils/definitions/chains/alephZero/alephZero.ts b/src/domains/chains/utils/definitions/chains/alephZero/alephZero.ts index 00e4169..a365bd5 100644 --- a/src/domains/chains/utils/definitions/chains/alephZero/alephZero.ts +++ b/src/domains/chains/utils/definitions/chains/alephZero/alephZero.ts @@ -16,6 +16,7 @@ const config: ChainConfig = { shielderConfig: { shielderContractAddress: '0x5A0dea46A96a5b578c9cf1730f461eD0bC9C32c6', relayerUrl: 'https://shielder-relayer-v2.azero.dev/azero', + shieldingFeeEstimatorUrl: 'https://fee-estimator.azero.dev/azero/get_fees', }, nativeCurrency: { decimals: 18, @@ -57,6 +58,7 @@ const config: ChainConfig = { shielderConfig: { shielderContractAddress: '0x5B496EB83172B52885f80207426042eA21597077', relayerUrl: 'https://shielder-relayer-v2.test.azero.dev/azero-testnet', + shieldingFeeEstimatorUrl: 'https://fee-estimator.test.azero.dev/azero-testnet/get_fees', }, nativeCurrency: { decimals: 18, diff --git a/src/domains/chains/utils/definitions/chains/arbitrum/arbitrum.ts b/src/domains/chains/utils/definitions/chains/arbitrum/arbitrum.ts index d635ccd..edc9ca7 100644 --- a/src/domains/chains/utils/definitions/chains/arbitrum/arbitrum.ts +++ b/src/domains/chains/utils/definitions/chains/arbitrum/arbitrum.ts @@ -15,6 +15,7 @@ const config: ChainConfig = { shielderConfig: { shielderContractAddress: '0x5A0dea46A96a5b578c9cf1730f461eD0bC9C32c6', relayerUrl: 'https://shielder-relayer-v2.azero.dev/arbitrum', + shieldingFeeEstimatorUrl: 'https://fee-estimator.azero.dev/arbitrum/get_fees', }, ChainIcon, NativeTokenIcon, @@ -30,6 +31,7 @@ const config: ChainConfig = { shielderConfig: { shielderContractAddress: '0xca2Ca45089Fa4E2BBef2BF26E632a8CA9CD1aFd0', relayerUrl: 'https://shielder-relayer-v2.test.azero.dev/arbitrum-testnet', + shieldingFeeEstimatorUrl: 'https://fee-estimator.test.azero.dev/arbitrum-testnet/get_fees', }, ChainIcon, NativeTokenIcon, diff --git a/src/domains/chains/utils/definitions/chains/base/base.ts b/src/domains/chains/utils/definitions/chains/base/base.ts index 9a3efaf..66904cc 100644 --- a/src/domains/chains/utils/definitions/chains/base/base.ts +++ b/src/domains/chains/utils/definitions/chains/base/base.ts @@ -13,6 +13,7 @@ const config: ChainConfig = { shielderConfig: { shielderContractAddress: '0x2098a5f59DAB63F1a2aB7C0715DA437D1efB012B', relayerUrl: 'https://shielder-relayer-v2.test.azero.dev/base-testnet', + shieldingFeeEstimatorUrl: 'https://fee-estimator.test.azero.dev/base-testnet/get_fees', }, ChainIcon, NativeTokenIcon, diff --git a/src/domains/chains/utils/definitions/definitions.ts b/src/domains/chains/utils/definitions/definitions.ts index b0d2f06..ff95335 100644 --- a/src/domains/chains/utils/definitions/definitions.ts +++ b/src/domains/chains/utils/definitions/definitions.ts @@ -18,6 +18,7 @@ export type Definition = { shielderConfig?: { shielderContractAddress: Address, relayerUrl: string, + shieldingFeeEstimatorUrl?: string, }, urlPathSegment: string, } & Pick; diff --git a/src/domains/misc/utils/getQueryKey.ts b/src/domains/misc/utils/getQueryKey.ts index 8657473..ab85d44 100644 --- a/src/domains/misc/utils/getQueryKey.ts +++ b/src/domains/misc/utils/getQueryKey.ts @@ -3,16 +3,19 @@ import type { Address } from 'viem'; import { NetworkEnvironment } from 'src/domains/chains/types/misc'; export const QUERY_KEYS = { - shielderFees: 'shielderFees', + wasmCryptoClient: 'wasm-crypto-client', shielderClient: 'shielder-client', - shielderTransactions: 'shielder-transactions', - tokenDecimals: 'token-decimals', + shielderPrivateKey: 'shielderPrivateKey', tokenName: 'token-name', tokenSymbol: 'token-symbol', + tokenDecimals: 'token-decimals', tokenPublicBalance: 'token-public-balance', tokenShieldedBalance: 'token-shielded-balance', - shielderPrivateKey: 'shielderPrivateKey', - wasmCryptoClient: 'wasm-crypto-client', + allowanceCheck: 'allowance-check', + allowanceFeeEstimate: 'allowance-fee-estimate', + shieldingFeesEstimate: 'shielding-fees-estimate', + sendingFeesEstimate: 'sending-fees-estimate', + shielderTransactions: 'shielder-transactions', } as const; export const MUTATION_KEYS = { @@ -21,10 +24,29 @@ export const MUTATION_KEYS = { } as const; const getQueryKey = { - shielderFees: (walletAddress: Address, chainId: string) => [ - QUERY_KEYS.shielderFees, + sendingFees: (walletAddress: Address, chainId: string) => [ + QUERY_KEYS.sendingFeesEstimate, + walletAddress, + chainId, + ], + shieldingFees: (chainId: string, tokenType: 'native' | 'erc20') => [ + QUERY_KEYS.shieldingFeesEstimate, + chainId, + tokenType, + ], + allowanceCheck: (tokenAddress: Address, chainId: string, walletAddress: Address, amount: string) => [ + QUERY_KEYS.allowanceCheck, + tokenAddress, + chainId, walletAddress, + amount, + ], + allowanceFeeEstimate: (tokenAddress: Address, chainId: string, walletAddress: Address, amount: string) => [ + QUERY_KEYS.allowanceFeeEstimate, + tokenAddress, chainId, + walletAddress, + amount, ], shielderClient: (chainId: number, shielderPrivateKey: Address) => [ QUERY_KEYS.shielderClient, diff --git a/src/domains/shielder/components/FeeRows.tsx b/src/domains/shielder/components/FeeRows.tsx deleted file mode 100644 index 59474a0..0000000 --- a/src/domains/shielder/components/FeeRows.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { motion, AnimatePresence } from 'framer-motion'; -import { ComponentType, useState } from 'react'; -import styled, { css } from 'styled-components'; - -import CIcon from 'src/domains/misc/components/CIcon'; -import InfoPair from 'src/domains/misc/components/InfoPair'; -import Skeleton from 'src/domains/misc/components/Skeleton'; -import TokenIcon from 'src/domains/misc/components/TokenIcon'; -import isPresent from 'src/domains/misc/utils/isPresent'; -import formatBalance from 'src/domains/numbers/utils/formatBalance'; -import vars from 'src/domains/styling/utils/vars'; - -type FeeConfig = { - label: string, - tokenSymbol?: string, - tokenIcon?: ComponentType, - tokenDecimals?: number, - amount?: bigint, - isError?: boolean, -}; - -type Props = { - config: FeeConfig[], -}; - -const FeeBreakdown = ({ config }: Props) => { - const [isExpanded, setIsExpanded] = useState(false); - const [mainFeeRow, ...additionalFeeRows] = config; - - const hasAdditionalRows = !!additionalFeeRows.length; - - const mainInfoPair = ( - - - {formatBalance({ - balance: mainFeeRow.amount, - decimals: mainFeeRow.tokenDecimals, - options: { formatDecimals: 5 }, - })} - {' '} - {mainFeeRow.tokenSymbol} - - ) : ( - - ) - } - /> - ); - - return ( - - {hasAdditionalRows ? ( - void setIsExpanded(prev => !prev)}> - - {mainInfoPair} - - - - - ) : ( - mainInfoPair - )} - - {hasAdditionalRows && ( - - {isExpanded && ( - - {additionalFeeRows.map(({ label, amount, isError, tokenDecimals, tokenIcon, tokenSymbol }) => ( - - - - - {formatBalance({ - balance: amount, - decimals: tokenDecimals, - options: { formatDecimals: 4 }, - })} - {' '} - {tokenSymbol} - - ) : ( - - ) - } - /> - - ))} - - )} - - )} - - ); -}; - -export default FeeBreakdown; - -const Container = styled.div` - display: flex; - flex-direction: column; - gap: ${vars('--spacing-xs')}; -`; - -const FeeAmount = styled.div<{ $isError: boolean }>` - display: flex; - align-items: center; - gap: ${vars('--spacing-s')}; - ${({ $isError }) => - $isError && - css` - color: ${vars('--color-status-danger-foreground-1-rest')}; - `} -`; - -const MainRowToggle = styled.button` - display: grid; - grid-template-columns: 16px 1fr 20px; - align-items: center; -`; - -const ChevronIconWrapper = styled(motion.div)` - display: flex; - align-self: center; - - justify-self: end; -`; - -const AdditionalRowsWrapper = styled(motion.div)` - display: flex; - flex-direction: column; - gap: ${vars('--spacing-xs')}; - overflow: hidden; -`; - -const AdditionalRow = styled.div` - display: grid; - grid-template-columns: 16px 1fr 20px; - align-items: center; -`; - -const VerticalConnector = styled.div` - height: 11px; - width: 7px; - margin-left: ${vars('--spacing-xs-nudge')}; - border-bottom: 2px solid ${vars('--color-neutral-stroke-subtle-rest')}; - - border-left: 2px solid ${vars('--color-neutral-stroke-subtle-rest')}; -`; - -const AdditionalInfoPair = styled(InfoPair)` - color: ${vars('--color-neutral-foreground-3-rest')}; -`; diff --git a/src/domains/shielder/components/TokenList/Modals/ConfirmPage.tsx b/src/domains/shielder/components/TokenList/Modals/ConfirmPage.tsx index b6505de..9b74f17 100644 --- a/src/domains/shielder/components/TokenList/Modals/ConfirmPage.tsx +++ b/src/domains/shielder/components/TokenList/Modals/ConfirmPage.tsx @@ -1,15 +1,21 @@ -import { ComponentProps } from 'react'; import styled from 'styled-components'; import { type Address } from 'viem'; +import useChain from 'src/domains/chains/utils/useChain'; import AccountTypeIcon from 'src/domains/misc/components/AccountTypeIcon'; import Button from 'src/domains/misc/components/Button'; +import CIcon from 'src/domains/misc/components/CIcon'; import DoubleBorderBox from 'src/domains/misc/components/DoubleBorderBox'; +import InfoPair from 'src/domains/misc/components/InfoPair'; +import Skeleton from 'src/domains/misc/components/Skeleton'; import TokenIcon from 'src/domains/misc/components/TokenIcon'; import formatAddress from 'src/domains/misc/utils/formatAddress'; +import isPresent from 'src/domains/misc/utils/isPresent'; import formatBalance from 'src/domains/numbers/utils/formatBalance'; -import FeeBreakdown from 'src/domains/shielder/components/FeeRows'; import { Token } from 'src/domains/shielder/components/TokenList'; +import useFeeBreakdownModal from 'src/domains/shielder/utils/useFeeBreakdownModal'; +import useShielderFees from 'src/domains/shielder/utils/useShielderFees'; +import useTokenData from 'src/domains/shielder/utils/useTokenData'; import { typography } from 'src/domains/styling/utils/tokens'; import vars from 'src/domains/styling/utils/vars'; @@ -21,7 +27,6 @@ type Props = { }, addressTo?: Address, addressFrom?: Address, - feeConfig: ComponentProps['config'], amount: bigint, onConfirm: () => void, isLoading?: boolean, @@ -31,7 +36,6 @@ type Props = { const ConfirmPage = ({ token, - feeConfig, amount, addressTo, onConfirm, @@ -41,6 +45,18 @@ const ConfirmPage = ({ buttonLabel, }: Props) => { + const chainConfig = useChain(); + + const operation = addressTo ? 'send' : 'shield'; + const { fees, totalFee, isLoading: areShielderFeesLoading } = useShielderFees({ token, operation, amount }); + + const { + symbolQuery: { data: nativeTokenSymbol }, + decimalsQuery: { data: nativeTokenDecimals }, + } = useTokenData({ isNative: true }, ['symbol', 'decimals']); + + const openFeeBreakdownModal = useFeeBreakdownModal({ fees, totalFee }); + const isButtonDisabled = amount <= 0n || !!hasInsufficientFees || !!isLoading; return ( @@ -77,8 +93,35 @@ const ConfirmPage = ({ )} +
+ +

Est. Total fee

+ + + } + value={ + !areShielderFeesLoading && isPresent(nativeTokenDecimals) && isPresent(totalFee) ? ( + + + {formatBalance({ + balance: totalFee, + decimals: nativeTokenDecimals, + options: { formatDecimals: 5 }, + })} + {' '} + {nativeTokenSymbol} + + ) : ( + + ) + } + /> +