diff --git a/frontend/package.json b/frontend/package.json index 02be8695..613af815 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@hookform/resolvers": "3.9.0", "@polkadot/api": "14.3.1", "@polkadot/react-identicon": "3.11.3", + "@polkadot/util": "13.2.3", "@tanstack/react-query": "5.61.5", "@web3modal/wagmi": "5.1.3", "graphql": "16.9.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a7037864..0fbee06c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@polkadot/react-identicon': specifier: 3.11.3 version: 3.11.3(@polkadot/keyring@13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3))(@polkadot/networks@13.2.3)(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@18.2.0)(react@18.3.1) + '@polkadot/util': + specifier: 13.2.3 + version: 13.2.3 '@tanstack/react-query': specifier: 5.61.5 version: 5.61.5(react@18.3.1) diff --git a/frontend/src/components/formatted-balance/fortmatted-balance.tsx b/frontend/src/components/formatted-balance/fortmatted-balance.tsx new file mode 100644 index 00000000..fd654c7d --- /dev/null +++ b/frontend/src/components/formatted-balance/fortmatted-balance.tsx @@ -0,0 +1,30 @@ +import { formatBalance } from '@polkadot/util'; +import { ComponentProps } from 'react'; +import { formatUnits } from 'viem'; + +import { TruncatedText } from '../layout'; +import { Tooltip } from '../tooltip'; + +type Props = { + value: bigint; + decimals: number; + symbol: string; + tooltipPosition?: ComponentProps['position']; + className?: string; +}; + +function FormattedBalance({ value, decimals, symbol, tooltipPosition, className }: Props) { + const formattedValue = formatUnits(value, decimals); + const compactBalance = formatBalance(value, { decimals, withUnit: symbol, withZero: false }); + + return ( + + + + ); +} + +export { FormattedBalance }; diff --git a/frontend/src/components/formatted-balance/index.ts b/frontend/src/components/formatted-balance/index.ts new file mode 100644 index 00000000..fb3db9fe --- /dev/null +++ b/frontend/src/components/formatted-balance/index.ts @@ -0,0 +1,3 @@ +import { FormattedBalance } from './fortmatted-balance'; + +export { FormattedBalance }; diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index 87f99d31..c6cf0aa5 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -2,6 +2,7 @@ import { Card } from './card'; import { CopyButton } from './copy-button'; import { FeeAndTimeFooter } from './fee-and-time-footer'; import { Input, Checkbox, Radio, Select, Textarea } from './form'; +import { FormattedBalance } from './formatted-balance'; import { Container, Footer, Header, ErrorBoundary, PrivateRoute, Skeleton, TruncatedText } from './layout'; import { LinkButton } from './link-button'; import { Tooltip } from './tooltip'; @@ -24,4 +25,5 @@ export { CopyButton, Tooltip, FeeAndTimeFooter, + FormattedBalance, }; diff --git a/frontend/src/components/tooltip/tooltip.module.scss b/frontend/src/components/tooltip/tooltip.module.scss index 6c0aa6dc..c04803bc 100644 --- a/frontend/src/components/tooltip/tooltip.module.scss +++ b/frontend/src/components/tooltip/tooltip.module.scss @@ -1,30 +1,35 @@ +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + .container, .skeleton { flex-shrink: 0; } .container { - display: flex; - - position: relative; - - svg { - transition: 0.25s opacity; - - &:hover { + &:hover { + .body { opacity: 0.75; } } +} - &:hover { - .tooltip { - opacity: 1; - } - } +.body { + display: flex; + + transition: 0.25s opacity; } .tooltip { position: absolute; + z-index: 11; padding: 10px 16px; @@ -42,18 +47,5 @@ border-radius: 4px; box-shadow: 0px 8px 16px 0px rgba(#000, 0.24); - opacity: 0; - pointer-events: none; - transition: 0.25s opacity; -} - -.top { - bottom: calc(100% + 8px); - left: 50%; - transform: translateX(-50%); -} - -.bottom-end { - top: calc(100% + 8px); - right: 0; + animation: fadeIn 0.25s; } diff --git a/frontend/src/components/tooltip/tooltip.tsx b/frontend/src/components/tooltip/tooltip.tsx index 379ecb1a..d911853b 100644 --- a/frontend/src/components/tooltip/tooltip.tsx +++ b/frontend/src/components/tooltip/tooltip.tsx @@ -1,32 +1,109 @@ -import { ReactNode } from 'react'; +import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { SVGComponent } from '@/types'; -import { cx } from '@/utils'; import { Skeleton } from '../layout'; import QuestionSVG from './question.svg?react'; import styles from './tooltip.module.scss'; -type BaseProps = { - text?: string; - children?: ReactNode; - position?: 'top' | 'bottom-end'; +type Props = { + value?: ReactNode; + position?: 'top' | 'right' | 'bottom-end'; SVG?: SVGComponent; + children?: ReactNode; }; -type TextProps = BaseProps & { text: string }; -type ChildrenProps = BaseProps & { children: ReactNode }; -type Props = TextProps | ChildrenProps; +function TooltipComponent({ value, style }: { value: ReactNode; style: CSSProperties }) { + const [root, setRoot] = useState(); + + useEffect(() => { + const ID = 'tooltip-root'; + const existingRoot = document.getElementById(ID); + + if (existingRoot) return setRoot(existingRoot); + + const newRoot = document.createElement('div'); + newRoot.id = ID; + document.body.appendChild(newRoot); + + setRoot(newRoot); + + return () => { + if (!newRoot) return; + + document.body.removeChild(newRoot); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (!root) return null; + + return createPortal( +
+ {typeof value === 'string' ?

{value}

: value} +
, + root, + ); +} + +function Tooltip({ value, position = 'top', SVG = QuestionSVG, children }: Props) { + const [style, setStyle] = useState(); + const containerRef = useRef(null); + + const handleMouseEnter = () => { + const container = containerRef.current; + + if (!container) return; + + const containerRect = container.getBoundingClientRect(); + + const GAP = 8; + let top = 0; + let left = 0; + let transform = ''; + + switch (position) { + case 'top': { + top = containerRect.top + window.scrollY - GAP; + left = containerRect.left + window.scrollX + containerRect.width / 2; + transform = 'translate(-50%, -100%)'; + + break; + } + + case 'right': { + top = containerRect.top + window.scrollY + containerRect.height / 2; + left = containerRect.right + window.scrollX + GAP; + transform = 'translateY(-50%)'; + + break; + } + case 'bottom-end': { + top = containerRect.bottom + window.scrollY + GAP; + left = containerRect.right - window.scrollX; + transform = 'translate(-100%, 0)'; + + break; + } + + default: + break; + } + + setStyle({ top, left, transform }); + }; -function Tooltip({ text, children, position = 'top', SVG = QuestionSVG }: Props) { return ( -
- +
setStyle(undefined)} + ref={containerRef}> +
{children || }
-
- {text ?

{text}

: children} -
+ {style && }
); } diff --git a/frontend/src/features/history/components/transaction-modal/transaction-modal.tsx b/frontend/src/features/history/components/transaction-modal/transaction-modal.tsx index 886af12d..3d15b14e 100644 --- a/frontend/src/features/history/components/transaction-modal/transaction-modal.tsx +++ b/frontend/src/features/history/components/transaction-modal/transaction-modal.tsx @@ -1,9 +1,8 @@ import { HexString } from '@gear-js/api'; import { getVaraAddress } from '@gear-js/react-hooks'; import { Modal } from '@gear-js/vara-ui'; -import { formatUnits } from 'viem'; -import { CopyButton, FeeAndTimeFooter, LinkButton, TruncatedText } from '@/components'; +import { CopyButton, FeeAndTimeFooter, FormattedBalance, LinkButton, TruncatedText } from '@/components'; import { useEthFee, useVaraFee } from '@/features/swap/hooks'; import { useTokens } from '@/hooks'; import { cx } from '@/utils'; @@ -56,8 +55,6 @@ function TransactionModal({ const sourceSymbol = symbols?.[source as HexString] || 'Unit'; const destinationSymbol = symbols?.[destination as HexString] || 'Unit'; - const formattedAmount = formatUnits(BigInt(amount), decimals?.[source as HexString] ?? 0); - const formattedSenderAddress = isGearNetwork ? getVaraAddress(sender) : sender; const formattedReceiverAddress = isGearNetwork ? receiver : getVaraAddress(receiver); @@ -83,11 +80,14 @@ function TransactionModal({ )} -

+

- - {formattedAmount} {sourceSymbol} - + on @@ -100,9 +100,12 @@ function TransactionModal({ - - {formattedAmount} {destinationSymbol} - + on @@ -123,7 +126,7 @@ function TransactionModal({ To -

+
{renderProgressBar?.()} diff --git a/frontend/src/features/history/components/transaction-pair/transaction-pair.tsx b/frontend/src/features/history/components/transaction-pair/transaction-pair.tsx index a9b4a39f..e6cfbefd 100644 --- a/frontend/src/features/history/components/transaction-pair/transaction-pair.tsx +++ b/frontend/src/features/history/components/transaction-pair/transaction-pair.tsx @@ -1,10 +1,9 @@ import { HexString } from '@gear-js/api'; import { getVaraAddress } from '@gear-js/react-hooks'; -import { formatUnits } from 'viem'; import TokenPlaceholderSVG from '@/assets/token-placeholder.svg?react'; import VaraSVG from '@/assets/vara.svg?react'; -import { Skeleton, TruncatedText } from '@/components'; +import { FormattedBalance, Skeleton, TruncatedText } from '@/components'; import { TOKEN_SVG } from '@/consts'; import { cx } from '@/utils'; @@ -41,8 +40,6 @@ function TransactionPair(props: Props) { const formattedSenderAddress = isGearNetwork ? getVaraAddress(sender) : sender; const formattedReceiverAddress = isGearNetwork ? receiver : getVaraAddress(receiver); - const formattedAmount = formatUnits(BigInt(amount), decimals[sourceHex] ?? 0); - return (
@@ -52,7 +49,13 @@ function TransactionPair(props: Props) {
- + +
@@ -66,7 +69,13 @@ function TransactionPair(props: Props) {
- + +
diff --git a/frontend/src/features/swap/components/account-balance/account-balance.tsx b/frontend/src/features/swap/components/account-balance/account-balance.tsx index 863a6050..c9e7c1e3 100644 --- a/frontend/src/features/swap/components/account-balance/account-balance.tsx +++ b/frontend/src/features/swap/components/account-balance/account-balance.tsx @@ -2,7 +2,7 @@ import { useApi } from '@gear-js/react-hooks'; import { useEffect } from 'react'; import { formatUnits } from 'viem'; -import { Skeleton, Tooltip } from '@/components'; +import { Skeleton, Tooltip, FormattedBalance } from '@/components'; import { isUndefined } from '@/utils'; import DangerSVG from '../../assets/danger.svg?react'; @@ -17,7 +17,7 @@ type Props = ReturnType & { isVaraNetwork: boolean; }; -function AccountBalance({ value, formattedValue, isLoading, isVaraNetwork, submit }: Props) { +function AccountBalance({ data: value, isLoading, isVaraNetwork, submit }: Props) { const { api } = useApi(); const { error } = submit; @@ -31,7 +31,7 @@ function AccountBalance({ value, formattedValue, isLoading, isVaraNetwork, submi // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); - if (isLoading || !formattedValue || !api) return ; + if (isLoading || isUndefined(value) || !api) return ; const decimals = isVaraNetwork ? api.registry.chainDecimals[0] : 18; const symbol = isVaraNetwork ? 'VARA' : 'ETH'; @@ -40,17 +40,22 @@ function AccountBalance({ value, formattedValue, isLoading, isVaraNetwork, submi
- {`${formattedValue} ${symbol}`} +
{isBalanceError && ( - -

{error.message}

+ +

{error.message}

-

- At least {formatUnits(error.requiredValue, decimals)} {symbol} is needed -

-
+

+ At least {formatUnits(error.requiredValue, decimals)} {symbol} is needed +

+ + } + /> )}
); diff --git a/frontend/src/features/swap/components/balance/balance.tsx b/frontend/src/features/swap/components/balance/balance.tsx index f730a004..95fbb15c 100644 --- a/frontend/src/features/swap/components/balance/balance.tsx +++ b/frontend/src/features/swap/components/balance/balance.tsx @@ -1,18 +1,20 @@ import { Button } from '@gear-js/vara-ui'; -import { Skeleton } from '@/components'; +import { FormattedBalance, Skeleton } from '@/components'; +import { isUndefined } from '@/utils'; import styles from './balance.module.scss'; type Props = { - value: string | undefined; - unit: string | undefined; + value: bigint | undefined; + decimals: number | undefined; + symbol: string | undefined; isLoading?: boolean; heading?: string; onMaxButtonClick?: () => void; }; -function Balance({ heading = 'Balance', value, unit, isLoading, onMaxButtonClick }: Props) { +function Balance({ heading = 'Balance', value, decimals, symbol, isLoading, onMaxButtonClick }: Props) { return (
@@ -23,11 +25,14 @@ function Balance({ heading = 'Balance', value, unit, isLoading, onMaxButtonClick )}
-

+

{isLoading && } - {!isLoading && !value && } - {value && unit && `${value} ${unit}`} -

+ {!isLoading && isUndefined(value) && } + + {!isUndefined(value) && !isUndefined(decimals) && symbol && ( + + )} +
); } diff --git a/frontend/src/features/swap/components/ft-allowance-tip/ft-allowance-tip.tsx b/frontend/src/features/swap/components/ft-allowance-tip/ft-allowance-tip.tsx index 6f1490a6..7ae140c2 100644 --- a/frontend/src/features/swap/components/ft-allowance-tip/ft-allowance-tip.tsx +++ b/frontend/src/features/swap/components/ft-allowance-tip/ft-allowance-tip.tsx @@ -35,15 +35,19 @@ function FTAllowanceTip({ allowance, decimals, symbol, amount, isVaraNetwork, is }; return ( - -

- {allowance > 0 - ? `You have already approved ${formattedAllowance} ${symbol} to the ${contractName} contract.` - : `You don't have any approved tokens to the ${contractName} contract yet.`} -

- -

{getSubheading()}

-
+ +

+ {allowance > 0 + ? `You have already approved ${formattedAllowance} ${symbol} to the ${contractName} contract.` + : `You don't have any approved tokens to the ${contractName} contract yet.`} +

+ +

{getSubheading()}

+ + } + /> ); } diff --git a/frontend/src/features/swap/components/swap-form/swap-form.tsx b/frontend/src/features/swap/components/swap-form/swap-form.tsx index 229ea468..6c096947 100644 --- a/frontend/src/features/swap/components/swap-form/swap-form.tsx +++ b/frontend/src/features/swap/components/swap-form/swap-form.tsx @@ -2,6 +2,7 @@ import { useAccount, useApi } from '@gear-js/react-hooks'; import { Button, Select } from '@gear-js/vara-ui'; import { ComponentProps, useState } from 'react'; import { FormProvider } from 'react-hook-form'; +import { parseUnits } from 'viem'; import EthSVG from '@/assets/eth.svg?react'; import VaraSVG from '@/assets/vara.svg?react'; @@ -58,7 +59,7 @@ function SwapForm({ const { fee, ...config } = useFee(); const accountBalance = useAccountBalance(); - const ftBalance = useFTBalance(address, decimals); + const ftBalance = useFTBalance(address); const allowance = useFTAllowance(address); const { account } = useAccount(); @@ -82,8 +83,8 @@ function SwapForm({ address, fee.value, allowance.data, - ftBalance.value, - accountBalance.value, + ftBalance.data, + accountBalance.data, openTransacionModal, ); @@ -98,12 +99,13 @@ function SwapForm({ ); const renderFromBalance = () => { - const balance = isNativeToken ? getMergedBalance(accountBalance, ftBalance, decimals) : ftBalance; + const balance = isNativeToken ? getMergedBalance(accountBalance, ftBalance) : ftBalance; return ( @@ -112,11 +114,11 @@ function SwapForm({ const isBalanceValid = () => { if (!isApiReady || accountBalance.isLoading || config.isLoading) return true; // not valid ofc, but we don't want to render error - if (!accountBalance.value || isUndefined(fee.value)) return false; + if (!accountBalance.data || isUndefined(fee.value)) return false; const requiredBalance = isVaraNetwork ? fee.value + api.existentialDeposit.toBigInt() : fee.value; - return accountBalance.value > requiredBalance; + return accountBalance.data > requiredBalance; }; const getButtonText = () => { @@ -185,7 +187,12 @@ function SwapForm({ /> - + diff --git a/frontend/src/features/swap/hooks/eth/use-eth-ft-balance.ts b/frontend/src/features/swap/hooks/eth/use-eth-ft-balance.ts index 894f1fe4..3238dfb3 100644 --- a/frontend/src/features/swap/hooks/eth/use-eth-ft-balance.ts +++ b/frontend/src/features/swap/hooks/eth/use-eth-ft-balance.ts @@ -1,21 +1,18 @@ import { HexString } from '@gear-js/api'; -import { formatUnits } from 'viem'; import { useReadContract } from 'wagmi'; import { FUNGIBLE_TOKEN_ABI } from '@/consts'; import { useEthAccount, useInvalidateOnBlock } from '@/hooks'; -import { isUndefined } from '@/utils'; import { FUNCTION_NAME } from '../../consts/eth'; const abi = FUNGIBLE_TOKEN_ABI; -function useEthFTBalance(address: HexString | undefined, decimals: number | undefined) { +function useEthFTBalance(address: HexString | undefined) { const ethAccount = useEthAccount(); const enabled = Boolean(address) && Boolean(ethAccount.address); - // TODO: logger - const { data, isLoading, queryKey } = useReadContract({ + const state = useReadContract({ address, abi, functionName: FUNCTION_NAME.FUNGIBLE_TOKEN_BALANCE, @@ -24,12 +21,10 @@ function useEthFTBalance(address: HexString | undefined, decimals: number | unde query: { enabled }, }); + const { queryKey } = state; useInvalidateOnBlock({ queryKey, enabled }); - const value = data; - const formattedValue = !isUndefined(value) && !isUndefined(decimals) ? formatUnits(value, decimals) : undefined; - - return { value, formattedValue, decimals, isLoading }; + return state; } export { useEthFTBalance }; diff --git a/frontend/src/features/swap/hooks/use-swap-form.ts b/frontend/src/features/swap/hooks/use-swap-form.ts index b2e01b35..b507e1d4 100644 --- a/frontend/src/features/swap/hooks/use-swap-form.ts +++ b/frontend/src/features/swap/hooks/use-swap-form.ts @@ -2,18 +2,18 @@ import { useAlert } from '@gear-js/react-hooks'; import { zodResolver } from '@hookform/resolvers/zod'; import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; +import { formatUnits } from 'viem'; import { WriteContractErrorType } from 'wagmi/actions'; import { z } from 'zod'; -import { logger } from '@/utils'; +import { isUndefined, logger } from '@/utils'; import { FIELD_NAME, DEFAULT_VALUES, ADDRESS_SCHEMA } from '../consts'; import { FormattedValues } from '../types'; import { getAmountSchema, getErrorMessage, getMergedBalance } from '../utils'; type Values = { - value: bigint | undefined; - formattedValue: string | undefined; + data: bigint | undefined; isLoading: boolean; }; @@ -28,7 +28,7 @@ function useSwapForm( ) { const alert = useAlert(); - const valueSchema = getAmountSchema(isNativeToken, accountBalance.value, ftBalance.value, decimals); + const valueSchema = getAmountSchema(isNativeToken, accountBalance.data, ftBalance.data, decimals); const addressSchema = isVaraNetwork ? ADDRESS_SCHEMA.ETH : ADDRESS_SCHEMA.VARA; const schema = z.object({ @@ -64,12 +64,14 @@ function useSwapForm( }, [disabled]); const setMaxBalance = () => { - const balance = isNativeToken ? getMergedBalance(accountBalance, ftBalance, decimals) : ftBalance; - if (!balance.formattedValue) throw new Error('Balance is not defined'); + const balance = isNativeToken ? getMergedBalance(accountBalance, ftBalance) : ftBalance; + if (isUndefined(decimals)) throw new Error('Decimals are not defined'); + if (isUndefined(balance.data)) throw new Error('Balance is not defined'); + const formattedValue = formatUnits(balance.data, decimals); const shouldValidate = formState.isSubmitted; // validating only if validation was already fired - setValue(FIELD_NAME.VALUE, balance.formattedValue, { shouldValidate }); + setValue(FIELD_NAME.VALUE, formattedValue, { shouldValidate }); }; return { form, amount, handleSubmit, setMaxBalance }; diff --git a/frontend/src/features/swap/types/hooks.ts b/frontend/src/features/swap/types/hooks.ts index 62513916..1e5fe364 100644 --- a/frontend/src/features/swap/types/hooks.ts +++ b/frontend/src/features/swap/types/hooks.ts @@ -7,14 +7,13 @@ type BalanceValues = { formattedValue: string | undefined; }; -type UseAccountBalance = () => BalanceValues & { +type UseAccountBalance = () => { + data: bigint | undefined; isLoading: boolean; }; -type UseFTBalance = ( - ftAddress: HexString | undefined, - decimals: number | undefined, -) => BalanceValues & { +type UseFTBalance = (ftAddress: HexString | undefined) => { + data: bigint | undefined; isLoading: boolean; }; diff --git a/frontend/src/features/swap/utils.ts b/frontend/src/features/swap/utils.ts index 0156c943..7f1554be 100644 --- a/frontend/src/features/swap/utils.ts +++ b/frontend/src/features/swap/utils.ts @@ -1,5 +1,5 @@ import { ExtrinsicFailedData, HexString } from '@gear-js/api'; -import { BaseError, formatUnits, parseUnits } from 'viem'; +import { BaseError, parseUnits } from 'viem'; import { WriteContractErrorType } from 'wagmi/actions'; import { z } from 'zod'; @@ -49,26 +49,15 @@ const getOptions = (addresses: FTAddressPair[] | undefined, symbols: Record, - ftBalance: ReturnType, - decimals: number | undefined, -) => { +const getMergedBalance = (accountBalance: ReturnType, ftBalance: ReturnType) => { const isLoading = accountBalance.isLoading || ftBalance.isLoading; - if ( - isUndefined(accountBalance.value) || - isUndefined(ftBalance.value) || - isUndefined(decimals) || - !accountBalance.formattedValue || - !ftBalance.formattedValue - ) - return { value: undefined, formattedValue: undefined, isLoading }; - - const value = accountBalance.value + ftBalance.value; - const formattedValue = formatUnits(value, decimals); + const data = + !isUndefined(accountBalance.data) && !isUndefined(ftBalance.data) + ? accountBalance.data + ftBalance.data + : undefined; - return { value, formattedValue, isLoading }; + return { data, isLoading }; }; // string is only for cancelled sign and send popup error during useSendProgramTransaction diff --git a/frontend/src/features/token-tracker/components/card/balance-card.tsx b/frontend/src/features/token-tracker/components/card/balance-card.tsx index 1f17d89c..2ca88e7d 100644 --- a/frontend/src/features/token-tracker/components/card/balance-card.tsx +++ b/frontend/src/features/token-tracker/components/card/balance-card.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren } from 'react'; import TokenPlaceholderSVG from '@/assets/token-placeholder.svg?react'; -import { Skeleton } from '@/components'; +import { FormattedBalance, Skeleton } from '@/components'; import { SVGComponent } from '@/types'; import { cx } from '@/utils'; @@ -9,17 +9,18 @@ import styles from './balance-card.module.scss'; type Props = PropsWithChildren & { SVG: SVGComponent; - value: string; + value: bigint; + decimals: number; symbol: string; locked?: boolean; }; -function BalanceCard({ locked, value, SVG, symbol, children }: Props) { +function BalanceCard({ locked, value, decimals, SVG, symbol, children }: Props) { return (
- {value} {symbol} + {children} diff --git a/frontend/src/features/token-tracker/components/token-tracker-modal/token-tracker-modal.tsx b/frontend/src/features/token-tracker/components/token-tracker-modal/token-tracker-modal.tsx index e6422d96..7cd1aadf 100644 --- a/frontend/src/features/token-tracker/components/token-tracker-modal/token-tracker-modal.tsx +++ b/frontend/src/features/token-tracker/components/token-tracker-modal/token-tracker-modal.tsx @@ -1,6 +1,5 @@ import { getTypedEntries, useAccount, useAlert } from '@gear-js/react-hooks'; import { Modal, Button } from '@gear-js/vara-ui'; -import { formatUnits } from 'viem'; import EthSVG from '@/assets/eth.svg?react'; import TokenPlaceholderSVG from '@/assets/token-placeholder.svg?react'; @@ -16,7 +15,7 @@ import { BalanceCard } from '../card'; import styles from './token-tracker-modal.module.scss'; type Props = { - lockedBalance: { value: bigint | undefined; formattedValue: string | undefined }; + lockedBalance: bigint | undefined; close: () => void; }; @@ -63,7 +62,8 @@ function TokenTrackerModal({ lockedBalance, close }: Props) {
  • - {Boolean(varaLockedBalance.value) && ( - + {Boolean(lockedBalance) && ( + )}
  • - {isOpen && } + {isOpen && } ); } diff --git a/frontend/src/hooks/use-eth-account-balance.ts b/frontend/src/hooks/use-eth-account-balance.ts index ea73db93..2a3b47d2 100644 --- a/frontend/src/hooks/use-eth-account-balance.ts +++ b/frontend/src/hooks/use-eth-account-balance.ts @@ -1,29 +1,20 @@ -import { formatEther } from 'viem'; import { useBalance } from 'wagmi'; import { useInvalidateOnBlock } from './common'; import { useEthAccount } from './use-eth-account'; -const withPrecision = (value: string) => { - // simplest solution without rounding for now - const digitsCount = 3; - - return value.slice(0, value.indexOf('.') + digitsCount + 1); -}; - function useEthAccountBalance() { const ethAccount = useEthAccount(); - const { data, isLoading, queryKey } = useBalance({ + const state = useBalance({ address: ethAccount?.address, + query: { select: ({ value }) => value }, }); + const { queryKey } = state; useInvalidateOnBlock({ queryKey }); - const { value } = data || {}; - const formattedValue = data ? withPrecision(formatEther(data.value)) : undefined; - - return { value, formattedValue, isLoading }; + return state; } export { useEthAccountBalance }; diff --git a/frontend/src/hooks/use-vara-account-balance.ts b/frontend/src/hooks/use-vara-account-balance.ts index 9a13cdcb..a65aea6e 100644 --- a/frontend/src/hooks/use-vara-account-balance.ts +++ b/frontend/src/hooks/use-vara-account-balance.ts @@ -1,15 +1,15 @@ -import { useAccount, useBalanceFormat, useDeriveBalancesAll } from '@gear-js/react-hooks'; +import { useAccount, useDeriveBalancesAll } from '@gear-js/react-hooks'; function useVaraAccountBalance() { const { account } = useAccount(); - const { getFormattedBalance } = useBalanceFormat(); - const { data, isLoading } = useDeriveBalancesAll({ address: account?.address, watch: true }); - const { transferable, availableBalance } = data || {}; - const value = (transferable || availableBalance)?.toBigInt(); - const formattedValue = value !== undefined ? getFormattedBalance(value).value : undefined; - - return { value, formattedValue, isLoading }; + return useDeriveBalancesAll({ + address: account?.address, + watch: true, + query: { + select: (value) => (value.transferable || value.availableBalance).toBigInt(), + }, + }); } export { useVaraAccountBalance }; diff --git a/frontend/src/hooks/use-vara-ft-balance.ts b/frontend/src/hooks/use-vara-ft-balance.ts index 882cc1d1..47ecb270 100644 --- a/frontend/src/hooks/use-vara-ft-balance.ts +++ b/frontend/src/hooks/use-vara-ft-balance.ts @@ -1,11 +1,9 @@ import { HexString } from '@gear-js/api'; import { useAccount, useProgram, useProgramQuery } from '@gear-js/react-hooks'; -import { formatUnits } from 'viem'; import { VftProgram } from '@/consts'; -import { isUndefined } from '@/utils'; -function useVaraFTBalance(address: HexString | undefined, decimals: number | undefined) { +function useVaraFTBalance(address: HexString | undefined) { const { account } = useAccount(); const { data: program } = useProgram({ @@ -13,7 +11,7 @@ function useVaraFTBalance(address: HexString | undefined, decimals: number | und id: address, }); - const { data, isLoading } = useProgramQuery({ + return useProgramQuery({ program, serviceName: 'vft', functionName: 'balanceOf', @@ -21,11 +19,6 @@ function useVaraFTBalance(address: HexString | undefined, decimals: number | und query: { enabled: Boolean(account) }, watch: true, }); - - const value = data; - const formattedValue = !isUndefined(value) && !isUndefined(decimals) ? formatUnits(value, decimals) : undefined; - - return { value, formattedValue, decimals, isLoading }; } export { useVaraFTBalance };