diff --git a/packages/web/components/alert/info.tsx b/packages/web/components/alert/info.tsx index e3519d31a7..8332b6a199 100644 --- a/packages/web/components/alert/info.tsx +++ b/packages/web/components/alert/info.tsx @@ -7,6 +7,7 @@ export const Info: FunctionComponent< { size?: "large" | "subtle" } & Alert & { data?: string; borderClassName?: string; + textClassName?: string; } & CustomClasses & MobileProps > = ({ @@ -15,6 +16,7 @@ export const Info: FunctionComponent< caption, data, borderClassName, + textClassName, className, isMobile = false, }) => @@ -25,7 +27,14 @@ export const Info: FunctionComponent< className )} > - {message} + + {message} + ) : (
{isMobile ? ( - + {message} {data && ` - ${data}`} ) : ( -
{message}
+ + {message} + )} {caption && ( - + {caption} )} diff --git a/packages/web/components/complex/add-liquidity.tsx b/packages/web/components/complex/add-liquidity.tsx index 1c867bf847..d3fc05f7e6 100644 --- a/packages/web/components/complex/add-liquidity.tsx +++ b/packages/web/components/complex/add-liquidity.tsx @@ -94,23 +94,6 @@ export const AddLiquidity: FunctionComponent< key={currency.coinDenom} className="flex flex-col gap-1 w-full md:p-3 p-4 border border-osmoverse-700 md:rounded-xl rounded-2xl" > - {isPeggedCurrency && ( - - currency.originCurrency!.pegMechanism!.startsWith(vowel) - ) - ? "an" - : "a" - } ${ - currency.originCurrency!.pegMechanism - }-backed stablecoin.`} - /> - )}
{addLiquidityConfig.isSingleAmountIn ? (
+ {isPeggedCurrency && ( + + )}
); })} diff --git a/packages/web/components/navbar/index.tsx b/packages/web/components/navbar/index.tsx index fcb714134d..7ecfd5ad06 100644 --- a/packages/web/components/navbar/index.tsx +++ b/packages/web/components/navbar/index.tsx @@ -137,6 +137,7 @@ const NavBarButton: FunctionComponent< src={hovered ? hovericonurl : iconurl} height={24} width={24} + priority={true} /> ); diff --git a/packages/web/components/table/cells/transfer-button.tsx b/packages/web/components/table/cells/transfer-button.tsx index c88171766d..87b8a60d3d 100644 --- a/packages/web/components/table/cells/transfer-button.tsx +++ b/packages/web/components/table/cells/transfer-button.tsx @@ -102,6 +102,7 @@ const TransferButton: FunctionComponent<{ src="/icons/chevron-right-rust.svg" height={13} width={13} + priority={true} /> ) : ( )} diff --git a/packages/web/components/trade-clipboard/index.tsx b/packages/web/components/trade-clipboard/index.tsx index c32b87bcdb..47b65052ef 100644 --- a/packages/web/components/trade-clipboard/index.tsx +++ b/packages/web/components/trade-clipboard/index.tsx @@ -30,991 +30,1003 @@ export const TradeClipboard: FunctionComponent<{ containerClassName?: string; isInModal?: boolean; -}> = observer(({ containerClassName, pools, isInModal }) => { - const { - chainStore, - accountStore, - queriesStore, - assetsStore: { nativeBalances, ibcBalances }, - priceStore, - } = useStore(); - const t = useTranslation(); - const { chainId } = chainStore.osmosis; - const { isMobile } = useWindowSize(); - const { logEvent } = useAmplitudeAnalytics(); - - const allTokenBalances = nativeBalances.concat(ibcBalances); - - const account = accountStore.getAccount(chainId); - const queries = queriesStore.get(chainId); - - const [isSettingOpen, setIsSettingOpen] = useBooleanWithWindowEvent(false); - const manualSlippageInputRef = useRef(null); - - const slippageConfig = useSlippageConfig(); - const tradeTokenInConfig = useTradeTokenInConfig( - chainStore, - chainId, - account.bech32Address, - queriesStore, - pools - ); - // Some validators allow 0 fee tx. - // Therefore, users can send tx at 0 fee even though they have no OSMO, - // Users who have OSMO pay a fee by default so that tx is processed faster. - let preferZeroFee = true; - const queryOsmo = queries.queryBalances.getQueryBech32Address( - account.bech32Address - ).stakable; - if ( - // If user has an OSMO 0.001 or higher, he pay the fee by default. - queryOsmo.balance.toDec().gt(DecUtils.getTenExponentN(-3)) - ) { - preferZeroFee = false; - } - const gasForecasted = (() => { + onRequestModalClose?: () => void; +}> = observer( + ({ containerClassName, pools, isInModal, onRequestModalClose }) => { + const { + chainStore, + accountStore, + queriesStore, + assetsStore: { nativeBalances, ibcBalances }, + priceStore, + } = useStore(); + const t = useTranslation(); + const { chainId } = chainStore.osmosis; + const { isMobile } = useWindowSize(); + const { logEvent } = useAmplitudeAnalytics(); + + const allTokenBalances = nativeBalances.concat(ibcBalances); + + const account = accountStore.getAccount(chainId); + const queries = queriesStore.get(chainId); + + const [isSettingOpen, setIsSettingOpen] = useBooleanWithWindowEvent(false); + const manualSlippageInputRef = useRef(null); + + const slippageConfig = useSlippageConfig(); + const tradeTokenInConfig = useTradeTokenInConfig( + chainStore, + chainId, + account.bech32Address, + queriesStore, + pools + ); + // Some validators allow 0 fee tx. + // Therefore, users can send tx at 0 fee even though they have no OSMO, + // Users who have OSMO pay a fee by default so that tx is processed faster. + let preferZeroFee = true; + const queryOsmo = queries.queryBalances.getQueryBech32Address( + account.bech32Address + ).stakable; if ( - tradeTokenInConfig.optimizedRoutePaths.length === 0 || - tradeTokenInConfig.optimizedRoutePaths[0].pools.length <= 1 + // If user has an OSMO 0.001 or higher, he pay the fee by default. + queryOsmo.balance.toDec().gt(DecUtils.getTenExponentN(-3)) ) { - return 250000; + preferZeroFee = false; } + const gasForecasted = (() => { + if ( + tradeTokenInConfig.optimizedRoutePaths.length === 0 || + tradeTokenInConfig.optimizedRoutePaths[0].pools.length <= 1 + ) { + return 250000; + } - return 250000 * tradeTokenInConfig.optimizedRoutePaths[0].pools.length; - })(); - - const feeConfig = useFakeFeeConfig( - chainStore, - chainStore.osmosis.chainId, - gasForecasted, - preferZeroFee - ); - tradeTokenInConfig.setFeeConfig(feeConfig); - - // show details - const [showEstimateDetails, setShowEstimateDetails] = useState(false); - const isEstimateDetailRelevant = !( - tradeTokenInConfig.amount === "" || tradeTokenInConfig.amount === "0" - ); - useEffect(() => { - // auto collapse on input clear - if (!isEstimateDetailRelevant) setShowEstimateDetails(false); - }, [isEstimateDetailRelevant]); - - // auto focus from amount on token switch - const fromAmountInput = useRef(null); - useEffect(() => { - fromAmountInput.current?.focus(); - }, [tradeTokenInConfig.sendCurrency]); - - useTokenSwapQueryParams(tradeTokenInConfig, allTokenBalances, isInModal); - - const showPriceImpactWarning = useMemo( - () => - tradeTokenInConfig.expectedSwapResult.priceImpact - .toDec() - .gt(new Dec(0.1)), - [tradeTokenInConfig.expectedSwapResult.priceImpact] - ); - - useEffect(() => { - if (isSettingOpen && slippageConfig.isManualSlippage) { - // Whenever the setting opened, give a focus to the input if the manual slippage setting mode is on. - manualSlippageInputRef.current?.focus(); - } - }, [isSettingOpen]); - - // token select dropdown - const [showFromTokenSelectDropdown, setFromTokenSelectDropdownLocal] = - useBooleanWithWindowEvent(false); - const [showToTokenSelectDropdown, setToTokenSelectDropdownLocal] = - useBooleanWithWindowEvent(false); - const setOneTokenSelectOpen = (dropdown: "to" | "from") => { - if (dropdown === "to") { - setToTokenSelectDropdownLocal(true); + return 250000 * tradeTokenInConfig.optimizedRoutePaths[0].pools.length; + })(); + + const feeConfig = useFakeFeeConfig( + chainStore, + chainStore.osmosis.chainId, + gasForecasted, + preferZeroFee + ); + tradeTokenInConfig.setFeeConfig(feeConfig); + + // show details + const [showEstimateDetails, setShowEstimateDetails] = useState(false); + const isEstimateDetailRelevant = !( + tradeTokenInConfig.amount === "" || tradeTokenInConfig.amount === "0" + ); + useEffect(() => { + // auto collapse on input clear + if (!isEstimateDetailRelevant) setShowEstimateDetails(false); + }, [isEstimateDetailRelevant]); + + // auto focus from amount on token switch + const fromAmountInput = useRef(null); + useEffect(() => { + fromAmountInput.current?.focus(); + }, [tradeTokenInConfig.sendCurrency]); + + useTokenSwapQueryParams(tradeTokenInConfig, allTokenBalances, isInModal); + + const showPriceImpactWarning = useMemo( + () => + tradeTokenInConfig.expectedSwapResult.priceImpact + .toDec() + .gt(new Dec(0.1)), + [tradeTokenInConfig.expectedSwapResult.priceImpact] + ); + + useEffect(() => { + if (isSettingOpen && slippageConfig.isManualSlippage) { + // Whenever the setting opened, give a focus to the input if the manual slippage setting mode is on. + manualSlippageInputRef.current?.focus(); + } + }, [isSettingOpen]); + + // token select dropdown + const [showFromTokenSelectDropdown, setFromTokenSelectDropdownLocal] = + useBooleanWithWindowEvent(false); + const [showToTokenSelectDropdown, setToTokenSelectDropdownLocal] = + useBooleanWithWindowEvent(false); + const setOneTokenSelectOpen = (dropdown: "to" | "from") => { + if (dropdown === "to") { + setToTokenSelectDropdownLocal(true); + setFromTokenSelectDropdownLocal(false); + } else { + setFromTokenSelectDropdownLocal(true); + setToTokenSelectDropdownLocal(false); + } + }; + const closeTokenSelectDropdowns = () => { setFromTokenSelectDropdownLocal(false); - } else { - setFromTokenSelectDropdownLocal(true); setToTokenSelectDropdownLocal(false); - } - }; - const closeTokenSelectDropdowns = () => { - setFromTokenSelectDropdownLocal(false); - setToTokenSelectDropdownLocal(false); - }; - - // trade metrics - const minOutAmountLessSlippage = useMemo( - () => - tradeTokenInConfig.expectedSwapResult.amount - .toDec() - .mul(new Dec(1).sub(slippageConfig.slippage.toDec())), - [tradeTokenInConfig.expectedSwapResult.amount, slippageConfig.slippage] - ); - const spotPrice = useMemo( - () => - tradeTokenInConfig.beforeSpotPriceWithoutSwapFeeOutOverIn - .trim(true) - .maxDecimals(tradeTokenInConfig.outCurrency.coinDecimals), - [ - tradeTokenInConfig.beforeSpotPriceWithoutSwapFeeOutOverIn, - tradeTokenInConfig.outCurrency, - ] - ); - - const [isHoveringSwitchButton, setHoveringSwitchButton] = useState(false); - - // to & from box switch animation - const [isAnimatingSwitch, setIsAnimatingSwitch] = useState(false); - const [switchOutBack, setSwitchOutBack] = useState(false); - useEffect(() => { - let timeout: NodeJS.Timeout | undefined; - let timeout2: NodeJS.Timeout | undefined; - const duration = 300; - - if (isAnimatingSwitch) { - timeout = setTimeout(() => { - setIsAnimatingSwitch(false); - setSwitchOutBack(false); - }, duration); - timeout2 = setTimeout(() => { - tradeTokenInConfig.switchInAndOut(); - setSwitchOutBack(true); - }, duration / 3); - } - - return () => { - if (timeout) clearTimeout(timeout); - if (timeout2) clearTimeout(timeout2); }; - }, [isAnimatingSwitch, tradeTokenInConfig]); - - // amount fiat value - const inAmountValue = useMemo( - () => - tradeTokenInConfig.amount !== "" && - new Dec(tradeTokenInConfig.amount).gt(new Dec(0)) - ? priceStore.calculatePrice( - new CoinPretty( - tradeTokenInConfig.sendCurrency, - new Dec(tradeTokenInConfig.amount).mul( - DecUtils.getTenExponentNInPrecisionRange( - tradeTokenInConfig.sendCurrency.coinDecimals + + // trade metrics + const minOutAmountLessSlippage = useMemo( + () => + tradeTokenInConfig.expectedSwapResult.amount + .toDec() + .mul(new Dec(1).sub(slippageConfig.slippage.toDec())), + [tradeTokenInConfig.expectedSwapResult.amount, slippageConfig.slippage] + ); + const spotPrice = useMemo( + () => + tradeTokenInConfig.beforeSpotPriceWithoutSwapFeeOutOverIn + .trim(true) + .maxDecimals(tradeTokenInConfig.outCurrency.coinDecimals), + [ + tradeTokenInConfig.beforeSpotPriceWithoutSwapFeeOutOverIn, + tradeTokenInConfig.outCurrency, + ] + ); + + const [isHoveringSwitchButton, setHoveringSwitchButton] = useState(false); + + // to & from box switch animation + const [isAnimatingSwitch, setIsAnimatingSwitch] = useState(false); + const [switchOutBack, setSwitchOutBack] = useState(false); + useEffect(() => { + let timeout: NodeJS.Timeout | undefined; + let timeout2: NodeJS.Timeout | undefined; + const duration = 300; + + if (isAnimatingSwitch) { + timeout = setTimeout(() => { + setIsAnimatingSwitch(false); + setSwitchOutBack(false); + }, duration); + timeout2 = setTimeout(() => { + tradeTokenInConfig.switchInAndOut(); + setSwitchOutBack(true); + }, duration / 3); + } + + return () => { + if (timeout) clearTimeout(timeout); + if (timeout2) clearTimeout(timeout2); + }; + }, [isAnimatingSwitch, tradeTokenInConfig]); + + // amount fiat value + const inAmountValue = useMemo( + () => + tradeTokenInConfig.amount !== "" && + new Dec(tradeTokenInConfig.amount).gt(new Dec(0)) + ? priceStore.calculatePrice( + new CoinPretty( + tradeTokenInConfig.sendCurrency, + new Dec(tradeTokenInConfig.amount).mul( + DecUtils.getTenExponentNInPrecisionRange( + tradeTokenInConfig.sendCurrency.coinDecimals + ) ) ) ) + : undefined, + [tradeTokenInConfig.amount, tradeTokenInConfig.sendCurrency] + ); + const outAmountValue = useMemo( + () => + (!tradeTokenInConfig.expectedSwapResult.amount.toDec().isZero() && + priceStore.calculatePrice( + tradeTokenInConfig.expectedSwapResult.amount + )) || + undefined, + [tradeTokenInConfig.expectedSwapResult.amount] + ); + + const swapResultAmount = useMemo( + () => + tradeTokenInConfig.expectedSwapResult.amount + .trim(true) + .shrink(true) + .maxDecimals( + Math.min( + tradeTokenInConfig.expectedSwapResult.amount.currency + .coinDecimals, + 8 + ) ) - : undefined, - [tradeTokenInConfig.amount, tradeTokenInConfig.sendCurrency] - ); - const outAmountValue = useMemo( - () => - (!tradeTokenInConfig.expectedSwapResult.amount.toDec().isZero() && - priceStore.calculatePrice( - tradeTokenInConfig.expectedSwapResult.amount - )) || - undefined, - [tradeTokenInConfig.expectedSwapResult.amount] - ); - - const swapResultAmount = useMemo( - () => - tradeTokenInConfig.expectedSwapResult.amount - .trim(true) - .shrink(true) - .maxDecimals( - Math.min( - tradeTokenInConfig.expectedSwapResult.amount.currency.coinDecimals, - 8 - ) - ) - .hideDenom(true) - .toString(), - [tradeTokenInConfig.expectedSwapResult.amount] - ); - - // user action - const swap = async () => { - if (account.walletStatus !== WalletStatus.Loaded) { - return account.init(); - } - if (tradeTokenInConfig.optimizedRoutePaths.length > 0) { - const routes: { - poolId: string; - tokenOutCurrency: Currency; - }[] = []; - - for ( - let i = 0; - i < tradeTokenInConfig.optimizedRoutePaths[0].pools.length; - i++ - ) { - const pool = tradeTokenInConfig.optimizedRoutePaths[0].pools[i]; - const tokenOutCurrency = chainStore.osmosisObservable.currencies.find( + .hideDenom(true) + .toString(), + [tradeTokenInConfig.expectedSwapResult.amount] + ); + + // user action + const swap = async () => { + if (account.walletStatus !== WalletStatus.Loaded) { + return account.init(); + } + if (tradeTokenInConfig.optimizedRoutePaths.length > 0) { + const routes: { + poolId: string; + tokenOutCurrency: Currency; + }[] = []; + + for ( + let i = 0; + i < tradeTokenInConfig.optimizedRoutePaths[0].pools.length; + i++ + ) { + const pool = tradeTokenInConfig.optimizedRoutePaths[0].pools[i]; + const tokenOutCurrency = chainStore.osmosisObservable.currencies.find( + (cur) => + cur.coinMinimalDenom === + tradeTokenInConfig.optimizedRoutePaths[0].tokenOutDenoms[i] + ); + + if (!tokenOutCurrency) { + tradeTokenInConfig.setError( + new Error( + t("swap.error.findCurrency", { + currency: + tradeTokenInConfig.optimizedRoutePaths[0].tokenOutDenoms[i], + }) + ) + ); + return; + } + + routes.push({ + poolId: pool.id, + tokenOutCurrency, + }); + } + + const tokenInCurrency = chainStore.osmosisObservable.currencies.find( (cur) => cur.coinMinimalDenom === - tradeTokenInConfig.optimizedRoutePaths[0].tokenOutDenoms[i] + tradeTokenInConfig.optimizedRoutePaths[0].tokenInDenom ); - if (!tokenOutCurrency) { + if (!tokenInCurrency) { tradeTokenInConfig.setError( new Error( t("swap.error.findCurrency", { currency: - tradeTokenInConfig.optimizedRoutePaths[0].tokenOutDenoms[i], + tradeTokenInConfig.optimizedRoutePaths[0].tokenInDenom, }) ) ); return; } - routes.push({ - poolId: pool.id, - tokenOutCurrency, - }); - } + const tokenIn = { + currency: tokenInCurrency, + amount: tradeTokenInConfig.amount, + }; + const maxSlippage = slippageConfig.slippage.symbol("").toString(); - const tokenInCurrency = chainStore.osmosisObservable.currencies.find( - (cur) => - cur.coinMinimalDenom === - tradeTokenInConfig.optimizedRoutePaths[0].tokenInDenom - ); - - if (!tokenInCurrency) { - tradeTokenInConfig.setError( - new Error( - t("swap.error.findCurrency", { - currency: tradeTokenInConfig.optimizedRoutePaths[0].tokenInDenom, - }) - ) - ); - return; - } - - const tokenIn = { - currency: tokenInCurrency, - amount: tradeTokenInConfig.amount, - }; - const maxSlippage = slippageConfig.slippage.symbol("").toString(); - - try { - logEvent([ - EventName.Swap.swapStarted, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - tokenAmount: Number(tokenIn.amount), - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - isMultiHop: routes.length !== 1, - }, - ]); - if (routes.length === 1) { - await account.osmosis.sendSwapExactAmountInMsg( - routes[0].poolId, - tokenIn, - routes[0].tokenOutCurrency, - maxSlippage, - "", - { - amount: [ - { - denom: chainStore.osmosis.stakeCurrency.coinMinimalDenom, - amount: "0", - }, - ], - }, + try { + logEvent([ + EventName.Swap.swapStarted, { - preferNoSetFee: preferZeroFee, + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + tokenAmount: Number(tokenIn.amount), + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, + isMultiHop: routes.length !== 1, }, - () => { - logEvent([ - EventName.Swap.swapCompleted, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - tokenAmount: Number(tokenIn.amount), - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, + ]); + if (routes.length === 1) { + await account.osmosis.sendSwapExactAmountInMsg( + routes[0].poolId, + tokenIn, + routes[0].tokenOutCurrency, + maxSlippage, + "", + { + amount: [ + { + denom: chainStore.osmosis.stakeCurrency.coinMinimalDenom, + amount: "0", + }, + ], + }, + { + preferNoSetFee: preferZeroFee, + }, + () => { + logEvent([ + EventName.Swap.swapCompleted, + { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + tokenAmount: Number(tokenIn.amount), + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, - isMultiHop: false, - }, - ]); - } - ); - } else { - await account.osmosis.sendMultihopSwapExactAmountInMsg( - routes, - tokenIn, - maxSlippage, - "", - { - amount: [ - { - denom: chainStore.osmosis.stakeCurrency.coinMinimalDenom, - amount: "0", - }, - ], - }, - { - preferNoSetFee: preferZeroFee, - }, - () => { - logEvent([ - EventName.Swap.swapCompleted, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - tokenAmount: Number(tokenIn.amount), - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - isMultiHop: true, - }, - ]); - } - ); + isMultiHop: false, + }, + ]); + } + ); + } else { + await account.osmosis.sendMultihopSwapExactAmountInMsg( + routes, + tokenIn, + maxSlippage, + "", + { + amount: [ + { + denom: chainStore.osmosis.stakeCurrency.coinMinimalDenom, + amount: "0", + }, + ], + }, + { + preferNoSetFee: preferZeroFee, + }, + () => { + logEvent([ + EventName.Swap.swapCompleted, + { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + tokenAmount: Number(tokenIn.amount), + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, + isMultiHop: true, + }, + ]); + } + ); + } + tradeTokenInConfig.setAmount(""); + tradeTokenInConfig.setFraction(undefined); + } catch (e) { + console.error(e); + } finally { + onRequestModalClose?.(); } - tradeTokenInConfig.setAmount(""); - tradeTokenInConfig.setFraction(undefined); - } catch (e) { - console.error(e); } - } - }; - - return ( -
-
-
{t("swap.title")}
- - {isSettingOpen && ( -
e.stopPropagation()} + }; + + return ( +
+
+
{t("swap.title")}
+ + {isSettingOpen && ( +
e.stopPropagation()} + > +
{t("swap.settings.title")}
+
+
+ {t("swap.settings.slippage")} +
+
- -
-
    - {slippageConfig.selectableSlippages.map((slippage) => { - return ( -
  • { - e.preventDefault(); +
      + {slippageConfig.selectableSlippages.map((slippage) => { + return ( +
    • { + e.preventDefault(); - slippageConfig.select(slippage.index); + slippageConfig.select(slippage.index); + + logEvent([ + EventName.Swap.slippageToleranceSet, + { + percentage: slippageConfig.slippage.toString(), + }, + ]); + }} + > + +
    • + ); + })} +
    • { + e.preventDefault(); + + if (manualSlippageInputRef.current) { + manualSlippageInputRef.current.focus(); + } + }} + > + { + slippageConfig.setManualSlippage(value); logEvent([ EventName.Swap.slippageToleranceSet, { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, percentage: slippageConfig.slippage.toString(), }, ]); }} + onFocus={() => slippageConfig.setIsManualSlippage(true)} + inputRef={manualSlippageInputRef} + isAutosize + /> + - -
    • - ); - })} -
    • { - e.preventDefault(); - - if (manualSlippageInputRef.current) { - manualSlippageInputRef.current.focus(); - } - }} - > - { - slippageConfig.setManualSlippage(value); - - logEvent([ - EventName.Swap.slippageToleranceSet, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - percentage: slippageConfig.slippage.toString(), - }, - ]); - }} - onFocus={() => slippageConfig.setIsManualSlippage(true)} - inputRef={manualSlippageInputRef} - isAutosize - /> - - % - -
    • -
    -
- )} -
- -
-
+ + +
)} - style={ - isAnimatingSwitch - ? { - transform: "translateY(60px)", - } - : undefined - } - > +
+ +
-
- - {t("swap.available")} - - - {queries.queryBalances - .getQueryBech32Address(account.bech32Address) - .getBalanceFromCurrency(tradeTokenInConfig.sendCurrency) - .trim(true) - .hideDenom(true) - .maxDecimals(tradeTokenInConfig.sendCurrency.coinDecimals) - .toString()} - +
+
+ + {t("swap.available")} + + + {queries.queryBalances + .getQueryBech32Address(account.bech32Address) + .getBalanceFromCurrency(tradeTokenInConfig.sendCurrency) + .trim(true) + .hideDenom(true) + .maxDecimals(tradeTokenInConfig.sendCurrency.coinDecimals) + .toString()} + +
+
+ { + if (tradeTokenInConfig.fraction !== 1) { + logEvent([ + EventName.Swap.maxClicked, + { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, + }, + ]); + tradeTokenInConfig.setFraction(1); + } else { + tradeTokenInConfig.setFraction(undefined); + } + }} + > + {t("swap.MAX")} + + { + if (tradeTokenInConfig.fraction !== 0.5) { + logEvent([ + EventName.Swap.halfClicked, + { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, + }, + ]); + tradeTokenInConfig.setFraction(0.5); + } else { + tradeTokenInConfig.setFraction(undefined); + } + }} + > + {t("swap.HALF")} + +
-
- { - if (tradeTokenInConfig.fraction !== 1) { - logEvent([ - EventName.Swap.maxClicked, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - }, - ]); - tradeTokenInConfig.setFraction(1); +
+ { + if (isOpen) { + setOneTokenSelectOpen("from"); } else { - tradeTokenInConfig.setFraction(undefined); + closeTokenSelectDropdowns(); } }} - > - {t("swap.MAX")} - - { - if (tradeTokenInConfig.fraction !== 0.5) { - logEvent([ - EventName.Swap.halfClicked, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - }, - ]); - tradeTokenInConfig.setFraction(0.5); - } else { - tradeTokenInConfig.setFraction(undefined); - } - }} - > - {t("swap.HALF")} - -
-
-
- { - if (isOpen) { - setOneTokenSelectOpen("from"); - } else { - closeTokenSelectDropdowns(); - } - }} - tokens={allTokenBalances - .filter( - (tokenBalance) => - tokenBalance.balance.currency.coinDenom !== - tradeTokenInConfig.outCurrency.coinDenom - ) - .filter((tokenBalance) => - tradeTokenInConfig.sendableCurrencies.some( - (sendableCurrency) => - sendableCurrency.coinDenom === - tokenBalance.balance.currency.coinDenom + tokens={allTokenBalances + .filter( + (tokenBalance) => + tokenBalance.balance.currency.coinDenom !== + tradeTokenInConfig.outCurrency.coinDenom ) - ) - .map((tokenBalance) => tokenBalance.balance)} - selectedTokenDenom={tradeTokenInConfig.sendCurrency.coinDenom} - onSelect={(tokenDenom: string) => { - const tokenInBalance = allTokenBalances.find( - (tokenBalance) => - tokenBalance.balance.currency.coinDenom === tokenDenom - ); - if (tokenInBalance) { - tradeTokenInConfig.setSendCurrency( - tokenInBalance.balance.currency + .filter((tokenBalance) => + tradeTokenInConfig.sendableCurrencies.some( + (sendableCurrency) => + sendableCurrency.coinDenom === + tokenBalance.balance.currency.coinDenom + ) + ) + .map((tokenBalance) => tokenBalance.balance)} + selectedTokenDenom={tradeTokenInConfig.sendCurrency.coinDenom} + onSelect={(tokenDenom: string) => { + const tokenInBalance = allTokenBalances.find( + (tokenBalance) => + tokenBalance.balance.currency.coinDenom === tokenDenom ); - } - closeTokenSelectDropdowns(); - }} - /> -
- = 14 - ? "caption" - : "font-h5 md:font-subtitle1 text-h5" - )} - placeholder="0" - onChange={(e) => { - e.preventDefault(); - if ( - Number(e.target.value) <= Number.MAX_SAFE_INTEGER && - e.target.value.length <= (isMobile ? 19 : 26) - ) { - logEvent([ - EventName.Swap.inputEntered, - { - fromToken: tradeTokenInConfig.sendCurrency.coinDenom, - toToken: tradeTokenInConfig.outCurrency.coinDenom, - isOnHome: !isInModal, - }, - ]); - tradeTokenInConfig.setAmount(e.target.value); + if (tokenInBalance) { + tradeTokenInConfig.setSendCurrency( + tokenInBalance.balance.currency + ); } + closeTokenSelectDropdowns(); }} - value={tradeTokenInConfig.amount} /> -
{`≈ ${inAmountValue || "0"}`}
+
+ = 14 + ? "caption" + : "font-h5 md:font-subtitle1 text-h5" + )} + placeholder="0" + onChange={(e) => { + e.preventDefault(); + if ( + Number(e.target.value) <= Number.MAX_SAFE_INTEGER && + e.target.value.length <= (isMobile ? 19 : 26) + ) { + logEvent([ + EventName.Swap.inputEntered, + { + fromToken: tradeTokenInConfig.sendCurrency.coinDenom, + toToken: tradeTokenInConfig.outCurrency.coinDenom, + isOnHome: !isInModal, + }, + ]); + tradeTokenInConfig.setAmount(e.target.value); + } + }} + value={tradeTokenInConfig.amount} + /> +
{`≈ ${inAmountValue || "0"}`}
+
-
- + -
- { - if (isOpen) { - setOneTokenSelectOpen("to"); - } else { - closeTokenSelectDropdowns(); - } - }} - sortByBalances - tokens={allTokenBalances - .filter( - (tokenBalance) => - tokenBalance.balance.currency.coinDenom !== - tradeTokenInConfig.sendCurrency.coinDenom - ) - .filter((tokenBalance) => - tradeTokenInConfig.sendableCurrencies.some( - (sendableCurrency) => - sendableCurrency.coinDenom === - tokenBalance.balance.currency.coinDenom +
+ { + if (isOpen) { + setOneTokenSelectOpen("to"); + } else { + closeTokenSelectDropdowns(); + } + }} + sortByBalances + tokens={allTokenBalances + .filter( + (tokenBalance) => + tokenBalance.balance.currency.coinDenom !== + tradeTokenInConfig.sendCurrency.coinDenom ) - ) - .map((tokenBalance) => tokenBalance.balance)} - selectedTokenDenom={tradeTokenInConfig.outCurrency.coinDenom} - onSelect={(tokenDenom: string) => { - const tokenOutBalance = allTokenBalances.find( - (tokenBalance) => - tokenBalance.balance.currency.coinDenom === tokenDenom - ); - if (tokenOutBalance) { - tradeTokenInConfig.setOutCurrency( - tokenOutBalance.balance.currency + .filter((tokenBalance) => + tradeTokenInConfig.sendableCurrencies.some( + (sendableCurrency) => + sendableCurrency.coinDenom === + tokenBalance.balance.currency.coinDenom + ) + ) + .map((tokenBalance) => tokenBalance.balance)} + selectedTokenDenom={tradeTokenInConfig.outCurrency.coinDenom} + onSelect={(tokenDenom: string) => { + const tokenOutBalance = allTokenBalances.find( + (tokenBalance) => + tokenBalance.balance.currency.coinDenom === tokenDenom ); - } - closeTokenSelectDropdowns(); - }} - /> -
-
{`≈ ${ - tradeTokenInConfig.expectedSwapResult.amount.denom !== "UNKNOWN" - ? swapResultAmount - : "0" - }`}
-
- {`≈ ${outAmountValue || "0"}`} + if (tokenOutBalance) { + tradeTokenInConfig.setOutCurrency( + tokenOutBalance.balance.currency + ); + } + closeTokenSelectDropdowns(); + }} + /> +
+
{`≈ ${ + tradeTokenInConfig.expectedSwapResult.amount.denom !== + "UNKNOWN" + ? swapResultAmount + : "0" + }`}
+
+ {`≈ ${outAmountValue || "0"}`} +
-
-
-
-
{ + if (isEstimateDetailRelevant) + setShowEstimateDetails(!showEstimateDetails); + }} > -
{t("swap.priceImpact")}
- {`-${tradeTokenInConfig.expectedSwapResult.priceImpact.toString()}`} -
-
-
-
- {t("swap.fee", { - fee: tradeTokenInConfig.expectedSwapResult.swapFee.toString(), + className={classNames("subtitle2 transition-all", { + "text-osmoverse-600": !isEstimateDetailRelevant, })} + > + {`1 ${ + tradeTokenInConfig.sendCurrency.coinDenom !== "UNKNOWN" + ? tradeTokenInConfig.sendCurrency.coinDenom + : "" + } ≈ ${ + spotPrice.toDec().lt(new Dec(1)) + ? spotPrice.toString() + : spotPrice.maxDecimals(6).toString() + } ${ + tradeTokenInConfig.outCurrency.coinDenom !== "UNKNOWN" + ? tradeTokenInConfig.outCurrency.coinDenom + : "" + }`}
-
- {`≈ ${ - priceStore.calculatePrice( - tradeTokenInConfig.expectedSwapResult.tokenInFeeAmount - ) ?? "0" - } `} -
-
-
-
-
{t("swap.expectedOutput")}
-
- {`≈ ${tradeTokenInConfig.expectedSwapResult.amount.toString()} `} -
-
-
-
- {t("swap.minimumSlippage", { - slippage: slippageConfig.slippage.trim(true).toString(), - })} +
+ alert circle + show estimates
+ +
- - {new CoinPretty( - tradeTokenInConfig.outCurrency, - minOutAmountLessSlippage.mul( - DecUtils.getTenExponentNInPrecisionRange( - tradeTokenInConfig.outCurrency.coinDecimals - ) - ) - ).toString()} - - +
{t("swap.priceImpact")}
+
+ {`-${tradeTokenInConfig.expectedSwapResult.priceImpact.toString()}`} +
+
+
+
+ {t("swap.fee", { + fee: tradeTokenInConfig.expectedSwapResult.swapFee.toString(), + })} +
+
{`≈ ${ priceStore.calculatePrice( - new CoinPretty( - tradeTokenInConfig.outCurrency, - minOutAmountLessSlippage.mul( - DecUtils.getTenExponentNInPrecisionRange( - tradeTokenInConfig.outCurrency.coinDecimals - ) + tradeTokenInConfig.expectedSwapResult.tokenInFeeAmount + ) ?? "0" + } `} +
+
+
+
+
{t("swap.expectedOutput")}
+
+ {`≈ ${tradeTokenInConfig.expectedSwapResult.amount.toString()} `} +
+
+
+
+ {t("swap.minimumSlippage", { + slippage: slippageConfig.slippage.trim(true).toString(), + })} +
+
+ + {new CoinPretty( + tradeTokenInConfig.outCurrency, + minOutAmountLessSlippage.mul( + DecUtils.getTenExponentNInPrecisionRange( + tradeTokenInConfig.outCurrency.coinDecimals ) ) - ) || "0" - }`} - + ).toString()} + + + {`≈ ${ + priceStore.calculatePrice( + new CoinPretty( + tradeTokenInConfig.outCurrency, + minOutAmountLessSlippage.mul( + DecUtils.getTenExponentNInPrecisionRange( + tradeTokenInConfig.outCurrency.coinDecimals + ) + ) + ) + ) || "0" + }`} + +
-
- -
- ); -}); +
+ wallet + {t("connectWallet")} +
+ )} + +
+ ); + } +); diff --git a/packages/web/hooks/ui-config/use-transfer-config.ts b/packages/web/hooks/ui-config/use-transfer-config.ts index 4dddb187c9..0699a0c3c0 100644 --- a/packages/web/hooks/ui-config/use-transfer-config.ts +++ b/packages/web/hooks/ui-config/use-transfer-config.ts @@ -1,35 +1,21 @@ -import { useState, useEffect } from "react"; -import { AccountSetBase } from "@keplr-wallet/stores"; -import { - ObservableAssets, - ObservableTransferUIConfig, -} from "../../stores/assets"; +import { useState } from "react"; +import { ObservableTransferUIConfig } from "../../stores/assets"; import { makeLocalStorageKVStore } from "../../stores/kv-store"; +import { useStore } from "../../stores"; import { useWindowSize } from "../window"; -export function useTransferConfig( - assetsStore: ObservableAssets, - account: AccountSetBase -) { +export function useTransferConfig() { const { isMobile } = useWindowSize(); + const { assetsStore } = useStore(); - const [transferConfig, setTransferConfig] = - useState(null); - const [transferKvStore] = useState(() => - makeLocalStorageKVStore("transfer-ui-config") - ); - useEffect( + const [transferConfig] = useState( () => - setTransferConfig( - new ObservableTransferUIConfig( - assetsStore, - account, - transferKvStore, - isMobile - ) - ), - [assetsStore, account, isMobile] + new ObservableTransferUIConfig( + assetsStore, + makeLocalStorageKVStore("transfer-ui-config") + ) ); + transferConfig.setIsMobile(isMobile); return transferConfig; } diff --git a/packages/web/localizations/en.json b/packages/web/localizations/en.json index ba2e4997c3..0703540a94 100644 --- a/packages/web/localizations/en.json +++ b/packages/web/localizations/en.json @@ -156,7 +156,8 @@ "allAssets": "All assets", "singleAsset": "Single asset", "available": "Available", - "autoswapCaption": "Use autoswap to add liquidity with a single asset." + "autoswapCaption": "Use autoswap to add liquidity with a single asset.", + "stablecoinWarning": "You are adding liquidity to {denom}, a {mechanism}-backed stablecoin." }, "lockToken": { "title": "Bond LP Shares", diff --git a/packages/web/modals/trade-tokens.tsx b/packages/web/modals/trade-tokens.tsx index 821df1f2e5..357fff42d5 100644 --- a/packages/web/modals/trade-tokens.tsx +++ b/packages/web/modals/trade-tokens.tsx @@ -11,9 +11,9 @@ export const TradeTokens: FunctionComponent = (props) => { return ( ); diff --git a/packages/web/modals/transfer-asset-select.tsx b/packages/web/modals/transfer-asset-select.tsx index 0da0c40e95..2a41af0919 100644 --- a/packages/web/modals/transfer-asset-select.tsx +++ b/packages/web/modals/transfer-asset-select.tsx @@ -89,6 +89,7 @@ export const TransferAssetSelectModal: FunctionComponent< { className: "mt-3", onClick: () => onSelectAsset(selectedTokenDenom, selectedNetwork?.id), + disabled: selectedToken?.originBridgeInfo && !selectedNetwork, // error in bridge integration config children: t("assets.transferAssetSelect.buttonNext"), }, props.onRequestClose, @@ -118,7 +119,7 @@ export const TransferAssetSelectModal: FunctionComponent< {selectedToken?.originBridgeInfo && selectedNetwork && keplrConnected && (
{selectedToken.originBridgeInfo.sourceChains .filter(({ id }) => id !== selectedNetwork.id) @@ -170,7 +171,7 @@ export const TransferAssetSelectModal: FunctionComponent<
{ const { isMobile } = useWindowSize(); - const { - assetsStore, - chainStore: { - osmosis: { chainId }, - }, - accountStore, - } = useStore(); + const { assetsStore } = useStore(); const { nativeBalances, ibcBalances } = assetsStore; - const account = accountStore.getAccount(chainId); const t = useTranslation(); const { setUserProperty, logEvent } = useAmplitudeAnalytics({ onLoadEvent: [EventName.Assets.pageViewed], }); - const transferConfig = useTransferConfig(assetsStore, account); + const transferConfig = useTransferConfig(); // mobile only const [preTransferModalProps, setPreTransferModalProps] = @@ -129,6 +122,25 @@ const Assets: NextPage = observer(() => { ], }); + const onTableDeposit = useCallback( + (chainId, coinDenom, externalDepositUrl) => { + if (!externalDepositUrl) { + isMobile + ? launchPreTransferModal(coinDenom) + : transferConfig?.transferAsset("deposit", chainId, coinDenom); + } + }, + [isMobile, launchPreTransferModal, transferConfig?.transferAsset] + ); + const onTableWithdraw = useCallback( + (chainId, coinDenom, externalWithdrawUrl) => { + if (!externalWithdrawUrl) { + transferConfig?.transferAsset("withdraw", chainId, coinDenom); + } + }, + [transferConfig?.transferAsset] + ); + return (
@@ -160,18 +172,8 @@ const Assets: NextPage = observer(() => { { - if (!externalDepositUrl) { - isMobile - ? launchPreTransferModal(coinDenom) - : transferConfig?.transferAsset("deposit", chainId, coinDenom); - } - }} - onWithdraw={(chainId, coinDenom, externalWithdrawUrl) => { - if (!externalWithdrawUrl) { - transferConfig?.transferAsset("withdraw", chainId, coinDenom); - } - }} + onDeposit={onTableDeposit} + onWithdraw={onTableWithdraw} onBuyOsmo={() => transferConfig?.buyOsmo()} /> {!isMobile && } diff --git a/packages/web/stores/assets/transfer-ui-config.ts b/packages/web/stores/assets/transfer-ui-config.ts index 487b6c43c1..090321b9bc 100644 --- a/packages/web/stores/assets/transfer-ui-config.ts +++ b/packages/web/stores/assets/transfer-ui-config.ts @@ -1,6 +1,11 @@ -import { makeObservable, observable, action, runInAction } from "mobx"; +import { + makeObservable, + observable, + action, + runInAction, + computed, +} from "mobx"; import { ComponentProps } from "react"; -import { AccountSetBase } from "@keplr-wallet/stores"; import { IBCCurrency } from "@keplr-wallet/types"; import { KVStore } from "@keplr-wallet/common"; import { @@ -74,15 +79,21 @@ export class ObservableTransferUIConfig { return this._fiatRampsModal; } + @observable + protected _isMobile: boolean = false; + constructor( protected readonly assetsStore: ObservableAssets, - protected readonly account: AccountSetBase, - protected readonly kvStore: KVStore, - protected readonly isMobile: boolean + protected readonly kvStore: KVStore ) { makeObservable(this); } + @action + public setIsMobile(isMobile: boolean) { + this._isMobile = isMobile; + } + @observable readonly metamask = new ObservableMetamask( makeLocalStorageKVStore("metamask") @@ -92,9 +103,10 @@ export class ObservableTransferUIConfig { makeLocalStorageKVStore("wc-eth") ); + @computed protected get _ethClientWallets(): EthWallet[] { return [this.metamask, this.walletConnectEth].filter((wallet) => - this.isMobile ? wallet.mobileEnabled : true + this._isMobile ? wallet.mobileEnabled : true ); } @@ -325,7 +337,7 @@ export class ObservableTransferUIConfig { isWithdraw: direction === "withdraw", onRequestClose: () => this.closeAllModals(), wallets, - fiatRamps: this.isMobile + fiatRamps: this._isMobile ? [] : balanceOnOsmosis.fiatRamps?.map(({ rampKey }) => rampKey), onSelectSource: (key) => {