Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@dao-xyz/borsh": "^5.1.5",
"@ledgerhq/hw-transport": "^6.31.4",
"@ledgerhq/hw-transport-webusb": "^6.29.4",
"@namada/sdk": "0.23.0",
"@namada/sdk": "0.24.0-beta.2",
"@zondax/ledger-namada": "^2.0.0",
"bignumber.js": "^9.1.1",
"buffer": "^6.0.3",
Expand All @@ -54,7 +54,7 @@
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
"@namada/sdk-node": "0.23.0",
"@namada/sdk-node": "0.24.0-beta.1",
"@svgr/webpack": "^6.3.1",
"@types/chrome": "^0.0.237",
"@types/firefox-webext-browser": "^94.0.1",
Expand Down
4 changes: 2 additions & 2 deletions apps/namadillo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@keplr-wallet/types": "^0.12.136",
"@namada/chain-registry": "^1.5.2",
"@namada/indexer-client": "4.0.5",
"@namada/sdk-multicore": "0.23.0",
"@namada/sdk-multicore": "0.24.0-beta.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/query-core": "^5.40.0",
"@tanstack/react-query": "^5.40.0",
Expand Down Expand Up @@ -79,7 +79,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@namada/sdk-node": "0.23.0",
"@namada/sdk-node": "0.24.0-beta.2",
"@namada/vite-esbuild-plugin": "^1.0.1",
"@playwright/test": "^1.24.1",
"@svgr/webpack": "^6.5.1",
Expand Down
6 changes: 6 additions & 0 deletions apps/namadillo/public/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@
#masp_indexer_url = ""
#localnet_enabled = false
#fathom_site_id = ""

[frontend_fee."*"]
transparent_target = "tnam1qrxsru5rdu4he400xny6p779fcw7xuftsgjnmzup"
shielded_target = "znam1494fmm9qd4frr7jrydnxlcyt57nhngzutxnzgh6y70mkvh23d9z0jk2aasxh4p8dn2kczlgpans"
percentage = 0.01

189 changes: 189 additions & 0 deletions apps/namadillo/src/App/Common/TransferFee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Stack } from "@namada/components";
import { namadaRegistryChainAssetsMapAtom } from "atoms/integrations";
import { tokenPricesFamily } from "atoms/prices/atoms";
import BigNumber from "bignumber.js";
import { TransactionFeeProps } from "hooks/useTransactionFee";
import { useAtomValue } from "jotai";
import { useMemo, useState } from "react";
import { GoInfo } from "react-icons/go";
import { IoIosArrowDown } from "react-icons/io";
import { twMerge } from "tailwind-merge";
import { Address, FrontendFeeEntry } from "types";
import { calculateFrontendFeeAmount } from "utils/frontendFee";
import { getDisplayGasFee } from "utils/gas";
import { GasFeeModal } from "./GasFeeModal";
import { IconTooltip } from "./IconTooltip";

type FrontendFeeInfo = {
fee: FrontendFeeEntry;
displayAmount?: BigNumber;
token?: Address;
};
export const TransferFee = ({
feeProps,
inOrOutOfMASP,
isShieldedTransfer = false,
frontendFeeInfo,
showButton = true,
}: {
feeProps: TransactionFeeProps;
inOrOutOfMASP: boolean;
isShieldedTransfer?: boolean;
frontendFeeInfo?: FrontendFeeInfo;
showButton?: boolean;
}): JSX.Element => {
const [modalOpen, setModalOpen] = useState(false);
const [feeDetailsOpen, setFeeDetailsOpen] = useState(false);

const chainAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom);

const chainAssetsMapData = chainAssetsMap.data;

const gasDisplayAmount = useMemo(() => {
if (!chainAssetsMapData) {
return;
}

return getDisplayGasFee(feeProps.gasConfig, chainAssetsMapData);
}, [feeProps, chainAssetsMapData]);

const gasToken = gasDisplayAmount?.asset.address;
const frontendFeeToken = frontendFeeInfo?.token;
const tokenAddresses = [gasToken, frontendFeeToken].filter(
(address) => typeof address !== "undefined"
);

const gasDollarMap =
useAtomValue(tokenPricesFamily(tokenAddresses)).data ?? {};

const [frontendFeeAmount, frontendFeeFiatAmount, symbol] = useMemo((): [
BigNumber?,
BigNumber?,
string?,
] => {
if (
frontendFeeInfo &&
frontendFeeInfo.token &&
frontendFeeInfo.displayAmount
) {
const feeAmount = calculateFrontendFeeAmount(
frontendFeeInfo.displayAmount,
frontendFeeInfo.fee
);
const dollarPrice = gasDollarMap[frontendFeeInfo.token];
const fiatFeeAmount = feeAmount.multipliedBy(dollarPrice);
const symbol = chainAssetsMapData?.[frontendFeeInfo.token]?.symbol;

return [feeAmount, fiatFeeAmount, symbol];
}
return [];
}, [gasDollarMap, frontendFeeInfo]);

const fiatAmount = useMemo(() => {
if (
!gasDisplayAmount ||
!gasDollarMap ||
!gasToken ||
!gasDollarMap[gasToken]
) {
return;
}
const dollarPrice = gasDollarMap[gasToken];
let fiatAmount =
gasDisplayAmount.totalDisplayAmount.multipliedBy(dollarPrice);

if (inOrOutOfMASP && frontendFeeFiatAmount) {
fiatAmount = fiatAmount.plus(frontendFeeFiatAmount);
}
return fiatAmount;
}, [gasDisplayAmount, gasDollarMap, inOrOutOfMASP, gasToken]);

return (
<Stack className="w-full text-sm text-neutral-300">
<Stack direction="horizontal" className="justify-between items-center">
<div
className="cursor-pointer select-none underline "
onClick={() => setFeeDetailsOpen((opened) => !opened)}
>
{feeDetailsOpen ? "Hide fee settings" : "Fee settings"}
</div>
<div>
Total Fee {fiatAmount ? `$${fiatAmount.decimalPlaces(6)}` : ""}
</div>
</Stack>
{feeDetailsOpen && (
<Stack className="w-full">
<Stack
direction="horizontal"
className="justify-between items-center"
>
<div>Gas fee:</div>
<Stack direction="horizontal" gap={2} className="items-center">
<div>
{gasDisplayAmount ?
gasDisplayAmount.totalDisplayAmount.toString()
: ""}{" "}
</div>
{!showButton && gasDisplayAmount?.asset.symbol}
{showButton && (
<div className="flex items-center gap-2">
<button
type="button"
className={twMerge(
"flex items-center gap-1",
"border rounded-sm px-2 py-1 text-xs",
"transition-all cursor-pointer hover:text-yellow"
)}
onClick={() => setModalOpen(true)}
>
<span className="text- font-medium">
{gasDisplayAmount?.asset.symbol || ""}
</span>
<IoIosArrowDown />
</button>
</div>
)}
</Stack>
</Stack>
{inOrOutOfMASP && frontendFeeInfo && (
<Stack
direction="horizontal"
className="justify-between items-center"
>
<Stack direction="horizontal" gap={2} className="items-center">
MASP fee
<div className="flex relative items-center">
<IconTooltip
className="bg-transparent w-5 h-5"
tooltipClassName="text-yellow text-center w-[340px]"
icon={<GoInfo className="w-5 h-5 text-yellow" />}
text={
<div className="w-full">
MASP fees are set by the Namadillo Host and may
<br /> vary accross Namadillo instances
</div>
}
/>
</div>
</Stack>

<div>
{frontendFeeAmount && symbol ?
`${frontendFeeAmount.toString()} ${symbol}`
: "0"}
</div>
</Stack>
)}
</Stack>
)}
{modalOpen && (
<GasFeeModal
feeProps={feeProps}
onClose={() => setModalOpen(false)}
isShielded={isShieldedTransfer}
chainAssetsMap={chainAssetsMap.data || {}}
/>
)}
</Stack>
);
};
30 changes: 21 additions & 9 deletions apps/namadillo/src/App/Ibc/IbcTransfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,28 @@ export const IbcTransfer = ({
);
const { trackEvent } = useFathomTracker();
const { storeTransaction } = useTransactionActions();
const shielded = isShieldedAddress(destinationAddress ?? "");

const availableDisplayAmount = mapUndefined((baseDenom) => {
return userAssets ? userAssets[baseDenom]?.amount : undefined;
}, selectedAssetWithAmount?.asset?.address);

const amountForGasCalc =
amount && availableDisplayAmount ?
BigNumber.minimum(amount, availableDisplayAmount)
: undefined;

const { transferToNamada, gasConfig } = useIbcTransaction({
const { transferToNamada, transactionFeeProps } = useIbcTransaction({
registry,
sourceAddress,
sourceChannel,
destinationChannel,
shielded: isShieldedAddress(destinationAddress ?? ""),
shielded,
selectedAsset: selectedAssetWithAmount?.asset,
amount: amountForGasCalc,
});

// DERIVED VALUES
const shielded = isShieldedAddress(destinationAddress ?? "");
const availableDisplayAmount = mapUndefined((baseDenom) => {
return userAssets ? userAssets[baseDenom]?.amount : undefined;
}, selectedAssetWithAmount?.asset?.address);
const namadaAddress = useMemo(() => {
return (
defaultAccounts.data?.find(
Expand Down Expand Up @@ -124,11 +131,16 @@ export const IbcTransfer = ({
}: OnSubmitTransferParams): Promise<void> => {
try {
invariant(registry?.chain, "Error: Chain not selected");
invariant(selectedAssetWithAmount?.asset, "Error: Asset not selected");
invariant(displayAmount, "Error: Amount not specified");
invariant(destinationAddress, "Error: Destination address not specified");

setGeneralErrorMessage("");
setCurrentStatus("Submitting...");

const result = await transferToNamada.mutateAsync({
destinationAddress: destinationAddress ?? "",
displayAmount: new BigNumber(displayAmount ?? "0"),
destinationAddress,
displayAmount: BigNumber(displayAmount),
memo,
onUpdateStatus: setCurrentStatus,
});
Expand Down Expand Up @@ -161,7 +173,7 @@ export const IbcTransfer = ({
isShieldedAddress: shielded,
onChangeAddress: setDestinationAddress,
}}
gasConfig={gasConfig.data}
feeProps={transactionFeeProps}
isSubmitting={
transferToNamada.isPending ||
/* isSuccess means that the transaction has been broadcasted, but doesn't take
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export function usePerformOsmosisSwapTx(): UsePerformOsmosisSwapResult {
account: transparentAccount,
params: [params],
gasConfig: feeProps.gasConfig,
frontendFee: feeProps.frontendFee,
});

setStatus(SwapStatus.awaitingSignature());
Expand Down
41 changes: 19 additions & 22 deletions apps/namadillo/src/App/Transfer/TransferDestination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { AccountType } from "@namada/types";
import { shortenAddress } from "@namada/utils";
import { ConnectProviderButton } from "App/Common/ConnectProviderButton";
import { TokenAmountCard } from "App/Common/TokenAmountCard";
import { TransactionFee } from "App/Common/TransactionFee";
import { TransactionFeeButton } from "App/Common/TransactionFeeButton";
import { TransferFee } from "App/Common/TransferFee";
import { routes } from "App/routes";
import {
isIbcAddress,
Expand All @@ -24,7 +23,7 @@ import { useAtomValue } from "jotai";
import { useCallback, useEffect, useState } from "react";
import { GoChevronDown } from "react-icons/go";
import { useLocation } from "react-router-dom";
import { Address } from "types";
import { Address, FrontendFeeEntry } from "types";
import namadaShieldedIcon from "./assets/namada-shielded.svg";
import namadaTransparentIcon from "./assets/namada-transparent.svg";
import semiTransparentEye from "./assets/semi-transparent-eye.svg";
Expand All @@ -40,8 +39,6 @@ type TransferDestinationProps = {
isShieldedTx?: boolean;
isSubmitting?: boolean;
walletAddress?: string;
gasDisplayAmount?: BigNumber;
gasAsset?: Asset;
feeProps?: TransactionFeeProps;
destinationAsset?: Asset;
amount?: BigNumber;
Expand All @@ -53,14 +50,13 @@ type TransferDestinationProps = {
onChangeMemo?: (address: string) => void;
isShielding?: boolean;
isUnshielding?: boolean;
frontendFee?: FrontendFeeEntry;
};

export const TransferDestination = ({
isShieldedAddress,
isShieldedTx = false,
isSubmitting,
gasDisplayAmount,
gasAsset,
feeProps,
destinationAsset,
amount,
Expand All @@ -72,6 +68,7 @@ export const TransferDestination = ({
onChangeMemo,
isShielding = false,
isUnshielding = false,
frontendFee,
}: TransferDestinationProps): JSX.Element => {
const { data: accounts } = useAtomValue(allDefaultAccountsAtom);
const [isModalOpen, setIsModalOpen] = useState(false);
Expand Down Expand Up @@ -307,21 +304,21 @@ export const TransferDestination = ({

{!isSubmitting && (
<footer className="flex mt-10">
{changeFeeEnabled ?
feeProps && (
<TransactionFeeButton
feeProps={feeProps}
isShieldedTransfer={isShieldedTx}
/>
)
: gasDisplayAmount &&
gasAsset && (
<TransactionFee
displayAmount={gasDisplayAmount}
symbol={gasAsset.symbol}
/>
)
}
{feeProps && (
<TransferFee
feeProps={feeProps}
isShieldedTransfer={isShieldedTx}
inOrOutOfMASP={isShielding || isUnshielding}
showButton={changeFeeEnabled}
frontendFeeInfo={
frontendFee && {
fee: frontendFee,
displayAmount: amount,
token: sourceAsset?.address,
}
}
/>
)}
</footer>
)}
</div>
Expand Down
Loading