From a4eaa423fd6de910c5d542bf67bb868856994be2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:02:33 +0300 Subject: [PATCH 1/4] feat: switch assets remove wallet support limitations --- .../SharedTradeInput/SharedTradeInputBody.tsx | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInputBody.tsx b/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInputBody.tsx index 1628a03c5bb..8548c51b8a3 100644 --- a/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInputBody.tsx +++ b/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInputBody.tsx @@ -9,13 +9,11 @@ import { } from '@chakra-ui/react' import type { AccountId, ChainId } from '@shapeshiftoss/caip' import type { Asset } from '@shapeshiftoss/types' -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslate } from 'react-polyglot' import { TradeAssetSelect } from 'components/AssetSelection/AssetSelection' import { useAccountsFetchQuery } from 'context/AppProvider/hooks/useAccountsFetchQuery' import { useModal } from 'hooks/useModal/useModal' -import { useWallet } from 'hooks/useWallet/useWallet' -import { useWalletSupportsChain } from 'hooks/useWalletSupportsChain/useWalletSupportsChain' import { isToken } from 'lib/utils' import { selectHighestMarketCapFeeAsset, @@ -48,7 +46,6 @@ type SharedTradeInputBodyProps = { } export const SharedTradeInputBody = ({ - buyAsset, children, isInputtingFiatSellAmount, isLoading, @@ -66,11 +63,6 @@ export const SharedTradeInputBody = ({ setSellAccountId, }: SharedTradeInputBodyProps) => { const translate = useTranslate() - const { - state: { walletInfo, wallet }, - } = useWallet() - - const hasWallet = useMemo(() => Boolean(walletInfo?.deviceId), [walletInfo]) const walletConnectedChainIds = useAppSelector(selectWalletConnectedChainIds) const defaultSellAsset = useAppSelector(selectHighestMarketCapFeeAsset) @@ -79,8 +71,6 @@ export const SharedTradeInputBody = ({ selectIsAccountMetadataLoadingByAccountId, ) - const walletSupportsBuyAssetChain = useWalletSupportsChain(buyAsset.chainId, wallet) - const sellAssetSearch = useModal('sellTradeAssetSearch') const percentOptions = useMemo(() => { @@ -90,8 +80,17 @@ export const SharedTradeInputBody = ({ return [1] }, [sellAsset.assetId]) + const hasJustSwitchedAssetsRef = useRef(false) + const handleSwitchAssets = useCallback(() => { + // Note we never set this back to false. This is intentional, as from the moment the user switches assets, we don't want any default pair logic to kick in anymore. + hasJustSwitchedAssetsRef.current = true + onSwitchAssets() + }, [onSwitchAssets]) + // If the user disconnects the chain for the currently selected sell asset, switch to the default asset useEffect(() => { + if (hasJustSwitchedAssetsRef.current) return + // Don't do any default asset business as some accounts meta is still loading, or a wrong default asset may be set, // which takes over the "default default" sellAsset - double default intended: // https://github.com/shapeshift/web/blob/ba43c41527156f8c7e0f1170472ff362e091b450/src/state/slices/tradeInputSlice/tradeInputSlice.ts#L27 @@ -132,14 +131,6 @@ export const SharedTradeInputBody = ({ [handleSellAssetClick, sellAsset.assetId, setSellAsset, chainIdFilterPredicate], ) - // disable switching assets if the buy asset isn't supported - const shouldDisableSwitchAssets = useMemo(() => { - if (isSwitchAssetsDisabled) return true - if (!hasWallet) return false - - return !walletSupportsBuyAssetChain - }, [hasWallet, isSwitchAssetsDisabled, walletSupportsBuyAssetChain]) - return ( From 702ab8645a5fbf1a8aff6b969b166cf458807604 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:13:52 +0300 Subject: [PATCH 2/4] feat: switch assets prorate sell amount over previous sell amount USD value --- .../LimitOrder/components/LimitOrderInput.tsx | 9 ++++++++- .../components/TradeInput/TradeInput.tsx | 13 ++++++++++--- .../tradeInputBase/createTradeInputBaseSlice.ts | 16 ++++++++++++++-- .../limitOrderInputSlice/limitOrderInputSlice.ts | 10 ++++++++-- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx b/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx index 2ac2292d14e..e12cfc7e8ff 100644 --- a/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx +++ b/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx @@ -54,6 +54,7 @@ import { limitOrderSlice } from 'state/slices/limitOrderSlice/limitOrderSlice' import { selectActiveQuoteNetworkFeeUserCurrency } from 'state/slices/limitOrderSlice/selectors' import { selectIsAnyAccountMetadataLoadedForChainId, + selectUsdRateByAssetId, selectUserCurrencyToUsdRate, } from 'state/slices/selectors' import { @@ -117,6 +118,8 @@ export const LimitOrderInput = ({ const expiry = useAppSelector(selectExpiry) const sellAssetBalanceCryptoBaseUnit = useAppSelector(selectSellAssetBalanceCryptoBaseUnit) const limitPriceMode = useAppSelector(selectLimitPriceMode) + const sellAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, sellAsset.assetId)) + const buyAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, buyAsset.assetId)) const { switchAssets, @@ -343,6 +346,10 @@ export const LimitOrderInput = ({ history.push(LimitOrderRoutePaths.Orders) }, [history]) + const handleSwitchAssets = useCallback(() => { + switchAssets({ sellAssetUsdRate, buyAssetUsdRate }) + }, [buyAssetUsdRate, sellAssetUsdRate, switchAssets]) + const isLoading = useMemo(() => { return ( isCheckingAllowance || @@ -399,7 +406,7 @@ export const LimitOrderInput = ({ sellAmountUserCurrency={inputSellAmountUserCurrency} sellAsset={sellAsset} sellAccountId={sellAccountId} - onSwitchAssets={switchAssets} + onSwitchAssets={handleSwitchAssets} isSwitchAssetsDisabled={isNativeEvmAsset(buyAsset.assetId)} onChangeIsInputtingFiatSellAmount={setIsInputtingFiatSellAmount} onChangeSellAmountCryptoPrecision={setSellAmountCryptoPrecision} diff --git a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx index 3d18ffa6088..aa9e6648b05 100644 --- a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx +++ b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx @@ -30,7 +30,11 @@ import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton' import { MixPanelEvent } from 'lib/mixpanel/types' import { selectIsVotingPowerLoading } from 'state/apis/snapshot/selectors' import type { ApiQuote } from 'state/apis/swapper/types' -import { selectIsAnyAccountMetadataLoadedForChainId, selectWalletId } from 'state/slices/selectors' +import { + selectIsAnyAccountMetadataLoadedForChainId, + selectUsdRateByAssetId, + selectWalletId, +} from 'state/slices/selectors' import { selectHasUserEnteredAmount, selectInputBuyAsset, @@ -124,6 +128,9 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput ) const walletId = useAppSelector(selectWalletId) + const sellAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, sellAsset.assetId)) + const buyAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, buyAsset.assetId)) + const inputOutputDifferenceDecimalPercentage = useInputOutputDifferenceDecimalPercentage(activeQuote) @@ -217,8 +224,8 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput [dispatch], ) const handleSwitchAssets = useCallback( - () => dispatch(tradeInput.actions.switchAssets()), - [dispatch], + () => dispatch(tradeInput.actions.switchAssets({ sellAssetUsdRate, buyAssetUsdRate })), + [buyAssetUsdRate, dispatch, sellAssetUsdRate], ) const handleConnect = useCallback(() => { diff --git a/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts b/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts index 9f7e25da00e..7dbc6431cad 100644 --- a/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts +++ b/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts @@ -3,6 +3,8 @@ import { createSlice } from '@reduxjs/toolkit' import type { AccountId } from '@shapeshiftoss/caip' import type { Asset } from '@shapeshiftoss/types' import { bnOrZero } from 'lib/bignumber/bignumber' +import { selectUsdRateByAssetId } from 'state/slices/marketDataSlice/selectors' +import { store } from 'state/store' export interface TradeInputBaseState { buyAsset: Asset @@ -62,11 +64,21 @@ const getBaseReducers = (initialState: T) => ({ setSellAmountCryptoPrecision: (state: Draft, action: PayloadAction) => { state.sellAmountCryptoPrecision = bnOrZero(action.payload).toString() }, - switchAssets: (state: Draft) => { + switchAssets: ( + state: Draft, + action: PayloadAction<{ + sellAssetUsdRate: string | undefined + buyAssetUsdRate: string | undefined + }>, + ) => { + const { sellAssetUsdRate, buyAssetUsdRate } = action.payload + const sellAmountUsd = bnOrZero(state.sellAmountCryptoPrecision).times(sellAssetUsdRate ?? '0') + + state.sellAmountCryptoPrecision = sellAmountUsd.div(buyAssetUsdRate ?? '0').toFixed() + const buyAsset = state.sellAsset state.sellAsset = state.buyAsset state.buyAsset = buyAsset - state.sellAmountCryptoPrecision = '0' const sellAssetAccountId = state.sellAccountId state.sellAccountId = state.buyAccountId diff --git a/src/state/slices/limitOrderInputSlice/limitOrderInputSlice.ts b/src/state/slices/limitOrderInputSlice/limitOrderInputSlice.ts index 8ce9421f5be..9fc65801e00 100644 --- a/src/state/slices/limitOrderInputSlice/limitOrderInputSlice.ts +++ b/src/state/slices/limitOrderInputSlice/limitOrderInputSlice.ts @@ -116,8 +116,14 @@ export const limitOrderInput = createTradeInputBaseSlice({ baseReducers.setSellAsset(state, action) resetLimitOrderConfig(state) }, - switchAssets: (state: LimitOrderInputState) => { - baseReducers.switchAssets(state) + switchAssets: ( + state: LimitOrderInputState, + action: PayloadAction<{ + sellAssetUsdRate: string | undefined + buyAssetUsdRate: string | undefined + }>, + ) => { + baseReducers.switchAssets(state, action) resetLimitOrderConfig(state) }, }), From be084abd45e176e6eb685018ce8d38b7796304c2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:18:59 +0300 Subject: [PATCH 3/4] feat: cleanup --- .../slices/common/tradeInputBase/createTradeInputBaseSlice.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts b/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts index 7dbc6431cad..c747100e840 100644 --- a/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts +++ b/src/state/slices/common/tradeInputBase/createTradeInputBaseSlice.ts @@ -3,8 +3,6 @@ import { createSlice } from '@reduxjs/toolkit' import type { AccountId } from '@shapeshiftoss/caip' import type { Asset } from '@shapeshiftoss/types' import { bnOrZero } from 'lib/bignumber/bignumber' -import { selectUsdRateByAssetId } from 'state/slices/marketDataSlice/selectors' -import { store } from 'state/store' export interface TradeInputBaseState { buyAsset: Asset From 3901cd86eff572364044e962de3dccb283996df6 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:28:55 +0300 Subject: [PATCH 4/4] fix: lint --- .../LimitOrder/components/LimitOrderInput.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx b/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx index e12cfc7e8ff..6fc1ee66a0e 100644 --- a/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx +++ b/src/components/MultiHopTrade/components/LimitOrder/components/LimitOrderInput.tsx @@ -436,25 +436,25 @@ export const LimitOrderInput = ({ ) }, [ - buyAccountId, buyAsset, - inputSellAmountUserCurrency, isInputtingFiatSellAmount, isLoading, - marketPriceBuyAsset, - sellAccountId, sellAmountCryptoPrecision, + inputSellAmountUserCurrency, sellAsset, - buyAssetFilterPredicate, - chainIdFilterPredicate, - sellAssetFilterPredicate, - setBuyAccountId, - setBuyAsset, + sellAccountId, + handleSwitchAssets, setIsInputtingFiatSellAmount, - setSellAccountId, setSellAmountCryptoPrecision, setSellAsset, - switchAssets, + setSellAccountId, + sellAssetFilterPredicate, + chainIdFilterPredicate, + buyAccountId, + setBuyAccountId, + setBuyAsset, + buyAssetFilterPredicate, + marketPriceBuyAsset, ]) const affiliateFeeAfterDiscountUserCurrency = useMemo(() => {