diff --git a/apps/namadillo/src/App/Common/GasFeeModal.tsx b/apps/namadillo/src/App/Common/GasFeeModal.tsx index 230418881..849944537 100644 --- a/apps/namadillo/src/App/Common/GasFeeModal.tsx +++ b/apps/namadillo/src/App/Common/GasFeeModal.tsx @@ -13,7 +13,6 @@ import { useAtomValue } from "jotai"; import { IoClose } from "react-icons/io5"; import { twMerge } from "tailwind-merge"; import { GasConfig } from "types"; -import { unknownAsset } from "utils/assets"; import { getDisplayGasFee } from "utils/gas"; import { FiatCurrency } from "./FiatCurrency"; import { TokenCurrency } from "./TokenCurrency"; @@ -34,6 +33,7 @@ const useBuildGasOption = ({ gasPriceTable: GasPriceTable | undefined; }) => { const chainAssetsMap = useAtomValue(chainAssetsMapAtom); + const gasDollarMap = useAtomValue( tokenPricesFamily(gasPriceTable?.map((item) => item.token) ?? []) @@ -54,10 +54,12 @@ const useBuildGasOption = ({ ...override, }; - const displayAmount = getDisplayGasFee(option); + const displayGasFee = getDisplayGasFee(option, chainAssetsMap); + const { totalDisplayAmount: displayAmount, asset } = displayGasFee; + const { symbol } = asset; + const price = gasDollarMap[option.gasToken]; const dollar = price ? price.multipliedBy(displayAmount) : undefined; - const selected = !gasConfig.gasLimit.isEqualTo(0) && option.gasLimit.isEqualTo(gasConfig.gasLimit) && @@ -67,10 +69,6 @@ const useBuildGasOption = ({ const disabled = gasConfig.gasLimit.isEqualTo(0) || gasConfig.gasPrice.isEqualTo(0); - const asset = - chainAssetsMap[option.gasToken] ?? unknownAsset(option.gasToken); - const symbol = asset.symbol; - return { option, selected, diff --git a/apps/namadillo/src/App/Common/TransactionFee.tsx b/apps/namadillo/src/App/Common/TransactionFee.tsx index d2d5a100c..dc9c23211 100644 --- a/apps/namadillo/src/App/Common/TransactionFee.tsx +++ b/apps/namadillo/src/App/Common/TransactionFee.tsx @@ -1,28 +1,21 @@ -import { chainAssetsMapAtom } from "atoms/chain"; -import { useAtomValue } from "jotai"; -import { GasConfig } from "types"; -import { unknownAsset } from "utils/assets"; -import { getDisplayGasFee } from "utils/gas"; +import BigNumber from "bignumber.js"; import { TokenCurrency } from "./TokenCurrency"; -export const TransactionFee = ({ - gasConfig, -}: { - gasConfig: GasConfig; -}): JSX.Element => { - const chainAssetsMap = useAtomValue(chainAssetsMapAtom); - - const { gasToken } = gasConfig; - const asset = chainAssetsMap[gasToken] ?? unknownAsset(gasToken); - - const amount = getDisplayGasFee(gasConfig); +type TransactionFeeProps = { + displayAmount: BigNumber; + symbol: string; +}; +export const TransactionFee = ({ + displayAmount, + symbol, +}: TransactionFeeProps): JSX.Element => { return (
Transaction fee:{" "}
diff --git a/apps/namadillo/src/App/Common/TransactionFeeButton.tsx b/apps/namadillo/src/App/Common/TransactionFeeButton.tsx index 11e605ff1..96a988ed0 100644 --- a/apps/namadillo/src/App/Common/TransactionFeeButton.tsx +++ b/apps/namadillo/src/App/Common/TransactionFeeButton.tsx @@ -1,5 +1,8 @@ +import { chainAssetsMapAtom } from "atoms/chain"; import { TransactionFeeProps } from "hooks/useTransactionFee"; -import { useState } from "react"; +import { useAtomValue } from "jotai"; +import { useMemo, useState } from "react"; +import { getDisplayGasFee } from "utils/gas"; import { GasFeeModal } from "./GasFeeModal"; import { TransactionFee } from "./TransactionFee"; @@ -9,6 +12,11 @@ export const TransactionFeeButton = ({ feeProps: TransactionFeeProps; }): JSX.Element => { const [modalOpen, setModalOpen] = useState(false); + const chainAssetsMap = useAtomValue(chainAssetsMapAtom); + const gasDisplayAmount = useMemo(() => { + return getDisplayGasFee(feeProps.gasConfig, chainAssetsMap); + }, [feeProps]); + return ( <> {modalOpen && ( setModalOpen(false)} /> diff --git a/apps/namadillo/src/App/Ibc/ShieldAllPanel.tsx b/apps/namadillo/src/App/Ibc/ShieldAllPanel.tsx index 0d6f4cde4..b1ed6573e 100644 --- a/apps/namadillo/src/App/Ibc/ShieldAllPanel.tsx +++ b/apps/namadillo/src/App/Ibc/ShieldAllPanel.tsx @@ -6,9 +6,7 @@ import { Stack, } from "@namada/components"; import svgImg from "App/Assets/ShieldedParty.svg"; -import { TransactionFee } from "App/Common/TransactionFee"; import { SelectedWallet } from "App/Transfer/SelectedWallet"; -import { getIbcGasConfig } from "integrations/utils"; import { useEffect, useMemo, useState } from "react"; import { AddressWithAssetAndAmount, @@ -32,7 +30,6 @@ type ShieldAllPanelProps = { }; export const ShieldAllPanel = ({ - registry, wallet, walletAddress, isLoading, @@ -77,7 +74,7 @@ export const ShieldAllPanel = ({ [selectableAssets] ); - const gasConfig = getIbcGasConfig(registry); + //const gasConfig = getIbcGasConfig(registry); return ( @@ -118,7 +115,7 @@ export const ShieldAllPanel = ({
- {gasConfig && } + {/*gasConfig && */}
} {changeFeeEnabled ? feeProps && - : gasConfig && } + : gasDisplayAmount && + gasAsset && ( + + ) + } diff --git a/apps/namadillo/src/App/Transfer/TransferModule.tsx b/apps/namadillo/src/App/Transfer/TransferModule.tsx index 15d640e70..72ad999cb 100644 --- a/apps/namadillo/src/App/Transfer/TransferModule.tsx +++ b/apps/namadillo/src/App/Transfer/TransferModule.tsx @@ -2,8 +2,10 @@ import { Chain, Chains } from "@chain-registry/types"; import { ActionButton, Stack } from "@namada/components"; import { mapUndefined } from "@namada/utils"; import { InlineError } from "App/Common/InlineError"; +import { chainAssetsMapAtom } from "atoms/chain"; import BigNumber from "bignumber.js"; import { TransactionFeeProps } from "hooks/useTransactionFee"; +import { useAtomValue } from "jotai"; import { useMemo, useState } from "react"; import { Address, @@ -115,9 +117,16 @@ export const TransferModule = ({ const [customAddressActive, setCustomAddressActive] = useState( destination.enableCustomAddress && !destination.availableWallets ); + const chainAssetsMap = useAtomValue(chainAssetsMapAtom); + const [memo, setMemo] = useState(); const gasConfig = gasConfigProp ?? feeProps?.gasConfig; + + const displayGasFee = useMemo(() => { + return gasConfig ? getDisplayGasFee(gasConfig, chainAssetsMap) : undefined; + }, [gasConfig]); + const selectedAsset = mapUndefined( (address) => source.availableAssets?.[address], source.selectedAssetAddress @@ -132,14 +141,16 @@ export const TransferModule = ({ return undefined; } - if (!gasConfig || gasConfig.gasToken !== selectedAssetAddress) { + if (!displayGasFee || !displayGasFee.totalDisplayAmount) { return availableAmount; } - const totalFees = getDisplayGasFee(gasConfig); - const amountMinusFees = availableAmount.minus(totalFees); + const amountMinusFees = availableAmount.minus( + displayGasFee.totalDisplayAmount + ); + return BigNumber.max(amountMinusFees, 0); - }, [source.selectedAssetAddress, source.availableAmount, gasConfig]); + }, [source.selectedAssetAddress, source.availableAmount, displayGasFee]); const validationResult = useMemo((): ValidationResult => { if (!source.wallet) { @@ -323,9 +334,10 @@ export const TransferModule = ({ onChangeAddress={destination.onChangeCustomAddress} memo={memo} onChangeMemo={setMemo} - gasConfig={gasConfig} feeProps={feeProps} changeFeeEnabled={changeFeeEnabled} + gasDisplayAmount={displayGasFee?.totalDisplayAmount} + gasAsset={displayGasFee?.asset} /> {isIbcTransfer && requiresIbcChannels && ( "ibc-transfer-white.png"); jest.mock("../../Common/GasFeeModal", () => null); @@ -8,7 +9,7 @@ import { randomChainMock, } from "App/Transfer/__mocks__/chains"; import { TransferDestination } from "App/Transfer/TransferDestination"; -import BigNumber from "bignumber.js"; +import { namadaAsset } from "utils"; import { walletMock } from "../__mocks__/providers"; import { parseChainInfo } from "../common"; @@ -99,11 +100,8 @@ describe("Component: TransferDestination", () => { render( ); const transactionFee = screen.getByText("Transaction fee:"); diff --git a/apps/namadillo/src/App/Transfer/common.ts b/apps/namadillo/src/App/Transfer/common.ts index c273785a2..c8c77cbf4 100644 --- a/apps/namadillo/src/App/Transfer/common.ts +++ b/apps/namadillo/src/App/Transfer/common.ts @@ -23,5 +23,9 @@ export const parseChainInfo = ( }; export const isShieldedAddress = (address: string): boolean => { - return address.startsWith("znam"); // TODO: integrate with registry + return address.startsWith("znam"); +}; + +export const isTransparentAddress = (address: string): boolean => { + return address.startsWith("tnam"); }; diff --git a/apps/namadillo/src/integrations/utils.ts b/apps/namadillo/src/integrations/utils.ts index b44e15913..3e93c3b23 100644 --- a/apps/namadillo/src/integrations/utils.ts +++ b/apps/namadillo/src/integrations/utils.ts @@ -1,7 +1,11 @@ import { Asset, Chain } from "@chain-registry/types"; import { Bech32Config, ChainInfo, Currency } from "@keplr-wallet/types"; import tokenImage from "App/Common/assets/token.svg"; -import { getRestApiAddressByIndex, getRpcByIndex } from "atoms/integrations"; +import { + getKnownChains, + getRestApiAddressByIndex, + getRpcByIndex, +} from "atoms/integrations"; import BigNumber from "bignumber.js"; import { ChainId, ChainRegistryEntry, GasConfig } from "types"; @@ -27,11 +31,16 @@ export const findRegistryByChainId = ( return undefined; }; -export const findAssetByDenom = ( - registry: ChainRegistryEntry, - denom: string -): Asset | undefined => { - return registry.assets.assets.find((asset) => asset.base === denom); +export const findAssetByDenom = (denom: string): Asset | undefined => { + const chainRegistry = getKnownChains(); + if (!chainRegistry) return undefined; + + for (const registry of chainRegistry) { + const asset = registry.assets.assets.find((asset) => asset.base === denom); + if (asset) return asset; + } + + return undefined; }; export const getAssetImageUrl = (asset?: Asset): string => { diff --git a/apps/namadillo/src/types.ts b/apps/namadillo/src/types.ts index 3621c48a1..e6f90231d 100644 --- a/apps/namadillo/src/types.ts +++ b/apps/namadillo/src/types.ts @@ -42,6 +42,11 @@ export type GasConfig = { gasToken: GasToken; }; +export type GasConfigToDisplay = { + totalDisplayAmount: BigNumber; + asset: Asset; +}; + export type TxGas = Record; export type GasTable = Record; diff --git a/apps/namadillo/src/utils/gas.ts b/apps/namadillo/src/utils/gas.ts index c5dd1ef77..ba194b9e0 100644 --- a/apps/namadillo/src/utils/gas.ts +++ b/apps/namadillo/src/utils/gas.ts @@ -1,8 +1,37 @@ +import { Asset } from "@chain-registry/types"; +import { isTransparentAddress } from "App/Transfer/common"; import BigNumber from "bignumber.js"; -import { GasConfig } from "types"; +import { findAssetByDenom } from "integrations/utils"; +import { Address, GasConfig, GasConfigToDisplay } from "types"; +import { isNamadaAsset, toDisplayAmount } from "utils"; +import { unknownAsset } from "./assets"; -export const getDisplayGasFee = (gasConfig: GasConfig): BigNumber => { - return BigNumber(gasConfig.gasPrice) - .multipliedBy(gasConfig.gasLimit) - .decimalPlaces(6); +export const calculateGasFee = (gasConfig: GasConfig): BigNumber => { + return BigNumber(gasConfig.gasPrice).multipliedBy(gasConfig.gasLimit); +}; + +export const getDisplayGasFee = ( + gasConfig: GasConfig, + chainAssetsMap?: Record +): GasConfigToDisplay => { + const { gasToken } = gasConfig; + let asset: Asset; + + if (isTransparentAddress(gasToken) && chainAssetsMap) { + // The gasConfig token might be the address of the token on Namada chain + asset = chainAssetsMap[gasToken] ?? unknownAsset(gasToken); + } else { + // However, if the gasConfig contains a token used by Keplr, it could be the asset + // denomination unit, like "uosmo" + asset = findAssetByDenom(gasToken) ?? unknownAsset(gasToken); + } + + const totalDisplayAmount = calculateGasFee(gasConfig); + return { + totalDisplayAmount: + isNamadaAsset(asset) ? totalDisplayAmount : ( + toDisplayAmount(asset, totalDisplayAmount).decimalPlaces(6) + ), + asset, + }; };