From 72a23081b8b7f501bc6c6c88011802faa0f6b740 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 19 Jan 2024 07:15:38 +0700 Subject: [PATCH] refactor: gas gas gas [FRE-446] --- src/components/AssetInput.tsx | 33 +++----------- src/components/SettingsDialog/GasSetting.tsx | 26 +++++------ src/components/SwapWidget/SwapDetails.tsx | 7 ++- src/components/SwapWidget/SwapWidget.tsx | 8 +++- src/components/SwapWidget/useSwapWidget.ts | 43 +++++++++++++++++-- .../TransactionDialogContent.tsx | 4 +- src/components/TransactionDialog/index.tsx | 2 +- src/context/settings.ts | 7 ++- 8 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index b16fd251..03849ab0 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -2,7 +2,6 @@ import { BigNumber } from "bignumber.js"; import { clsx } from "clsx"; import { formatUnits } from "ethers"; import { MouseEventHandler, useMemo } from "react"; -import toast from "react-hot-toast"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { useSettingsStore } from "@/context/settings"; @@ -46,7 +45,7 @@ function AssetInput({ context, isLoading, }: Props) { - const { assetsByChainID, getNativeAssets, getFeeDenom } = useAssets(); + const { assetsByChainID, getNativeAssets } = useAssets(); const assets = useMemo(() => { if (!chain) { @@ -85,37 +84,17 @@ function AssetInput({ const handleMax: MouseEventHandler = (event) => { if (!selectedAssetBalance || !chain || !asset) return; - let amount = new BigNumber(selectedAssetBalance); + let balance = new BigNumber(selectedAssetBalance); if (event.shiftKey) { - onAmountChange?.(amount.toString()); + onAmountChange?.(balance.toString()); return; } - const feeDenom = getFeeDenom(chain.chainID); + const { gasComputed } = useSettingsStore.getState(); + gasComputed && (balance = balance.minus(gasComputed)); - // if selected asset is the fee denom, subtract the fee - if (feeDenom && feeDenom.denom === asset.denom) { - const { gas } = useSettingsStore.getState(); - - const { gasPrice } = chain.feeAssets.find((a) => a.denom === feeDenom.denom)!; - - const fee = new BigNumber(gasPrice.average).multipliedBy(gas).shiftedBy(-(feeDenom.decimals ?? 6)); // denom decimals - - amount = amount.minus(fee); - if (amount.isNegative()) { - amount = new BigNumber(0); - toast.error( -

- Insufficient Balance -
- You need to have at least ≈{fee.toString()} to accommodate gas fees. -

, - ); - } - } - - onAmountChange?.(amount.toString()); + onAmountChange?.(balance.toString()); }; return ( diff --git a/src/components/SettingsDialog/GasSetting.tsx b/src/components/SettingsDialog/GasSetting.tsx index 81e49e9e..767f58d6 100644 --- a/src/components/SettingsDialog/GasSetting.tsx +++ b/src/components/SettingsDialog/GasSetting.tsx @@ -1,27 +1,29 @@ -import { clsx } from "clsx"; - import { useSettingsStore } from "@/context/settings"; +import { formatNumberWithCommas, formatNumberWithoutCommas } from "@/utils/number"; export const GasSetting = () => { - const currentValue = useSettingsStore((state) => state.gas); + const currentValue = useSettingsStore((state) => state.gasMultiplier); return (
-

Gas

+

Gas Multiplier

{ - const value = Math.max(0, +event.target.value); - useSettingsStore.setState({ gas: value.toString() }); + let latest = formatNumberWithoutCommas(event.target.value); + latest = latest.replace(/^[0]{2,}/, "0"); // Remove leading zeros + latest = latest.replace(/[^\d,]/g, ""); // Remove non-numeric and non-decimal characters + latest = latest.replace(/[,]{2,}/g, ","); // Remove multiple commas + + const value = Math.max(0, +latest); + useSettingsStore.setState({ gasMultiplier: value.toString() }); }} />
diff --git a/src/components/SwapWidget/SwapDetails.tsx b/src/components/SwapWidget/SwapDetails.tsx index 3cbadddf..1b0ff337 100644 --- a/src/components/SwapWidget/SwapDetails.tsx +++ b/src/components/SwapWidget/SwapDetails.tsx @@ -33,7 +33,7 @@ export const SwapDetails = ({ }: Props) => { const [open, control] = useDisclosureKey("swapDetailsCollapsible"); - const { gas, slippage } = useSettingsStore(); + const { gasComputed, slippage } = useSettingsStore(); const axelarTransferOperation = useMemo(() => { for (const op of route.operations) { @@ -172,7 +172,10 @@ export const SwapDetails = ({ - {parseFloat(gas).toLocaleString()} + {gasComputed && + parseFloat(gasComputed).toLocaleString("en-US", { + maximumFractionDigits: 8, + })}
Bridging Fee
diff --git a/src/components/SwapWidget/SwapWidget.tsx b/src/components/SwapWidget/SwapWidget.tsx index b6f3a9f5..1e53ec11 100644 --- a/src/components/SwapWidget/SwapWidget.tsx +++ b/src/components/SwapWidget/SwapWidget.tsx @@ -285,7 +285,13 @@ export function SwapWidget() { routeWarningMessage={routeWarningMessage} /> {insufficientBalance && ( -

Insufficient Balance

+

+ {typeof insufficientBalance === "string" ? ( + <>Insufficient Balance: {insufficientBalance} + ) : ( + <>Insufficient Balance + )} +

)}
)} diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index 11f88ff1..fcf27c36 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -13,6 +13,7 @@ import { createWithEqualityFn as create } from "zustand/traditional"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { useAnyDisclosureOpen } from "@/context/disclosures"; +import { useSettingsStore } from "@/context/settings"; import { trackWallet } from "@/context/track-wallet"; import { useAccount } from "@/hooks/useAccount"; import { useBalancesByChain } from "@/hooks/useBalancesByChain"; @@ -90,6 +91,9 @@ export function useSwapWidget() { const { data: balances } = useBalancesByChain(srcAccount?.address, srcChain, srcAssets); + const gasComputed = useSettingsStore((state) => state.gasComputed); + const gasMultiplier = useSettingsStore((state) => state.gasMultiplier); + // #endregion ///////////////////////////////////////////////////////////////////////////// @@ -116,8 +120,12 @@ export function useSwapWidget() { const balanceStr = balances[asset.denom] ?? "0"; const balance = parseFloat(formatUnits(balanceStr, asset.decimals)); + if (gasComputed && parsedAmount + +gasComputed > balance) { + return `You need to have at least more than ≈${gasComputed} to accommodate gas fees.`; + } + return parsedAmount > balance; - }, [amountIn, balances, srcAsset]); + }, [amountIn, balances, gasComputed, srcAsset]); const swapPriceImpactPercent = useMemo(() => { if (!route?.swapPriceImpactPercent) return undefined; @@ -277,6 +285,33 @@ export function useSwapWidget() { // #region -- side effects + /** + * compute gas amount on source chain change + */ + useEffect(() => { + return useSwapFormStore.subscribe( + (state) => state.sourceChain, + (srcChain) => { + if (!srcChain) return; + const feeDenom = getFeeDenom(srcChain.chainID); + if (!feeDenom) return; + const { gasPrice } = srcChain.feeAssets.find(({ denom }) => { + return denom === feeDenom.denom; + })!; + useSettingsStore.setState({ + gasComputed: new BigNumber(gasPrice.average) + .multipliedBy(gasMultiplier) + .shiftedBy(-(feeDenom.decimals ?? 6)) + .toString(), + }); + }, + { + equalityFn: shallow, + fireImmediately: true, + }, + ); + }, [gasMultiplier, getFeeDenom]); + /** * sync either amount in or out depending on {@link direction} */ @@ -374,7 +409,7 @@ export function useSwapWidget() { } if (wallet) { try { - await wallet.client.addChain?.({ + await wallet.client?.addChain?.({ chain: { bech32_prefix: wallet.chain.bech32_prefix, chain_id: wallet.chain.chain_id, @@ -454,7 +489,7 @@ export function useSwapWidget() { } if (wallet) { try { - await wallet.client.addChain?.({ + await wallet.client?.addChain?.({ chain: { bech32_prefix: wallet.chain.bech32_prefix, chain_id: wallet.chain.chain_id, @@ -528,7 +563,7 @@ export function useSwapWidget() { }); if (wallet) { try { - await wallet.client.addChain?.({ + await wallet.client?.addChain?.({ chain: { bech32_prefix: wallet.chain.bech32_prefix, chain_id: wallet.chain.chain_id, diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index da6518c8..3704a75d 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -30,7 +30,7 @@ export interface RouteTransaction { interface Props { route: RouteResponse; transactionCount: number; - insufficentBalance?: boolean; + insufficentBalance?: boolean | string; onClose: () => void; } @@ -403,7 +403,7 @@ function TransactionDialogContent({ route, onClose, insufficentBalance, transact "disabled:cursor-not-allowed disabled:opacity-75", )} onClick={onSubmit} - disabled={transacting || insufficentBalance} + disabled={transacting || !!insufficentBalance} > Submit diff --git a/src/components/TransactionDialog/index.tsx b/src/components/TransactionDialog/index.tsx index 706b3cb9..43e08aa0 100644 --- a/src/components/TransactionDialog/index.tsx +++ b/src/components/TransactionDialog/index.tsx @@ -13,7 +13,7 @@ interface Props { isLoading?: boolean; route?: RouteResponse; transactionCount: number; - insufficientBalance?: boolean; + insufficientBalance?: boolean | string; shouldShowPriceImpactWarning?: boolean; routeWarningMessage?: string; routeWarningTitle?: string; diff --git a/src/context/settings.ts b/src/context/settings.ts index d4d4c532..995aa196 100644 --- a/src/context/settings.ts +++ b/src/context/settings.ts @@ -2,17 +2,20 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; interface SettingsStore { - gas: string; + gasComputed?: string; + gasMultiplier: string; slippage: string; } export const defaultValues: SettingsStore = { - gas: (150_000).toString(), + gasComputed: undefined, + gasMultiplier: (150_000).toString(), slippage: (3).toString(), }; export const useSettingsStore = create()( persist(() => defaultValues, { name: "SettingsState", + version: 1, }), );