From 01c23f9870b0ab92feccbe995305fb2e85df79fb Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 18:18:00 +0500 Subject: [PATCH 01/90] feat(yield): setup yield widget --- .../src/common/constants/routes.ts | 2 + .../updaters/orders/PendingOrdersUpdater.ts | 28 ++--- .../src/modules/analytics/events.ts | 1 + .../application/containers/App/RoutesApp.tsx | 2 + .../containers/LimitOrdersWidget/index.tsx | 13 +-- .../pure/ReceiptModal/OrderTypeField.tsx | 1 + .../TradeWidget/TradeWidgetForm.tsx | 2 +- .../containers/TradeWidgetLinks/index.tsx | 1 + .../trade/hooks/useNotifyWidgetTrade.ts | 1 + .../src/modules/trade/hooks/useTradeState.ts | 14 +++ .../trade/hooks/useTradeTypeInfoFromUrl.tsx | 4 +- .../src/modules/trade/index.ts | 2 + .../trade/state/derivedTradeStateAtom.ts | 5 + .../src/modules/trade/types/TradeType.ts | 1 + .../tradeQuote/hooks/useTradeQuotePolling.ts | 2 +- .../modules/volumeFee/state/volumeFeeAtom.ts | 1 + .../containers/YieldConfirmModal/index.tsx | 55 +++++++++ .../yield/containers/YieldWidget/index.tsx | 106 ++++++++++++++++++ .../yield/hooks/useUpdateCurrencyAmount.ts | 22 ++++ .../yield/hooks/useUpdateYieldRawState.ts | 7 ++ .../yield/hooks/useYieldDerivedState.ts | 26 +++++ .../modules/yield/hooks/useYieldRawState.ts | 7 ++ .../yield/hooks/useYieldWidgetActions.ts | 47 ++++++++ .../src/modules/yield/index.ts | 5 + .../modules/yield/state/yieldRawStateAtom.ts | 30 +++++ .../modules/yield/state/yieldSettingsAtom.ts | 19 ++++ .../updaters/QuoteObserverUpdater/index.tsx | 40 +++++++ .../src/modules/yield/updaters/index.tsx | 17 +++ .../src/pages/Yield/index.tsx | 10 ++ .../src/utils/orderUtils/getUiOrderType.ts | 1 + libs/types/src/common.ts | 1 + libs/widget-lib/src/types.ts | 1 + 32 files changed, 447 insertions(+), 27 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useUpdateCurrencyAmount.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useUpdateYieldRawState.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useYieldDerivedState.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useYieldRawState.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/index.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/yield/updaters/index.tsx create mode 100644 apps/cowswap-frontend/src/pages/Yield/index.tsx diff --git a/apps/cowswap-frontend/src/common/constants/routes.ts b/apps/cowswap-frontend/src/common/constants/routes.ts index 1457da5df3..2e47db51c4 100644 --- a/apps/cowswap-frontend/src/common/constants/routes.ts +++ b/apps/cowswap-frontend/src/common/constants/routes.ts @@ -7,6 +7,7 @@ export const Routes = { SWAP: `/:chainId?${TRADE_WIDGET_PREFIX}/swap/:inputCurrencyId?/:outputCurrencyId?`, HOOKS: `/:chainId?${TRADE_WIDGET_PREFIX}/swap/hooks/:inputCurrencyId?/:outputCurrencyId?`, LIMIT_ORDER: `/:chainId?${TRADE_WIDGET_PREFIX}/limit/:inputCurrencyId?/:outputCurrencyId?`, + YIELD: `/:chainId?${TRADE_WIDGET_PREFIX}/yield/:inputCurrencyId?/:outputCurrencyId?`, ADVANCED_ORDERS: `/:chainId?${TRADE_WIDGET_PREFIX}/advanced/:inputCurrencyId?/:outputCurrencyId?`, LONG_LIMIT_ORDER: `/:chainId?${TRADE_WIDGET_PREFIX}/limit-orders/:inputCurrencyId?/:outputCurrencyId?`, LONG_ADVANCED_ORDERS: `/:chainId?${TRADE_WIDGET_PREFIX}/advanced-orders/:inputCurrencyId?/:outputCurrencyId?`, @@ -41,6 +42,7 @@ export const MENU_ITEMS: { route: RoutesValues; label: string; fullLabel?: strin { route: Routes.SWAP, label: 'Swap', description: 'Trade tokens' }, { route: Routes.LIMIT_ORDER, label: 'Limit', fullLabel: 'Limit order', description: 'Set your own price' }, { route: Routes.ADVANCED_ORDERS, label: 'TWAP', description: 'Place orders with a time-weighted average price' }, + { route: Routes.YIELD, label: 'Yield', fullLabel: 'Yield', description: 'Provide liquidity' }, // TODO ] export const HOOKS_STORE_MENU_ITEM = { diff --git a/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts b/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts index bb35f5283b..91e589f3a7 100644 --- a/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts +++ b/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts @@ -57,7 +57,7 @@ async function _updatePresignGnosisSafeTx( getSafeTxInfo: GetSafeTxInfo, updatePresignGnosisSafeTx: UpdatePresignGnosisSafeTxCallback, cancelOrdersBatch: CancelOrdersBatchCallback, - safeInfo: GnosisSafeInfo | undefined + safeInfo: GnosisSafeInfo | undefined, ) { const getSafeTxPromises = allPendingOrders // Update orders that are pending for presingature @@ -100,7 +100,7 @@ async function _updatePresignGnosisSafeTx( if (!error.isCancelledError) { console.error( `[PendingOrdersUpdater] Failed to check Gnosis Safe tx hash: ${presignGnosisSafeTxHash}`, - error + error, ) } }) @@ -113,7 +113,7 @@ async function _updateCreatingOrders( chainId: ChainId, pendingOrders: Order[], isSafeWallet: boolean, - addOrUpdateOrders: AddOrUpdateOrdersCallback + addOrUpdateOrders: AddOrUpdateOrdersCallback, ): Promise { const promises = pendingOrders.reduce[]>((acc, order) => { if (order.status === OrderStatus.CREATING) { @@ -205,7 +205,7 @@ async function _updateOrders({ // Iterate over pending orders fetching API data const unfilteredOrdersData = await Promise.all( - pending.map(async (orderFromStore) => fetchAndClassifyOrder(orderFromStore, chainId)) + pending.map(async (orderFromStore) => fetchAndClassifyOrder(orderFromStore, chainId)), ) // Group resolved promises by status @@ -219,7 +219,7 @@ async function _updateOrders({ } return acc }, - { fulfilled: [], expired: [], cancelled: [], unknown: [], presigned: [], pending: [], presignaturePending: [] } + { fulfilled: [], expired: [], cancelled: [], unknown: [], presigned: [], pending: [], presignaturePending: [] }, ) if (presigned.length > 0) { @@ -310,7 +310,7 @@ async function _updateOrders({ getSafeTxInfo, updatePresignGnosisSafeTx, cancelOrdersBatch, - safeInfo + safeInfo, ) // Update the creating EthFlow orders (if any) await _updateCreatingOrders(chainId, orders, isSafeWallet, addOrUpdateOrders) @@ -318,7 +318,7 @@ async function _updateOrders({ function getReplacedOrCancelledEthFlowOrders( orders: Order[], - allTransactions: UpdateOrdersParams['allTransactions'] + allTransactions: UpdateOrdersParams['allTransactions'], ): Order[] { return orders.filter((order) => { if (!order.orderCreationHash || order.status !== OrderStatus.CREATING) return false @@ -373,6 +373,7 @@ export function PendingOrdersUpdater(): null { const isUpdatingLimit = useRef(false) const isUpdatingTwap = useRef(false) const isUpdatingHooks = useRef(false) + const isUpdatingYield = useRef(false) const updatersRefMap = useMemo( () => ({ @@ -380,8 +381,9 @@ export function PendingOrdersUpdater(): null { [UiOrderType.LIMIT]: isUpdatingLimit, [UiOrderType.TWAP]: isUpdatingTwap, [UiOrderType.HOOKS]: isUpdatingHooks, + [UiOrderType.YIELD]: isUpdatingYield, }), - [] + [], ) // Ref, so we don't rerun useEffect @@ -411,7 +413,7 @@ export function PendingOrdersUpdater(): null { // Remove orders from the cancelling queue (marked by checkbox in the orders table) removeOrdersToCancel(fulfillOrdersBatchParams.orders.map(({ uid }) => uid)) }, - [chainId, _fulfillOrdersBatch, removeOrdersToCancel] + [chainId, _fulfillOrdersBatch, removeOrdersToCancel], ) const updateOrders = useCallback( @@ -460,7 +462,7 @@ export function PendingOrdersUpdater(): null { getSafeTxInfo, safeInfo, allTransactions, - ] + ], ) useEffect(() => { @@ -470,15 +472,15 @@ export function PendingOrdersUpdater(): null { const marketInterval = setInterval( () => updateOrders(chainId, account, isSafeWallet, UiOrderType.SWAP), - MARKET_OPERATOR_API_POLL_INTERVAL + MARKET_OPERATOR_API_POLL_INTERVAL, ) const limitInterval = setInterval( () => updateOrders(chainId, account, isSafeWallet, UiOrderType.LIMIT), - LIMIT_OPERATOR_API_POLL_INTERVAL + LIMIT_OPERATOR_API_POLL_INTERVAL, ) const twapInterval = setInterval( () => updateOrders(chainId, account, isSafeWallet, UiOrderType.TWAP), - LIMIT_OPERATOR_API_POLL_INTERVAL + LIMIT_OPERATOR_API_POLL_INTERVAL, ) updateOrders(chainId, account, isSafeWallet, UiOrderType.SWAP) diff --git a/apps/cowswap-frontend/src/modules/analytics/events.ts b/apps/cowswap-frontend/src/modules/analytics/events.ts index 58cd62ea61..cfbb5757c4 100644 --- a/apps/cowswap-frontend/src/modules/analytics/events.ts +++ b/apps/cowswap-frontend/src/modules/analytics/events.ts @@ -168,6 +168,7 @@ const LABEL_FROM_TYPE: Record = { [UiOrderType.SWAP]: 'Market Order', [UiOrderType.TWAP]: 'TWAP Order', [UiOrderType.HOOKS]: 'Hooks', + [UiOrderType.YIELD]: 'Yield', } function getClassLabel(orderClass: UiOrderType, label?: string) { diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx index 327fe74254..9874d1e49e 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx @@ -24,6 +24,7 @@ import { SwapPage } from 'pages/Swap' // Async routes const LimitOrders = lazy(() => import(/* webpackChunkName: "limit_orders" */ 'pages/LimitOrders')) +const YieldPage = lazy(() => import(/* webpackChunkName: "yield_widget" */ 'pages/Yield')) const AdvancedOrders = lazy(() => import(/* webpackChunkName: "advanced_orders" */ 'pages/AdvancedOrders')) const NotFound = lazy(() => import(/* webpackChunkName: "not_found" */ 'pages/error/NotFound')) const CowRunner = lazy(() => import(/* webpackChunkName: "cow_runner" */ 'pages/games/CowRunner')) @@ -52,6 +53,7 @@ function LazyRoute({ route, element, key }: LazyRouteProps) { const lazyRoutes: LazyRouteProps[] = [ { route: RoutesEnum.LIMIT_ORDER, element: }, + { route: RoutesEnum.YIELD, element: }, { route: RoutesEnum.LONG_LIMIT_ORDER, element: }, { route: RoutesEnum.ADVANCED_ORDERS, element: }, { route: RoutesEnum.LONG_ADVANCED_ORDERS, element: }, diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx index 15fd6f7921..2d71eef976 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx @@ -79,7 +79,7 @@ export function LimitOrdersWidget() { const priceImpact = useTradePriceImpact() const quoteAmount = useMemo( () => (isSell ? inputCurrencyAmount : outputCurrencyAmount), - [isSell, inputCurrencyAmount, outputCurrencyAmount] + [isSell, inputCurrencyAmount, outputCurrencyAmount], ) useSetTradeQuoteParams(quoteAmount) @@ -136,15 +136,6 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => { feeAmount, } = props - const inputCurrency = inputCurrencyInfo.currency - const outputCurrency = outputCurrencyInfo.currency - - const isTradePriceUpdating = useMemo(() => { - if (!inputCurrency || !outputCurrency) return false - - return isRateLoading - }, [isRateLoading, inputCurrency, outputCurrency]) - const tradeContext = useTradeFlowContext() const updateLimitOrdersState = useUpdateLimitOrdersRawState() const localFormValidation = useLimitOrdersFormState() @@ -206,7 +197,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => { compactView: false, recipient, showRecipient, - isTradePriceUpdating, + isTradePriceUpdating: isRateLoading, priceImpact, disablePriceImpact: localFormValidation === LimitOrdersFormState.FeeExceedsFrom, disableQuotePolling: isConfirmOpen, diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/OrderTypeField.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/OrderTypeField.tsx index 48b9f0abad..d7256f6ffa 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/OrderTypeField.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/OrderTypeField.tsx @@ -14,6 +14,7 @@ const ORDER_UI_TYPE_LABELS: Record = { [UiOrderType.LIMIT]: 'Limit', [UiOrderType.TWAP]: 'TWAP', [UiOrderType.HOOKS]: 'Hooks', + [UiOrderType.YIELD]: 'Yield', } export function OrderTypeField({ order }: Props) { diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index bba2c13940..a3c22f9e09 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -199,7 +199,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { isCollapsed={compactView} hasSeparatorLine={!compactView} onSwitchTokens={isChainIdUnsupported ? () => void 0 : throttledOnSwitchTokens} - isLoading={isTradePriceUpdating} + isLoading={Boolean(inputCurrencyInfo.currency && outputCurrencyInfo.currency && isTradePriceUpdating)} disabled={isAlternativeOrderModalVisible} /> diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx index cb7c298f86..02eb509ccf 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx @@ -29,6 +29,7 @@ const TRADE_TYPE_TO_ROUTE: Record = { swap: Routes.SWAP, limit: Routes.LIMIT_ORDER, advanced: Routes.ADVANCED_ORDERS, + yield: Routes.YIELD, } interface TradeWidgetLinksProps { diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts index 0d3b539695..38d74eae3b 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts @@ -36,6 +36,7 @@ const TradeTypeToUiOrderType: Record = { [TradeType.SWAP]: UiOrderType.SWAP, [TradeType.LIMIT_ORDER]: UiOrderType.LIMIT, [TradeType.ADVANCED_ORDERS]: UiOrderType.TWAP, + [TradeType.YIELD]: UiOrderType.YIELD, } function getTradeParamsEventPayload(tradeType: TradeType, state: TradeDerivedState): OnTradeParamsPayload { diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts index 213cd9002f..bcdc6afe69 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts @@ -11,6 +11,7 @@ import { ExtendedTradeRawState, TradeRawState } from 'modules/trade/types/TradeR import { useTradeTypeInfoFromUrl } from './useTradeTypeInfoFromUrl' import { TradeType } from '../types' +import { useUpdateYieldRawState, useYieldRawState } from 'modules/yield' const EMPTY_TRADE_STATE = {} @@ -29,6 +30,9 @@ export function useTradeState(): { const swapTradeState = useSwapRawState() const updateSwapState = useUpdateSwapRawState() + const yieldRawState = useYieldRawState() + const updateYieldRawState = useUpdateYieldRawState() + return useMemo(() => { if (!tradeTypeInfo) return EMPTY_TRADE_STATE @@ -46,6 +50,13 @@ export function useTradeState(): { } } + if (tradeTypeInfo.tradeType === TradeType.YIELD) { + return { + state: yieldRawState, + updateState: updateYieldRawState, + } + } + return { state: limitOrdersState, updateState: updateLimitOrdersState, @@ -60,7 +71,10 @@ export function useTradeState(): { JSON.stringify(advancedOrdersState), // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(swapTradeState), + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(yieldRawState), updateSwapState, updateLimitOrdersState, + updateYieldRawState, ]) } diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeTypeInfoFromUrl.tsx b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeTypeInfoFromUrl.tsx index c5ee725884..0774836eae 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeTypeInfoFromUrl.tsx +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeTypeInfoFromUrl.tsx @@ -12,15 +12,17 @@ export function useTradeTypeInfoFromUrl(): TradeTypeInfo | null { const hooksMatch = !!useMatchTradeRoute('swap/hooks') const limitOrderMatch = !!useMatchTradeRoute('limit') const advancedOrdersMatch = !!useMatchTradeRoute('advanced') + const yieldMatch = !!useMatchTradeRoute('yield') return useMemo(() => { if (hooksMatch) return { tradeType: TradeType.SWAP, route: Routes.HOOKS } if (swapMatch) return { tradeType: TradeType.SWAP, route: Routes.SWAP } if (limitOrderMatch) return { tradeType: TradeType.LIMIT_ORDER, route: Routes.LIMIT_ORDER } if (advancedOrdersMatch) return { tradeType: TradeType.ADVANCED_ORDERS, route: Routes.ADVANCED_ORDERS } + if (yieldMatch) return { tradeType: TradeType.YIELD, route: Routes.YIELD } return null - }, [swapMatch, limitOrderMatch, advancedOrdersMatch]) + }, [swapMatch, hooksMatch, limitOrderMatch, advancedOrdersMatch, yieldMatch]) } function useMatchTradeRoute(route: string): PathMatch<'chainId'> | null { diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index c08f839ed6..eb10eefe88 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -25,6 +25,8 @@ export * from './hooks/useIsWrapOrUnwrap' export * from './hooks/useIsHooksTradeType' export * from './hooks/useHasTradeEnoughAllowance' export * from './hooks/useIsSellNative' +export * from './hooks/useBuildTradeDerivedState' +export * from './hooks/useOnCurrencySelection' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' diff --git a/apps/cowswap-frontend/src/modules/trade/state/derivedTradeStateAtom.ts b/apps/cowswap-frontend/src/modules/trade/state/derivedTradeStateAtom.ts index a72ff94d9a..17414929b0 100644 --- a/apps/cowswap-frontend/src/modules/trade/state/derivedTradeStateAtom.ts +++ b/apps/cowswap-frontend/src/modules/trade/state/derivedTradeStateAtom.ts @@ -3,6 +3,7 @@ import { atom } from 'jotai' import { advancedOrdersDerivedStateAtom } from 'modules/advancedOrders' import { limitOrdersDerivedStateAtom } from 'modules/limitOrders' import { swapDerivedStateAtom } from 'modules/swap' +import { yieldDerivedStateAtom } from 'modules/yield' import { tradeTypeAtom } from './tradeTypeAtom' @@ -21,5 +22,9 @@ export const derivedTradeStateAtom = atom((get) => { return get(advancedOrdersDerivedStateAtom) } + if (tradeTypeInfo.tradeType === TradeType.YIELD) { + return get(yieldDerivedStateAtom) + } + return get(limitOrdersDerivedStateAtom) }) diff --git a/apps/cowswap-frontend/src/modules/trade/types/TradeType.ts b/apps/cowswap-frontend/src/modules/trade/types/TradeType.ts index 0fdcfe3556..283e8ba206 100644 --- a/apps/cowswap-frontend/src/modules/trade/types/TradeType.ts +++ b/apps/cowswap-frontend/src/modules/trade/types/TradeType.ts @@ -4,6 +4,7 @@ export enum TradeType { SWAP = 'SWAP', LIMIT_ORDER = 'LIMIT_ORDER', ADVANCED_ORDERS = 'ADVANCED_ORDERS', + YIELD = 'YIELD', } export interface TradeTypeInfo { diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts index 769e40e219..ed17700e86 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts @@ -29,7 +29,7 @@ export function useTradeQuotePolling() { const { amount } = useAtomValue(tradeQuoteParamsAtom) const amountStr = useDebounce( useMemo(() => amount?.quotient.toString() || null, [amount]), - AMOUNT_CHANGE_DEBOUNCE_TIME + AMOUNT_CHANGE_DEBOUNCE_TIME, ) const quoteParams = useQuoteParams(amountStr) diff --git a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts index ebe42b5f42..60b30aa4e0 100644 --- a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts +++ b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts @@ -49,4 +49,5 @@ const TradeTypeMap: Record = { [TradeType.SWAP]: WidgetTradeType.SWAP, [TradeType.LIMIT_ORDER]: WidgetTradeType.LIMIT, [TradeType.ADVANCED_ORDERS]: WidgetTradeType.ADVANCED, + [TradeType.YIELD]: WidgetTradeType.YIELD, } diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx new file mode 100644 index 0000000000..6693c19c50 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -0,0 +1,55 @@ +import React from 'react' + +import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' + +import type { PriceImpact } from 'legacy/hooks/usePriceImpact' + +import { useAppData } from 'modules/appData' +import { TradeConfirmation, TradeConfirmModal, useTradeConfirmActions } from 'modules/trade' + +import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' + +const CONFIRM_TITLE = 'Confirm order' + +export interface YieldConfirmModalProps { + inputCurrencyInfo: CurrencyPreviewInfo + outputCurrencyInfo: CurrencyPreviewInfo + priceImpact: PriceImpact + recipient?: string | null +} + +export function YieldConfirmModal(props: YieldConfirmModalProps) { + const { inputCurrencyInfo, outputCurrencyInfo, priceImpact, recipient } = props + + const { account } = useWalletInfo() + const { ensName } = useWalletDetails() + const appData = useAppData() + const tradeConfirmActions = useTradeConfirmActions() + + const doTrade = () => { + console.log('TODO doTrade') + } + + // TODO + const isConfirmDisabled = false + + return ( + + + + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx new file mode 100644 index 0000000000..ec97f4df89 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -0,0 +1,106 @@ +import { useAtomValue } from 'jotai' +import React from 'react' + +import { Field } from 'legacy/state/types' + +import { TradeWidget, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' +import { useTradeQuote } from 'modules/tradeQuote' + +import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' + +import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' +import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' +import { yieldSettingsAtom } from '../../state/yieldSettingsAtom' +import { YieldConfirmModal } from '../YieldConfirmModal' + +export function YieldWidget() { + const settingsState = useAtomValue(yieldSettingsAtom) + const { isLoading: isRateLoading } = useTradeQuote() + const priceImpact = useTradePriceImpact() + const { isOpen: isConfirmOpen } = useTradeConfirmState() + const widgetActions = useYieldWidgetActions() + const { + inputCurrency, + outputCurrency, + inputCurrencyAmount, + outputCurrencyAmount, + inputCurrencyBalance, + outputCurrencyBalance, + inputCurrencyFiatAmount, + outputCurrencyFiatAmount, + recipient, + } = useYieldDerivedState() + + const { showRecipient } = settingsState + + const inputCurrencyInfo: CurrencyInfo = { + field: Field.INPUT, + label: 'Sell amount', + currency: inputCurrency, + amount: inputCurrencyAmount, + isIndependent: true, + balance: inputCurrencyBalance, + fiatAmount: inputCurrencyFiatAmount, + receiveAmountInfo: null, + } + const outputCurrencyInfo: CurrencyInfo = { + field: Field.OUTPUT, + label: 'Buy exactly', + currency: outputCurrency, + amount: outputCurrencyAmount, + isIndependent: true, + balance: outputCurrencyBalance, + fiatAmount: outputCurrencyFiatAmount, + receiveAmountInfo: null, + } + const inputCurrencyPreviewInfo = { + amount: inputCurrencyInfo.amount, + fiatAmount: inputCurrencyInfo.fiatAmount, + balance: inputCurrencyInfo.balance, + label: inputCurrencyInfo.label, + } + + const outputCurrencyPreviewInfo = { + amount: outputCurrencyInfo.amount, + fiatAmount: outputCurrencyInfo.fiatAmount, + balance: outputCurrencyInfo.balance, + label: outputCurrencyInfo.label, + } + + const slots = { + settingsWidget: , + bottomContent: ( + <> + + + ), + } + + const params = { + compactView: false, + recipient, + showRecipient, + isTradePriceUpdating: isRateLoading, + priceImpact, + disableQuotePolling: isConfirmOpen, + } + + return ( + + } + /> + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateCurrencyAmount.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateCurrencyAmount.ts new file mode 100644 index 0000000000..701a2d52ae --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateCurrencyAmount.ts @@ -0,0 +1,22 @@ +import { useCallback } from 'react' + +import { FractionUtils } from '@cowprotocol/common-utils' +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' + +import { Field } from 'legacy/state/types' + +import { useUpdateYieldRawState } from './useUpdateYieldRawState' + +export function useUpdateCurrencyAmount() { + const updateYieldState = useUpdateYieldRawState() + + return useCallback( + (field: Field, value: CurrencyAmount) => { + updateYieldState({ + [field === Field.INPUT ? 'inputCurrencyAmount' : 'outputCurrencyAmount']: + FractionUtils.serializeFractionToJSON(value), + }) + }, + [updateYieldState], + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateYieldRawState.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateYieldRawState.ts new file mode 100644 index 0000000000..11b110957d --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useUpdateYieldRawState.ts @@ -0,0 +1,7 @@ +import { useSetAtom } from 'jotai' + +import { updateYieldRawStateAtom } from '../state/yieldRawStateAtom' + +export function useUpdateYieldRawState() { + return useSetAtom(updateYieldRawStateAtom) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldDerivedState.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldDerivedState.ts new file mode 100644 index 0000000000..d2554d390a --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldDerivedState.ts @@ -0,0 +1,26 @@ +import { useAtomValue } from 'jotai' +import { useSetAtom } from 'jotai/index' +import { useEffect } from 'react' + +import { INITIAL_ALLOWED_SLIPPAGE_PERCENT } from '@cowprotocol/common-const' + +import { TradeType, useBuildTradeDerivedState } from 'modules/trade' + +import { yieldDerivedStateAtom, yieldRawStateAtom } from '../state/yieldRawStateAtom' + +export function useYieldDerivedState() { + return useAtomValue(yieldDerivedStateAtom) +} + +export function useFillYieldDerivedState() { + const updateDerivedState = useSetAtom(yieldDerivedStateAtom) + const derivedState = useBuildTradeDerivedState(yieldRawStateAtom) + + useEffect(() => { + updateDerivedState({ + ...derivedState, + slippage: INITIAL_ALLOWED_SLIPPAGE_PERCENT, + tradeType: TradeType.YIELD, + }) + }, [derivedState, updateDerivedState]) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldRawState.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldRawState.ts new file mode 100644 index 0000000000..bc09f9631d --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldRawState.ts @@ -0,0 +1,7 @@ +import { useAtomValue } from 'jotai' + +import { yieldRawStateAtom } from '../state/yieldRawStateAtom' + +export function useYieldRawState() { + return useAtomValue(yieldRawStateAtom) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts new file mode 100644 index 0000000000..6c6a7503aa --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts @@ -0,0 +1,47 @@ +import { useCallback, useMemo } from 'react' + +import { FractionUtils, isSellOrder, tryParseCurrencyAmount } from '@cowprotocol/common-utils' +import { OrderKind } from '@cowprotocol/cow-sdk' + +import { Field } from 'legacy/state/types' + +import { TradeWidgetActions, useIsWrapOrUnwrap, useOnCurrencySelection, useSwitchTokensPlaces } from 'modules/trade' + +import { useUpdateYieldRawState } from './useUpdateYieldRawState' +import { useYieldDerivedState } from './useYieldDerivedState' +import { useUpdateCurrencyAmount } from './useUpdateCurrencyAmount' + +export function useYieldWidgetActions(): TradeWidgetActions { + const { inputCurrency, outputCurrency, orderKind } = useYieldDerivedState() + const isWrapOrUnwrap = useIsWrapOrUnwrap() + const updateYieldState = useUpdateYieldRawState() + const onCurrencySelection = useOnCurrencySelection() + const updateCurrencyAmount = useUpdateCurrencyAmount() + + const onUserInput = useCallback( + (field: Field, typedValue: string) => { + const currency = field === Field.INPUT ? inputCurrency : outputCurrency + + if (!currency) return + + const value = tryParseCurrencyAmount(typedValue, currency) || null + + updateCurrencyAmount(field, value) + }, + [updateCurrencyAmount, isWrapOrUnwrap, inputCurrency, outputCurrency], + ) + + const onSwitchTokens = useSwitchTokensPlaces({ + orderKind: isSellOrder(orderKind) ? OrderKind.BUY : OrderKind.SELL, + }) + + const onChangeRecipient = useCallback( + (recipient: string | null) => updateYieldState({ recipient }), + [updateYieldState], + ) + + return useMemo( + () => ({ onUserInput, onSwitchTokens, onChangeRecipient, onCurrencySelection }), + [onUserInput, onSwitchTokens, onChangeRecipient, onCurrencySelection], + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/index.ts b/apps/cowswap-frontend/src/modules/yield/index.ts new file mode 100644 index 0000000000..54d34b9a12 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/index.ts @@ -0,0 +1,5 @@ +export { YieldWidget } from './containers/YieldWidget' +export { useYieldRawState } from './hooks/useYieldRawState' +export { useUpdateYieldRawState } from './hooks/useUpdateYieldRawState' +export { YieldUpdaters } from './updaters' +export { yieldDerivedStateAtom } from './state/yieldRawStateAtom' diff --git a/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts new file mode 100644 index 0000000000..749a5d3964 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts @@ -0,0 +1,30 @@ +import { atom } from 'jotai/index' +import { atomWithStorage } from 'jotai/utils' + +import { atomWithPartialUpdate } from '@cowprotocol/common-utils' +import { getJotaiIsolatedStorage } from '@cowprotocol/core' +import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' + +import { DEFAULT_TRADE_DERIVED_STATE, TradeDerivedState } from 'modules/trade/types/TradeDerivedState' +import { ExtendedTradeRawState, getDefaultTradeRawState } from 'modules/trade/types/TradeRawState' + +export interface YieldDerivedState extends TradeDerivedState {} + +export interface YieldRawState extends ExtendedTradeRawState {} + +export function getDefaultYieldState(chainId: SupportedChainId | null): YieldRawState { + return { + ...getDefaultTradeRawState(chainId), + inputCurrencyAmount: null, + outputCurrencyAmount: null, + orderKind: OrderKind.SELL, + } +} + +export const { atom: yieldRawStateAtom, updateAtom: updateYieldRawStateAtom } = atomWithPartialUpdate( + atomWithStorage('yieldStateAtom:v0', getDefaultYieldState(null), getJotaiIsolatedStorage()), +) + +export const yieldDerivedStateAtom = atom({ + ...DEFAULT_TRADE_DERIVED_STATE, +}) diff --git a/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts new file mode 100644 index 0000000000..d407eb9ff7 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts @@ -0,0 +1,19 @@ +import { atomWithStorage } from 'jotai/utils' + +import { getJotaiIsolatedStorage } from '@cowprotocol/core' + +export interface YieldSettingsState { + readonly showRecipient: boolean + readonly partialFillsEnabled: boolean +} + +export const defaultYieldSettings: YieldSettingsState = { + showRecipient: false, + partialFillsEnabled: true, +} + +export const yieldSettingsAtom = atomWithStorage( + 'yieldSettingsAtom:v0', + defaultYieldSettings, + getJotaiIsolatedStorage(), +) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx new file mode 100644 index 0000000000..f0907f40f5 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx @@ -0,0 +1,40 @@ +import { useEffect, useLayoutEffect } from 'react' + +import { CurrencyAmount } from '@uniswap/sdk-core' + +import { Field } from 'legacy/state/types' + +import { useDerivedTradeState } from 'modules/trade/hooks/useDerivedTradeState' +import { useTradeQuote } from 'modules/tradeQuote' + +import { useUpdateCurrencyAmount } from '../../hooks/useUpdateCurrencyAmount' + +export function QuoteObserverUpdater() { + const { response } = useTradeQuote() + const state = useDerivedTradeState() + + const updateLimitRateState = useUpdateCurrencyAmount() + + const inputCurrency = state?.inputCurrency + const outputCurrency = state?.outputCurrency + + useLayoutEffect(() => { + if (!outputCurrency || !inputCurrency || !response) { + return + } + + const { buyAmount: buyAmountRaw } = response.quote + + updateLimitRateState(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, buyAmountRaw)) + }, [response, inputCurrency, outputCurrency, updateLimitRateState]) + + useEffect(() => { + if (!outputCurrency) { + return + } + + updateLimitRateState(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, 0)) + }, [state?.inputCurrencyAmount, updateLimitRateState, outputCurrency]) + + return null +} diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx new file mode 100644 index 0000000000..0f62e0c158 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx @@ -0,0 +1,17 @@ +import { QuoteObserverUpdater } from './QuoteObserverUpdater' + +import { useSetTradeQuoteParams } from '../../tradeQuote' +import { useFillYieldDerivedState, useYieldDerivedState } from '../hooks/useYieldDerivedState' + +export function YieldUpdaters() { + const { inputCurrencyAmount } = useYieldDerivedState() + + useFillYieldDerivedState() + useSetTradeQuoteParams(inputCurrencyAmount) + + return ( + <> + + + ) +} diff --git a/apps/cowswap-frontend/src/pages/Yield/index.tsx b/apps/cowswap-frontend/src/pages/Yield/index.tsx new file mode 100644 index 0000000000..0c4d78cf01 --- /dev/null +++ b/apps/cowswap-frontend/src/pages/Yield/index.tsx @@ -0,0 +1,10 @@ +import { YieldWidget, YieldUpdaters } from 'modules/yield' + +export default function YieldPage() { + return ( + <> + + + + ) +} diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getUiOrderType.ts b/apps/cowswap-frontend/src/utils/orderUtils/getUiOrderType.ts index 3094b3c877..fcffd64457 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/getUiOrderType.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/getUiOrderType.ts @@ -25,6 +25,7 @@ export const ORDER_UI_TYPE_TITLES: Record = { [UiOrderType.LIMIT]: 'Limit order', [UiOrderType.TWAP]: 'TWAP order', [UiOrderType.HOOKS]: 'Hooks', + [UiOrderType.YIELD]: 'Yield', } export type UiOrderTypeParams = Pick diff --git a/libs/types/src/common.ts b/libs/types/src/common.ts index 8b40e93722..35ec4a3fd8 100644 --- a/libs/types/src/common.ts +++ b/libs/types/src/common.ts @@ -11,6 +11,7 @@ export enum UiOrderType { LIMIT = 'LIMIT', TWAP = 'TWAP', HOOKS = 'HOOKS', + YIELD = 'YIELD', } export type TokenInfo = { diff --git a/libs/widget-lib/src/types.ts b/libs/widget-lib/src/types.ts index 718ac05d76..d7514c866d 100644 --- a/libs/widget-lib/src/types.ts +++ b/libs/widget-lib/src/types.ts @@ -90,6 +90,7 @@ export enum TradeType { * But in the future it can be extended to support other order types. */ ADVANCED = 'advanced', + YIELD = 'yield', } /** From e8514b52c06c8fea7d98345869bb0e0697879590 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 19:56:49 +0500 Subject: [PATCH 02/90] fix(yield): display correct output amount --- apps/cowswap-frontend/src/modules/trade/index.ts | 1 + .../updaters/QuoteObserverUpdater/index.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index eb10eefe88..a835e2625a 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -27,6 +27,7 @@ export * from './hooks/useHasTradeEnoughAllowance' export * from './hooks/useIsSellNative' export * from './hooks/useBuildTradeDerivedState' export * from './hooks/useOnCurrencySelection' +export * from './hooks/useDerivedTradeState' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx index f0907f40f5..fafefa5fa8 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx @@ -4,30 +4,30 @@ import { CurrencyAmount } from '@uniswap/sdk-core' import { Field } from 'legacy/state/types' -import { useDerivedTradeState } from 'modules/trade/hooks/useDerivedTradeState' -import { useTradeQuote } from 'modules/tradeQuote' +import { useReceiveAmountInfo, useDerivedTradeState } from 'modules/trade' import { useUpdateCurrencyAmount } from '../../hooks/useUpdateCurrencyAmount' export function QuoteObserverUpdater() { - const { response } = useTradeQuote() const state = useDerivedTradeState() + const receiveAmountInfo = useReceiveAmountInfo() + const { beforeNetworkCosts } = receiveAmountInfo || {} const updateLimitRateState = useUpdateCurrencyAmount() const inputCurrency = state?.inputCurrency const outputCurrency = state?.outputCurrency + // Set the output amount from quote response (receiveAmountInfo is a derived state from tradeQuote state) useLayoutEffect(() => { - if (!outputCurrency || !inputCurrency || !response) { + if (!outputCurrency || !inputCurrency || !beforeNetworkCosts?.buyAmount) { return } - const { buyAmount: buyAmountRaw } = response.quote - - updateLimitRateState(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, buyAmountRaw)) - }, [response, inputCurrency, outputCurrency, updateLimitRateState]) + updateLimitRateState(Field.OUTPUT, beforeNetworkCosts?.buyAmount) + }, [beforeNetworkCosts, inputCurrency, outputCurrency, updateLimitRateState]) + // Reset the output amount when the input amount changes useEffect(() => { if (!outputCurrency) { return From 4bde353a731d5f44066c9465bdfa569cfd18bae6 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 20:15:39 +0500 Subject: [PATCH 03/90] feat(trade-quote): support fast quote requests --- .../services/validateTradeForm.ts | 2 + .../hooks/useSetTradeQuoteParams.ts | 6 +-- .../tradeQuote/hooks/useTradeQuotePolling.ts | 42 ++++++++++++++----- .../tradeQuote/state/tradeQuoteParamsAtom.ts | 1 + .../updaters/QuoteObserverUpdater/index.tsx | 4 +- .../src/modules/yield/updaters/index.tsx | 5 ++- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts b/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts index 2b0656b1ca..c6f1713cb3 100644 --- a/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts +++ b/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts @@ -1,4 +1,5 @@ import { getIsNativeToken, isAddress, isFractionFalsy } from '@cowprotocol/common-utils' +import { PriceQuality } from '@cowprotocol/cow-sdk' import { TradeType } from 'modules/trade' import { isQuoteExpired } from 'modules/tradeQuote' @@ -79,6 +80,7 @@ export function validateTradeForm(context: TradeFormValidationContext): TradeFor if ( derivedTradeState.tradeType !== TradeType.LIMIT_ORDER && !tradeQuote.isLoading && + tradeQuote.quoteParams?.priceQuality !== PriceQuality.FAST && isQuoteExpired({ expirationDate: tradeQuote.response?.expiration, deadlineParams: { diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useSetTradeQuoteParams.ts b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useSetTradeQuoteParams.ts index ac0677b301..47af867581 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useSetTradeQuoteParams.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useSetTradeQuoteParams.ts @@ -11,14 +11,14 @@ import { useUpdateTradeQuote } from './useUpdateTradeQuote' import { tradeQuoteParamsAtom } from '../state/tradeQuoteParamsAtom' -export function useSetTradeQuoteParams(amount: Nullish>) { +export function useSetTradeQuoteParams(amount: Nullish>, fastQuote?: boolean) { const updateTradeQuote = useUpdateTradeQuote() const updateState = useSetAtom(tradeQuoteParamsAtom) - const context = useSafeMemoObject({ amount, updateTradeQuote, updateState }) + const context = useSafeMemoObject({ amount, fastQuote, updateTradeQuote, updateState }) useEffect(() => { context.updateTradeQuote({ response: null, error: null }) - context.updateState({ amount: context.amount || null }) + context.updateState({ amount: context.amount || null, fastQuote: context.fastQuote }) }, [context]) } diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts index ed17700e86..c023bfd92d 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts @@ -3,7 +3,7 @@ import { useLayoutEffect, useMemo } from 'react' import { useDebounce } from '@cowprotocol/common-hooks' import { onlyResolvesLast } from '@cowprotocol/common-utils' -import { OrderQuoteResponse } from '@cowprotocol/cow-sdk' +import { OrderQuoteResponse, PriceQuality } from '@cowprotocol/cow-sdk' import { useAreUnsupportedTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -23,10 +23,11 @@ export const PRICE_UPDATE_INTERVAL = ms`30s` const AMOUNT_CHANGE_DEBOUNCE_TIME = ms`300` // Solves the problem of multiple requests -const getQuoteOnlyResolveLast = onlyResolvesLast(getQuote) +const getFastQuote = onlyResolvesLast(getQuote) +const getOptimalQuote = onlyResolvesLast(getQuote) export function useTradeQuotePolling() { - const { amount } = useAtomValue(tradeQuoteParamsAtom) + const { amount, fastQuote } = useAtomValue(tradeQuoteParamsAtom) const amountStr = useDebounce( useMemo(() => amount?.quotient.toString() || null, [amount]), AMOUNT_CHANGE_DEBOUNCE_TIME, @@ -51,10 +52,14 @@ export function useTradeQuotePolling() { return } - const fetchQuote = (hasParamsChanged: boolean) => { + const fetchQuote = (hasParamsChanged: boolean, priceQuality: PriceQuality) => { updateQuoteState({ isLoading: true, hasParamsChanged }) - getQuoteOnlyResolveLast(quoteParams) + const isOptimalQuote = priceQuality === PriceQuality.OPTIMAL + const requestParams = { ...quoteParams, priceQuality } + const request = isOptimalQuote ? getOptimalQuote(requestParams) : getFastQuote(requestParams) + + request .then((response) => { const { cancelled, data } = response @@ -62,24 +67,41 @@ export function useTradeQuotePolling() { return } - updateQuoteState({ response: data, quoteParams, isLoading: false, error: null, hasParamsChanged: false }) + updateQuoteState({ + response: data, + quoteParams: requestParams, + ...(isOptimalQuote ? { isLoading: false } : null), + error: null, + hasParamsChanged: false, + }) }) .catch((error: QuoteApiError) => { console.log('[useGetQuote]:: fetchQuote error', error) updateQuoteState({ isLoading: false, error, hasParamsChanged: false }) if (error.type === QuoteApiErrorCodes.UnsupportedToken) { - processUnsupportedTokenError(error, quoteParams) + processUnsupportedTokenError(error, requestParams) } }) } - fetchQuote(true) + fetchQuote(true, PriceQuality.OPTIMAL) + if (fastQuote) fetchQuote(true, PriceQuality.FAST) - const intervalId = setInterval(() => fetchQuote(false), PRICE_UPDATE_INTERVAL) + const intervalId = setInterval(() => { + fetchQuote(false, PriceQuality.OPTIMAL) + if (fastQuote) fetchQuote(false, PriceQuality.FAST) + }, PRICE_UPDATE_INTERVAL) return () => clearInterval(intervalId) - }, [quoteParams, updateQuoteState, updateCurrencyAmount, processUnsupportedTokenError, getIsUnsupportedTokens]) + }, [ + fastQuote, + quoteParams, + updateQuoteState, + updateCurrencyAmount, + processUnsupportedTokenError, + getIsUnsupportedTokens, + ]) return null } diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteParamsAtom.ts b/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteParamsAtom.ts index dd5e54de55..ae1193ec84 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteParamsAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteParamsAtom.ts @@ -4,6 +4,7 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' export interface TradeQuoteParamsState { amount: CurrencyAmount | null + fastQuote?: boolean } export const tradeQuoteParamsAtom = atom({ amount: null }) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx index fafefa5fa8..99159eda0d 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx @@ -24,7 +24,7 @@ export function QuoteObserverUpdater() { return } - updateLimitRateState(Field.OUTPUT, beforeNetworkCosts?.buyAmount) + updateLimitRateState(Field.OUTPUT, beforeNetworkCosts.buyAmount) }, [beforeNetworkCosts, inputCurrency, outputCurrency, updateLimitRateState]) // Reset the output amount when the input amount changes @@ -34,7 +34,7 @@ export function QuoteObserverUpdater() { } updateLimitRateState(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, 0)) - }, [state?.inputCurrencyAmount, updateLimitRateState, outputCurrency]) + }, [state?.inputCurrencyAmount, state?.inputCurrency, updateLimitRateState, outputCurrency]) return null } diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx index 0f62e0c158..eddbf07bea 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx @@ -1,13 +1,14 @@ +import { useSetTradeQuoteParams } from 'modules/tradeQuote' + import { QuoteObserverUpdater } from './QuoteObserverUpdater' -import { useSetTradeQuoteParams } from '../../tradeQuote' import { useFillYieldDerivedState, useYieldDerivedState } from '../hooks/useYieldDerivedState' export function YieldUpdaters() { const { inputCurrencyAmount } = useYieldDerivedState() useFillYieldDerivedState() - useSetTradeQuoteParams(inputCurrencyAmount) + useSetTradeQuoteParams(inputCurrencyAmount, true) return ( <> From cf6321495048e00c8263d8558270981f588722f5 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 20:35:31 +0500 Subject: [PATCH 04/90] feat(yield): display trade buttons --- .../yield/containers/TradeButtons/index.tsx | 32 +++++++++++++++++++ .../containers/YieldConfirmModal/index.tsx | 2 +- .../yield/containers/YieldWidget/index.tsx | 7 ++-- 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx diff --git a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx new file mode 100644 index 0000000000..a463d0d70b --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx @@ -0,0 +1,32 @@ +import React from 'react' + +import { useTradeConfirmActions } from 'modules/trade' +import { TradeFormButtons, useGetTradeFormValidation, useTradeFormButtonContext } from 'modules/tradeFormValidation' + +const CONFIRM_TEXT = 'Swap' + +interface TradeButtonsProps { + isTradeContextReady: boolean +} + +export function TradeButtons({ isTradeContextReady }: TradeButtonsProps) { + const primaryFormValidation = useGetTradeFormValidation() + const tradeConfirmActions = useTradeConfirmActions() + + const confirmTrade = tradeConfirmActions.onOpen + + const tradeFormButtonContext = useTradeFormButtonContext(CONFIRM_TEXT, confirmTrade) + + const isDisabled = !isTradeContextReady + + if (!tradeFormButtonContext) return null + + return ( + + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index 6693c19c50..b1d345cc15 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -45,7 +45,7 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { onDismiss={tradeConfirmActions.onDismiss} isConfirmDisabled={isConfirmDisabled} priceImpact={priceImpact} - buttonText="Deposit" // TODO + buttonText="Confirm and swap" // TODO recipient={recipient} appData={appData || undefined} isPriceStatic={true} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index ec97f4df89..eb3e50e219 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -11,6 +11,7 @@ import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' import { yieldSettingsAtom } from '../../state/yieldSettingsAtom' +import { TradeButtons } from '../TradeButtons' import { YieldConfirmModal } from '../YieldConfirmModal' export function YieldWidget() { @@ -69,11 +70,7 @@ export function YieldWidget() { const slots = { settingsWidget: , - bottomContent: ( - <> - - - ), + bottomContent: , } const params = { From 2513c8968ec759445d13374f01327e9159d7828b Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 20:54:21 +0500 Subject: [PATCH 05/90] feat(yield): display confirm details --- .../ConfirmSwapModalSetup/index.tsx | 5 --- .../TradeBasicConfirmDetails/index.tsx | 10 +++--- .../src/modules/trade/index.ts | 1 + .../containers/TwapConfirmModal/index.tsx | 9 +---- .../containers/YieldConfirmModal/index.tsx | 36 +++++++++++++++++-- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 9b58d26442..227dfdef0a 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -73,14 +73,11 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { const { recipient } = useSwapState() const tradeConfirmActions = useTradeConfirmActions() const receiveAmountInfo = useReceiveAmountInfo() - const widgetParams = useInjectedWidgetParams() const shouldPayGas = useShouldPayGas() const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() const baseFlowContextSource = useBaseFlowContextSource() - const isInvertedState = useState(false) - const slippageAdjustedSellAmount = trade?.maximumAmountIn(allowedSlippage) const isExactIn = trade?.tradeType === TradeType.EXACT_INPUT @@ -129,11 +126,9 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { <> {receiveAmountInfo && ( >] slippage: Percent - widgetParams: Partial labelsAndTooltips?: LabelsAndTooltips children?: ReactNode recipient?: Nullish @@ -53,11 +51,9 @@ type LabelsAndTooltips = { export function TradeBasicConfirmDetails(props: Props) { const { rateInfoParams, - isInvertedState, slippage, labelsAndTooltips, receiveAmountInfo, - widgetParams, hideLimitPrice, hideUsdValues, withTimelineDot = true, @@ -66,6 +62,8 @@ export function TradeBasicConfirmDetails(props: Props) { recipient, account, } = props + const isInvertedState = useState(false) + const widgetParams = useInjectedWidgetParams() const { amountAfterFees, amountAfterSlippage } = getOrderTypeReceiveAmounts(receiveAmountInfo) const { networkCostsSuffix, networkCostsTooltipSuffix } = labelsAndTooltips || {} diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index a835e2625a..1ba9196575 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -3,6 +3,7 @@ export * from './containers/TradeConfirmModal' export * from './containers/TradeWidgetLinks' export * from './containers/TradeFeesAndCosts' export * from './containers/TradeTotalCostsDetails' +export * from './containers/TradeBasicConfirmDetails' export * from './pure/TradeConfirmation' export * from './hooks/useTradeConfirmActions' export * from './hooks/useTradeTypeInfo' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx index 9ea59d9265..ed7946c3c5 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx @@ -1,10 +1,9 @@ import { useAtomValue } from 'jotai' -import React, { useState } from 'react' +import React from 'react' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders' -import { useInjectedWidgetParams } from 'modules/injectedWidget' import { TradeConfirmation, TradeConfirmModal, useTradeConfirmActions, useTradePriceImpact } from 'modules/trade' import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' @@ -75,10 +74,6 @@ export function TwapConfirmModal() { const tradeConfirmActions = useTradeConfirmActions() const createTwapOrder = useCreateTwapOrder() - const widgetParams = useInjectedWidgetParams() - - const isInvertedState = useState(false) - const isConfirmDisabled = !!localFormValidation const priceImpact = useTradePriceImpact() @@ -124,10 +119,8 @@ export function TwapConfirmModal() { <> {receiveAmountInfo && numOfParts && ( { console.log('TODO doTrade') @@ -49,7 +62,26 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { recipient={recipient} appData={appData || undefined} isPriceStatic={true} - > + > + {(restContent) => ( + <> + {receiveAmountInfo && slippage && ( + + )} + {restContent} + + )} + ) } From 893859f3f6a74cba0e8f8f76e61efb2b646c908b Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 21:43:12 +0500 Subject: [PATCH 06/90] feat(yield): scope context to trade --- .../src/modules/permit/hooks/usePermitInfo.ts | 1 + .../ConfirmSwapModalSetup/index.tsx | 3 +- .../src/modules/swap/hooks/useFlowContext.ts | 15 +- .../modules/swap/hooks/useSwapFlowContext.ts | 9 ++ .../modules/swap/services/swapFlow/index.ts | 14 +- .../src/modules/swap/services/types.ts | 22 ++- .../src/modules/swap/types/flowContext.ts | 3 - .../swap/updaters/BaseFlowContextUpdater.tsx | 4 - .../src/modules/trade/hooks/useTradeState.ts | 2 +- .../yield/hooks/useTradeFlowContext.ts | 153 ++++++++++++++++++ .../yield/hooks/useYieldWidgetActions.ts | 4 +- 11 files changed, 192 insertions(+), 38 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/usePermitInfo.ts b/apps/cowswap-frontend/src/modules/permit/hooks/usePermitInfo.ts index b837474df6..3cceff4ffd 100644 --- a/apps/cowswap-frontend/src/modules/permit/hooks/usePermitInfo.ts +++ b/apps/cowswap-frontend/src/modules/permit/hooks/usePermitInfo.ts @@ -23,6 +23,7 @@ const ORDER_TYPE_SUPPORTS_PERMIT: Record = { [TradeType.SWAP]: true, [TradeType.LIMIT_ORDER]: true, [TradeType.ADVANCED_ORDERS]: false, + [TradeType.YIELD]: true, } const UNSUPPORTED: PermitInfo = { type: 'unsupported', name: 'native' } diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 227dfdef0a..733190b28e 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useMemo } from 'react' import { getMinimumReceivedTooltip } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' @@ -11,7 +11,6 @@ import { PriceImpact } from 'legacy/hooks/usePriceImpact' import { useOrder } from 'legacy/state/orders/hooks' import TradeGp from 'legacy/state/swap/TradeGp' -import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' import { TradeConfirmation, diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts index 9510a7c33d..23ea67454e 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts @@ -53,7 +53,6 @@ export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContex appData, wethContract, inputAmountWithSlippage, - outputAmountWithSlippage, gnosisSafeInfo, recipient, recipientAddressOrName, @@ -61,7 +60,6 @@ export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContex ensRecipientAddress, allowsOffchainSigning, closeModals, - addOrderCallback, uploadAppData, dispatch, flowType, @@ -73,16 +71,7 @@ export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContex typedHooks, } = baseProps - if ( - !chainId || - !account || - !provider || - !trade || - !appData || - !wethContract || - !inputAmountWithSlippage || - !outputAmountWithSlippage - ) { + if (!chainId || !account || !provider || !trade || !appData || !wethContract || !inputAmountWithSlippage) { return null } @@ -143,7 +132,6 @@ export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContex chainId, trade, inputAmountWithSlippage, - outputAmountWithSlippage, flowType, }, flags: { @@ -151,7 +139,6 @@ export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContex }, callbacks: { closeModals, - addOrderCallback, uploadAppData, getCachedPermit, }, diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts index 77bbff00bb..221dc1fff5 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts @@ -45,6 +45,15 @@ export function useSwapFlowContext(): SwapFlowContext | null { return { ...baseContext, + context: { + ...baseContext.context, + inputAmount: baseProps.trade.inputAmount, + outputAmount: baseProps.trade.outputAmount, + }, + callbacks: { + ...baseContext.callbacks, + dispatch: baseProps.dispatch, + }, contract, permitInfo: !enoughAllowance ? permitInfo : undefined, generatePermitHook, diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts b/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts index 405bb7367a..c08f6c5a92 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts @@ -30,9 +30,7 @@ export async function swapFlow( } = input const { - context: { - trade: { inputAmount, outputAmount }, - }, + context: { inputAmount, outputAmount }, typedHooks, } = input const tradeAmounts = { inputAmount, outputAmount } @@ -42,9 +40,9 @@ export async function swapFlow( return false } - const { orderParams, context, permitInfo, generatePermitHook, swapFlowAnalyticsContext, callbacks, dispatch } = input - const { chainId, trade } = context - const inputCurrency = trade.inputAmount.currency + const { orderParams, context, permitInfo, generatePermitHook, swapFlowAnalyticsContext, callbacks } = input + const { chainId } = context + const inputCurrency = inputAmount.currency const cachedPermit = await getCachedPermit(getAddress(inputCurrency)) try { @@ -88,7 +86,7 @@ export async function swapFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) logTradeFlow('SWAP FLOW', 'STEP 5: presign order (optional)') @@ -119,7 +117,7 @@ export async function swapFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) } diff --git a/apps/cowswap-frontend/src/modules/swap/services/types.ts b/apps/cowswap-frontend/src/modules/swap/services/types.ts index e48dd370f5..173fea8d0e 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/types.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/types.ts @@ -6,7 +6,6 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { AppDispatch } from 'legacy/state' import { useTransactionAdder } from 'legacy/state/enhancedTransactions/hooks' -import { AddOrderCallback } from 'legacy/state/orders/hooks' import type { QuoteInformationObject } from 'legacy/state/price/reducer' import TradeGp from 'legacy/state/swap/TradeGp' import { PostOrderParams } from 'legacy/utils/trade' @@ -24,7 +23,6 @@ export interface BaseFlowContext { chainId: number trade: TradeGp inputAmountWithSlippage: CurrencyAmount - outputAmountWithSlippage: CurrencyAmount flowType: FlowType } flags: { @@ -32,7 +30,6 @@ export interface BaseFlowContext { } callbacks: { closeModals: Command - addOrderCallback: AddOrderCallback uploadAppData: (params: UploadAppDataParams) => void getCachedPermit: ReturnType } @@ -46,7 +43,24 @@ export interface BaseFlowContext { typedHooks?: TypedAppDataHooks } -export type SwapFlowContext = BaseFlowContext & { +export type SwapFlowContext = { + context: { + chainId: number + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + inputAmountWithSlippage: CurrencyAmount + } + flags: { + allowsOffchainSigning: boolean + } + callbacks: { + closeModals: Command + getCachedPermit: ReturnType + dispatch: AppDispatch + } + tradeConfirmActions: TradeConfirmActions + swapFlowAnalyticsContext: TradeFlowAnalyticsContext + orderParams: PostOrderParams contract: GPv2Settlement permitInfo: IsTokenPermittableResult generatePermitHook: GeneratePermitHook diff --git a/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts b/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts index 79b9fb126d..2051f2ed3f 100644 --- a/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts @@ -6,7 +6,6 @@ import type { Web3Provider } from '@ethersproject/providers' import type { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import type { AppDispatch } from 'legacy/state' -import type { AddOrderCallback } from 'legacy/state/orders/hooks' import type { QuoteInformationObject } from 'legacy/state/price/reducer' import type TradeGp from 'legacy/state/swap/TradeGp' @@ -31,7 +30,6 @@ export interface BaseFlowContextSource { appData: AppDataInfo | null wethContract: Weth | null inputAmountWithSlippage: CurrencyAmount | undefined - outputAmountWithSlippage: CurrencyAmount | undefined gnosisSafeInfo: GnosisSafeInfo | undefined recipient: string | null recipientAddressOrName: string | null @@ -41,7 +39,6 @@ export interface BaseFlowContextSource { flowType: FlowType closeModals: Command uploadAppData: (update: UploadAppDataParams) => void - addOrderCallback: AddOrderCallback dispatch: AppDispatch allowedSlippage: Percent tradeConfirmActions: TradeConfirmActions diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx index 12857e8c11..826379ae67 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx @@ -10,7 +10,6 @@ import { useDispatch } from 'react-redux' import { AppDispatch } from 'legacy/state' import { useCloseModals } from 'legacy/state/application/hooks' -import { useAddPendingOrder } from 'legacy/state/orders/hooks' import { useGetQuoteAndStatus } from 'legacy/state/price/hooks' import { useUserTransactionTTL } from 'legacy/state/user/hooks' @@ -48,7 +47,6 @@ export function BaseFlowContextUpdater() { const typedHooks = useAppDataHooks() const closeModals = useCloseModals() const uploadAppData = useUploadAppData() - const addOrderCallback = useAddPendingOrder() const dispatch = useDispatch() const tradeConfirmActions = useTradeConfirmActions() @@ -86,7 +84,6 @@ export function BaseFlowContextUpdater() { uploadAppData, flowType, closeModals, - addOrderCallback, dispatch, allowedSlippage: slippage, tradeConfirmActions, @@ -113,7 +110,6 @@ export function BaseFlowContextUpdater() { uploadAppData, flowType, closeModals, - addOrderCallback, dispatch, slippage, tradeConfirmActions, diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts index bcdc6afe69..a4abaf3502 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts @@ -7,11 +7,11 @@ import { import { useLimitOrdersRawState, useUpdateLimitOrdersRawState } from 'modules/limitOrders/hooks/useLimitOrdersRawState' import { useSwapRawState, useUpdateSwapRawState } from 'modules/swap/hooks/useSwapRawState' import { ExtendedTradeRawState, TradeRawState } from 'modules/trade/types/TradeRawState' +import { useUpdateYieldRawState, useYieldRawState } from 'modules/yield' import { useTradeTypeInfoFromUrl } from './useTradeTypeInfoFromUrl' import { TradeType } from '../types' -import { useUpdateYieldRawState, useYieldRawState } from 'modules/yield' const EMPTY_TRADE_STATE = {} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts new file mode 100644 index 0000000000..ec0b4872e4 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts @@ -0,0 +1,153 @@ +import { useMemo } from 'react' + +import { TokenWithLogo } from '@cowprotocol/common-const' +import { MAX_VALID_TO_EPOCH } from '@cowprotocol/common-utils' +import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' +import { UiOrderType } from '@cowprotocol/types' +import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' +import { useWalletProvider } from '@cowprotocol/wallet-provider' + +import { useDispatch } from 'react-redux' + +import { AppDispatch } from 'legacy/state' +import { useCloseModals } from 'legacy/state/application/hooks' + +import { useAppData, useAppDataHooks } from 'modules/appData' +import { useGeneratePermitHook, useGetCachedPermit, usePermitInfo } from 'modules/permit' +import type { SwapFlowContext } from 'modules/swap/services/types' +import { useEnoughBalanceAndAllowance } from 'modules/tokens' +import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmActions } from 'modules/trade' + +import { useTradeQuote } from 'modules/tradeQuote' +import { useGP2SettlementContract } from 'common/hooks/useContract' + +export function useTradeFlowContext(): SwapFlowContext | null { + const { chainId, account } = useWalletInfo() + const provider = useWalletProvider() + const { allowsOffchainSigning } = useWalletDetails() + const isSafeWallet = useIsSafeWallet() + const derivedTradeState = useDerivedTradeState() + const receiveAmountInfo = useReceiveAmountInfo() + const sellCurrency = derivedTradeState?.inputCurrency + const sellAmountBeforeFee = receiveAmountInfo?.beforeNetworkCosts.sellAmount + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency + const permitInfo = usePermitInfo(sellCurrency, TradeType.YIELD) + const generatePermitHook = useGeneratePermitHook() + const getCachedPermit = useGetCachedPermit() + const closeModals = useCloseModals() + const dispatch = useDispatch() + const tradeConfirmActions = useTradeConfirmActions() + const settlementContract = useGP2SettlementContract() + const appData = useAppData() + const typedHooks = useAppDataHooks() + const tradeQuote = useTradeQuote() + + const checkAllowanceAddress = COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId || SupportedChainId.MAINNET] + const { enoughAllowance } = useEnoughBalanceAndAllowance({ + account, + amount: inputAmountWithSlippage, + checkAllowanceAddress, + }) + + const quoteId = tradeQuote.response?.id + + const { + inputCurrency: sellToken, + outputCurrency: buyToken, + inputCurrencyAmount: inputAmount, + outputCurrencyAmount: outputAmount, + recipient, + recipientAddress, + } = derivedTradeState || {} + + return useMemo(() => { + if ( + !inputAmount || + !outputAmount || + !inputAmountWithSlippage || + !sellAmountBeforeFee || + !networkFee || + !sellToken || + !buyToken || + !account || + !provider || + !appData || + !settlementContract + ) + return null + + return { + context: { + chainId, + inputAmount, + outputAmount, + inputAmountWithSlippage, + }, + flags: { + allowsOffchainSigning, + }, + callbacks: { + closeModals, + getCachedPermit, + dispatch, + }, + tradeConfirmActions, + swapFlowAnalyticsContext: { + account, + recipient, + recipientAddress, + marketLabel: [inputAmount?.currency.symbol, outputAmount?.currency.symbol].join(','), + orderType: UiOrderType.YIELD, + }, + contract: settlementContract, + permitInfo: !enoughAllowance ? permitInfo : undefined, + generatePermitHook, + typedHooks, + orderParams: { + account, + chainId, + signer: provider.getSigner(), + kind: OrderKind.SELL, + inputAmount, + outputAmount, + sellAmountBeforeFee, + feeAmount: networkFee, + sellToken: sellToken as TokenWithLogo, + buyToken: buyToken as TokenWithLogo, + validTo: MAX_VALID_TO_EPOCH, // TODO: bind to settings + recipient: recipient || account, + recipientAddressOrName: recipient || null, + allowsOffchainSigning, + appData, + class: OrderClass.MARKET, + partiallyFillable: false, // TODO: bind to settings + quoteId, + isSafeWallet, + }, + } + }, [ + account, + allowsOffchainSigning, + appData, + buyToken, + chainId, + closeModals, + dispatch, + enoughAllowance, + generatePermitHook, + inputAmount, + inputAmountWithSlippage, + networkFee, + outputAmount, + permitInfo, + provider, + quoteId, + recipient, + sellAmountBeforeFee, + sellToken, + settlementContract, + tradeConfirmActions, + typedHooks, + ]) +} diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts index 6c6a7503aa..88b044dc26 100644 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldWidgetActions.ts @@ -1,15 +1,15 @@ import { useCallback, useMemo } from 'react' -import { FractionUtils, isSellOrder, tryParseCurrencyAmount } from '@cowprotocol/common-utils' +import { isSellOrder, tryParseCurrencyAmount } from '@cowprotocol/common-utils' import { OrderKind } from '@cowprotocol/cow-sdk' import { Field } from 'legacy/state/types' import { TradeWidgetActions, useIsWrapOrUnwrap, useOnCurrencySelection, useSwitchTokensPlaces } from 'modules/trade' +import { useUpdateCurrencyAmount } from './useUpdateCurrencyAmount' import { useUpdateYieldRawState } from './useUpdateYieldRawState' import { useYieldDerivedState } from './useYieldDerivedState' -import { useUpdateCurrencyAmount } from './useUpdateCurrencyAmount' export function useYieldWidgetActions(): TradeWidgetActions { const { inputCurrency, outputCurrency, orderKind } = useYieldDerivedState() From 89aeca477f8ce15f76abbf151255dfc1549f63fa Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 21:53:34 +0500 Subject: [PATCH 07/90] feat(yield): do trade after confirmation --- .../containers/YieldConfirmModal/index.tsx | 41 +++++++++++++++---- .../yield/containers/YieldWidget/index.tsx | 19 +++++---- .../yield/hooks/useTradeFlowContext.ts | 38 +++++++++-------- .../src/modules/yield/updaters/index.tsx | 5 +++ 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index 4f2e6a79f1..c0bd77d282 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -1,26 +1,33 @@ -import React from 'react' +import React, { useCallback, useMemo } from 'react' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import type { PriceImpact } from 'legacy/hooks/usePriceImpact' +import { Field } from 'legacy/state/types' import { useAppData } from 'modules/appData' +import { swapFlow } from 'modules/swap/services/swapFlow' +import type { SwapFlowContext } from 'modules/swap/services/types' import { TradeConfirmation, TradeConfirmModal, useReceiveAmountInfo, useTradeConfirmActions, TradeBasicConfirmDetails, + useTradePriceImpact, } from 'modules/trade' +import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' +import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' const CONFIRM_TITLE = 'Confirm order' export interface YieldConfirmModalProps { + tradeFlowContext: SwapFlowContext inputCurrencyInfo: CurrencyPreviewInfo outputCurrencyInfo: CurrencyPreviewInfo priceImpact: PriceImpact @@ -28,23 +35,43 @@ export interface YieldConfirmModalProps { } export function YieldConfirmModal(props: YieldConfirmModalProps) { - const { inputCurrencyInfo, outputCurrencyInfo, priceImpact, recipient } = props + const { inputCurrencyInfo, outputCurrencyInfo, priceImpact, recipient, tradeFlowContext: tradeContextInitial } = props + + /** + * This is a very important part of the code. + * After the confirmation modal opens, the trade context should not be recreated. + * In order to prevent this, we use useMemo to keep the trade context the same when the modal was opened. + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + const tradeFlowContext = useMemo(() => tradeContextInitial, []) const { account } = useWalletInfo() const { ensName } = useWalletDetails() + const { onChangeRecipient, onUserInput } = useYieldWidgetActions() const appData = useAppData() const receiveAmountInfo = useReceiveAmountInfo() const tradeConfirmActions = useTradeConfirmActions() const { slippage } = useYieldDerivedState() + const priceImpactParams = useTradePriceImpact() + const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) - const doTrade = () => { - console.log('TODO doTrade') - } + const doTrade = useCallback(async () => { + if (!tradeFlowContext) return + + const tradeResult = await swapFlow(tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) + + const isPriceImpactDeclined = tradeResult === false + + // Clean up form fields after successful swap + if (!isPriceImpactDeclined) { + onChangeRecipient(null) + onUserInput(Field.INPUT, '') + } + }, [tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee]) - // TODO - const isConfirmDisabled = false + const isConfirmDisabled = false // TODO: add conditions if needed return ( diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index eb3e50e219..4d744eb07c 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -8,6 +8,7 @@ import { useTradeQuote } from 'modules/tradeQuote' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' +import { useTradeFlowContext } from '../../hooks/useTradeFlowContext' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' import { yieldSettingsAtom } from '../../state/yieldSettingsAtom' @@ -31,6 +32,7 @@ export function YieldWidget() { outputCurrencyFiatAmount, recipient, } = useYieldDerivedState() + const tradeFlowContext = useTradeFlowContext() const { showRecipient } = settingsState @@ -70,7 +72,7 @@ export function YieldWidget() { const slots = { settingsWidget: , - bottomContent: , + bottomContent: , } const params = { @@ -91,12 +93,15 @@ export function YieldWidget() { inputCurrencyInfo={inputCurrencyInfo} outputCurrencyInfo={outputCurrencyInfo} confirmModal={ - + tradeFlowContext ? ( + + ) : null } /> ) diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts index ec0b4872e4..fb0f5d250e 100644 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react' -import { TokenWithLogo } from '@cowprotocol/common-const' -import { MAX_VALID_TO_EPOCH } from '@cowprotocol/common-utils' +import { DEFAULT_DEADLINE_FROM_NOW, TokenWithLogo } from '@cowprotocol/common-const' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' import { UiOrderType } from '@cowprotocol/types' import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' @@ -17,8 +16,8 @@ import { useGeneratePermitHook, useGetCachedPermit, usePermitInfo } from 'module import type { SwapFlowContext } from 'modules/swap/services/types' import { useEnoughBalanceAndAllowance } from 'modules/tokens' import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmActions } from 'modules/trade' +import { getOrderValidTo, useTradeQuote } from 'modules/tradeQuote' -import { useTradeQuote } from 'modules/tradeQuote' import { useGP2SettlementContract } from 'common/hooks/useContract' export function useTradeFlowContext(): SwapFlowContext | null { @@ -28,10 +27,14 @@ export function useTradeFlowContext(): SwapFlowContext | null { const isSafeWallet = useIsSafeWallet() const derivedTradeState = useDerivedTradeState() const receiveAmountInfo = useReceiveAmountInfo() + const sellCurrency = derivedTradeState?.inputCurrency - const sellAmountBeforeFee = receiveAmountInfo?.beforeNetworkCosts.sellAmount + const inputAmount = receiveAmountInfo?.afterNetworkCosts.sellAmount + const outputAmount = receiveAmountInfo?.afterSlippage.buyAmount + const sellAmountBeforeFee = receiveAmountInfo?.afterNetworkCosts.sellAmount const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency + const permitInfo = usePermitInfo(sellCurrency, TradeType.YIELD) const generatePermitHook = useGeneratePermitHook() const getCachedPermit = useGetCachedPermit() @@ -50,16 +53,7 @@ export function useTradeFlowContext(): SwapFlowContext | null { checkAllowanceAddress, }) - const quoteId = tradeQuote.response?.id - - const { - inputCurrency: sellToken, - outputCurrency: buyToken, - inputCurrencyAmount: inputAmount, - outputCurrencyAmount: outputAmount, - recipient, - recipientAddress, - } = derivedTradeState || {} + const { inputCurrency: sellToken, outputCurrency: buyToken, recipient, recipientAddress } = derivedTradeState || {} return useMemo(() => { if ( @@ -73,9 +67,12 @@ export function useTradeFlowContext(): SwapFlowContext | null { !account || !provider || !appData || + !tradeQuote?.quoteParams || + !tradeQuote?.response || !settlementContract - ) + ) { return null + } return { context: { @@ -115,14 +112,19 @@ export function useTradeFlowContext(): SwapFlowContext | null { feeAmount: networkFee, sellToken: sellToken as TokenWithLogo, buyToken: buyToken as TokenWithLogo, - validTo: MAX_VALID_TO_EPOCH, // TODO: bind to settings + validTo: getOrderValidTo(DEFAULT_DEADLINE_FROM_NOW, { + // TODO: bind to settings + validFor: tradeQuote.quoteParams.validFor, + quoteValidTo: tradeQuote.response.quote.validTo, + localQuoteTimestamp: tradeQuote.localQuoteTimestamp, + }), recipient: recipient || account, recipientAddressOrName: recipient || null, allowsOffchainSigning, appData, class: OrderClass.MARKET, partiallyFillable: false, // TODO: bind to settings - quoteId, + quoteId: tradeQuote.response.id, isSafeWallet, }, } @@ -142,7 +144,7 @@ export function useTradeFlowContext(): SwapFlowContext | null { outputAmount, permitInfo, provider, - quoteId, + tradeQuote, recipient, sellAmountBeforeFee, sellToken, diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx index eddbf07bea..e46923e292 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/index.tsx @@ -1,3 +1,7 @@ +import { INITIAL_ALLOWED_SLIPPAGE_PERCENT } from '@cowprotocol/common-const' +import { percentToBps } from '@cowprotocol/common-utils' + +import { AppDataUpdater } from 'modules/appData' import { useSetTradeQuoteParams } from 'modules/tradeQuote' import { QuoteObserverUpdater } from './QuoteObserverUpdater' @@ -13,6 +17,7 @@ export function YieldUpdaters() { return ( <> + ) } From ca61cbb81f4030a773e305aa515f3d8a8d5f3c16 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 7 Oct 2024 22:34:35 +0500 Subject: [PATCH 08/90] feat(yield): display order progress bar --- .../TransactionSubmittedContent/index.tsx | 7 ++-- .../ConfirmSwapModalSetup/index.tsx | 34 ++---------------- .../containers/SurplusModalSetup/index.tsx | 3 +- .../hooks/useNavigateToNewOrderCallback.ts | 8 ++--- .../trade/hooks/useOrderSubmittedContent.tsx | 35 +++++++++++++++++++ .../src/modules/trade/index.ts | 2 ++ .../containers/YieldConfirmModal/index.tsx | 19 +++------- 7 files changed, 54 insertions(+), 54 deletions(-) rename apps/cowswap-frontend/src/modules/{swap => trade}/hooks/useNavigateToNewOrderCallback.ts (83%) create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useOrderSubmittedContent.tsx diff --git a/apps/cowswap-frontend/src/common/pure/TransactionSubmittedContent/index.tsx b/apps/cowswap-frontend/src/common/pure/TransactionSubmittedContent/index.tsx index f8ca1c4d75..d1ab7fe366 100644 --- a/apps/cowswap-frontend/src/common/pure/TransactionSubmittedContent/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/TransactionSubmittedContent/index.tsx @@ -1,4 +1,5 @@ -import { SupportedChainId as ChainId } from '@cowprotocol/cow-sdk' +import { SupportedChainId, SupportedChainId as ChainId } from '@cowprotocol/cow-sdk' +import { Command } from '@cowprotocol/types' import { BackButton } from '@cowprotocol/ui' import { Currency } from '@uniswap/sdk-core' @@ -6,12 +7,12 @@ import { Nullish } from 'types' import { DisplayLink } from 'legacy/components/TransactionConfirmationModal/DisplayLink' import { ActivityStatus } from 'legacy/hooks/useRecentActivity' +import type { Order } from 'legacy/state/orders/actions' import { ActivityDerivedState } from 'modules/account/containers/Transaction' import { GnosisSafeTxDetails } from 'modules/account/containers/Transaction/ActivityDetails' import { Category, cowAnalytics } from 'modules/analytics' import { EthFlowStepper } from 'modules/swap/containers/EthFlowStepper' -import { NavigateToNewOrderCallback } from 'modules/swap/hooks/useNavigateToNewOrderCallback' import { WatchAssetInWallet } from 'modules/wallet/containers/WatchAssetInWallet' import * as styledEl from './styled' @@ -46,7 +47,7 @@ export interface TransactionSubmittedContentProps { activityDerivedState: ActivityDerivedState | null currencyToAdd?: Nullish orderProgressBarV2Props: OrderProgressBarV2Props - navigateToNewOrderCallback?: NavigateToNewOrderCallback + navigateToNewOrderCallback?: (chainId: SupportedChainId, order?: Order, callback?: Command) => () => void } export function TransactionSubmittedContent({ diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 733190b28e..104bf22dd1 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -1,37 +1,32 @@ -import { useCallback, useMemo } from 'react' +import { useMemo } from 'react' import { getMinimumReceivedTooltip } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { Command } from '@cowprotocol/types' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { Percent, TradeType } from '@uniswap/sdk-core' import { HighFeeWarning } from 'legacy/components/SwapWarnings' import { PriceImpact } from 'legacy/hooks/usePriceImpact' -import { useOrder } from 'legacy/state/orders/hooks' import TradeGp from 'legacy/state/swap/TradeGp' import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' import { TradeConfirmation, TradeConfirmModal, + useOrderSubmittedContent, useReceiveAmountInfo, useTradeConfirmActions, - useTradeConfirmState, } from 'modules/trade' import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' -import { useOrderProgressBarV2Props } from 'common/hooks/orderProgressBarV2' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' import { NetworkCostsSuffix } from 'common/pure/NetworkCostsSuffix' import { RateInfoParams } from 'common/pure/RateInfo' -import { TransactionSubmittedContent } from 'common/pure/TransactionSubmittedContent' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useBaseFlowContextSource } from '../../hooks/useFlowContext' import { useIsEoaEthFlow } from '../../hooks/useIsEoaEthFlow' -import { useNavigateToNewOrderCallback } from '../../hooks/useNavigateToNewOrderCallback' import { useShouldPayGas } from '../../hooks/useShouldPayGas' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' @@ -102,7 +97,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { [chainId, allowedSlippage, nativeCurrency.symbol, isEoaEthFlow, isExactIn, shouldPayGas], ) - const submittedContent = useSubmittedContent(chainId) + const submittedContent = useOrderSubmittedContent(chainId) return ( @@ -148,26 +143,3 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { ) } - -function useSubmittedContent(chainId: SupportedChainId) { - const { transactionHash } = useTradeConfirmState() - const order = useOrder({ chainId, id: transactionHash || undefined }) - - const orderProgressBarV2Props = useOrderProgressBarV2Props(chainId, order) - - const navigateToNewOrderCallback = useNavigateToNewOrderCallback() - - return useCallback( - (onDismiss: Command) => ( - - ), - [chainId, transactionHash, orderProgressBarV2Props, navigateToNewOrderCallback], - ) -} diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SurplusModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SurplusModalSetup/index.tsx index ec16c85a42..d53df031ed 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SurplusModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SurplusModalSetup/index.tsx @@ -4,13 +4,12 @@ import { useWalletInfo } from '@cowprotocol/wallet' import { useOrder } from 'legacy/state/orders/hooks' -import { useNavigateToNewOrderCallback } from 'modules/swap/hooks/useNavigateToNewOrderCallback' +import { useTradeConfirmState, useNavigateToNewOrderCallback } from 'modules/trade' import { useOrderProgressBarV2Props } from 'common/hooks/orderProgressBarV2' import { CowModal } from 'common/pure/Modal' import { TransactionSubmittedContent } from 'common/pure/TransactionSubmittedContent' -import { useTradeConfirmState } from '../../../trade' import { useOrderIdForSurplusModal, useRemoveOrderFromSurplusQueue } from '../../state/surplusModal' // TODO: rename? diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useNavigateToNewOrderCallback.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateToNewOrderCallback.ts similarity index 83% rename from apps/cowswap-frontend/src/modules/swap/hooks/useNavigateToNewOrderCallback.ts rename to apps/cowswap-frontend/src/modules/trade/hooks/useNavigateToNewOrderCallback.ts index 43ee8fe36c..3e35a29f40 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useNavigateToNewOrderCallback.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateToNewOrderCallback.ts @@ -9,10 +9,10 @@ import { Order } from 'legacy/state/orders/actions' import { Routes } from 'common/constants/routes' import { useNavigate } from 'common/hooks/useNavigate' -import { parameterizeTradeRoute } from '../../trade' -import { TradeUrlParams } from '../../trade/types/TradeRawState' +import { TradeUrlParams } from '../types/TradeRawState' +import { parameterizeTradeRoute } from '../utils/parameterizeTradeRoute' -export type NavigateToNewOrderCallback = (chainId: SupportedChainId, order?: Order, callback?: Command) => () => void +type NavigateToNewOrderCallback = (chainId: SupportedChainId, order?: Order, callback?: Command) => () => void export function useNavigateToNewOrderCallback(): NavigateToNewOrderCallback { const navigate = useNavigate() @@ -41,6 +41,6 @@ export function useNavigateToNewOrderCallback(): NavigateToNewOrderCallback { callback?.() } }, - [navigate] + [navigate], ) } diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useOrderSubmittedContent.tsx b/apps/cowswap-frontend/src/modules/trade/hooks/useOrderSubmittedContent.tsx new file mode 100644 index 0000000000..bf7c5b6d15 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useOrderSubmittedContent.tsx @@ -0,0 +1,35 @@ +import { useCallback } from 'react' + +import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { Command } from '@cowprotocol/types' + +import { useOrder } from 'legacy/state/orders/hooks' + +import { useOrderProgressBarV2Props } from 'common/hooks/orderProgressBarV2' +import { TransactionSubmittedContent } from 'common/pure/TransactionSubmittedContent' + +import { useNavigateToNewOrderCallback } from './useNavigateToNewOrderCallback' +import { useTradeConfirmState } from './useTradeConfirmState' + +export function useOrderSubmittedContent(chainId: SupportedChainId) { + const { transactionHash } = useTradeConfirmState() + const order = useOrder({ chainId, id: transactionHash || undefined }) + + const orderProgressBarV2Props = useOrderProgressBarV2Props(chainId, order) + + const navigateToNewOrderCallback = useNavigateToNewOrderCallback() + + return useCallback( + (onDismiss: Command) => ( + + ), + [chainId, transactionHash, orderProgressBarV2Props, navigateToNewOrderCallback], + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 1ba9196575..841092f24d 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -29,6 +29,8 @@ export * from './hooks/useIsSellNative' export * from './hooks/useBuildTradeDerivedState' export * from './hooks/useOnCurrencySelection' export * from './hooks/useDerivedTradeState' +export * from './hooks/useNavigateToNewOrderCallback' +export * from './hooks/useOrderSubmittedContent' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index c0bd77d282..c10390ee4a 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -3,7 +3,6 @@ import React, { useCallback, useMemo } from 'react' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import type { PriceImpact } from 'legacy/hooks/usePriceImpact' -import { Field } from 'legacy/state/types' import { useAppData } from 'modules/appData' import { swapFlow } from 'modules/swap/services/swapFlow' @@ -15,6 +14,7 @@ import { useTradeConfirmActions, TradeBasicConfirmDetails, useTradePriceImpact, + useOrderSubmittedContent, } from 'modules/trade' import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' @@ -22,7 +22,6 @@ import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' -import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' const CONFIRM_TITLE = 'Confirm order' @@ -45,9 +44,8 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { // eslint-disable-next-line react-hooks/exhaustive-deps const tradeFlowContext = useMemo(() => tradeContextInitial, []) - const { account } = useWalletInfo() + const { account, chainId } = useWalletInfo() const { ensName } = useWalletDetails() - const { onChangeRecipient, onUserInput } = useYieldWidgetActions() const appData = useAppData() const receiveAmountInfo = useReceiveAmountInfo() const tradeConfirmActions = useTradeConfirmActions() @@ -56,25 +54,18 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) + const submittedContent = useOrderSubmittedContent(chainId) const doTrade = useCallback(async () => { if (!tradeFlowContext) return - const tradeResult = await swapFlow(tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - - const isPriceImpactDeclined = tradeResult === false - - // Clean up form fields after successful swap - if (!isPriceImpactDeclined) { - onChangeRecipient(null) - onUserInput(Field.INPUT, '') - } + swapFlow(tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) }, [tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee]) const isConfirmDisabled = false // TODO: add conditions if needed return ( - + Date: Tue, 8 Oct 2024 16:06:56 +0500 Subject: [PATCH 09/90] refactor: move useIsEoaEthFlow to trade module --- .../src/common/updaters/FeesUpdater.ts | 4 +- .../components/TransactionSettings/index.tsx | 181 +++--------------- .../components/TransactionSettings/styled.tsx | 131 +++++++++++++ .../legacy/hooks/useRefetchPriceCallback.tsx | 8 +- .../ConfirmSwapModalSetup/index.tsx | 3 +- .../swap/containers/Row/RowDeadline/index.tsx | 2 +- .../swap/containers/Row/RowSlippage/index.tsx | 18 +- .../swap/containers/SwapWidget/index.tsx | 2 +- .../src/modules/swap/hooks/useShouldPayGas.ts | 2 +- .../swap/pure/NetworkCostsTooltipSuffix.tsx | 4 +- .../swap/pure/ReceiveAmountInfo/index.tsx | 2 +- .../updaters/EthFlowDeadlineUpdater.tsx | 2 +- .../swap/state/slippageValueAndTypeAtom.ts | 2 +- .../swap/updaters/BaseFlowContextUpdater.tsx | 3 +- .../{swap => trade}/hooks/useIsEoaEthFlow.ts | 0 .../src/modules/trade/index.ts | 1 + .../{swap => trade}/state/isEoaEthFlowAtom.ts | 0 17 files changed, 191 insertions(+), 174 deletions(-) create mode 100644 apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx rename apps/cowswap-frontend/src/modules/{swap => trade}/hooks/useIsEoaEthFlow.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => trade}/state/isEoaEthFlowAtom.ts (100%) diff --git a/apps/cowswap-frontend/src/common/updaters/FeesUpdater.ts b/apps/cowswap-frontend/src/common/updaters/FeesUpdater.ts index e5e0fcce0b..61bf095e9e 100644 --- a/apps/cowswap-frontend/src/common/updaters/FeesUpdater.ts +++ b/apps/cowswap-frontend/src/common/updaters/FeesUpdater.ts @@ -19,8 +19,8 @@ import { Field } from 'legacy/state/types' import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { useAppData } from 'modules/appData' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' import { useDerivedSwapInfo, useSwapState } from 'modules/swap/hooks/useSwapState' +import { useIsEoaEthFlow } from 'modules/trade' export const TYPED_VALUE_DEBOUNCE_TIME = 350 export const SWAP_QUOTE_CHECK_INTERVAL = ms`30s` // Every 30s @@ -81,7 +81,7 @@ function quoteUsingSameParameters(currentParams: FeeQuoteParams, quoteInfo: Quot function isRefetchQuoteRequired( isLoading: boolean, currentParams: FeeQuoteParams, - quoteInformation?: QuoteInformationObject + quoteInformation?: QuoteInformationObject, ): boolean { // If there's no quote/fee information, we always re-fetch if (!quoteInformation) { diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx index ab1060e8df..71e1842c9d 100644 --- a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx @@ -14,29 +14,30 @@ import { } from '@cowprotocol/common-const' import { useOnClickOutside } from '@cowprotocol/common-hooks' import { getWrappedToken, percentToBps } from '@cowprotocol/common-utils' -import { FancyButton, HelpTooltip, Media, RowBetween, RowFixed, UI } from '@cowprotocol/ui' +import { HelpTooltip, RowBetween, RowFixed, UI } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' import { Percent } from '@uniswap/sdk-core' import { Trans } from '@lingui/macro' -import { darken } from 'color2k' -import styled, { ThemeContext } from 'styled-components/macro' +import { ThemeContext } from 'styled-components/macro' import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { orderExpirationTimeAnalytics, slippageToleranceAnalytics } from 'modules/analytics' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' import { useIsSlippageModified } from 'modules/swap/hooks/useIsSlippageModified' import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' import { useSetSlippage } from 'modules/swap/hooks/useSetSlippage' import { useDefaultSwapSlippage, useSmartSwapSlippage, useSwapSlippage } from 'modules/swap/hooks/useSwapSlippage' import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'modules/swap/pure/Row/RowDeadline' import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'modules/swap/pure/Row/RowSlippageContent' +import { useIsEoaEthFlow } from 'modules/trade' import useNativeCurrency from 'lib/hooks/useNativeCurrency' +import * as styledEl from './styled' + const MAX_DEADLINE_MINUTES = 180 // 3h enum SlippageError { @@ -47,133 +48,6 @@ enum DeadlineError { InvalidInput = 'InvalidInput', } -const Option = styled(FancyButton) <{ active: boolean }>` - margin-right: 8px; - - :hover { - cursor: pointer; - } - - &:disabled { - border: none; - pointer-events: none; - } -` - -export const Input = styled.input` - background: var(${UI.COLOR_PAPER}); - font-size: 16px; - width: auto; - outline: none; - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - } - - color: ${({ theme, color }) => (color === 'red' ? theme.error : `var(${UI.COLOR_TEXT})`)}; - text-align: right; -` - -export const OptionCustom = styled(FancyButton) <{ active?: boolean; warning?: boolean }>` - height: 2rem; - position: relative; - padding: 0 0.75rem; - flex: 1; - border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.error : theme.bg2}`}; - - :hover { - border: ${({ theme, active, warning }) => - active && `1px solid ${warning ? darken(theme.error, 0.1) : darken(theme.bg2, 0.1)}`}; - } - - input { - width: 100%; - height: 100%; - border: 0; - border-radius: 2rem; - } -` - -const SlippageEmojiContainer = styled.span` - color: #f3841e; - ${Media.upToSmall()} { - display: none; - } -` - -const SmartSlippageInfo = styled.div` - color: var(${UI.COLOR_GREEN}); - font-size: 13px; - text-align: right; - width: 100%; - padding-right: 0.2rem; - display: flex; - justify-content: flex-end; - padding-bottom: 0.35rem; - - > span { - margin-left: 4px; - } -` - -const Wrapper = styled.div` - ${RowBetween} > button, ${OptionCustom} { - &:disabled { - color: var(${UI.COLOR_TEXT_OPACITY_50}); - background-color: var(${UI.COLOR_PAPER}); - border: none; - pointer-events: none; - } - } - - ${OptionCustom} { - background-color: var(${UI.COLOR_PAPER_DARKER}); - border: 0; - color: inherit; - - > div > input { - background: transparent; - color: inherit; - - &:disabled { - color: inherit; - background-color: inherit; - } - } - - > div > input::placeholder { - opacity: 0.5; - color: inherit; - } - } - - ${RowFixed} { - color: inherit; - - > div { - color: inherit; - opacity: 0.85; - } - - > button { - background-color: var(${UI.COLOR_PAPER_DARKER}); - border: 0; - } - - > button > input { - background: transparent; - color: inherit; - } - - > button > input::placeholder { - background: transparent; - opacity: 0.5; - color: inherit; - } - } -` - export function TransactionSettings() { const { chainId } = useWalletInfo() const theme = useContext(ThemeContext) @@ -242,7 +116,7 @@ export function TransactionSettings() { const tooLow = swapSlippage.lessThan(new Percent(isEoaEthFlow ? minEthFlowSlippageBps : LOW_SLIPPAGE_BPS, 10_000)) const tooHigh = swapSlippage.greaterThan( - new Percent(isEoaEthFlow ? HIGH_ETH_FLOW_SLIPPAGE_BPS : HIGH_SLIPPAGE_BPS, 10_000) + new Percent(isEoaEthFlow ? HIGH_ETH_FLOW_SLIPPAGE_BPS : HIGH_SLIPPAGE_BPS, 10_000), ) function parseCustomDeadline(value: string) { @@ -259,10 +133,10 @@ export function TransactionSettings() { if ( !Number.isInteger(parsed) || // Check deadline is a number parsed < - (isEoaEthFlow - ? // 10 minute low threshold for eth flow - MINIMUM_ETH_FLOW_DEADLINE_SECONDS - : MINIMUM_ORDER_VALID_TO_TIME_SECONDS) || // Check deadline is not too small + (isEoaEthFlow + ? // 10 minute low threshold for eth flow + MINIMUM_ETH_FLOW_DEADLINE_SECONDS + : MINIMUM_ORDER_VALID_TO_TIME_SECONDS) || // Check deadline is not too small parsed > MAX_DEADLINE_MINUTES * 60 // Check deadline is not too big ) { setDeadlineError(DeadlineError.InvalidInput) @@ -294,7 +168,7 @@ export function TransactionSettings() { useOnClickOutside([wrapperRef], onSlippageInputBlur) return ( - + @@ -311,24 +185,24 @@ export function TransactionSettings() { /> - - + + {!isSmartSlippageApplied && !chosenSlippageMatchesSmartSlippage && (tooLow || tooHigh) ? ( - + ⚠️ - + ) : null} - 0 ? slippageInput : !isSlippageModified ? '' : swapSlippage.toFixed(2)} onChange={(e) => parseSlippageInput(e.target.value)} @@ -337,15 +211,14 @@ export function TransactionSettings() { /> % - + {!isSmartSlippageApplied && !chosenSlippageMatchesSmartSlippage && (slippageError || tooLow || tooHigh) ? ( {slippageError ? ( @@ -362,17 +235,17 @@ export function TransactionSettings() { ) : null} {isSmartSlippageApplied && ( - + - Based on recent volatility observed for this token pair, it's recommended to leave the default - to account for price changes. + Based on recent volatility observed for this token pair, it's recommended to leave the default to + account for price changes. } /> Dynamic - + )} @@ -394,8 +267,8 @@ export function TransactionSettings() { /> - - + 0 @@ -411,7 +284,7 @@ export function TransactionSettings() { }} color={deadlineError ? 'red' : ''} /> - + minutes @@ -419,6 +292,6 @@ export function TransactionSettings() { )} - + ) } diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx new file mode 100644 index 0000000000..d3ef4a7381 --- /dev/null +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx @@ -0,0 +1,131 @@ +import { FancyButton, Media, RowBetween, RowFixed, UI } from '@cowprotocol/ui' + +import { darken } from 'color2k' +import styled from 'styled-components/macro' + +export const Option = styled(FancyButton)<{ active: boolean }>` + margin-right: 8px; + + :hover { + cursor: pointer; + } + + &:disabled { + border: none; + pointer-events: none; + } +` + +export const Input = styled.input` + background: var(${UI.COLOR_PAPER}); + font-size: 16px; + width: auto; + outline: none; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + } + + color: ${({ theme, color }) => (color === 'red' ? theme.error : `var(${UI.COLOR_TEXT})`)}; + text-align: right; +` + +export const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }>` + height: 2rem; + position: relative; + padding: 0 0.75rem; + flex: 1; + border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.error : theme.bg2}`}; + + :hover { + border: ${({ theme, active, warning }) => + active && `1px solid ${warning ? darken(theme.error, 0.1) : darken(theme.bg2, 0.1)}`}; + } + + input { + width: 100%; + height: 100%; + border: 0; + border-radius: 2rem; + } +` + +export const SlippageEmojiContainer = styled.span` + color: #f3841e; + ${Media.upToSmall()} { + display: none; + } +` + +export const SmartSlippageInfo = styled.div` + color: var(${UI.COLOR_GREEN}); + font-size: 13px; + text-align: right; + width: 100%; + padding-right: 0.2rem; + display: flex; + justify-content: flex-end; + padding-bottom: 0.35rem; + + > span { + margin-left: 4px; + } +` + +export const Wrapper = styled.div` + ${RowBetween} > button, ${OptionCustom} { + &:disabled { + color: var(${UI.COLOR_TEXT_OPACITY_50}); + background-color: var(${UI.COLOR_PAPER}); + border: none; + pointer-events: none; + } + } + + ${OptionCustom} { + background-color: var(${UI.COLOR_PAPER_DARKER}); + border: 0; + color: inherit; + + > div > input { + background: transparent; + color: inherit; + + &:disabled { + color: inherit; + background-color: inherit; + } + } + + > div > input::placeholder { + opacity: 0.5; + color: inherit; + } + } + + ${RowFixed} { + color: inherit; + + > div { + color: inherit; + opacity: 0.85; + } + + > button { + background-color: var(${UI.COLOR_PAPER_DARKER}); + border: 0; + } + + > button > input { + background: transparent; + color: inherit; + } + + > button > input::placeholder { + background: transparent; + opacity: 0.5; + color: inherit; + } + } +` diff --git a/apps/cowswap-frontend/src/legacy/hooks/useRefetchPriceCallback.tsx b/apps/cowswap-frontend/src/legacy/hooks/useRefetchPriceCallback.tsx index af1d89d539..9c0144dfd7 100644 --- a/apps/cowswap-frontend/src/legacy/hooks/useRefetchPriceCallback.tsx +++ b/apps/cowswap-frontend/src/legacy/hooks/useRefetchPriceCallback.tsx @@ -19,7 +19,7 @@ import { LegacyFeeQuoteParams, LegacyQuoteParams } from 'legacy/state/price/type import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { getBestQuote, getFastQuote, QuoteResult } from 'legacy/utils/price' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' +import { useIsEoaEthFlow } from 'modules/trade' import { ApiErrorCodes, isValidOperatorError } from 'api/cowProtocol/errors/OperatorError' import QuoteApiError, { @@ -183,8 +183,8 @@ export function useRefetchQuoteCallback() { const previouslyUnsupportedToken = getIsUnsupportedToken(sellToken) ? sellToken : getIsUnsupportedToken(buyToken) - ? buyToken - : null + ? buyToken + : null // can be a previously unsupported token which is now valid // so we check against map and remove it if (previouslyUnsupportedToken) { @@ -263,6 +263,6 @@ export function useRefetchQuoteCallback() { removeGpUnsupportedToken, addUnsupportedToken, setQuoteError, - ] + ], ) } diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 104bf22dd1..6e60c1b312 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -13,6 +13,7 @@ import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippage import { TradeConfirmation, TradeConfirmModal, + useIsEoaEthFlow, useOrderSubmittedContent, useReceiveAmountInfo, useTradeConfirmActions, @@ -26,7 +27,7 @@ import { RateInfoParams } from 'common/pure/RateInfo' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useBaseFlowContextSource } from '../../hooks/useFlowContext' -import { useIsEoaEthFlow } from '../../hooks/useIsEoaEthFlow' + import { useShouldPayGas } from '../../hooks/useShouldPayGas' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx index dc1025aeb8..2796864e55 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx @@ -3,8 +3,8 @@ import { useMemo } from 'react' import { useToggleSettingsMenu } from 'legacy/state/application/hooks' import { useUserTransactionTTL } from 'legacy/state/user/hooks' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' import { RowDeadlineContent } from 'modules/swap/pure/Row/RowDeadline' +import { useIsEoaEthFlow } from 'modules/trade' import { useIsWrapOrUnwrap } from 'modules/trade/hooks/useIsWrapOrUnwrap' import useNativeCurrency from 'lib/hooks/useNativeCurrency' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx index 681cec67ab..d622103d13 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx @@ -6,7 +6,8 @@ import { Percent } from '@uniswap/sdk-core' import { useToggleSettingsMenu } from 'legacy/state/application/hooks' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' +import { useIsEoaEthFlow } from 'modules/trade' + import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' import { useSetSlippage } from 'modules/swap/hooks/useSetSlippage' import { useSmartSwapSlippage } from 'modules/swap/hooks/useSwapSlippage' @@ -49,10 +50,21 @@ export function RowSlippage({ slippageTooltip, displaySlippage: `${formatPercent(allowedSlippage)}%`, isSmartSlippageApplied, - smartSlippage: smartSwapSlippage && !isEoaEthFlow ? `${formatPercent(new Percent(smartSwapSlippage, 10_000))}%` : undefined, + smartSlippage: + smartSwapSlippage && !isEoaEthFlow ? `${formatPercent(new Percent(smartSwapSlippage, 10_000))}%` : undefined, setAutoSlippage: smartSwapSlippage && !isEoaEthFlow ? () => setSlippage(null) : undefined, }), - [chainId, isEoaEthFlow, nativeCurrency.symbol, showSettingOnClick, allowedSlippage, slippageLabel, slippageTooltip, smartSwapSlippage, isSmartSlippageApplied] + [ + chainId, + isEoaEthFlow, + nativeCurrency.symbol, + showSettingOnClick, + allowedSlippage, + slippageLabel, + slippageTooltip, + smartSwapSlippage, + isSmartSlippageApplied, + ], ) return diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 555a036e89..4c19f1c8bc 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -17,7 +17,6 @@ import { useInjectedWidgetParams } from 'modules/injectedWidget' import { EthFlowModal, EthFlowProps } from 'modules/swap/containers/EthFlow' import { SwapModals, SwapModalsProps } from 'modules/swap/containers/SwapModals' import { SwapButtonState } from 'modules/swap/helpers/getSwapButtonState' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' import { useShowRecipientControls } from 'modules/swap/hooks/useShowRecipientControls' import { useSwapButtonContext } from 'modules/swap/hooks/useSwapButtonContext' import { useSwapCurrenciesAmounts } from 'modules/swap/hooks/useSwapCurrenciesAmounts' @@ -30,6 +29,7 @@ import { SwapWarningsTopProps, } from 'modules/swap/pure/warnings' import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' +import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts index bb0f2fc0b6..eb9ca97852 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts @@ -1,6 +1,6 @@ import { useWalletDetails } from '@cowprotocol/wallet' -import { useIsEoaEthFlow } from './useIsEoaEthFlow' +import { useIsEoaEthFlow } from 'modules/trade' export function useShouldPayGas() { const { allowsOffchainSigning } = useWalletDetails() diff --git a/apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx b/apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx index 60c9cc8290..204feb6e6a 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx @@ -1,9 +1,9 @@ import { isTruthy } from '@cowprotocol/common-utils' import { useWalletDetails } from '@cowprotocol/wallet' -import useNativeCurrency from 'lib/hooks/useNativeCurrency' +import { useIsEoaEthFlow } from 'modules/trade' -import { useIsEoaEthFlow } from '../hooks/useIsEoaEthFlow' +import useNativeCurrency from 'lib/hooks/useNativeCurrency' export function NetworkCostsTooltipSuffix() { const { allowsOffchainSigning } = useWalletDetails() diff --git a/apps/cowswap-frontend/src/modules/swap/pure/ReceiveAmountInfo/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/ReceiveAmountInfo/index.tsx index 28dae3637f..61a12cd9c2 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/ReceiveAmountInfo/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/ReceiveAmountInfo/index.tsx @@ -6,8 +6,8 @@ import { Trans } from '@lingui/macro' import { BalanceAndSubsidy } from 'legacy/hooks/useCowBalanceAndSubsidy' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' import { getOrderTypeReceiveAmounts } from 'modules/trade' +import { useIsEoaEthFlow } from 'modules/trade' import { ReceiveAmountInfo } from 'modules/trade/types' import * as styledEl from './styled' diff --git a/apps/cowswap-frontend/src/modules/swap/state/EthFlow/updaters/EthFlowDeadlineUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/state/EthFlow/updaters/EthFlowDeadlineUpdater.tsx index 21ba267bc1..38b095c69d 100644 --- a/apps/cowswap-frontend/src/modules/swap/state/EthFlow/updaters/EthFlowDeadlineUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/swap/state/EthFlow/updaters/EthFlowDeadlineUpdater.tsx @@ -5,7 +5,7 @@ import { loadJsonFromLocalStorage, setJsonToLocalStorage } from '@cowprotocol/co import { useUserTransactionTTL } from 'legacy/state/user/hooks' -import { useIsEoaEthFlow } from 'modules/swap/hooks/useIsEoaEthFlow' +import { useIsEoaEthFlow } from 'modules/trade' import { DeadlineSettings } from './types' diff --git a/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts index c7351bcafd..9e635ec829 100644 --- a/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts @@ -6,7 +6,7 @@ import { bpsToPercent } from '@cowprotocol/common-utils' import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { walletInfoAtom } from '@cowprotocol/wallet' -import { isEoaEthFlowAtom } from './isEoaEthFlowAtom' +import { isEoaEthFlowAtom } from '../../trade/state/isEoaEthFlowAtom' type SlippageBpsPerNetwork = Record diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx index 826379ae67..244a414c9c 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx @@ -15,14 +15,13 @@ import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { useAppData, useAppDataHooks, useUploadAppData } from 'modules/appData' import { useGetCachedPermit } from 'modules/permit' -import { useTradeConfirmActions } from 'modules/trade' +import { useTradeConfirmActions, useIsEoaEthFlow } from 'modules/trade' import { useTokenContract, useWETHContract } from 'common/hooks/useContract' import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' import { useSafeMemo } from 'common/hooks/useSafeMemo' import { useSwapAmountsWithSlippage } from '../hooks/useFlowContext' -import { useIsEoaEthFlow } from '../hooks/useIsEoaEthFlow' import { useIsSafeEthFlow } from '../hooks/useIsSafeEthFlow' import { useSwapSlippage } from '../hooks/useSwapSlippage' import { useDerivedSwapInfo, useSwapState } from '../hooks/useSwapState' diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useIsEoaEthFlow.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useIsEoaEthFlow.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useIsEoaEthFlow.ts rename to apps/cowswap-frontend/src/modules/trade/hooks/useIsEoaEthFlow.ts diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 841092f24d..c5dca545b2 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -31,6 +31,7 @@ export * from './hooks/useOnCurrencySelection' export * from './hooks/useDerivedTradeState' export * from './hooks/useNavigateToNewOrderCallback' export * from './hooks/useOrderSubmittedContent' +export * from './hooks/useIsEoaEthFlow' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' diff --git a/apps/cowswap-frontend/src/modules/swap/state/isEoaEthFlowAtom.ts b/apps/cowswap-frontend/src/modules/trade/state/isEoaEthFlowAtom.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/state/isEoaEthFlowAtom.ts rename to apps/cowswap-frontend/src/modules/trade/state/isEoaEthFlowAtom.ts From 2df7b69ea63491cc55bf36ea52eb7893d09458d0 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 16:14:17 +0500 Subject: [PATCH 10/90] refactor: move hooks to tradeSlippage module --- .../components/TransactionSettings/index.tsx | 12 +++++++---- .../ConfirmSwapModalSetup/index.tsx | 2 +- .../swap/containers/Row/RowSlippage/index.tsx | 4 +--- .../swap/containers/SwapUpdaters/index.tsx | 6 ++---- .../swap/containers/SwapWidget/index.tsx | 3 +-- .../src/modules/swap/hooks/useFlowContext.ts | 2 +- .../src/modules/swap/hooks/useSwapState.tsx | 21 +++++++++---------- .../src/modules/swap/index.ts | 2 +- .../modules/swap/state/useSwapDerivedState.ts | 2 +- .../swap/updaters/BaseFlowContextUpdater.tsx | 2 +- .../src/modules/trade/index.ts | 1 + .../hooks/useIsSlippageModified.ts | 0 .../hooks/useIsSmartSlippageApplied.ts | 0 .../hooks/useSetSlippage.ts | 0 .../hooks/useSwapSlippage.ts | 0 .../src/modules/tradeSlippage/index.tsx | 5 +++++ .../state/slippageValueAndTypeAtom.ts | 2 +- .../updaters/SmartSlippageUpdater.ts | 2 +- 18 files changed, 35 insertions(+), 31 deletions(-) rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/hooks/useIsSlippageModified.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/hooks/useIsSmartSlippageApplied.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/hooks/useSetSlippage.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/hooks/useSwapSlippage.ts (100%) create mode 100644 apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/state/slippageValueAndTypeAtom.ts (97%) rename apps/cowswap-frontend/src/modules/{swap => tradeSlippage}/updaters/SmartSlippageUpdater.ts (99%) diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx index 71e1842c9d..acfa49b11a 100644 --- a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx @@ -26,13 +26,17 @@ import { AutoColumn } from 'legacy/components/Column' import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { orderExpirationTimeAnalytics, slippageToleranceAnalytics } from 'modules/analytics' -import { useIsSlippageModified } from 'modules/swap/hooks/useIsSlippageModified' -import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' -import { useSetSlippage } from 'modules/swap/hooks/useSetSlippage' -import { useDefaultSwapSlippage, useSmartSwapSlippage, useSwapSlippage } from 'modules/swap/hooks/useSwapSlippage' import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'modules/swap/pure/Row/RowDeadline' import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'modules/swap/pure/Row/RowSlippageContent' import { useIsEoaEthFlow } from 'modules/trade' +import { + useDefaultSwapSlippage, + useSmartSwapSlippage, + useSwapSlippage, + useIsSlippageModified, + useIsSmartSlippageApplied, + useSetSlippage, +} from 'modules/tradeSlippage' import useNativeCurrency from 'lib/hooks/useNativeCurrency' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 6e60c1b312..ed566d9392 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -9,7 +9,7 @@ import { HighFeeWarning } from 'legacy/components/SwapWarnings' import { PriceImpact } from 'legacy/hooks/usePriceImpact' import TradeGp from 'legacy/state/swap/TradeGp' -import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' +import { useIsSmartSlippageApplied } from 'modules/tradeSlippage' import { TradeConfirmation, TradeConfirmModal, diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx index d622103d13..1659a21a00 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx @@ -8,9 +8,7 @@ import { useToggleSettingsMenu } from 'legacy/state/application/hooks' import { useIsEoaEthFlow } from 'modules/trade' -import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' -import { useSetSlippage } from 'modules/swap/hooks/useSetSlippage' -import { useSmartSwapSlippage } from 'modules/swap/hooks/useSwapSlippage' +import { useIsSmartSlippageApplied, useSetSlippage, useSmartSwapSlippage } from 'modules/tradeSlippage' import { RowSlippageContent } from 'modules/swap/pure/Row/RowSlippageContent' import useNativeCurrency from 'lib/hooks/useNativeCurrency' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx index 021a8fd42e..423e31942c 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx @@ -1,11 +1,9 @@ import { percentToBps } from '@cowprotocol/common-utils' -import { useIsSmartSlippageApplied } from 'modules/swap/hooks/useIsSmartSlippageApplied' +import { AppDataUpdater } from 'modules/appData' +import { SmartSlippageUpdater, useSwapSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' -import { AppDataUpdater } from '../../../appData' -import { useSwapSlippage } from '../../hooks/useSwapSlippage' import { BaseFlowContextUpdater } from '../../updaters/BaseFlowContextUpdater' -import { SmartSlippageUpdater } from '../../updaters/SmartSlippageUpdater' import { SwapAmountsFromUrlUpdater } from '../../updaters/SwapAmountsFromUrlUpdater' import { SwapDerivedStateUpdater } from '../../updaters/SwapDerivedStateUpdater' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 4c19f1c8bc..8c5700f166 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -33,6 +33,7 @@ import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' +import { useIsSlippageModified, useSwapSlippage } from 'modules/tradeSlippage' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' @@ -42,9 +43,7 @@ import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { SWAP_QUOTE_CHECK_INTERVAL } from 'common/updaters/FeesUpdater' import useNativeCurrency from 'lib/hooks/useNativeCurrency' -import { useIsSlippageModified } from '../../hooks/useIsSlippageModified' import { useIsSwapEth } from '../../hooks/useIsSwapEth' -import { useSwapSlippage } from '../../hooks/useSwapSlippage' import { useDerivedSwapInfo, useHighFeeWarning, diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts index 23ea67454e..33bdc15006 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts @@ -12,10 +12,10 @@ import { PostOrderParams } from 'legacy/utils/trade' import { BaseFlowContext } from 'modules/swap/services/types' import { TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics' import { getOrderValidTo } from 'modules/tradeQuote' +import { useSwapSlippage } from 'modules/tradeSlippage' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { useSwapSlippage } from './useSwapSlippage' import { useDerivedSwapInfo } from './useSwapState' import { getAmountsForSignature } from '../helpers/getAmountsForSignature' diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx index c506885a3b..1b1b392467 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx @@ -23,13 +23,12 @@ import { Field } from 'legacy/state/types' import { changeSwapAmountAnalytics, switchTokensAnalytics } from 'modules/analytics' import { useNavigateOnCurrencySelection } from 'modules/trade/hooks/useNavigateOnCurrencySelection' import { useTradeNavigate } from 'modules/trade/hooks/useTradeNavigate' +import { useSwapSlippage } from 'modules/tradeSlippage' import { useVolumeFee } from 'modules/volumeFee' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { useSwapSlippage } from './useSwapSlippage' - export const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a': true, // v2 router 01 @@ -90,14 +89,14 @@ export function useSwapActionHandlers(): SwapActions { changeSwapAmountAnalytics(field, Number(typedValue)) dispatch(typeInput({ field, typedValue })) }, - [dispatch] + [dispatch], ) const onChangeRecipient = useCallback( (recipient: string | null) => { dispatch(setRecipient({ recipient })) }, - [dispatch] + [dispatch], ) return useMemo( @@ -107,7 +106,7 @@ export function useSwapActionHandlers(): SwapActions { onUserInput, onChangeRecipient, }), - [onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient] + [onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient], ) } @@ -150,7 +149,7 @@ export function useHighFeeWarning(trade?: TradeGp) { feeWarningAccepted: _computeFeeWarningAcceptedState({ feeWarningAccepted, isHighFee }), setFeeWarningAccepted, }), - [isHighFee, feePercentage, feeWarningAccepted, setFeeWarningAccepted] + [isHighFee, feePercentage, feeWarningAccepted, setFeeWarningAccepted], ) } @@ -187,7 +186,7 @@ export function useUnknownImpactWarning() { impactWarningAccepted, setImpactWarningAccepted, }), - [impactWarningAccepted, setImpactWarningAccepted] + [impactWarningAccepted, setImpactWarningAccepted], ) } @@ -220,7 +219,7 @@ export function useDerivedSwapInfo(): DerivedSwapInfo { const isExactIn: boolean = independentField === Field.INPUT const parsedAmount = useMemo( () => tryParseCurrencyAmount(typedValue, (isExactIn ? inputCurrency : outputCurrency) ?? undefined), - [inputCurrency, isExactIn, outputCurrency, typedValue] + [inputCurrency, isExactIn, outputCurrency, typedValue], ) const currencies: { [field in Field]?: Currency | null } = useMemo( @@ -228,7 +227,7 @@ export function useDerivedSwapInfo(): DerivedSwapInfo { [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency, }), - [inputCurrency, outputCurrency] + [inputCurrency, outputCurrency], ) // TODO: be careful! For native tokens we use symbol instead of address @@ -243,7 +242,7 @@ export function useDerivedSwapInfo(): DerivedSwapInfo { ? currencies.OUTPUT.symbol : currencies.OUTPUT?.address?.toLowerCase(), }), - [currencies] + [currencies], ) const { quote } = useGetQuoteAndStatus({ @@ -280,7 +279,7 @@ export function useDerivedSwapInfo(): DerivedSwapInfo { [Field.INPUT]: inputCurrencyBalance, [Field.OUTPUT]: outputCurrencyBalance, }), - [inputCurrencyBalance, outputCurrencyBalance] + [inputCurrencyBalance, outputCurrencyBalance], ) // allowed slippage is either auto slippage, or custom user defined slippage if auto slippage disabled diff --git a/apps/cowswap-frontend/src/modules/swap/index.ts b/apps/cowswap-frontend/src/modules/swap/index.ts index d22922dbb8..772c226a02 100644 --- a/apps/cowswap-frontend/src/modules/swap/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/index.ts @@ -2,5 +2,5 @@ export * from './containers/SwapWidget' export * from './containers/SwapUpdaters' export * from './updaters/SwapDerivedStateUpdater' export * from './updaters/SwapAmountsFromUrlUpdater' -export * from './updaters/SmartSlippageUpdater' +export * from '../tradeSlippage/updaters/SmartSlippageUpdater' export * from './state/swapDerivedStateAtom' diff --git a/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts b/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts index c15bbd9a72..01532dbd48 100644 --- a/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts +++ b/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts @@ -6,13 +6,13 @@ import { OrderKind } from '@cowprotocol/cow-sdk' import { Field } from 'legacy/state/types' import { TradeType } from 'modules/trade' +import { useSwapSlippage } from 'modules/tradeSlippage' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useSafeMemoObject } from 'common/hooks/useSafeMemo' import { SwapDerivedState, swapDerivedStateAtom } from './swapDerivedStateAtom' -import { useSwapSlippage } from '../hooks/useSwapSlippage' import { useDerivedSwapInfo, useSwapState } from '../hooks/useSwapState' export function useSwapDerivedState(): SwapDerivedState { diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx index 244a414c9c..535af1fb47 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx @@ -16,6 +16,7 @@ import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { useAppData, useAppDataHooks, useUploadAppData } from 'modules/appData' import { useGetCachedPermit } from 'modules/permit' import { useTradeConfirmActions, useIsEoaEthFlow } from 'modules/trade' +import { useSwapSlippage } from 'modules/tradeSlippage' import { useTokenContract, useWETHContract } from 'common/hooks/useContract' import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' @@ -23,7 +24,6 @@ import { useSafeMemo } from 'common/hooks/useSafeMemo' import { useSwapAmountsWithSlippage } from '../hooks/useFlowContext' import { useIsSafeEthFlow } from '../hooks/useIsSafeEthFlow' -import { useSwapSlippage } from '../hooks/useSwapSlippage' import { useDerivedSwapInfo, useSwapState } from '../hooks/useSwapState' import { baseFlowContextSourceAtom } from '../state/baseFlowContextSourceAtom' import { FlowType } from '../types/flowContext' diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index c5dca545b2..a2c798b8a8 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -39,6 +39,7 @@ export * from './state/receiveAmountInfoAtom' export * from './state/tradeTypeAtom' export * from './state/derivedTradeStateAtom' export * from './state/isWrapOrUnwrapAtom' +export * from './state/isEoaEthFlowAtom' export * from './pure/RecipientRow' export * from './pure/ReceiveAmountTitle' export * from './pure/PartnerFeeRow' diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useIsSlippageModified.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useIsSlippageModified.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useIsSlippageModified.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useIsSlippageModified.ts diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useIsSmartSlippageApplied.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useIsSmartSlippageApplied.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useIsSmartSlippageApplied.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useIsSmartSlippageApplied.ts diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSetSlippage.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useSetSlippage.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapSlippage.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useSwapSlippage.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx new file mode 100644 index 0000000000..4cc5a1ee96 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx @@ -0,0 +1,5 @@ +export { SmartSlippageUpdater } from './updaters/SmartSlippageUpdater' +export * from './hooks/useSetSlippage' +export * from './hooks/useSwapSlippage' +export * from './hooks/useIsSmartSlippageApplied' +export * from './hooks/useIsSlippageModified' diff --git a/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts similarity index 97% rename from apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts index 9e635ec829..21a8fa7430 100644 --- a/apps/cowswap-frontend/src/modules/swap/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts @@ -6,7 +6,7 @@ import { bpsToPercent } from '@cowprotocol/common-utils' import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { walletInfoAtom } from '@cowprotocol/wallet' -import { isEoaEthFlowAtom } from '../../trade/state/isEoaEthFlowAtom' +import { isEoaEthFlowAtom } from 'modules/trade' type SlippageBpsPerNetwork = Record diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts similarity index 99% rename from apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater.ts rename to apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts index 7e79d89102..43944e3737 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts @@ -42,7 +42,7 @@ export function SmartSlippageUpdater() { return response.slippageBps }, - SWR_OPTIONS + SWR_OPTIONS, ).data useEffect(() => { From 4ff2db584055b7087148300be03c4b446831473e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 16:16:35 +0500 Subject: [PATCH 11/90] refactor: rename swapSlippage to tradeSlippage --- .../components/TransactionSettings/index.tsx | 12 +++++----- .../swap/containers/Row/RowSlippage/index.tsx | 4 ++-- .../swap/containers/SwapUpdaters/index.tsx | 4 ++-- .../swap/containers/SwapWidget/index.tsx | 4 ++-- .../src/modules/swap/hooks/useFlowContext.ts | 4 ++-- .../src/modules/swap/hooks/useSwapState.tsx | 4 ++-- .../modules/swap/state/useSwapDerivedState.ts | 4 ++-- .../swap/updaters/BaseFlowContextUpdater.tsx | 4 ++-- .../tradeSlippage/hooks/useSetSlippage.ts | 4 ++-- .../tradeSlippage/hooks/useSwapSlippage.ts | 18 --------------- .../tradeSlippage/hooks/useTradeSlippage.ts | 22 ++++++++++++++++++ .../src/modules/tradeSlippage/index.tsx | 2 +- .../state/slippageValueAndTypeAtom.ts | 23 +++++++++++-------- .../updaters/SmartSlippageUpdater.ts | 8 +++---- 14 files changed, 62 insertions(+), 55 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts create mode 100644 apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useTradeSlippage.ts diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx index acfa49b11a..a262155628 100644 --- a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx @@ -30,9 +30,9 @@ import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'modules/swap/pure/Row/RowSlippageContent' import { useIsEoaEthFlow } from 'modules/trade' import { - useDefaultSwapSlippage, - useSmartSwapSlippage, - useSwapSlippage, + useDefaultTradeSlippage, + useSmartTradeSlippage, + useTradeSlippage, useIsSlippageModified, useIsSmartSlippageApplied, useSetSlippage, @@ -59,11 +59,11 @@ export function TransactionSettings() { const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() - const swapSlippage = useSwapSlippage() - const defaultSwapSlippage = useDefaultSwapSlippage() + const swapSlippage = useTradeSlippage() + const defaultSwapSlippage = useDefaultTradeSlippage() const setSwapSlippage = useSetSlippage() const isSmartSlippageApplied = useIsSmartSlippageApplied() - const smartSlippage = useSmartSwapSlippage() + const smartSlippage = useSmartTradeSlippage() const chosenSlippageMatchesSmartSlippage = smartSlippage && new Percent(smartSlippage, 10_000).equalTo(swapSlippage) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx index 1659a21a00..2a47af4fef 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx @@ -8,7 +8,7 @@ import { useToggleSettingsMenu } from 'legacy/state/application/hooks' import { useIsEoaEthFlow } from 'modules/trade' -import { useIsSmartSlippageApplied, useSetSlippage, useSmartSwapSlippage } from 'modules/tradeSlippage' +import { useIsSmartSlippageApplied, useSetSlippage, useSmartTradeSlippage } from 'modules/tradeSlippage' import { RowSlippageContent } from 'modules/swap/pure/Row/RowSlippageContent' import useNativeCurrency from 'lib/hooks/useNativeCurrency' @@ -33,7 +33,7 @@ export function RowSlippage({ const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() - const smartSwapSlippage = useSmartSwapSlippage() + const smartSwapSlippage = useSmartTradeSlippage() const isSmartSlippageApplied = useIsSmartSlippageApplied() const setSlippage = useSetSlippage() diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx index 423e31942c..e947d8651c 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx @@ -1,14 +1,14 @@ import { percentToBps } from '@cowprotocol/common-utils' import { AppDataUpdater } from 'modules/appData' -import { SmartSlippageUpdater, useSwapSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' +import { SmartSlippageUpdater, useTradeSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' import { BaseFlowContextUpdater } from '../../updaters/BaseFlowContextUpdater' import { SwapAmountsFromUrlUpdater } from '../../updaters/SwapAmountsFromUrlUpdater' import { SwapDerivedStateUpdater } from '../../updaters/SwapDerivedStateUpdater' export function SwapUpdaters() { - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const isSmartSlippageApplied = useIsSmartSlippageApplied() return ( diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 8c5700f166..8796901be2 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -33,7 +33,7 @@ import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' -import { useIsSlippageModified, useSwapSlippage } from 'modules/tradeSlippage' +import { useIsSlippageModified, useTradeSlippage } from 'modules/tradeSlippage' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' @@ -66,7 +66,7 @@ export interface SwapWidgetProps { export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const { chainId, account } = useWalletInfo() const { slippageAdjustedSellAmount, currencies, trade } = useDerivedSwapInfo() - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const isSlippageModified = useIsSlippageModified() const parsedAmounts = useSwapCurrenciesAmounts() const { isSupportedWallet } = useWalletDetails() diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts index 33bdc15006..ff37d5df84 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts @@ -12,7 +12,7 @@ import { PostOrderParams } from 'legacy/utils/trade' import { BaseFlowContext } from 'modules/swap/services/types' import { TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics' import { getOrderValidTo } from 'modules/tradeQuote' -import { useSwapSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { useSafeMemo } from 'common/hooks/useSafeMemo' @@ -26,7 +26,7 @@ export function useSwapAmountsWithSlippage(): [ CurrencyAmount | undefined, CurrencyAmount | undefined, ] { - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const { trade } = useDerivedSwapInfo() const { INPUT, OUTPUT } = computeSlippageAdjustedAmounts(trade, slippage) diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx index 1b1b392467..ae3eaaf7f4 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx @@ -23,7 +23,7 @@ import { Field } from 'legacy/state/types' import { changeSwapAmountAnalytics, switchTokensAnalytics } from 'modules/analytics' import { useNavigateOnCurrencySelection } from 'modules/trade/hooks/useNavigateOnCurrencySelection' import { useTradeNavigate } from 'modules/trade/hooks/useTradeNavigate' -import { useSwapSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { useVolumeFee } from 'modules/volumeFee' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' @@ -193,7 +193,7 @@ export function useUnknownImpactWarning() { // from the current swap inputs, compute the best trade and return it. export function useDerivedSwapInfo(): DerivedSwapInfo { const { account, chainId } = useWalletInfo() - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const { independentField, diff --git a/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts b/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts index 01532dbd48..45b0f95097 100644 --- a/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts +++ b/apps/cowswap-frontend/src/modules/swap/state/useSwapDerivedState.ts @@ -6,7 +6,7 @@ import { OrderKind } from '@cowprotocol/cow-sdk' import { Field } from 'legacy/state/types' import { TradeType } from 'modules/trade' -import { useSwapSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useSafeMemoObject } from 'common/hooks/useSafeMemo' @@ -23,7 +23,7 @@ export function useFillSwapDerivedState() { const { independentField, recipient, recipientAddress } = useSwapState() const { trade, currencyBalances, currencies, slippageAdjustedSellAmount, slippageAdjustedBuyAmount, parsedAmount } = useDerivedSwapInfo() - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const isSellTrade = independentField === Field.INPUT const inputCurrency = currencies.INPUT || null diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx index 535af1fb47..95b2e62f78 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx @@ -16,7 +16,7 @@ import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { useAppData, useAppDataHooks, useUploadAppData } from 'modules/appData' import { useGetCachedPermit } from 'modules/permit' import { useTradeConfirmActions, useIsEoaEthFlow } from 'modules/trade' -import { useSwapSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { useTokenContract, useWETHContract } from 'common/hooks/useContract' import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' @@ -35,7 +35,7 @@ export function BaseFlowContextUpdater() { const { allowsOffchainSigning } = useWalletDetails() const gnosisSafeInfo = useGnosisSafeInfo() const { recipient } = useSwapState() - const slippage = useSwapSlippage() + const slippage = useTradeSlippage() const { trade, currenciesIds } = useDerivedSwapInfo() const { quote } = useGetQuoteAndStatus({ token: currenciesIds.INPUT, diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts index 579dbed4d0..3040c569c6 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSetSlippage.ts @@ -1,7 +1,7 @@ import { useSetAtom } from 'jotai' -import { setSwapSlippageAtom } from '../state/slippageValueAndTypeAtom' +import { setTradeSlippageAtom } from '../state/slippageValueAndTypeAtom' export function useSetSlippage() { - return useSetAtom(setSwapSlippageAtom) + return useSetAtom(setTradeSlippageAtom) } diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts deleted file mode 100644 index 3f041d35fd..0000000000 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useSwapSlippage.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useAtomValue } from 'jotai/index' - -import { bpsToPercent } from '@cowprotocol/common-utils' -import { Percent } from '@uniswap/sdk-core' - -import { defaultSlippageAtom, smartSwapSlippageAtom, swapSlippagePercentAtom } from '../state/slippageValueAndTypeAtom' - -export function useSwapSlippage(): Percent { - return useAtomValue(swapSlippagePercentAtom) -} - -export function useDefaultSwapSlippage() { - return bpsToPercent(useAtomValue(defaultSlippageAtom)) -} - -export function useSmartSwapSlippage() { - return useAtomValue(smartSwapSlippageAtom) -} diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useTradeSlippage.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useTradeSlippage.ts new file mode 100644 index 0000000000..e3666cbc36 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/hooks/useTradeSlippage.ts @@ -0,0 +1,22 @@ +import { useAtomValue } from 'jotai/index' + +import { bpsToPercent } from '@cowprotocol/common-utils' +import { Percent } from '@uniswap/sdk-core' + +import { + defaultSlippageAtom, + smartTradeSlippageAtom, + tradeSlippagePercentAtom, +} from '../state/slippageValueAndTypeAtom' + +export function useTradeSlippage(): Percent { + return useAtomValue(tradeSlippagePercentAtom) +} + +export function useDefaultTradeSlippage() { + return bpsToPercent(useAtomValue(defaultSlippageAtom)) +} + +export function useSmartTradeSlippage() { + return useAtomValue(smartTradeSlippageAtom) +} diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx index 4cc5a1ee96..5dc0646e05 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx @@ -1,5 +1,5 @@ export { SmartSlippageUpdater } from './updaters/SmartSlippageUpdater' export * from './hooks/useSetSlippage' -export * from './hooks/useSwapSlippage' +export * from './hooks/useTradeSlippage' export * from './hooks/useIsSmartSlippageApplied' export * from './hooks/useIsSlippageModified' diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts index 21a8fa7430..9a5f12511e 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts @@ -12,7 +12,10 @@ type SlippageBpsPerNetwork = Record type SlippageType = 'smart' | 'default' | 'user' -const normalSwapSlippageAtom = atomWithStorage('swapSlippageAtom:v0', mapSupportedNetworks(null)) +const normalTradeSlippageAtom = atomWithStorage( + 'tradeSlippageAtom:v0', + mapSupportedNetworks(null), +) const ethFlowSlippageAtom = atomWithStorage('ethFlowSlippageAtom:v0', mapSupportedNetworks(null)) @@ -26,40 +29,40 @@ export const defaultSlippageAtom = atom((get) => { const currentSlippageAtom = atom((get) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) - const normalSwapSlippage = get(normalSwapSlippageAtom) + const normalSlippage = get(normalTradeSlippageAtom) const ethFlowSlippage = get(ethFlowSlippageAtom) - return (isEoaEthFlow ? ethFlowSlippage : normalSwapSlippage)?.[chainId] ?? null + return (isEoaEthFlow ? ethFlowSlippage : normalSlippage)?.[chainId] ?? null }) -export const smartSwapSlippageAtom = atom(null) +export const smartTradeSlippageAtom = atom(null) export const slippageValueAndTypeAtom = atom<{ type: SlippageType; value: number }>((get) => { const currentSlippage = get(currentSlippageAtom) const defaultSlippage = get(defaultSlippageAtom) - const smartSwapSlippage = get(smartSwapSlippageAtom) + const smartSlippage = get(smartTradeSlippageAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) if (typeof currentSlippage === 'number') { return { type: 'user', value: currentSlippage } } - if (!isEoaEthFlow && smartSwapSlippage && smartSwapSlippage !== defaultSlippage) { - return { type: 'smart', value: smartSwapSlippage } + if (!isEoaEthFlow && smartSlippage && smartSlippage !== defaultSlippage) { + return { type: 'smart', value: smartSlippage } } return { type: 'default', value: defaultSlippage } }) -export const swapSlippagePercentAtom = atom((get) => { +export const tradeSlippagePercentAtom = atom((get) => { return bpsToPercent(get(slippageValueAndTypeAtom).value) }) -export const setSwapSlippageAtom = atom(null, (get, set, slippageBps: number | null) => { +export const setTradeSlippageAtom = atom(null, (get, set, slippageBps: number | null) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) - const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalSwapSlippageAtom + const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalTradeSlippageAtom const currentState = get(currentStateAtom) set(currentStateAtom, { ...currentState, [chainId]: slippageBps }) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts index 43944e3737..548200b761 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater.ts @@ -11,7 +11,7 @@ import useSWR from 'swr' import { useDerivedTradeState, useIsWrapOrUnwrap } from 'modules/trade' -import { smartSwapSlippageAtom } from '../state/slippageValueAndTypeAtom' +import { smartTradeSlippageAtom } from '../state/slippageValueAndTypeAtom' const SWR_OPTIONS = { dedupingInterval: ms`1m`, @@ -25,7 +25,7 @@ export function SmartSlippageUpdater() { const { isSmartSlippageEnabled } = useFeatureFlags() const { chainId } = useWalletInfo() const { inputCurrency, outputCurrency } = useDerivedTradeState() || {} - const setSmartSwapSlippage = useSetAtom(smartSwapSlippageAtom) + const setSmartSlippage = useSetAtom(smartTradeSlippageAtom) const isWrapOrUnwrap = useIsWrapOrUnwrap() const sellTokenAddress = inputCurrency && getCurrencyAddress(inputCurrency).toLowerCase() @@ -46,8 +46,8 @@ export function SmartSlippageUpdater() { ).data useEffect(() => { - setSmartSwapSlippage(typeof slippageBps === 'number' ? slippageBps : null) - }, [slippageBps, setSmartSwapSlippage]) + setSmartSlippage(typeof slippageBps === 'number' ? slippageBps : null) + }, [slippageBps, setSmartSlippage]) return null } From f5c45e9a182d8afc6c0ed6f950f00cc594db5489 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 16:32:20 +0500 Subject: [PATCH 12/90] feat(trade-slippage): split slippage state by trade type --- .../state/slippageValueAndTypeAtom.ts | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts index 9a5f12511e..1cdf864787 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts @@ -6,18 +6,25 @@ import { bpsToPercent } from '@cowprotocol/common-utils' import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { walletInfoAtom } from '@cowprotocol/wallet' -import { isEoaEthFlowAtom } from 'modules/trade' +import { isEoaEthFlowAtom, TradeType, tradeTypeAtom } from 'modules/trade' type SlippageBpsPerNetwork = Record +type SlippagePerTradeType = Record + type SlippageType = 'smart' | 'default' | 'user' -const normalTradeSlippageAtom = atomWithStorage( - 'tradeSlippageAtom:v0', - mapSupportedNetworks(null), -) +const getDefaultSlippageState = () => + Object.keys(TradeType).reduce((acc, tradeType) => { + acc[tradeType as TradeType] = mapSupportedNetworks(null) + return acc + }, {} as SlippagePerTradeType) + +const normalTradeSlippageAtom = atomWithStorage('tradeSlippageAtom:v1', getDefaultSlippageState()) -const ethFlowSlippageAtom = atomWithStorage('ethFlowSlippageAtom:v0', mapSupportedNetworks(null)) +const ethFlowSlippageAtom = atomWithStorage('ethFlowSlippageAtom:v1', getDefaultSlippageState()) + +export const smartTradeSlippageAtom = atom(null) export const defaultSlippageAtom = atom((get) => { const { chainId } = get(walletInfoAtom) @@ -29,13 +36,14 @@ export const defaultSlippageAtom = atom((get) => { const currentSlippageAtom = atom((get) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) + const tradeTypeState = get(tradeTypeAtom) const normalSlippage = get(normalTradeSlippageAtom) const ethFlowSlippage = get(ethFlowSlippageAtom) - return (isEoaEthFlow ? ethFlowSlippage : normalSlippage)?.[chainId] ?? null -}) + if (!tradeTypeState) return null -export const smartTradeSlippageAtom = atom(null) + return (isEoaEthFlow ? ethFlowSlippage : normalSlippage)[tradeTypeState.tradeType]?.[chainId] ?? null +}) export const slippageValueAndTypeAtom = atom<{ type: SlippageType; value: number }>((get) => { const currentSlippage = get(currentSlippageAtom) @@ -61,9 +69,21 @@ export const tradeSlippagePercentAtom = atom((get) => { export const setTradeSlippageAtom = atom(null, (get, set, slippageBps: number | null) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) - - const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalTradeSlippageAtom - const currentState = get(currentStateAtom) - - set(currentStateAtom, { ...currentState, [chainId]: slippageBps }) + const tradeTypeState = get(tradeTypeAtom) + + if (tradeTypeState) { + const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalTradeSlippageAtom + const currentState = get(currentStateAtom) + + set(currentStateAtom, { + ...currentState, + [tradeTypeState.tradeType]: { + ...currentState[tradeTypeState.tradeType], + [chainId]: slippageBps, + }, + }) + } else { + // It should not happen in the normal flow + console.error('Trade type is not set, cannot set slippage!') + } }) From 8b8ae26e666b4a501fe6dbc555188ae98e8fdeed Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 16:51:59 +0500 Subject: [PATCH 13/90] refactor: unlink TransactionSettings from swap module --- .../common/utils/tradeSettingsTooltips.tsx | 53 +++++++++ .../src/legacy/components/Settings/index.tsx | 111 +++--------------- .../src/legacy/components/Settings/styled.tsx | 87 ++++++++++++++ .../components/TransactionSettings/index.tsx | 18 ++- .../src/legacy/state/user/hooks.tsx | 18 ++- .../ConfirmSwapModalSetup/index.tsx | 2 +- .../swap/containers/SwapWidget/index.tsx | 5 +- .../swap/pure/Row/RowDeadline/index.tsx | 21 +--- .../pure/Row/RowSlippageContent/index.tsx | 71 ++++++----- libs/types/src/common.ts | 2 + 10 files changed, 218 insertions(+), 170 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx create mode 100644 apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx diff --git a/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx new file mode 100644 index 0000000000..1032e65a60 --- /dev/null +++ b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx @@ -0,0 +1,53 @@ +import { + INPUT_OUTPUT_EXPLANATION, + MINIMUM_ETH_FLOW_DEADLINE_SECONDS, + MINIMUM_ETH_FLOW_SLIPPAGE, + PERCENTAGE_PRECISION, +} from '@cowprotocol/common-const' +import { SupportedChainId } from '@cowprotocol/cow-sdk' + +import { Trans } from '@lingui/macro' + +export function getNativeOrderDeadlineTooltip(symbols: (string | undefined)[] | undefined) { + return ( + + {symbols?.[0] || 'Native currency (e.g ETH)'} orders require a minimum transaction expiration time threshold of{' '} + {MINIMUM_ETH_FLOW_DEADLINE_SECONDS / 60} minutes to ensure the best swapping experience. +
+
+ Orders not matched after the threshold time are automatically refunded. +
+ ) +} + +export function getNonNativeOrderDeadlineTooltip() { + return ( + + Your swap expires and will not execute if it is pending for longer than the selected duration. + {INPUT_OUTPUT_EXPLANATION} + + ) +} + +export const getNativeSlippageTooltip = (chainId: SupportedChainId, symbols: (string | undefined)[] | undefined) => ( + + When selling {symbols?.[0] || 'a native currency'}, the minimum slippage tolerance is set to{' '} + {MINIMUM_ETH_FLOW_SLIPPAGE[chainId].toSignificant(PERCENTAGE_PRECISION)}% to ensure a high likelihood of order + matching, even in volatile market conditions. +
+
+ Orders on CoW Swap are always protected from MEV, so your slippage tolerance cannot be exploited. +
+) + +export const getNonNativeSlippageTooltip = () => ( + + Your slippage is MEV protected: all orders are submitted with tight spread (0.1%) on-chain. +
+
+ The slippage set enables a resubmission of your order in case of unfavourable price movements. +
+
+ {INPUT_OUTPUT_EXPLANATION} +
+) diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx b/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx index 9ac795d417..91279bfff0 100644 --- a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx @@ -1,12 +1,11 @@ import { useCallback, useRef } from 'react' import { useOnClickOutside } from '@cowprotocol/common-hooks' -import { HelpTooltip, Media, RowBetween, RowFixed, UI } from '@cowprotocol/ui' +import { StatefulValue } from '@cowprotocol/types' +import { HelpTooltip, RowBetween, RowFixed } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' -import { transparentize } from 'color2k' import { Text } from 'rebass' -import styled from 'styled-components/macro' import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' @@ -14,127 +13,47 @@ import { Toggle } from 'legacy/components/Toggle' import { TransactionSettings } from 'legacy/components/TransactionSettings' import { useModalIsOpen, useToggleSettingsMenu } from 'legacy/state/application/hooks' import { ApplicationModal } from 'legacy/state/application/reducer' -import { useRecipientToggleManager } from 'legacy/state/user/hooks' import { toggleRecipientAddressAnalytics } from 'modules/analytics' import { SettingsIcon } from 'modules/trade/pure/Settings' -export const StyledMenuButton = styled.button` - position: relative; - width: 100%; - border: none; - background-color: transparent; - margin: 0; - padding: 0; - border-radius: 0.5rem; - height: var(${UI.ICON_SIZE_NORMAL}); - opacity: 0.6; - transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; - color: inherit; - display: flex; - align-items: center; - - &:hover, - &:focus { - opacity: 1; - cursor: pointer; - outline: none; - color: currentColor; - } - - svg { - opacity: 1; - margin: auto; - transition: transform 0.3s cubic-bezier(0.65, 0.05, 0.36, 1); - color: inherit; - } -` - -const StyledMenu = styled.div` - margin: 0; - display: flex; - justify-content: center; - align-items: center; - position: relative; - border: none; - text-align: left; - color: inherit; - - ${RowFixed} { - color: inherit; - - > div { - color: inherit; - opacity: 0.85; - } - } -` - -export const MenuFlyout = styled.span` - min-width: 20.125rem; - background: var(${UI.COLOR_PRIMARY}); - box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), - 0px 24px 32px rgba(0, 0, 0, 0.01); - border-radius: 12px; - display: flex; - flex-direction: column; - font-size: 1rem; - position: absolute; - z-index: 100; - color: inherit; - box-shadow: ${({ theme }) => theme.boxShadow2}; - border: 1px solid ${({ theme }) => transparentize(theme.white, 0.95)}; - background-color: var(${UI.COLOR_PAPER}); - color: inherit; - padding: 0; - margin: 0; - top: 36px; - right: 0; - width: 280px; - - ${Media.upToMedium()} { - min-width: 18.125rem; - } - - user-select: none; -` +import * as styledEl from './styled' interface SettingsTabProps { className?: string + recipientToggleState: StatefulValue + deadlineState: StatefulValue } -export function SettingsTab({ className }: SettingsTabProps) { +export function SettingsTab({ className, recipientToggleState, deadlineState }: SettingsTabProps) { const node = useRef(null) const open = useModalIsOpen(ApplicationModal.SETTINGS) const toggle = useToggleSettingsMenu() - const [recipientToggleVisible, toggleRecipientVisibilityAux] = useRecipientToggleManager() + const [recipientToggleVisible, toggleRecipientVisibilityAux] = recipientToggleState const toggleRecipientVisibility = useCallback( (value?: boolean) => { const isVisible = value ?? !recipientToggleVisible toggleRecipientAddressAnalytics(isVisible) toggleRecipientVisibilityAux(isVisible) }, - [toggleRecipientVisibilityAux, recipientToggleVisible] + [toggleRecipientVisibilityAux, recipientToggleVisible], ) - // show confirmation view before turning on - useOnClickOutside([node], open ? toggle : undefined) return ( - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 - - + + - + {open && ( - + Transaction Settings - + Interface Settings @@ -157,8 +76,8 @@ export function SettingsTab({ className }: SettingsTabProps) { /> - + )} - + ) } diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx b/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx new file mode 100644 index 0000000000..2b1ba99ae0 --- /dev/null +++ b/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx @@ -0,0 +1,87 @@ +import { Media, RowFixed, UI } from '@cowprotocol/ui' + +import { transparentize } from 'color2k' +import styled from 'styled-components/macro' + +export const StyledMenuButton = styled.button` + position: relative; + width: 100%; + border: none; + background-color: transparent; + margin: 0; + padding: 0; + border-radius: 0.5rem; + height: var(${UI.ICON_SIZE_NORMAL}); + opacity: 0.6; + transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; + color: inherit; + display: flex; + align-items: center; + + &:hover, + &:focus { + opacity: 1; + cursor: pointer; + outline: none; + color: currentColor; + } + + svg { + opacity: 1; + margin: auto; + transition: transform 0.3s cubic-bezier(0.65, 0.05, 0.36, 1); + color: inherit; + } +` + +export const StyledMenu = styled.div` + margin: 0; + display: flex; + justify-content: center; + align-items: center; + position: relative; + border: none; + text-align: left; + color: inherit; + + ${RowFixed} { + color: inherit; + + > div { + color: inherit; + opacity: 0.85; + } + } +` + +export const MenuFlyout = styled.span` + min-width: 20.125rem; + background: var(${UI.COLOR_PRIMARY}); + box-shadow: + 0px 0px 1px rgba(0, 0, 0, 0.01), + 0px 4px 8px rgba(0, 0, 0, 0.04), + 0px 16px 24px rgba(0, 0, 0, 0.04), + 0px 24px 32px rgba(0, 0, 0, 0.01); + border-radius: 12px; + display: flex; + flex-direction: column; + font-size: 1rem; + position: absolute; + z-index: 100; + color: inherit; + box-shadow: ${({ theme }) => theme.boxShadow2}; + border: 1px solid ${({ theme }) => transparentize(theme.white, 0.95)}; + background-color: var(${UI.COLOR_PAPER}); + color: inherit; + padding: 0; + margin: 0; + top: 36px; + right: 0; + width: 280px; + + ${Media.upToMedium()} { + min-width: 18.125rem; + } + + user-select: none; +` diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx index a262155628..206b008dc0 100644 --- a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx @@ -14,6 +14,7 @@ import { } from '@cowprotocol/common-const' import { useOnClickOutside } from '@cowprotocol/common-hooks' import { getWrappedToken, percentToBps } from '@cowprotocol/common-utils' +import { StatefulValue } from '@cowprotocol/types' import { HelpTooltip, RowBetween, RowFixed, UI } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' import { Percent } from '@uniswap/sdk-core' @@ -23,11 +24,8 @@ import { ThemeContext } from 'styled-components/macro' import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' -import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { orderExpirationTimeAnalytics, slippageToleranceAnalytics } from 'modules/analytics' -import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'modules/swap/pure/Row/RowDeadline' -import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'modules/swap/pure/Row/RowSlippageContent' import { useIsEoaEthFlow } from 'modules/trade' import { useDefaultTradeSlippage, @@ -38,6 +36,12 @@ import { useSetSlippage, } from 'modules/tradeSlippage' +import { + getNativeOrderDeadlineTooltip, + getNativeSlippageTooltip, + getNonNativeOrderDeadlineTooltip, + getNonNativeSlippageTooltip, +} from 'common/utils/tradeSettingsTooltips' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import * as styledEl from './styled' @@ -52,7 +56,11 @@ enum DeadlineError { InvalidInput = 'InvalidInput', } -export function TransactionSettings() { +interface TransactionSettingsProps { + deadlineState: StatefulValue +} + +export function TransactionSettings({ deadlineState }: TransactionSettingsProps) { const { chainId } = useWalletInfo() const theme = useContext(ThemeContext) @@ -67,7 +75,7 @@ export function TransactionSettings() { const chosenSlippageMatchesSmartSlippage = smartSlippage && new Percent(smartSlippage, 10_000).equalTo(swapSlippage) - const [deadline, setDeadline] = useUserTransactionTTL() + const [deadline, setDeadline] = deadlineState const [slippageInput, setSlippageInput] = useState('') const [slippageError, setSlippageError] = useState(false) diff --git a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx index 01fa1094ad..09bb6bd53f 100644 --- a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx +++ b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx @@ -20,7 +20,7 @@ export function useIsDarkMode(): boolean { userDarkMode, matchesDarkMode, }), - shallowEqual + shallowEqual, ) return userDarkMode === null ? matchesDarkMode : userDarkMode @@ -48,37 +48,35 @@ export function useUserLocaleManager(): [SupportedLocale | null, (newLocale: Sup (newLocale: SupportedLocale) => { dispatch(updateUserLocale({ userLocale: newLocale })) }, - [dispatch] + [dispatch], ) return [locale, setLocale] } -// TODO: mod, move to mod file export function useIsRecipientToggleVisible(): boolean { return useAppSelector((state) => state.user.recipientToggleVisible) } -// TODO: mod, move to mod file -export function useRecipientToggleManager(): [boolean, (value?: boolean) => void] { +export function useRecipientToggleManager(): [boolean, (value: boolean) => void] { const dispatch = useAppDispatch() const isVisible = useIsRecipientToggleVisible() const onChangeRecipient = useCallback( (recipient: string | null) => { dispatch(setRecipient({ recipient })) }, - [dispatch] + [dispatch], ) const toggleVisibility = useCallback( - (value?: boolean) => { - const newIsVisible = value ?? !isVisible + (value: boolean) => { + const newIsVisible = value dispatch(updateRecipientToggleVisible({ recipientToggleVisible: newIsVisible })) if (!newIsVisible) { onChangeRecipient(null) } }, - [isVisible, dispatch, onChangeRecipient] + [dispatch, onChangeRecipient], ) return [isVisible, toggleVisibility] @@ -94,7 +92,7 @@ export function useUserTransactionTTL(): [number, (slippage: number) => void] { (userDeadline: number) => { dispatch(updateUserDeadline({ userDeadline })) }, - [dispatch] + [dispatch], ) return [deadline, setUserDeadline] diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index ed566d9392..258b7ff3b5 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -32,8 +32,8 @@ import { useShouldPayGas } from '../../hooks/useShouldPayGas' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' import { NetworkCostsTooltipSuffix } from '../../pure/NetworkCostsTooltipSuffix' -import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from '../../pure/Row/RowSlippageContent' import { RowDeadline } from '../Row/RowDeadline' +import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from '../../../../common/utils/tradeSettingsTooltips' const CONFIRM_TITLE = 'Swap' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 8796901be2..42aad1083c 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -12,6 +12,7 @@ import { SettingsTab } from 'legacy/components/Settings' import { useModalIsOpen } from 'legacy/state/application/hooks' import { ApplicationModal } from 'legacy/state/application/reducer' import { Field } from 'legacy/state/types' +import { useRecipientToggleManager, useUserTransactionTTL } from 'legacy/state/user/hooks' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { EthFlowModal, EthFlowProps } from 'modules/swap/containers/EthFlow' @@ -82,6 +83,8 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const priceImpactParams = useTradePriceImpact() const tradeQuoteStateOverride = useTradeQuoteStateFromLegacy() const receiveAmountInfo = useReceiveAmountInfo() + const recipientToggleState = useRecipientToggleManager() + const deadlineState = useUserTransactionTTL() const isTradePriceUpdating = useTradePricesUpdate() @@ -251,7 +254,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { } const slots = { - settingsWidget: , + settingsWidget: , topContent, bottomContent: ( diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx index d74a91ad0d..e91c606e9d 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx @@ -1,4 +1,3 @@ -import { INPUT_OUTPUT_EXPLANATION, MINIMUM_ETH_FLOW_DEADLINE_SECONDS } from '@cowprotocol/common-const' import { Command } from '@cowprotocol/types' import { HoverTooltip, RowFixed } from '@cowprotocol/ui' @@ -9,25 +8,7 @@ import { StyledRowBetween, TextWrapper } from 'modules/swap/pure/Row/styled' import { RowStyleProps } from 'modules/swap/pure/Row/typings' import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' -export function getNativeOrderDeadlineTooltip(symbols: (string | undefined)[] | undefined) { - return ( - - {symbols?.[0] || 'Native currency (e.g ETH)'} orders require a minimum transaction expiration time threshold of{' '} - {MINIMUM_ETH_FLOW_DEADLINE_SECONDS / 60} minutes to ensure the best swapping experience. -
-
- Orders not matched after the threshold time are automatically refunded. -
- ) -} -export function getNonNativeOrderDeadlineTooltip() { - return ( - - Your swap expires and will not execute if it is pending for longer than the selected duration. - {INPUT_OUTPUT_EXPLANATION} - - ) -} +import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'common/utils/tradeSettingsTooltips' export interface RowDeadlineProps { toggleSettings: Command diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx index a3385962ef..21868b3665 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx @@ -1,4 +1,3 @@ -import { INPUT_OUTPUT_EXPLANATION, MINIMUM_ETH_FLOW_SLIPPAGE, PERCENTAGE_PRECISION } from '@cowprotocol/common-const' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' import { HoverTooltip, LinkStyledButton, RowFixed, UI } from '@cowprotocol/ui' @@ -11,6 +10,8 @@ import { StyledRowBetween, TextWrapper } from 'modules/swap/pure/Row/styled' import { RowStyleProps } from 'modules/swap/pure/Row/types' import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' +import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' + export const ClickableText = styled.button` background: none; border: none; @@ -42,29 +43,8 @@ const DefaultSlippage = styled.span` } ` -export const getNativeSlippageTooltip = (chainId: SupportedChainId, symbols: (string | undefined)[] | undefined) => ( - - When selling {symbols?.[0] || 'a native currency'}, the minimum slippage tolerance is set to{' '} - {MINIMUM_ETH_FLOW_SLIPPAGE[chainId].toSignificant(PERCENTAGE_PRECISION)}% to ensure a high likelihood of order - matching, even in volatile market conditions. -
-
- Orders on CoW Swap are always protected from MEV, so your slippage tolerance cannot be exploited. -
-) -export const getNonNativeSlippageTooltip = () => ( - - Your slippage is MEV protected: all orders are submitted with tight spread (0.1%) on-chain. -
-
- The slippage set enables a resubmission of your order in case of unfavourable price movements. -
-
- {INPUT_OUTPUT_EXPLANATION} -
-) - -const SUGGESTED_SLIPPAGE_TOOLTIP = "Based on recent volatility for the selected token pair, this is the suggested slippage for ensuring quick execution of your order." +const SUGGESTED_SLIPPAGE_TOOLTIP = + 'Based on recent volatility for the selected token pair, this is the suggested slippage for ensuring quick execution of your order.' export interface RowSlippageContentProps { chainId: SupportedChainId @@ -109,14 +89,17 @@ export function RowSlippageContent(props: RowSlippageContentProps) { // In case the user happened to set the same slippage as the suggestion, do not show the suggestion const suggestedEqualToUserSlippage = smartSlippage && smartSlippage === displaySlippage - const displayDefaultSlippage = isSlippageModified && setAutoSlippage && smartSlippage && !suggestedEqualToUserSlippage && ( - - (Suggested: {smartSlippage}) - - - - - ) + const displayDefaultSlippage = isSlippageModified && + setAutoSlippage && + smartSlippage && + !suggestedEqualToUserSlippage && ( + + (Suggested: {smartSlippage}) + + + + + ) return ( @@ -124,10 +107,18 @@ export function RowSlippageContent(props: RowSlippageContentProps) { {showSettingOnClick ? ( - + ) : ( - + )} @@ -137,11 +128,13 @@ export function RowSlippageContent(props: RowSlippageContentProps) { {showSettingOnClick ? ( - {displaySlippage}{displayDefaultSlippage} + {displaySlippage} + {displayDefaultSlippage} ) : ( - {displaySlippage}{displayDefaultSlippage} + {displaySlippage} + {displayDefaultSlippage} )} @@ -149,7 +142,11 @@ export function RowSlippageContent(props: RowSlippageContentProps) { ) } -type SlippageTextContentsProps = { isEoaEthFlow: boolean; slippageLabel?: React.ReactNode, isDynamicSlippageSet: boolean } +type SlippageTextContentsProps = { + isEoaEthFlow: boolean + slippageLabel?: React.ReactNode + isDynamicSlippageSet: boolean +} function SlippageTextContents({ isEoaEthFlow, slippageLabel, isDynamicSlippageSet }: SlippageTextContentsProps) { return ( diff --git a/libs/types/src/common.ts b/libs/types/src/common.ts index 35ec4a3fd8..50ff3e3638 100644 --- a/libs/types/src/common.ts +++ b/libs/types/src/common.ts @@ -1,5 +1,7 @@ export type Command = () => void +export type StatefulValue = [T, (value: T) => void] + /** * UI order type that is different from existing types or classes * From 39a95c3cdc4ac83cce0445c03a37f3daa19fe3cf Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 17:27:43 +0500 Subject: [PATCH 14/90] refactor: use reach modal in Settings component --- .../src/legacy/components/Settings/index.tsx | 28 ++++------ .../src/legacy/components/Settings/styled.tsx | 7 ++- .../src/legacy/state/application/hooks.ts | 4 -- .../src/legacy/state/application/reducer.ts | 4 +- .../swap/containers/Row/RowDeadline/index.tsx | 6 +-- .../swap/containers/Row/RowSlippage/index.tsx | 16 +----- .../pure/Row/RowDeadline/index.cosmos.tsx | 1 - .../swap/pure/Row/RowDeadline/index.tsx | 20 ++----- .../Row/RowSlippageContent/index.cosmos.tsx | 3 -- .../pure/Row/RowSlippageContent/index.tsx | 54 ++++--------------- 10 files changed, 29 insertions(+), 114 deletions(-) diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx b/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx index 91279bfff0..fc2ba09d9c 100644 --- a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx @@ -1,18 +1,16 @@ -import { useCallback, useRef } from 'react' +import { useCallback } from 'react' -import { useOnClickOutside } from '@cowprotocol/common-hooks' import { StatefulValue } from '@cowprotocol/types' import { HelpTooltip, RowBetween, RowFixed } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' +import { Menu } from '@reach/menu-button' import { Text } from 'rebass' import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' import { Toggle } from 'legacy/components/Toggle' import { TransactionSettings } from 'legacy/components/TransactionSettings' -import { useModalIsOpen, useToggleSettingsMenu } from 'legacy/state/application/hooks' -import { ApplicationModal } from 'legacy/state/application/reducer' import { toggleRecipientAddressAnalytics } from 'modules/analytics' import { SettingsIcon } from 'modules/trade/pure/Settings' @@ -26,10 +24,6 @@ interface SettingsTabProps { } export function SettingsTab({ className, recipientToggleState, deadlineState }: SettingsTabProps) { - const node = useRef(null) - const open = useModalIsOpen(ApplicationModal.SETTINGS) - const toggle = useToggleSettingsMenu() - const [recipientToggleVisible, toggleRecipientVisibilityAux] = recipientToggleState const toggleRecipientVisibility = useCallback( (value?: boolean) => { @@ -40,15 +34,13 @@ export function SettingsTab({ className, recipientToggleState, deadlineState }: [toggleRecipientVisibilityAux, recipientToggleVisible], ) - useOnClickOutside([node], open ? toggle : undefined) - return ( - - - - - {open && ( - + + + + + + Transaction Settings @@ -77,7 +69,7 @@ export function SettingsTab({ className, recipientToggleState, deadlineState }: - )} - + + ) } diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx b/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx index 2b1ba99ae0..0b63cc2c26 100644 --- a/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx @@ -1,9 +1,10 @@ import { Media, RowFixed, UI } from '@cowprotocol/ui' +import { MenuButton, MenuList } from '@reach/menu-button' import { transparentize } from 'color2k' import styled from 'styled-components/macro' -export const StyledMenuButton = styled.button` +export const StyledMenuButton = styled(MenuButton)` position: relative; width: 100%; border: none; @@ -54,7 +55,7 @@ export const StyledMenu = styled.div` } ` -export const MenuFlyout = styled.span` +export const MenuFlyout = styled(MenuList)` min-width: 20.125rem; background: var(${UI.COLOR_PRIMARY}); box-shadow: @@ -82,6 +83,4 @@ export const MenuFlyout = styled.span` ${Media.upToMedium()} { min-width: 18.125rem; } - - user-select: none; ` diff --git a/apps/cowswap-frontend/src/legacy/state/application/hooks.ts b/apps/cowswap-frontend/src/legacy/state/application/hooks.ts index 67b8e196a9..ca72c2b086 100644 --- a/apps/cowswap-frontend/src/legacy/state/application/hooks.ts +++ b/apps/cowswap-frontend/src/legacy/state/application/hooks.ts @@ -31,10 +31,6 @@ export function useToggleWalletModal(): Command { return useToggleModal(ApplicationModal.WALLET) } -export function useToggleSettingsMenu(): Command { - return useToggleModal(ApplicationModal.SETTINGS) -} - // TODO: These two seem to be gone from original. Check whether they have been replaced export function useOpenModal(modal: ApplicationModal): Command { const dispatch = useAppDispatch() diff --git a/apps/cowswap-frontend/src/legacy/state/application/reducer.ts b/apps/cowswap-frontend/src/legacy/state/application/reducer.ts index 6eabdf1cad..18840cb75f 100644 --- a/apps/cowswap-frontend/src/legacy/state/application/reducer.ts +++ b/apps/cowswap-frontend/src/legacy/state/application/reducer.ts @@ -4,7 +4,6 @@ import { initialState } from './initialState' export enum ApplicationModal { NETWORK_SELECTOR, - SETTINGS, WALLET, // ----------------- MOD: CowSwap specific modals -------------------- TRANSACTION_ERROR, @@ -16,7 +15,7 @@ export enum ApplicationModal { } export interface ApplicationState { - readonly openModal: ApplicationModal | null + openModal: ApplicationModal | null } const applicationSlice = createSlice({ @@ -29,5 +28,4 @@ const applicationSlice = createSlice({ }, }) -export const { setOpenModal } = applicationSlice.actions export default applicationSlice.reducer diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx index 2796864e55..7d3937b7bd 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx @@ -1,6 +1,5 @@ import { useMemo } from 'react' -import { useToggleSettingsMenu } from 'legacy/state/application/hooks' import { useUserTransactionTTL } from 'legacy/state/user/hooks' import { RowDeadlineContent } from 'modules/swap/pure/Row/RowDeadline' @@ -11,7 +10,6 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency' export function RowDeadline() { const [userDeadline] = useUserTransactionTTL() - const toggleSettings = useToggleSettingsMenu() const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() const isWrapOrUnwrap = useIsWrapOrUnwrap() @@ -24,10 +22,8 @@ export function RowDeadline() { displayDeadline, isEoaEthFlow, isWrapOrUnwrap, - toggleSettings, - showSettingOnClick: true, } - }, [isEoaEthFlow, isWrapOrUnwrap, nativeCurrency.symbol, toggleSettings, userDeadline]) + }, [isEoaEthFlow, isWrapOrUnwrap, nativeCurrency.symbol, userDeadline]) if (!isEoaEthFlow || isWrapOrUnwrap) { return null diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx index 2a47af4fef..5e552e2951 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx @@ -4,8 +4,6 @@ import { formatPercent } from '@cowprotocol/common-utils' import { useWalletInfo } from '@cowprotocol/wallet' import { Percent } from '@uniswap/sdk-core' -import { useToggleSettingsMenu } from 'legacy/state/application/hooks' - import { useIsEoaEthFlow } from 'modules/trade' import { useIsSmartSlippageApplied, useSetSlippage, useSmartTradeSlippage } from 'modules/tradeSlippage' @@ -15,21 +13,13 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency' export interface RowSlippageProps { allowedSlippage: Percent - showSettingOnClick?: boolean slippageLabel?: React.ReactNode slippageTooltip?: React.ReactNode isSlippageModified: boolean } -export function RowSlippage({ - allowedSlippage, - showSettingOnClick = true, - slippageTooltip, - slippageLabel, - isSlippageModified, -}: RowSlippageProps) { +export function RowSlippage({ allowedSlippage, slippageTooltip, slippageLabel, isSlippageModified }: RowSlippageProps) { const { chainId } = useWalletInfo() - const toggleSettings = useToggleSettingsMenu() const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() @@ -42,7 +32,6 @@ export function RowSlippage({ chainId, isEoaEthFlow, symbols: [nativeCurrency.symbol], - showSettingOnClick, allowedSlippage, slippageLabel, slippageTooltip, @@ -56,7 +45,6 @@ export function RowSlippage({ chainId, isEoaEthFlow, nativeCurrency.symbol, - showSettingOnClick, allowedSlippage, slippageLabel, slippageTooltip, @@ -65,5 +53,5 @@ export function RowSlippage({ ], ) - return + return } diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx index 06633ec398..7b353c6f7f 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx @@ -3,7 +3,6 @@ import { MINIMUM_ETH_FLOW_DEADLINE_SECONDS } from '@cowprotocol/common-const' import { RowDeadlineContent, RowDeadlineProps } from '.' const defaultProps: RowDeadlineProps = { - toggleSettings: console.log, isEoaEthFlow: true, displayDeadline: Math.floor(MINIMUM_ETH_FLOW_DEADLINE_SECONDS / 60) + ' minutes', symbols: ['ETH', 'WETH'], diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx index e91c606e9d..71075a87f0 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx @@ -1,9 +1,7 @@ -import { Command } from '@cowprotocol/types' import { HoverTooltip, RowFixed } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' -import { ClickableText } from 'modules/swap/pure/Row/RowSlippageContent' import { StyledRowBetween, TextWrapper } from 'modules/swap/pure/Row/styled' import { RowStyleProps } from 'modules/swap/pure/Row/typings' import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' @@ -11,13 +9,11 @@ import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'common/utils/tradeSettingsTooltips' export interface RowDeadlineProps { - toggleSettings: Command isEoaEthFlow: boolean symbols?: (string | undefined)[] displayDeadline: string styleProps?: RowStyleProps userDeadline: number - showSettingOnClick?: boolean slippageLabel?: React.ReactNode slippageTooltip?: React.ReactNode } @@ -25,7 +21,7 @@ export interface RowDeadlineProps { // TODO: RowDeadlineContent and RowSlippageContent are very similar. Refactor and extract base component? export function RowDeadlineContent(props: RowDeadlineProps) { - const { showSettingOnClick, toggleSettings, displayDeadline, isEoaEthFlow, symbols, styleProps } = props + const { displayDeadline, isEoaEthFlow, symbols, styleProps } = props const deadlineTooltipContent = isEoaEthFlow ? getNativeOrderDeadlineTooltip(symbols) : getNonNativeOrderDeadlineTooltip() @@ -34,24 +30,14 @@ export function RowDeadlineContent(props: RowDeadlineProps) { - {showSettingOnClick ? ( - - - - ) : ( - - )} + - {showSettingOnClick ? ( - {displayDeadline} - ) : ( - {displayDeadline} - )} + {displayDeadline} ) diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx index 40671ff0f1..f0f6ed0aae 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx @@ -10,9 +10,6 @@ const defaultProps: RowSlippageContentProps = { get displaySlippage() { return this.isEoaEthFlow ? '2%' : '0.2%' }, - toggleSettings() { - console.log('RowSlippageContent settings toggled!') - }, isSlippageModified: false, isSmartSlippageApplied: false, smartSlippage: '0.2%', diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx index 21868b3665..9e6e6db3e2 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx @@ -12,21 +12,6 @@ import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' -export const ClickableText = styled.button` - background: none; - border: none; - outline: none; - padding: 0; - margin: 0; - font-size: inherit; - font-weight: inherit; - color: inherit; - - > div { - display: inline-block; - } -` - const DefaultSlippage = styled.span` display: inline-flex; color: var(${UI.COLOR_TEXT_OPACITY_70}); @@ -48,14 +33,12 @@ const SUGGESTED_SLIPPAGE_TOOLTIP = export interface RowSlippageContentProps { chainId: SupportedChainId - toggleSettings: Command displaySlippage: string isEoaEthFlow: boolean symbols?: (string | undefined)[] wrappedSymbol?: string styleProps?: RowStyleProps allowedSlippage: Percent - showSettingOnClick?: boolean slippageLabel?: React.ReactNode slippageTooltip?: React.ReactNode isSlippageModified: boolean @@ -69,8 +52,6 @@ export interface RowSlippageContentProps { export function RowSlippageContent(props: RowSlippageContentProps) { const { chainId, - showSettingOnClick, - toggleSettings, displaySlippage, isEoaEthFlow, symbols, @@ -105,38 +86,21 @@ export function RowSlippageContent(props: RowSlippageContentProps) { - {showSettingOnClick ? ( - - - - ) : ( - - )} + - {showSettingOnClick ? ( - - {displaySlippage} - {displayDefaultSlippage} - - ) : ( - - {displaySlippage} - {displayDefaultSlippage} - - )} + + {displaySlippage} + {displayDefaultSlippage} + ) From 7c3c5597a382485838076bb2bcfe2ce80306c97b Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 17:31:43 +0500 Subject: [PATCH 15/90] refactor: move Settings component in trade module --- .../src/modules/swap/containers/SwapWidget/index.tsx | 9 +++++++-- .../trade/containers/SettingsTab}/index.tsx | 3 ++- .../trade/containers/SettingsTab}/styled.tsx | 0 .../trade/containers}/TransactionSettings/index.tsx | 0 .../trade/containers}/TransactionSettings/styled.tsx | 0 apps/cowswap-frontend/src/modules/trade/index.ts | 1 + 6 files changed, 10 insertions(+), 3 deletions(-) rename apps/cowswap-frontend/src/{legacy/components/Settings => modules/trade/containers/SettingsTab}/index.tsx (97%) rename apps/cowswap-frontend/src/{legacy/components/Settings => modules/trade/containers/SettingsTab}/styled.tsx (100%) rename apps/cowswap-frontend/src/{legacy/components => modules/trade/containers}/TransactionSettings/index.tsx (100%) rename apps/cowswap-frontend/src/{legacy/components => modules/trade/containers}/TransactionSettings/styled.tsx (100%) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 42aad1083c..e7cbe12a41 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -8,7 +8,6 @@ import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wa import { TradeType } from '@cowprotocol/widget-lib' import { NetworkAlert } from 'legacy/components/NetworkAlert/NetworkAlert' -import { SettingsTab } from 'legacy/components/Settings' import { useModalIsOpen } from 'legacy/state/application/hooks' import { ApplicationModal } from 'legacy/state/application/reducer' import { Field } from 'legacy/state/types' @@ -29,7 +28,13 @@ import { SwapWarningsTop, SwapWarningsTopProps, } from 'modules/swap/pure/warnings' -import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' +import { + SettingsTab, + TradeWidget, + TradeWidgetContainer, + useReceiveAmountInfo, + useTradePriceImpact, +} from 'modules/trade' import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/index.tsx similarity index 97% rename from apps/cowswap-frontend/src/legacy/components/Settings/index.tsx rename to apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/index.tsx index fc2ba09d9c..e21fccba20 100644 --- a/apps/cowswap-frontend/src/legacy/components/Settings/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/index.tsx @@ -10,13 +10,14 @@ import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' import { Toggle } from 'legacy/components/Toggle' -import { TransactionSettings } from 'legacy/components/TransactionSettings' import { toggleRecipientAddressAnalytics } from 'modules/analytics' import { SettingsIcon } from 'modules/trade/pure/Settings' import * as styledEl from './styled' +import { TransactionSettings } from '../TransactionSettings' + interface SettingsTabProps { className?: string recipientToggleState: StatefulValue diff --git a/apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx b/apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/styled.tsx similarity index 100% rename from apps/cowswap-frontend/src/legacy/components/Settings/styled.tsx rename to apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/styled.tsx diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/index.tsx similarity index 100% rename from apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx rename to apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/index.tsx diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/styled.tsx similarity index 100% rename from apps/cowswap-frontend/src/legacy/components/TransactionSettings/styled.tsx rename to apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/styled.tsx diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index a2c798b8a8..0691d4091b 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -4,6 +4,7 @@ export * from './containers/TradeWidgetLinks' export * from './containers/TradeFeesAndCosts' export * from './containers/TradeTotalCostsDetails' export * from './containers/TradeBasicConfirmDetails' +export { SettingsTab } from './containers/SettingsTab' export * from './pure/TradeConfirmation' export * from './hooks/useTradeConfirmActions' export * from './hooks/useTradeTypeInfo' From 57d4ad0f17b076a1c77373317b1fc058c38243ef Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 17:38:19 +0500 Subject: [PATCH 16/90] feat(yield): add settings widget --- .../yield/containers/YieldWidget/index.tsx | 11 +++---- .../modules/yield/hooks/useYieldSettings.ts | 30 +++++++++++++++++++ .../modules/yield/state/yieldSettingsAtom.ts | 12 ++++---- 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useYieldSettings.ts diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 4d744eb07c..6c4a686b4e 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -1,22 +1,23 @@ -import { useAtomValue } from 'jotai' import React from 'react' import { Field } from 'legacy/state/types' -import { TradeWidget, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' +import { SettingsTab, TradeWidget, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { useTradeFlowContext } from '../../hooks/useTradeFlowContext' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' +import { useYieldDeadlineState, useYieldRecipientToggleState, useYieldSettings } from '../../hooks/useYieldSettings' import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' -import { yieldSettingsAtom } from '../../state/yieldSettingsAtom' import { TradeButtons } from '../TradeButtons' import { YieldConfirmModal } from '../YieldConfirmModal' export function YieldWidget() { - const settingsState = useAtomValue(yieldSettingsAtom) + const settingsState = useYieldSettings() + const deadlineState = useYieldDeadlineState() + const recipientToggleState = useYieldRecipientToggleState() const { isLoading: isRateLoading } = useTradeQuote() const priceImpact = useTradePriceImpact() const { isOpen: isConfirmOpen } = useTradeConfirmState() @@ -71,7 +72,7 @@ export function YieldWidget() { } const slots = { - settingsWidget: , + settingsWidget: , bottomContent: , } diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useYieldSettings.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldSettings.ts new file mode 100644 index 0000000000..6ba8f042a7 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useYieldSettings.ts @@ -0,0 +1,30 @@ +import { useSetAtom } from 'jotai' +import { useAtomValue } from 'jotai/index' +import { useMemo } from 'react' + +import { StatefulValue } from '@cowprotocol/types' + +import { updateYieldSettingsAtom, yieldSettingsAtom } from '../state/yieldSettingsAtom' + +export function useYieldSettings() { + return useAtomValue(yieldSettingsAtom) +} + +export function useYieldDeadlineState(): StatefulValue { + const updateState = useSetAtom(updateYieldSettingsAtom) + const settings = useYieldSettings() + + return useMemo( + () => [settings.deadline, (deadline: number) => updateState({ deadline })], + [settings.deadline, updateState], + ) +} +export function useYieldRecipientToggleState(): StatefulValue { + const updateState = useSetAtom(updateYieldSettingsAtom) + const settings = useYieldSettings() + + return useMemo( + () => [settings.showRecipient, (showRecipient: boolean) => updateState({ showRecipient })], + [settings.showRecipient, updateState], + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts index d407eb9ff7..a0eb0cf8c4 100644 --- a/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts +++ b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts @@ -1,19 +1,19 @@ import { atomWithStorage } from 'jotai/utils' +import { DEFAULT_DEADLINE_FROM_NOW } from '@cowprotocol/common-const' +import { atomWithPartialUpdate } from '@cowprotocol/common-utils' import { getJotaiIsolatedStorage } from '@cowprotocol/core' export interface YieldSettingsState { readonly showRecipient: boolean - readonly partialFillsEnabled: boolean + readonly deadline: number } export const defaultYieldSettings: YieldSettingsState = { showRecipient: false, - partialFillsEnabled: true, + deadline: DEFAULT_DEADLINE_FROM_NOW, } -export const yieldSettingsAtom = atomWithStorage( - 'yieldSettingsAtom:v0', - defaultYieldSettings, - getJotaiIsolatedStorage(), +export const { atom: yieldSettingsAtom, updateAtom: updateYieldSettingsAtom } = atomWithPartialUpdate( + atomWithStorage('yieldSettingsAtom:v1', defaultYieldSettings, getJotaiIsolatedStorage()), ) From 4b8a40d4c824b2a6d3e53cf534c864e8bbec2435 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 17:40:21 +0500 Subject: [PATCH 17/90] feat(yield): use deadline value from settings --- .../src/modules/yield/containers/YieldWidget/index.tsx | 4 +--- .../src/modules/yield/hooks/useTradeFlowContext.ts | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 6c4a686b4e..9fed64d793 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -15,7 +15,7 @@ import { TradeButtons } from '../TradeButtons' import { YieldConfirmModal } from '../YieldConfirmModal' export function YieldWidget() { - const settingsState = useYieldSettings() + const { showRecipient } = useYieldSettings() const deadlineState = useYieldDeadlineState() const recipientToggleState = useYieldRecipientToggleState() const { isLoading: isRateLoading } = useTradeQuote() @@ -35,8 +35,6 @@ export function YieldWidget() { } = useYieldDerivedState() const tradeFlowContext = useTradeFlowContext() - const { showRecipient } = settingsState - const inputCurrencyInfo: CurrencyInfo = { field: Field.INPUT, label: 'Sell amount', diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts index fb0f5d250e..56a62a904c 100644 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts @@ -19,6 +19,7 @@ import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmA import { getOrderValidTo, useTradeQuote } from 'modules/tradeQuote' import { useGP2SettlementContract } from 'common/hooks/useContract' +import { useYieldSettings } from './useYieldSettings' export function useTradeFlowContext(): SwapFlowContext | null { const { chainId, account } = useWalletInfo() @@ -27,6 +28,7 @@ export function useTradeFlowContext(): SwapFlowContext | null { const isSafeWallet = useIsSafeWallet() const derivedTradeState = useDerivedTradeState() const receiveAmountInfo = useReceiveAmountInfo() + const { deadline } = useYieldSettings() const sellCurrency = derivedTradeState?.inputCurrency const inputAmount = receiveAmountInfo?.afterNetworkCosts.sellAmount @@ -112,8 +114,7 @@ export function useTradeFlowContext(): SwapFlowContext | null { feeAmount: networkFee, sellToken: sellToken as TokenWithLogo, buyToken: buyToken as TokenWithLogo, - validTo: getOrderValidTo(DEFAULT_DEADLINE_FROM_NOW, { - // TODO: bind to settings + validTo: getOrderValidTo(deadline, { validFor: tradeQuote.quoteParams.validFor, quoteValidTo: tradeQuote.response.quote.validTo, localQuoteTimestamp: tradeQuote.localQuoteTimestamp, @@ -123,7 +124,7 @@ export function useTradeFlowContext(): SwapFlowContext | null { allowsOffchainSigning, appData, class: OrderClass.MARKET, - partiallyFillable: false, // TODO: bind to settings + partiallyFillable: true, quoteId: tradeQuote.response.id, isSafeWallet, }, @@ -151,5 +152,6 @@ export function useTradeFlowContext(): SwapFlowContext | null { settlementContract, tradeConfirmActions, typedHooks, + deadline, ]) } From 2c2ee8819e9f74a6853793d964dce1e3926e4d01 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 17:47:33 +0500 Subject: [PATCH 18/90] fix(trade-quote): skip fast quote if it slower than optimal --- .../tradeQuote/hooks/useTradeQuotePolling.ts | 15 +++++++++------ .../modules/tradeQuote/state/tradeQuoteAtom.ts | 13 ++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts index c023bfd92d..e2f3d301c5 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts @@ -52,14 +52,14 @@ export function useTradeQuotePolling() { return } - const fetchQuote = (hasParamsChanged: boolean, priceQuality: PriceQuality) => { + const fetchQuote = (hasParamsChanged: boolean, priceQuality: PriceQuality, fetchStartTimestamp: number) => { updateQuoteState({ isLoading: true, hasParamsChanged }) const isOptimalQuote = priceQuality === PriceQuality.OPTIMAL const requestParams = { ...quoteParams, priceQuality } const request = isOptimalQuote ? getOptimalQuote(requestParams) : getFastQuote(requestParams) - request + return request .then((response) => { const { cancelled, data } = response @@ -73,6 +73,7 @@ export function useTradeQuotePolling() { ...(isOptimalQuote ? { isLoading: false } : null), error: null, hasParamsChanged: false, + fetchStartTimestamp, }) }) .catch((error: QuoteApiError) => { @@ -85,12 +86,14 @@ export function useTradeQuotePolling() { }) } - fetchQuote(true, PriceQuality.OPTIMAL) - if (fastQuote) fetchQuote(true, PriceQuality.FAST) + const fetchStartTimestamp = Date.now() + if (fastQuote) fetchQuote(true, PriceQuality.FAST, fetchStartTimestamp) + fetchQuote(true, PriceQuality.OPTIMAL, fetchStartTimestamp) const intervalId = setInterval(() => { - fetchQuote(false, PriceQuality.OPTIMAL) - if (fastQuote) fetchQuote(false, PriceQuality.FAST) + const fetchStartTimestamp = Date.now() + if (fastQuote) fetchQuote(false, PriceQuality.FAST, fetchStartTimestamp) + fetchQuote(false, PriceQuality.OPTIMAL, fetchStartTimestamp) }, PRICE_UPDATE_INTERVAL) return () => clearInterval(intervalId) diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteAtom.ts b/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteAtom.ts index b4e84e580e..0929541273 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeQuote/state/tradeQuoteAtom.ts @@ -1,6 +1,6 @@ import { atom } from 'jotai' -import { OrderQuoteResponse } from '@cowprotocol/cow-sdk' +import { OrderQuoteResponse, PriceQuality } from '@cowprotocol/cow-sdk' import type { LegacyFeeQuoteParams } from 'legacy/state/price/types' @@ -12,6 +12,7 @@ export interface TradeQuoteState { isLoading: boolean hasParamsChanged: boolean quoteParams: LegacyFeeQuoteParams | null + fetchStartTimestamp: number | null localQuoteTimestamp: number | null } @@ -21,6 +22,7 @@ export const DEFAULT_TRADE_QUOTE_STATE: TradeQuoteState = { isLoading: false, hasParamsChanged: false, quoteParams: null, + fetchStartTimestamp: null, localQuoteTimestamp: null, } @@ -30,6 +32,15 @@ export const updateTradeQuoteAtom = atom(null, (get, set, nextState: Partial { const prevState = get(tradeQuoteAtom) + // Don't update state if Fast quote finished after Optimal quote + if ( + prevState.fetchStartTimestamp === nextState.fetchStartTimestamp && + nextState.response && + nextState.quoteParams?.priceQuality === PriceQuality.FAST + ) { + return { ...prevState } + } + return { ...prevState, ...nextState, From 0bbec8b0f56d6897370a6e8cb24b67dc2307ab6a Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 18:09:12 +0500 Subject: [PATCH 19/90] refactor: generalise TradeRateDetails from swap module --- .../ConfirmSwapModalSetup/index.tsx | 6 +++-- .../swap/containers/Row/RowDeadline/index.tsx | 17 +++++------- .../swap/containers/SwapWidget/index.tsx | 10 ++----- .../containers/TradeRateDetails/index.tsx | 27 +++++++++---------- .../swap/pure/Row/RowDeadline/index.tsx | 2 -- .../pure/Row/RowSlippageContent/index.tsx | 2 -- 6 files changed, 26 insertions(+), 38 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 258b7ff3b5..082960f297 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -33,7 +33,8 @@ import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' import { NetworkCostsTooltipSuffix } from '../../pure/NetworkCostsTooltipSuffix' import { RowDeadline } from '../Row/RowDeadline' -import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from '../../../../common/utils/tradeSettingsTooltips' +import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' +import { useUserTransactionTTL } from 'legacy/state/user/hooks' const CONFIRM_TITLE = 'Swap' @@ -72,6 +73,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() const baseFlowContextSource = useBaseFlowContextSource() + const [userDeadline] = useUserTransactionTTL() const slippageAdjustedSellAmount = trade?.maximumAmountIn(allowedSlippage) const isExactIn = trade?.tradeType === TradeType.EXACT_INPUT @@ -132,7 +134,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { withTimelineDot={false} alwaysRow > - +
)} {restContent} diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx index 7d3937b7bd..e1bd28b89d 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx @@ -1,29 +1,26 @@ import { useMemo } from 'react' -import { useUserTransactionTTL } from 'legacy/state/user/hooks' - -import { RowDeadlineContent } from 'modules/swap/pure/Row/RowDeadline' -import { useIsEoaEthFlow } from 'modules/trade' -import { useIsWrapOrUnwrap } from 'modules/trade/hooks/useIsWrapOrUnwrap' +import { useIsEoaEthFlow, useIsWrapOrUnwrap } from 'modules/trade' import useNativeCurrency from 'lib/hooks/useNativeCurrency' -export function RowDeadline() { - const [userDeadline] = useUserTransactionTTL() +import { RowDeadlineContent } from '../../../pure/Row/RowDeadline' + +export function RowDeadline({ deadline }: { deadline: number }) { const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() const isWrapOrUnwrap = useIsWrapOrUnwrap() const props = useMemo(() => { - const displayDeadline = Math.floor(userDeadline / 60) + ' minutes' + const displayDeadline = Math.floor(deadline / 60) + ' minutes' return { - userDeadline, + userDeadline: deadline, symbols: [nativeCurrency.symbol], displayDeadline, isEoaEthFlow, isWrapOrUnwrap, } - }, [isEoaEthFlow, isWrapOrUnwrap, nativeCurrency.symbol, userDeadline]) + }, [isEoaEthFlow, isWrapOrUnwrap, nativeCurrency.symbol, deadline]) if (!isEoaEthFlow || isWrapOrUnwrap) { return null diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index e7cbe12a41..510fc3d949 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -39,7 +39,7 @@ import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' -import { useIsSlippageModified, useTradeSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' @@ -73,7 +73,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const { chainId, account } = useWalletInfo() const { slippageAdjustedSellAmount, currencies, trade } = useDerivedSwapInfo() const slippage = useTradeSlippage() - const isSlippageModified = useIsSlippageModified() const parsedAmounts = useSwapCurrenciesAmounts() const { isSupportedWallet } = useWalletDetails() const isSwapUnsupported = useIsTradeUnsupported(currencies.INPUT, currencies.OUTPUT) @@ -265,12 +264,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { bottomContent: ( <> {bottomContent} - + diff --git a/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx index 7c5aa2e111..ab1403f6b3 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx @@ -1,17 +1,18 @@ -import React, { useMemo, useState, useCallback } from 'react' +import React, { useMemo, useState, useCallback, ReactElement } from 'react' -import { CurrencyAmount, Percent } from '@uniswap/sdk-core' +import { CurrencyAmount } from '@uniswap/sdk-core' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { getTotalCosts, - ReceiveAmountInfo, TradeFeesAndCosts, TradeTotalCostsDetails, useDerivedTradeState, NetworkCostsRow, + useReceiveAmountInfo, } from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' +import { useIsSlippageModified, useTradeSlippage } from 'modules/tradeSlippage' import { useUsdAmount } from 'modules/usdAmount' import { NetworkCostsSuffix } from 'common/pure/NetworkCostsSuffix' @@ -23,19 +24,17 @@ import { RowDeadline } from '../Row/RowDeadline' import { RowSlippage } from '../Row/RowSlippage' interface TradeRateDetailsProps { - receiveAmountInfo: ReceiveAmountInfo | null + deadline: number rateInfoParams: RateInfoParams - allowedSlippage: Percent | null - isSlippageModified: boolean + children?: ReactElement } -export function TradeRateDetails({ - allowedSlippage, - receiveAmountInfo, - rateInfoParams, - isSlippageModified, -}: TradeRateDetailsProps) { +export function TradeRateDetails({ rateInfoParams, deadline }: TradeRateDetailsProps) { const [isFeeDetailsOpen, setFeeDetailsOpen] = useState(false) + + const slippage = useTradeSlippage() + const isSlippageModified = useIsSlippageModified() + const receiveAmountInfo = useReceiveAmountInfo() const derivedTradeState = useDerivedTradeState() const tradeQuote = useTradeQuote() const shouldPayGas = useShouldPayGas() @@ -90,8 +89,8 @@ export function TradeRateDetails({ networkCostsTooltipSuffix={} alwaysRow /> - {allowedSlippage && } - + {slippage && } + ) } diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx index 71075a87f0..00f78f1a78 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx @@ -18,8 +18,6 @@ export interface RowDeadlineProps { slippageTooltip?: React.ReactNode } -// TODO: RowDeadlineContent and RowSlippageContent are very similar. Refactor and extract base component? - export function RowDeadlineContent(props: RowDeadlineProps) { const { displayDeadline, isEoaEthFlow, symbols, styleProps } = props const deadlineTooltipContent = isEoaEthFlow diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx index 9e6e6db3e2..430f3030eb 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx @@ -47,8 +47,6 @@ export interface RowSlippageContentProps { isSmartSlippageApplied: boolean } -// TODO: RowDeadlineContent and RowSlippageContent are very similar. Refactor and extract base component? - export function RowSlippageContent(props: RowSlippageContentProps) { const { chainId, From fbb9a735ab770616b7b7f42e2c32e4c69c8cbb69 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 18:27:57 +0500 Subject: [PATCH 20/90] refactor: move TradeRateDetails into tradeWidgetAddons module --- .../src/common/hooks/useGetMarketDimension.ts | 1 + .../ConfirmSwapModalSetup/index.tsx | 13 ++++----- .../swap/containers/SwapWidget/index.tsx | 2 +- .../hooks/useTradeQuoteStateFromLegacy.ts | 3 +- .../src/modules/swap/pure/Row/types.ts | 5 ---- .../src/modules/swap/pure/Row/typings.ts | 4 --- .../{swap => trade}/hooks/useShouldPayGas.ts | 0 .../src/modules/trade/index.ts | 1 + .../trade/pure/ConfirmDetailsItem/index.tsx | 4 +-- .../trade/pure/ConfirmDetailsItem/styled.ts | 6 ++-- .../containers}/RowDeadline/index.tsx | 2 +- .../containers}/RowSlippage/index.tsx | 12 ++++---- .../containers/TradeRateDetails/index.tsx | 6 ++-- .../src/modules/tradeWidgetAddons/index.ts | 3 ++ .../pure/NetworkCostsTooltipSuffix.tsx | 0 .../pure/Row/RowDeadline/index.cosmos.tsx | 0 .../pure/Row/RowDeadline/index.tsx | 6 ++-- .../Row/RowSlippageContent/index.cosmos.tsx | 2 +- .../pure/Row/RowSlippageContent/index.tsx | 6 ++-- .../pure/Row/styled.ts | 29 ++++++++++++++++++- .../yield/containers/YieldWidget/index.tsx | 25 ++++++++++++---- .../yield/hooks/useTradeFlowContext.ts | 3 +- 22 files changed, 85 insertions(+), 48 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/swap/pure/Row/types.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/pure/Row/typings.ts rename apps/cowswap-frontend/src/modules/{swap => trade}/hooks/useShouldPayGas.ts (100%) rename apps/cowswap-frontend/src/modules/{swap/containers/Row => tradeWidgetAddons/containers}/RowDeadline/index.tsx (92%) rename apps/cowswap-frontend/src/modules/{swap/containers/Row => tradeWidgetAddons/containers}/RowSlippage/index.tsx (80%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/containers/TradeRateDetails/index.tsx (94%) create mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/NetworkCostsTooltipSuffix.tsx (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/Row/RowDeadline/index.cosmos.tsx (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/Row/RowDeadline/index.tsx (87%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/Row/RowSlippageContent/index.cosmos.tsx (82%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/Row/RowSlippageContent/index.tsx (94%) rename apps/cowswap-frontend/src/modules/{swap => tradeWidgetAddons}/pure/Row/styled.ts (73%) diff --git a/apps/cowswap-frontend/src/common/hooks/useGetMarketDimension.ts b/apps/cowswap-frontend/src/common/hooks/useGetMarketDimension.ts index c964124f4f..35291e9a04 100644 --- a/apps/cowswap-frontend/src/common/hooks/useGetMarketDimension.ts +++ b/apps/cowswap-frontend/src/common/hooks/useGetMarketDimension.ts @@ -9,6 +9,7 @@ const widgetTypeMap: Record = { [TradeType.LIMIT_ORDER]: 'LIMIT', // TODO: set different type for other advanced orders [TradeType.ADVANCED_ORDERS]: 'TWAP', + [TradeType.YIELD]: 'YIELD', } /**\ diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 082960f297..1108bf623f 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -8,33 +8,32 @@ import { Percent, TradeType } from '@uniswap/sdk-core' import { HighFeeWarning } from 'legacy/components/SwapWarnings' import { PriceImpact } from 'legacy/hooks/usePriceImpact' import TradeGp from 'legacy/state/swap/TradeGp' +import { useUserTransactionTTL } from 'legacy/state/user/hooks' -import { useIsSmartSlippageApplied } from 'modules/tradeSlippage' import { TradeConfirmation, TradeConfirmModal, useIsEoaEthFlow, useOrderSubmittedContent, useReceiveAmountInfo, + useShouldPayGas, useTradeConfirmActions, } from 'modules/trade' import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' +import { useIsSmartSlippageApplied } from 'modules/tradeSlippage' +import { NetworkCostsTooltipSuffix, RowDeadline } from 'modules/tradeWidgetAddons' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' import { NetworkCostsSuffix } from 'common/pure/NetworkCostsSuffix' import { RateInfoParams } from 'common/pure/RateInfo' +import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useBaseFlowContextSource } from '../../hooks/useFlowContext' - -import { useShouldPayGas } from '../../hooks/useShouldPayGas' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' -import { NetworkCostsTooltipSuffix } from '../../pure/NetworkCostsTooltipSuffix' -import { RowDeadline } from '../Row/RowDeadline' -import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' -import { useUserTransactionTTL } from 'legacy/state/user/hooks' + const CONFIRM_TITLE = 'Swap' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 510fc3d949..b87d3924c9 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -40,6 +40,7 @@ import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' import { useTradeSlippage } from 'modules/tradeSlippage' +import { TradeRateDetails } from 'modules/tradeWidgetAddons' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' @@ -59,7 +60,6 @@ import { } from '../../hooks/useSwapState' import { useTradeQuoteStateFromLegacy } from '../../hooks/useTradeQuoteStateFromLegacy' import { ConfirmSwapModalSetup } from '../ConfirmSwapModalSetup' -import { TradeRateDetails } from '../TradeRateDetails' const BUTTON_STATES_TO_SHOW_BUNDLE_APPROVAL_BANNER = [SwapButtonState.ApproveAndSwap] const BUTTON_STATES_TO_SHOW_BUNDLE_WRAP_BANNER = [SwapButtonState.WrapAndSwap] diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useTradeQuoteStateFromLegacy.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useTradeQuoteStateFromLegacy.ts index fa840f5c97..b9f88732f3 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useTradeQuoteStateFromLegacy.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useTradeQuoteStateFromLegacy.ts @@ -26,7 +26,8 @@ export function useTradeQuoteStateFromLegacy(): TradeQuoteState | null { quoteParams: quote || null, localQuoteTimestamp: quote?.localQuoteTimestamp || null, hasParamsChanged: isGettingNewQuote, + fetchStartTimestamp: null, }), - [quote, isLoading, isGettingNewQuote] + [quote, isLoading, isGettingNewQuote], ) } diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/types.ts b/apps/cowswap-frontend/src/modules/swap/pure/Row/types.ts deleted file mode 100644 index f922980cf1..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface RowStyleProps { - fontWeight?: number - fontSize?: number - alignContentRight?: boolean -} diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/typings.ts b/apps/cowswap-frontend/src/modules/swap/pure/Row/typings.ts deleted file mode 100644 index 8e5cd4480d..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/typings.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface RowStyleProps { - fontWeight?: number - fontSize?: number -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useShouldPayGas.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useShouldPayGas.ts rename to apps/cowswap-frontend/src/modules/trade/hooks/useShouldPayGas.ts diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 0691d4091b..3a75f5d8f4 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -33,6 +33,7 @@ export * from './hooks/useDerivedTradeState' export * from './hooks/useNavigateToNewOrderCallback' export * from './hooks/useOrderSubmittedContent' export * from './hooks/useIsEoaEthFlow' +export * from './hooks/useShouldPayGas' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' diff --git a/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/index.tsx index 75612ae86f..65f4bbc52c 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/index.tsx @@ -5,10 +5,10 @@ import { InfoTooltip } from '@cowprotocol/ui' import { CornerDownRight } from 'react-feather' -import { TimelineDot } from 'modules/trade/pure/Row/styled' - import { Content, Row, Wrapper, Label } from './styled' +import { TimelineDot } from '../Row/styled' + export type ConfirmDetailsItemProps = { children: ReactNode label?: ReactNode diff --git a/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/styled.ts b/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/styled.ts index a442a84135..7416efef16 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/styled.ts +++ b/apps/cowswap-frontend/src/modules/trade/pure/ConfirmDetailsItem/styled.ts @@ -2,7 +2,7 @@ import { Media, UI } from '@cowprotocol/ui' import styled, { css } from 'styled-components/macro' -import { StyledRowBetween } from 'modules/swap/pure/Row/styled' +import { StyledRowBetween } from 'modules/tradeWidgetAddons/pure/Row/styled' export const Wrapper = styled.div<{ alwaysRow: boolean }>` display: flex; @@ -83,7 +83,9 @@ export const Label = styled.span<{ labelOpacity?: boolean }>` gap: 5px; text-align: left; opacity: ${({ labelOpacity }) => (labelOpacity ? 0.7 : 1)}; - transition: color var(${UI.ANIMATION_DURATION}) ease-in-out, opacity var(${UI.ANIMATION_DURATION}) ease-in-out; + transition: + color var(${UI.ANIMATION_DURATION}) ease-in-out, + opacity var(${UI.ANIMATION_DURATION}) ease-in-out; color: inherit; &:hover { diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowDeadline/index.tsx similarity index 92% rename from apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowDeadline/index.tsx index e1bd28b89d..2bc83f665d 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowDeadline/index.tsx @@ -4,7 +4,7 @@ import { useIsEoaEthFlow, useIsWrapOrUnwrap } from 'modules/trade' import useNativeCurrency from 'lib/hooks/useNativeCurrency' -import { RowDeadlineContent } from '../../../pure/Row/RowDeadline' +import { RowDeadlineContent } from '../../pure/Row/RowDeadline' export function RowDeadline({ deadline }: { deadline: number }) { const isEoaEthFlow = useIsEoaEthFlow() diff --git a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx similarity index 80% rename from apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx index 5e552e2951..571234bde0 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/Row/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx @@ -5,12 +5,12 @@ import { useWalletInfo } from '@cowprotocol/wallet' import { Percent } from '@uniswap/sdk-core' import { useIsEoaEthFlow } from 'modules/trade' - import { useIsSmartSlippageApplied, useSetSlippage, useSmartTradeSlippage } from 'modules/tradeSlippage' -import { RowSlippageContent } from 'modules/swap/pure/Row/RowSlippageContent' import useNativeCurrency from 'lib/hooks/useNativeCurrency' +import { RowSlippageContent } from '../../pure/Row/RowSlippageContent' + export interface RowSlippageProps { allowedSlippage: Percent slippageLabel?: React.ReactNode @@ -23,7 +23,7 @@ export function RowSlippage({ allowedSlippage, slippageTooltip, slippageLabel, i const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() - const smartSwapSlippage = useSmartTradeSlippage() + const smartSlippage = useSmartTradeSlippage() const isSmartSlippageApplied = useIsSmartSlippageApplied() const setSlippage = useSetSlippage() @@ -38,8 +38,8 @@ export function RowSlippage({ allowedSlippage, slippageTooltip, slippageLabel, i displaySlippage: `${formatPercent(allowedSlippage)}%`, isSmartSlippageApplied, smartSlippage: - smartSwapSlippage && !isEoaEthFlow ? `${formatPercent(new Percent(smartSwapSlippage, 10_000))}%` : undefined, - setAutoSlippage: smartSwapSlippage && !isEoaEthFlow ? () => setSlippage(null) : undefined, + smartSlippage && !isEoaEthFlow ? `${formatPercent(new Percent(smartSlippage, 10_000))}%` : undefined, + setAutoSlippage: smartSlippage && !isEoaEthFlow ? () => setSlippage(null) : undefined, }), [ chainId, @@ -48,7 +48,7 @@ export function RowSlippage({ allowedSlippage, slippageTooltip, slippageLabel, i allowedSlippage, slippageLabel, slippageTooltip, - smartSwapSlippage, + smartSlippage, isSmartSlippageApplied, ], ) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx similarity index 94% rename from apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx index ab1403f6b3..62d38d9175 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/TradeRateDetails/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx @@ -10,6 +10,7 @@ import { useDerivedTradeState, NetworkCostsRow, useReceiveAmountInfo, + useShouldPayGas, } from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' import { useIsSlippageModified, useTradeSlippage } from 'modules/tradeSlippage' @@ -18,10 +19,9 @@ import { useUsdAmount } from 'modules/usdAmount' import { NetworkCostsSuffix } from 'common/pure/NetworkCostsSuffix' import { RateInfoParams } from 'common/pure/RateInfo' -import { useShouldPayGas } from '../../hooks/useShouldPayGas' import { NetworkCostsTooltipSuffix } from '../../pure/NetworkCostsTooltipSuffix' -import { RowDeadline } from '../Row/RowDeadline' -import { RowSlippage } from '../Row/RowSlippage' +import { RowDeadline } from '../RowDeadline' +import { RowSlippage } from '../RowSlippage' interface TradeRateDetailsProps { deadline: number diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts new file mode 100644 index 0000000000..7beeab9dda --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -0,0 +1,3 @@ +export { RowDeadline } from './containers/RowDeadline' +export { TradeRateDetails } from './containers/TradeRateDetails' +export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' diff --git a/apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/NetworkCostsTooltipSuffix.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/pure/NetworkCostsTooltipSuffix.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/NetworkCostsTooltipSuffix.tsx diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowDeadline/index.cosmos.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.cosmos.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowDeadline/index.cosmos.tsx diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowDeadline/index.tsx similarity index 87% rename from apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowDeadline/index.tsx index 00f78f1a78..2cc56caa79 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowDeadline/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowDeadline/index.tsx @@ -2,12 +2,10 @@ import { HoverTooltip, RowFixed } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' -import { StyledRowBetween, TextWrapper } from 'modules/swap/pure/Row/styled' -import { RowStyleProps } from 'modules/swap/pure/Row/typings' -import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' - import { getNativeOrderDeadlineTooltip, getNonNativeOrderDeadlineTooltip } from 'common/utils/tradeSettingsTooltips' +import { StyledRowBetween, TextWrapper, StyledInfoIcon, TransactionText, RowStyleProps } from '../styled' + export interface RowDeadlineProps { isEoaEthFlow: boolean symbols?: (string | undefined)[] diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.cosmos.tsx similarity index 82% rename from apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.cosmos.tsx index f0f6ed0aae..42e42f060e 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.cosmos.tsx @@ -1,6 +1,6 @@ import { Percent } from '@uniswap/sdk-core' -import { RowSlippageContent, RowSlippageContentProps } from 'modules/swap/pure/Row/RowSlippageContent' +import { RowSlippageContent, RowSlippageContentProps } from './index' const defaultProps: RowSlippageContentProps = { chainId: 1, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx similarity index 94% rename from apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx index 430f3030eb..ed6327c995 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx @@ -6,12 +6,10 @@ import { Percent } from '@uniswap/sdk-core' import { Trans } from '@lingui/macro' import styled from 'styled-components/macro' -import { StyledRowBetween, TextWrapper } from 'modules/swap/pure/Row/styled' -import { RowStyleProps } from 'modules/swap/pure/Row/types' -import { StyledInfoIcon, TransactionText } from 'modules/swap/pure/styled' - import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' +import { StyledRowBetween, TextWrapper, StyledInfoIcon, TransactionText, RowStyleProps } from '../styled' + const DefaultSlippage = styled.span` display: inline-flex; color: var(${UI.COLOR_TEXT_OPACITY_70}); diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/styled.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/styled.ts similarity index 73% rename from apps/cowswap-frontend/src/modules/swap/pure/Row/styled.ts rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/styled.ts index 0ace5489f6..121d164342 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/styled.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/styled.ts @@ -2,10 +2,15 @@ import { Media, UI } from '@cowprotocol/ui' import { RowBetween, RowFixed } from '@cowprotocol/ui' import { HoverTooltip } from '@cowprotocol/ui' +import { Info } from 'react-feather' import { Text } from 'rebass' import styled from 'styled-components/macro' -import { RowStyleProps } from './types' +export interface RowStyleProps { + fontWeight?: number + fontSize?: number + alignContentRight?: boolean +} const StyledHoverTooltip = styled(HoverTooltip)`` export const TextWrapper = styled(Text)<{ success?: boolean }>` @@ -58,3 +63,25 @@ export const StyledRowBetween = styled(RowBetween)` color: inherit; } ` + +export const StyledInfoIcon = styled(Info)` + color: inherit; + opacity: 0.6; + line-height: 0; + vertical-align: middle; + transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; + + &:hover { + opacity: 1; + } +` + +export const TransactionText = styled.span` + display: flex; + gap: 3px; + cursor: pointer; + + > i { + font-style: normal; + } +` diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 9fed64d793..83092fb81d 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -2,9 +2,17 @@ import React from 'react' import { Field } from 'legacy/state/types' -import { SettingsTab, TradeWidget, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' +import { + SettingsTab, + TradeWidget, + useReceiveAmountInfo, + useTradeConfirmState, + useTradePriceImpact, +} from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' +import { TradeRateDetails } from 'modules/tradeWidgetAddons' +import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { useTradeFlowContext } from '../../hooks/useTradeFlowContext' @@ -22,6 +30,8 @@ export function YieldWidget() { const priceImpact = useTradePriceImpact() const { isOpen: isConfirmOpen } = useTradeConfirmState() const widgetActions = useYieldWidgetActions() + const receiveAmountInfo = useReceiveAmountInfo() + const { inputCurrency, outputCurrency, @@ -37,7 +47,6 @@ export function YieldWidget() { const inputCurrencyInfo: CurrencyInfo = { field: Field.INPUT, - label: 'Sell amount', currency: inputCurrency, amount: inputCurrencyAmount, isIndependent: true, @@ -47,13 +56,12 @@ export function YieldWidget() { } const outputCurrencyInfo: CurrencyInfo = { field: Field.OUTPUT, - label: 'Buy exactly', currency: outputCurrency, amount: outputCurrencyAmount, isIndependent: true, balance: outputCurrencyBalance, fiatAmount: outputCurrencyFiatAmount, - receiveAmountInfo: null, + receiveAmountInfo, } const inputCurrencyPreviewInfo = { amount: inputCurrencyInfo.amount, @@ -69,9 +77,16 @@ export function YieldWidget() { label: outputCurrencyInfo.label, } + const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) + const slots = { settingsWidget: , - bottomContent: , + bottomContent: ( + <> + + + + ), } const params = { diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts index 56a62a904c..f92496f1d0 100644 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' -import { DEFAULT_DEADLINE_FROM_NOW, TokenWithLogo } from '@cowprotocol/common-const' +import { TokenWithLogo } from '@cowprotocol/common-const' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' import { UiOrderType } from '@cowprotocol/types' import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' @@ -19,6 +19,7 @@ import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmA import { getOrderValidTo, useTradeQuote } from 'modules/tradeQuote' import { useGP2SettlementContract } from 'common/hooks/useContract' + import { useYieldSettings } from './useYieldSettings' export function useTradeFlowContext(): SwapFlowContext | null { From 275893ee5bcda934f5e9b057a09133033f5d2b11 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 8 Oct 2024 18:30:04 +0500 Subject: [PATCH 21/90] refactor: move SettingsTab into tradeWidgetAddons module --- .../src/modules/swap/containers/SwapWidget/index.tsx | 10 ++-------- apps/cowswap-frontend/src/modules/trade/index.ts | 1 - .../containers/SettingsTab/index.tsx | 0 .../containers/SettingsTab/styled.tsx | 0 .../containers/TransactionSettings/index.tsx | 0 .../containers/TransactionSettings/styled.tsx | 0 .../src/modules/tradeWidgetAddons/index.ts | 1 + .../src/modules/yield/containers/YieldWidget/index.tsx | 10 ++-------- 8 files changed, 5 insertions(+), 17 deletions(-) rename apps/cowswap-frontend/src/modules/{trade => tradeWidgetAddons}/containers/SettingsTab/index.tsx (100%) rename apps/cowswap-frontend/src/modules/{trade => tradeWidgetAddons}/containers/SettingsTab/styled.tsx (100%) rename apps/cowswap-frontend/src/modules/{trade => tradeWidgetAddons}/containers/TransactionSettings/index.tsx (100%) rename apps/cowswap-frontend/src/modules/{trade => tradeWidgetAddons}/containers/TransactionSettings/styled.tsx (100%) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index b87d3924c9..417e14de89 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -28,19 +28,13 @@ import { SwapWarningsTop, SwapWarningsTopProps, } from 'modules/swap/pure/warnings' -import { - SettingsTab, - TradeWidget, - TradeWidgetContainer, - useReceiveAmountInfo, - useTradePriceImpact, -} from 'modules/trade' +import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' import { useIsEoaEthFlow } from 'modules/trade' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' import { getQuoteTimeOffset } from 'modules/tradeQuote' import { useTradeSlippage } from 'modules/tradeSlippage' -import { TradeRateDetails } from 'modules/tradeWidgetAddons' +import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 3a75f5d8f4..211111a5ca 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -4,7 +4,6 @@ export * from './containers/TradeWidgetLinks' export * from './containers/TradeFeesAndCosts' export * from './containers/TradeTotalCostsDetails' export * from './containers/TradeBasicConfirmDetails' -export { SettingsTab } from './containers/SettingsTab' export * from './pure/TradeConfirmation' export * from './hooks/useTradeConfirmActions' export * from './hooks/useTradeTypeInfo' diff --git a/apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx diff --git a/apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/styled.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/trade/containers/SettingsTab/styled.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/styled.tsx diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/styled.tsx similarity index 100% rename from apps/cowswap-frontend/src/modules/trade/containers/TransactionSettings/styled.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/styled.tsx diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts index 7beeab9dda..f241c1a786 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -1,3 +1,4 @@ export { RowDeadline } from './containers/RowDeadline' export { TradeRateDetails } from './containers/TradeRateDetails' +export { SettingsTab } from './containers/SettingsTab' export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 83092fb81d..22fa00a18f 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -2,15 +2,9 @@ import React from 'react' import { Field } from 'legacy/state/types' -import { - SettingsTab, - TradeWidget, - useReceiveAmountInfo, - useTradeConfirmState, - useTradePriceImpact, -} from 'modules/trade' +import { TradeWidget, useReceiveAmountInfo, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' -import { TradeRateDetails } from 'modules/tradeWidgetAddons' +import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' From 452d107088d4eb995b5e9c51bbedab5bbbb15037 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 13:22:30 +0500 Subject: [PATCH 22/90] refactor(swap): generalise useHighFeeWarning() --- .../legacy/components/SwapWarnings/index.tsx | 9 +-- .../ConfirmSwapModalSetup/index.tsx | 3 +- .../swap/containers/SwapWidget/index.tsx | 2 +- .../src/modules/swap/hooks/useSwapState.tsx | 57 +++++++++++++------ .../src/modules/swap/pure/warnings.tsx | 1 - 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx index fca6d9cf67..65271bd4f7 100644 --- a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx @@ -1,4 +1,3 @@ - import { Command } from '@cowprotocol/types' import { HoverTooltip } from '@cowprotocol/ui' import { Fraction } from '@uniswap/sdk-core' @@ -6,7 +5,6 @@ import { Fraction } from '@uniswap/sdk-core' import { AlertTriangle } from 'react-feather' import styled from 'styled-components/macro' -import TradeGp from 'legacy/state/swap/TradeGp' import { useIsDarkMode } from 'legacy/state/user/hooks' import { useHighFeeWarning } from 'modules/swap/hooks/useSwapState' @@ -45,7 +43,7 @@ const WarningCheckboxContainer = styled.span` const WarningContainer = styled(AuxInformationContainer).attrs((props) => ({ ...props, hideInput: true, -})) ` +}))` --warningColor: ${({ theme, level }) => level === HIGH_TIER_FEE ? theme.danger @@ -136,7 +134,6 @@ const HighFeeWarningMessage = ({ feePercentage }: { feePercentage?: Fraction }) ) export type WarningProps = { - trade?: TradeGp acceptedStatus?: boolean className?: string acceptWarningCb?: Command @@ -144,10 +141,10 @@ export type WarningProps = { } & HighFeeContainerProps export const HighFeeWarning = (props: WarningProps) => { - const { acceptedStatus, acceptWarningCb, trade } = props + const { acceptedStatus, acceptWarningCb } = props const darkMode = useIsDarkMode() - const { isHighFee, feePercentage } = useHighFeeWarning(trade) + const { isHighFee, feePercentage } = useHighFeeWarning() const level = useSafeMemo(() => _getWarningInfo(feePercentage), [feePercentage]) if (!isHighFee) return null diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 1108bf623f..3da637d727 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -34,7 +34,6 @@ import { useBaseFlowContextSource } from '../../hooks/useFlowContext' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' - const CONFIRM_TITLE = 'Swap' export interface ConfirmSwapModalSetupProps { @@ -137,7 +136,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) {
)} {restContent} - + {!priceImpact.priceImpact && } )} diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 417e14de89..54d3d52759 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -170,7 +170,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { !!priceImpactParams.priceImpact || priceImpactParams.loading - const { feeWarningAccepted, setFeeWarningAccepted } = useHighFeeWarning(trade) + const { feeWarningAccepted, setFeeWarningAccepted } = useHighFeeWarning() const { impactWarningAccepted: _impactWarningAccepted, setImpactWarningAccepted } = useUnknownImpactWarning() const impactWarningAccepted = hideUnknownImpactWarning || _impactWarningAccepted diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx index ae3eaaf7f4..7a269d2086 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' import { FEE_SIZE_THRESHOLD } from '@cowprotocol/common-const' @@ -7,7 +7,7 @@ import { useENS } from '@cowprotocol/ens' import { useAreThereTokensWithSameSymbol, useTokenBySymbolOrAddress } from '@cowprotocol/tokens' import { Command } from '@cowprotocol/types' import { useWalletInfo } from '@cowprotocol/wallet' -import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { t } from '@lingui/macro' @@ -21,13 +21,14 @@ import { isWrappingTrade } from 'legacy/state/swap/utils' import { Field } from 'legacy/state/types' import { changeSwapAmountAnalytics, switchTokensAnalytics } from 'modules/analytics' +import { useReceiveAmountInfo } from 'modules/trade' import { useNavigateOnCurrencySelection } from 'modules/trade/hooks/useNavigateOnCurrencySelection' import { useTradeNavigate } from 'modules/trade/hooks/useTradeNavigate' import { useTradeSlippage } from 'modules/tradeSlippage' import { useVolumeFee } from 'modules/volumeFee' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' -import { useSafeMemo } from 'common/hooks/useSafeMemo' +import { useSafeEffect, useSafeMemo } from 'common/hooks/useSafeMemo' export const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory @@ -114,32 +115,47 @@ export function useSwapActionHandlers(): SwapActions { * useHighFeeWarning * @description checks whether fee vs trade inputAmount = high fee warning * @description returns params related to high fee and a cb for checking/unchecking fee acceptance - * @param trade TradeGp param */ -export function useHighFeeWarning(trade?: TradeGp) { - const { INPUT, OUTPUT, independentField } = useSwapState() +export function useHighFeeWarning() { + const receiveAmountInfo = useReceiveAmountInfo() const [feeWarningAccepted, setFeeWarningAccepted] = useState(false) // mod - high fee warning disable state // only considers inputAmount vs fee (fee is in input token) const [isHighFee, feePercentage] = useMemo(() => { - if (!trade) return [false, undefined] + if (!receiveAmountInfo) return [false, undefined] - const { outputAmountWithoutFee, inputAmountAfterFees, fee, volumeFeeAmount } = trade - const isExactInput = trade.tradeType === TradeType.EXACT_INPUT - const feeAsCurrency = isExactInput ? trade.executionPrice.quote(fee.feeAsCurrency) : fee.feeAsCurrency + const { + isSell, + beforeNetworkCosts, + afterNetworkCosts, + costs: { networkFee, partnerFee }, + quotePrice, + } = receiveAmountInfo + + const outputAmountWithoutFee = isSell ? beforeNetworkCosts.buyAmount : afterNetworkCosts.buyAmount + + const inputAmountAfterFees = isSell ? beforeNetworkCosts.sellAmount : afterNetworkCosts.sellAmount + + const feeAsCurrency = isSell ? quotePrice.quote(networkFee.amountInSellCurrency) : networkFee.amountInSellCurrency + + const volumeFeeAmount = partnerFee.amount const totalFeeAmount = volumeFeeAmount ? feeAsCurrency.add(volumeFeeAmount) : feeAsCurrency - const targetAmount = isExactInput ? outputAmountWithoutFee : inputAmountAfterFees + const targetAmount = isSell ? outputAmountWithoutFee : inputAmountAfterFees const feePercentage = totalFeeAmount.divide(targetAmount).multiply(100).asFraction return [feePercentage.greaterThan(FEE_SIZE_THRESHOLD), feePercentage] - }, [trade]) + }, [receiveAmountInfo]) // reset the state when users change swap params - useEffect(() => { + useSafeEffect(() => { setFeeWarningAccepted(false) - }, [INPUT.currencyId, OUTPUT.currencyId, independentField]) + }, [ + receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, + receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, + receiveAmountInfo?.isSell, + ]) return useSafeMemo( () => ({ @@ -172,14 +188,19 @@ function _computeFeeWarningAcceptedState({ } export function useUnknownImpactWarning() { - const { INPUT, OUTPUT, independentField } = useSwapState() + const receiveAmountInfo = useReceiveAmountInfo() - const [impactWarningAccepted, setImpactWarningAccepted] = useState(false) + const state = useState(false) + const [impactWarningAccepted, setImpactWarningAccepted] = state // reset the state when users change swap params - useEffect(() => { + useSafeEffect(() => { setImpactWarningAccepted(false) - }, [INPUT.currencyId, OUTPUT.currencyId, independentField]) + }, [ + receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, + receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, + receiveAmountInfo?.isSell, + ]) return useMemo( () => ({ diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index 8ef2d09252..a9a5ab8e71 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -76,7 +76,6 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) <> {shouldZeroApprove && } setFeeWarningAccepted((state) => !state) : undefined} /> From 27407be3276deec4198c1c3ec73f35028b5ace8e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 13:27:38 +0500 Subject: [PATCH 23/90] refactor(swap): move hooks to trade module --- .../legacy/components/SwapWarnings/index.tsx | 2 +- .../swap/containers/SwapWidget/index.tsx | 18 ++- .../src/modules/swap/hooks/useSwapState.tsx | 106 +----------------- .../modules/trade/hooks/useHighFeeWarning.ts | 83 ++++++++++++++ .../trade/hooks/useUnknownImpactWarning.ts | 29 +++++ .../src/modules/trade/index.ts | 3 + 6 files changed, 126 insertions(+), 115 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useHighFeeWarning.ts create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useUnknownImpactWarning.ts diff --git a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx index 65271bd4f7..041ac4ad65 100644 --- a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx @@ -7,8 +7,8 @@ import styled from 'styled-components/macro' import { useIsDarkMode } from 'legacy/state/user/hooks' -import { useHighFeeWarning } from 'modules/swap/hooks/useSwapState' import { StyledInfoIcon } from 'modules/swap/pure/styled' +import { useHighFeeWarning } from 'modules/trade' import { useSafeMemo } from 'common/hooks/useSafeMemo' diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 54d3d52759..3bd323bba6 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -29,9 +29,13 @@ import { SwapWarningsTopProps, } from 'modules/swap/pure/warnings' import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' -import { useIsEoaEthFlow } from 'modules/trade' -import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' -import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' +import { + useIsEoaEthFlow, + useHighFeeWarning, + useTradeRouteContext, + useWrappedToken, + useUnknownImpactWarning, +} from 'modules/trade' import { getQuoteTimeOffset } from 'modules/tradeQuote' import { useTradeSlippage } from 'modules/tradeSlippage' import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' @@ -45,13 +49,7 @@ import { SWAP_QUOTE_CHECK_INTERVAL } from 'common/updaters/FeesUpdater' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useIsSwapEth } from '../../hooks/useIsSwapEth' -import { - useDerivedSwapInfo, - useHighFeeWarning, - useSwapActionHandlers, - useSwapState, - useUnknownImpactWarning, -} from '../../hooks/useSwapState' +import { useDerivedSwapInfo, useSwapActionHandlers, useSwapState } from '../../hooks/useSwapState' import { useTradeQuoteStateFromLegacy } from '../../hooks/useTradeQuoteStateFromLegacy' import { ConfirmSwapModalSetup } from '../ConfirmSwapModalSetup' diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx index 7a269d2086..a1bf6d45ac 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapState.tsx @@ -1,7 +1,6 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useMemo } from 'react' import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' -import { FEE_SIZE_THRESHOLD } from '@cowprotocol/common-const' import { formatSymbol, getIsNativeToken, isAddress, tryParseCurrencyAmount } from '@cowprotocol/common-utils' import { useENS } from '@cowprotocol/ens' import { useAreThereTokensWithSameSymbol, useTokenBySymbolOrAddress } from '@cowprotocol/tokens' @@ -21,14 +20,13 @@ import { isWrappingTrade } from 'legacy/state/swap/utils' import { Field } from 'legacy/state/types' import { changeSwapAmountAnalytics, switchTokensAnalytics } from 'modules/analytics' -import { useReceiveAmountInfo } from 'modules/trade' import { useNavigateOnCurrencySelection } from 'modules/trade/hooks/useNavigateOnCurrencySelection' import { useTradeNavigate } from 'modules/trade/hooks/useTradeNavigate' import { useTradeSlippage } from 'modules/tradeSlippage' import { useVolumeFee } from 'modules/volumeFee' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' -import { useSafeEffect, useSafeMemo } from 'common/hooks/useSafeMemo' +import { useSafeMemo } from 'common/hooks/useSafeMemo' export const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory @@ -111,106 +109,6 @@ export function useSwapActionHandlers(): SwapActions { ) } -/** - * useHighFeeWarning - * @description checks whether fee vs trade inputAmount = high fee warning - * @description returns params related to high fee and a cb for checking/unchecking fee acceptance - */ -export function useHighFeeWarning() { - const receiveAmountInfo = useReceiveAmountInfo() - - const [feeWarningAccepted, setFeeWarningAccepted] = useState(false) // mod - high fee warning disable state - - // only considers inputAmount vs fee (fee is in input token) - const [isHighFee, feePercentage] = useMemo(() => { - if (!receiveAmountInfo) return [false, undefined] - - const { - isSell, - beforeNetworkCosts, - afterNetworkCosts, - costs: { networkFee, partnerFee }, - quotePrice, - } = receiveAmountInfo - - const outputAmountWithoutFee = isSell ? beforeNetworkCosts.buyAmount : afterNetworkCosts.buyAmount - - const inputAmountAfterFees = isSell ? beforeNetworkCosts.sellAmount : afterNetworkCosts.sellAmount - - const feeAsCurrency = isSell ? quotePrice.quote(networkFee.amountInSellCurrency) : networkFee.amountInSellCurrency - - const volumeFeeAmount = partnerFee.amount - - const totalFeeAmount = volumeFeeAmount ? feeAsCurrency.add(volumeFeeAmount) : feeAsCurrency - const targetAmount = isSell ? outputAmountWithoutFee : inputAmountAfterFees - const feePercentage = totalFeeAmount.divide(targetAmount).multiply(100).asFraction - - return [feePercentage.greaterThan(FEE_SIZE_THRESHOLD), feePercentage] - }, [receiveAmountInfo]) - - // reset the state when users change swap params - useSafeEffect(() => { - setFeeWarningAccepted(false) - }, [ - receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, - receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, - receiveAmountInfo?.isSell, - ]) - - return useSafeMemo( - () => ({ - isHighFee, - feePercentage, - // we only care/check about feeWarning being accepted if the fee is actually high.. - feeWarningAccepted: _computeFeeWarningAcceptedState({ feeWarningAccepted, isHighFee }), - setFeeWarningAccepted, - }), - [isHighFee, feePercentage, feeWarningAccepted, setFeeWarningAccepted], - ) -} - -function _computeFeeWarningAcceptedState({ - feeWarningAccepted, - isHighFee, -}: { - feeWarningAccepted: boolean - isHighFee: boolean -}) { - if (feeWarningAccepted) return true - else { - // is the fee high? that's only when we care - if (isHighFee) { - return feeWarningAccepted - } else { - return true - } - } -} - -export function useUnknownImpactWarning() { - const receiveAmountInfo = useReceiveAmountInfo() - - const state = useState(false) - const [impactWarningAccepted, setImpactWarningAccepted] = state - - // reset the state when users change swap params - useSafeEffect(() => { - setImpactWarningAccepted(false) - }, [ - receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, - receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, - receiveAmountInfo?.isSell, - ]) - - return useMemo( - () => ({ - impactWarningAccepted, - setImpactWarningAccepted, - }), - [impactWarningAccepted, setImpactWarningAccepted], - ) -} - // from the current swap inputs, compute the best trade and return it. export function useDerivedSwapInfo(): DerivedSwapInfo { const { account, chainId } = useWalletInfo() diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useHighFeeWarning.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useHighFeeWarning.ts new file mode 100644 index 0000000000..0f6a0a9035 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useHighFeeWarning.ts @@ -0,0 +1,83 @@ +import { useMemo, useState } from 'react' + +import { FEE_SIZE_THRESHOLD } from '@cowprotocol/common-const' + +import { useSafeEffect, useSafeMemo } from 'common/hooks/useSafeMemo' + +import { useReceiveAmountInfo } from './useReceiveAmountInfo' + +/** + * useHighFeeWarning + * @description checks whether fee vs trade inputAmount = high fee warning + * @description returns params related to high fee and a cb for checking/unchecking fee acceptance + */ +export function useHighFeeWarning() { + const receiveAmountInfo = useReceiveAmountInfo() + + const [feeWarningAccepted, setFeeWarningAccepted] = useState(false) + + // only considers inputAmount vs fee (fee is in input token) + const [isHighFee, feePercentage] = useMemo(() => { + if (!receiveAmountInfo) return [false, undefined] + + const { + isSell, + beforeNetworkCosts, + afterNetworkCosts, + costs: { networkFee, partnerFee }, + quotePrice, + } = receiveAmountInfo + + const outputAmountWithoutFee = isSell ? beforeNetworkCosts.buyAmount : afterNetworkCosts.buyAmount + + const inputAmountAfterFees = isSell ? beforeNetworkCosts.sellAmount : afterNetworkCosts.sellAmount + + const feeAsCurrency = isSell ? quotePrice.quote(networkFee.amountInSellCurrency) : networkFee.amountInSellCurrency + + const volumeFeeAmount = partnerFee.amount + + const totalFeeAmount = volumeFeeAmount ? feeAsCurrency.add(volumeFeeAmount) : feeAsCurrency + const targetAmount = isSell ? outputAmountWithoutFee : inputAmountAfterFees + const feePercentage = totalFeeAmount.divide(targetAmount).multiply(100).asFraction + + return [feePercentage.greaterThan(FEE_SIZE_THRESHOLD), feePercentage] + }, [receiveAmountInfo]) + + // reset the state when users change swap params + useSafeEffect(() => { + setFeeWarningAccepted(false) + }, [ + receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, + receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, + receiveAmountInfo?.isSell, + ]) + + return useSafeMemo( + () => ({ + isHighFee, + feePercentage, + // we only care/check about feeWarning being accepted if the fee is actually high.. + feeWarningAccepted: _computeFeeWarningAcceptedState({ feeWarningAccepted, isHighFee }), + setFeeWarningAccepted, + }), + [isHighFee, feePercentage, feeWarningAccepted, setFeeWarningAccepted], + ) +} + +function _computeFeeWarningAcceptedState({ + feeWarningAccepted, + isHighFee, +}: { + feeWarningAccepted: boolean + isHighFee: boolean +}) { + if (feeWarningAccepted) return true + else { + // is the fee high? that's only when we care + if (isHighFee) { + return feeWarningAccepted + } else { + return true + } + } +} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useUnknownImpactWarning.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useUnknownImpactWarning.ts new file mode 100644 index 0000000000..e7665060e8 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useUnknownImpactWarning.ts @@ -0,0 +1,29 @@ +import { useMemo, useState } from 'react' + +import { useSafeEffect } from 'common/hooks/useSafeMemo' + +import { useReceiveAmountInfo } from './useReceiveAmountInfo' + +export function useUnknownImpactWarning() { + const receiveAmountInfo = useReceiveAmountInfo() + + const state = useState(false) + const [impactWarningAccepted, setImpactWarningAccepted] = state + + // reset the state when users change swap params + useSafeEffect(() => { + setImpactWarningAccepted(false) + }, [ + receiveAmountInfo?.beforeNetworkCosts.sellAmount.currency, + receiveAmountInfo?.beforeNetworkCosts.buyAmount.currency, + receiveAmountInfo?.isSell, + ]) + + return useMemo( + () => ({ + impactWarningAccepted, + setImpactWarningAccepted, + }), + [impactWarningAccepted, setImpactWarningAccepted], + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 211111a5ca..f3c65085d2 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -33,6 +33,9 @@ export * from './hooks/useNavigateToNewOrderCallback' export * from './hooks/useOrderSubmittedContent' export * from './hooks/useIsEoaEthFlow' export * from './hooks/useShouldPayGas' +export * from './hooks/useHighFeeWarning' +export * from './hooks/useWrappedToken' +export * from './hooks/useUnknownImpactWarning' export * from './containers/TradeWidget/types' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' From 1073d5f74e0b79632102a33ae6b155f1f061f75c Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 13:39:03 +0500 Subject: [PATCH 24/90] refactor: move HighFeeWarning to trade widget addons --- .../src/legacy/components/swap/styleds.tsx | 100 +----------- .../ConfirmSwapModalSetup/index.tsx | 2 +- .../src/modules/swap/pure/styled.tsx | 26 --- .../src/modules/swap/pure/warnings.tsx | 2 +- .../containers/HighFeeWarning/consts.ts | 3 + .../containers/HighFeeWarning}/index.tsx | 153 ++++-------------- .../containers/HighFeeWarning/styled.tsx | 145 +++++++++++++++++ .../src/modules/tradeWidgetAddons/index.ts | 1 + 8 files changed, 184 insertions(+), 248 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/swap/pure/styled.tsx create mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/consts.ts rename apps/cowswap-frontend/src/{legacy/components/SwapWarnings => modules/tradeWidgetAddons/containers/HighFeeWarning}/index.tsx (54%) create mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx diff --git a/apps/cowswap-frontend/src/legacy/components/swap/styleds.tsx b/apps/cowswap-frontend/src/legacy/components/swap/styleds.tsx index 9dbb80ca71..7a1ae3d4be 100644 --- a/apps/cowswap-frontend/src/legacy/components/swap/styleds.tsx +++ b/apps/cowswap-frontend/src/legacy/components/swap/styleds.tsx @@ -1,109 +1,11 @@ -import { Media, UI } from '@cowprotocol/ui' - import styled from 'styled-components/macro' -const FeeInformationTooltipWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - height: 60px; -` - export const Container = styled.div` max-width: 460px; width: 100%; ` + export const Wrapper = styled.div` position: relative; padding: 8px; ` - -// TODO: refactor these styles -export const AuxInformationContainer = styled.div<{ - margin?: string - borderColor?: string - borderWidth?: string - hideInput: boolean - disabled?: boolean - showAux?: boolean -}>` - border: 1px solid ${({ hideInput }) => (hideInput ? ' transparent' : `var(${UI.COLOR_PAPER_DARKER})`)}; - background-color: var(${UI.COLOR_PAPER}); - width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; - - :focus, - :hover { - border: 1px solid ${({ theme, hideInput }) => (hideInput ? ' transparent' : theme.background)}; - } - - ${({ theme, hideInput, disabled }) => - !disabled && - ` - :focus, - :hover { - border: 1px solid ${hideInput ? ' transparent' : theme.background}; - } - `} - - margin: ${({ margin = '0 auto' }) => margin}; - border-radius: 0 0 15px 15px; - border: 2px solid var(${UI.COLOR_PAPER_DARKER}); - - &:hover { - border: 2px solid var(${UI.COLOR_PAPER_DARKER}); - } - - ${Media.upToSmall()} { - height: auto; - flex-flow: column wrap; - justify-content: flex-end; - align-items: flex-end; - } - > ${FeeInformationTooltipWrapper} { - align-items: center; - justify-content: space-between; - margin: 0 16px; - padding: 16px 0; - font-weight: 600; - font-size: 14px; - height: auto; - - ${Media.upToSmall()} { - flex-flow: column wrap; - width: 100%; - align-items: flex-start; - margin: 0; - padding: 16px; - } - - > span { - font-size: 18px; - gap: 2px; - word-break: break-all; - text-align: right; - - ${Media.upToSmall()} { - text-align: left; - align-items: flex-start; - width: 100%; - } - } - - > span:first-child { - font-size: 14px; - display: flex; - align-items: center; - white-space: nowrap; - - ${Media.upToSmall()} { - margin: 0 0 10px; - } - } - - > span > small { - opacity: 0.75; - font-size: 13px; - font-weight: 500; - } - } -` diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 3da637d727..ba0c972b52 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -5,7 +5,7 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { Percent, TradeType } from '@uniswap/sdk-core' -import { HighFeeWarning } from 'legacy/components/SwapWarnings' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { PriceImpact } from 'legacy/hooks/usePriceImpact' import TradeGp from 'legacy/state/swap/TradeGp' import { useUserTransactionTTL } from 'legacy/state/user/hooks' diff --git a/apps/cowswap-frontend/src/modules/swap/pure/styled.tsx b/apps/cowswap-frontend/src/modules/swap/pure/styled.tsx deleted file mode 100644 index dee470f06c..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/pure/styled.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { UI } from '@cowprotocol/ui' - -import { Info } from 'react-feather' -import styled from 'styled-components/macro' - -export const StyledInfoIcon = styled(Info)` - color: inherit; - opacity: 0.6; - line-height: 0; - vertical-align: middle; - transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; - - &:hover { - opacity: 1; - } -` - -export const TransactionText = styled.span` - display: flex; - gap: 3px; - cursor: pointer; - - > i { - font-style: normal; - } -` diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index a9a5ab8e71..aaab6a5006 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -7,12 +7,12 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import styled from 'styled-components/macro' -import { HighFeeWarning } from 'legacy/components/SwapWarnings' import TradeGp from 'legacy/state/swap/TradeGp' import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning' import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/consts.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/consts.ts new file mode 100644 index 0000000000..6dc0eed2a7 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/consts.ts @@ -0,0 +1,3 @@ +export const HIGH_TIER_FEE = 30 +export const MEDIUM_TIER_FEE = 20 +export const LOW_TIER_FEE = 10 diff --git a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx similarity index 54% rename from apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx rename to apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx index 041ac4ad65..f92c43282c 100644 --- a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx @@ -3,104 +3,51 @@ import { HoverTooltip } from '@cowprotocol/ui' import { Fraction } from '@uniswap/sdk-core' import { AlertTriangle } from 'react-feather' -import styled from 'styled-components/macro' import { useIsDarkMode } from 'legacy/state/user/hooks' -import { StyledInfoIcon } from 'modules/swap/pure/styled' import { useHighFeeWarning } from 'modules/trade' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { AuxInformationContainer } from '../swap/styleds' +import { HIGH_TIER_FEE, LOW_TIER_FEE, MEDIUM_TIER_FEE } from './consts' +import { ErrorStyledInfoIcon, HighFeeContainerProps, WarningCheckboxContainer, WarningContainer } from './styled' -interface HighFeeContainerProps { - padding?: string - margin?: string - width?: string - level?: number - isDarkMode?: boolean -} - -const WarningCheckboxContainer = styled.span` - display: flex; - width: 100%; - margin: 0 auto; - font-weight: bold; - gap: 2px; - justify-content: center; - align-items: center; - border-radius: 16px; - padding: 0; - margin: 10px auto; - - > input { - cursor: pointer; - margin: 1px 4px 0 0; - } -` - -const WarningContainer = styled(AuxInformationContainer).attrs((props) => ({ - ...props, - hideInput: true, -}))` - --warningColor: ${({ theme, level }) => - level === HIGH_TIER_FEE - ? theme.danger - : level === MEDIUM_TIER_FEE - ? theme.warning - : LOW_TIER_FEE - ? theme.alert - : theme.info}; - color: inherit; - padding: ${({ padding = '16px' }) => padding}; - width: ${({ width = '100%' }) => width}; - border-radius: 16px; - border: 0; - margin: ${({ margin = '0 auto' }) => margin}; - position: relative; - z-index: 1; +export type WarningProps = { + acceptedStatus?: boolean + className?: string + acceptWarningCb?: Command + hide?: boolean +} & HighFeeContainerProps - &:hover { - border: 0; - } +export const HighFeeWarning = (props: WarningProps) => { + const { acceptedStatus, acceptWarningCb } = props + const darkMode = useIsDarkMode() - &::before { - content: ''; - display: block; - position: absolute; - top: 0; - left: 0; - border-radius: inherit; - background: var(--warningColor); - opacity: ${({ isDarkMode }) => (isDarkMode ? 0.2 : 0.15)}; - z-index: -1; - width: 100%; - height: 100%; - pointer-events: none; - } + const { isHighFee, feePercentage } = useHighFeeWarning() + const level = useSafeMemo(() => _getWarningInfo(feePercentage), [feePercentage]) - > div { - display: flex; - justify-content: center; - align-items: center; - gap: 8px; - font-size: 14px; - font-weight: 500; - text-align: center; + if (!isHighFee) return null - > svg:first-child { - stroke: var(--warningColor); - } - } -` + return ( + +
+ + Costs exceed {level}% of the swap amount!{' '} + }> + + {' '} +
-const ErrorStyledInfoIcon = styled(StyledInfoIcon)` - color: ${({ theme }) => (theme.darkMode ? '#FFCA4A' : '#564D00')}; -` -const HIGH_TIER_FEE = 30 -const MEDIUM_TIER_FEE = 20 -const LOW_TIER_FEE = 10 + {acceptWarningCb && ( + + Swap + anyway + + )} +
+ ) +} // checks fee as percentage (30% not a decimal) function _getWarningInfo(feePercentage?: Fraction) { @@ -132,39 +79,3 @@ const HighFeeWarningMessage = ({ feePercentage }: { feePercentage?: Fraction }) ) - -export type WarningProps = { - acceptedStatus?: boolean - className?: string - acceptWarningCb?: Command - hide?: boolean -} & HighFeeContainerProps - -export const HighFeeWarning = (props: WarningProps) => { - const { acceptedStatus, acceptWarningCb } = props - const darkMode = useIsDarkMode() - - const { isHighFee, feePercentage } = useHighFeeWarning() - const level = useSafeMemo(() => _getWarningInfo(feePercentage), [feePercentage]) - - if (!isHighFee) return null - - return ( - -
- - Costs exceed {level}% of the swap amount!{' '} - }> - - {' '} -
- - {acceptWarningCb && ( - - Swap - anyway - - )} -
- ) -} diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx new file mode 100644 index 0000000000..9d1f2a61bd --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx @@ -0,0 +1,145 @@ +import { Media, UI } from '@cowprotocol/ui' + +import { Info } from 'react-feather' +import styled from 'styled-components/macro' + +import { HIGH_TIER_FEE, LOW_TIER_FEE, MEDIUM_TIER_FEE } from './consts' + +export interface HighFeeContainerProps { + padding?: string + margin?: string + width?: string + level?: number + isDarkMode?: boolean +} + +// TODO: refactor these styles +export const AuxInformationContainer = styled.div<{ + margin?: string + borderColor?: string + borderWidth?: string + hideInput: boolean + disabled?: boolean + showAux?: boolean +}>` + border: 1px solid ${({ hideInput }) => (hideInput ? ' transparent' : `var(${UI.COLOR_PAPER_DARKER})`)}; + background-color: var(${UI.COLOR_PAPER}); + width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; + + :focus, + :hover { + border: 1px solid ${({ theme, hideInput }) => (hideInput ? ' transparent' : theme.background)}; + } + + ${({ theme, hideInput, disabled }) => + !disabled && + ` + :focus, + :hover { + border: 1px solid ${hideInput ? ' transparent' : theme.background}; + } + `} + + margin: ${({ margin = '0 auto' }) => margin}; + border-radius: 0 0 15px 15px; + border: 2px solid var(${UI.COLOR_PAPER_DARKER}); + + &:hover { + border: 2px solid var(${UI.COLOR_PAPER_DARKER}); + } + + ${Media.upToSmall()} { + height: auto; + flex-flow: column wrap; + justify-content: flex-end; + align-items: flex-end; + } +` + +export const WarningCheckboxContainer = styled.span` + display: flex; + width: 100%; + font-weight: bold; + gap: 2px; + justify-content: center; + align-items: center; + border-radius: 16px; + padding: 0; + margin: 10px auto; + + > input { + cursor: pointer; + margin: 1px 4px 0 0; + } +` + +export const WarningContainer = styled(AuxInformationContainer).attrs((props) => ({ + ...props, + hideInput: true, +}))` + --warningColor: ${({ theme, level }) => + level === HIGH_TIER_FEE + ? theme.danger + : level === MEDIUM_TIER_FEE + ? theme.warning + : LOW_TIER_FEE + ? theme.alert + : theme.info}; + color: inherit; + padding: ${({ padding = '16px' }) => padding}; + width: ${({ width = '100%' }) => width}; + border-radius: 16px; + border: 0; + margin: ${({ margin = '0 auto' }) => margin}; + position: relative; + z-index: 1; + + &:hover { + border: 0; + } + + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + border-radius: inherit; + background: var(--warningColor); + opacity: ${({ isDarkMode }) => (isDarkMode ? 0.2 : 0.15)}; + z-index: -1; + width: 100%; + height: 100%; + pointer-events: none; + } + + > div { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 500; + text-align: center; + + > svg:first-child { + stroke: var(--warningColor); + } + } +` + +const StyledInfoIcon = styled(Info)` + color: inherit; + opacity: 0.6; + line-height: 0; + vertical-align: middle; + transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; + + &:hover { + opacity: 1; + } +` + +export const ErrorStyledInfoIcon = styled(StyledInfoIcon)` + color: ${({ theme }) => (theme.darkMode ? '#FFCA4A' : '#564D00')}; +` diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts index f241c1a786..6ac060e90f 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -1,4 +1,5 @@ export { RowDeadline } from './containers/RowDeadline' export { TradeRateDetails } from './containers/TradeRateDetails' export { SettingsTab } from './containers/SettingsTab' +export { HighFeeWarning } from './containers/HighFeeWarning' export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' From 11243ee3337053100dbd31cf7c7ce86b29e8365c Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 13:57:32 +0500 Subject: [PATCH 25/90] refactor: make HighFeeWarning independent --- .../ConfirmSwapModalSetup/index.tsx | 4 +- .../swap/containers/SwapWidget/index.tsx | 14 ++----- .../src/modules/swap/pure/warnings.tsx | 9 +---- .../src/modules/trade/index.ts | 1 - .../hooks/useHighFeeWarning.ts | 9 +++-- .../containers/HighFeeWarning/index.tsx | 40 +++++++++++-------- .../containers/HighFeeWarning/styled.tsx | 9 ++--- .../src/modules/tradeWidgetAddons/index.ts | 1 + 8 files changed, 40 insertions(+), 47 deletions(-) rename apps/cowswap-frontend/src/modules/{trade => tradeWidgetAddons/containers/HighFeeWarning}/hooks/useHighFeeWarning.ts (90%) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index ba0c972b52..1836b4d741 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -5,7 +5,6 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { Percent, TradeType } from '@uniswap/sdk-core' -import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { PriceImpact } from 'legacy/hooks/usePriceImpact' import TradeGp from 'legacy/state/swap/TradeGp' import { useUserTransactionTTL } from 'legacy/state/user/hooks' @@ -22,6 +21,7 @@ import { import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { useIsSmartSlippageApplied } from 'modules/tradeSlippage' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { NetworkCostsTooltipSuffix, RowDeadline } from 'modules/tradeWidgetAddons' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' @@ -136,7 +136,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { )} {restContent} - + {!priceImpact.priceImpact && } )} diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 3bd323bba6..a7c542fa4d 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -29,16 +29,10 @@ import { SwapWarningsTopProps, } from 'modules/swap/pure/warnings' import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' -import { - useIsEoaEthFlow, - useHighFeeWarning, - useTradeRouteContext, - useWrappedToken, - useUnknownImpactWarning, -} from 'modules/trade' +import { useIsEoaEthFlow, useTradeRouteContext, useWrappedToken, useUnknownImpactWarning } from 'modules/trade' import { getQuoteTimeOffset } from 'modules/tradeQuote' import { useTradeSlippage } from 'modules/tradeSlippage' -import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' +import { SettingsTab, TradeRateDetails, useHighFeeWarning } from 'modules/tradeWidgetAddons' import { useTradeUsdAmounts } from 'modules/usdAmount' import { useShouldZeroApprove } from 'modules/zeroApproval' @@ -168,7 +162,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { !!priceImpactParams.priceImpact || priceImpactParams.loading - const { feeWarningAccepted, setFeeWarningAccepted } = useHighFeeWarning() + const { feeWarningAccepted } = useHighFeeWarning() const { impactWarningAccepted: _impactWarningAccepted, setImpactWarningAccepted } = useUnknownImpactWarning() const impactWarningAccepted = hideUnknownImpactWarning || _impactWarningAccepted @@ -225,7 +219,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { chainId, trade, account, - feeWarningAccepted, impactWarningAccepted, hideUnknownImpactWarning, showApprovalBundlingBanner, @@ -234,7 +227,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { showTwapSuggestionBanner, nativeCurrencySymbol, wrappedCurrencySymbol, - setFeeWarningAccepted, setImpactWarningAccepted, shouldZeroApprove, buyingFiatAmount, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index aaab6a5006..c645f0a0c3 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -22,7 +22,6 @@ export interface SwapWarningsTopProps { chainId: SupportedChainId trade: TradeGp | undefined account: string | undefined - feeWarningAccepted: boolean impactWarningAccepted: boolean hideUnknownImpactWarning: boolean showApprovalBundlingBanner: boolean @@ -35,7 +34,6 @@ export interface SwapWarningsTopProps { buyingFiatAmount: CurrencyAmount | null priceImpact: Percent | undefined tradeUrlParams: TradeUrlParams - setFeeWarningAccepted(cb: (state: boolean) => boolean): void setImpactWarningAccepted(cb: (state: boolean) => boolean): void } @@ -55,7 +53,6 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) chainId, trade, account, - feeWarningAccepted, impactWarningAccepted, hideUnknownImpactWarning, showApprovalBundlingBanner, @@ -64,7 +61,6 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) showTwapSuggestionBanner, nativeCurrencySymbol, wrappedCurrencySymbol, - setFeeWarningAccepted, setImpactWarningAccepted, shouldZeroApprove, buyingFiatAmount, @@ -75,10 +71,7 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) return ( <> {shouldZeroApprove && } - setFeeWarningAccepted((state) => !state) : undefined} - /> + {!hideUnknownImpactWarning && ( (false) + const [feeWarningAccepted, setFeeWarningAccepted] = useAtom(feeWarningAcceptedAtom) // only considers inputAmount vs fee (fee is in input token) const [isHighFee, feePercentage] = useMemo(() => { diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx index f92c43282c..a68da1afc3 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/index.tsx @@ -1,36 +1,39 @@ -import { Command } from '@cowprotocol/types' +import { useCallback } from 'react' + import { HoverTooltip } from '@cowprotocol/ui' +import { useWalletInfo } from '@cowprotocol/wallet' import { Fraction } from '@uniswap/sdk-core' import { AlertTriangle } from 'react-feather' import { useIsDarkMode } from 'legacy/state/user/hooks' -import { useHighFeeWarning } from 'modules/trade' - import { useSafeMemo } from 'common/hooks/useSafeMemo' import { HIGH_TIER_FEE, LOW_TIER_FEE, MEDIUM_TIER_FEE } from './consts' -import { ErrorStyledInfoIcon, HighFeeContainerProps, WarningCheckboxContainer, WarningContainer } from './styled' +import { useHighFeeWarning } from './hooks/useHighFeeWarning' +import { ErrorStyledInfoIcon, WarningCheckboxContainer, WarningContainer } from './styled' -export type WarningProps = { - acceptedStatus?: boolean - className?: string - acceptWarningCb?: Command - hide?: boolean -} & HighFeeContainerProps +interface HighFeeWarningProps { + readonlyMode?: boolean +} -export const HighFeeWarning = (props: WarningProps) => { - const { acceptedStatus, acceptWarningCb } = props +export function HighFeeWarning({ readonlyMode }: HighFeeWarningProps) { + const { account } = useWalletInfo() + const { feeWarningAccepted, setFeeWarningAccepted } = useHighFeeWarning() const darkMode = useIsDarkMode() + const toggleFeeWarningAccepted = useCallback(() => { + setFeeWarningAccepted((state) => !state) + }, [setFeeWarningAccepted]) + const { isHighFee, feePercentage } = useHighFeeWarning() const level = useSafeMemo(() => _getWarningInfo(feePercentage), [feePercentage]) if (!isHighFee) return null return ( - +
Costs exceed {level}% of the swap amount!{' '} @@ -39,10 +42,15 @@ export const HighFeeWarning = (props: WarningProps) => { {' '}
- {acceptWarningCb && ( + {account && !readonlyMode && ( - Swap - anyway + {' '} + Swap anyway )}
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx index 9d1f2a61bd..5d28d5ffc0 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx @@ -5,10 +5,7 @@ import styled from 'styled-components/macro' import { HIGH_TIER_FEE, LOW_TIER_FEE, MEDIUM_TIER_FEE } from './consts' -export interface HighFeeContainerProps { - padding?: string - margin?: string - width?: string +interface HighFeeContainerProps { level?: number isDarkMode?: boolean } @@ -86,8 +83,8 @@ export const WarningContainer = styled(AuxInformationContainer).attrs((props) => ? theme.alert : theme.info}; color: inherit; - padding: ${({ padding = '16px' }) => padding}; - width: ${({ width = '100%' }) => width}; + padding: 16px; + width: 100%; border-radius: 16px; border: 0; margin: ${({ margin = '0 auto' }) => margin}; diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts index 6ac060e90f..fd7a4f89af 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -2,4 +2,5 @@ export { RowDeadline } from './containers/RowDeadline' export { TradeRateDetails } from './containers/TradeRateDetails' export { SettingsTab } from './containers/SettingsTab' export { HighFeeWarning } from './containers/HighFeeWarning' +export { useHighFeeWarning } from './containers/HighFeeWarning/hooks/useHighFeeWarning' export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' From 2548e10465b2f7bfe1e28e2a857c5eb419857831 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 14:03:20 +0500 Subject: [PATCH 26/90] feat(yield): display trade warnings ZeroApprovalWarning and HighFeeWarning --- .../swap/containers/SwapWidget/index.tsx | 3 +-- .../src/modules/swap/pure/warnings.tsx | 2 -- .../containers/HighFeeWarning/styled.tsx | 3 ++- .../yield/containers/TradeButtons/index.tsx | 4 +++- .../yield/containers/Warnings/index.tsx | 20 +++++++++++++++++++ .../containers/YieldConfirmModal/index.tsx | 6 ++++-- .../yield/containers/YieldWidget/index.tsx | 2 ++ 7 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index a7c542fa4d..52e51c99a7 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -56,7 +56,7 @@ export interface SwapWidgetProps { } export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { - const { chainId, account } = useWalletInfo() + const { chainId } = useWalletInfo() const { slippageAdjustedSellAmount, currencies, trade } = useDerivedSwapInfo() const slippage = useTradeSlippage() const parsedAmounts = useSwapCurrenciesAmounts() @@ -218,7 +218,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const swapWarningsTopProps: SwapWarningsTopProps = { chainId, trade, - account, impactWarningAccepted, hideUnknownImpactWarning, showApprovalBundlingBanner, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index c645f0a0c3..aa5ade722e 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -21,7 +21,6 @@ import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner' export interface SwapWarningsTopProps { chainId: SupportedChainId trade: TradeGp | undefined - account: string | undefined impactWarningAccepted: boolean hideUnknownImpactWarning: boolean showApprovalBundlingBanner: boolean @@ -52,7 +51,6 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) const { chainId, trade, - account, impactWarningAccepted, hideUnknownImpactWarning, showApprovalBundlingBanner, diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx index 5d28d5ffc0..26ecc1985b 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx @@ -53,7 +53,7 @@ export const AuxInformationContainer = styled.div<{ } ` -export const WarningCheckboxContainer = styled.span` +export const WarningCheckboxContainer = styled.label` display: flex; width: 100%; font-weight: bold; @@ -63,6 +63,7 @@ export const WarningCheckboxContainer = styled.span` border-radius: 16px; padding: 0; margin: 10px auto; + cursor: pointer; > input { cursor: pointer; diff --git a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx index a463d0d70b..6d8de5e79d 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx @@ -2,6 +2,7 @@ import React from 'react' import { useTradeConfirmActions } from 'modules/trade' import { TradeFormButtons, useGetTradeFormValidation, useTradeFormButtonContext } from 'modules/tradeFormValidation' +import { useHighFeeWarning } from 'modules/tradeWidgetAddons' const CONFIRM_TEXT = 'Swap' @@ -12,12 +13,13 @@ interface TradeButtonsProps { export function TradeButtons({ isTradeContextReady }: TradeButtonsProps) { const primaryFormValidation = useGetTradeFormValidation() const tradeConfirmActions = useTradeConfirmActions() + const { feeWarningAccepted } = useHighFeeWarning() const confirmTrade = tradeConfirmActions.onOpen const tradeFormButtonContext = useTradeFormButtonContext(CONFIRM_TEXT, confirmTrade) - const isDisabled = !isTradeContextReady + const isDisabled = !isTradeContextReady || !feeWarningAccepted if (!tradeFormButtonContext) return null diff --git a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx new file mode 100644 index 0000000000..6b63bb4a85 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx @@ -0,0 +1,20 @@ +import React from 'react' + +import { useReceiveAmountInfo } from 'modules/trade' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' +import { useShouldZeroApprove } from 'modules/zeroApproval' + +import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' + +export function Warnings() { + const receiveAmountInfo = useReceiveAmountInfo() + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) + + return ( + <> + {shouldZeroApprove && } + + + ) +} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index c10390ee4a..3d20c5624a 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -8,14 +8,15 @@ import { useAppData } from 'modules/appData' import { swapFlow } from 'modules/swap/services/swapFlow' import type { SwapFlowContext } from 'modules/swap/services/types' import { + TradeBasicConfirmDetails, TradeConfirmation, TradeConfirmModal, + useOrderSubmittedContent, useReceiveAmountInfo, useTradeConfirmActions, - TradeBasicConfirmDetails, useTradePriceImpact, - useOrderSubmittedContent, } from 'modules/trade' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' @@ -97,6 +98,7 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { > )} {restContent} + )} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 22fa00a18f..6e6d64fca0 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -14,6 +14,7 @@ import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' import { useYieldDeadlineState, useYieldRecipientToggleState, useYieldSettings } from '../../hooks/useYieldSettings' import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' import { TradeButtons } from '../TradeButtons' +import { Warnings } from '../Warnings' import { YieldConfirmModal } from '../YieldConfirmModal' export function YieldWidget() { @@ -78,6 +79,7 @@ export function YieldWidget() { bottomContent: ( <> + ), From f9e3d82d3d078d8561ea695abee1dfa8c5f137cd Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 15:41:25 +0500 Subject: [PATCH 27/90] refactor(trade): generalise NoImpactWarning --- .../containers/AdvancedOrdersWidget/index.tsx | 11 ++- .../containers/LimitOrdersWarnings/index.tsx | 28 +------ .../containers/LimitOrdersWidget/index.tsx | 34 +++++---- .../hooks/useLimitOrdersWarningsAccepted.ts | 17 ++--- .../state/limitOrdersWarningsAtom.ts | 2 - .../ConfirmSwapModalSetup/index.tsx | 2 - .../swap/containers/SwapWidget/index.tsx | 56 +++++++------- .../src/modules/swap/pure/warnings.tsx | 19 ----- .../containers/NoImpactWarning/index.tsx | 73 +++++++++++++++++++ .../TradeWidget/TradeWidgetForm.tsx | 23 +++++- .../trade/containers/TradeWidget/types.ts | 3 +- .../src/modules/trade/index.ts | 1 + .../trade/pure/NoImpactWarning/index.tsx | 40 ---------- .../trade/pure/TradeConfirmation/index.tsx | 11 ++- .../containers/TwapConfirmModal/index.tsx | 67 +++++++++-------- .../containers/TwapFormWarnings/index.tsx | 17 +---- .../twap/containers/TwapFormWidget/index.tsx | 13 +++- .../twap/hooks/useAreWarningsAccepted.ts | 11 ++- .../twap/hooks/useTwapWarningsContext.ts | 3 - .../twap/state/twapOrdersSettingsAtom.ts | 4 +- .../yield/containers/TradeButtons/index.tsx | 5 +- .../yield/containers/YieldWidget/index.tsx | 27 ++++--- .../src/pages/AdvancedOrders/index.tsx | 8 +- 23 files changed, 245 insertions(+), 230 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx delete mode 100644 apps/cowswap-frontend/src/modules/trade/pure/NoImpactWarning/index.tsx diff --git a/apps/cowswap-frontend/src/modules/advancedOrders/containers/AdvancedOrdersWidget/index.tsx b/apps/cowswap-frontend/src/modules/advancedOrders/containers/AdvancedOrdersWidget/index.tsx index 83b4bb4d89..19f1c6c246 100644 --- a/apps/cowswap-frontend/src/modules/advancedOrders/containers/AdvancedOrdersWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/advancedOrders/containers/AdvancedOrdersWidget/index.tsx @@ -1,5 +1,5 @@ import { useAtomValue } from 'jotai' -import { PropsWithChildren, ReactNode } from 'react' +import { ReactNode } from 'react' import { isSellOrder } from '@cowprotocol/common-utils' @@ -38,12 +38,13 @@ export type AdvancedOrdersWidgetParams = { disablePriceImpact: boolean } -export type AdvancedOrdersWidgetProps = PropsWithChildren<{ +export type AdvancedOrdersWidgetProps = { updaters?: ReactNode params: AdvancedOrdersWidgetParams mapCurrencyInfo?: (info: CurrencyInfo) => CurrencyInfo confirmContent: JSX.Element -}> + children(warnings: ReactNode): ReactNode +} export function AdvancedOrdersWidget({ children, @@ -98,7 +99,9 @@ export function AdvancedOrdersWidget({ const slots: TradeWidgetSlots = { settingsWidget: , - bottomContent: children, + bottomContent(warnings) { + return children(warnings) + }, updaters, lockScreen: isUnlocked ? undefined : ( { - updateLimitOrdersWarnings({ isPriceImpactAccepted: !showPriceImpactWarning }) - }, [showPriceImpactWarning, updateLimitOrdersWarnings]) - // Reset rate impact before opening confirmation screen useEffect(() => { if (isConfirmScreen) { updateLimitOrdersWarnings({ isRateImpactAccepted: false }) } }, [updateLimitOrdersWarnings, isConfirmScreen]) - const onAcceptPriceImpact = useCallback(() => { - updateLimitOrdersWarnings({ isPriceImpactAccepted: !isPriceImpactAccepted }) - }, [updateLimitOrdersWarnings, isPriceImpactAccepted]) const onAcceptRateImpact = useCallback( (value: boolean) => { @@ -129,13 +112,6 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { return isVisible ? ( {showZeroApprovalWarning && } - {showPriceImpactWarning && ( - - )} {showRateImpactWarning && ( { console.debug('RENDER LIMIT ORDERS WIDGET', { inputCurrencyInfo, outputCurrencyInfo }) - const slots = { + const slots: TradeWidgetSlots = { settingsWidget: , lockScreen: isUnlocked ? undefined : ( { ), - bottomContent: ( - <> - - - - - - - - - - - ), + bottomContent(warnings) { + return ( + <> + + + + + + {warnings} + + + + + + ) + }, outerContent: <>{isUnlocked && }, } @@ -201,6 +204,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => { priceImpact, disablePriceImpact: localFormValidation === LimitOrdersFormState.FeeExceedsFrom, disableQuotePolling: isConfirmOpen, + hideTradeWarnings: !!localFormValidation, } return ( diff --git a/apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersWarningsAccepted.ts b/apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersWarningsAccepted.ts index 70b7df391e..9270330b3e 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersWarningsAccepted.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersWarningsAccepted.ts @@ -1,16 +1,15 @@ import { useAtomValue } from 'jotai' -import { useMemo } from 'react' import { limitOrdersWarningsAtom } from 'modules/limitOrders/state/limitOrdersWarningsAtom' +import { useIsNoImpactWarningAccepted } from 'modules/trade' export function useLimitOrdersWarningsAccepted(isConfirmScreen: boolean): boolean { - const { isPriceImpactAccepted, isRateImpactAccepted } = useAtomValue(limitOrdersWarningsAtom) + const { isRateImpactAccepted } = useAtomValue(limitOrdersWarningsAtom) + const isPriceImpactAccepted = useIsNoImpactWarningAccepted() - return useMemo(() => { - if (isConfirmScreen) { - return isRateImpactAccepted - } else { - return isPriceImpactAccepted - } - }, [isConfirmScreen, isPriceImpactAccepted, isRateImpactAccepted]) + if (isConfirmScreen) { + return isRateImpactAccepted + } else { + return isPriceImpactAccepted + } } diff --git a/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersWarningsAtom.ts b/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersWarningsAtom.ts index 61a4ca8fff..51295d045e 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersWarningsAtom.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersWarningsAtom.ts @@ -2,12 +2,10 @@ import { atom } from 'jotai' interface LimitOrdersWarnings { isRateImpactAccepted: boolean - isPriceImpactAccepted: boolean } export const limitOrdersWarningsAtom = atom({ isRateImpactAccepted: false, - isPriceImpactAccepted: false, }) export const updateLimitOrdersWarningsAtom = atom(null, (get, set, nextState: Partial) => { diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 1836b4d741..8a5460049b 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -19,7 +19,6 @@ import { useTradeConfirmActions, } from 'modules/trade' import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' -import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { useIsSmartSlippageApplied } from 'modules/tradeSlippage' import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { NetworkCostsTooltipSuffix, RowDeadline } from 'modules/tradeWidgetAddons' @@ -137,7 +136,6 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { )} {restContent} - {!priceImpact.priceImpact && } )} diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 52e51c99a7..a6fc76d3de 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -2,7 +2,6 @@ import { ReactNode, useCallback, useMemo, useState } from 'react' import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' -import { isFractionFalsy } from '@cowprotocol/common-utils' import { useIsTradeUnsupported } from '@cowprotocol/tokens' import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { TradeType } from '@cowprotocol/widget-lib' @@ -28,8 +27,20 @@ import { SwapWarningsTop, SwapWarningsTopProps, } from 'modules/swap/pure/warnings' -import { TradeWidget, TradeWidgetContainer, useReceiveAmountInfo, useTradePriceImpact } from 'modules/trade' -import { useIsEoaEthFlow, useTradeRouteContext, useWrappedToken, useUnknownImpactWarning } from 'modules/trade' +import { + TradeWidget, + TradeWidgetContainer, + TradeWidgetSlots, + useReceiveAmountInfo, + useTradePriceImpact, +} from 'modules/trade' +import { + useIsEoaEthFlow, + useTradeRouteContext, + useWrappedToken, + useUnknownImpactWarning, + useIsNoImpactWarningAccepted, +} from 'modules/trade' import { getQuoteTimeOffset } from 'modules/tradeQuote' import { useTradeSlippage } from 'modules/tradeSlippage' import { SettingsTab, TradeRateDetails, useHighFeeWarning } from 'modules/tradeWidgetAddons' @@ -154,17 +165,10 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const [showNativeWrapModal, setOpenNativeWrapModal] = useState(false) const showCowSubsidyModal = useModalIsOpen(ApplicationModal.COW_SUBSIDY) - // Hide the price impact warning when there is priceImpact value or when it's loading - // The loading values is debounced in useFiatValuePriceImpact() to avoid flickering - const hideUnknownImpactWarning = - isFractionFalsy(parsedAmounts.INPUT) || - isFractionFalsy(parsedAmounts.OUTPUT) || - !!priceImpactParams.priceImpact || - priceImpactParams.loading - const { feeWarningAccepted } = useHighFeeWarning() - const { impactWarningAccepted: _impactWarningAccepted, setImpactWarningAccepted } = useUnknownImpactWarning() - const impactWarningAccepted = hideUnknownImpactWarning || _impactWarningAccepted + const noImpactWarningAccepted = useIsNoImpactWarningAccepted() + const { impactWarningAccepted: unknownImpactWarning } = useUnknownImpactWarning() + const impactWarningAccepted = noImpactWarningAccepted || unknownImpactWarning const openNativeWrapModal = useCallback(() => setOpenNativeWrapModal(true), []) const dismissNativeWrapModal = useCallback(() => setOpenNativeWrapModal(false), []) @@ -218,15 +222,12 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const swapWarningsTopProps: SwapWarningsTopProps = { chainId, trade, - impactWarningAccepted, - hideUnknownImpactWarning, showApprovalBundlingBanner, showWrapBundlingBanner, showSafeWcBundlingBanner, showTwapSuggestionBanner, nativeCurrencySymbol, wrappedCurrencySymbol, - setImpactWarningAccepted, shouldZeroApprove, buyingFiatAmount, priceImpact: priceImpactParams.priceImpact, @@ -240,19 +241,22 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { currencyOut: currencies.OUTPUT || undefined, } - const slots = { + const slots: TradeWidgetSlots = { settingsWidget: , topContent, - bottomContent: ( - <> - {bottomContent} - - - - - - ), + bottomContent(warnings) { + return ( + <> + {bottomContent} + + + {warnings} + + + + ) + }, } const params = { diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index aa5ade722e..a766ad50dd 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -5,12 +5,9 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { BundleTxApprovalBanner, BundleTxSafeWcBanner, BundleTxWrapBanner } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' -import styled from 'styled-components/macro' - import TradeGp from 'legacy/state/swap/TradeGp' import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning' -import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' import { HighFeeWarning } from 'modules/tradeWidgetAddons' @@ -21,8 +18,6 @@ import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner' export interface SwapWarningsTopProps { chainId: SupportedChainId trade: TradeGp | undefined - impactWarningAccepted: boolean - hideUnknownImpactWarning: boolean showApprovalBundlingBanner: boolean showWrapBundlingBanner: boolean shouldZeroApprove: boolean @@ -33,7 +28,6 @@ export interface SwapWarningsTopProps { buyingFiatAmount: CurrencyAmount | null priceImpact: Percent | undefined tradeUrlParams: TradeUrlParams - setImpactWarningAccepted(cb: (state: boolean) => boolean): void } export interface SwapWarningsBottomProps { @@ -43,23 +37,16 @@ export interface SwapWarningsBottomProps { currencyOut: Currency | undefined } -const StyledNoImpactWarning = styled(NoImpactWarning)` - margin-bottom: 15px; -` - export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) { const { chainId, trade, - impactWarningAccepted, - hideUnknownImpactWarning, showApprovalBundlingBanner, showWrapBundlingBanner, showSafeWcBundlingBanner, showTwapSuggestionBanner, nativeCurrencySymbol, wrappedCurrencySymbol, - setImpactWarningAccepted, shouldZeroApprove, buyingFiatAmount, priceImpact, @@ -70,12 +57,6 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) <> {shouldZeroApprove && } - {!hideUnknownImpactWarning && ( - setImpactWarningAccepted((state) => !state)} - /> - )} {showApprovalBundlingBanner && } {showWrapBundlingBanner && ( diff --git a/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx new file mode 100644 index 0000000000..4bf760ae32 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx @@ -0,0 +1,73 @@ +import { atom, useAtom, useAtomValue } from 'jotai' +import { useEffect } from 'react' + +import { useWalletInfo } from '@cowprotocol/wallet' + +import { useTradePriceImpact } from 'modules/trade' +import { TradeWarning, TradeWarningType } from 'modules/trade/pure/TradeWarning' +import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFormValidation' +import { useTradeQuote } from 'modules/tradeQuote' + +const noImpactWarningAcceptedAtom = atom(false) + +const NoImpactWarningMessage = ( +
+ + We are unable to calculate the price impact for this order. +
+
+ You may still move forward but{' '} + please review carefully that the receive amounts are what you expect. +
+
+) + +export function useIsNoImpactWarningAccepted() { + return useAtomValue(noImpactWarningAcceptedAtom) +} + +export interface NoImpactWarningProps { + withoutAccepting?: boolean + className?: string +} + +export function NoImpactWarning(props: NoImpactWarningProps) { + const { withoutAccepting, className } = props + + const [isAccepted, setIsAccepted] = useAtom(noImpactWarningAcceptedAtom) + + const { account } = useWalletInfo() + const priceImpactParams = useTradePriceImpact() + const primaryFormValidation = useGetTradeFormValidation() + const tradeQuote = useTradeQuote() + + const canTrade = + (primaryFormValidation === null || primaryFormValidation === TradeFormValidation.ApproveAndSwap) && + !tradeQuote.error + + const showPriceImpactWarning = canTrade && !!account && !priceImpactParams.loading && !priceImpactParams.priceImpact + + const acceptCallback = () => setIsAccepted((state) => !state) + + useEffect(() => { + setIsAccepted(!showPriceImpactWarning) + }, [showPriceImpactWarning]) + + if (!showPriceImpactWarning) return null + + return ( + + Price impact unknown - trade carefully + + } + /> + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index a3c22f9e09..4561c1499e 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -35,6 +35,7 @@ import { useTradeStateFromUrl } from '../../hooks/setupTradeState/useTradeStateF import { useIsWrapOrUnwrap } from '../../hooks/useIsWrapOrUnwrap' import { useTradeTypeInfo } from '../../hooks/useTradeTypeInfo' import { TradeType } from '../../types' +import { NoImpactWarning } from '../NoImpactWarning' import { TradeWidgetLinks } from '../TradeWidgetLinks' import { WrapFlowActionButton } from '../WrapFlowActionButton' @@ -59,7 +60,15 @@ export function TradeWidgetForm(props: TradeWidgetProps) { const { settingsWidget, lockScreen, topContent, middleContent, bottomContent, outerContent } = slots const { onCurrencySelection, onUserInput, onSwitchTokens, onChangeRecipient } = actions - const { compactView, showRecipient, isTradePriceUpdating, isEoaEthFlow = false, priceImpact, recipient } = params + const { + compactView, + showRecipient, + isTradePriceUpdating, + isEoaEthFlow = false, + priceImpact, + recipient, + hideTradeWarnings, + } = params const inputCurrencyInfo = useMemo( () => (isWrapOrUnwrap ? { ...props.inputCurrencyInfo, receiveAmountInfo: null } : props.inputCurrencyInfo), @@ -220,7 +229,17 @@ export function TradeWidgetForm(props: TradeWidgetProps) { {withRecipient && } - {isWrapOrUnwrap ? : bottomContent} + {isWrapOrUnwrap ? ( + + ) : ( + bottomContent?.( + hideTradeWarnings ? null : ( + <> + + + ), + ) + )} )} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts index 396db9923c..6aa00537ca 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts @@ -26,6 +26,7 @@ interface TradeWidgetParams { disableQuotePolling?: boolean disableNativeSelling?: boolean disablePriceImpact?: boolean + hideTradeWarnings?: boolean } export interface TradeWidgetSlots { @@ -33,7 +34,7 @@ export interface TradeWidgetSlots { lockScreen?: ReactNode topContent?: ReactNode middleContent?: ReactNode - bottomContent?: ReactNode + bottomContent?(warnings: ReactNode | null): ReactNode outerContent?: ReactNode updaters?: ReactNode } diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 6766a50fed..628a6ebe5e 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -36,6 +36,7 @@ export * from './hooks/useShouldPayGas' export * from './hooks/useWrappedToken' export * from './hooks/useUnknownImpactWarning' export * from './containers/TradeWidget/types' +export { useIsNoImpactWarningAccepted } from './containers/NoImpactWarning/index' export * from './utils/getReceiveAmountInfo' export * from './utils/parameterizeTradeRoute' export * from './state/receiveAmountInfoAtom' diff --git a/apps/cowswap-frontend/src/modules/trade/pure/NoImpactWarning/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/NoImpactWarning/index.tsx deleted file mode 100644 index 4b339bb851..0000000000 --- a/apps/cowswap-frontend/src/modules/trade/pure/NoImpactWarning/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { TradeWarning, TradeWarningType } from 'modules/trade/pure/TradeWarning' - -const NoImpactWarningMessage = ( -
- - We are unable to calculate the price impact for this order. -
-
- You may still move forward but{' '} - please review carefully that the receive amounts are what you expect. -
-
-) - -export interface NoImpactWarningProps { - isAccepted: boolean - withoutAccepting?: boolean - className?: string - acceptCallback?(): void -} - -export function NoImpactWarning(props: NoImpactWarningProps) { - const { acceptCallback, isAccepted, withoutAccepting, className } = props - - return ( - - Price impact unknown - trade carefully - - } - /> - ) -} diff --git a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx index 31cd765195..d8b6e7ab8b 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx @@ -25,6 +25,7 @@ import { QuoteCountdown } from './CountDown' import { useIsPriceChanged } from './hooks/useIsPriceChanged' import * as styledEl from './styled' +import { NoImpactWarning } from '../../containers/NoImpactWarning' import { useTradeConfirmState } from '../../hooks/useTradeConfirmState' import { PriceUpdatedBanner } from '../PriceUpdatedBanner' @@ -47,7 +48,7 @@ export interface TradeConfirmationProps { isPriceStatic?: boolean recipient?: string | null buttonText?: React.ReactNode - children?: ReactElement | ((restContent: ReactElement) => ReactElement) + children?: (restContent: ReactElement) => ReactElement } export function TradeConfirmation(props: TradeConfirmationProps) { @@ -151,13 +152,11 @@ export function TradeConfirmation(props: TradeConfirmationProps) { priceImpactParams={priceImpact} /> - {typeof children === 'function' ? ( - children(hookDetailsElement) - ) : ( + {children?.( <> - {children} {hookDetailsElement} - + + , )} {showRecipientWarning && } diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx index ed7946c3c5..ad08864297 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapConfirmModal/index.tsx @@ -6,7 +6,6 @@ import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders' import { TradeConfirmation, TradeConfirmModal, useTradeConfirmActions, useTradePriceImpact } from 'modules/trade' import { TradeBasicConfirmDetails } from 'modules/trade/containers/TradeBasicConfirmDetails' -import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { DividerHorizontal } from 'modules/trade/pure/Row/styled' import { PRICE_UPDATE_INTERVAL } from 'modules/tradeQuote/hooks/useTradeQuotePolling' @@ -19,7 +18,6 @@ import { useCreateTwapOrder } from '../../hooks/useCreateTwapOrder' import { useIsFallbackHandlerRequired } from '../../hooks/useFallbackHandlerVerification' import { useTwapFormState } from '../../hooks/useTwapFormState' import { useTwapSlippage } from '../../hooks/useTwapSlippage' -import { useTwapWarningsContext } from '../../hooks/useTwapWarningsContext' import { scaledReceiveAmountInfoAtom } from '../../state/scaledReceiveAmountInfoAtom' import { twapOrderAtom } from '../../state/twapOrderAtom' import { TwapFormWarnings } from '../TwapFormWarnings' @@ -69,7 +67,6 @@ export function TwapConfirmModal() { const twapOrder = useAtomValue(twapOrderAtom) const receiveAmountInfo = useAtomValue(scaledReceiveAmountInfoAtom) const slippage = useTwapSlippage() - const { showPriceImpactWarning } = useTwapWarningsContext() const localFormValidation = useTwapFormState() const tradeConfirmActions = useTradeConfirmActions() const createTwapOrder = useCreateTwapOrder() @@ -116,38 +113,40 @@ export function TwapConfirmModal() { refreshInterval={PRICE_UPDATE_INTERVAL} recipient={recipient} > - <> - {receiveAmountInfo && numOfParts && ( - : null, - networkCostsTooltipSuffix: !allowsOffchainSigning ? ( - <> -
-
- Because you are using a smart contract wallet, you will pay a separate gas cost for signing the - order placement on-chain. - - ) : null, - }} + {(warnings) => ( + <> + {receiveAmountInfo && numOfParts && ( + : null, + networkCostsTooltipSuffix: !allowsOffchainSigning ? ( + <> +
+
+ Because you are using a smart contract wallet, you will pay a separate gas cost for signing the + order placement on-chain. + + ) : null, + }} + /> + )} + + - )} - - - {showPriceImpactWarning && } - - + {warnings} + + + )} ) diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx index 07fd7f1b14..5fe047a018 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx @@ -8,7 +8,6 @@ import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders' import { modifySafeHandlerAnalytics } from 'modules/analytics' import { SellNativeWarningBanner } from 'modules/trade/containers/SellNativeWarningBanner' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' -import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { useGetTradeFormValidation } from 'modules/tradeFormValidation' import { TradeFormValidation } from 'modules/tradeFormValidation/types' import { useTradeQuoteFeeFiatAmount } from 'modules/tradeQuote' @@ -43,7 +42,7 @@ interface TwapFormWarningsProps { } export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: TwapFormWarningsProps) { - const { isFallbackHandlerSetupAccepted, isPriceImpactAccepted } = useAtomValue(twapOrdersSettingsAtom) + const { isFallbackHandlerSetupAccepted } = useAtomValue(twapOrdersSettingsAtom) const updateTwapOrdersSettings = useSetAtom(updateTwapOrdersSettingsAtom) const twapOrder = useAtomValue(twapOrderAtom) const slippage = useTwapSlippage() @@ -56,7 +55,7 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T const isFallbackHandlerRequired = useIsFallbackHandlerRequired() const isSafeViaWc = useIsSafeViaWc() const tradeQuoteFeeFiatAmount = useTradeQuoteFeeFiatAmount() - const { canTrade, showPriceImpactWarning, walletIsNotConnected } = useTwapWarningsContext() + const { canTrade, walletIsNotConnected } = useTwapWarningsContext() const tradeUrlParams = useTradeRouteContext() const toggleFallbackHandlerSetupFlag = useCallback( @@ -74,10 +73,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T const showTradeFormWarnings = !isConfirmationModal && canTrade const showFallbackHandlerWarning = showTradeFormWarnings && isFallbackHandlerRequired - const setIsPriceImpactAccepted = useCallback(() => { - updateTwapOrdersSettings({ isPriceImpactAccepted: !isPriceImpactAccepted }) - }, [updateTwapOrdersSettings, isPriceImpactAccepted]) - // Don't display any warnings while a wallet is not connected if (walletIsNotConnected) return null @@ -94,14 +89,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T {showZeroApprovalWarning && } {showApprovalBundlingBanner && } - {!isConfirmationModal && showPriceImpactWarning && ( - setIsPriceImpactAccepted()} - /> - )} - {(() => { if (localFormValidation === TwapFormState.NOT_SAFE) { return diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWidget/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWidget/index.tsx index fb804210f3..666eca5fca 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWidget/index.tsx @@ -1,5 +1,5 @@ import { useAtomValue, useSetAtom } from 'jotai' -import { useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { ReactNode, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { renderTooltip } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' @@ -39,7 +39,11 @@ import { TwapFormWarnings } from '../TwapFormWarnings' export type { LabelTooltip, LabelTooltipItems } from './tooltips' -export function TwapFormWidget() { +interface TwapFormWidget { + tradeWarnings: ReactNode +} + +export function TwapFormWidget({ tradeWarnings }: TwapFormWidget) { const { account } = useWalletInfo() const { numberOfPartsValue, deadline, customDeadline, isCustomDeadline } = useAtomValue(twapOrdersSettingsAtom) @@ -65,7 +69,7 @@ export function TwapFormWidget() { const limitPriceAfterSlippage = usePrice( receiveAmountInfo?.afterSlippage.sellAmount, - receiveAmountInfo?.afterSlippage.buyAmount + receiveAmountInfo?.afterSlippage.buyAmount, ) const deadlineState = { @@ -76,7 +80,7 @@ export function TwapFormWidget() { // Reset warnings flags once on start useEffect(() => { - updateSettingsState({ isFallbackHandlerSetupAccepted: false, isPriceImpactAccepted: false }) + updateSettingsState({ isFallbackHandlerSetupAccepted: false }) openAdvancedOrdersTabAnalytics() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -182,6 +186,7 @@ export function TwapFormWidget() { + {tradeWarnings} { const canTrade = !primaryFormValidation || NOT_BLOCKING_VALIDATIONS.includes(primaryFormValidation) - const showPriceImpactWarning = canTrade && !priceImpactParams.loading && !priceImpactParams.priceImpact const walletIsNotConnected = !account return { canTrade, - showPriceImpactWarning, walletIsNotConnected, } }, [primaryFormValidation, account, priceImpactParams]) diff --git a/apps/cowswap-frontend/src/modules/twap/state/twapOrdersSettingsAtom.ts b/apps/cowswap-frontend/src/modules/twap/state/twapOrdersSettingsAtom.ts index faaecd4b8d..fa99ca5455 100644 --- a/apps/cowswap-frontend/src/modules/twap/state/twapOrdersSettingsAtom.ts +++ b/apps/cowswap-frontend/src/modules/twap/state/twapOrdersSettingsAtom.ts @@ -21,7 +21,6 @@ export interface TwapOrdersSettingsState extends TwapOrdersDeadline { readonly numberOfPartsValue: number readonly slippageValue: number | null readonly isFallbackHandlerSetupAccepted: boolean - readonly isPriceImpactAccepted: boolean } export const defaultCustomDeadline: TwapOrdersDeadline['customDeadline'] = { @@ -38,13 +37,12 @@ export const defaultTwapOrdersSettings: TwapOrdersSettingsState = { // null = auto slippageValue: null, isFallbackHandlerSetupAccepted: false, - isPriceImpactAccepted: false, } export const twapOrdersSettingsAtom = atomWithStorage( 'twap-orders-settings-atom:v1', defaultTwapOrdersSettings, - getJotaiIsolatedStorage() + getJotaiIsolatedStorage(), ) export const updateTwapOrdersSettingsAtom = atom(null, (get, set, nextState: Partial) => { diff --git a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx index 6d8de5e79d..58db93e9fe 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { useTradeConfirmActions } from 'modules/trade' +import { useIsNoImpactWarningAccepted, useTradeConfirmActions } from 'modules/trade' import { TradeFormButtons, useGetTradeFormValidation, useTradeFormButtonContext } from 'modules/tradeFormValidation' import { useHighFeeWarning } from 'modules/tradeWidgetAddons' @@ -14,12 +14,13 @@ export function TradeButtons({ isTradeContextReady }: TradeButtonsProps) { const primaryFormValidation = useGetTradeFormValidation() const tradeConfirmActions = useTradeConfirmActions() const { feeWarningAccepted } = useHighFeeWarning() + const isNoImpactWarningAccepted = useIsNoImpactWarningAccepted() const confirmTrade = tradeConfirmActions.onOpen const tradeFormButtonContext = useTradeFormButtonContext(CONFIRM_TEXT, confirmTrade) - const isDisabled = !isTradeContextReady || !feeWarningAccepted + const isDisabled = !isTradeContextReady || !feeWarningAccepted || !isNoImpactWarningAccepted if (!tradeFormButtonContext) return null diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 6e6d64fca0..6f3ef27b21 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -2,7 +2,13 @@ import React from 'react' import { Field } from 'legacy/state/types' -import { TradeWidget, useReceiveAmountInfo, useTradeConfirmState, useTradePriceImpact } from 'modules/trade' +import { + TradeWidget, + TradeWidgetSlots, + useReceiveAmountInfo, + useTradeConfirmState, + useTradePriceImpact, +} from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' @@ -74,15 +80,18 @@ export function YieldWidget() { const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) - const slots = { + const slots: TradeWidgetSlots = { settingsWidget: , - bottomContent: ( - <> - - - - - ), + bottomContent(tradeWarnings) { + return ( + <> + + + {tradeWarnings} + + + ) + }, } const params = { diff --git a/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx b/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx index 72644944e9..9537c8ec91 100644 --- a/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx +++ b/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx @@ -44,8 +44,12 @@ export default function AdvancedOrdersPage() { params={advancedWidgetParams} mapCurrencyInfo={mapTwapCurrencyInfo} > - {/*TODO: conditionally display a widget for current advanced order type*/} - + {(tradeWarnings) => ( + <> + {/*TODO: conditionally display a widget for current advanced order type*/} + + + )} From 3c6ed690d52b64cee15bcaa04871a452e7859760 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 15:47:45 +0500 Subject: [PATCH 28/90] refactor(trade): generalise ZeroApprovalWarning --- .../containers/LimitOrdersWarnings/index.tsx | 6 ------ .../src/modules/swap/containers/SwapWidget/index.tsx | 5 +---- .../src/modules/swap/pure/warnings.tsx | 5 ----- .../trade/containers/TradeWidget/TradeWidgetForm.tsx | 9 ++++++++- .../ZeroApprovalWarning.cosmos.tsx | 0 .../pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx | 2 +- .../trade}/pure/ZeroApprovalWarning/index.tsx | 0 .../twap/containers/TwapFormWarnings/index.tsx | 11 +---------- .../src/modules/yield/containers/Warnings/index.tsx | 9 --------- 9 files changed, 11 insertions(+), 36 deletions(-) rename apps/cowswap-frontend/src/{common => modules/trade}/pure/ZeroApprovalWarning/ZeroApprovalWarning.cosmos.tsx (100%) rename apps/cowswap-frontend/src/{common => modules/trade}/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx (94%) rename apps/cowswap-frontend/src/{common => modules/trade}/pure/ZeroApprovalWarning/index.tsx (100%) diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx index 3ea5090d77..314b107e58 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx @@ -21,10 +21,8 @@ import { SellNativeWarningBanner } from 'modules/trade/containers/SellNativeWarn import { useGetTradeFormValidation } from 'modules/tradeFormValidation' import { TradeFormValidation } from 'modules/tradeFormValidation/types' import { useTradeQuote } from 'modules/tradeQuote' -import { useShouldZeroApprove } from 'modules/zeroApproval' import { HIGH_FEE_WARNING_PERCENTAGE } from 'common/constants/common' -import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' import { calculatePercentageInRelationToReference } from 'utils/orderUtils/calculatePercentageInRelationToReference' import { RateImpactWarning } from '../../pure/RateImpactWarning' @@ -73,8 +71,6 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { const showHighFeeWarning = feePercentage?.greaterThan(HIGH_FEE_WARNING_PERCENTAGE) const showApprovalBundlingBanner = !isConfirmScreen && isBundling - const shouldZeroApprove = useShouldZeroApprove(slippageAdjustedSellAmount) - const showZeroApprovalWarning = shouldZeroApprove && outputCurrency !== null // Show warning only when output currency is also present. const isSafeViaWc = useIsSafeViaWc() const showSafeWcBundlingBanner = @@ -92,7 +88,6 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { showHighFeeWarning || showApprovalBundlingBanner || showSafeWcBundlingBanner || - shouldZeroApprove || showNativeSellWarning // Reset rate impact before opening confirmation screen @@ -111,7 +106,6 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { return isVisible ? ( - {showZeroApprovalWarning && } {showRateImpactWarning && ( - {shouldZeroApprove && } {showApprovalBundlingBanner && } {showWrapBundlingBanner && ( diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 4561c1499e..a567ee4543 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react' +import React, { useCallback, useMemo } from 'react' import ICON_ORDERS from '@cowprotocol/assets/svg/orders.svg' import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg' @@ -20,6 +20,7 @@ import { SetRecipient } from 'modules/swap/containers/SetRecipient' import { useOpenTokenSelectWidget } from 'modules/tokensList' import { useIsAlternativeOrderModalVisible } from 'modules/trade/state/alternativeOrder' import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFormValidation' +import { useShouldZeroApprove } from 'modules/zeroApproval' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' @@ -33,7 +34,9 @@ import { TradeWidgetProps } from './types' import { useTradeStateFromUrl } from '../../hooks/setupTradeState/useTradeStateFromUrl' import { useIsWrapOrUnwrap } from '../../hooks/useIsWrapOrUnwrap' +import { useReceiveAmountInfo } from '../../hooks/useReceiveAmountInfo' import { useTradeTypeInfo } from '../../hooks/useTradeTypeInfo' +import { ZeroApprovalWarning } from '../../pure/ZeroApprovalWarning' import { TradeType } from '../../types' import { NoImpactWarning } from '../NoImpactWarning' import { TradeWidgetLinks } from '../TradeWidgetLinks' @@ -91,6 +94,9 @@ export function TradeWidgetForm(props: TradeWidgetProps) { const tradeStateFromUrl = useTradeStateFromUrl() const alternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const primaryFormValidation = useGetTradeFormValidation() + const receiveAmountInfo = useReceiveAmountInfo() + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) const areCurrenciesLoading = !inputCurrencyInfo.currency && !outputCurrencyInfo.currency const bothCurrenciesSet = !!inputCurrencyInfo.currency && !!outputCurrencyInfo.currency @@ -235,6 +241,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { bottomContent?.( hideTradeWarnings ? null : ( <> + {shouldZeroApprove && } ), diff --git a/apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/ZeroApprovalWarning.cosmos.tsx b/apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/ZeroApprovalWarning.cosmos.tsx similarity index 100% rename from apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/ZeroApprovalWarning.cosmos.tsx rename to apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/ZeroApprovalWarning.cosmos.tsx diff --git a/apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx b/apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx similarity index 94% rename from apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx rename to apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx index 8725c58e99..333ef50ab3 100644 --- a/apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/ZeroApprovalWarning.tsx @@ -4,7 +4,7 @@ import { HashLink } from 'react-router-hash-link' import styled from 'styled-components/macro' import { Nullish } from 'types' -import { WarningCard } from '../WarningCard' +import { WarningCard } from 'common/pure/WarningCard' const Link = styled(HashLink)` text-decoration: underline; diff --git a/apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/index.tsx similarity index 100% rename from apps/cowswap-frontend/src/common/pure/ZeroApprovalWarning/index.tsx rename to apps/cowswap-frontend/src/modules/trade/pure/ZeroApprovalWarning/index.tsx diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx index 5fe047a018..dbfea88c23 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx @@ -4,16 +4,12 @@ import { useCallback } from 'react' import { BundleTxApprovalBanner } from '@cowprotocol/ui' import { useIsSafeViaWc, useWalletInfo } from '@cowprotocol/wallet' -import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders' import { modifySafeHandlerAnalytics } from 'modules/analytics' import { SellNativeWarningBanner } from 'modules/trade/containers/SellNativeWarningBanner' import { useTradeRouteContext } from 'modules/trade/hooks/useTradeRouteContext' import { useGetTradeFormValidation } from 'modules/tradeFormValidation' import { TradeFormValidation } from 'modules/tradeFormValidation/types' import { useTradeQuoteFeeFiatAmount } from 'modules/tradeQuote' -import { useShouldZeroApprove } from 'modules/zeroApproval' - -import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' import { FallbackHandlerWarning, @@ -30,7 +26,7 @@ import { useTwapSlippage } from '../../hooks/useTwapSlippage' import { useTwapWarningsContext } from '../../hooks/useTwapWarningsContext' import { TwapFormState } from '../../pure/PrimaryActionButton/getTwapFormState' import { swapAmountDifferenceAtom } from '../../state/swapAmountDifferenceAtom' -import { twapDeadlineAtom, twapOrderAtom } from '../../state/twapOrderAtom' +import { twapDeadlineAtom } from '../../state/twapOrderAtom' import { twapOrdersSettingsAtom, updateTwapOrdersSettingsAtom } from '../../state/twapOrdersSettingsAtom' import { isPriceProtectionNotEnough } from '../../utils/isPriceProtectionNotEnough' @@ -44,11 +40,9 @@ interface TwapFormWarningsProps { export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: TwapFormWarningsProps) { const { isFallbackHandlerSetupAccepted } = useAtomValue(twapOrdersSettingsAtom) const updateTwapOrdersSettings = useSetAtom(updateTwapOrdersSettingsAtom) - const twapOrder = useAtomValue(twapOrderAtom) const slippage = useTwapSlippage() const deadline = useAtomValue(twapDeadlineAtom) const swapAmountDifference = useAtomValue(swapAmountDifferenceAtom) - const { outputCurrencyAmount } = useAdvancedOrdersDerivedState() const primaryFormValidation = useGetTradeFormValidation() const { chainId } = useWalletInfo() @@ -66,8 +60,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T [updateTwapOrdersSettings], ) - const shouldZeroApprove = useShouldZeroApprove(twapOrder?.sellAmount) - const showZeroApprovalWarning = !isConfirmationModal && shouldZeroApprove && outputCurrencyAmount !== null const showApprovalBundlingBanner = !isConfirmationModal && primaryFormValidation && BUNDLE_APPROVAL_STATES.includes(primaryFormValidation) const showTradeFormWarnings = !isConfirmationModal && canTrade @@ -86,7 +78,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T return ( <> - {showZeroApprovalWarning && } {showApprovalBundlingBanner && } {(() => { diff --git a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx index 6b63bb4a85..4aebdf4211 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx @@ -1,19 +1,10 @@ import React from 'react' -import { useReceiveAmountInfo } from 'modules/trade' import { HighFeeWarning } from 'modules/tradeWidgetAddons' -import { useShouldZeroApprove } from 'modules/zeroApproval' - -import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' export function Warnings() { - const receiveAmountInfo = useReceiveAmountInfo() - const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount - const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) - return ( <> - {shouldZeroApprove && } ) From 807066520274d2ef477cd0fbe6eea29ad0c813d4 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 16:26:17 +0500 Subject: [PATCH 29/90] refactor(trade): generalise bundle tx banners --- .../containers/LimitOrdersWarnings/index.tsx | 27 ++------------ .../swap/containers/SwapWidget/index.tsx | 37 +------------------ .../modules/swap/helpers/getEthFlowEnabled.ts | 3 -- .../swap/helpers/getSwapButtonState.ts | 3 +- .../src/modules/swap/pure/warnings.tsx | 30 ++------------- .../TradeWidget/TradeWidgetForm.tsx | 20 ++++++++-- .../containers/BundleTxWrapBanner/index.tsx | 29 +++++++++++++++ .../src/modules/tradeWidgetAddons/index.ts | 1 + .../containers/TwapFormWarnings/index.tsx | 7 ---- .../yield/containers/Warnings/index.tsx | 3 +- .../src/containers/InlineBanner/banners.tsx | 18 --------- 11 files changed, 58 insertions(+), 120 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/swap/helpers/getEthFlowEnabled.ts create mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx index 314b107e58..ec34c608a1 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx @@ -2,14 +2,12 @@ import { useAtomValue, useSetAtom } from 'jotai' import React, { useCallback, useEffect } from 'react' import { isFractionFalsy } from '@cowprotocol/common-utils' -import { BundleTxApprovalBanner, BundleTxSafeWcBanner, SmallVolumeWarningBanner } from '@cowprotocol/ui' -import { useIsSafeViaWc } from '@cowprotocol/wallet' +import { SmallVolumeWarningBanner } from '@cowprotocol/ui' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import styled from 'styled-components/macro' import { Nullish } from 'types' -import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState' import { useLimitOrdersFormState } from 'modules/limitOrders/hooks/useLimitOrdersFormState' import { useRateImpact } from 'modules/limitOrders/hooks/useRateImpact' @@ -54,10 +52,8 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { const localFormValidation = useLimitOrdersFormState() const primaryFormValidation = useGetTradeFormValidation() const rateImpact = useRateImpact() - const { slippageAdjustedSellAmount, inputCurrency, inputCurrencyAmount, outputCurrency, outputCurrencyAmount } = - useLimitOrdersDerivedState() + const { inputCurrency, inputCurrencyAmount, outputCurrencyAmount } = useLimitOrdersDerivedState() const tradeQuote = useTradeQuote() - const { banners: widgetBanners } = useInjectedWidgetParams() const isBundling = primaryFormValidation && FORM_STATES_TO_SHOW_BUNDLE_BANNER.includes(primaryFormValidation) @@ -70,25 +66,10 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { const showHighFeeWarning = feePercentage?.greaterThan(HIGH_FEE_WARNING_PERCENTAGE) - const showApprovalBundlingBanner = !isConfirmScreen && isBundling - - const isSafeViaWc = useIsSafeViaWc() - const showSafeWcBundlingBanner = - !isConfirmScreen && - !showApprovalBundlingBanner && - isSafeViaWc && - primaryFormValidation === TradeFormValidation.ApproveRequired && - !widgetBanners?.hideSafeWebAppBanner - // TODO: implement Safe App EthFlow bundling for LIMIT and disable the warning in that case const showNativeSellWarning = primaryFormValidation === TradeFormValidation.SellNativeToken - const isVisible = - rateImpact < 0 || - showHighFeeWarning || - showApprovalBundlingBanner || - showSafeWcBundlingBanner || - showNativeSellWarning + const isVisible = rateImpact < 0 || showHighFeeWarning || showNativeSellWarning // Reset rate impact before opening confirmation screen useEffect(() => { @@ -118,8 +99,6 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) { {/*// TODO: must be replaced by */} {showHighFeeWarning && } - {showApprovalBundlingBanner && } - {showSafeWcBundlingBanner && } {showNativeSellWarning && } ) : null diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 935e572576..750ba72959 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -3,7 +3,7 @@ import { ReactNode, useCallback, useMemo, useState } from 'react' import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' import { useIsTradeUnsupported } from '@cowprotocol/tokens' -import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' +import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { TradeType } from '@cowprotocol/widget-lib' import { NetworkAlert } from 'legacy/components/NetworkAlert/NetworkAlert' @@ -15,7 +15,6 @@ import { useRecipientToggleManager, useUserTransactionTTL } from 'legacy/state/u import { useInjectedWidgetParams } from 'modules/injectedWidget' import { EthFlowModal, EthFlowProps } from 'modules/swap/containers/EthFlow' import { SwapModals, SwapModalsProps } from 'modules/swap/containers/SwapModals' -import { SwapButtonState } from 'modules/swap/helpers/getSwapButtonState' import { useShowRecipientControls } from 'modules/swap/hooks/useShowRecipientControls' import { useSwapButtonContext } from 'modules/swap/hooks/useSwapButtonContext' import { useSwapCurrenciesAmounts } from 'modules/swap/hooks/useSwapCurrenciesAmounts' @@ -37,7 +36,6 @@ import { import { useIsEoaEthFlow, useTradeRouteContext, - useWrappedToken, useUnknownImpactWarning, useIsNoImpactWarningAccepted, } from 'modules/trade' @@ -50,16 +48,11 @@ import { useSetLocalTimeOffset } from 'common/containers/InvalidLocalTimeWarning import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { SWAP_QUOTE_CHECK_INTERVAL } from 'common/updaters/FeesUpdater' -import useNativeCurrency from 'lib/hooks/useNativeCurrency' -import { useIsSwapEth } from '../../hooks/useIsSwapEth' import { useDerivedSwapInfo, useSwapActionHandlers, useSwapState } from '../../hooks/useSwapState' import { useTradeQuoteStateFromLegacy } from '../../hooks/useTradeQuoteStateFromLegacy' import { ConfirmSwapModalSetup } from '../ConfirmSwapModalSetup' -const BUTTON_STATES_TO_SHOW_BUNDLE_APPROVAL_BANNER = [SwapButtonState.ApproveAndSwap] -const BUTTON_STATES_TO_SHOW_BUNDLE_WRAP_BANNER = [SwapButtonState.WrapAndSwap] - export interface SwapWidgetProps { topContent?: ReactNode bottomContent?: ReactNode @@ -78,7 +71,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const showRecipientControls = useShowRecipientControls(recipient) const isEoaEthFlow = useIsEoaEthFlow() const widgetParams = useInjectedWidgetParams() - const { enabledTradeTypes, banners: widgetBanners } = widgetParams + const { enabledTradeTypes } = widgetParams const priceImpactParams = useTradePriceImpact() const tradeQuoteStateOverride = useTradeQuoteStateFromLegacy() const receiveAmountInfo = useReceiveAmountInfo() @@ -194,38 +187,12 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { showNativeWrapModal, showCowSubsidyModal, } - - const showApprovalBundlingBanner = BUTTON_STATES_TO_SHOW_BUNDLE_APPROVAL_BANNER.includes( - swapButtonContext.swapButtonState, - ) - const showWrapBundlingBanner = BUTTON_STATES_TO_SHOW_BUNDLE_WRAP_BANNER.includes(swapButtonContext.swapButtonState) - - const isSafeViaWc = useIsSafeViaWc() - const isSwapEth = useIsSwapEth() - - const showSafeWcApprovalBundlingBanner = - !showApprovalBundlingBanner && isSafeViaWc && swapButtonContext.swapButtonState === SwapButtonState.NeedApprove - - const showSafeWcWrapBundlingBanner = !showWrapBundlingBanner && isSafeViaWc && isSwapEth - - // Show the same banner when approval is needed or selling native token - const showSafeWcBundlingBanner = - (showSafeWcApprovalBundlingBanner || showSafeWcWrapBundlingBanner) && !widgetBanners?.hideSafeWebAppBanner - const showTwapSuggestionBanner = !enabledTradeTypes || enabledTradeTypes.includes(TradeType.ADVANCED) - const nativeCurrencySymbol = useNativeCurrency().symbol || 'ETH' - const wrappedCurrencySymbol = useWrappedToken().symbol || 'WETH' - const swapWarningsTopProps: SwapWarningsTopProps = { chainId, trade, - showApprovalBundlingBanner, - showWrapBundlingBanner, - showSafeWcBundlingBanner, showTwapSuggestionBanner, - nativeCurrencySymbol, - wrappedCurrencySymbol, buyingFiatAmount, priceImpact: priceImpactParams.priceImpact, tradeUrlParams, diff --git a/apps/cowswap-frontend/src/modules/swap/helpers/getEthFlowEnabled.ts b/apps/cowswap-frontend/src/modules/swap/helpers/getEthFlowEnabled.ts deleted file mode 100644 index 76e9a374e4..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/helpers/getEthFlowEnabled.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getEthFlowEnabled(isSmartContractWallet: boolean): boolean { - return !isSmartContractWallet -} diff --git a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts index 34f8a208aa..677617b953 100644 --- a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts +++ b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts @@ -4,7 +4,6 @@ import { QuoteError } from 'legacy/state/price/actions' import { QuoteInformationObject } from 'legacy/state/price/reducer' import TradeGp from 'legacy/state/swap/TradeGp' -import { getEthFlowEnabled } from 'modules/swap/helpers/getEthFlowEnabled' import { isQuoteExpired, QuoteDeadlineParams } from 'modules/tradeQuote' import { ApprovalState } from 'common/hooks/useApproveState' @@ -141,7 +140,7 @@ export function getSwapButtonState(input: SwapButtonStateParams): SwapButtonStat } if (input.isNativeIn) { - if (getEthFlowEnabled(input.isSmartContractWallet === true)) { + if (!input.isSmartContractWallet) { return SwapButtonState.RegularEthFlowSwap } else if (input.isBundlingSupported) { return SwapButtonState.WrapAndSwap diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index 65ba21a029..efaeef37e8 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -2,26 +2,20 @@ import React from 'react' import { genericPropsChecker } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { BundleTxApprovalBanner, BundleTxSafeWcBanner, BundleTxWrapBanner } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import TradeGp from 'legacy/state/swap/TradeGp' import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' -import { HighFeeWarning } from 'modules/tradeWidgetAddons' +import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner' export interface SwapWarningsTopProps { chainId: SupportedChainId trade: TradeGp | undefined - showApprovalBundlingBanner: boolean - showWrapBundlingBanner: boolean - showSafeWcBundlingBanner: boolean showTwapSuggestionBanner: boolean - nativeCurrencySymbol: string - wrappedCurrencySymbol: string buyingFiatAmount: CurrencyAmount | null priceImpact: Percent | undefined tradeUrlParams: TradeUrlParams @@ -35,30 +29,12 @@ export interface SwapWarningsBottomProps { } export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) { - const { - chainId, - trade, - showApprovalBundlingBanner, - showWrapBundlingBanner, - showSafeWcBundlingBanner, - showTwapSuggestionBanner, - nativeCurrencySymbol, - wrappedCurrencySymbol, - buyingFiatAmount, - priceImpact, - tradeUrlParams, - } = props + const { chainId, trade, showTwapSuggestionBanner, buyingFiatAmount, priceImpact, tradeUrlParams } = props return ( <> - {showApprovalBundlingBanner && } - {showWrapBundlingBanner && ( - - )} - {showSafeWcBundlingBanner && ( - - )} + {showTwapSuggestionBanner && ( { export function TradeWidgetForm(props: TradeWidgetProps) { const isInjectedWidgetMode = isInjectedWidget() - const { standaloneMode } = useInjectedWidgetParams() + const { standaloneMode, banners: widgetBanners } = useInjectedWidgetParams() const isAlternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const { pendingActivity } = useCategorizeRecentActivity() @@ -97,6 +105,10 @@ export function TradeWidgetForm(props: TradeWidgetProps) { const receiveAmountInfo = useReceiveAmountInfo() const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) + const isSafeViaWc = useIsSafeViaWc() + + const showSafeWcBundlingBanner = + isSafeViaWc && primaryFormValidation === TradeFormValidation.ApproveRequired && !widgetBanners?.hideSafeWebAppBanner const areCurrenciesLoading = !inputCurrencyInfo.currency && !outputCurrencyInfo.currency const bothCurrenciesSet = !!inputCurrencyInfo.currency && !!outputCurrencyInfo.currency @@ -243,6 +255,8 @@ export function TradeWidgetForm(props: TradeWidgetProps) { <> {shouldZeroApprove && } + {primaryFormValidation === TradeFormValidation.ApproveAndSwap && } + {showSafeWcBundlingBanner && } ), ) diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx new file mode 100644 index 0000000000..dfc6ddcdd5 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx @@ -0,0 +1,29 @@ +import { InlineBanner } from '@cowprotocol/ui' +import { useIsBundlingSupported, useIsSmartContractWallet } from '@cowprotocol/wallet' + +import { useIsNativeIn, useWrappedToken } from 'modules/trade' + +import useNativeCurrency from 'lib/hooks/useNativeCurrency' + +export function BundleTxWrapBanner() { + const nativeCurrencySymbol = useNativeCurrency().symbol || 'ETH' + const wrappedCurrencySymbol = useWrappedToken().symbol || 'WETH' + + const isBundlingSupported = useIsBundlingSupported() + const isNativeIn = useIsNativeIn() + const isSmartContractWallet = useIsSmartContractWallet() + const showWrapBundlingBanner = Boolean(isNativeIn && isSmartContractWallet && isBundlingSupported) + + if (!showWrapBundlingBanner) return null + + return ( + + Token wrapping bundling +

+ For your convenience, CoW Swap will bundle all the necessary actions for this trade into a single transaction. + This includes the {nativeCurrencySymbol} wrapping and, if needed, {wrappedCurrencySymbol} +  approval. Even if the trade fails, your wrapping and approval will be done! +

+
+ ) +} diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts index fd7a4f89af..5506835feb 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -2,5 +2,6 @@ export { RowDeadline } from './containers/RowDeadline' export { TradeRateDetails } from './containers/TradeRateDetails' export { SettingsTab } from './containers/SettingsTab' export { HighFeeWarning } from './containers/HighFeeWarning' +export { BundleTxWrapBanner } from './containers/BundleTxWrapBanner' export { useHighFeeWarning } from './containers/HighFeeWarning/hooks/useHighFeeWarning' export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx index dbfea88c23..ec1f665172 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx @@ -1,7 +1,6 @@ import { useAtomValue, useSetAtom } from 'jotai' import { useCallback } from 'react' -import { BundleTxApprovalBanner } from '@cowprotocol/ui' import { useIsSafeViaWc, useWalletInfo } from '@cowprotocol/wallet' import { modifySafeHandlerAnalytics } from 'modules/analytics' @@ -30,8 +29,6 @@ import { twapDeadlineAtom } from '../../state/twapOrderAtom' import { twapOrdersSettingsAtom, updateTwapOrdersSettingsAtom } from '../../state/twapOrdersSettingsAtom' import { isPriceProtectionNotEnough } from '../../utils/isPriceProtectionNotEnough' -const BUNDLE_APPROVAL_STATES = [TradeFormValidation.ApproveAndSwap] - interface TwapFormWarningsProps { localFormValidation: TwapFormState | null isConfirmationModal?: boolean @@ -60,8 +57,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T [updateTwapOrdersSettings], ) - const showApprovalBundlingBanner = - !isConfirmationModal && primaryFormValidation && BUNDLE_APPROVAL_STATES.includes(primaryFormValidation) const showTradeFormWarnings = !isConfirmationModal && canTrade const showFallbackHandlerWarning = showTradeFormWarnings && isFallbackHandlerRequired @@ -78,8 +73,6 @@ export function TwapFormWarnings({ localFormValidation, isConfirmationModal }: T return ( <> - {showApprovalBundlingBanner && } - {(() => { if (localFormValidation === TwapFormState.NOT_SAFE) { return diff --git a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx index 4aebdf4211..ca99cf8a21 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx @@ -1,11 +1,12 @@ import React from 'react' -import { HighFeeWarning } from 'modules/tradeWidgetAddons' +import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' export function Warnings() { return ( <> + ) } diff --git a/libs/ui/src/containers/InlineBanner/banners.tsx b/libs/ui/src/containers/InlineBanner/banners.tsx index 993d6de17a..e36a08446b 100644 --- a/libs/ui/src/containers/InlineBanner/banners.tsx +++ b/libs/ui/src/containers/InlineBanner/banners.tsx @@ -28,24 +28,6 @@ export function BundleTxApprovalBanner() { ) } -export type BundleTxWrapBannerProps = { - nativeCurrencySymbol: string - wrappedCurrencySymbol: string -} - -export function BundleTxWrapBanner({ nativeCurrencySymbol, wrappedCurrencySymbol }: BundleTxWrapBannerProps) { - return ( - - Token wrapping bundling -

- For your convenience, CoW Swap will bundle all the necessary actions for this trade into a single transaction. - This includes the {nativeCurrencySymbol} wrapping and, if needed, {wrappedCurrencySymbol} -  approval. Even if the trade fails, your wrapping and approval will be done! -

-
- ) -} - // If supportsWrapping is true, nativeCurrencySymbol is required type WrappingSupportedProps = { supportsWrapping: true; nativeCurrencySymbol: string } // If supportsWrapping is not set or false, nativeCurrencySymbol is not required From b6d9717b5708ad7af1f9739b3ab215127ebecff2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 16:30:54 +0500 Subject: [PATCH 30/90] refactor: extract TradeWarnings --- .../trade/containers/TradeWarnings/index.tsx | 58 +++++++++++++++++++ .../TradeWidget/TradeWidgetForm.tsx | 41 ++----------- .../src/containers/InlineBanner/banners.tsx | 34 ----------- 3 files changed, 63 insertions(+), 70 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx new file mode 100644 index 0000000000..cdaaa58eb1 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx @@ -0,0 +1,58 @@ +import React from 'react' + +import { CowSwapSafeAppLink, InlineBanner } from '@cowprotocol/ui' +import { useIsSafeViaWc } from '@cowprotocol/wallet' + +import { useInjectedWidgetParams } from 'modules/injectedWidget' +import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFormValidation' +import { useShouldZeroApprove } from 'modules/zeroApproval' + +import { useReceiveAmountInfo } from '../../hooks/useReceiveAmountInfo' +import { ZeroApprovalWarning } from '../../pure/ZeroApprovalWarning' +import { NoImpactWarning } from '../NoImpactWarning' + +export function TradeWarnings() { + const { banners: widgetBanners } = useInjectedWidgetParams() + const primaryFormValidation = useGetTradeFormValidation() + const receiveAmountInfo = useReceiveAmountInfo() + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) + const isSafeViaWc = useIsSafeViaWc() + + const showBundleTxApprovalBanner = primaryFormValidation === TradeFormValidation.ApproveAndSwap + const showSafeWcBundlingBanner = + isSafeViaWc && primaryFormValidation === TradeFormValidation.ApproveRequired && !widgetBanners?.hideSafeWebAppBanner + + return ( + <> + {shouldZeroApprove && } + + {showBundleTxApprovalBanner && } + {showSafeWcBundlingBanner && } + + ) +} + +function BundleTxApprovalBanner() { + return ( + + Token approval bundling +

+ For your convenience, token approval and order placement will be bundled into a single transaction, streamlining + your experience! +

+
+ ) +} + +function BundleTxSafeWcBanner() { + return ( + + Use Safe web app +

+ Use the Safe web app for streamlined trading: token approval and orders bundled in one go! Only available in the{' '} + +

+
+ ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 904a7b3ca2..0acb4f3bdd 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -3,16 +3,8 @@ import React, { useCallback, useMemo } from 'react' import ICON_ORDERS from '@cowprotocol/assets/svg/orders.svg' import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg' import { isInjectedWidget, maxAmountSpend } from '@cowprotocol/common-utils' -import { - BannerOrientation, - BundleTxApprovalBanner, - BundleTxSafeWcBanner, - ButtonOutlined, - ClosableBanner, - InlineBanner, - MY_ORDERS_ID, -} from '@cowprotocol/ui' -import { useIsSafeViaWc, useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' +import { BannerOrientation, ButtonOutlined, ClosableBanner, InlineBanner, MY_ORDERS_ID } from '@cowprotocol/ui' +import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { t } from '@lingui/macro' import SVG from 'react-inlinesvg' @@ -28,7 +20,6 @@ import { SetRecipient } from 'modules/swap/containers/SetRecipient' import { useOpenTokenSelectWidget } from 'modules/tokensList' import { useIsAlternativeOrderModalVisible } from 'modules/trade/state/alternativeOrder' import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFormValidation' -import { useShouldZeroApprove } from 'modules/zeroApproval' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' @@ -42,11 +33,9 @@ import { TradeWidgetProps } from './types' import { useTradeStateFromUrl } from '../../hooks/setupTradeState/useTradeStateFromUrl' import { useIsWrapOrUnwrap } from '../../hooks/useIsWrapOrUnwrap' -import { useReceiveAmountInfo } from '../../hooks/useReceiveAmountInfo' import { useTradeTypeInfo } from '../../hooks/useTradeTypeInfo' -import { ZeroApprovalWarning } from '../../pure/ZeroApprovalWarning' import { TradeType } from '../../types' -import { NoImpactWarning } from '../NoImpactWarning' +import { TradeWarnings } from '../TradeWarnings' import { TradeWidgetLinks } from '../TradeWidgetLinks' import { WrapFlowActionButton } from '../WrapFlowActionButton' @@ -61,7 +50,7 @@ const scrollToMyOrders = () => { export function TradeWidgetForm(props: TradeWidgetProps) { const isInjectedWidgetMode = isInjectedWidget() - const { standaloneMode, banners: widgetBanners } = useInjectedWidgetParams() + const { standaloneMode } = useInjectedWidgetParams() const isAlternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const { pendingActivity } = useCategorizeRecentActivity() @@ -102,13 +91,6 @@ export function TradeWidgetForm(props: TradeWidgetProps) { const tradeStateFromUrl = useTradeStateFromUrl() const alternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const primaryFormValidation = useGetTradeFormValidation() - const receiveAmountInfo = useReceiveAmountInfo() - const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount - const shouldZeroApprove = useShouldZeroApprove(inputAmountWithSlippage) - const isSafeViaWc = useIsSafeViaWc() - - const showSafeWcBundlingBanner = - isSafeViaWc && primaryFormValidation === TradeFormValidation.ApproveRequired && !widgetBanners?.hideSafeWebAppBanner const areCurrenciesLoading = !inputCurrencyInfo.currency && !outputCurrencyInfo.currency const bothCurrenciesSet = !!inputCurrencyInfo.currency && !!outputCurrencyInfo.currency @@ -247,20 +229,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { {withRecipient && } - {isWrapOrUnwrap ? ( - - ) : ( - bottomContent?.( - hideTradeWarnings ? null : ( - <> - {shouldZeroApprove && } - - {primaryFormValidation === TradeFormValidation.ApproveAndSwap && } - {showSafeWcBundlingBanner && } - - ), - ) - )} + {isWrapOrUnwrap ? : bottomContent?.(hideTradeWarnings ? null : )} )} diff --git a/libs/ui/src/containers/InlineBanner/banners.tsx b/libs/ui/src/containers/InlineBanner/banners.tsx index e36a08446b..699261d24b 100644 --- a/libs/ui/src/containers/InlineBanner/banners.tsx +++ b/libs/ui/src/containers/InlineBanner/banners.tsx @@ -3,7 +3,6 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import styled from 'styled-components/macro' -import { CowSwapSafeAppLink } from '../../containers/CowSwapSafeAppLink' import { ButtonSecondaryAlt } from '../../pure/ButtonSecondaryAlt' import { LinkStyledButton } from '../../pure/LinkStyledButton' import { TokenAmount } from '../../pure/TokenAmount' @@ -16,39 +15,6 @@ export enum BannerOrientation { Vertical = 'vertical', } -export function BundleTxApprovalBanner() { - return ( - - Token approval bundling -

- For your convenience, token approval and order placement will be bundled into a single transaction, streamlining - your experience! -

-
- ) -} - -// If supportsWrapping is true, nativeCurrencySymbol is required -type WrappingSupportedProps = { supportsWrapping: true; nativeCurrencySymbol: string } -// If supportsWrapping is not set or false, nativeCurrencySymbol is not required -type WrappingUnsupportedProps = { supportsWrapping?: false; nativeCurrencySymbol?: undefined } - -export type BundleTxSafeWcBannerProps = WrappingSupportedProps | WrappingUnsupportedProps - -export function BundleTxSafeWcBanner({ nativeCurrencySymbol, supportsWrapping }: BundleTxSafeWcBannerProps) { - const supportsWrappingText = supportsWrapping ? `${nativeCurrencySymbol} wrapping, ` : '' - - return ( - - Use Safe web app -

- Use the Safe web app for streamlined trading: {supportsWrappingText}token approval and orders bundled in one go! - Only available in the -

-
- ) -} - export type SmallVolumeWarningBannerProps = { feePercentage: Nullish feeAmount: Nullish> From 759bc0fd4e69f2e537d49df44ea5cb01b021c073 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 16:41:16 +0500 Subject: [PATCH 31/90] chore: fix yield form displaying --- .../src/modules/yield/containers/YieldWidget/index.tsx | 2 +- .../yield/updaters/QuoteObserverUpdater/index.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 6f3ef27b21..d220d487d9 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -59,7 +59,7 @@ export function YieldWidget() { field: Field.OUTPUT, currency: outputCurrency, amount: outputCurrencyAmount, - isIndependent: true, + isIndependent: false, balance: outputCurrencyBalance, fiatAmount: outputCurrencyFiatAmount, receiveAmountInfo, diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx index 99159eda0d..2390d91029 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/QuoteObserverUpdater/index.tsx @@ -13,7 +13,7 @@ export function QuoteObserverUpdater() { const receiveAmountInfo = useReceiveAmountInfo() const { beforeNetworkCosts } = receiveAmountInfo || {} - const updateLimitRateState = useUpdateCurrencyAmount() + const updateCurrencyAmount = useUpdateCurrencyAmount() const inputCurrency = state?.inputCurrency const outputCurrency = state?.outputCurrency @@ -24,8 +24,8 @@ export function QuoteObserverUpdater() { return } - updateLimitRateState(Field.OUTPUT, beforeNetworkCosts.buyAmount) - }, [beforeNetworkCosts, inputCurrency, outputCurrency, updateLimitRateState]) + updateCurrencyAmount(Field.OUTPUT, beforeNetworkCosts.buyAmount) + }, [beforeNetworkCosts, inputCurrency, outputCurrency, updateCurrencyAmount]) // Reset the output amount when the input amount changes useEffect(() => { @@ -33,8 +33,8 @@ export function QuoteObserverUpdater() { return } - updateLimitRateState(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, 0)) - }, [state?.inputCurrencyAmount, state?.inputCurrency, updateLimitRateState, outputCurrency]) + updateCurrencyAmount(Field.OUTPUT, CurrencyAmount.fromRawAmount(outputCurrency, 0)) + }, [state?.inputCurrencyAmount, state?.inputCurrency, updateCurrencyAmount, outputCurrency]) return null } From f320ff7525e1bf273767aeda8c92b6fa6f1732aa Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 18:05:02 +0500 Subject: [PATCH 32/90] refactor(swap): generalise trade flow --- .../hooks/useSetupHooksStoreOrderParams.ts | 9 +- .../ConfirmSwapModalSetup/index.tsx | 6 +- .../swap/containers/SwapUpdaters/index.tsx | 2 - .../swap/containers/SwapWidget/index.tsx | 1 - .../hooks/useBaseSafeBundleFlowContext.ts | 43 ---- .../modules/swap/hooks/useEthFlowContext.ts | 64 +++--- .../src/modules/swap/hooks/useFlowContext.ts | 154 -------------- .../modules/swap/hooks/useHandleSwap.test.tsx | 85 -------- .../src/modules/swap/hooks/useHandleSwap.ts | 65 ++++-- .../hooks/useSafeBundleApprovalFlowContext.ts | 35 ---- .../swap/hooks/useSafeBundleEthFlowContext.ts | 27 --- .../swap/hooks/useSwapButtonContext.ts | 25 +-- .../swap/hooks/useSwapConfirmButtonText.tsx | 4 +- .../modules/swap/hooks/useSwapFlowContext.ts | 64 +----- .../modules/swap/services/ethFlow/index.ts | 28 ++- .../safeBundleFlow/safeBundleApprovalFlow.ts | 31 +-- .../safeBundleFlow/safeBundleEthFlow.ts | 32 +-- .../modules/swap/services/swapFlow/index.ts | 5 +- .../src/modules/swap/services/types.ts | 84 +------- .../swap/state/baseFlowContextSourceAtom.ts | 5 - .../src/modules/swap/types/flowContext.ts | 48 ----- .../swap/updaters/BaseFlowContextUpdater.tsx | 142 ------------- .../{swap => trade}/hooks/useIsSafeEthFlow.ts | 0 .../{swap => trade}/hooks/useIsSwapEth.ts | 4 +- .../trade/hooks/useSafeBundleFlowContext.ts | 47 +++++ .../trade/hooks/useTradeFlowContext.ts | 192 ++++++++++++++++++ .../modules/trade/hooks/useTradeFlowType.ts | 33 +++ .../src/modules/trade/index.ts | 5 + .../modules/trade/types/TradeFlowContext.ts | 53 +++++ .../src/modules/trade/types/index.ts | 1 + .../yield/containers/YieldWidget/index.tsx | 4 +- .../yield/hooks/useTradeFlowContext.ts | 158 -------------- 32 files changed, 477 insertions(+), 979 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useBaseSafeBundleFlowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.test.tsx delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useSafeBundleApprovalFlowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useSafeBundleEthFlowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/state/baseFlowContextSourceAtom.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/types/flowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx rename apps/cowswap-frontend/src/modules/{swap => trade}/hooks/useIsSafeEthFlow.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => trade}/hooks/useIsSwapEth.ts (53%) create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts create mode 100644 apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts create mode 100644 apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts delete mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts index 7bc2cb2dce..7ae9db0f0b 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts @@ -2,14 +2,17 @@ import { useEffect } from 'react' import { getCurrencyAddress } from '@cowprotocol/common-utils' -import { useSwapFlowContext } from 'modules/swap/hooks/useSwapFlowContext' +import { useUserTransactionTTL } from 'legacy/state/user/hooks' + +import { useTradeFlowContext } from 'modules/trade' import { useSetOrderParams } from './useSetOrderParams' export function useSetupHooksStoreOrderParams() { - const swapFlowContext = useSwapFlowContext() + const [deadline] = useUserTransactionTTL() + const tradeFlowContext = useTradeFlowContext({ deadline }) const setOrderParams = useSetOrderParams() - const orderParams = swapFlowContext?.orderParams + const orderParams = tradeFlowContext?.orderParams useEffect(() => { if (!orderParams) return diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 8a5460049b..8877d4e503 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -9,6 +9,7 @@ import { PriceImpact } from 'legacy/hooks/usePriceImpact' import TradeGp from 'legacy/state/swap/TradeGp' import { useUserTransactionTTL } from 'legacy/state/user/hooks' +import { useAppData } from 'modules/appData' import { TradeConfirmation, TradeConfirmModal, @@ -29,7 +30,6 @@ import { RateInfoParams } from 'common/pure/RateInfo' import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' import useNativeCurrency from 'lib/hooks/useNativeCurrency' -import { useBaseFlowContextSource } from '../../hooks/useFlowContext' import { useSwapConfirmButtonText } from '../../hooks/useSwapConfirmButtonText' import { useSwapState } from '../../hooks/useSwapState' @@ -69,7 +69,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { const shouldPayGas = useShouldPayGas() const isEoaEthFlow = useIsEoaEthFlow() const nativeCurrency = useNativeCurrency() - const baseFlowContextSource = useBaseFlowContextSource() + const appData = useAppData() const [userDeadline] = useUserTransactionTTL() const slippageAdjustedSellAmount = trade?.maximumAmountIn(allowedSlippage) @@ -114,7 +114,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { priceImpact={priceImpact} buttonText={buttonText} recipient={recipient} - appData={baseFlowContextSource?.appData || undefined} + appData={appData || undefined} > {(restContent) => ( <> diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx index e947d8651c..4a16bb9a78 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx @@ -3,7 +3,6 @@ import { percentToBps } from '@cowprotocol/common-utils' import { AppDataUpdater } from 'modules/appData' import { SmartSlippageUpdater, useTradeSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' -import { BaseFlowContextUpdater } from '../../updaters/BaseFlowContextUpdater' import { SwapAmountsFromUrlUpdater } from '../../updaters/SwapAmountsFromUrlUpdater' import { SwapDerivedStateUpdater } from '../../updaters/SwapDerivedStateUpdater' @@ -21,7 +20,6 @@ export function SwapUpdaters() { - ) } diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 750ba72959..c80ff0441e 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -168,7 +168,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { feeWarningAccepted, impactWarningAccepted, openNativeWrapModal, - priceImpactParams, }) const tradeUrlParams = useTradeRouteContext() diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useBaseSafeBundleFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useBaseSafeBundleFlowContext.ts deleted file mode 100644 index 6bed3d4da2..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useBaseSafeBundleFlowContext.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useMemo } from 'react' - -import { getWrappedToken } from '@cowprotocol/common-utils' -import { OrderKind } from '@cowprotocol/cow-sdk' -import { useSafeAppsSdk } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' -import { TradeType } from '@uniswap/sdk-core' - -import { getFlowContext, useBaseFlowContextSource } from 'modules/swap/hooks/useFlowContext' -import { BaseSafeFlowContext } from 'modules/swap/services/types' - -import { useGP2SettlementContract } from 'common/hooks/useContract' -import { useTradeSpenderAddress } from 'common/hooks/useTradeSpenderAddress' - -export function useBaseSafeBundleFlowContext(): BaseSafeFlowContext | null { - const baseProps = useBaseFlowContextSource() - const sellToken = baseProps?.trade ? getWrappedToken(baseProps.trade.inputAmount.currency) : undefined - const settlementContract = useGP2SettlementContract() - const spender = useTradeSpenderAddress() - - const safeAppsSdk = useSafeAppsSdk() - const provider = useWalletProvider() - - return useMemo(() => { - if (!baseProps?.trade || !settlementContract || !spender || !safeAppsSdk || !provider) return null - - const baseContext = getFlowContext({ - baseProps, - sellToken, - kind: baseProps.trade.tradeType === TradeType.EXACT_INPUT ? OrderKind.SELL : OrderKind.BUY, - }) - - if (!baseContext) return null - - return { - ...baseContext, - settlementContract, - spender, - safeAppsSdk, - provider, - } - }, [baseProps, settlementContract, spender, safeAppsSdk, provider, sellToken]) -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useEthFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useEthFlowContext.ts index c3939fb295..f73262fc99 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useEthFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useEthFlowContext.ts @@ -1,52 +1,54 @@ import { useSetAtom } from 'jotai' -import { useMemo } from 'react' -import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' -import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' +import { useWalletInfo } from '@cowprotocol/wallet' + +import useSWR from 'swr' import { useTransactionAdder } from 'legacy/state/enhancedTransactions/hooks' +import { useGetQuoteAndStatus } from 'legacy/state/price/hooks' -import { getFlowContext, useBaseFlowContextSource } from 'modules/swap/hooks/useFlowContext' -import { EthFlowContext } from 'modules/swap/services/types' -import { addInFlightOrderIdAtom } from 'modules/swap/state/EthFlow/ethFlowInFlightOrderIdsAtom' +import { useAppData, useUploadAppData } from 'modules/appData' import { useEthFlowContract } from 'common/hooks/useContract' import { useCheckEthFlowOrderExists } from './useCheckEthFlowOrderExists' +import { useDerivedSwapInfo } from './useSwapState' -import { FlowType } from '../types/flowContext' +import { EthFlowContext } from '../services/types' +import { addInFlightOrderIdAtom } from '../state/EthFlow/ethFlowInFlightOrderIdsAtom' export function useEthFlowContext(): EthFlowContext | null { + const { chainId } = useWalletInfo() + const { currenciesIds } = useDerivedSwapInfo() + const { quote } = useGetQuoteAndStatus({ + token: currenciesIds.INPUT, + chainId, + }) const contract = useEthFlowContract() - const baseProps = useBaseFlowContextSource() const addTransaction = useTransactionAdder() - - const sellToken = baseProps?.chainId ? NATIVE_CURRENCIES[baseProps.chainId as SupportedChainId] : undefined + const uploadAppData = useUploadAppData() + const appData = useAppData() const addInFlightOrderId = useSetAtom(addInFlightOrderIdAtom) const checkEthFlowOrderExists = useCheckEthFlowOrderExists() - const baseContext = useMemo( - () => - baseProps && - getFlowContext({ - baseProps, - sellToken, - kind: OrderKind.SELL, - }), - [baseProps, sellToken], + return ( + useSWR( + appData && contract + ? [quote, contract, addTransaction, checkEthFlowOrderExists, addInFlightOrderId, uploadAppData, appData] + : null, + ([quote, contract, addTransaction, checkEthFlowOrderExists, addInFlightOrderId, uploadAppData, appData]) => { + return { + quote, + contract, + addTransaction, + checkEthFlowOrderExists, + addInFlightOrderId, + uploadAppData, + appData, + } + }, + ).data || null ) - - return useMemo(() => { - if (!baseContext || !contract || baseProps?.flowType !== FlowType.EOA_ETH_FLOW) return null - - return { - ...baseContext, - contract, - addTransaction, - checkEthFlowOrderExists, - addInFlightOrderId, - } - }, [baseContext, contract, addTransaction, checkEthFlowOrderExists, addInFlightOrderId, baseProps?.flowType]) } diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts deleted file mode 100644 index ff37d5df84..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { useAtomValue } from 'jotai/index' - -import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' -import { getIsNativeToken } from '@cowprotocol/common-utils' -import { OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' -import { UiOrderType } from '@cowprotocol/types' -import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' - -import { computeSlippageAdjustedAmounts } from 'legacy/utils/prices' -import { PostOrderParams } from 'legacy/utils/trade' - -import { BaseFlowContext } from 'modules/swap/services/types' -import { TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics' -import { getOrderValidTo } from 'modules/tradeQuote' -import { useTradeSlippage } from 'modules/tradeSlippage' - -import { useSafeMemo } from 'common/hooks/useSafeMemo' - -import { useDerivedSwapInfo } from './useSwapState' - -import { getAmountsForSignature } from '../helpers/getAmountsForSignature' -import { baseFlowContextSourceAtom } from '../state/baseFlowContextSourceAtom' -import { BaseFlowContextSource } from '../types/flowContext' - -export function useSwapAmountsWithSlippage(): [ - CurrencyAmount | undefined, - CurrencyAmount | undefined, -] { - const slippage = useTradeSlippage() - const { trade } = useDerivedSwapInfo() - - const { INPUT, OUTPUT } = computeSlippageAdjustedAmounts(trade, slippage) - - return useSafeMemo(() => [INPUT, OUTPUT], [INPUT, OUTPUT]) -} - -export function useBaseFlowContextSource(): BaseFlowContextSource | null { - return useAtomValue(baseFlowContextSourceAtom) -} - -type BaseGetFlowContextProps = { - baseProps: BaseFlowContextSource - sellToken?: Token - kind: OrderKind -} - -export function getFlowContext({ baseProps, sellToken, kind }: BaseGetFlowContextProps): BaseFlowContext | null { - const { - chainId, - account, - provider, - trade, - appData, - wethContract, - inputAmountWithSlippage, - gnosisSafeInfo, - recipient, - recipientAddressOrName, - deadline, - ensRecipientAddress, - allowsOffchainSigning, - closeModals, - uploadAppData, - dispatch, - flowType, - sellTokenContract, - allowedSlippage, - tradeConfirmActions, - getCachedPermit, - quote, - typedHooks, - } = baseProps - - if (!chainId || !account || !provider || !trade || !appData || !wethContract || !inputAmountWithSlippage) { - return null - } - - const isSafeWallet = !!gnosisSafeInfo - - const buyToken = getIsNativeToken(trade.outputAmount.currency) - ? NATIVE_CURRENCIES[chainId as SupportedChainId] - : trade.outputAmount.currency - const marketLabel = [sellToken?.symbol, buyToken.symbol].join(',') - - if (!sellToken || !buyToken) { - return null - } - - const swapFlowAnalyticsContext: TradeFlowAnalyticsContext = { - account, - recipient, - recipientAddress: recipientAddressOrName, - marketLabel, - orderType: UiOrderType.SWAP, - } - - const validTo = getOrderValidTo(deadline, { - validFor: quote?.validFor, - quoteValidTo: quote?.quoteValidTo, - localQuoteTimestamp: quote?.localQuoteTimestamp, - }) - - const amountsForSignature = getAmountsForSignature({ - trade, - kind, - allowedSlippage, - }) - - const orderParams: PostOrderParams = { - class: OrderClass.MARKET, - kind, - account, - chainId, - ...amountsForSignature, - sellAmountBeforeFee: trade.inputAmountWithoutFee, - feeAmount: trade.fee.feeAsCurrency, - buyToken, - sellToken, - validTo, - recipient: ensRecipientAddress || recipient || account, - recipientAddressOrName, - signer: provider.getSigner(), - allowsOffchainSigning, - partiallyFillable: false, // SWAP orders are always fill or kill - for now - appData, - quoteId: trade.quoteId, - isSafeWallet, - } - - return { - context: { - chainId, - trade, - inputAmountWithSlippage, - flowType, - }, - flags: { - allowsOffchainSigning, - }, - callbacks: { - closeModals, - uploadAppData, - getCachedPermit, - }, - dispatch, - swapFlowAnalyticsContext, - orderParams, - appDataInfo: appData, - sellTokenContract, - tradeConfirmActions, - quote, - typedHooks, - } -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.test.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.test.tsx deleted file mode 100644 index 4aa2350231..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { PropsWithChildren } from 'react' - -import { renderHook } from '@testing-library/react-hooks' - -import { PriceImpact } from 'legacy/hooks/usePriceImpact' -import { Field } from 'legacy/state/types' - -import { useSafeBundleApprovalFlowContext } from 'modules/swap/hooks/useSafeBundleApprovalFlowContext' -import { ethFlow } from 'modules/swap/services/ethFlow' -import { safeBundleApprovalFlow, safeBundleEthFlow } from 'modules/swap/services/safeBundleFlow' -import { swapFlow } from 'modules/swap/services/swapFlow' - -import { WithModalProvider } from 'utils/withModalProvider' - -import { useEthFlowContext } from './useEthFlowContext' -import { useHandleSwap } from './useHandleSwap' -import { useSafeBundleEthFlowContext } from './useSafeBundleEthFlowContext' -import { useSwapFlowContext } from './useSwapFlowContext' -import { useSwapActionHandlers } from './useSwapState' - -jest.mock('./useSwapState') -jest.mock('./useSwapFlowContext') -jest.mock('./useEthFlowContext') -jest.mock('./useSafeBundleApprovalFlowContext') -jest.mock('./useSafeBundleEthFlowContext') -jest.mock('modules/swap/services/swapFlow') -jest.mock('modules/swap/services/ethFlow') -jest.mock('modules/swap/services/safeBundleFlow') -jest.mock('modules/twap/state/twapOrdersListAtom', () => ({})) -jest.mock('modules/analytics/useAnalyticsReporterCowSwap') - -const mockUseSwapActionHandlers = useSwapActionHandlers as jest.MockedFunction -const mockSwapFlow = swapFlow as jest.MockedFunction -const mockEthFlow = ethFlow as jest.MockedFunction -const mockSafeBundleApprovalFlow = safeBundleApprovalFlow as jest.MockedFunction -const mockSafeBundleEthFlow = safeBundleEthFlow as jest.MockedFunction -const mockUseSwapFlowContext = useSwapFlowContext as jest.MockedFunction -const mockUseEthFlowContext = useEthFlowContext as jest.MockedFunction -const mockUseSafeBundleFlowContext = useSafeBundleApprovalFlowContext as jest.MockedFunction< - typeof useSafeBundleApprovalFlowContext -> -const mockUseSafeBundleEthFlowContext = useSafeBundleEthFlowContext as jest.MockedFunction< - typeof useSafeBundleEthFlowContext -> - -const priceImpactMock: PriceImpact = { - priceImpact: undefined, - loading: false, -} - -const WithProviders = ({ children }: PropsWithChildren) => { - return {children} -} - -describe('useHandleSwapCallback', () => { - let onUserInput: jest.Mock - let onChangeRecipient: jest.Mock - - beforeEach(() => { - onChangeRecipient = jest.fn() - onUserInput = jest.fn() - - mockUseSwapActionHandlers.mockReturnValue({ onChangeRecipient, onUserInput } as any) - mockUseSwapFlowContext.mockReturnValue(1 as any) - mockUseEthFlowContext.mockReturnValue(1 as any) - mockUseSafeBundleFlowContext.mockReturnValue(1 as any) - mockUseSafeBundleEthFlowContext.mockReturnValue(1 as any) - - mockSwapFlow.mockImplementation(() => Promise.resolve()) - mockEthFlow.mockImplementation(() => Promise.resolve()) - mockSafeBundleApprovalFlow.mockImplementation(() => Promise.resolve()) - mockSafeBundleEthFlow.mockImplementation(() => Promise.resolve()) - }) - - it('When a swap happened, then the recipient value should be deleted', async () => { - const { result } = renderHook(() => useHandleSwap(priceImpactMock), { wrapper: WithProviders }) - - await result.current() - - expect(onChangeRecipient).toBeCalledTimes(1) - expect(onChangeRecipient).toHaveBeenCalledWith(null) - expect(onUserInput).toBeCalledTimes(1) - expect(onUserInput).toHaveBeenCalledWith(Field.INPUT, '') - }) -}) diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts index b345a36269..76766178c2 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts @@ -1,46 +1,72 @@ import { useCallback } from 'react' -import { PriceImpact } from 'legacy/hooks/usePriceImpact' import { Field } from 'legacy/state/types' -import { useSafeBundleApprovalFlowContext } from 'modules/swap/hooks/useSafeBundleApprovalFlowContext' import { ethFlow } from 'modules/swap/services/ethFlow' import { safeBundleApprovalFlow, safeBundleEthFlow } from 'modules/swap/services/safeBundleFlow' import { swapFlow } from 'modules/swap/services/swapFlow' +import { FlowType, useSafeBundleFlowContext, useTradeFlowType, useTradePriceImpact } from 'modules/trade' import { logTradeFlow } from 'modules/trade/utils/logger' import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' import { useEthFlowContext } from './useEthFlowContext' -import { useSafeBundleEthFlowContext } from './useSafeBundleEthFlowContext' import { useSwapFlowContext } from './useSwapFlowContext' import { useSwapActionHandlers } from './useSwapState' -export function useHandleSwap(priceImpactParams: PriceImpact): () => Promise { +export function useHandleSwap() { + const tradeFlowType = useTradeFlowType() const swapFlowContext = useSwapFlowContext() + const safeBundleFlowContext = useSafeBundleFlowContext() const ethFlowContext = useEthFlowContext() - const safeBundleApprovalFlowContext = useSafeBundleApprovalFlowContext() - const safeBundleEthFlowContext = useSafeBundleEthFlowContext() const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() const { onChangeRecipient, onUserInput } = useSwapActionHandlers() + const priceImpactParams = useTradePriceImpact() - return useCallback(async () => { - if (!swapFlowContext && !ethFlowContext && !safeBundleApprovalFlowContext && !safeBundleEthFlowContext) return + const contextIsReady = + Boolean( + tradeFlowType === FlowType.EOA_ETH_FLOW + ? ethFlowContext + : [FlowType.SAFE_BUNDLE_ETH, FlowType.SAFE_BUNDLE_APPROVAL].includes(tradeFlowType) + ? safeBundleFlowContext + : swapFlowContext, + ) && !!swapFlowContext + const callback = useCallback(async () => { const tradeResult = await (async () => { - if (safeBundleApprovalFlowContext) { + if (!swapFlowContext) return + + if (tradeFlowType === FlowType.SAFE_BUNDLE_APPROVAL) { + if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') + logTradeFlow('SAFE BUNDLE APPROVAL FLOW', 'Start safe bundle approval flow') - return safeBundleApprovalFlow(safeBundleApprovalFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - } else if (safeBundleEthFlowContext) { + return safeBundleApprovalFlow( + swapFlowContext, + safeBundleFlowContext, + priceImpactParams, + confirmPriceImpactWithoutFee, + ) + } + if (tradeFlowType === FlowType.SAFE_BUNDLE_ETH) { + if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') + logTradeFlow('SAFE BUNDLE ETH FLOW', 'Start safe bundle eth flow') - return safeBundleEthFlow(safeBundleEthFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - } else if (swapFlowContext) { - logTradeFlow('SWAP FLOW', 'Start swap flow') - return swapFlow(swapFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - } else if (ethFlowContext) { + return safeBundleEthFlow( + swapFlowContext, + safeBundleFlowContext, + priceImpactParams, + confirmPriceImpactWithoutFee, + ) + } + if (tradeFlowType === FlowType.EOA_ETH_FLOW) { + if (!ethFlowContext) throw new Error('Eth flow context is not ready') + logTradeFlow('ETH FLOW', 'Start eth flow') - return ethFlow(ethFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) + return ethFlow(swapFlowContext, ethFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) } + + logTradeFlow('SWAP FLOW', 'Start swap flow') + return swapFlow(swapFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) })() const isPriceImpactDeclined = tradeResult === false @@ -52,12 +78,13 @@ export function useHandleSwap(priceImpactParams: PriceImpact): () => Promise { - if ( - !baseContext || - !baseContext.context.trade || - !erc20Contract || - baseContext.context.flowType !== FlowType.SAFE_BUNDLE_APPROVAL - ) { - return null - } - - return { - ...baseContext, - erc20Contract, - } - }, [baseContext, erc20Contract]) -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSafeBundleEthFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSafeBundleEthFlowContext.ts deleted file mode 100644 index cc8bfb1a15..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSafeBundleEthFlowContext.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useMemo } from 'react' - -import { SafeBundleEthFlowContext } from 'modules/swap/services/types' - -import { useWETHContract } from 'common/hooks/useContract' -import { useNeedsApproval } from 'common/hooks/useNeedsApproval' - -import { useBaseSafeBundleFlowContext } from './useBaseSafeBundleFlowContext' - -import { FlowType } from '../types/flowContext' - -export function useSafeBundleEthFlowContext(): SafeBundleEthFlowContext | null { - const baseContext = useBaseSafeBundleFlowContext() - - const wrappedNativeContract = useWETHContract() - const needsApproval = useNeedsApproval(baseContext?.context.inputAmountWithSlippage) - - return useMemo(() => { - if (!wrappedNativeContract || !baseContext || baseContext.context.flowType !== FlowType.SAFE_BUNDLE_ETH) return null - - return { - ...baseContext, - wrappedNativeContract, - needsApproval, - } - }, [baseContext, wrappedNativeContract, needsApproval]) -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts index f92a26c36a..7d748de6d9 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts @@ -12,7 +12,6 @@ import { } from '@cowprotocol/wallet' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' -import { PriceImpact } from 'legacy/hooks/usePriceImpact' import { useToggleWalletModal } from 'legacy/state/application/hooks' import { useGetQuoteAndStatus, useIsBestQuoteLoading } from 'legacy/state/price/hooks' import { Field } from 'legacy/state/types' @@ -20,10 +19,7 @@ import { Field } from 'legacy/state/types' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useTokenSupportsPermit } from 'modules/permit' import { getSwapButtonState } from 'modules/swap/helpers/getSwapButtonState' -import { useEthFlowContext } from 'modules/swap/hooks/useEthFlowContext' import { useHandleSwap } from 'modules/swap/hooks/useHandleSwap' -import { useSafeBundleApprovalFlowContext } from 'modules/swap/hooks/useSafeBundleApprovalFlowContext' -import { useSwapFlowContext } from 'modules/swap/hooks/useSwapFlowContext' import { SwapButtonsContext } from 'modules/swap/pure/SwapButtons' import { TradeType, useTradeConfirmActions, useWrapNativeFlow } from 'modules/trade' import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut' @@ -34,18 +30,17 @@ import { QuoteDeadlineParams } from 'modules/tradeQuote' import { useApproveState } from 'common/hooks/useApproveState' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { useSafeBundleEthFlowContext } from './useSafeBundleEthFlowContext' +import { useSwapFlowContext } from './useSwapFlowContext' import { useDerivedSwapInfo, useSwapActionHandlers } from './useSwapState' export interface SwapButtonInput { feeWarningAccepted: boolean impactWarningAccepted: boolean - priceImpactParams: PriceImpact openNativeWrapModal(): void } export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext { - const { feeWarningAccepted, impactWarningAccepted, openNativeWrapModal, priceImpactParams } = input + const { feeWarningAccepted, impactWarningAccepted, openNativeWrapModal } = input const { account, chainId } = useWalletInfo() const { isSupportedWallet } = useWalletDetails() @@ -58,14 +53,11 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext inputError: swapInputError, } = useDerivedSwapInfo() const toggleWalletModal = useToggleWalletModal() - const swapFlowContext = useSwapFlowContext() - const ethFlowContext = useEthFlowContext() - const safeBundleApprovalFlowContext = useSafeBundleApprovalFlowContext() - const safeBundleEthFlowContext = useSafeBundleEthFlowContext() const { onCurrencySelection } = useSwapActionHandlers() const isBestQuoteLoading = useIsBestQuoteLoading() const tradeConfirmActions = useTradeConfirmActions() const { standaloneMode } = useInjectedWidgetParams() + const tradeFlowContext = useSwapFlowContext() const currencyIn = currencies[Field.INPUT] const currencyOut = currencies[Field.OUTPUT] @@ -86,12 +78,11 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext const wrapCallback = useWrapNativeFlow() const { state: approvalState } = useApproveState(slippageAdjustedSellAmount || null) - const handleSwap = useHandleSwap(priceImpactParams) + const { callback: handleSwap, contextIsReady } = useHandleSwap() - const contextExists = ethFlowContext || swapFlowContext || safeBundleApprovalFlowContext || safeBundleEthFlowContext - const recipientAddressOrName = contextExists?.orderParams.recipientAddressOrName || null + const recipientAddressOrName = tradeFlowContext?.orderParams.recipientAddressOrName || null - const swapCallbackError = contextExists ? null : 'Missing dependencies' + const swapCallbackError = contextIsReady ? null : 'Missing dependencies' const gnosisSafeInfo = useGnosisSafeInfo() const isReadonlyGnosisSafeUser = gnosisSafeInfo?.isReadOnly || false @@ -106,7 +97,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext quoteValidTo: quote?.quoteValidTo, localQuoteTimestamp: quote?.localQuoteTimestamp, }), - [quote?.validFor, quote?.quoteValidTo, quote?.localQuoteTimestamp] + [quote?.validFor, quote?.quoteValidTo, quote?.localQuoteTimestamp], ) const swapButtonState = getSwapButtonState({ @@ -166,7 +157,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext recipientAddressOrName, standaloneMode, quoteDeadlineParams, - ] + ], ) } diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapConfirmButtonText.tsx b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapConfirmButtonText.tsx index 685d4c3bc7..6f249918a5 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapConfirmButtonText.tsx +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapConfirmButtonText.tsx @@ -6,9 +6,9 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Nullish } from 'types' -import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' +import { useIsSafeEthFlow } from 'modules/trade' -import { useIsSafeEthFlow } from './useIsSafeEthFlow' +import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' export function useSwapConfirmButtonText(slippageAdjustedSellAmount: Nullish>) { const isSafeApprovalBundle = useIsSafeApprovalBundle(slippageAdjustedSellAmount) diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts index 221dc1fff5..9f02680d3f 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts @@ -1,62 +1,8 @@ -import { useMemo } from 'react' +import { useUserTransactionTTL } from 'legacy/state/user/hooks' -import { getWrappedToken } from '@cowprotocol/common-utils' -import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' -import { TradeType as UniTradeType } from '@uniswap/sdk-core' +import { useTradeFlowContext } from 'modules/trade' -import { useGeneratePermitHook, usePermitInfo } from 'modules/permit' -import { getFlowContext, useBaseFlowContextSource } from 'modules/swap/hooks/useFlowContext' -import { SwapFlowContext } from 'modules/swap/services/types' -import { useEnoughBalanceAndAllowance } from 'modules/tokens' -import { TradeType } from 'modules/trade' - -import { useGP2SettlementContract } from 'common/hooks/useContract' - -import { FlowType } from '../types/flowContext' - -export function useSwapFlowContext(): SwapFlowContext | null { - const contract = useGP2SettlementContract() - const baseProps = useBaseFlowContextSource() - const sellCurrency = baseProps?.trade?.inputAmount?.currency - const permitInfo = usePermitInfo(sellCurrency, TradeType.SWAP) - const generatePermitHook = useGeneratePermitHook() - - const checkAllowanceAddress = COW_PROTOCOL_VAULT_RELAYER_ADDRESS[baseProps?.chainId || SupportedChainId.MAINNET] - const { enoughAllowance } = useEnoughBalanceAndAllowance({ - account: baseProps?.account, - amount: baseProps?.inputAmountWithSlippage, - checkAllowanceAddress, - }) - - return useMemo(() => { - if (!baseProps?.trade) { - return null - } - - const baseContext = getFlowContext({ - baseProps, - sellToken: getWrappedToken(baseProps.trade.inputAmount.currency), - kind: baseProps.trade.tradeType === UniTradeType.EXACT_INPUT ? OrderKind.SELL : OrderKind.BUY, - }) - - if (!contract || !baseContext || baseProps.flowType !== FlowType.REGULAR) { - return null - } - - return { - ...baseContext, - context: { - ...baseContext.context, - inputAmount: baseProps.trade.inputAmount, - outputAmount: baseProps.trade.outputAmount, - }, - callbacks: { - ...baseContext.callbacks, - dispatch: baseProps.dispatch, - }, - contract, - permitInfo: !enoughAllowance ? permitInfo : undefined, - generatePermitHook, - } - }, [baseProps, contract, enoughAllowance, permitInfo, generatePermitHook]) +export function useSwapFlowContext() { + const [deadline] = useUserTransactionTTL() + return useTradeFlowContext({ deadline }) } diff --git a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts index 2b058aa17e..011cc5a09e 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts @@ -8,6 +8,7 @@ import { removePermitHookFromAppData } from 'modules/appData' import { emitPostedOrderEvent } from 'modules/orders' import { signEthFlowOrderStep } from 'modules/swap/services/ethFlow/steps/signEthFlowOrderStep' import { EthFlowContext } from 'modules/swap/services/types' +import { TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' @@ -17,6 +18,7 @@ import { isQuoteExpired } from 'modules/tradeQuote' import { calculateUniqueOrderId } from './steps/calculateUniqueOrderId' export async function ethFlow( + tradeContext: TradeFlowContext, ethFlowContext: EthFlowContext, priceImpactParams: PriceImpact, confirmPriceImpactWithoutFee: (priceImpact: Percent) => Promise, @@ -25,20 +27,14 @@ export async function ethFlow( tradeConfirmActions, swapFlowAnalyticsContext, context, - contract, callbacks, - appDataInfo, - dispatch, orderParams: orderParamsOriginal, - checkEthFlowOrderExists, - addInFlightOrderId, - quote, typedHooks, - } = ethFlowContext - const { - chainId, - trade: { inputAmount, outputAmount, fee }, - } = context + } = tradeContext + const { contract, appData, uploadAppData, addTransaction, checkEthFlowOrderExists, addInFlightOrderId, quote } = + ethFlowContext + + const { chainId, inputAmount, outputAmount } = context const tradeAmounts = { inputAmount, outputAmount } const { account, recipientAddressOrName, kind } = orderParamsOriginal @@ -61,7 +57,7 @@ export async function ethFlow( // Do not proceed if fee is expired if ( isQuoteExpired({ - expirationDate: fee.expirationDate, + expirationDate: quote?.fee?.expirationDate, deadlineParams: { validFor: quote?.validFor, quoteValidTo: quote?.quoteValidTo, @@ -69,7 +65,7 @@ export async function ethFlow( }, }) ) { - reportPlaceOrderWithExpiredQuote({ ...orderParamsOriginal, fee }) + reportPlaceOrderWithExpiredQuote({ ...orderParamsOriginal, fee: quote?.fee }) throw new Error('Quote expired. Please refresh.') } @@ -105,13 +101,13 @@ export async function ethFlow( order, isSafeWallet: orderParams.isSafeWallet, }, - dispatch, + callbacks.dispatch, ) // TODO: maybe move this into addPendingOrderStep? - ethFlowContext.addTransaction({ hash: txReceipt.hash, ethFlow: { orderId: order.id, subType: 'creation' } }) + addTransaction({ hash: txReceipt.hash, ethFlow: { orderId: order.id, subType: 'creation' } }) logTradeFlow('ETH FLOW', 'STEP 6: add app data to upload queue') - callbacks.uploadAppData({ chainId: context.chainId, orderId, appData: appDataInfo }) + uploadAppData({ chainId: context.chainId, orderId, appData }) logTradeFlow('ETH FLOW', 'STEP 7: show UI of the successfully sent transaction', orderId) tradeConfirmActions.onSuccess(orderId) diff --git a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts b/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts index c31a835586..bb4f6593bd 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts @@ -11,7 +11,7 @@ import { buildApproveTx } from 'modules/operations/bundle/buildApproveTx' import { buildPresignTx } from 'modules/operations/bundle/buildPresignTx' import { buildZeroApproveTx } from 'modules/operations/bundle/buildZeroApproveTx' import { emitPostedOrderEvent } from 'modules/orders' -import { SafeBundleApprovalFlowContext } from 'modules/swap/services/types' +import { SafeBundleFlowContext, TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' @@ -21,7 +21,8 @@ import { shouldZeroApprove as shouldZeroApproveFn } from 'modules/zeroApproval' const LOG_PREFIX = 'SAFE APPROVAL BUNDLE FLOW' export async function safeBundleApprovalFlow( - input: SafeBundleApprovalFlowContext, + tradeContext: TradeFlowContext, + safeBundleContext: SafeBundleFlowContext, priceImpactParams: PriceImpact, confirmPriceImpactWithoutFee: (priceImpact: Percent) => Promise, ): Promise { @@ -31,19 +32,9 @@ export async function safeBundleApprovalFlow( return false } - const { - erc20Contract, - spender, - context, - callbacks, - dispatch, - orderParams, - settlementContract, - safeAppsSdk, - swapFlowAnalyticsContext, - tradeConfirmActions, - typedHooks, - } = input + const { context, callbacks, orderParams, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext + + const { spender, settlementContract, safeAppsSdk, erc20Contract } = safeBundleContext const { chainId } = context const { account, isSafeWallet, recipientAddressOrName, inputAmount, outputAmount, kind } = orderParams @@ -59,7 +50,7 @@ export async function safeBundleApprovalFlow( const approveTx = await buildApproveTx({ erc20Contract, spender, - amountToApprove: context.trade.inputAmount, + amountToApprove: context.inputAmount, }) orderParams.appData = await removePermitHookFromAppData(orderParams.appData, typedHooks) @@ -79,7 +70,7 @@ export async function safeBundleApprovalFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) logTradeFlow(LOG_PREFIX, 'STEP 4: build presign tx') @@ -94,7 +85,7 @@ export async function safeBundleApprovalFlow( const shouldZeroApprove = await shouldZeroApproveFn({ tokenContract: erc20Contract, spender, - amountToApprove: context.trade.inputAmount, + amountToApprove: context.inputAmount, isBundle: true, }) @@ -102,7 +93,7 @@ export async function safeBundleApprovalFlow( const zeroApproveTx = await buildZeroApproveTx({ erc20Contract, spender, - currency: context.trade.inputAmount.currency, + currency: context.inputAmount.currency, }) safeTransactionData.unshift({ to: zeroApproveTx.to!, @@ -137,7 +128,7 @@ export async function safeBundleApprovalFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) tradeFlowAnalytics.sign(swapFlowAnalyticsContext) diff --git a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts b/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts index 16377662e1..713fc6f083 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts @@ -12,7 +12,7 @@ import { buildApproveTx } from 'modules/operations/bundle/buildApproveTx' import { buildPresignTx } from 'modules/operations/bundle/buildPresignTx' import { buildWrapTx } from 'modules/operations/bundle/buildWrapTx' import { emitPostedOrderEvent } from 'modules/orders' -import { SafeBundleEthFlowContext } from 'modules/swap/services/types' +import { SafeBundleFlowContext, TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' @@ -21,7 +21,8 @@ import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' const LOG_PREFIX = 'SAFE BUNDLE ETH FLOW' export async function safeBundleEthFlow( - input: SafeBundleEthFlowContext, + tradeContext: TradeFlowContext, + safeBundleContext: SafeBundleFlowContext, priceImpactParams: PriceImpact, confirmPriceImpactWithoutFee: (priceImpact: Percent) => Promise, ): Promise { @@ -31,27 +32,12 @@ export async function safeBundleEthFlow( return false } - const { - wrappedNativeContract, - needsApproval, - spender, - context, - callbacks, - dispatch, - orderParams, - settlementContract, - safeAppsSdk, - swapFlowAnalyticsContext, - tradeConfirmActions, - typedHooks, - } = input + const { context, callbacks, orderParams, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext + + const { spender, settlementContract, safeAppsSdk, needsApproval, wrappedNativeContract } = safeBundleContext const { account, recipientAddressOrName, kind } = orderParams - const { - inputAmountWithSlippage, - chainId, - trade: { inputAmount, outputAmount }, - } = context + const { inputAmountWithSlippage, chainId, inputAmount, outputAmount } = context tradeFlowAnalytics.wrapApproveAndPresign(swapFlowAnalyticsContext) const nativeAmountInWei = inputAmountWithSlippage.quotient.toString() @@ -107,7 +93,7 @@ export async function safeBundleEthFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) logTradeFlow(LOG_PREFIX, 'STEP 5: build presign tx') @@ -148,7 +134,7 @@ export async function safeBundleEthFlow( }, isSafeWallet, }, - dispatch, + callbacks.dispatch, ) tradeFlowAnalytics.sign(swapFlowAnalyticsContext) diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts b/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts index c08f6c5a92..5269c55cfd 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts @@ -10,6 +10,7 @@ import { signAndPostOrder } from 'legacy/utils/trade' import { emitPostedOrderEvent } from 'modules/orders' import { handlePermit } from 'modules/permit' import { callDataContainsPermitSigner } from 'modules/permit' +import { TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' @@ -17,10 +18,8 @@ import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' import { presignOrderStep } from './steps/presignOrderStep' -import { SwapFlowContext } from '../types' - export async function swapFlow( - input: SwapFlowContext, + input: TradeFlowContext, priceImpactParams: PriceImpact, confirmPriceImpactWithoutFee: (priceImpact: Percent) => Promise, ): Promise { diff --git a/apps/cowswap-frontend/src/modules/swap/services/types.ts b/apps/cowswap-frontend/src/modules/swap/services/types.ts index 173fea8d0e..cc24db27a4 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/types.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/types.ts @@ -1,92 +1,18 @@ -import { CoWSwapEthFlow, Erc20, GPv2Settlement, Weth } from '@cowprotocol/abis' -import { Command } from '@cowprotocol/types' -import { Web3Provider } from '@ethersproject/providers' -import SafeAppsSDK from '@safe-global/safe-apps-sdk' -import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { CoWSwapEthFlow } from '@cowprotocol/abis' -import { AppDispatch } from 'legacy/state' import { useTransactionAdder } from 'legacy/state/enhancedTransactions/hooks' import type { QuoteInformationObject } from 'legacy/state/price/reducer' -import TradeGp from 'legacy/state/swap/TradeGp' -import { PostOrderParams } from 'legacy/utils/trade' -import { AppDataInfo, TypedAppDataHooks, UploadAppDataParams } from 'modules/appData' -import { GeneratePermitHook, IsTokenPermittableResult, useGetCachedPermit } from 'modules/permit' -import { TradeConfirmActions } from 'modules/trade' -import { TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics' +import { AppDataInfo, UploadAppDataParams } from 'modules/appData' import { EthFlowOrderExistsCallback } from '../hooks/useCheckEthFlowOrderExists' -import { FlowType } from '../types/flowContext' -export interface BaseFlowContext { - context: { - chainId: number - trade: TradeGp - inputAmountWithSlippage: CurrencyAmount - flowType: FlowType - } - flags: { - allowsOffchainSigning: boolean - } - callbacks: { - closeModals: Command - uploadAppData: (params: UploadAppDataParams) => void - getCachedPermit: ReturnType - } - sellTokenContract: Erc20 | null - dispatch: AppDispatch - swapFlowAnalyticsContext: TradeFlowAnalyticsContext - orderParams: PostOrderParams - appDataInfo: AppDataInfo - tradeConfirmActions: TradeConfirmActions - quote: QuoteInformationObject | undefined - typedHooks?: TypedAppDataHooks -} - -export type SwapFlowContext = { - context: { - chainId: number - inputAmount: CurrencyAmount - outputAmount: CurrencyAmount - inputAmountWithSlippage: CurrencyAmount - } - flags: { - allowsOffchainSigning: boolean - } - callbacks: { - closeModals: Command - getCachedPermit: ReturnType - dispatch: AppDispatch - } - tradeConfirmActions: TradeConfirmActions - swapFlowAnalyticsContext: TradeFlowAnalyticsContext - orderParams: PostOrderParams - contract: GPv2Settlement - permitInfo: IsTokenPermittableResult - generatePermitHook: GeneratePermitHook - typedHooks?: TypedAppDataHooks -} - -export type EthFlowContext = BaseFlowContext & { +export type EthFlowContext = { contract: CoWSwapEthFlow addTransaction: ReturnType checkEthFlowOrderExists: EthFlowOrderExistsCallback addInFlightOrderId: (orderId: string) => void quote: QuoteInformationObject | undefined -} - -export type BaseSafeFlowContext = BaseFlowContext & { - settlementContract: GPv2Settlement - spender: string - safeAppsSdk: SafeAppsSDK - provider: Web3Provider -} - -export type SafeBundleApprovalFlowContext = BaseSafeFlowContext & { - erc20Contract: Erc20 -} - -export type SafeBundleEthFlowContext = BaseSafeFlowContext & { - wrappedNativeContract: Weth - needsApproval: boolean + uploadAppData: (params: UploadAppDataParams) => void + appData: AppDataInfo } diff --git a/apps/cowswap-frontend/src/modules/swap/state/baseFlowContextSourceAtom.ts b/apps/cowswap-frontend/src/modules/swap/state/baseFlowContextSourceAtom.ts deleted file mode 100644 index 5295aaffde..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/state/baseFlowContextSourceAtom.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { atom } from 'jotai' - -import { BaseFlowContextSource } from '../types/flowContext' - -export const baseFlowContextSourceAtom = atom(null) diff --git a/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts b/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts deleted file mode 100644 index 2051f2ed3f..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/types/flowContext.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Erc20, Weth } from '@cowprotocol/abis' -import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import type { Command } from '@cowprotocol/types' -import type { GnosisSafeInfo } from '@cowprotocol/wallet' -import type { Web3Provider } from '@ethersproject/providers' -import type { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' - -import type { AppDispatch } from 'legacy/state' -import type { QuoteInformationObject } from 'legacy/state/price/reducer' -import type TradeGp from 'legacy/state/swap/TradeGp' - -import type { useGetCachedPermit } from 'modules/permit' -import type { TradeConfirmActions } from 'modules/trade' - -import type { AppDataInfo, TypedAppDataHooks, UploadAppDataParams } from '../../appData' - -export enum FlowType { - REGULAR = 'REGULAR', - EOA_ETH_FLOW = 'EOA_ETH_FLOW', - SAFE_BUNDLE_APPROVAL = 'SAFE_BUNDLE_APPROVAL', - SAFE_BUNDLE_ETH = 'SAFE_BUNDLE_ETH', -} - -export interface BaseFlowContextSource { - chainId: SupportedChainId - account: string | undefined - sellTokenContract: Erc20 | null - provider: Web3Provider | undefined - trade: TradeGp | undefined - appData: AppDataInfo | null - wethContract: Weth | null - inputAmountWithSlippage: CurrencyAmount | undefined - gnosisSafeInfo: GnosisSafeInfo | undefined - recipient: string | null - recipientAddressOrName: string | null - deadline: number - ensRecipientAddress: string | null - allowsOffchainSigning: boolean - flowType: FlowType - closeModals: Command - uploadAppData: (update: UploadAppDataParams) => void - dispatch: AppDispatch - allowedSlippage: Percent - tradeConfirmActions: TradeConfirmActions - getCachedPermit: ReturnType - quote: QuoteInformationObject | undefined - typedHooks: TypedAppDataHooks | undefined -} diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx b/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx deleted file mode 100644 index 95b2e62f78..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/updaters/BaseFlowContextUpdater.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useSetAtom } from 'jotai' -import { useEffect } from 'react' - -import { getAddress } from '@cowprotocol/common-utils' -import { useENSAddress } from '@cowprotocol/ens' -import { useGnosisSafeInfo, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' - -import { useDispatch } from 'react-redux' - -import { AppDispatch } from 'legacy/state' -import { useCloseModals } from 'legacy/state/application/hooks' -import { useGetQuoteAndStatus } from 'legacy/state/price/hooks' -import { useUserTransactionTTL } from 'legacy/state/user/hooks' - -import { useAppData, useAppDataHooks, useUploadAppData } from 'modules/appData' -import { useGetCachedPermit } from 'modules/permit' -import { useTradeConfirmActions, useIsEoaEthFlow } from 'modules/trade' -import { useTradeSlippage } from 'modules/tradeSlippage' - -import { useTokenContract, useWETHContract } from 'common/hooks/useContract' -import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' -import { useSafeMemo } from 'common/hooks/useSafeMemo' - -import { useSwapAmountsWithSlippage } from '../hooks/useFlowContext' -import { useIsSafeEthFlow } from '../hooks/useIsSafeEthFlow' -import { useDerivedSwapInfo, useSwapState } from '../hooks/useSwapState' -import { baseFlowContextSourceAtom } from '../state/baseFlowContextSourceAtom' -import { FlowType } from '../types/flowContext' - -export function BaseFlowContextUpdater() { - const setBaseFlowContextSource = useSetAtom(baseFlowContextSourceAtom) - const provider = useWalletProvider() - const { account, chainId } = useWalletInfo() - const { allowsOffchainSigning } = useWalletDetails() - const gnosisSafeInfo = useGnosisSafeInfo() - const { recipient } = useSwapState() - const slippage = useTradeSlippage() - const { trade, currenciesIds } = useDerivedSwapInfo() - const { quote } = useGetQuoteAndStatus({ - token: currenciesIds.INPUT, - chainId, - }) - - const appData = useAppData() - const typedHooks = useAppDataHooks() - const closeModals = useCloseModals() - const uploadAppData = useUploadAppData() - const dispatch = useDispatch() - const tradeConfirmActions = useTradeConfirmActions() - - const { address: ensRecipientAddress } = useENSAddress(recipient) - const recipientAddressOrName = recipient || ensRecipientAddress - const [deadline] = useUserTransactionTTL() - const wethContract = useWETHContract() - const isEoaEthFlow = useIsEoaEthFlow() - const isSafeEthFlow = useIsSafeEthFlow() - const getCachedPermit = useGetCachedPermit() - - const [inputAmountWithSlippage, outputAmountWithSlippage] = useSwapAmountsWithSlippage() - const sellTokenContract = useTokenContract(getAddress(inputAmountWithSlippage?.currency) || undefined, true) - - const isSafeBundle = useIsSafeApprovalBundle(inputAmountWithSlippage) - const flowType = getFlowType(isSafeBundle, isEoaEthFlow, isSafeEthFlow) - - const source = useSafeMemo( - () => ({ - chainId, - account, - sellTokenContract, - provider, - trade, - appData, - wethContract, - inputAmountWithSlippage, - outputAmountWithSlippage, - gnosisSafeInfo, - recipient, - recipientAddressOrName, - deadline, - ensRecipientAddress, - allowsOffchainSigning, - uploadAppData, - flowType, - closeModals, - dispatch, - allowedSlippage: slippage, - tradeConfirmActions, - getCachedPermit, - quote, - typedHooks, - }), - [ - chainId, - account, - sellTokenContract, - provider, - trade, - appData, - wethContract, - inputAmountWithSlippage, - outputAmountWithSlippage, - gnosisSafeInfo, - recipient, - recipientAddressOrName, - deadline, - ensRecipientAddress, - allowsOffchainSigning, - uploadAppData, - flowType, - closeModals, - dispatch, - slippage, - tradeConfirmActions, - getCachedPermit, - quote, - typedHooks, - ], - ) - - useEffect(() => { - setBaseFlowContextSource(source) - }, [source, setBaseFlowContextSource]) - - return null -} - -function getFlowType(isSafeBundle: boolean, isEoaEthFlow: boolean, isSafeEthFlow: boolean): FlowType { - if (isSafeEthFlow) { - // Takes precedence over bundle approval - return FlowType.SAFE_BUNDLE_ETH - } - if (isSafeBundle) { - // Takes precedence over eth flow - return FlowType.SAFE_BUNDLE_APPROVAL - } - if (isEoaEthFlow) { - // Takes precedence over regular flow - return FlowType.EOA_ETH_FLOW - } - return FlowType.REGULAR -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useIsSafeEthFlow.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useIsSafeEthFlow.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/hooks/useIsSafeEthFlow.ts rename to apps/cowswap-frontend/src/modules/trade/hooks/useIsSafeEthFlow.ts diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useIsSwapEth.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useIsSwapEth.ts similarity index 53% rename from apps/cowswap-frontend/src/modules/swap/hooks/useIsSwapEth.ts rename to apps/cowswap-frontend/src/modules/trade/hooks/useIsSwapEth.ts index 30b3a94d08..b73d67afca 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useIsSwapEth.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useIsSwapEth.ts @@ -1,5 +1,5 @@ -import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut' -import { useIsWrapOrUnwrap } from 'modules/trade/hooks/useIsWrapOrUnwrap' +import { useIsNativeIn } from './useIsNativeInOrOut' +import { useIsWrapOrUnwrap } from './useIsWrapOrUnwrap' export function useIsSwapEth(): boolean { const isNativeIn = useIsNativeIn() diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts new file mode 100644 index 0000000000..b20ef22326 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts @@ -0,0 +1,47 @@ +import { useMemo } from 'react' + +import { getCurrencyAddress } from '@cowprotocol/common-utils' +import { useSafeAppsSdk } from '@cowprotocol/wallet' + +import useSWR from 'swr' + +import { useGP2SettlementContract, useTokenContract, useWETHContract } from 'common/hooks/useContract' +import { useNeedsApproval } from 'common/hooks/useNeedsApproval' +import { useTradeSpenderAddress } from 'common/hooks/useTradeSpenderAddress' + +import { useReceiveAmountInfo } from './useReceiveAmountInfo' + +import { SafeBundleFlowContext } from '../types/TradeFlowContext' + +export function useSafeBundleFlowContext(): SafeBundleFlowContext | null { + const settlementContract = useGP2SettlementContract() + const spender = useTradeSpenderAddress() + + const safeAppsSdk = useSafeAppsSdk() + const wrappedNativeContract = useWETHContract() + const receiveAmountInfo = useReceiveAmountInfo() + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const needsApproval = useNeedsApproval(inputAmountWithSlippage) + const inputCurrencyAddress = useMemo(() => { + return inputAmountWithSlippage ? getCurrencyAddress(inputAmountWithSlippage.currency) : undefined + }, [inputAmountWithSlippage]) + const erc20Contract = useTokenContract(inputCurrencyAddress) + + return ( + useSWR( + settlementContract && spender && safeAppsSdk && wrappedNativeContract && erc20Contract + ? [settlementContract, spender, safeAppsSdk, wrappedNativeContract, needsApproval, erc20Contract] + : null, + ([settlementContract, spender, safeAppsSdk, wrappedNativeContract, needsApproval, erc20Contract]) => { + return { + settlementContract, + spender, + safeAppsSdk, + wrappedNativeContract, + needsApproval, + erc20Contract, + } + }, + ).data || null + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts new file mode 100644 index 0000000000..d88fda1d3c --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts @@ -0,0 +1,192 @@ +import { TokenWithLogo } from '@cowprotocol/common-const' +import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' +import { UiOrderType } from '@cowprotocol/types' +import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' +import { useWalletProvider } from '@cowprotocol/wallet-provider' + +import { useDispatch } from 'react-redux' +import useSWR from 'swr' + +import { AppDispatch } from 'legacy/state' +import { useCloseModals } from 'legacy/state/application/hooks' + +import { useAppData, useAppDataHooks } from 'modules/appData' +import { useGeneratePermitHook, useGetCachedPermit, usePermitInfo } from 'modules/permit' +import { useEnoughBalanceAndAllowance } from 'modules/tokens' +import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmActions } from 'modules/trade' +import { getOrderValidTo, useTradeQuote } from 'modules/tradeQuote' + +import { useGP2SettlementContract } from 'common/hooks/useContract' + +import { TradeFlowContext } from '../types/TradeFlowContext' + +interface Params { + deadline: number +} + +export function useTradeFlowContext({ deadline }: Params): TradeFlowContext | null { + const { chainId, account } = useWalletInfo() + const provider = useWalletProvider() + const { allowsOffchainSigning } = useWalletDetails() + const isSafeWallet = useIsSafeWallet() + const derivedTradeState = useDerivedTradeState() + const receiveAmountInfo = useReceiveAmountInfo() + + const sellCurrency = derivedTradeState?.inputCurrency + const inputAmount = receiveAmountInfo?.afterNetworkCosts.sellAmount + const outputAmount = receiveAmountInfo?.afterSlippage.buyAmount + const sellAmountBeforeFee = receiveAmountInfo?.afterNetworkCosts.sellAmount + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency + + const permitInfo = usePermitInfo(sellCurrency, TradeType.YIELD) + const generatePermitHook = useGeneratePermitHook() + const getCachedPermit = useGetCachedPermit() + const closeModals = useCloseModals() + const dispatch = useDispatch() + const tradeConfirmActions = useTradeConfirmActions() + const settlementContract = useGP2SettlementContract() + const appData = useAppData() + const typedHooks = useAppDataHooks() + const tradeQuote = useTradeQuote() + + const checkAllowanceAddress = COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId || SupportedChainId.MAINNET] + const { enoughAllowance } = useEnoughBalanceAndAllowance({ + account, + amount: inputAmountWithSlippage, + checkAllowanceAddress, + }) + + const { inputCurrency: sellToken, outputCurrency: buyToken, recipient, recipientAddress } = derivedTradeState || {} + const quoteParams = tradeQuote?.quoteParams + const quoteResponse = tradeQuote?.response + const localQuoteTimestamp = tradeQuote?.localQuoteTimestamp + + return ( + useSWR( + inputAmount && + outputAmount && + inputAmountWithSlippage && + sellAmountBeforeFee && + networkFee && + sellToken && + buyToken && + account && + provider && + appData && + quoteParams && + quoteResponse && + localQuoteTimestamp && + settlementContract + ? [ + account, + allowsOffchainSigning, + appData, + quoteParams, + quoteResponse, + localQuoteTimestamp, + buyToken, + chainId, + closeModals, + dispatch, + enoughAllowance, + generatePermitHook, + inputAmount, + inputAmountWithSlippage, + networkFee, + outputAmount, + permitInfo, + provider, + recipient, + sellAmountBeforeFee, + sellToken, + settlementContract, + tradeConfirmActions, + typedHooks, + deadline, + ] + : null, + ([ + account, + allowsOffchainSigning, + appData, + quoteParams, + quoteResponse, + localQuoteTimestamp, + buyToken, + chainId, + closeModals, + dispatch, + enoughAllowance, + generatePermitHook, + inputAmount, + inputAmountWithSlippage, + networkFee, + outputAmount, + permitInfo, + provider, + recipient, + sellAmountBeforeFee, + sellToken, + settlementContract, + tradeConfirmActions, + typedHooks, + deadline, + ]) => { + return { + context: { + chainId, + inputAmount, + outputAmount, + inputAmountWithSlippage, + }, + flags: { + allowsOffchainSigning, + }, + callbacks: { + closeModals, + getCachedPermit, + dispatch, + }, + tradeConfirmActions, + swapFlowAnalyticsContext: { + account, + recipient, + recipientAddress, + marketLabel: [inputAmount?.currency.symbol, outputAmount?.currency.symbol].join(','), + orderType: UiOrderType.YIELD, + }, + contract: settlementContract, + permitInfo: !enoughAllowance ? permitInfo : undefined, + generatePermitHook, + typedHooks, + orderParams: { + account, + chainId, + signer: provider.getSigner(), + kind: OrderKind.SELL, + inputAmount, + outputAmount, + sellAmountBeforeFee, + feeAmount: networkFee, + sellToken: sellToken as TokenWithLogo, + buyToken: buyToken as TokenWithLogo, + validTo: getOrderValidTo(deadline, { + validFor: quoteParams.validFor, + quoteValidTo: quoteResponse.quote.validTo, + localQuoteTimestamp, + }), + recipient: recipient || account, + recipientAddressOrName: recipient || null, + allowsOffchainSigning, + appData, + class: OrderClass.MARKET, + partiallyFillable: true, + quoteId: quoteResponse.id, + isSafeWallet, + }, + } + }, + ).data || null + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts new file mode 100644 index 0000000000..cb035bb603 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts @@ -0,0 +1,33 @@ +import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' + +import { useIsEoaEthFlow } from './useIsEoaEthFlow' +import { useIsSafeEthFlow } from './useIsSafeEthFlow' +import { useReceiveAmountInfo } from './useReceiveAmountInfo' + +import { FlowType } from '../types' + +export function useTradeFlowType(): FlowType { + const isEoaEthFlow = useIsEoaEthFlow() + const isSafeEthFlow = useIsSafeEthFlow() + const receiveAmountInfo = useReceiveAmountInfo() + const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount + + const isSafeBundle = useIsSafeApprovalBundle(inputAmountWithSlippage) + return getFlowType(isSafeBundle, isEoaEthFlow, isSafeEthFlow) +} + +function getFlowType(isSafeBundle: boolean, isEoaEthFlow: boolean, isSafeEthFlow: boolean): FlowType { + if (isSafeEthFlow) { + // Takes precedence over bundle approval + return FlowType.SAFE_BUNDLE_ETH + } + if (isSafeBundle) { + // Takes precedence over eth flow + return FlowType.SAFE_BUNDLE_APPROVAL + } + if (isEoaEthFlow) { + // Takes precedence over regular flow + return FlowType.EOA_ETH_FLOW + } + return FlowType.REGULAR +} diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 628a6ebe5e..80ae85e2ac 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -35,6 +35,11 @@ export * from './hooks/useIsEoaEthFlow' export * from './hooks/useShouldPayGas' export * from './hooks/useWrappedToken' export * from './hooks/useUnknownImpactWarning' +export * from './hooks/useIsSwapEth' +export * from './hooks/useIsSafeEthFlow' +export * from './hooks/useTradeFlowType' +export { useTradeFlowContext } from './hooks/useTradeFlowContext' +export { useSafeBundleFlowContext } from './hooks/useSafeBundleFlowContext' export * from './containers/TradeWidget/types' export { useIsNoImpactWarningAccepted } from './containers/NoImpactWarning/index' export * from './utils/getReceiveAmountInfo' diff --git a/apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts b/apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts new file mode 100644 index 0000000000..0e484fcffe --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts @@ -0,0 +1,53 @@ +import { Erc20, GPv2Settlement, Weth } from '@cowprotocol/abis' +import type { Command } from '@cowprotocol/types' +import type SafeAppsSDK from '@safe-global/safe-apps-sdk' +import type { Currency, CurrencyAmount } from '@uniswap/sdk-core' + +import type { AppDispatch } from 'legacy/state' +import type { PostOrderParams } from 'legacy/utils/trade' + +import type { TypedAppDataHooks } from 'modules/appData' +import type { GeneratePermitHook, IsTokenPermittableResult, useGetCachedPermit } from 'modules/permit' + +import type { TradeConfirmActions } from '../hooks/useTradeConfirmActions' +import type { TradeFlowAnalyticsContext } from '../utils/tradeFlowAnalytics' + +export enum FlowType { + REGULAR = 'REGULAR', + EOA_ETH_FLOW = 'EOA_ETH_FLOW', + SAFE_BUNDLE_APPROVAL = 'SAFE_BUNDLE_APPROVAL', + SAFE_BUNDLE_ETH = 'SAFE_BUNDLE_ETH', +} + +export interface TradeFlowContext { + context: { + chainId: number + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + inputAmountWithSlippage: CurrencyAmount + } + flags: { + allowsOffchainSigning: boolean + } + callbacks: { + closeModals: Command + getCachedPermit: ReturnType + dispatch: AppDispatch + } + tradeConfirmActions: TradeConfirmActions + swapFlowAnalyticsContext: TradeFlowAnalyticsContext + orderParams: PostOrderParams + contract: GPv2Settlement + permitInfo: IsTokenPermittableResult + generatePermitHook: GeneratePermitHook + typedHooks?: TypedAppDataHooks +} + +export interface SafeBundleFlowContext { + settlementContract: GPv2Settlement + spender: string + safeAppsSdk: SafeAppsSDK + wrappedNativeContract: Weth + needsApproval: boolean + erc20Contract: Erc20 +} diff --git a/apps/cowswap-frontend/src/modules/trade/types/index.ts b/apps/cowswap-frontend/src/modules/trade/types/index.ts index f179f214e3..9762914b32 100644 --- a/apps/cowswap-frontend/src/modules/trade/types/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/types/index.ts @@ -1,3 +1,4 @@ export * from './TradeDerivedState' export * from './ReceiveAmountInfo' export * from './TradeType' +export * from './TradeFlowContext' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index d220d487d9..ba276ffcae 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -9,13 +9,13 @@ import { useTradeConfirmState, useTradePriceImpact, } from 'modules/trade' +import { useTradeFlowContext } from 'modules/trade' import { useTradeQuote } from 'modules/tradeQuote' import { SettingsTab, TradeRateDetails } from 'modules/tradeWidgetAddons' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' -import { useTradeFlowContext } from '../../hooks/useTradeFlowContext' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' import { useYieldDeadlineState, useYieldRecipientToggleState, useYieldSettings } from '../../hooks/useYieldSettings' import { useYieldWidgetActions } from '../../hooks/useYieldWidgetActions' @@ -44,7 +44,7 @@ export function YieldWidget() { outputCurrencyFiatAmount, recipient, } = useYieldDerivedState() - const tradeFlowContext = useTradeFlowContext() + const tradeFlowContext = useTradeFlowContext({ deadline: deadlineState[0] }) const inputCurrencyInfo: CurrencyInfo = { field: Field.INPUT, diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts deleted file mode 100644 index f92496f1d0..0000000000 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useTradeFlowContext.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { useMemo } from 'react' - -import { TokenWithLogo } from '@cowprotocol/common-const' -import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, OrderClass, OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' -import { UiOrderType } from '@cowprotocol/types' -import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' - -import { useDispatch } from 'react-redux' - -import { AppDispatch } from 'legacy/state' -import { useCloseModals } from 'legacy/state/application/hooks' - -import { useAppData, useAppDataHooks } from 'modules/appData' -import { useGeneratePermitHook, useGetCachedPermit, usePermitInfo } from 'modules/permit' -import type { SwapFlowContext } from 'modules/swap/services/types' -import { useEnoughBalanceAndAllowance } from 'modules/tokens' -import { TradeType, useDerivedTradeState, useReceiveAmountInfo, useTradeConfirmActions } from 'modules/trade' -import { getOrderValidTo, useTradeQuote } from 'modules/tradeQuote' - -import { useGP2SettlementContract } from 'common/hooks/useContract' - -import { useYieldSettings } from './useYieldSettings' - -export function useTradeFlowContext(): SwapFlowContext | null { - const { chainId, account } = useWalletInfo() - const provider = useWalletProvider() - const { allowsOffchainSigning } = useWalletDetails() - const isSafeWallet = useIsSafeWallet() - const derivedTradeState = useDerivedTradeState() - const receiveAmountInfo = useReceiveAmountInfo() - const { deadline } = useYieldSettings() - - const sellCurrency = derivedTradeState?.inputCurrency - const inputAmount = receiveAmountInfo?.afterNetworkCosts.sellAmount - const outputAmount = receiveAmountInfo?.afterSlippage.buyAmount - const sellAmountBeforeFee = receiveAmountInfo?.afterNetworkCosts.sellAmount - const inputAmountWithSlippage = receiveAmountInfo?.afterSlippage.sellAmount - const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency - - const permitInfo = usePermitInfo(sellCurrency, TradeType.YIELD) - const generatePermitHook = useGeneratePermitHook() - const getCachedPermit = useGetCachedPermit() - const closeModals = useCloseModals() - const dispatch = useDispatch() - const tradeConfirmActions = useTradeConfirmActions() - const settlementContract = useGP2SettlementContract() - const appData = useAppData() - const typedHooks = useAppDataHooks() - const tradeQuote = useTradeQuote() - - const checkAllowanceAddress = COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId || SupportedChainId.MAINNET] - const { enoughAllowance } = useEnoughBalanceAndAllowance({ - account, - amount: inputAmountWithSlippage, - checkAllowanceAddress, - }) - - const { inputCurrency: sellToken, outputCurrency: buyToken, recipient, recipientAddress } = derivedTradeState || {} - - return useMemo(() => { - if ( - !inputAmount || - !outputAmount || - !inputAmountWithSlippage || - !sellAmountBeforeFee || - !networkFee || - !sellToken || - !buyToken || - !account || - !provider || - !appData || - !tradeQuote?.quoteParams || - !tradeQuote?.response || - !settlementContract - ) { - return null - } - - return { - context: { - chainId, - inputAmount, - outputAmount, - inputAmountWithSlippage, - }, - flags: { - allowsOffchainSigning, - }, - callbacks: { - closeModals, - getCachedPermit, - dispatch, - }, - tradeConfirmActions, - swapFlowAnalyticsContext: { - account, - recipient, - recipientAddress, - marketLabel: [inputAmount?.currency.symbol, outputAmount?.currency.symbol].join(','), - orderType: UiOrderType.YIELD, - }, - contract: settlementContract, - permitInfo: !enoughAllowance ? permitInfo : undefined, - generatePermitHook, - typedHooks, - orderParams: { - account, - chainId, - signer: provider.getSigner(), - kind: OrderKind.SELL, - inputAmount, - outputAmount, - sellAmountBeforeFee, - feeAmount: networkFee, - sellToken: sellToken as TokenWithLogo, - buyToken: buyToken as TokenWithLogo, - validTo: getOrderValidTo(deadline, { - validFor: tradeQuote.quoteParams.validFor, - quoteValidTo: tradeQuote.response.quote.validTo, - localQuoteTimestamp: tradeQuote.localQuoteTimestamp, - }), - recipient: recipient || account, - recipientAddressOrName: recipient || null, - allowsOffchainSigning, - appData, - class: OrderClass.MARKET, - partiallyFillable: true, - quoteId: tradeQuote.response.id, - isSafeWallet, - }, - } - }, [ - account, - allowsOffchainSigning, - appData, - buyToken, - chainId, - closeModals, - dispatch, - enoughAllowance, - generatePermitHook, - inputAmount, - inputAmountWithSlippage, - networkFee, - outputAmount, - permitInfo, - provider, - tradeQuote, - recipient, - sellAmountBeforeFee, - sellToken, - settlementContract, - tradeConfirmActions, - typedHooks, - deadline, - ]) -} From 057f55286fd6611a7deab1f69ca57a028d08f423 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 19:09:42 +0500 Subject: [PATCH 33/90] feat(yield): support safe bundle swaps --- .../appData/updater/AppDataInfoUpdater.ts | 1 + .../containers/HookDappContainer/index.tsx | 1 + .../useRescueFundsFromProxy.ts | 2 +- .../containers/TenderlySimulate/index.tsx | 2 +- .../hooksStore/dapps/BuildHookApp/index.tsx | 2 +- .../dapps/ClaimGnoHookApp/index.tsx | 2 +- .../hooksStore/dapps/PermitHookApp/index.tsx | 2 +- .../modules/hooksStore/hooks/useAddHook.ts | 2 +- .../modules/hooksStore/hooks/useRemoveHook.ts | 2 +- .../hooks/useSetRecipientOverride.ts | 2 +- .../hooks/useSetupHooksStoreOrderParams.ts | 27 +++--- .../hooksStore/hooks/useTenderlySimulate.ts | 2 +- .../CustomDappLoader/index.tsx | 2 +- .../updaters/iframeDappsManifestUpdater.tsx | 4 +- .../limitOrders/services/tradeFlow/index.ts | 2 +- .../ConfirmSwapModalSetup/index.tsx | 2 +- .../src/modules/swap/hooks/useHandleSwap.ts | 90 ------------------- .../swap/hooks/useHandleSwapOrEthFlow.ts | 42 +++++++++ .../swap/hooks/useSwapButtonContext.ts | 10 +-- .../modules/swap/hooks/useSwapFlowContext.ts | 6 +- .../swap/pure/SwapButtons/index.cosmos.tsx | 1 - .../modules/swap/pure/SwapButtons/index.tsx | 1 - .../modules/swap/services/ethFlow/index.ts | 2 +- .../containers/NoImpactWarning/index.tsx | 2 +- .../TradeConfirmModal/index.cosmos.tsx | 2 +- .../src/modules/trade/index.ts | 3 - .../pure/TradeConfirmation/index.cosmos.tsx | 2 +- .../src/modules/trade/types/index.ts | 1 - .../modules/tradeFlow/hooks/useHandleSwap.ts | 56 ++++++++++++ .../hooks/useSafeBundleFlowContext.ts | 4 +- .../hooks/useTradeFlowContext.ts | 4 +- .../hooks/useTradeFlowType.ts | 8 +- .../src/modules/tradeFlow/index.ts | 4 + .../services/safeBundleFlow/index.ts | 0 .../safeBundleFlow/safeBundleApprovalFlow.ts | 3 +- .../safeBundleFlow/safeBundleEthFlow.ts | 3 +- .../services/swapFlow/README.md | 0 .../services/swapFlow/index.ts | 3 +- .../swapFlow/steps/presignOrderStep.ts | 0 .../services/swapFlow/swapFlow.puml | 0 .../types/TradeFlowContext.ts | 5 +- .../state/slippageValueAndTypeAtom.ts | 3 +- .../twap/hooks/useTwapWarningsContext.ts | 4 +- .../containers/YieldConfirmModal/index.tsx | 26 ++---- .../yield/containers/YieldWidget/index.tsx | 11 +-- .../modules/yield/state/yieldRawStateAtom.ts | 2 +- 46 files changed, 171 insertions(+), 184 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts create mode 100644 apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts create mode 100644 apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts rename apps/cowswap-frontend/src/modules/{trade => tradeFlow}/hooks/useSafeBundleFlowContext.ts (96%) rename apps/cowswap-frontend/src/modules/{trade => tradeFlow}/hooks/useTradeFlowContext.ts (97%) rename apps/cowswap-frontend/src/modules/{trade => tradeFlow}/hooks/useTradeFlowType.ts (81%) create mode 100644 apps/cowswap-frontend/src/modules/tradeFlow/index.ts rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/safeBundleFlow/index.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/safeBundleFlow/safeBundleApprovalFlow.ts (98%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/safeBundleFlow/safeBundleEthFlow.ts (98%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/swapFlow/README.md (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/swapFlow/index.ts (98%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/swapFlow/steps/presignOrderStep.ts (100%) rename apps/cowswap-frontend/src/modules/{swap => tradeFlow}/services/swapFlow/swapFlow.puml (100%) rename apps/cowswap-frontend/src/modules/{trade => tradeFlow}/types/TradeFlowContext.ts (90%) diff --git a/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts b/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts index eb91c3cc43..83f01614cc 100644 --- a/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts +++ b/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts @@ -88,6 +88,7 @@ export function AppDataInfoUpdater({ typedHooks, volumeFee, replacedOrderUid, + isSmartSlippage, ]) } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/containers/HookDappContainer/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/containers/HookDappContainer/index.tsx index f8eb2cf33e..f678cefe26 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/containers/HookDappContainer/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/containers/HookDappContainer/index.tsx @@ -78,6 +78,7 @@ export function HookDappContainer({ dapp, isPreHook, onDismiss, hookToEdit }: Ho inputCurrencyId, outputCurrencyId, isDarkMode, + orderParams, ]) const dappProps = useMemo(() => ({ context, dapp, isPreHook }), [context, dapp, isPreHook]) diff --git a/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/useRescueFundsFromProxy.ts b/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/useRescueFundsFromProxy.ts index b3e113a014..d5bafe2f30 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/useRescueFundsFromProxy.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/useRescueFundsFromProxy.ts @@ -96,7 +96,7 @@ export function useRescueFundsFromProxy( } finally { setTxSigningInProgress(false) } - }, [provider, proxyAddress, cowShedContract, selectedTokenAddress, account, tokenBalance]) + }, [provider, proxyAddress, cowShedContract, selectedTokenAddress, account, tokenBalance, cowShedHooks]) return { callback, isTxSigningInProgress, proxyAddress } } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx index 800da41ee1..ef3a5adb4a 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx @@ -52,7 +52,7 @@ export function TenderlySimulate({ hook }: TenderlySimulateProps) { } finally { setIsLoading(false) } - }, [simulate, hook, hookId]) + }, [simulate, hook, hookId, setSimulationError]) if (isLoading) { return ( diff --git a/apps/cowswap-frontend/src/modules/hooksStore/dapps/BuildHookApp/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/dapps/BuildHookApp/index.tsx index ffeed661bd..55a7afffe2 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/dapps/BuildHookApp/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/dapps/BuildHookApp/index.tsx @@ -74,7 +74,7 @@ export function BuildHookApp({ context }: HookDappProps) { hook, }) : context.addHook({ hook }) - }, [hook, context, hookToEdit, isPreHook]) + }, [hook, context, hookToEdit]) return ( diff --git a/apps/cowswap-frontend/src/modules/hooksStore/dapps/ClaimGnoHookApp/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/dapps/ClaimGnoHookApp/index.tsx index 6c9be054d0..e5d24ee68f 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/dapps/ClaimGnoHookApp/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/dapps/ClaimGnoHookApp/index.tsx @@ -38,7 +38,7 @@ export function ClaimGnoHookApp({ context }: HookDappProps) { } return SbcDepositContractInterface.encodeFunctionData('claimWithdrawal', [account]) - }, [context]) + }, [context, account]) useEffect(() => { if (!account || !provider) { diff --git a/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/index.tsx index a619b317c6..2f29f33527 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/index.tsx @@ -42,7 +42,7 @@ export function PermitHookApp({ context }: HookDappProps) { } context.addHook({ hook }) - }, [generatePermitHook, context, permitInfo, token, spenderAddress]) + }, [generatePermitHook, context, permitInfo, token, spenderAddress, hookToEdit]) const buttonProps = useMemo(() => { if (!context.account) return { message: 'Connect wallet', disabled: true } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useAddHook.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useAddHook.ts index b0e012b116..a3bf438e4b 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useAddHook.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useAddHook.ts @@ -33,6 +33,6 @@ export function useAddHook(dapp: HookDapp, isPreHook: boolean): AddHook { } }) }, - [updateHooks, dapp], + [updateHooks, dapp, isPreHook], ) } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useRemoveHook.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useRemoveHook.ts index 477f8cb68e..531411e864 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useRemoveHook.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useRemoveHook.ts @@ -25,6 +25,6 @@ export function useRemoveHook(isPreHook: boolean): RemoveHook { } }) }, - [updateHooks], + [updateHooks, isPreHook], ) } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetRecipientOverride.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetRecipientOverride.ts index 63fb6bbe4e..2df029bc11 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetRecipientOverride.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetRecipientOverride.ts @@ -19,5 +19,5 @@ export function useSetRecipientOverride() { if (!hookRecipientOverride || !isHooksTradeType) return onChangeRecipient(hookRecipientOverride) - }, [hookRecipientOverride, isHooksTradeType, isNativeIn]) + }, [hookRecipientOverride, isHooksTradeType, isNativeIn, onChangeRecipient]) } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts index 7ae9db0f0b..6b4fe7ed9f 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useSetupHooksStoreOrderParams.ts @@ -2,25 +2,24 @@ import { useEffect } from 'react' import { getCurrencyAddress } from '@cowprotocol/common-utils' -import { useUserTransactionTTL } from 'legacy/state/user/hooks' - -import { useTradeFlowContext } from 'modules/trade' - import { useSetOrderParams } from './useSetOrderParams' +import { useSwapFlowContext } from '../../swap/hooks/useSwapFlowContext' + export function useSetupHooksStoreOrderParams() { - const [deadline] = useUserTransactionTTL() - const tradeFlowContext = useTradeFlowContext({ deadline }) + const tradeFlowContext = useSwapFlowContext() const setOrderParams = useSetOrderParams() const orderParams = tradeFlowContext?.orderParams useEffect(() => { - if (!orderParams) return - - setOrderParams({ - validTo: orderParams.validTo, - sellTokenAddress: getCurrencyAddress(orderParams.inputAmount.currency), - buyTokenAddress: getCurrencyAddress(orderParams.outputAmount.currency), - }) - }, [orderParams]) + if (!orderParams) { + setOrderParams(null) + } else { + setOrderParams({ + validTo: orderParams.validTo, + sellTokenAddress: getCurrencyAddress(orderParams.inputAmount.currency), + buyTokenAddress: getCurrencyAddress(orderParams.outputAmount.currency), + }) + } + }, [orderParams, setOrderParams]) } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useTenderlySimulate.ts b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useTenderlySimulate.ts index e3d41b223d..64505270cb 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/hooks/useTenderlySimulate.ts +++ b/apps/cowswap-frontend/src/modules/hooksStore/hooks/useTenderlySimulate.ts @@ -20,7 +20,7 @@ export function useTenderlySimulate(): (params: CowHook) => Promise { isRequestRelevant = false } - }, [input, chainId]) + }, [input, isSmartContractWallet, chainId]) return null } diff --git a/apps/cowswap-frontend/src/modules/hooksStore/updaters/iframeDappsManifestUpdater.tsx b/apps/cowswap-frontend/src/modules/hooksStore/updaters/iframeDappsManifestUpdater.tsx index 6a5d950898..82e97b64c4 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/updaters/iframeDappsManifestUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/updaters/iframeDappsManifestUpdater.tsx @@ -3,7 +3,6 @@ import { useAtomValue } from 'jotai/index' import { useCallback, useEffect, useMemo } from 'react' import { HookDappBase, HookDappType } from '@cowprotocol/hook-dapp-lib' -import { useWalletInfo } from '@cowprotocol/wallet' import ms from 'ms.macro' @@ -21,7 +20,6 @@ const getLastUpdateTimestamp = () => { export function IframeDappsManifestUpdater() { const hooksState = useAtomValue(customHookDappsAtom) const upsertCustomHookDapp = useSetAtom(upsertCustomHookDappAtom) - const { chainId } = useWalletInfo() const [preHooks, postHooks] = useMemo( () => [Object.values(hooksState.pre), Object.values(hooksState.post)], @@ -55,7 +53,7 @@ export function IframeDappsManifestUpdater() { } }) }, - [chainId, upsertCustomHookDapp], + [upsertCustomHookDapp], ) /** diff --git a/apps/cowswap-frontend/src/modules/limitOrders/services/tradeFlow/index.ts b/apps/cowswap-frontend/src/modules/limitOrders/services/tradeFlow/index.ts index 4ab16a5a0a..5e956da0d8 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/services/tradeFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/services/tradeFlow/index.ts @@ -14,11 +14,11 @@ import { calculateLimitOrdersDeadline } from 'modules/limitOrders/utils/calculat import { emitPostedOrderEvent } from 'modules/orders' import { handlePermit } from 'modules/permit' import { callDataContainsPermitSigner } from 'modules/permit' -import { presignOrderStep } from 'modules/swap/services/swapFlow/steps/presignOrderStep' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' import { TradeFlowAnalyticsContext, tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' +import { presignOrderStep } from 'modules/tradeFlow/services/swapFlow/steps/presignOrderStep' export async function tradeFlow( params: TradeFlowContext, diff --git a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx index 8877d4e503..0403137082 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx @@ -94,7 +94,7 @@ export function ConfirmSwapModalSetup(props: ConfirmSwapModalSetupProps) { networkCostsSuffix: shouldPayGas ? : null, networkCostsTooltipSuffix: , }), - [chainId, allowedSlippage, nativeCurrency.symbol, isEoaEthFlow, isExactIn, shouldPayGas], + [chainId, allowedSlippage, nativeCurrency.symbol, isEoaEthFlow, isExactIn, shouldPayGas, isSmartSlippageApplied], ) const submittedContent = useOrderSubmittedContent(chainId) diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts deleted file mode 100644 index 76766178c2..0000000000 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwap.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useCallback } from 'react' - -import { Field } from 'legacy/state/types' - -import { ethFlow } from 'modules/swap/services/ethFlow' -import { safeBundleApprovalFlow, safeBundleEthFlow } from 'modules/swap/services/safeBundleFlow' -import { swapFlow } from 'modules/swap/services/swapFlow' -import { FlowType, useSafeBundleFlowContext, useTradeFlowType, useTradePriceImpact } from 'modules/trade' -import { logTradeFlow } from 'modules/trade/utils/logger' - -import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' - -import { useEthFlowContext } from './useEthFlowContext' -import { useSwapFlowContext } from './useSwapFlowContext' -import { useSwapActionHandlers } from './useSwapState' - -export function useHandleSwap() { - const tradeFlowType = useTradeFlowType() - const swapFlowContext = useSwapFlowContext() - const safeBundleFlowContext = useSafeBundleFlowContext() - const ethFlowContext = useEthFlowContext() - const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() - const { onChangeRecipient, onUserInput } = useSwapActionHandlers() - const priceImpactParams = useTradePriceImpact() - - const contextIsReady = - Boolean( - tradeFlowType === FlowType.EOA_ETH_FLOW - ? ethFlowContext - : [FlowType.SAFE_BUNDLE_ETH, FlowType.SAFE_BUNDLE_APPROVAL].includes(tradeFlowType) - ? safeBundleFlowContext - : swapFlowContext, - ) && !!swapFlowContext - - const callback = useCallback(async () => { - const tradeResult = await (async () => { - if (!swapFlowContext) return - - if (tradeFlowType === FlowType.SAFE_BUNDLE_APPROVAL) { - if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') - - logTradeFlow('SAFE BUNDLE APPROVAL FLOW', 'Start safe bundle approval flow') - return safeBundleApprovalFlow( - swapFlowContext, - safeBundleFlowContext, - priceImpactParams, - confirmPriceImpactWithoutFee, - ) - } - if (tradeFlowType === FlowType.SAFE_BUNDLE_ETH) { - if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') - - logTradeFlow('SAFE BUNDLE ETH FLOW', 'Start safe bundle eth flow') - return safeBundleEthFlow( - swapFlowContext, - safeBundleFlowContext, - priceImpactParams, - confirmPriceImpactWithoutFee, - ) - } - if (tradeFlowType === FlowType.EOA_ETH_FLOW) { - if (!ethFlowContext) throw new Error('Eth flow context is not ready') - - logTradeFlow('ETH FLOW', 'Start eth flow') - return ethFlow(swapFlowContext, ethFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - } - - logTradeFlow('SWAP FLOW', 'Start swap flow') - return swapFlow(swapFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - })() - - const isPriceImpactDeclined = tradeResult === false - - // Clean up form fields after successful swap - if (!isPriceImpactDeclined) { - onChangeRecipient(null) - onUserInput(Field.INPUT, '') - } - }, [ - swapFlowContext, - safeBundleFlowContext, - ethFlowContext, - onChangeRecipient, - onUserInput, - priceImpactParams, - confirmPriceImpactWithoutFee, - ]) - - return { callback, contextIsReady } -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts new file mode 100644 index 0000000000..10ecb4104b --- /dev/null +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts @@ -0,0 +1,42 @@ +import { useCallback } from 'react' + +import { useUserTransactionTTL } from 'legacy/state/user/hooks' + +import { useTradePriceImpact } from 'modules/trade' +import { logTradeFlow } from 'modules/trade/utils/logger' +import { useHandleSwap, useTradeFlowType } from 'modules/tradeFlow' +import { FlowType } from 'modules/tradeFlow' + +import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' +import { useSafeMemoObject } from 'common/hooks/useSafeMemo' + +import { useEthFlowContext } from './useEthFlowContext' +import { useSwapFlowContext } from './useSwapFlowContext' + +import { ethFlow } from '../services/ethFlow' + +export function useHandleSwapOrEthFlow() { + const priceImpactParams = useTradePriceImpact() + const swapFlowContext = useSwapFlowContext() + const ethFlowContext = useEthFlowContext() + const tradeFlowType = useTradeFlowType() + const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() + + const [deadline] = useUserTransactionTTL() + const { callback: handleSwap, contextIsReady } = useHandleSwap(useSafeMemoObject({ deadline })) + + const callback = useCallback(() => { + if (!swapFlowContext) return + + if (tradeFlowType === FlowType.EOA_ETH_FLOW) { + if (!ethFlowContext) throw new Error('Eth flow context is not ready') + + logTradeFlow('ETH FLOW', 'Start eth flow') + return ethFlow(swapFlowContext, ethFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) + } + + return handleSwap() + }, [swapFlowContext, ethFlowContext, handleSwap, tradeFlowType, priceImpactParams, confirmPriceImpactWithoutFee]) + + return { callback, contextIsReady } +} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts index 7d748de6d9..5efc07fdac 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts @@ -19,7 +19,6 @@ import { Field } from 'legacy/state/types' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useTokenSupportsPermit } from 'modules/permit' import { getSwapButtonState } from 'modules/swap/helpers/getSwapButtonState' -import { useHandleSwap } from 'modules/swap/hooks/useHandleSwap' import { SwapButtonsContext } from 'modules/swap/pure/SwapButtons' import { TradeType, useTradeConfirmActions, useWrapNativeFlow } from 'modules/trade' import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut' @@ -30,7 +29,7 @@ import { QuoteDeadlineParams } from 'modules/tradeQuote' import { useApproveState } from 'common/hooks/useApproveState' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { useSwapFlowContext } from './useSwapFlowContext' +import { useHandleSwapOrEthFlow } from './useHandleSwapOrEthFlow' import { useDerivedSwapInfo, useSwapActionHandlers } from './useSwapState' export interface SwapButtonInput { @@ -57,7 +56,6 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext const isBestQuoteLoading = useIsBestQuoteLoading() const tradeConfirmActions = useTradeConfirmActions() const { standaloneMode } = useInjectedWidgetParams() - const tradeFlowContext = useSwapFlowContext() const currencyIn = currencies[Field.INPUT] const currencyOut = currencies[Field.OUTPUT] @@ -78,9 +76,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext const wrapCallback = useWrapNativeFlow() const { state: approvalState } = useApproveState(slippageAdjustedSellAmount || null) - const { callback: handleSwap, contextIsReady } = useHandleSwap() - - const recipientAddressOrName = tradeFlowContext?.orderParams.recipientAddressOrName || null + const { callback: handleSwap, contextIsReady } = useHandleSwapOrEthFlow() const swapCallbackError = contextIsReady ? null : 'Missing dependencies' @@ -137,7 +133,6 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext toggleWalletModal, swapInputError, onCurrencySelection, - recipientAddressOrName, widgetStandaloneMode: standaloneMode, quoteDeadlineParams, }), @@ -154,7 +149,6 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext toggleWalletModal, swapInputError, onCurrencySelection, - recipientAddressOrName, standaloneMode, quoteDeadlineParams, ], diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts index 9f02680d3f..afed5d89d7 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts @@ -1,8 +1,10 @@ import { useUserTransactionTTL } from 'legacy/state/user/hooks' -import { useTradeFlowContext } from 'modules/trade' +import { useTradeFlowContext } from 'modules/tradeFlow' + +import { useSafeMemoObject } from 'common/hooks/useSafeMemo' export function useSwapFlowContext() { const [deadline] = useUserTransactionTTL() - return useTradeFlowContext({ deadline }) + return useTradeFlowContext(useSafeMemoObject({ deadline })) } diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx index a6c14a7ca2..110dd701f1 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx @@ -26,7 +26,6 @@ const swapButtonsContext: SwapButtonsContext = { openSwapConfirm: () => void 0, toggleWalletModal: () => void 0, hasEnoughWrappedBalanceForSwap: true, - recipientAddressOrName: null, quoteDeadlineParams: { validFor: 0, quoteValidTo: 0, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx index c4b93a9e3b..11c34bce1f 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx @@ -38,7 +38,6 @@ export interface SwapButtonsContext { hasEnoughWrappedBalanceForSwap: boolean swapInputError?: ReactNode onCurrencySelection: (field: Field, currency: Currency) => void - recipientAddressOrName: string | null widgetStandaloneMode?: boolean quoteDeadlineParams: QuoteDeadlineParams } diff --git a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts index 011cc5a09e..af590d611f 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts @@ -8,11 +8,11 @@ import { removePermitHookFromAppData } from 'modules/appData' import { emitPostedOrderEvent } from 'modules/orders' import { signEthFlowOrderStep } from 'modules/swap/services/ethFlow/steps/signEthFlowOrderStep' import { EthFlowContext } from 'modules/swap/services/types' -import { TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' +import { TradeFlowContext } from 'modules/tradeFlow' import { isQuoteExpired } from 'modules/tradeQuote' import { calculateUniqueOrderId } from './steps/calculateUniqueOrderId' diff --git a/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx index 4bf760ae32..cd69f633ba 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/NoImpactWarning/index.tsx @@ -51,7 +51,7 @@ export function NoImpactWarning(props: NoImpactWarningProps) { useEffect(() => { setIsAccepted(!showPriceImpactWarning) - }, [showPriceImpactWarning]) + }, [showPriceImpactWarning, setIsAccepted]) if (!showPriceImpactWarning) return null diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeConfirmModal/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeConfirmModal/index.cosmos.tsx index f4e50e9ed7..428a89e636 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeConfirmModal/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeConfirmModal/index.cosmos.tsx @@ -70,7 +70,7 @@ function Custom({ stateValue }: { stateValue: string }) { return ( - Some content + {() => Some content} ) diff --git a/apps/cowswap-frontend/src/modules/trade/index.ts b/apps/cowswap-frontend/src/modules/trade/index.ts index 80ae85e2ac..10c978158e 100644 --- a/apps/cowswap-frontend/src/modules/trade/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/index.ts @@ -37,9 +37,6 @@ export * from './hooks/useWrappedToken' export * from './hooks/useUnknownImpactWarning' export * from './hooks/useIsSwapEth' export * from './hooks/useIsSafeEthFlow' -export * from './hooks/useTradeFlowType' -export { useTradeFlowContext } from './hooks/useTradeFlowContext' -export { useSafeBundleFlowContext } from './hooks/useSafeBundleFlowContext' export * from './containers/TradeWidget/types' export { useIsNoImpactWarningAccepted } from './containers/NoImpactWarning/index' export * from './utils/getReceiveAmountInfo' diff --git a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.cosmos.tsx index 39ffc51975..18263525af 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.cosmos.tsx @@ -17,7 +17,7 @@ const Fixtures = { refreshInterval={10_000} recipient={null} > - Trade confirmation + {() => Trade confirmation} ), } diff --git a/apps/cowswap-frontend/src/modules/trade/types/index.ts b/apps/cowswap-frontend/src/modules/trade/types/index.ts index 9762914b32..f179f214e3 100644 --- a/apps/cowswap-frontend/src/modules/trade/types/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/types/index.ts @@ -1,4 +1,3 @@ export * from './TradeDerivedState' export * from './ReceiveAmountInfo' export * from './TradeType' -export * from './TradeFlowContext' diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts new file mode 100644 index 0000000000..7feb7896b8 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts @@ -0,0 +1,56 @@ +import { useCallback } from 'react' + +import { useTradePriceImpact } from 'modules/trade' +import { logTradeFlow } from 'modules/trade/utils/logger' + +import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' + +import { useSafeBundleFlowContext } from './useSafeBundleFlowContext' +import { TradeFlowParams, useTradeFlowContext } from './useTradeFlowContext' +import { useTradeFlowType } from './useTradeFlowType' + +import { safeBundleApprovalFlow, safeBundleEthFlow } from '../services/safeBundleFlow' +import { swapFlow } from '../services/swapFlow' +import { FlowType } from '../types/TradeFlowContext' + +export function useHandleSwap(params: TradeFlowParams) { + const tradeFlowType = useTradeFlowType() + const tradeFlowContext = useTradeFlowContext(params) + const safeBundleFlowContext = useSafeBundleFlowContext() + const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() + const priceImpactParams = useTradePriceImpact() + + const contextIsReady = + Boolean( + [FlowType.SAFE_BUNDLE_ETH, FlowType.SAFE_BUNDLE_APPROVAL].includes(tradeFlowType) + ? safeBundleFlowContext + : tradeFlowContext, + ) && !!tradeFlowContext + + const callback = useCallback(async () => { + if (!tradeFlowContext) return + + if (tradeFlowType === FlowType.SAFE_BUNDLE_APPROVAL) { + if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') + + logTradeFlow('SAFE BUNDLE APPROVAL FLOW', 'Start safe bundle approval flow') + return safeBundleApprovalFlow( + tradeFlowContext, + safeBundleFlowContext, + priceImpactParams, + confirmPriceImpactWithoutFee, + ) + } + if (tradeFlowType === FlowType.SAFE_BUNDLE_ETH) { + if (!safeBundleFlowContext) throw new Error('Safe bundle flow context is not ready') + + logTradeFlow('SAFE BUNDLE ETH FLOW', 'Start safe bundle eth flow') + return safeBundleEthFlow(tradeFlowContext, safeBundleFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) + } + + logTradeFlow('SWAP FLOW', 'Start swap flow') + return swapFlow(tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) + }, [tradeFlowType, tradeFlowContext, safeBundleFlowContext, priceImpactParams, confirmPriceImpactWithoutFee]) + + return { callback, contextIsReady } +} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts similarity index 96% rename from apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts index b20ef22326..5a83744dfa 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useSafeBundleFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts @@ -5,12 +5,12 @@ import { useSafeAppsSdk } from '@cowprotocol/wallet' import useSWR from 'swr' +import { useReceiveAmountInfo } from 'modules/trade' + import { useGP2SettlementContract, useTokenContract, useWETHContract } from 'common/hooks/useContract' import { useNeedsApproval } from 'common/hooks/useNeedsApproval' import { useTradeSpenderAddress } from 'common/hooks/useTradeSpenderAddress' -import { useReceiveAmountInfo } from './useReceiveAmountInfo' - import { SafeBundleFlowContext } from '../types/TradeFlowContext' export function useSafeBundleFlowContext(): SafeBundleFlowContext | null { diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts similarity index 97% rename from apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts index d88fda1d3c..e080f6d985 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts @@ -20,11 +20,11 @@ import { useGP2SettlementContract } from 'common/hooks/useContract' import { TradeFlowContext } from '../types/TradeFlowContext' -interface Params { +export interface TradeFlowParams { deadline: number } -export function useTradeFlowContext({ deadline }: Params): TradeFlowContext | null { +export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowContext | null { const { chainId, account } = useWalletInfo() const provider = useWalletProvider() const { allowsOffchainSigning } = useWalletDetails() diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowType.ts similarity index 81% rename from apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowType.ts index cb035bb603..3f8b847dd8 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeFlowType.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowType.ts @@ -1,10 +1,8 @@ -import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' +import { useIsEoaEthFlow, useIsSafeEthFlow, useReceiveAmountInfo } from 'modules/trade' -import { useIsEoaEthFlow } from './useIsEoaEthFlow' -import { useIsSafeEthFlow } from './useIsSafeEthFlow' -import { useReceiveAmountInfo } from './useReceiveAmountInfo' +import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle' -import { FlowType } from '../types' +import { FlowType } from '../types/TradeFlowContext' export function useTradeFlowType(): FlowType { const isEoaEthFlow = useIsEoaEthFlow() diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/index.ts b/apps/cowswap-frontend/src/modules/tradeFlow/index.ts new file mode 100644 index 0000000000..ee07046f0d --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeFlow/index.ts @@ -0,0 +1,4 @@ +export { useHandleSwap } from './hooks/useHandleSwap' +export { useTradeFlowContext } from './hooks/useTradeFlowContext' +export { useTradeFlowType } from './hooks/useTradeFlowType' +export * from './types/TradeFlowContext' diff --git a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/index.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/index.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/index.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/index.ts diff --git a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts similarity index 98% rename from apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts index bb4f6593bd..dd64fcdbc0 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleApprovalFlow.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts @@ -11,13 +11,14 @@ import { buildApproveTx } from 'modules/operations/bundle/buildApproveTx' import { buildPresignTx } from 'modules/operations/bundle/buildPresignTx' import { buildZeroApproveTx } from 'modules/operations/bundle/buildZeroApproveTx' import { emitPostedOrderEvent } from 'modules/orders' -import { SafeBundleFlowContext, TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' import { shouldZeroApprove as shouldZeroApproveFn } from 'modules/zeroApproval' +import { SafeBundleFlowContext, TradeFlowContext } from '../../types/TradeFlowContext' + const LOG_PREFIX = 'SAFE APPROVAL BUNDLE FLOW' export async function safeBundleApprovalFlow( diff --git a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts similarity index 98% rename from apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts index 713fc6f083..0b6fe9277b 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/safeBundleFlow/safeBundleEthFlow.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts @@ -12,12 +12,13 @@ import { buildApproveTx } from 'modules/operations/bundle/buildApproveTx' import { buildPresignTx } from 'modules/operations/bundle/buildPresignTx' import { buildWrapTx } from 'modules/operations/bundle/buildWrapTx' import { emitPostedOrderEvent } from 'modules/orders' -import { SafeBundleFlowContext, TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' +import { SafeBundleFlowContext, TradeFlowContext } from '../../types/TradeFlowContext' + const LOG_PREFIX = 'SAFE BUNDLE ETH FLOW' export async function safeBundleEthFlow( diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/README.md b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/README.md similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/services/swapFlow/README.md rename to apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/README.md diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts similarity index 98% rename from apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts index 5269c55cfd..afef3f3941 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts @@ -10,7 +10,6 @@ import { signAndPostOrder } from 'legacy/utils/trade' import { emitPostedOrderEvent } from 'modules/orders' import { handlePermit } from 'modules/permit' import { callDataContainsPermitSigner } from 'modules/permit' -import { TradeFlowContext } from 'modules/trade' import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' @@ -18,6 +17,8 @@ import { tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics' import { presignOrderStep } from './steps/presignOrderStep' +import { TradeFlowContext } from '../../types/TradeFlowContext' + export async function swapFlow( input: TradeFlowContext, priceImpactParams: PriceImpact, diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/steps/presignOrderStep.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/steps/presignOrderStep.ts similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/services/swapFlow/steps/presignOrderStep.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/steps/presignOrderStep.ts diff --git a/apps/cowswap-frontend/src/modules/swap/services/swapFlow/swapFlow.puml b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/swapFlow.puml similarity index 100% rename from apps/cowswap-frontend/src/modules/swap/services/swapFlow/swapFlow.puml rename to apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/swapFlow.puml diff --git a/apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts similarity index 90% rename from apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts rename to apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts index 0e484fcffe..69ea103b01 100644 --- a/apps/cowswap-frontend/src/modules/trade/types/TradeFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts @@ -8,9 +8,8 @@ import type { PostOrderParams } from 'legacy/utils/trade' import type { TypedAppDataHooks } from 'modules/appData' import type { GeneratePermitHook, IsTokenPermittableResult, useGetCachedPermit } from 'modules/permit' - -import type { TradeConfirmActions } from '../hooks/useTradeConfirmActions' -import type { TradeFlowAnalyticsContext } from '../utils/tradeFlowAnalytics' +import type { TradeConfirmActions } from 'modules/trade' +import type { TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics' export enum FlowType { REGULAR = 'REGULAR', diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts index 1cdf864787..da67c788ce 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts @@ -6,7 +6,8 @@ import { bpsToPercent } from '@cowprotocol/common-utils' import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { walletInfoAtom } from '@cowprotocol/wallet' -import { isEoaEthFlowAtom, TradeType, tradeTypeAtom } from 'modules/trade' +import { isEoaEthFlowAtom, tradeTypeAtom } from 'modules/trade' +import { TradeType } from 'modules/trade/types/TradeType' type SlippageBpsPerNetwork = Record diff --git a/apps/cowswap-frontend/src/modules/twap/hooks/useTwapWarningsContext.ts b/apps/cowswap-frontend/src/modules/twap/hooks/useTwapWarningsContext.ts index cca657b335..10f0a41e7e 100644 --- a/apps/cowswap-frontend/src/modules/twap/hooks/useTwapWarningsContext.ts +++ b/apps/cowswap-frontend/src/modules/twap/hooks/useTwapWarningsContext.ts @@ -2,7 +2,6 @@ import { useMemo } from 'react' import { useWalletInfo } from '@cowprotocol/wallet' -import { useTradePriceImpact } from 'modules/trade' import { useGetTradeFormValidation } from 'modules/tradeFormValidation' import { TradeFormValidation } from 'modules/tradeFormValidation/types' @@ -16,7 +15,6 @@ export interface TwapWarningsContext { export function useTwapWarningsContext(): TwapWarningsContext { const { account } = useWalletInfo() const primaryFormValidation = useGetTradeFormValidation() - const priceImpactParams = useTradePriceImpact() return useMemo(() => { const canTrade = !primaryFormValidation || NOT_BLOCKING_VALIDATIONS.includes(primaryFormValidation) @@ -26,5 +24,5 @@ export function useTwapWarningsContext(): TwapWarningsContext { canTrade, walletIsNotConnected, } - }, [primaryFormValidation, account, priceImpactParams]) + }, [primaryFormValidation, account]) } diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index 3d20c5624a..7e2ec6b0b5 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -1,12 +1,10 @@ -import React, { useCallback, useMemo } from 'react' +import React, { useMemo } from 'react' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import type { PriceImpact } from 'legacy/hooks/usePriceImpact' import { useAppData } from 'modules/appData' -import { swapFlow } from 'modules/swap/services/swapFlow' -import type { SwapFlowContext } from 'modules/swap/services/types' import { TradeBasicConfirmDetails, TradeConfirmation, @@ -14,11 +12,9 @@ import { useOrderSubmittedContent, useReceiveAmountInfo, useTradeConfirmActions, - useTradePriceImpact, } from 'modules/trade' import { HighFeeWarning } from 'modules/tradeWidgetAddons' -import { useConfirmPriceImpactWithoutFee } from 'common/hooks/useConfirmPriceImpactWithoutFee' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' @@ -27,7 +23,7 @@ import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' const CONFIRM_TITLE = 'Confirm order' export interface YieldConfirmModalProps { - tradeFlowContext: SwapFlowContext + doTrade(): Promise inputCurrencyInfo: CurrencyPreviewInfo outputCurrencyInfo: CurrencyPreviewInfo priceImpact: PriceImpact @@ -35,7 +31,7 @@ export interface YieldConfirmModalProps { } export function YieldConfirmModal(props: YieldConfirmModalProps) { - const { inputCurrencyInfo, outputCurrencyInfo, priceImpact, recipient, tradeFlowContext: tradeContextInitial } = props + const { inputCurrencyInfo, outputCurrencyInfo, priceImpact, recipient, doTrade: _doTrade } = props /** * This is a very important part of the code. @@ -43,7 +39,7 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { * In order to prevent this, we use useMemo to keep the trade context the same when the modal was opened. */ // eslint-disable-next-line react-hooks/exhaustive-deps - const tradeFlowContext = useMemo(() => tradeContextInitial, []) + const doTrade = useMemo(() => _doTrade, []) const { account, chainId } = useWalletInfo() const { ensName } = useWalletDetails() @@ -51,20 +47,10 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { const receiveAmountInfo = useReceiveAmountInfo() const tradeConfirmActions = useTradeConfirmActions() const { slippage } = useYieldDerivedState() - const priceImpactParams = useTradePriceImpact() - const { confirmPriceImpactWithoutFee } = useConfirmPriceImpactWithoutFee() const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) const submittedContent = useOrderSubmittedContent(chainId) - const doTrade = useCallback(async () => { - if (!tradeFlowContext) return - - swapFlow(tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee) - }, [tradeFlowContext, priceImpactParams, confirmPriceImpactWithoutFee]) - - const isConfirmDisabled = false // TODO: add conditions if needed - return ( {tradeWarnings} - + ) }, @@ -112,9 +113,9 @@ export function YieldWidget() { inputCurrencyInfo={inputCurrencyInfo} outputCurrencyInfo={outputCurrencyInfo} confirmModal={ - tradeFlowContext ? ( + doTrade.contextIsReady ? ( ('yieldStateAtom:v0', getDefaultYieldState(null), getJotaiIsolatedStorage()), + atomWithStorage('yieldStateAtom:v1', getDefaultYieldState(null), getJotaiIsolatedStorage()), ) export const yieldDerivedStateAtom = atom({ From b3ac9063c47fa060269253bccbe991c29a62a83d Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 10 Oct 2024 19:11:53 +0500 Subject: [PATCH 34/90] chore: hide yield under ff --- .../src/common/constants/routes.ts | 8 +++++++- .../src/common/hooks/useMenuItems.ts | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/cowswap-frontend/src/common/constants/routes.ts b/apps/cowswap-frontend/src/common/constants/routes.ts index 2e47db51c4..82cda8ab5b 100644 --- a/apps/cowswap-frontend/src/common/constants/routes.ts +++ b/apps/cowswap-frontend/src/common/constants/routes.ts @@ -42,7 +42,6 @@ export const MENU_ITEMS: { route: RoutesValues; label: string; fullLabel?: strin { route: Routes.SWAP, label: 'Swap', description: 'Trade tokens' }, { route: Routes.LIMIT_ORDER, label: 'Limit', fullLabel: 'Limit order', description: 'Set your own price' }, { route: Routes.ADVANCED_ORDERS, label: 'TWAP', description: 'Place orders with a time-weighted average price' }, - { route: Routes.YIELD, label: 'Yield', fullLabel: 'Yield', description: 'Provide liquidity' }, // TODO ] export const HOOKS_STORE_MENU_ITEM = { @@ -50,3 +49,10 @@ export const HOOKS_STORE_MENU_ITEM = { label: 'Hooks', description: 'Powerful tool to generate pre/post interaction for CoW Protocol', } + +export const YIELD_MENU_ITEM = { + route: Routes.YIELD, + label: 'Yield', + fullLabel: 'Yield', + description: 'Provide liquidity', +} diff --git a/apps/cowswap-frontend/src/common/hooks/useMenuItems.ts b/apps/cowswap-frontend/src/common/hooks/useMenuItems.ts index 2826cee38d..bf6a72af20 100644 --- a/apps/cowswap-frontend/src/common/hooks/useMenuItems.ts +++ b/apps/cowswap-frontend/src/common/hooks/useMenuItems.ts @@ -1,14 +1,25 @@ import { useMemo } from 'react' import { useFeatureFlags } from '@cowprotocol/common-hooks' -import { isLocal } from '@cowprotocol/common-utils' +import { isLocal, isPr } from '@cowprotocol/common-utils' -import { HOOKS_STORE_MENU_ITEM, MENU_ITEMS } from '../constants/routes' +import { HOOKS_STORE_MENU_ITEM, MENU_ITEMS, YIELD_MENU_ITEM } from '../constants/routes' export function useMenuItems() { const { isHooksStoreEnabled } = useFeatureFlags() + const { isYieldEnabled } = useFeatureFlags() return useMemo(() => { - return isHooksStoreEnabled || isLocal ? MENU_ITEMS.concat(HOOKS_STORE_MENU_ITEM) : MENU_ITEMS + const items = [...MENU_ITEMS] + + if (isHooksStoreEnabled || isLocal) { + items.push(HOOKS_STORE_MENU_ITEM) + } + + if (isYieldEnabled || isLocal || isPr) { + items.push(YIELD_MENU_ITEM) + } + + return items }, [isHooksStoreEnabled]) } From c780d1bd6e0e61a695630a948e3631420b5d14d7 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 11 Oct 2024 12:31:20 +0500 Subject: [PATCH 35/90] chore: remove lazy loading --- .../modules/application/containers/App/RoutesApp.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx index 9874d1e49e..7469615b85 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/RoutesApp.tsx @@ -18,14 +18,14 @@ import { RedirectPathToSwapOnly, RedirectToPath } from 'legacy/pages/Swap/redire import { Routes as RoutesEnum, RoutesValues } from 'common/constants/routes' import Account, { AccountOverview } from 'pages/Account' +import AdvancedOrdersPage from 'pages/AdvancedOrders' import AnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers' import { HooksPage } from 'pages/Hooks' +import LimitOrderPage from 'pages/LimitOrders' import { SwapPage } from 'pages/Swap' +import YieldPage from 'pages/Yield' // Async routes -const LimitOrders = lazy(() => import(/* webpackChunkName: "limit_orders" */ 'pages/LimitOrders')) -const YieldPage = lazy(() => import(/* webpackChunkName: "yield_widget" */ 'pages/Yield')) -const AdvancedOrders = lazy(() => import(/* webpackChunkName: "advanced_orders" */ 'pages/AdvancedOrders')) const NotFound = lazy(() => import(/* webpackChunkName: "not_found" */ 'pages/error/NotFound')) const CowRunner = lazy(() => import(/* webpackChunkName: "cow_runner" */ 'pages/games/CowRunner')) const MevSlicer = lazy(() => import(/* webpackChunkName: "mev_slicer" */ 'pages/games/MevSlicer')) @@ -52,10 +52,10 @@ function LazyRoute({ route, element, key }: LazyRouteProps) { } const lazyRoutes: LazyRouteProps[] = [ - { route: RoutesEnum.LIMIT_ORDER, element: }, + { route: RoutesEnum.LIMIT_ORDER, element: }, { route: RoutesEnum.YIELD, element: }, { route: RoutesEnum.LONG_LIMIT_ORDER, element: }, - { route: RoutesEnum.ADVANCED_ORDERS, element: }, + { route: RoutesEnum.ADVANCED_ORDERS, element: }, { route: RoutesEnum.LONG_ADVANCED_ORDERS, element: }, { route: RoutesEnum.ABOUT, element: }, { route: RoutesEnum.FAQ, element: }, From 4c88126d6428da0eed45688611eb4a114bf485a5 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 14 Oct 2024 14:09:14 +0500 Subject: [PATCH 36/90] chore: fix imports --- .../src/modules/yield/containers/TradeButtons/index.tsx | 2 -- .../src/modules/yield/containers/Warnings/index.tsx | 2 -- .../src/modules/yield/containers/YieldWidget/index.tsx | 2 -- 3 files changed, 6 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx index 58db93e9fe..5dc96fefef 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/TradeButtons/index.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { useIsNoImpactWarningAccepted, useTradeConfirmActions } from 'modules/trade' import { TradeFormButtons, useGetTradeFormValidation, useTradeFormButtonContext } from 'modules/tradeFormValidation' import { useHighFeeWarning } from 'modules/tradeWidgetAddons' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx index ca99cf8a21..9112bbbdf9 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' export function Warnings() { diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 23f5bfb8d2..993b12c45d 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { Field } from 'legacy/state/types' import { From 0bf3ba12990db4c621b95f7595309e9e00a3c293 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 14 Oct 2024 15:17:32 +0500 Subject: [PATCH 37/90] fix: generalize smart slippage usage --- .../swap/containers/SwapUpdaters/index.tsx | 3 +- .../swap/containers/SwapWidget/index.tsx | 10 ++---- .../src/modules/swap/pure/warnings.tsx | 16 ++-------- .../trade/containers/TradeWarnings/index.tsx | 9 +++++- .../TradeWidget/TradeWidgetForm.tsx | 14 +++++++- .../TradeWidget/TradeWidgetUpdaters.tsx | 4 +++ .../trade/containers/TradeWidget/index.tsx | 8 ++++- .../trade/containers/TradeWidget/types.ts | 1 + .../HighSuggestedSlippageWarning/index.tsx | 30 +++++++++++++++++ .../src/modules/tradeSlippage/index.tsx | 1 + .../containers/HighFeeWarning/styled.tsx | 6 +--- .../HighSuggestedSlippageWarning/index.tsx | 32 ------------------- .../src/modules/tradeWidgetAddons/index.ts | 1 - .../yield/containers/YieldWidget/index.tsx | 3 +- libs/ui/src/containers/InlineBanner/index.tsx | 4 ++- libs/ui/src/pure/InfoTooltip/index.tsx | 5 +-- 16 files changed, 79 insertions(+), 68 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx delete mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighSuggestedSlippageWarning/index.tsx diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx index 4a16bb9a78..3e3a8fcd8e 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapUpdaters/index.tsx @@ -1,7 +1,7 @@ import { percentToBps } from '@cowprotocol/common-utils' import { AppDataUpdater } from 'modules/appData' -import { SmartSlippageUpdater, useTradeSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' +import { useTradeSlippage, useIsSmartSlippageApplied } from 'modules/tradeSlippage' import { SwapAmountsFromUrlUpdater } from '../../updaters/SwapAmountsFromUrlUpdater' import { SwapDerivedStateUpdater } from '../../updaters/SwapDerivedStateUpdater' @@ -19,7 +19,6 @@ export function SwapUpdaters() { /> - ) } diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index 068c26da30..d760cc28bd 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -2,7 +2,6 @@ import { ReactNode, useCallback, useMemo, useState } from 'react' import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' -import { percentToBps } from '@cowprotocol/common-utils' import { useIsTradeUnsupported } from '@cowprotocol/tokens' import { useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { TradeType } from '@cowprotocol/widget-lib' @@ -41,7 +40,7 @@ import { useIsNoImpactWarningAccepted, } from 'modules/trade' import { getQuoteTimeOffset } from 'modules/tradeQuote' -import { useIsSmartSlippageApplied, useTradeSlippage } from 'modules/tradeSlippage' +import { useTradeSlippage } from 'modules/tradeSlippage' import { SettingsTab, TradeRateDetails, useHighFeeWarning } from 'modules/tradeWidgetAddons' import { useTradeUsdAmounts } from 'modules/usdAmount' @@ -60,7 +59,7 @@ export interface SwapWidgetProps { } export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { - const { chainId, account } = useWalletInfo() + const { chainId } = useWalletInfo() const { currencies, trade } = useDerivedSwapInfo() const slippage = useTradeSlippage() const parsedAmounts = useSwapCurrenciesAmounts() @@ -189,8 +188,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { } const showTwapSuggestionBanner = !enabledTradeTypes || enabledTradeTypes.includes(TradeType.ADVANCED) - const isSuggestedSlippage = useIsSmartSlippageApplied() && !isTradePriceUpdating && !!account - const swapWarningsTopProps: SwapWarningsTopProps = { chainId, trade, @@ -198,8 +195,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { buyingFiatAmount, priceImpact: priceImpactParams.priceImpact, tradeUrlParams, - slippageBps: percentToBps(slippage), - isSuggestedSlippage, } const swapWarningsBottomProps: SwapWarningsBottomProps = { @@ -234,6 +229,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { const params = { isEoaEthFlow, compactView: true, + enableSmartSlippage: true, recipient, showRecipient: showRecipientControls, isTradePriceUpdating, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index 2f25dd4624..efaeef37e8 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -8,7 +8,7 @@ import TradeGp from 'legacy/state/swap/TradeGp' import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' -import { BundleTxWrapBanner, HighFeeWarning, HighSuggestedSlippageWarning } from 'modules/tradeWidgetAddons' +import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner' @@ -19,8 +19,6 @@ export interface SwapWarningsTopProps { buyingFiatAmount: CurrencyAmount | null priceImpact: Percent | undefined tradeUrlParams: TradeUrlParams - isSuggestedSlippage: boolean | undefined - slippageBps: number | undefined } export interface SwapWarningsBottomProps { @@ -31,21 +29,11 @@ export interface SwapWarningsBottomProps { } export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) { - const { - chainId, - trade, - showTwapSuggestionBanner, - buyingFiatAmount, - priceImpact, - tradeUrlParams, - isSuggestedSlippage, - slippageBps, - } = props + const { chainId, trade, showTwapSuggestionBanner, buyingFiatAmount, priceImpact, tradeUrlParams } = props return ( <> - {showTwapSuggestionBanner && ( diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx index cdaaa58eb1..a3d98716b4 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWarnings/index.tsx @@ -5,13 +5,19 @@ import { useIsSafeViaWc } from '@cowprotocol/wallet' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFormValidation' +import { HighSuggestedSlippageWarning } from 'modules/tradeSlippage' import { useShouldZeroApprove } from 'modules/zeroApproval' import { useReceiveAmountInfo } from '../../hooks/useReceiveAmountInfo' import { ZeroApprovalWarning } from '../../pure/ZeroApprovalWarning' import { NoImpactWarning } from '../NoImpactWarning' -export function TradeWarnings() { +interface TradeWarningsProps { + isTradePriceUpdating: boolean + enableSmartSlippage?: boolean +} + +export function TradeWarnings({ isTradePriceUpdating, enableSmartSlippage }: TradeWarningsProps) { const { banners: widgetBanners } = useInjectedWidgetParams() const primaryFormValidation = useGetTradeFormValidation() const receiveAmountInfo = useReceiveAmountInfo() @@ -29,6 +35,7 @@ export function TradeWarnings() { {showBundleTxApprovalBanner && } {showSafeWcBundlingBanner && } + {enableSmartSlippage && } ) } diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 0acb4f3bdd..7e06c6f06e 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -68,6 +68,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { priceImpact, recipient, hideTradeWarnings, + enableSmartSlippage, } = params const inputCurrencyInfo = useMemo( @@ -229,7 +230,18 @@ export function TradeWidgetForm(props: TradeWidgetProps) { {withRecipient && } - {isWrapOrUnwrap ? : bottomContent?.(hideTradeWarnings ? null : )} + {isWrapOrUnwrap ? ( + + ) : ( + bottomContent?.( + hideTradeWarnings ? null : ( + + ), + ) + )} )} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetUpdaters.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetUpdaters.tsx index f7623ca734..ae2243872b 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetUpdaters.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetUpdaters.tsx @@ -5,6 +5,7 @@ import { useWalletInfo } from '@cowprotocol/wallet' import { TradeFormValidationUpdater } from 'modules/tradeFormValidation' import { TradeQuoteState, TradeQuoteUpdater, useUpdateTradeQuote } from 'modules/tradeQuote' +import { SmartSlippageUpdater } from 'modules/tradeSlippage' import { usePriorityTokenAddresses } from '../../hooks/usePriorityTokenAddresses' import { useResetRecipient } from '../../hooks/useResetRecipient' @@ -16,6 +17,7 @@ import { RecipientAddressUpdater } from '../../updaters/RecipientAddressUpdater' interface TradeWidgetUpdatersProps { disableQuotePolling: boolean disableNativeSelling: boolean + enableSmartSlippage?: boolean children: ReactNode tradeQuoteStateOverride?: TradeQuoteState | null onChangeRecipient: (recipient: string | null) => void @@ -25,6 +27,7 @@ export function TradeWidgetUpdaters({ disableQuotePolling, disableNativeSelling, tradeQuoteStateOverride, + enableSmartSlippage, onChangeRecipient, children, }: TradeWidgetUpdatersProps) { @@ -49,6 +52,7 @@ export function TradeWidgetUpdaters({ + {enableSmartSlippage && } {disableNativeSelling && } {children} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx index fc9935a4c9..764652582f 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx @@ -8,7 +8,12 @@ export const TradeWidgetContainer = styledEl.Container export function TradeWidget(props: TradeWidgetProps) { const { id, slots, params, confirmModal, genericModal } = props - const { disableQuotePolling = false, disableNativeSelling = false, tradeQuoteStateOverride } = params + const { + disableQuotePolling = false, + disableNativeSelling = false, + tradeQuoteStateOverride, + enableSmartSlippage, + } = params const modals = TradeWidgetModals(confirmModal, genericModal) return ( @@ -18,6 +23,7 @@ export function TradeWidget(props: TradeWidgetProps) { disableQuotePolling={disableQuotePolling} disableNativeSelling={disableNativeSelling} tradeQuoteStateOverride={tradeQuoteStateOverride} + enableSmartSlippage={enableSmartSlippage} onChangeRecipient={props.actions.onChangeRecipient} > {slots.updaters} diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts index 6aa00537ca..d0e0e8e3d8 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts @@ -27,6 +27,7 @@ interface TradeWidgetParams { disableNativeSelling?: boolean disablePriceImpact?: boolean hideTradeWarnings?: boolean + enableSmartSlippage?: boolean } export interface TradeWidgetSlots { diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx new file mode 100644 index 0000000000..867400f38f --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx @@ -0,0 +1,30 @@ +import { percentToBps } from '@cowprotocol/common-utils' +import { BannerOrientation, InfoTooltip, InlineBanner } from '@cowprotocol/ui' +import { useWalletInfo } from '@cowprotocol/wallet' + +import { useIsSmartSlippageApplied, useTradeSlippage } from 'modules/tradeSlippage' + +export type HighSuggestedSlippageWarningProps = { + isTradePriceUpdating: boolean +} + +export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarningProps) { + const { isTradePriceUpdating } = props + const { account } = useWalletInfo() + const slippage = useTradeSlippage() + + const isSmartSlippageApplied = useIsSmartSlippageApplied() + const isSuggestedSlippage = isSmartSlippageApplied && !isTradePriceUpdating && !!account + const slippageBps = percentToBps(slippage) + + if (!isSuggestedSlippage || !slippageBps || slippageBps <= 200) { + return null + } + + return ( + + Beware! High dynamic slippage suggested ({`${slippageBps / 100}`}%) + + + ) +} diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx index 5dc0646e05..35f3926515 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/index.tsx @@ -1,4 +1,5 @@ export { SmartSlippageUpdater } from './updaters/SmartSlippageUpdater' +export { HighSuggestedSlippageWarning } from './containers/HighSuggestedSlippageWarning' export * from './hooks/useSetSlippage' export * from './hooks/useTradeSlippage' export * from './hooks/useIsSmartSlippageApplied' diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx index 26ecc1985b..8088eb926a 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/styled.tsx @@ -126,8 +126,7 @@ export const WarningContainer = styled(AuxInformationContainer).attrs((props) => } ` -const StyledInfoIcon = styled(Info)` - color: inherit; +export const ErrorStyledInfoIcon = styled(Info)` opacity: 0.6; line-height: 0; vertical-align: middle; @@ -136,8 +135,5 @@ const StyledInfoIcon = styled(Info)` &:hover { opacity: 1; } -` - -export const ErrorStyledInfoIcon = styled(StyledInfoIcon)` color: ${({ theme }) => (theme.darkMode ? '#FFCA4A' : '#564D00')}; ` diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighSuggestedSlippageWarning/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighSuggestedSlippageWarning/index.tsx deleted file mode 100644 index 57f68ae417..0000000000 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighSuggestedSlippageWarning/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { HoverTooltip } from '@cowprotocol/ui' - -import { AlertTriangle } from 'react-feather' - -import { LOW_TIER_FEE } from '../HighFeeWarning/consts' -import { ErrorStyledInfoIcon, WarningContainer } from '../HighFeeWarning/styled' - -export type HighSuggestedSlippageWarningProps = { - isSuggestedSlippage: boolean | undefined - slippageBps: number | undefined - className?: string -} - -export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarningProps) { - const { isSuggestedSlippage, slippageBps, ...rest } = props - - if (!isSuggestedSlippage || !slippageBps || slippageBps <= 200) { - return null - } - - return ( - -
- - Beware! High dynamic slippage suggested ({`${slippageBps / 100}`}%) - - - -
-
- ) -} diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts index 2f4b055393..5506835feb 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts @@ -3,6 +3,5 @@ export { TradeRateDetails } from './containers/TradeRateDetails' export { SettingsTab } from './containers/SettingsTab' export { HighFeeWarning } from './containers/HighFeeWarning' export { BundleTxWrapBanner } from './containers/BundleTxWrapBanner' -export { HighSuggestedSlippageWarning } from './containers/HighSuggestedSlippageWarning' export { useHighFeeWarning } from './containers/HighFeeWarning/hooks/useHighFeeWarning' export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 993b12c45d..f060a4a45a 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -98,7 +98,8 @@ export function YieldWidget() { } const params = { - compactView: false, + compactView: true, + enableSmartSlippage: true, recipient, showRecipient, isTradePriceUpdating: isRateLoading, diff --git a/libs/ui/src/containers/InlineBanner/index.tsx b/libs/ui/src/containers/InlineBanner/index.tsx index 0d994b1490..1e9ae9195b 100644 --- a/libs/ui/src/containers/InlineBanner/index.tsx +++ b/libs/ui/src/containers/InlineBanner/index.tsx @@ -168,6 +168,7 @@ export interface InlineBannerProps { padding?: string margin?: string width?: string + noWrapContent?: boolean onClose?: () => void } @@ -185,6 +186,7 @@ export function InlineBanner({ margin, width, onClose, + noWrapContent, }: InlineBannerProps) { const colorEnums = getColorEnums(bannerType) @@ -213,7 +215,7 @@ export function InlineBanner({ ) : !hideIcon && colorEnums.iconText ? ( {colorEnums.iconText} ) : null} - {children} + {noWrapContent ? children : {children}} {onClose && } diff --git a/libs/ui/src/pure/InfoTooltip/index.tsx b/libs/ui/src/pure/InfoTooltip/index.tsx index 4d26d37df3..03ad62fb31 100644 --- a/libs/ui/src/pure/InfoTooltip/index.tsx +++ b/libs/ui/src/pure/InfoTooltip/index.tsx @@ -32,16 +32,17 @@ const StyledTooltipContainer = styled(TooltipContainer)` export interface InfoTooltipProps { content: ReactNode + size?: number className?: string } -export function InfoTooltip({ content, className }: InfoTooltipProps) { +export function InfoTooltip({ content, className, size = 16 }: InfoTooltipProps) { const tooltipContent = {content} return ( - + ) From 78c0b894bc91f62cb17a71187a1e15ba195b4f79 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 14 Oct 2024 17:25:45 +0500 Subject: [PATCH 38/90] fix: don't sync url params while navigating with yield --- .../containers/TradeWidgetLinks/index.tsx | 46 +++++++++++++++---- .../useSetupTradeStateFromUrl.ts | 6 ++- .../src/modules/trade/hooks/useTradeState.ts | 21 ++++++++- .../trade/utils/parameterizeTradeRoute.ts | 9 +++- .../yield/containers/Warnings/index.tsx | 3 +- .../containers/YieldConfirmModal/index.tsx | 2 +- .../modules/yield/state/yieldRawStateAtom.ts | 9 +++- 7 files changed, 79 insertions(+), 17 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx index 02eb509ccf..79dae77d1e 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { Command } from '@cowprotocol/types' import { BadgeType } from '@cowprotocol/ui' @@ -7,7 +7,7 @@ import type { TradeType } from '@cowprotocol/widget-lib' import { Trans } from '@lingui/macro' import IMAGE_CARET from 'assets/icon/caret.svg' import SVG from 'react-inlinesvg' -import { matchPath, useLocation } from 'react-router-dom' +import { useLocation } from 'react-router-dom' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { ModalHeader } from 'modules/tokensList/pure/ModalHeader' @@ -18,7 +18,9 @@ import { useMenuItems } from 'common/hooks/useMenuItems' import * as styledEl from './styled' import { useTradeRouteContext } from '../../hooks/useTradeRouteContext' -import { parameterizeTradeRoute } from '../../utils/parameterizeTradeRoute' +import { useGetTradeStateByRoute } from '../../hooks/useTradeState' +import { TradeUrlParams } from '../../types/TradeRawState' +import { addChainIdToRoute, parameterizeTradeRoute } from '../../utils/parameterizeTradeRoute' interface MenuItemConfig { route: RoutesValues @@ -48,11 +50,7 @@ export function TradeWidgetLinks({ const [isDropdownVisible, setDropdownVisible] = useState(false) const { enabledTradeTypes } = useInjectedWidgetParams() const menuItems = useMenuItems() - - const handleMenuItemClick = (_item?: MenuItemConfig) => { - if (menuItemsElements.length === 1) return - setDropdownVisible(false) - } + const getTradeStateByType = useGetTradeStateByRoute() const enabledItems = useMemo(() => { return menuItems.filter((item) => { @@ -62,10 +60,37 @@ export function TradeWidgetLinks({ }) }, [menuItems, enabledTradeTypes]) + const enabledItemsCount = enabledItems.length + + const handleMenuItemClick = useCallback( + (_item?: MenuItemConfig) => { + if (enabledItemsCount === 1) return + setDropdownVisible(false) + }, + [enabledItemsCount], + ) + const menuItemsElements = useMemo(() => { return enabledItems.map((item) => { - const routePath = parameterizeTradeRoute(tradeContext, item.route, true) - const isActive = !!matchPath(location.pathname, routePath.split('?')[0]) + const isItemYield = item.route === Routes.YIELD + const isCurrentPathYield = location.pathname.startsWith(addChainIdToRoute(Routes.YIELD, tradeContext.chainId)) + const itemTradeState = getTradeStateByType(item.route) + + const routePath = isItemYield + ? addChainIdToRoute(item.route, tradeContext.chainId) + : parameterizeTradeRoute( + isCurrentPathYield + ? ({ + chainId: tradeContext.chainId, + inputCurrencyId: itemTradeState.inputCurrencyId, + outputCurrencyId: itemTradeState.outputCurrencyId, + } as TradeUrlParams) + : tradeContext, + item.route, + true, + ) + + const isActive = location.pathname.startsWith(routePath.split('?')[0]) return ( { const searchParams = new URLSearchParams(location.search) diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts index a4abaf3502..27e0b62276 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { useAdvancedOrdersRawState, @@ -9,6 +9,8 @@ import { useSwapRawState, useUpdateSwapRawState } from 'modules/swap/hooks/useSw import { ExtendedTradeRawState, TradeRawState } from 'modules/trade/types/TradeRawState' import { useUpdateYieldRawState, useYieldRawState } from 'modules/yield' +import { Routes, RoutesValues } from 'common/constants/routes' + import { useTradeTypeInfoFromUrl } from './useTradeTypeInfoFromUrl' import { TradeType } from '../types' @@ -78,3 +80,20 @@ export function useTradeState(): { updateYieldRawState, ]) } + +export function useGetTradeStateByRoute() { + const limitOrdersState = useLimitOrdersRawState() + const advancedOrdersState = useAdvancedOrdersRawState() + const swapTradeState = useSwapRawState() + const yieldRawState = useYieldRawState() + + return useCallback( + (route: RoutesValues) => { + if (route === Routes.SWAP) return swapTradeState + if (route === Routes.ABOUT) return advancedOrdersState + if (route === Routes.YIELD) return yieldRawState + return limitOrdersState + }, + [swapTradeState, advancedOrdersState, yieldRawState, limitOrdersState], + ) +} diff --git a/apps/cowswap-frontend/src/modules/trade/utils/parameterizeTradeRoute.ts b/apps/cowswap-frontend/src/modules/trade/utils/parameterizeTradeRoute.ts index 6a29a001b8..fc893ef326 100644 --- a/apps/cowswap-frontend/src/modules/trade/utils/parameterizeTradeRoute.ts +++ b/apps/cowswap-frontend/src/modules/trade/utils/parameterizeTradeRoute.ts @@ -11,7 +11,7 @@ import { RoutesValues } from 'common/constants/routes' export function parameterizeTradeRoute( { chainId, orderKind, inputCurrencyId, outputCurrencyId, inputCurrencyAmount, outputCurrencyAmount }: TradeUrlParams, route: RoutesValues, - withAmounts?: boolean + withAmounts?: boolean, ): string { const path = route .replace('/:chainId?', chainId ? `/${encodeURIComponent(chainId)}` : '') @@ -36,3 +36,10 @@ export function parameterizeTradeRoute( return path } + +export function addChainIdToRoute(route: RoutesValues, chainId: string | undefined): string { + return route + .replace('/:chainId?', chainId ? `/${encodeURIComponent(chainId)}` : '') + .replace('/:inputCurrencyId?', '') + .replace('/:outputCurrencyId?', '') +} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx index 9112bbbdf9..c81c3644e0 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/Warnings/index.tsx @@ -1,10 +1,9 @@ -import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' +import { HighFeeWarning } from 'modules/tradeWidgetAddons' export function Warnings() { return ( <> - ) } diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index 7e2ec6b0b5..00f86a2bb1 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -63,7 +63,7 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { onDismiss={tradeConfirmActions.onDismiss} isConfirmDisabled={false} priceImpact={priceImpact} - buttonText="Confirm and swap" + buttonText="Confirm Swap" recipient={recipient} appData={appData || undefined} isPriceStatic={true} diff --git a/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts index 1e7e830854..b378ead59d 100644 --- a/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts +++ b/apps/cowswap-frontend/src/modules/yield/state/yieldRawStateAtom.ts @@ -21,10 +21,17 @@ export function getDefaultYieldState(chainId: SupportedChainId | null): YieldRaw } } -export const { atom: yieldRawStateAtom, updateAtom: updateYieldRawStateAtom } = atomWithPartialUpdate( +const rawState = atomWithPartialUpdate( atomWithStorage('yieldStateAtom:v1', getDefaultYieldState(null), getJotaiIsolatedStorage()), ) +export const yieldRawStateAtom = atom((get) => ({ + ...get(rawState.atom), + orderKind: OrderKind.SELL, +})) + +export const updateYieldRawStateAtom = rawState.updateAtom + export const yieldDerivedStateAtom = atom({ ...DEFAULT_TRADE_DERIVED_STATE, }) From 16785573a04dccc05c068634c8a04de451b08c2a Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 14 Oct 2024 18:45:18 +0500 Subject: [PATCH 39/90] feat: open settings menu on slippage click --- .../containers/SettingsTab/index.tsx | 110 ++++++++++++------ .../pure/Row/RowSlippageContent/index.tsx | 7 +- .../state/settingsTabState.ts | 3 + 3 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx index e21fccba20..e43aba6dcb 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx @@ -1,10 +1,11 @@ -import { useCallback } from 'react' +import { useAtom } from 'jotai' +import { ReactElement, RefObject, useCallback, useEffect, useRef } from 'react' import { StatefulValue } from '@cowprotocol/types' import { HelpTooltip, RowBetween, RowFixed } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' -import { Menu } from '@reach/menu-button' +import { Menu, useMenuButtonContext } from '@reach/menu-button' import { Text } from 'rebass' import { ThemedText } from 'theme' @@ -16,6 +17,7 @@ import { SettingsIcon } from 'modules/trade/pure/Settings' import * as styledEl from './styled' +import { settingsTabStateAtom } from '../../state/settingsTabState' import { TransactionSettings } from '../TransactionSettings' interface SettingsTabProps { @@ -25,6 +27,8 @@ interface SettingsTabProps { } export function SettingsTab({ className, recipientToggleState, deadlineState }: SettingsTabProps) { + const menuButtonRef = useRef(null) + const [recipientToggleVisible, toggleRecipientVisibilityAux] = recipientToggleState const toggleRecipientVisibility = useCallback( (value?: boolean) => { @@ -37,40 +41,76 @@ export function SettingsTab({ className, recipientToggleState, deadlineState }: return ( - - - - - - - - Transaction Settings - - - - Interface Settings - - - - - - Custom Recipient - - Allows you to choose a destination address for the swap other than the connected one. - } + + + + + + + + + Transaction Settings + + + + Interface Settings + + + + + + Custom Recipient + + + Allows you to choose a destination address for the swap other than the connected one. + + } + /> + + - - - - - - + + + + + ) } + +interface SettingsTabControllerProps { + buttonRef: RefObject + children: ReactElement +} + +/** + * https://stackoverflow.com/questions/70596487/how-to-programmatically-expand-react-reach-ui-reach-menu-button-menu + */ +function SettingsTabController({ buttonRef, children }: SettingsTabControllerProps) { + const [settingsTabState, setSettingsTabState] = useAtom(settingsTabStateAtom) + const { isExpanded } = useMenuButtonContext() + + const toggleMenu = () => { + buttonRef.current?.dispatchEvent(new Event('mousedown', { bubbles: true })) + } + + useEffect(() => { + if (settingsTabState.open) { + toggleMenu() + } + }, [settingsTabState.open]) + + useEffect(() => { + if (settingsTabState.open && !isExpanded) { + toggleMenu() + setSettingsTabState({ open: false }) + } + }, [settingsTabState.open, isExpanded]) + + return children +} diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx index b72bf23c30..8c8d622317 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx @@ -1,3 +1,5 @@ +import { useSetAtom } from 'jotai' + import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' import { CenteredDots, HoverTooltip, LinkStyledButton, RowFixed, UI } from '@cowprotocol/ui' @@ -8,6 +10,7 @@ import styled from 'styled-components/macro' import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' +import { settingsTabStateAtom } from '../../../state/settingsTabState' import { StyledRowBetween, TextWrapper, StyledInfoIcon, TransactionText, RowStyleProps } from '../styled' const DefaultSlippage = styled.span` @@ -62,6 +65,8 @@ export function RowSlippageContent(props: RowSlippageContentProps) { isSmartSlippageLoading, } = props + const setSettingTabState = useSetAtom(settingsTabStateAtom) + const tooltipContent = slippageTooltip || (isEoaEthFlow ? getNativeSlippageTooltip(chainId, symbols) : getNonNativeSlippageTooltip()) @@ -99,7 +104,7 @@ export function RowSlippageContent(props: RowSlippageContentProps) { return ( - + setSettingTabState({ open: true })}> Date: Mon, 14 Oct 2024 18:59:32 +0500 Subject: [PATCH 40/90] chore: update btn text --- .../src/modules/swap/pure/SwapButtons/styled.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/styled.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/styled.tsx index 5d1c870e35..71d69029e2 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/styled.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/styled.tsx @@ -23,7 +23,7 @@ export function FeesExceedFromAmountMessage() { - Costs exceed from amount + Sell amount is too small From 3b769af1c4b7316eca53b90569630df5a8759a1e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 14 Oct 2024 19:54:34 +0500 Subject: [PATCH 41/90] fix: make slippage settings through --- .../state/slippageValueAndTypeAtom.ts | 48 ++++++------------- .../modules/yield/state/yieldSettingsAtom.ts | 2 +- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts index da67c788ce..f1d081e351 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/state/slippageValueAndTypeAtom.ts @@ -6,24 +6,18 @@ import { bpsToPercent } from '@cowprotocol/common-utils' import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { walletInfoAtom } from '@cowprotocol/wallet' -import { isEoaEthFlowAtom, tradeTypeAtom } from 'modules/trade' -import { TradeType } from 'modules/trade/types/TradeType' +import { isEoaEthFlowAtom } from 'modules/trade' type SlippageBpsPerNetwork = Record -type SlippagePerTradeType = Record - type SlippageType = 'smart' | 'default' | 'user' -const getDefaultSlippageState = () => - Object.keys(TradeType).reduce((acc, tradeType) => { - acc[tradeType as TradeType] = mapSupportedNetworks(null) - return acc - }, {} as SlippagePerTradeType) - -const normalTradeSlippageAtom = atomWithStorage('tradeSlippageAtom:v1', getDefaultSlippageState()) +const normalTradeSlippageAtom = atomWithStorage( + 'swapSlippageAtom:v0', + mapSupportedNetworks(null), +) -const ethFlowSlippageAtom = atomWithStorage('ethFlowSlippageAtom:v1', getDefaultSlippageState()) +const ethFlowSlippageAtom = atomWithStorage('ethFlowSlippageAtom:v0', mapSupportedNetworks(null)) export const smartTradeSlippageAtom = atom(null) @@ -37,13 +31,10 @@ export const defaultSlippageAtom = atom((get) => { const currentSlippageAtom = atom((get) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) - const tradeTypeState = get(tradeTypeAtom) const normalSlippage = get(normalTradeSlippageAtom) const ethFlowSlippage = get(ethFlowSlippageAtom) - if (!tradeTypeState) return null - - return (isEoaEthFlow ? ethFlowSlippage : normalSlippage)[tradeTypeState.tradeType]?.[chainId] ?? null + return (isEoaEthFlow ? ethFlowSlippage : normalSlippage)?.[chainId] ?? null }) export const slippageValueAndTypeAtom = atom<{ type: SlippageType; value: number }>((get) => { @@ -70,21 +61,12 @@ export const tradeSlippagePercentAtom = atom((get) => { export const setTradeSlippageAtom = atom(null, (get, set, slippageBps: number | null) => { const { chainId } = get(walletInfoAtom) const isEoaEthFlow = get(isEoaEthFlowAtom) - const tradeTypeState = get(tradeTypeAtom) - - if (tradeTypeState) { - const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalTradeSlippageAtom - const currentState = get(currentStateAtom) - - set(currentStateAtom, { - ...currentState, - [tradeTypeState.tradeType]: { - ...currentState[tradeTypeState.tradeType], - [chainId]: slippageBps, - }, - }) - } else { - // It should not happen in the normal flow - console.error('Trade type is not set, cannot set slippage!') - } + + const currentStateAtom = isEoaEthFlow ? ethFlowSlippageAtom : normalTradeSlippageAtom + const currentState = get(currentStateAtom) + + set(currentStateAtom, { + ...currentState, + [chainId]: slippageBps, + }) }) diff --git a/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts index a0eb0cf8c4..116c884354 100644 --- a/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts +++ b/apps/cowswap-frontend/src/modules/yield/state/yieldSettingsAtom.ts @@ -15,5 +15,5 @@ export const defaultYieldSettings: YieldSettingsState = { } export const { atom: yieldSettingsAtom, updateAtom: updateYieldSettingsAtom } = atomWithPartialUpdate( - atomWithStorage('yieldSettingsAtom:v1', defaultYieldSettings, getJotaiIsolatedStorage()), + atomWithStorage('yieldSettingsAtom:v0', defaultYieldSettings, getJotaiIsolatedStorage()), ) From b153e9ecd94a1472c587bbcbd1bdbabd50c32132 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 12:46:10 +0500 Subject: [PATCH 42/90] chore: merge develop --- .../common/utils/tradeSettingsTooltips.tsx | 24 ++++++++++++------- .../HighSuggestedSlippageWarning/index.tsx | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx index 1032e65a60..66b921fb4c 100644 --- a/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx +++ b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx @@ -36,18 +36,24 @@ export const getNativeSlippageTooltip = (chainId: SupportedChainId, symbols: (st matching, even in volatile market conditions.

- Orders on CoW Swap are always protected from MEV, so your slippage tolerance cannot be exploited. + {symbols?.[0] || 'Native currency'} orders can, in rare cases, be frontrun due to their on-chain component. For more + robust MEV protection, consider wrapping your {symbols?.[0] || 'native currency'} before trading. ) -export const getNonNativeSlippageTooltip = () => ( +export const getNonNativeSlippageTooltip = (isSettingsModal?: boolean) => ( - Your slippage is MEV protected: all orders are submitted with tight spread (0.1%) on-chain. -
-
- The slippage set enables a resubmission of your order in case of unfavourable price movements. -
-
- {INPUT_OUTPUT_EXPLANATION} + CoW Swap dynamically adjusts your slippage tolerance to ensure your trade executes quickly while still getting the + best price.{' '} + {isSettingsModal ? ( + <> + To override this, enter your desired slippage amount. +
+
+ Either way, your slippage is protected from MEV! + + ) : ( + "Trades are protected from MEV, so your slippage can't be exploited!" + )}
) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx index 867400f38f..f063e4dbe3 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx @@ -23,8 +23,8 @@ export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarning return ( - Beware! High dynamic slippage suggested ({`${slippageBps / 100}`}%) - + Slippage adjusted to {`${slippageBps / 100}`}% to ensure quick execution + ) } From bea304e0b2f42a7c0cbd5dcd1074c754d0923bf0 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 14:00:40 +0500 Subject: [PATCH 43/90] chore: fix yield widget --- .../src/modules/trade/containers/TradeWidgetLinks/index.tsx | 2 +- .../tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx | 6 ++++-- .../src/modules/yield/containers/YieldWidget/index.tsx | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx index 79dae77d1e..c591a5dd36 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx @@ -87,7 +87,7 @@ export function TradeWidgetLinks({ } as TradeUrlParams) : tradeContext, item.route, - true, + !isCurrentPathYield, ) const isActive = location.pathname.startsWith(routePath.split('?')[0]) diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx index 8173e9c02c..75e4388c4b 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx @@ -67,6 +67,8 @@ export function RowSlippageContent(props: RowSlippageContentProps) { const setSettingTabState = useSetAtom(settingsTabStateAtom) + const openSettings = () => setSettingTabState({ open: true }) + const tooltipContent = slippageTooltip || (isEoaEthFlow ? getNativeSlippageTooltip(chainId, symbols) : getNonNativeSlippageTooltip()) @@ -104,7 +106,7 @@ export function RowSlippageContent(props: RowSlippageContentProps) { return ( - setSettingTabState({ open: true })}> + - + {displaySlippageWithLoader} diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index f060a4a45a..a8c795f889 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -67,14 +67,14 @@ export function YieldWidget() { amount: inputCurrencyInfo.amount, fiatAmount: inputCurrencyInfo.fiatAmount, balance: inputCurrencyInfo.balance, - label: inputCurrencyInfo.label, + label: 'Sell amount', } const outputCurrencyPreviewInfo = { amount: outputCurrencyInfo.amount, fiatAmount: outputCurrencyInfo.fiatAmount, balance: outputCurrencyInfo.balance, - label: outputCurrencyInfo.label, + label: 'Receive (before fees)', } const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) From 9ecaf5731adaaaf7df239e3e5ddfe5f4349598a4 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 15:06:22 +0500 Subject: [PATCH 44/90] chore: remove old migration --- libs/tokens/src/index.ts | 7 ---- .../userAddedTokenListsAtomv2Migration.ts | 34 ------------------- 2 files changed, 41 deletions(-) delete mode 100644 libs/tokens/src/migrations/userAddedTokenListsAtomv2Migration.ts diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index b4315f4d41..011f61f5e3 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -1,10 +1,3 @@ -// Containers -import { userAddedTokenListsAtomv2Migration } from './migrations/userAddedTokenListsAtomv2Migration' - -// Run migrations first of all -// TODO: remove it after 01.04.2024 -userAddedTokenListsAtomv2Migration() - // Updaters export { TokensListsUpdater } from './updaters/TokensListsUpdater' export { UnsupportedTokensUpdater } from './updaters/UnsupportedTokensUpdater' diff --git a/libs/tokens/src/migrations/userAddedTokenListsAtomv2Migration.ts b/libs/tokens/src/migrations/userAddedTokenListsAtomv2Migration.ts deleted file mode 100644 index dfb65121a1..0000000000 --- a/libs/tokens/src/migrations/userAddedTokenListsAtomv2Migration.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SupportedChainId } from '@cowprotocol/cow-sdk' - -import { ListsSourcesByNetwork } from '../types' - -/** - * Context: https://github.com/cowprotocol/cowswap/pull/3881#issuecomment-1953522918 - * In v2 atom we added excessive data to the atom, which is not needed and causes localStorage to be bloated. - * To not loose user-added token lists, we need to migrate the data to a new atom version. - */ -export function userAddedTokenListsAtomv2Migration() { - try { - const v2StateRaw = localStorage.getItem('userAddedTokenListsAtom:v2') - if (!v2StateRaw) return - - const v2State = JSON.parse(v2StateRaw) as ListsSourcesByNetwork - - // Remove excessive data - Object.keys(v2State).forEach((chainId) => { - const state = v2State[chainId as unknown as SupportedChainId] - - state.forEach((list) => { - delete (list as never)['list'] - }) - }) - - // Save the new state - localStorage.setItem('userAddedTokenListsAtom:v3', JSON.stringify(v2State)) - } catch (e) { - console.error('userAddedTokenListsAtomv2Migration failed', e) - } - - // Remove the old state - localStorage.removeItem('userAddedTokenListsAtom:v2') -} From 2722b3e805ee159c715142a0f9063ec4a4569e01 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 15:55:56 +0500 Subject: [PATCH 45/90] feat: add default lp-token lists --- libs/tokens/src/const/lpTokensList.json | 27 +++++++++++++++++ libs/tokens/src/const/tokensLists.ts | 10 +++++-- libs/tokens/src/types.ts | 6 ++++ .../src/updaters/TokensListsUpdater/index.ts | 2 +- libs/tokens/src/utils/validateTokenList.ts | 29 ++++++++++++------- 5 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 libs/tokens/src/const/lpTokensList.json diff --git a/libs/tokens/src/const/lpTokensList.json b/libs/tokens/src/const/lpTokensList.json new file mode 100644 index 0000000000..88f0667d80 --- /dev/null +++ b/libs/tokens/src/const/lpTokensList.json @@ -0,0 +1,27 @@ +[ + { + "priority": 1, + "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/4ff665a85e88d7015c7bddac7c5e66ecfcfa77a8/src/public/lp-tokens/cow-amm.json" + }, + { + "priority": 1, + "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/uniswapv2.json" + }, + { + "priority": 1, + "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/balancerv2.json" + }, + { + "priority": 1, + "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/sushiswap.json" + }, + { + "priority": 1, + "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/pancakeswap.json" + } +] diff --git a/libs/tokens/src/const/tokensLists.ts b/libs/tokens/src/const/tokensLists.ts index d30a403e82..e08f078524 100644 --- a/libs/tokens/src/const/tokensLists.ts +++ b/libs/tokens/src/const/tokensLists.ts @@ -1,8 +1,14 @@ +import { mapSupportedNetworks } from '@cowprotocol/cow-sdk' + +import lpTokensList from './lpTokensList.json' import tokensList from './tokensList.json' -import { ListsSourcesByNetwork } from '../types' +import { ListSourceConfig, ListsSourcesByNetwork } from '../types' -export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = tokensList +export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = { + ...tokensList, + ...mapSupportedNetworks(lpTokensList as Array) +} export const UNISWAP_TOKENS_LIST = 'https://ipfs.io/ipns/tokens.uniswap.org' diff --git a/libs/tokens/src/types.ts b/libs/tokens/src/types.ts index bce11b429c..e2ce7f3fa8 100644 --- a/libs/tokens/src/types.ts +++ b/libs/tokens/src/types.ts @@ -2,10 +2,16 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { TokenInfo } from '@cowprotocol/types' import type { TokenList as UniTokenList } from '@uniswap/token-lists' +export enum TokenListCategory { + ERC20 = 'ERC20', + LP = 'LP' +} + export type ListSourceConfig = { widgetAppCode?: string priority?: number enabledByDefault?: boolean + category?: TokenListCategory source: string } diff --git a/libs/tokens/src/updaters/TokensListsUpdater/index.ts b/libs/tokens/src/updaters/TokensListsUpdater/index.ts index acc4285dab..9be000293f 100644 --- a/libs/tokens/src/updaters/TokensListsUpdater/index.ts +++ b/libs/tokens/src/updaters/TokensListsUpdater/index.ts @@ -20,7 +20,7 @@ import { ListState } from '../../types' const { atom: lastUpdateTimeAtom, updateAtom: updateLastUpdateTimeAtom } = atomWithPartialUpdate( atomWithStorage>( - 'tokens:lastUpdateTimeAtom:v1', + 'tokens:lastUpdateTimeAtom:v2', mapSupportedNetworks(0), getJotaiMergerStorage() ) diff --git a/libs/tokens/src/utils/validateTokenList.ts b/libs/tokens/src/utils/validateTokenList.ts index 98fc447dfe..eac5b01d12 100644 --- a/libs/tokens/src/utils/validateTokenList.ts +++ b/libs/tokens/src/utils/validateTokenList.ts @@ -5,11 +5,11 @@ import type { Ajv, ValidateFunction } from 'ajv' const SYMBOL_AND_NAME_VALIDATION = [ { - const: '', + const: '' }, { - pattern: '^[^<>]+$', - }, + pattern: '^[^<>]+$' + } ] const patchValidationSchema = (schema: any) => ({ @@ -23,17 +23,26 @@ const patchValidationSchema = (schema: any) => ({ symbol: { ...schema.definitions.TokenInfo.properties.symbol, maxLength: 80, - anyOf: SYMBOL_AND_NAME_VALIDATION, + anyOf: SYMBOL_AND_NAME_VALIDATION }, name: { ...schema.definitions.TokenInfo.properties.name, maxLength: 100, - anyOf: SYMBOL_AND_NAME_VALIDATION, - }, - }, + anyOf: SYMBOL_AND_NAME_VALIDATION + } + } }, - }, + ExtensionPrimitiveValue: { + 'anyOf': [{ + 'type': 'string', + 'minLength': 1, + 'maxLength': 420, + 'examples': ['#00000'] + }, { 'type': 'boolean', 'examples': [true] }, { 'type': 'number', 'examples': [15] }, { 'type': 'null' }] + } + } }) + enum ValidationSchema { LIST = 'list', TOKENS = 'tokens', @@ -48,9 +57,9 @@ const validator = new Promise((resolve) => { { ...patchValidationSchema(schema), $id: schema.$id + '#tokens', - required: ['tokens'], + required: ['tokens'] }, - ValidationSchema.TOKENS, + ValidationSchema.TOKENS ) resolve(validator) }) From aa53aac467eeaec1ab1a0e18fc792612486ce4bf Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 17:06:19 +0500 Subject: [PATCH 46/90] feat(tokens): display custom token selector for Yield widget --- .../containers/LpTokenListsWidget/index.tsx | 33 ++++++++++++ .../containers/LpTokenListsWidget/styled.tsx | 26 ++++++++++ .../containers/SelectTokenWidget/index.tsx | 7 ++- .../pure/SelectTokenModal/index.tsx | 51 +++++++++++-------- .../TradeWidget/TradeWidgetModals.tsx | 22 +++++--- .../trade/containers/TradeWidget/index.tsx | 2 +- .../trade/containers/TradeWidget/types.ts | 1 + .../updaters/SmartSlippageUpdater/index.ts | 1 - .../yield/containers/YieldWidget/index.tsx | 2 + libs/tokens/src/const/lpTokensList.json | 2 +- libs/tokens/src/types.ts | 3 +- 11 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/styled.tsx diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx new file mode 100644 index 0000000000..12ebba6749 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -0,0 +1,33 @@ +import { ReactNode, useState } from 'react' + +import { TokenListCategory } from '@cowprotocol/tokens' + +import { TabButton, TabsContainer } from './styled' + +interface LpTokenListsProps { + children: ReactNode +} + +const tabs = [ + { title: 'All', value: null }, + { title: 'Pool tokens', value: TokenListCategory.LP }, + { title: 'CoW AMM only', value: TokenListCategory.COW_AMM_LP } +] + +export function LpTokenListsWidget({ children }: LpTokenListsProps) { + const [listsCategory, setListsCategory] = useState(null) + + return <> + + {tabs.map(tab => { + return ( setListsCategory(tab.value)}>{tab.title}) + })} + + {listsCategory === null && children} + {/*TODO*/} + {listsCategory === TokenListCategory.LP && 'LP'} + {listsCategory === TokenListCategory.COW_AMM_LP && 'COW_AMM_LP'} + +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/styled.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/styled.tsx new file mode 100644 index 0000000000..8640f9e3b5 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/styled.tsx @@ -0,0 +1,26 @@ +import { UI } from '@cowprotocol/ui' + +import styled from 'styled-components/macro' + +export const TabsContainer = styled.div` + display: flex; + flex-direction: row; + gap: 12px; + margin: 0 20px 15px 20px; + +` +export const TabButton = styled.button<{ active$: boolean }>` + cursor: pointer; + border: 1px solid var(${UI.COLOR_BACKGROUND}); + outline: none; + padding: 8px 16px; + border-radius: 32px; + font-size: 13px; + font-weight: 600; + color: var(${UI.COLOR_TEXT}); + background: ${({ active$ }) => active$ ? `var(${UI.COLOR_BACKGROUND})` : 'transparent'}; + + &:hover { + background : var(${UI.COLOR_BACKGROUND}); + } +` diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx index e4296a5f64..9a30f04cb0 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx @@ -37,7 +37,11 @@ const Wrapper = styled.div` } ` -export function SelectTokenWidget() { +interface SelectTokenWidgetProps { + displayLpTokenLists?: boolean +} + +export function SelectTokenWidget({displayLpTokenLists}: SelectTokenWidgetProps) { const { open, onSelectToken, tokenToImport, listToImport, selectedToken, onInputPressEnter } = useSelectTokenWidgetState() const [isManageWidgetOpen, setIsManageWidgetOpen] = useState(false) @@ -137,6 +141,7 @@ export function SelectTokenWidget() { return ( (defaultInputValue) @@ -61,9 +64,32 @@ export function SelectTokenModal(props: SelectTokenModalProps) { selectedToken, onSelectToken, unsupportedTokens, - permitCompatibleTokens, + permitCompatibleTokens } + const allListsContent = <> + + + + + {inputValue.trim() ? ( + + ) : ( + + )} + +
+ + Manage Token Lists + +
+ + return ( @@ -80,26 +106,9 @@ export function SelectTokenModal(props: SelectTokenModalProps) { placeholder="Search name or paste address" /> - - - - - {inputValue.trim() ? ( - - ) : ( - - )} - -
- - Manage Token Lists - -
+ {displayLpTokenLists + ? {allListsContent} + : allListsContent}
) } diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetModals.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetModals.tsx index c6f916a43b..be84faee34 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetModals.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetModals.tsx @@ -8,9 +8,9 @@ import { SelectTokenWidget, useSelectTokenWidgetState, useTokenListAddingError, - useUpdateSelectTokenWidgetState, + useUpdateSelectTokenWidgetState } from 'modules/tokensList' -import { ZeroApprovalModal, useZeroApproveModalState } from 'modules/zeroApproval' +import { useZeroApproveModalState, ZeroApprovalModal } from 'modules/zeroApproval' import { TradeApproveModal } from 'common/containers/TradeApprove' import { useTradeApproveState } from 'common/hooks/useTradeApproveState' @@ -24,7 +24,17 @@ import { useTradeState } from '../../hooks/useTradeState' import { useWrapNativeScreenState } from '../../hooks/useWrapNativeScreenState' import { WrapNativeModal } from '../WrapNativeModal' -export function TradeWidgetModals(confirmModal: ReactNode | undefined, genericModal: ReactNode | undefined) { +interface TradeWidgetModalsProps { + confirmModal: ReactNode | undefined, + genericModal: ReactNode | undefined + selectTokenWidget: ReactNode | undefined +} + +export function TradeWidgetModals({ + confirmModal, + genericModal, + selectTokenWidget = +}: TradeWidgetModalsProps) { const { chainId, account } = useWalletInfo() const { state: rawState } = useTradeState() const importTokenCallback = useAddUserToken() @@ -37,7 +47,7 @@ export function TradeWidgetModals(confirmModal: ReactNode | undefined, genericMo const { isModalOpen: isZeroApprovalModalOpen, closeModal: closeZeroApprovalModal } = useZeroApproveModalState() const { tokensToImport, - modalState: { isModalOpen: isAutoImportModalOpen, closeModal: closeAutoImportModal }, + modalState: { isModalOpen: isAutoImportModalOpen, closeModal: closeAutoImportModal } } = useAutoImportTokensState(rawState?.inputCurrencyId, rawState?.outputCurrencyId) const { onDismiss: closeTradeConfirm } = useTradeConfirmActions() @@ -59,7 +69,7 @@ export function TradeWidgetModals(confirmModal: ReactNode | undefined, genericMo updateSelectTokenWidgetState, setWrapNativeScreenState, updateTradeApproveState, - setTokenListAddingError, + setTokenListAddingError ]) const error = tokenListAddingError || approveError || confirmError @@ -84,7 +94,7 @@ export function TradeWidgetModals(confirmModal: ReactNode | undefined, genericMo } if (isTokenSelectOpen) { - return + return selectTokenWidget } if (isWrapNativeOpen) { diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx index 764652582f..743505a670 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx @@ -14,7 +14,7 @@ export function TradeWidget(props: TradeWidgetProps) { tradeQuoteStateOverride, enableSmartSlippage, } = params - const modals = TradeWidgetModals(confirmModal, genericModal) + const modals = TradeWidgetModals({confirmModal, genericModal, selectTokenWidget: slots.selectTokenWidget}) return ( <> diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts index d0e0e8e3d8..fe0ecd5bc2 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts @@ -38,6 +38,7 @@ export interface TradeWidgetSlots { bottomContent?(warnings: ReactNode | null): ReactNode outerContent?: ReactNode updaters?: ReactNode + selectTokenWidget?: ReactNode } export interface TradeWidgetProps { diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts index cbedf858b7..88efcdae6a 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts @@ -2,7 +2,6 @@ import { useSetAtom } from 'jotai' import { useEffect } from 'react' import { useTradeConfirmState } from 'modules/trade' -import { useHighFeeWarning } from 'modules/tradeWidgetAddons' import { useSmartSlippageFromBff } from './useSmartSlippageFromBff' import { useSmartSlippageFromFeeMultiplier } from './useSmartSlippageFromFeeMultiplier' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index a8c795f889..7f49de63e0 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -1,5 +1,6 @@ import { Field } from 'legacy/state/types' +import { SelectTokenWidget } from 'modules/tokensList' import { TradeWidget, TradeWidgetSlots, @@ -80,6 +81,7 @@ export function YieldWidget() { const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) const slots: TradeWidgetSlots = { + selectTokenWidget: , settingsWidget: , bottomContent(tradeWarnings) { return ( diff --git a/libs/tokens/src/const/lpTokensList.json b/libs/tokens/src/const/lpTokensList.json index 88f0667d80..e5709d5c20 100644 --- a/libs/tokens/src/const/lpTokensList.json +++ b/libs/tokens/src/const/lpTokensList.json @@ -1,7 +1,7 @@ [ { "priority": 1, - "category": "LP", + "category": "COW_AMM_LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/4ff665a85e88d7015c7bddac7c5e66ecfcfa77a8/src/public/lp-tokens/cow-amm.json" }, { diff --git a/libs/tokens/src/types.ts b/libs/tokens/src/types.ts index e2ce7f3fa8..0bf4d0c187 100644 --- a/libs/tokens/src/types.ts +++ b/libs/tokens/src/types.ts @@ -4,7 +4,8 @@ import type { TokenList as UniTokenList } from '@uniswap/token-lists' export enum TokenListCategory { ERC20 = 'ERC20', - LP = 'LP' + LP = 'LP', + COW_AMM_LP = 'COW_AMM_LP', } export type ListSourceConfig = { From ac0a957e8aa6e58cde7da09330156124036212a9 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 15 Oct 2024 20:21:57 +0500 Subject: [PATCH 47/90] feat(tokens): display lp-token lists --- .../containers/LpTokenListsWidget/index.tsx | 42 ++++++----- .../tokensList/pure/LpTokenLists/index.tsx | 74 +++++++++++++++++++ .../tokensList/pure/LpTokenLists/styled.ts | 27 +++++++ .../tokens/src/hooks/tokens/useAllLpTokens.ts | 43 +++++++++++ libs/tokens/src/index.ts | 1 + libs/tokens/src/services/fetchTokenList.ts | 21 +++--- libs/tokens/src/state/tokens/allTokensAtom.ts | 26 ++----- libs/tokens/src/types.ts | 5 +- libs/tokens/src/utils/parseTokenInfo.ts | 25 +++++++ 9 files changed, 213 insertions(+), 51 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts create mode 100644 libs/tokens/src/hooks/tokens/useAllLpTokens.ts create mode 100644 libs/tokens/src/utils/parseTokenInfo.ts diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 12ebba6749..3165dd2fb5 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,33 +1,41 @@ import { ReactNode, useState } from 'react' -import { TokenListCategory } from '@cowprotocol/tokens' +import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' import { TabButton, TabsContainer } from './styled' +import { LpTokenLists } from '../../pure/LpTokenLists' + interface LpTokenListsProps { children: ReactNode } const tabs = [ { title: 'All', value: null }, - { title: 'Pool tokens', value: TokenListCategory.LP }, - { title: 'CoW AMM only', value: TokenListCategory.COW_AMM_LP } + { title: 'Pool tokens', value: [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] }, + { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, ] export function LpTokenListsWidget({ children }: LpTokenListsProps) { - const [listsCategory, setListsCategory] = useState(null) + const [listsCategories, setListsCategories] = useState(null) + const lpTokens = useAllLpTokens(listsCategories) - return <> - - {tabs.map(tab => { - return ( setListsCategory(tab.value)}>{tab.title}) - })} - - {listsCategory === null && children} - {/*TODO*/} - {listsCategory === TokenListCategory.LP && 'LP'} - {listsCategory === TokenListCategory.COW_AMM_LP && 'COW_AMM_LP'} - + return ( + <> + + {tabs.map((tab) => { + return ( + setListsCategories(tab.value)} + > + {tab.title} + + ) + })} + + {listsCategories === null ? children : } + + ) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx new file mode 100644 index 0000000000..d43fd0b563 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -0,0 +1,74 @@ +import { useCallback, useRef } from 'react' + +import { TokenWithLogo } from '@cowprotocol/common-const' + +import { useVirtualizer } from '@tanstack/react-virtual' +import ms from 'ms.macro' + +import { ListInner, ListScroller, ListWrapper, Wrapper } from './styled' + +const scrollDelay = ms`400ms` + +const estimateSize = () => 56 + +interface LpTokenListsProps { + tokens: TokenWithLogo[] +} + +export function LpTokenLists({ tokens }: LpTokenListsProps) { + const parentRef = useRef(null) + const wrapperRef = useRef(null) + const scrollTimeoutRef = useRef() + + const onScroll = useCallback(() => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + if (wrapperRef.current) wrapperRef.current.style.pointerEvents = 'none' + } + + scrollTimeoutRef.current = setTimeout(() => { + if (wrapperRef.current) wrapperRef.current.style.pointerEvents = '' + }, scrollDelay) + }, []) + + const virtualizer = useVirtualizer({ + getScrollElement: () => parentRef.current, + count: tokens.length, + estimateSize, + overscan: 5, + }) + + const items = virtualizer.getVirtualItems() + + return ( + +
+ Pool + Balance + APR + +
+ + + + {items.map((item) => { + const token = tokens[item.index] + return ( +
+ {token.name} + --- + 40% + +
+ ) + })} +
+
+
+
+
Can' find?
+ Create a pool +
+
+ ) +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts new file mode 100644 index 0000000000..cacec07aaa --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -0,0 +1,27 @@ +import styled from 'styled-components/macro' + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + overflow: auto; +` +export const ListWrapper = styled.div` + display: block; + width: 100%; + height: 100%; + overflow: auto; + + ${({ theme }) => theme.colorScrollbar}; +` + +export const ListInner = styled.div` + width: 100%; + position: relative; +` + +export const ListScroller = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; +` diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts new file mode 100644 index 0000000000..69e9714c48 --- /dev/null +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -0,0 +1,43 @@ +import { useAtomValue } from 'jotai' + +import { SWR_NO_REFRESH_OPTIONS, TokenWithLogo } from '@cowprotocol/common-const' + +import useSWR from 'swr' + +import { environmentAtom } from '../../state/environmentAtom' +import { listsStatesListAtom } from '../../state/tokenLists/tokenListsStateAtom' +import { TokenListCategory, TokensMap } from '../../types' +import { parseTokenInfo } from '../../utils/parseTokenInfo' +import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' + +const fallbackData: TokenWithLogo[] = [] + +export function useAllLpTokens(categories: TokenListCategory[] | null): TokenWithLogo[] { + const { chainId } = useAtomValue(environmentAtom) + const state = useAtomValue(listsStatesListAtom) + + return useSWR( + categories ? [state, chainId, categories] : null, + ([state, chainId, categories]) => { + const tokensMap = state.reduce((acc, list) => { + if (!list.category || !categories.includes(list.category)) { + return acc + } + + list.list.tokens.forEach((token) => { + const tokenInfo = parseTokenInfo(chainId, token) + const tokenAddressKey = tokenInfo?.address.toLowerCase() + + if (!tokenInfo || !tokenAddressKey) return + + acc[tokenAddressKey] = tokenInfo + }) + + return acc + }, {}) + + return tokenMapToListWithLogo(tokensMap, chainId) + }, + { ...SWR_NO_REFRESH_OPTIONS, fallbackData }, + ).data +} diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index 011f61f5e3..6c61741e59 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -40,6 +40,7 @@ export { useAreThereTokensWithSameSymbol } from './hooks/tokens/useAreThereToken export { useSearchList } from './hooks/lists/useSearchList' export { useSearchToken } from './hooks/tokens/useSearchToken' export { useSearchNonExistentToken } from './hooks/tokens/useSearchNonExistentToken' +export { useAllLpTokens } from './hooks/tokens/useAllLpTokens' // Utils export { getTokenListViewLink } from './utils/getTokenListViewLink' diff --git a/libs/tokens/src/services/fetchTokenList.ts b/libs/tokens/src/services/fetchTokenList.ts index 4f9b159424..80fb2db075 100644 --- a/libs/tokens/src/services/fetchTokenList.ts +++ b/libs/tokens/src/services/fetchTokenList.ts @@ -21,11 +21,7 @@ export function fetchTokenList(list: ListSourceConfig): Promise { async function fetchTokenListByUrl(list: ListSourceConfig): Promise { return _fetchTokenList(list.source, [list.source]).then((result) => { - return { - ...result, - priority: list.priority, - source: list.source, - } + return listStateFromSourceConfig(result, list) }) } @@ -35,11 +31,7 @@ async function fetchTokenListByEnsName(list: ListSourceConfig): Promise { - return { - ...result, - priority: list.priority, - source: list.source, - } + return listStateFromSourceConfig(result, list) }) } @@ -90,6 +82,15 @@ async function _fetchTokenList(source: string, urls: string[]): Promise { // Remove tokens from the list that don't have valid addresses const tokens = list.tokens.filter(({ address }) => isAddress(address)) diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index 855c70dd5a..fde9b92bea 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -8,11 +8,11 @@ import { userAddedTokensAtom } from './userAddedTokensAtom' import { TokensMap } from '../../types' import { lowerCaseTokensMap } from '../../utils/lowerCaseTokensMap' +import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' import { environmentAtom } from '../environmentAtom' import { listsEnabledStateAtom, listsStatesListAtom } from '../tokenLists/tokenListsStateAtom' - export interface TokensByAddress { [address: string]: TokenWithLogo | undefined } @@ -26,12 +26,6 @@ export interface TokensState { inactiveTokens: TokensMap } -interface BridgeInfo { - [chainId: number]: { - tokenAddress: string - } -} - export const tokensStateAtom = atom((get) => { const { chainId } = get(environmentAtom) const listsStatesList = get(listsStatesListAtom) @@ -42,18 +36,10 @@ export const tokensStateAtom = atom((get) => { const isListEnabled = listsEnabledState[list.source] list.list.tokens.forEach((token) => { - const bridgeInfo = token.extensions?.['bridgeInfo'] as never as BridgeInfo | undefined - const currentChainInfo = bridgeInfo?.[chainId] - const bridgeAddress = currentChainInfo?.tokenAddress + const tokenInfo = parseTokenInfo(chainId, token) + const tokenAddressKey = tokenInfo?.address.toLowerCase() - if (token.chainId !== chainId && !bridgeAddress) return - - const tokenAddress = bridgeAddress || token.address - const tokenAddressKey = tokenAddress.toLowerCase() - const tokenInfo: TokenInfo = { - ...token, - address: tokenAddress, - } + if (!tokenInfo || !tokenAddressKey) return if (isListEnabled) { if (!acc.activeTokens[tokenAddressKey]) { @@ -68,7 +54,7 @@ export const tokensStateAtom = atom((get) => { return acc }, - { activeTokens: {}, inactiveTokens: {} } + { activeTokens: {}, inactiveTokens: {} }, ) }) @@ -92,7 +78,7 @@ export const activeTokensAtom = atom((get) => { ...lowerCaseTokensMap(userAddedTokens[chainId]), ...lowerCaseTokensMap(favoriteTokensState[chainId]), }, - chainId + chainId, ) }) diff --git a/libs/tokens/src/types.ts b/libs/tokens/src/types.ts index 0bf4d0c187..d1e6d4a206 100644 --- a/libs/tokens/src/types.ts +++ b/libs/tokens/src/types.ts @@ -24,11 +24,8 @@ export type UnsupportedTokensState = { [tokenAddress: string]: { dateAdded: numb export type ListsEnabledState = { [listId: string]: boolean | undefined } -export interface ListState { - source: string +export interface ListState extends Pick{ list: UniTokenList - widgetAppCode?: string - priority?: number isEnabled?: boolean } diff --git a/libs/tokens/src/utils/parseTokenInfo.ts b/libs/tokens/src/utils/parseTokenInfo.ts new file mode 100644 index 0000000000..6c9a82a9e6 --- /dev/null +++ b/libs/tokens/src/utils/parseTokenInfo.ts @@ -0,0 +1,25 @@ +import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { TokenInfo } from '@cowprotocol/types' +import type { TokenInfo as Erc20TokenInfo } from '@uniswap/token-lists' + + +interface BridgeInfo { + [chainId: number]: { + tokenAddress: string + } +} + +export function parseTokenInfo(chainId: SupportedChainId, token: Erc20TokenInfo): TokenInfo | null { + const bridgeInfo = token.extensions?.['bridgeInfo'] as never as BridgeInfo | undefined + const currentChainInfo = bridgeInfo?.[chainId] + const bridgeAddress = currentChainInfo?.tokenAddress + + if (token.chainId !== chainId && !bridgeAddress) return null + + const tokenAddress = bridgeAddress || token.address + + return { + ...token, + address: tokenAddress + } +} From 704d003474cbe630d9338391ebd01f0bf3ba000d Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 13:14:04 +0500 Subject: [PATCH 48/90] feat: display lp-token logo --- .../containers/LpTokenListsWidget/index.tsx | 5 +- .../tokensList/pure/LpTokenLists/index.tsx | 59 +++++++++++++---- .../tokensList/pure/LpTokenLists/styled.ts | 65 +++++++++++++++++++ libs/common-const/src/types.ts | 29 ++++++++- libs/tokens/src/const/lpTokensList.json | 10 +-- libs/tokens/src/const/tokensLists.ts | 8 +-- .../tokens/src/hooks/tokens/useAllLpTokens.ts | 8 +-- .../state/tokenLists/tokenListsStateAtom.ts | 4 +- .../src/updaters/TokensListsUpdater/index.ts | 7 +- libs/tokens/src/utils/parseTokenInfo.ts | 5 +- .../src/utils/tokenMapToListWithLogo.ts | 4 +- libs/types/src/common.ts | 1 + libs/ui/src/pure/InfoTooltip/index.tsx | 8 ++- 13 files changed, 172 insertions(+), 41 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 3165dd2fb5..55fd841a73 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,6 +1,6 @@ import { ReactNode, useState } from 'react' -import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { TokenListCategory, useAllLpTokens, useTokensByAddressMap } from '@cowprotocol/tokens' import { TabButton, TabsContainer } from './styled' @@ -19,6 +19,7 @@ const tabs = [ export function LpTokenListsWidget({ children }: LpTokenListsProps) { const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) + const tokensByAddress = useTokensByAddressMap() return ( <> @@ -35,7 +36,7 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { ) })} - {listsCategories === null ? children : } + {listsCategories === null ? children : } ) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index d43fd0b563..49bcdfabf8 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -1,21 +1,34 @@ import { useCallback, useRef } from 'react' -import { TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken } from '@cowprotocol/common-const' +import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' +import { InfoTooltip, TokenName, TokenSymbol } from '@cowprotocol/ui' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' -import { ListInner, ListScroller, ListWrapper, Wrapper } from './styled' +import { + ListHeader, + ListInner, + ListItem, + ListScroller, + ListWrapper, + LpTokenInfo, + LpTokenLogo, + LpTokenWrapper, + Wrapper, +} from './styled' const scrollDelay = ms`400ms` const estimateSize = () => 56 interface LpTokenListsProps { - tokens: TokenWithLogo[] + lpTokens: LpToken[] + tokensByAddress: TokensByAddress } -export function LpTokenLists({ tokens }: LpTokenListsProps) { +export function LpTokenLists({ lpTokens, tokensByAddress }: LpTokenListsProps) { const parentRef = useRef(null) const wrapperRef = useRef(null) const scrollTimeoutRef = useRef() @@ -33,7 +46,7 @@ export function LpTokenLists({ tokens }: LpTokenListsProps) { const virtualizer = useVirtualizer({ getScrollElement: () => parentRef.current, - count: tokens.length, + count: lpTokens.length, estimateSize, overscan: 5, }) @@ -42,24 +55,46 @@ export function LpTokenLists({ tokens }: LpTokenListsProps) { return ( -
+ Pool Balance APR -
+ {items.map((item) => { - const token = tokens[item.index] + const token = lpTokens[item.index] + const token0 = token.tokens?.[0]?.toLowerCase() + const token1 = token.tokens?.[1]?.toLowerCase() + return ( -
- {token.name} + + + +
+ +
+
+ +
+
+ + + + +

+ +

+
+
--- 40% - -
+ + TODO + + ) })}
diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index cacec07aaa..2b02f71845 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -1,9 +1,12 @@ +import { UI } from '@cowprotocol/ui' + import styled from 'styled-components/macro' export const Wrapper = styled.div` display: flex; flex-direction: column; overflow: auto; + margin-bottom: 15px; ` export const ListWrapper = styled.div` display: block; @@ -25,3 +28,65 @@ export const ListScroller = styled.div` left: 0; width: 100%; ` + +export const LpTokenWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +` + +export const ListHeader = styled.div` + display: grid; + grid-template-columns: 1fr 100px 50px 20px; + font-size: 12px; + font-weight: 500; + color: var(${UI.COLOR_TEXT_OPACITY_70}); + margin: 0 20px 15px 20px; +` + +export const ListItem = styled.div` + display: grid; + grid-template-columns: 1fr 100px 50px 20px; + padding: 10px 20px; + cursor: pointer; + + &:hover { + background: var(${UI.COLOR_PAPER_DARKER}); + } +` + +export const LpTokenLogo = styled.div` + --size: 36px; + --halfSize: 18px; + + width: var(--size); + height: var(--size); + position: relative; + + > div { + position: absolute; + width: var(--halfSize); + overflow: hidden; + } + + > div:last-child { + right: -1px; + } + + > div:last-child > div { + right: 100%; + position: relative; + } +` + +export const LpTokenInfo = styled.div` + display: flex; + flex-direction: column; + + > p { + margin: 0; + font-size: 13px; + color: var(${UI.COLOR_TEXT_OPACITY_70}); + } +` diff --git a/libs/common-const/src/types.ts b/libs/common-const/src/types.ts index 33c595ebc7..ed3d668c4a 100644 --- a/libs/common-const/src/types.ts +++ b/libs/common-const/src/types.ts @@ -1,6 +1,8 @@ import { TokenInfo } from '@cowprotocol/types' import { Token } from '@uniswap/sdk-core' +const emptyTokens = [] as string[] + export class TokenWithLogo extends Token { static fromToken(token: Token | TokenInfo, logoURI?: string): TokenWithLogo { return new TokenWithLogo(logoURI, token.chainId, token.address, token.decimals, token.symbol, token.name) @@ -13,8 +15,33 @@ export class TokenWithLogo extends Token { decimals: number, symbol?: string, name?: string, - bypassChecksum?: boolean + bypassChecksum?: boolean, ) { super(chainId, address, decimals, symbol, name, bypassChecksum) } } + +export class LpToken extends TokenWithLogo { + static fromToken(token: Token | TokenInfo): LpToken { + return new LpToken( + token instanceof Token ? emptyTokens : token.tokens || emptyTokens, + token.chainId, + token.address, + token.decimals, + token.symbol, + token.name, + ) + } + + constructor( + public tokens: string[], + chainId: number, + address: string, + decimals: number, + symbol?: string, + name?: string, + bypassChecksum?: boolean, + ) { + super(undefined, chainId, address, decimals, symbol, name, bypassChecksum) + } +} diff --git a/libs/tokens/src/const/lpTokensList.json b/libs/tokens/src/const/lpTokensList.json index e5709d5c20..0cce6b5c68 100644 --- a/libs/tokens/src/const/lpTokensList.json +++ b/libs/tokens/src/const/lpTokensList.json @@ -1,26 +1,26 @@ [ { - "priority": 1, + "priority": 100, "category": "COW_AMM_LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/4ff665a85e88d7015c7bddac7c5e66ecfcfa77a8/src/public/lp-tokens/cow-amm.json" }, { - "priority": 1, + "priority": 101, "category": "LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/uniswapv2.json" }, { - "priority": 1, + "priority": 102, "category": "LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/balancerv2.json" }, { - "priority": 1, + "priority": 103, "category": "LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/sushiswap.json" }, { - "priority": 1, + "priority": 104, "category": "LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/pancakeswap.json" } diff --git a/libs/tokens/src/const/tokensLists.ts b/libs/tokens/src/const/tokensLists.ts index e08f078524..f692865510 100644 --- a/libs/tokens/src/const/tokensLists.ts +++ b/libs/tokens/src/const/tokensLists.ts @@ -5,10 +5,10 @@ import tokensList from './tokensList.json' import { ListSourceConfig, ListsSourcesByNetwork } from '../types' -export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = { - ...tokensList, - ...mapSupportedNetworks(lpTokensList as Array) -} +export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = mapSupportedNetworks((chainId) => [ + ...tokensList[chainId], + ...(lpTokensList as Array), +]) export const UNISWAP_TOKENS_LIST = 'https://ipfs.io/ipns/tokens.uniswap.org' diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts index 69e9714c48..68d94a767e 100644 --- a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -1,6 +1,6 @@ import { useAtomValue } from 'jotai' -import { SWR_NO_REFRESH_OPTIONS, TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import useSWR from 'swr' @@ -10,9 +10,9 @@ import { TokenListCategory, TokensMap } from '../../types' import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' -const fallbackData: TokenWithLogo[] = [] +const fallbackData: LpToken[] = [] -export function useAllLpTokens(categories: TokenListCategory[] | null): TokenWithLogo[] { +export function useAllLpTokens(categories: TokenListCategory[] | null): LpToken[] { const { chainId } = useAtomValue(environmentAtom) const state = useAtomValue(listsStatesListAtom) @@ -36,7 +36,7 @@ export function useAllLpTokens(categories: TokenListCategory[] | null): TokenWit return acc }, {}) - return tokenMapToListWithLogo(tokensMap, chainId) + return tokenMapToListWithLogo(tokensMap, chainId) as LpToken[] }, { ...SWR_NO_REFRESH_OPTIONS, fallbackData }, ).data diff --git a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts index d983ae7120..3728e1baa7 100644 --- a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts +++ b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts @@ -39,7 +39,7 @@ const curatedListSourceAtom = atom((get) => { export const userAddedListsSourcesAtom = atomWithStorage( 'userAddedTokenListsAtom:v3', mapSupportedNetworks([]), - getJotaiMergerStorage() + getJotaiMergerStorage(), ) export const allListsSourcesAtom = atom((get) => { @@ -57,7 +57,7 @@ export const allListsSourcesAtom = atom((get) => { export const listsStatesByChainAtom = atomWithStorage( 'allTokenListsInfoAtom:v3', mapSupportedNetworks({}), - getJotaiMergerStorage() + getJotaiMergerStorage(), ) export const tokenListsUpdatingAtom = atom(false) diff --git a/libs/tokens/src/updaters/TokensListsUpdater/index.ts b/libs/tokens/src/updaters/TokensListsUpdater/index.ts index 9be000293f..7a882406e3 100644 --- a/libs/tokens/src/updaters/TokensListsUpdater/index.ts +++ b/libs/tokens/src/updaters/TokensListsUpdater/index.ts @@ -2,7 +2,6 @@ import { useAtomValue, useSetAtom } from 'jotai' import { atomWithStorage } from 'jotai/utils' import { useEffect } from 'react' - import { atomWithPartialUpdate, isInjectedWidget } from '@cowprotocol/common-utils' import { getJotaiMergerStorage } from '@cowprotocol/core' import { SupportedChainId, mapSupportedNetworks } from '@cowprotocol/cow-sdk' @@ -22,8 +21,8 @@ const { atom: lastUpdateTimeAtom, updateAtom: updateLastUpdateTimeAtom } = atomW atomWithStorage>( 'tokens:lastUpdateTimeAtom:v2', mapSupportedNetworks(0), - getJotaiMergerStorage() - ) + getJotaiMergerStorage(), + ), ) const swrOptions: SWRConfiguration = { @@ -68,7 +67,7 @@ export function TokensListsUpdater({ chainId: currentChainId, isGeoBlockEnabled return Promise.allSettled(allTokensLists.map(fetchTokenList)).then(getFulfilledResults) }, - swrOptions + swrOptions, ) // Fulfill tokens lists with tokens from fetched lists diff --git a/libs/tokens/src/utils/parseTokenInfo.ts b/libs/tokens/src/utils/parseTokenInfo.ts index 6c9a82a9e6..2c9f2a67a1 100644 --- a/libs/tokens/src/utils/parseTokenInfo.ts +++ b/libs/tokens/src/utils/parseTokenInfo.ts @@ -2,7 +2,6 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { TokenInfo } from '@cowprotocol/types' import type { TokenInfo as Erc20TokenInfo } from '@uniswap/token-lists' - interface BridgeInfo { [chainId: number]: { tokenAddress: string @@ -17,9 +16,11 @@ export function parseTokenInfo(chainId: SupportedChainId, token: Erc20TokenInfo) if (token.chainId !== chainId && !bridgeAddress) return null const tokenAddress = bridgeAddress || token.address + const lpTokens = token.extensions?.['tokens'] as string | undefined return { ...token, - address: tokenAddress + address: tokenAddress, + ...(lpTokens ? { tokens: lpTokens.split(',') } : undefined), } } diff --git a/libs/tokens/src/utils/tokenMapToListWithLogo.ts b/libs/tokens/src/utils/tokenMapToListWithLogo.ts index ab0345687b..d66c8a1d73 100644 --- a/libs/tokens/src/utils/tokenMapToListWithLogo.ts +++ b/libs/tokens/src/utils/tokenMapToListWithLogo.ts @@ -1,4 +1,4 @@ -import { TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { TokensMap } from '../types' @@ -9,5 +9,5 @@ export function tokenMapToListWithLogo(tokenMap: TokensMap, chainId: number): To return Object.values(tokenMap) .filter((token) => token.chainId === chainId) .sort((a, b) => a.symbol.localeCompare(b.symbol)) - .map((token) => TokenWithLogo.fromToken(token, token.logoURI)) + .map((token) => (token.tokens ? LpToken.fromToken(token) : TokenWithLogo.fromToken(token, token.logoURI))) } diff --git a/libs/types/src/common.ts b/libs/types/src/common.ts index 50ff3e3638..0228b65166 100644 --- a/libs/types/src/common.ts +++ b/libs/types/src/common.ts @@ -23,4 +23,5 @@ export type TokenInfo = { decimals: number symbol: string logoURI?: string + tokens?: string[] } diff --git a/libs/ui/src/pure/InfoTooltip/index.tsx b/libs/ui/src/pure/InfoTooltip/index.tsx index 03ad62fb31..21c12d0b65 100644 --- a/libs/ui/src/pure/InfoTooltip/index.tsx +++ b/libs/ui/src/pure/InfoTooltip/index.tsx @@ -31,13 +31,15 @@ const StyledTooltipContainer = styled(TooltipContainer)` ` export interface InfoTooltipProps { - content: ReactNode + // @deprecated use children instead + content?: ReactNode + children?: ReactNode size?: number className?: string } -export function InfoTooltip({ content, className, size = 16 }: InfoTooltipProps) { - const tooltipContent = {content} +export function InfoTooltip({ content, children, className, size = 16 }: InfoTooltipProps) { + const tooltipContent = {children || content} return ( From bae773dc70fdb8e0201f6d6bfff1c6eeda0d6f82 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 15:35:24 +0500 Subject: [PATCH 49/90] chore: slippage label --- .../modules/yield/containers/YieldConfirmModal/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx index 00f86a2bb1..08b205bac0 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldConfirmModal/index.tsx @@ -17,13 +17,19 @@ import { HighFeeWarning } from 'modules/tradeWidgetAddons' import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview' +import { getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' const CONFIRM_TITLE = 'Confirm order' +const labelsAndTooltips = { + slippageTooltip: getNonNativeSlippageTooltip(), +} + export interface YieldConfirmModalProps { doTrade(): Promise + inputCurrencyInfo: CurrencyPreviewInfo outputCurrencyInfo: CurrencyPreviewInfo priceImpact: PriceImpact @@ -77,6 +83,7 @@ export function YieldConfirmModal(props: YieldConfirmModalProps) { receiveAmountInfo={receiveAmountInfo} recipient={recipient} account={account} + labelsAndTooltips={labelsAndTooltips} hideLimitPrice hideUsdValues withTimelineDot={false} From c01f9b841659c3a5c09ce23aedebfb401d568bb6 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 15:51:00 +0500 Subject: [PATCH 50/90] fix: fix default trade state in menu --- .../trade/containers/TradeWidgetLinks/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx index c591a5dd36..5584092097 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx @@ -19,7 +19,7 @@ import * as styledEl from './styled' import { useTradeRouteContext } from '../../hooks/useTradeRouteContext' import { useGetTradeStateByRoute } from '../../hooks/useTradeState' -import { TradeUrlParams } from '../../types/TradeRawState' +import { getDefaultTradeRawState, TradeUrlParams } from '../../types/TradeRawState' import { addChainIdToRoute, parameterizeTradeRoute } from '../../utils/parameterizeTradeRoute' interface MenuItemConfig { @@ -73,16 +73,19 @@ export function TradeWidgetLinks({ const menuItemsElements = useMemo(() => { return enabledItems.map((item) => { const isItemYield = item.route === Routes.YIELD - const isCurrentPathYield = location.pathname.startsWith(addChainIdToRoute(Routes.YIELD, tradeContext.chainId)) + const chainId = tradeContext.chainId + + const isCurrentPathYield = location.pathname.startsWith(addChainIdToRoute(Routes.YIELD, chainId)) const itemTradeState = getTradeStateByType(item.route) const routePath = isItemYield - ? addChainIdToRoute(item.route, tradeContext.chainId) + ? addChainIdToRoute(item.route, chainId) : parameterizeTradeRoute( isCurrentPathYield ? ({ - chainId: tradeContext.chainId, - inputCurrencyId: itemTradeState.inputCurrencyId, + chainId, + inputCurrencyId: + itemTradeState.inputCurrencyId || (chainId && getDefaultTradeRawState(+chainId).inputCurrencyId), outputCurrencyId: itemTradeState.outputCurrencyId, } as TradeUrlParams) : tradeContext, From 02539252207c2c6bfae70471d371fd6e1899e7c0 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 16:15:19 +0500 Subject: [PATCH 51/90] chore: fix lint --- .../modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts index cbedf858b7..88efcdae6a 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/updaters/SmartSlippageUpdater/index.ts @@ -2,7 +2,6 @@ import { useSetAtom } from 'jotai' import { useEffect } from 'react' import { useTradeConfirmState } from 'modules/trade' -import { useHighFeeWarning } from 'modules/tradeWidgetAddons' import { useSmartSlippageFromBff } from './useSmartSlippageFromBff' import { useSmartSlippageFromFeeMultiplier } from './useSmartSlippageFromFeeMultiplier' From 854d685b803b4e1ec5c25432f0f3247e3b77d5f7 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 18:11:03 +0500 Subject: [PATCH 52/90] feat: fetch lp-token balances Also added optimization to the balances updater, because node started firing 429 error --- .../hooks/usePersistBalancesAndAllowances.ts | 6 ++-- libs/balances-and-allowances/src/index.ts | 1 + .../updaters/BalancesAndAllowancesUpdater.ts | 35 +++++++++++++++++-- .../hooks/useMultipleContractSingleData.ts | 8 ++--- libs/multicall/src/multicall.ts | 27 ++++++++++---- libs/tokens/src/const/lpTokensList.json | 13 ++++--- 6 files changed, 70 insertions(+), 20 deletions(-) diff --git a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts index 787b588085..021a1d0d3d 100644 --- a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts +++ b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts @@ -13,7 +13,7 @@ import { SWRConfiguration } from 'swr' import { AllowancesState, allowancesFullState } from '../state/allowancesAtom' import { balancesAtom, BalancesState } from '../state/balancesAtom' -const MULTICALL_OPTIONS = {} +const MULTICALL_OPTIONS = { consequentExecution: true } export interface PersistBalancesAndAllowancesParams { account: string | undefined @@ -44,7 +44,7 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa 'balanceOf', balanceOfParams, MULTICALL_OPTIONS, - balancesSwrConfig + balancesSwrConfig, ) const { isLoading: isAllowancesLoading, data: allowances } = useMultipleContractSingleData<[BigNumber]>( @@ -53,7 +53,7 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa 'allowance', allowanceParams, MULTICALL_OPTIONS, - allowancesSwrConfig + allowancesSwrConfig, ) // Set balances loading state diff --git a/libs/balances-and-allowances/src/index.ts b/libs/balances-and-allowances/src/index.ts index 2423102726..2f6a868643 100644 --- a/libs/balances-and-allowances/src/index.ts +++ b/libs/balances-and-allowances/src/index.ts @@ -11,6 +11,7 @@ export { useNativeCurrencyAmount } from './hooks/useNativeCurrencyAmount' export { useCurrencyAmountBalance } from './hooks/useCurrencyAmountBalance' export { useTokenBalanceForAccount } from './hooks/useTokenBalanceForAccount' export { useAddPriorityAllowance } from './hooks/useAddPriorityAllowance' +export { usePersistBalancesAndAllowances } from './hooks/usePersistBalancesAndAllowances' // Types export type { BalancesState } from './state/balancesAtom' diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts index b2221819f1..38f8eff314 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts @@ -1,9 +1,9 @@ import { useSetAtom } from 'jotai/index' -import { useEffect, useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { useAllTokens } from '@cowprotocol/tokens' +import { TokenListCategory, useAllLpTokens, useAllTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -15,6 +15,16 @@ import { balancesAtom } from '../state/balancesAtom' const BALANCES_SWR_CONFIG = { refreshInterval: ms`31s` } const ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`33s` } +// A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time +const LP_BALANCES_SWR_CONFIG = { refreshInterval: ms`62s` } +const LP_ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`66s` } + +// To avoid high load to the node at the same time +// We start the updater with a delay +const LP_UPDATER_START_DELAY = ms`6s` + +const LP_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] + export interface BalancesAndAllowancesUpdaterProps { account: string | undefined chainId: SupportedChainId @@ -25,6 +35,10 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl const allTokens = useAllTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account) + const allLpTokens = useAllLpTokens(LP_CATEGORIES) + const [isUpdaterPaused, setIsUpdaterPaused] = useState(true) + + const lpTokenAddresses = useMemo(() => allLpTokens.map((token) => token.address), [allLpTokens]) const tokenAddresses = useMemo(() => allTokens.map((token) => token.address), [allTokens]) usePersistBalancesAndAllowances({ @@ -36,6 +50,23 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl allowancesSwrConfig: ALLOWANCES_SWR_CONFIG, }) + usePersistBalancesAndAllowances({ + account: isUpdaterPaused ? undefined : account, + chainId, + tokenAddresses: lpTokenAddresses, + setLoadingState: false, + balancesSwrConfig: LP_BALANCES_SWR_CONFIG, + allowancesSwrConfig: LP_ALLOWANCES_SWR_CONFIG, + }) + + useEffect(() => { + const timeout = setTimeout(() => { + setIsUpdaterPaused(false) + }, LP_UPDATER_START_DELAY) + + return () => clearTimeout(timeout) + }, []) + // Add native token balance to the store as well useEffect(() => { const nativeToken = NATIVE_CURRENCIES[chainId] diff --git a/libs/multicall/src/hooks/useMultipleContractSingleData.ts b/libs/multicall/src/hooks/useMultipleContractSingleData.ts index 8610778a29..2d4a03cfb6 100644 --- a/libs/multicall/src/hooks/useMultipleContractSingleData.ts +++ b/libs/multicall/src/hooks/useMultipleContractSingleData.ts @@ -1,7 +1,9 @@ import { useMemo } from 'react' +import type { Multicall3 } from '@cowprotocol/abis' import { useWalletProvider } from '@cowprotocol/wallet-provider' import { Interface, Result } from '@ethersproject/abi' +import type { Web3Provider } from '@ethersproject/providers' import useSWR, { SWRConfiguration, SWRResponse } from 'swr' @@ -35,10 +37,8 @@ export function useMultipleContractSingleData( }, [addresses, callData]) return useSWR<(T | undefined)[] | null>( - ['useMultipleContractSingleData', provider, calls, multicallOptions], - () => { - if (!calls || calls.length === 0 || !provider) return null - + !calls?.length || !provider ? null : [provider, calls, multicallOptions, 'useMultipleContractSingleData'], + async ([provider, calls, multicallOptions]: [Web3Provider, Multicall3.CallStruct[], MultiCallOptions]) => { return multiCall(provider, calls, multicallOptions).then((results) => { return results.map((result) => { try { diff --git a/libs/multicall/src/multicall.ts b/libs/multicall/src/multicall.ts index 8aed7aff7b..abb3616e84 100644 --- a/libs/multicall/src/multicall.ts +++ b/libs/multicall/src/multicall.ts @@ -6,6 +6,7 @@ import { getMulticallContract } from './utils/getMulticallContract' export interface MultiCallOptions { batchSize?: number + consequentExecution?: boolean } /** @@ -16,19 +17,31 @@ export interface MultiCallOptions { export async function multiCall( provider: Web3Provider, calls: Multicall3.CallStruct[], - options: MultiCallOptions = {} + options: MultiCallOptions = {}, ): Promise { - const { batchSize = DEFAULT_BATCH_SIZE } = options + const { batchSize = DEFAULT_BATCH_SIZE, consequentExecution } = options const multicall = getMulticallContract(provider) const batches = splitIntoBatches(calls, batchSize) - const requests = batches.map((batch) => { - return multicall.callStatic.tryAggregate(false, batch) - }) - - return (await Promise.all(requests)).flat() + return consequentExecution + ? batches + .reduce>((acc, batch) => { + return acc.then((results) => { + return multicall.callStatic.tryAggregate(false, batch).then((batchResults) => { + results.push(batchResults) + + return results + }) + }) + }, Promise.resolve([])) + .then((results) => results.flat()) + : Promise.all( + batches.map((batch) => { + return multicall.callStatic.tryAggregate(false, batch) + }), + ).then((res) => res.flat()) } function splitIntoBatches(calls: Multicall3.CallStruct[], batchSize: number): Multicall3.CallStruct[][] { diff --git a/libs/tokens/src/const/lpTokensList.json b/libs/tokens/src/const/lpTokensList.json index 0cce6b5c68..bffe99e65c 100644 --- a/libs/tokens/src/const/lpTokensList.json +++ b/libs/tokens/src/const/lpTokensList.json @@ -2,26 +2,31 @@ { "priority": 100, "category": "COW_AMM_LP", - "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/4ff665a85e88d7015c7bddac7c5e66ecfcfa77a8/src/public/lp-tokens/cow-amm.json" + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/95687bdc19a91b2934eca8a11b8fb6f09546bbda/src/public/lp-tokens/cow-amm.json" }, { "priority": 101, "category": "LP", - "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/uniswapv2.json" + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/95687bdc19a91b2934eca8a11b8fb6f09546bbda/src/public/lp-tokens/uniswapv2.json" }, { "priority": 102, "category": "LP", - "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/balancerv2.json" + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/95687bdc19a91b2934eca8a11b8fb6f09546bbda/src/public/lp-tokens/curve.json" }, { "priority": 103, "category": "LP", - "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/sushiswap.json" + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/95687bdc19a91b2934eca8a11b8fb6f09546bbda/src/public/lp-tokens/balancerv2.json" }, { "priority": 104, "category": "LP", + "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/95687bdc19a91b2934eca8a11b8fb6f09546bbda/src/public/lp-tokens/sushiswap.json" + }, + { + "priority": 105, + "category": "LP", "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/055c8ccebe59e91874baf8600403a487ad33e93d/src/public/lp-tokens/pancakeswap.json" } ] From 03da23d4a9bd1f512d87517fc997d5ec93700f44 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 18:30:17 +0500 Subject: [PATCH 53/90] feat: reuse virtual list for lp-tokens list --- .../src/common/pure/VirtualList/index.tsx | 70 ++++++++++ .../pure/VirtualList}/styled.ts | 19 ++- .../containers/LpTokenListsWidget/index.tsx | 8 +- .../tokensList/pure/LpTokenLists/index.tsx | 129 +++++++----------- .../tokensList/pure/LpTokenLists/styled.ts | 20 --- .../tokensList/pure/TokenListItem/index.tsx | 19 +-- .../pure/TokensVirtualList/index.tsx | 95 ++++--------- 7 files changed, 169 insertions(+), 191 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx rename apps/cowswap-frontend/src/{modules/tokensList/pure/TokensVirtualList => common/pure/VirtualList}/styled.ts (66%) diff --git a/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx b/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx new file mode 100644 index 0000000000..348f86a792 --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/VirtualList/index.tsx @@ -0,0 +1,70 @@ +import { ReactNode, useCallback, useRef } from 'react' + +import { useVirtualizer, VirtualItem } from '@tanstack/react-virtual' +import ms from 'ms.macro' + +import { ListInner, ListScroller, ListWrapper, LoadingRows } from './styled' + +const scrollDelay = ms`400ms` + +const threeDivs = () => ( + <> +
+
+
+ +) + +interface VirtualListProps { + id?: string + items: T[] + getItemView(items: T[], item: VirtualItem): ReactNode + loading?: boolean + estimateSize?: () => number +} + +export function VirtualList({ id, items, loading, getItemView, estimateSize = () => 56 }: VirtualListProps) { + const parentRef = useRef(null) + const wrapperRef = useRef(null) + const scrollTimeoutRef = useRef() + + const onScroll = useCallback(() => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + if (wrapperRef.current) wrapperRef.current.style.pointerEvents = 'none' + } + + scrollTimeoutRef.current = setTimeout(() => { + if (wrapperRef.current) wrapperRef.current.style.pointerEvents = '' + }, scrollDelay) + }, []) + + const virtualizer = useVirtualizer({ + getScrollElement: () => parentRef.current, + count: items.length, + estimateSize, + overscan: 5, + }) + + const virtualItems = virtualizer.getVirtualItems() + + return ( + + + + {virtualItems.map((item) => { + if (loading) { + return {threeDivs()} + } + + return ( +
+ {getItemView(items, item)} +
+ ) + })} +
+
+
+ ) +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/styled.ts b/apps/cowswap-frontend/src/common/pure/VirtualList/styled.ts similarity index 66% rename from apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/styled.ts rename to apps/cowswap-frontend/src/common/pure/VirtualList/styled.ts index 1585ac2abd..8169ec3773 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/VirtualList/styled.ts @@ -2,12 +2,27 @@ import { LoadingRows as BaseLoadingRows } from '@cowprotocol/ui' import styled from 'styled-components/macro' -export const TokensInner = styled.div` +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + overflow: auto; + margin-bottom: 15px; +` +export const ListWrapper = styled.div` + display: block; + width: 100%; + height: 100%; + overflow: auto; + + ${({ theme }) => theme.colorScrollbar}; +` + +export const ListInner = styled.div` width: 100%; position: relative; ` -export const TokensScroller = styled.div` +export const ListScroller = styled.div` position: absolute; top: 0; left: 0; diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 55fd841a73..ed17872c13 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,5 +1,6 @@ import { ReactNode, useState } from 'react' +import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenListCategory, useAllLpTokens, useTokensByAddressMap } from '@cowprotocol/tokens' import { TabButton, TabsContainer } from './styled' @@ -20,6 +21,7 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) const tokensByAddress = useTokensByAddressMap() + const balancesState = useTokensBalances() return ( <> @@ -36,7 +38,11 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { ) })} - {listsCategories === null ? children : } + {listsCategories === null ? ( + children + ) : ( + + )} ) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 49bcdfabf8..8e4a0aff02 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -1,57 +1,66 @@ -import { useCallback, useRef } from 'react' +import { useCallback } from 'react' +import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken } from '@cowprotocol/common-const' import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' -import { InfoTooltip, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { InfoTooltip, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { CurrencyAmount } from '@uniswap/sdk-core' -import { useVirtualizer } from '@tanstack/react-virtual' -import ms from 'ms.macro' +import { VirtualItem } from '@tanstack/react-virtual' -import { - ListHeader, - ListInner, - ListItem, - ListScroller, - ListWrapper, - LpTokenInfo, - LpTokenLogo, - LpTokenWrapper, - Wrapper, -} from './styled' +import { VirtualList } from 'common/pure/VirtualList' -const scrollDelay = ms`400ms` - -const estimateSize = () => 56 +import { ListHeader, ListItem, LpTokenInfo, LpTokenLogo, LpTokenWrapper, Wrapper } from './styled' interface LpTokenListsProps { lpTokens: LpToken[] tokensByAddress: TokensByAddress + balancesState: BalancesState } -export function LpTokenLists({ lpTokens, tokensByAddress }: LpTokenListsProps) { - const parentRef = useRef(null) - const wrapperRef = useRef(null) - const scrollTimeoutRef = useRef() - - const onScroll = useCallback(() => { - if (scrollTimeoutRef.current) { - clearTimeout(scrollTimeoutRef.current) - if (wrapperRef.current) wrapperRef.current.style.pointerEvents = 'none' - } - - scrollTimeoutRef.current = setTimeout(() => { - if (wrapperRef.current) wrapperRef.current.style.pointerEvents = '' - }, scrollDelay) - }, []) +export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTokenListsProps) { + const { values: balances, isLoading: balancesLoading } = balancesState - const virtualizer = useVirtualizer({ - getScrollElement: () => parentRef.current, - count: lpTokens.length, - estimateSize, - overscan: 5, - }) + const getItemView = useCallback( + (lpTokens: LpToken[], item: VirtualItem) => { + const token = lpTokens[item.index] + const token0 = token.tokens?.[0]?.toLowerCase() + const token1 = token.tokens?.[1]?.toLowerCase() + const balance = balances ? balances[token.address.toLowerCase()] : undefined + const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined - const items = virtualizer.getVirtualItems() + return ( + + + +
+ +
+
+ +
+
+ + + + +

+ +

+
+
+ + + + 40% + + TODO + +
+ ) + }, + [balances, tokensByAddress], + ) return ( @@ -61,45 +70,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress }: LpTokenListsProps) { APR - - - - {items.map((item) => { - const token = lpTokens[item.index] - const token0 = token.tokens?.[0]?.toLowerCase() - const token1 = token.tokens?.[1]?.toLowerCase() - - return ( - - - -
- -
-
- -
-
- - - - -

- -

-
-
- --- - 40% - - TODO - -
- ) - })} -
-
-
+
Can' find?
Create a pool diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index 2b02f71845..f6fa6c7e38 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -8,26 +8,6 @@ export const Wrapper = styled.div` overflow: auto; margin-bottom: 15px; ` -export const ListWrapper = styled.div` - display: block; - width: 100%; - height: 100%; - overflow: auto; - - ${({ theme }) => theme.colorScrollbar}; -` - -export const ListInner = styled.div` - width: 100%; - position: relative; -` - -export const ListScroller = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; -` export const LpTokenWrapper = styled.div` display: flex; diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx index 7823fb7dc6..c95713a1a9 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx @@ -8,32 +8,18 @@ import * as styledEl from './styled' import { TokenInfo } from '../TokenInfo' import { TokenTags } from '../TokenTags' -import type { VirtualItem } from '@tanstack/react-virtual' - export interface TokenListItemProps { token: TokenWithLogo selectedToken?: string balance: BigNumber | undefined onSelectToken(token: TokenWithLogo): void - measureElement?: (node: Element | null) => void - virtualRow?: VirtualItem isUnsupported: boolean isPermitCompatible: boolean isWalletConnected: boolean } export function TokenListItem(props: TokenListItemProps) { - const { - token, - selectedToken, - balance, - onSelectToken, - virtualRow, - isUnsupported, - isPermitCompatible, - measureElement, - isWalletConnected, - } = props + const { token, selectedToken, balance, onSelectToken, isUnsupported, isPermitCompatible, isWalletConnected } = props const isTokenSelected = token.address.toLowerCase() === selectedToken?.toLowerCase() @@ -41,9 +27,6 @@ export function TokenListItem(props: TokenListItemProps) { return ( onSelectToken(token)} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx index 16e91c70a8..f426b1f462 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -1,28 +1,15 @@ -import { useCallback, useMemo, useRef } from 'react' +import { useCallback, useMemo } from 'react' import { TokenWithLogo } from '@cowprotocol/common-const' -import { useVirtualizer } from '@tanstack/react-virtual' -import ms from 'ms.macro' +import { VirtualItem } from '@tanstack/react-virtual' -import * as styledEl from './styled' +import { VirtualList } from 'common/pure/VirtualList' import { SelectTokenContext } from '../../types' import { tokensListSorter } from '../../utils/tokensListSorter' -import { CommonListContainer } from '../commonElements' import { TokenListItem } from '../TokenListItem' -const estimateSize = () => 56 -const threeDivs = () => ( - <> -
-
-
- -) - -const scrollDelay = ms`400ms` - export interface TokensVirtualListProps extends SelectTokenContext { allTokens: TokenWithLogo[] account: string | undefined @@ -35,64 +22,30 @@ export function TokensVirtualList(props: TokensVirtualListProps) { const isWalletConnected = !!account - const scrollTimeoutRef = useRef() - const parentRef = useRef(null) - const wrapperRef = useRef(null) - - const onScroll = useCallback(() => { - if (scrollTimeoutRef.current) { - clearTimeout(scrollTimeoutRef.current) - if (wrapperRef.current) wrapperRef.current.style.pointerEvents = 'none' - } - - scrollTimeoutRef.current = setTimeout(() => { - if (wrapperRef.current) wrapperRef.current.style.pointerEvents = '' - }, scrollDelay) - }, []) - - const virtualizer = useVirtualizer({ - getScrollElement: () => parentRef.current, - count: allTokens.length, - estimateSize, - overscan: 5, - }) - const sortedTokens = useMemo(() => { return balances ? allTokens.sort(tokensListSorter(balances)) : allTokens }, [allTokens, balances]) - const items = virtualizer.getVirtualItems() - - return ( - - - - {items.map((virtualRow) => { - const token = sortedTokens[virtualRow.index] - const addressLowerCase = token.address.toLowerCase() - const balance = balances ? balances[token.address.toLowerCase()] : undefined - - if (balancesLoading) { - return {threeDivs()} - } - - return ( - - ) - })} - - - + const getItemView = useCallback( + (sortedTokens: TokenWithLogo[], virtualRow: VirtualItem) => { + const token = sortedTokens[virtualRow.index] + const addressLowerCase = token.address.toLowerCase() + const balance = balances ? balances[token.address.toLowerCase()] : undefined + + return ( + + ) + }, + [balances, unsupportedTokens, permitCompatibleTokens, selectedToken, onSelectToken, isWalletConnected], ) + + return } From 7228879b5c6ab58662c678d3f79ef7dafb86c267 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 18:35:12 +0500 Subject: [PATCH 54/90] chore: adjust balances updater --- .../hooks/usePersistBalancesAndAllowances.ts | 19 ++++++++++++++----- .../updaters/BalancesAndAllowancesUpdater.ts | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts index 021a1d0d3d..c54453d524 100644 --- a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts +++ b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts @@ -5,7 +5,7 @@ import { useEffect, useMemo } from 'react' import { ERC_20_INTERFACE } from '@cowprotocol/abis' import { getIsNativeToken } from '@cowprotocol/common-utils' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' -import { useMultipleContractSingleData } from '@cowprotocol/multicall' +import { MultiCallOptions, useMultipleContractSingleData } from '@cowprotocol/multicall' import { BigNumber } from '@ethersproject/bignumber' import { SWRConfiguration } from 'swr' @@ -13,7 +13,7 @@ import { SWRConfiguration } from 'swr' import { AllowancesState, allowancesFullState } from '../state/allowancesAtom' import { balancesAtom, BalancesState } from '../state/balancesAtom' -const MULTICALL_OPTIONS = { consequentExecution: true } +const MULTICALL_OPTIONS = {} export interface PersistBalancesAndAllowancesParams { account: string | undefined @@ -22,10 +22,19 @@ export interface PersistBalancesAndAllowancesParams { balancesSwrConfig: SWRConfiguration allowancesSwrConfig: SWRConfiguration setLoadingState?: boolean + multicallOptions?: MultiCallOptions } export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowancesParams) { - const { account, chainId, tokenAddresses, setLoadingState, balancesSwrConfig, allowancesSwrConfig } = params + const { + account, + chainId, + tokenAddresses, + setLoadingState, + balancesSwrConfig, + allowancesSwrConfig, + multicallOptions = MULTICALL_OPTIONS, + } = params const setBalances = useSetAtom(balancesAtom) const setAllowances = useSetAtom(allowancesFullState) @@ -43,7 +52,7 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa ERC_20_INTERFACE, 'balanceOf', balanceOfParams, - MULTICALL_OPTIONS, + multicallOptions, balancesSwrConfig, ) @@ -52,7 +61,7 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa ERC_20_INTERFACE, 'allowance', allowanceParams, - MULTICALL_OPTIONS, + multicallOptions, allowancesSwrConfig, ) diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts index 38f8eff314..33fe9c84ca 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts @@ -18,10 +18,11 @@ const ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`33s` } // A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time const LP_BALANCES_SWR_CONFIG = { refreshInterval: ms`62s` } const LP_ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`66s` } +const LP_MULTICALL_OPTIONS = { consequentExecution: true } // To avoid high load to the node at the same time // We start the updater with a delay -const LP_UPDATER_START_DELAY = ms`6s` +const LP_UPDATER_START_DELAY = ms`3s` const LP_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] @@ -57,6 +58,7 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl setLoadingState: false, balancesSwrConfig: LP_BALANCES_SWR_CONFIG, allowancesSwrConfig: LP_ALLOWANCES_SWR_CONFIG, + multicallOptions: LP_MULTICALL_OPTIONS, }) useEffect(() => { From b8b33015766cd16b95ddff863a70c9a0415b8c46 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 16 Oct 2024 18:38:24 +0500 Subject: [PATCH 55/90] chore: add yield in widget conf --- apps/widget-configurator/src/app/configurator/consts.ts | 2 +- apps/widget-configurator/src/app/embedDialog/const.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/widget-configurator/src/app/configurator/consts.ts b/apps/widget-configurator/src/app/configurator/consts.ts index 2d229cce5e..30cf0b2f20 100644 --- a/apps/widget-configurator/src/app/configurator/consts.ts +++ b/apps/widget-configurator/src/app/configurator/consts.ts @@ -15,7 +15,7 @@ export const DEFAULT_PARTNER_FEE_RECIPIENT_PER_NETWORK: Record = { tradeType: 'swap, limit or advanced', sell: 'Sell token. Optionally add amount for sell orders', buy: 'Buy token. Optionally add amount for buy orders', - enabledTradeTypes: 'swap, limit and/or advanced', + enabledTradeTypes: 'swap, limit, advanced, yield', partnerFee: 'Partner fee, in Basis Points (BPS) and a receiver address', } From 3902abb2ad9df440b3856cc72db39645d570bb2b Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 14:02:03 +0500 Subject: [PATCH 56/90] chore: reset balances only when account changed --- .../src/hooks/usePersistBalancesAndAllowances.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts index c54453d524..267810206f 100644 --- a/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts +++ b/libs/balances-and-allowances/src/hooks/usePersistBalancesAndAllowances.ts @@ -1,8 +1,9 @@ -import { useSetAtom } from 'jotai/index' +import { useSetAtom } from 'jotai' import { useResetAtom } from 'jotai/utils' import { useEffect, useMemo } from 'react' import { ERC_20_INTERFACE } from '@cowprotocol/abis' +import { usePrevious } from '@cowprotocol/common-hooks' import { getIsNativeToken } from '@cowprotocol/common-utils' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' import { MultiCallOptions, useMultipleContractSingleData } from '@cowprotocol/multicall' @@ -36,6 +37,7 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa multicallOptions = MULTICALL_OPTIONS, } = params + const prevAccount = usePrevious(account) const setBalances = useSetAtom(balancesAtom) const setAllowances = useSetAtom(allowancesFullState) @@ -119,9 +121,9 @@ export function usePersistBalancesAndAllowances(params: PersistBalancesAndAllowa // Reset states when wallet is not connected useEffect(() => { - if (!account) { + if (prevAccount && prevAccount !== account) { resetBalances() resetAllowances() } - }, [account, resetAllowances, resetBalances]) + }, [account, prevAccount, resetAllowances, resetBalances]) } From 6060aaea95148bf7417ae9132f39c6b9b9ae6a03 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 14:07:04 +0500 Subject: [PATCH 57/90] feat: cache balances into localStorage --- .../src/state/balancesAtom.ts | 13 ++- ...er.ts => BalancesAndAllowancesUpdater.tsx} | 6 +- .../src/updaters/BalancesCacheUpdater.tsx | 89 +++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) rename libs/balances-and-allowances/src/updaters/{BalancesAndAllowancesUpdater.ts => BalancesAndAllowancesUpdater.tsx} (95%) create mode 100644 libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx diff --git a/libs/balances-and-allowances/src/state/balancesAtom.ts b/libs/balances-and-allowances/src/state/balancesAtom.ts index 8e1a8ec6d5..a9f3f7c3fc 100644 --- a/libs/balances-and-allowances/src/state/balancesAtom.ts +++ b/libs/balances-and-allowances/src/state/balancesAtom.ts @@ -1,7 +1,18 @@ -import { atomWithReset } from 'jotai/utils' +import { atomWithReset, atomWithStorage } from 'jotai/utils' + +import { getJotaiMergerStorage } from '@cowprotocol/core' +import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { Erc20MulticallState } from '../types' +type BalancesCache = Record> + export interface BalancesState extends Erc20MulticallState {} +export const balancesCacheAtom = atomWithStorage( + 'balancesCacheAtom:v0', + mapSupportedNetworks({}), + getJotaiMergerStorage(), +) + export const balancesAtom = atomWithReset({ isLoading: false, values: {} }) diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx similarity index 95% rename from libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts rename to libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 33fe9c84ca..3cd6692459 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.ts +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -1,4 +1,4 @@ -import { useSetAtom } from 'jotai/index' +import { useSetAtom } from 'jotai' import { useEffect, useMemo, useState } from 'react' import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' @@ -7,6 +7,8 @@ import { TokenListCategory, useAllLpTokens, useAllTokens } from '@cowprotocol/to import ms from 'ms.macro' +import { BalancesCacheUpdater } from './BalancesCacheUpdater' + import { useNativeTokenBalance } from '../hooks/useNativeTokenBalance' import { usePersistBalancesAndAllowances } from '../hooks/usePersistBalancesAndAllowances' import { balancesAtom } from '../state/balancesAtom' @@ -77,5 +79,5 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl setBalances((state) => ({ ...state, values: { ...state.values, ...nativeBalanceState } })) }, [nativeTokenBalance, chainId, setBalances]) - return null + return } diff --git a/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx new file mode 100644 index 0000000000..0cd33ae770 --- /dev/null +++ b/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx @@ -0,0 +1,89 @@ +import { useAtom } from 'jotai/index' +import { useEffect, useRef } from 'react' + +import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { BigNumber } from '@ethersproject/bignumber' + +import { balancesAtom, balancesCacheAtom } from '../state/balancesAtom' + +export function BalancesCacheUpdater({ chainId }: { chainId: SupportedChainId }) { + const [balances, setBalances] = useAtom(balancesAtom) + const [balancesCache, setBalancesCache] = useAtom(balancesCacheAtom) + const areBalancesRestoredFromCacheRef = useRef(false) + + // Persist into localStorage only non-zero balances + useEffect(() => { + setBalancesCache((state) => { + const balancesValues = balances.values + + const balancesToCache = Object.keys(balancesValues).reduce( + (acc, tokenAddress) => { + const balance = balancesValues[tokenAddress] + + if (balance && !balance.isZero()) { + acc[tokenAddress] = balance.toString() + } + + return acc + }, + {} as Record, + ) + + const currentCache = state[chainId] + // Remove zero balances from the current cache + const updatedCache = Object.keys(currentCache).reduce( + (acc, tokenAddress) => { + if (!balancesValues[tokenAddress]?.isZero()) { + acc[tokenAddress] = currentCache[tokenAddress] + } + + return acc + }, + {} as Record, + ) + + return { + ...state, + [chainId]: { + ...updatedCache, + ...balancesToCache, + }, + } + }) + }, [chainId, balances.values]) + + // Restore balances from cache once + useEffect(() => { + const cache = balancesCache[chainId] + + if (areBalancesRestoredFromCacheRef.current) return + if (!cache) return + + const cacheKeys = Object.keys(cache) + + if (cacheKeys.length === 0) return + + areBalancesRestoredFromCacheRef.current = true + + setBalances((state) => { + return { + isLoading: state.isLoading, + values: { + ...state.values, + ...cacheKeys.reduce( + (acc, tokenAddress) => { + acc[tokenAddress] = BigNumber.from(cache[tokenAddress]) + + return acc + }, + {} as Record, + ), + }, + } + }) + + return + }, [balancesCache, chainId]) + + return null +} From 20ba5dea3037638f3b71c043c3a77031614b3dbe Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 14:07:33 +0500 Subject: [PATCH 58/90] feat: display cached token balances --- .../tokensList/pure/LpTokenLists/index.tsx | 16 ++++++++++------ .../tokensList/pure/LpTokenLists/styled.ts | 2 +- .../tokensList/pure/TokenListItem/index.tsx | 12 ++++++++++-- .../modules/tokensList/pure/TokenTags/index.tsx | 2 +- .../tokensList/pure/TokensVirtualList/index.tsx | 4 ++-- libs/ui/src/index.ts | 2 +- libs/ui/src/pure/Loader/styled.tsx | 5 +++++ 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 8e4a0aff02..01fc120d47 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -3,7 +3,7 @@ import { useCallback } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken } from '@cowprotocol/common-const' import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' -import { InfoTooltip, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { InfoTooltip, LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' import { VirtualItem } from '@tanstack/react-virtual' @@ -12,6 +12,12 @@ import { VirtualList } from 'common/pure/VirtualList' import { ListHeader, ListItem, LpTokenInfo, LpTokenLogo, LpTokenWrapper, Wrapper } from './styled' +const LoadingElement = ( + + + +) + interface LpTokenListsProps { lpTokens: LpToken[] tokensByAddress: TokensByAddress @@ -19,7 +25,7 @@ interface LpTokenListsProps { } export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTokenListsProps) { - const { values: balances, isLoading: balancesLoading } = balancesState + const { values: balances } = balancesState const getItemView = useCallback( (lpTokens: LpToken[], item: VirtualItem) => { @@ -49,9 +55,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTok

- - - + {balanceAmount ? : LoadingElement} 40% TODO @@ -70,7 +74,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTok APR - +
Can' find?
Create a pool diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index f6fa6c7e38..e56843c65e 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -18,7 +18,7 @@ export const LpTokenWrapper = styled.div` export const ListHeader = styled.div` display: grid; - grid-template-columns: 1fr 100px 50px 20px; + grid-template-columns: 1fr 100px 50px 30px; font-size: 12px; font-weight: 500; color: var(${UI.COLOR_TEXT_OPACITY_70}); diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx index c95713a1a9..e9493aaf32 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx @@ -1,5 +1,5 @@ import { TokenWithLogo } from '@cowprotocol/common-const' -import { TokenAmount } from '@cowprotocol/ui' +import { LoadingRows, LoadingRowSmall, TokenAmount } from '@cowprotocol/ui' import { BigNumber } from '@ethersproject/bignumber' import { CurrencyAmount } from '@uniswap/sdk-core' @@ -8,6 +8,12 @@ import * as styledEl from './styled' import { TokenInfo } from '../TokenInfo' import { TokenTags } from '../TokenTags' +const LoadingElement = ( + + + +) + export interface TokenListItemProps { token: TokenWithLogo selectedToken?: string @@ -34,7 +40,9 @@ export function TokenListItem(props: TokenListItemProps) { {isWalletConnected && ( <> - {balanceAmount && } + + {balanceAmount ? : LoadingElement} + )} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenTags/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenTags/index.tsx index 86cc250495..89df32f8a3 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokenTags/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokenTags/index.tsx @@ -50,7 +50,7 @@ export function TokenTags({ } if (tagsToShow.length === 0) { - return + return null } return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx index f426b1f462..0abaff5f96 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -18,7 +18,7 @@ export interface TokensVirtualListProps extends SelectTokenContext { export function TokensVirtualList(props: TokensVirtualListProps) { const { allTokens, selectedToken, balancesState, onSelectToken, unsupportedTokens, permitCompatibleTokens, account } = props - const { values: balances, isLoading: balancesLoading } = balancesState + const { values: balances } = balancesState const isWalletConnected = !!account @@ -47,5 +47,5 @@ export function TokensVirtualList(props: TokensVirtualListProps) { [balances, unsupportedTokens, permitCompatibleTokens, selectedToken, onSelectToken, isWalletConnected], ) - return + return } diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index 403e897f4f..87720c6dfa 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -17,7 +17,7 @@ export * from './pure/ProductLogo' export * from './pure/MenuBar' export * from './pure/InfoTooltip' export * from './pure/HelpTooltip' -export { loadingOpacityMixin, LoadingRows } from './pure/Loader/styled' +export { loadingOpacityMixin, LoadingRows, LoadingRowSmall } from './pure/Loader/styled' export * from './pure/Row' export * from './pure/FiatAmount' export * from './pure/TokenSymbol' diff --git a/libs/ui/src/pure/Loader/styled.tsx b/libs/ui/src/pure/Loader/styled.tsx index e942ea4054..798d0735e6 100644 --- a/libs/ui/src/pure/Loader/styled.tsx +++ b/libs/ui/src/pure/Loader/styled.tsx @@ -30,6 +30,11 @@ export const LoadingRows = styled.div` } ` +export const LoadingRowSmall = styled.div` + width: 24px; + height: 10px !important; +` + export const loadingOpacityMixin = css<{ $loading: boolean }>` filter: ${({ $loading }) => ($loading ? 'grayscale(1)' : 'none')}; opacity: ${({ $loading }) => ($loading ? '0.4' : '1')}; From 3fe17276c419d24937345ac8b96dd8c8cc2075a4 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 14:22:15 +0500 Subject: [PATCH 59/90] feat: link to create pool --- .../tokensList/pure/LpTokenLists/index.tsx | 27 +++++++++--- .../tokensList/pure/LpTokenLists/styled.ts | 33 +++++++++++++++ libs/ui/src/containers/ExternalLink/index.tsx | 41 ------------------- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 01fc120d47..95099376d9 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -10,7 +10,17 @@ import { VirtualItem } from '@tanstack/react-virtual' import { VirtualList } from 'common/pure/VirtualList' -import { ListHeader, ListItem, LpTokenInfo, LpTokenLogo, LpTokenWrapper, Wrapper } from './styled' +import { + ArrowUpRight, + CreatePoolLink, + ListHeader, + ListItem, + LpTokenInfo, + LpTokenLogo, + LpTokenWrapper, + NoPoolWrapper, + Wrapper, +} from './styled' const LoadingElement = ( @@ -75,10 +85,17 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTok -
-
Can' find?
- Create a pool -
+ +
Can’t find the pool you’re looking for?
+ + Create a pool + + +
) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index e56843c65e..bf9473d7b8 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -1,5 +1,6 @@ import { UI } from '@cowprotocol/ui' +import { ArrowRight } from 'react-feather' import styled from 'styled-components/macro' export const Wrapper = styled.div` @@ -70,3 +71,35 @@ export const LpTokenInfo = styled.div` color: var(${UI.COLOR_TEXT_OPACITY_70}); } ` + +export const NoPoolWrapper = styled.div` + border-top: 1px solid var(${UI.COLOR_BORDER}); + color: var(${UI.COLOR_TEXT_OPACITY_70}); + padding: 20px 0 10px 0; + display: flex; + flex-direction: column; + gap: 16px; + align-items: center; + font-size: 13px; +` + +export const ArrowUpRight = styled(ArrowRight)` + transform: rotate(-45deg); + margin-left: 2px; + margin-bottom: -2px; +` + +export const CreatePoolLink = styled.a` + display: inline-block; + background: #bcec79; + color: #194d05; + font-size: 16px; + font-weight: bold; + border-radius: 24px; + padding: 10px 24px; + text-decoration: none; + + &:hover { + opacity: 0.8; + } +` diff --git a/libs/ui/src/containers/ExternalLink/index.tsx b/libs/ui/src/containers/ExternalLink/index.tsx index 03bec48e6c..3dff19f6a2 100644 --- a/libs/ui/src/containers/ExternalLink/index.tsx +++ b/libs/ui/src/containers/ExternalLink/index.tsx @@ -28,27 +28,7 @@ export const StyledLink = styled.a` text-decoration: none; } ` -const LinkIconWrapper = styled.a` - text-decoration: none; - cursor: pointer; - align-items: center; - justify-content: center; - display: flex; - - :hover { - text-decoration: none; - opacity: 0.7; - } - :focus { - outline: none; - text-decoration: none; - } - - :active { - text-decoration: none; - } -` export const LinkIcon = styled(LinkIconFeather as any)` height: 16px; width: 18px; @@ -114,24 +94,3 @@ function handleClickExternalLink(cowAnalytics: CowAnalytics, event: React.MouseE }, }) } - -export function ExternalLinkIcon({ - target = '_blank', - href, - rel = 'noopener noreferrer', - ...rest -}: Omit, 'as' | 'ref' | 'onClick'> & { href: string }) { - const cowAnalytics = useCowAnalytics() - - return ( - handleClickExternalLink(cowAnalytics, e)} - {...rest} - > - - - ) -} From 4d541032b45e4a6a37d3727a1271405725a17679 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 19:42:00 +0500 Subject: [PATCH 60/90] chore: merge develop --- .../src/common/utils/tradeSettingsTooltips.tsx | 2 ++ .../containers/TransactionSettings/index.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx index 66b921fb4c..f48fb7aacb 100644 --- a/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx +++ b/apps/cowswap-frontend/src/common/utils/tradeSettingsTooltips.tsx @@ -24,6 +24,8 @@ export function getNonNativeOrderDeadlineTooltip() { return ( Your swap expires and will not execute if it is pending for longer than the selected duration. +
+
{INPUT_OUTPUT_EXPLANATION}
) diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx index 101f1ca97d..274c563b77 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TransactionSettings/index.tsx @@ -27,14 +27,15 @@ import { ThemedText } from 'theme' import { AutoColumn } from 'legacy/components/Column' import { orderExpirationTimeAnalytics, slippageToleranceAnalytics } from 'modules/analytics' +import { useInjectedWidgetDeadline } from 'modules/injectedWidget' import { useIsEoaEthFlow } from 'modules/trade' import { useDefaultTradeSlippage, - useSmartTradeSlippage, - useTradeSlippage, useIsSlippageModified, useIsSmartSlippageApplied, useSetSlippage, + useSmartTradeSlippage, + useTradeSlippage, } from 'modules/tradeSlippage' import { From 535a046817c6ac95697dd80f8b33e2763528bff5 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 17 Oct 2024 19:45:55 +0500 Subject: [PATCH 61/90] chore: fix hooks trade state --- apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts index 27e0b62276..60631701d7 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useTradeState.ts @@ -89,7 +89,7 @@ export function useGetTradeStateByRoute() { return useCallback( (route: RoutesValues) => { - if (route === Routes.SWAP) return swapTradeState + if (route === Routes.SWAP || route === Routes.HOOKS) return swapTradeState if (route === Routes.ABOUT) return advancedOrdersState if (route === Routes.YIELD) return yieldRawState return limitOrdersState From c9df5d222bf85e74c1e61b3ec04a2c324e49a654 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 18 Oct 2024 13:14:05 +0500 Subject: [PATCH 62/90] fix: fix smart slippage displaying --- .../swap/containers/SwapWidget/index.tsx | 43 ++++++++++++------- .../containers/RowSlippage/index.tsx | 1 + .../yield/containers/YieldWidget/index.tsx | 33 ++++++++------ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index d760cc28bd..30c61ac4bc 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -208,22 +208,33 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { settingsWidget: , topContent, - bottomContent(warnings) { - return ( - <> - {bottomContent} - - - {warnings} - - - - ) - }, + bottomContent: useCallback( + (warnings: ReactNode | null) => { + return ( + <> + {bottomContent} + + + {warnings} + + + + ) + }, + [ + bottomContent, + deadlineState, + isTradePriceUpdating, + rateInfoParams, + swapButtonContext, + swapWarningsTopProps, + swapWarningsBottomProps, + ], + ), } const params = { diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx index 59dc312a37..99f75e014f 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowSlippage/index.tsx @@ -58,6 +58,7 @@ export function RowSlippage({ slippageTooltip, smartSlippage, isSmartSlippageApplied, + isTradePriceUpdating, ], ) diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index a8c795f889..b034093df9 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -1,3 +1,5 @@ +import { ReactNode, useCallback } from 'react' + import { Field } from 'legacy/state/types' import { @@ -81,20 +83,23 @@ export function YieldWidget() { const slots: TradeWidgetSlots = { settingsWidget: , - bottomContent(tradeWarnings) { - return ( - <> - - - {tradeWarnings} - - - ) - }, + bottomContent: useCallback( + (tradeWarnings: ReactNode | null) => { + return ( + <> + + + {tradeWarnings} + + + ) + }, + [doTrade.contextIsReady, isRateLoading, rateInfoParams, deadlineState], + ), } const params = { From 60c1033d1166c7974847b02c9d2f5e186c46f162 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 18 Oct 2024 13:25:49 +0500 Subject: [PATCH 63/90] chore: center slippage banner --- .../HighSuggestedSlippageWarning/index.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx index f063e4dbe3..225e0b99f9 100644 --- a/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeSlippage/containers/HighSuggestedSlippageWarning/index.tsx @@ -2,8 +2,14 @@ import { percentToBps } from '@cowprotocol/common-utils' import { BannerOrientation, InfoTooltip, InlineBanner } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' +import styled from 'styled-components/macro' + import { useIsSmartSlippageApplied, useTradeSlippage } from 'modules/tradeSlippage' +const StyledInlineBanner = styled(InlineBanner)` + text-align: center; +` + export type HighSuggestedSlippageWarningProps = { isTradePriceUpdating: boolean } @@ -22,9 +28,12 @@ export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarning } return ( - + Slippage adjusted to {`${slippageBps / 100}`}% to ensure quick execution - - + + ) } From ee11cb2a35bf4904a3c7577b7f6d491b2556313f Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 18 Oct 2024 13:46:25 +0500 Subject: [PATCH 64/90] chore: condition to displayCreatePoolBanner --- .../containers/LpTokenListsWidget/index.tsx | 7 ++++- .../tokensList/pure/LpTokenLists/index.tsx | 27 ++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index ed17872c13..3d0a99c4b1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -41,7 +41,12 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { {listsCategories === null ? ( children ) : ( - + )} ) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 95099376d9..bfb017735d 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -32,9 +32,10 @@ interface LpTokenListsProps { lpTokens: LpToken[] tokensByAddress: TokensByAddress balancesState: BalancesState + displayCreatePoolBanner: boolean } -export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTokenListsProps) { +export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, displayCreatePoolBanner }: LpTokenListsProps) { const { values: balances } = balancesState const getItemView = useCallback( @@ -85,17 +86,19 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState }: LpTok - -
Can’t find the pool you’re looking for?
- - Create a pool - - -
+ {displayCreatePoolBanner && ( + +
Can’t find the pool you’re looking for?
+ + Create a pool + + +
+ )} ) } From 5d5c843bb87152cee52f13c1798a864a079210f2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 18 Oct 2024 17:30:21 +0500 Subject: [PATCH 65/90] fix: poll lp-token balances only in yield widget --- .../LpBalancesAndAllowancesUpdater.tsx | 51 +++++++++++++++++++ .../application/containers/App/Updaters.tsx | 5 ++ .../updaters/BalancesAndAllowancesUpdater.tsx | 37 +------------- 3 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx diff --git a/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx new file mode 100644 index 0000000000..7661ea860b --- /dev/null +++ b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx @@ -0,0 +1,51 @@ +import { useEffect, useMemo, useState } from 'react' + +import { usePersistBalancesAndAllowances } from '@cowprotocol/balances-and-allowances' +import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' +import type { SupportedChainId } from '@cowprotocol/cow-sdk' +import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' + +import ms from 'ms.macro' + +// A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time +const LP_BALANCES_SWR_CONFIG = { refreshInterval: ms`32s` } +const LP_ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`34s` } +const LP_MULTICALL_OPTIONS = { consequentExecution: true } + +// To avoid high load to the node at the same time +// We start the updater with a delay +const LP_UPDATER_START_DELAY = ms`3s` + +const LP_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] + +export interface BalancesAndAllowancesUpdaterProps { + account: string | undefined + chainId: SupportedChainId + enablePolling: boolean +} +export function LpBalancesAndAllowancesUpdater({ account, chainId, enablePolling }: BalancesAndAllowancesUpdaterProps) { + const allLpTokens = useAllLpTokens(LP_CATEGORIES) + const [isUpdaterPaused, setIsUpdaterPaused] = useState(true) + + const lpTokenAddresses = useMemo(() => allLpTokens.map((token) => token.address), [allLpTokens]) + + usePersistBalancesAndAllowances({ + account: isUpdaterPaused ? undefined : account, + chainId, + tokenAddresses: lpTokenAddresses, + setLoadingState: false, + balancesSwrConfig: enablePolling ? LP_BALANCES_SWR_CONFIG : SWR_NO_REFRESH_OPTIONS, + allowancesSwrConfig: enablePolling ? LP_ALLOWANCES_SWR_CONFIG : SWR_NO_REFRESH_OPTIONS, + multicallOptions: LP_MULTICALL_OPTIONS, + }) + + useEffect(() => { + const timeout = setTimeout(() => { + setIsUpdaterPaused(false) + }, LP_UPDATER_START_DELAY) + + return () => clearTimeout(timeout) + }, []) + + return null +} diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx index 55a2dc74a6..4b42b461c9 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx @@ -13,6 +13,7 @@ import { FinalizeTxUpdater } from 'modules/onchainTransactions' import { OrdersNotificationsUpdater } from 'modules/orders' import { EthFlowDeadlineUpdater } from 'modules/swap/state/EthFlow/updaters' import { useOnTokenListAddingError } from 'modules/tokensList' +import { TradeType, useTradeTypeInfo } from 'modules/trade' import { UsdPricesUpdater } from 'modules/usdAmount' import { ProgressBarV2ExecutingOrdersUpdater } from 'common/hooks/orderProgressBarV2' @@ -20,6 +21,7 @@ import { TotalSurplusUpdater } from 'common/state/totalSurplusState' import { FeatureFlagsUpdater } from 'common/updaters/FeatureFlagsUpdater' import { FeesUpdater } from 'common/updaters/FeesUpdater' import { GasUpdater } from 'common/updaters/GasUpdater' +import { LpBalancesAndAllowancesUpdater } from 'common/updaters/LpBalancesAndAllowancesUpdater' import { CancelledOrdersUpdater, ExpiredOrdersUpdater, @@ -36,6 +38,8 @@ export function Updaters() { const { tokenLists, appCode, customTokens, standaloneMode } = useInjectedWidgetParams() const onTokenListAddingError = useOnTokenListAddingError() const { isGeoBlockEnabled } = useFeatureFlags() + const tradeTypeInfo = useTradeTypeInfo() + const isYieldWidget = tradeTypeInfo?.tradeType === TradeType.YIELD return ( <> @@ -76,6 +80,7 @@ export function Updaters() { /> + ) } diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 3cd6692459..e5cf06bff9 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -1,9 +1,9 @@ import { useSetAtom } from 'jotai' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo } from 'react' import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { TokenListCategory, useAllLpTokens, useAllTokens } from '@cowprotocol/tokens' +import { useAllTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -17,17 +17,6 @@ import { balancesAtom } from '../state/balancesAtom' const BALANCES_SWR_CONFIG = { refreshInterval: ms`31s` } const ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`33s` } -// A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time -const LP_BALANCES_SWR_CONFIG = { refreshInterval: ms`62s` } -const LP_ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`66s` } -const LP_MULTICALL_OPTIONS = { consequentExecution: true } - -// To avoid high load to the node at the same time -// We start the updater with a delay -const LP_UPDATER_START_DELAY = ms`3s` - -const LP_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] - export interface BalancesAndAllowancesUpdaterProps { account: string | undefined chainId: SupportedChainId @@ -38,10 +27,6 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl const allTokens = useAllTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account) - const allLpTokens = useAllLpTokens(LP_CATEGORIES) - const [isUpdaterPaused, setIsUpdaterPaused] = useState(true) - - const lpTokenAddresses = useMemo(() => allLpTokens.map((token) => token.address), [allLpTokens]) const tokenAddresses = useMemo(() => allTokens.map((token) => token.address), [allTokens]) usePersistBalancesAndAllowances({ @@ -53,24 +38,6 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl allowancesSwrConfig: ALLOWANCES_SWR_CONFIG, }) - usePersistBalancesAndAllowances({ - account: isUpdaterPaused ? undefined : account, - chainId, - tokenAddresses: lpTokenAddresses, - setLoadingState: false, - balancesSwrConfig: LP_BALANCES_SWR_CONFIG, - allowancesSwrConfig: LP_ALLOWANCES_SWR_CONFIG, - multicallOptions: LP_MULTICALL_OPTIONS, - }) - - useEffect(() => { - const timeout = setTimeout(() => { - setIsUpdaterPaused(false) - }, LP_UPDATER_START_DELAY) - - return () => clearTimeout(timeout) - }, []) - // Add native token balance to the store as well useEffect(() => { const nativeToken = NATIVE_CURRENCIES[chainId] From 9abbf85c975f0638d2fd8d99d75c20bbc1792317 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 14:22:16 +0500 Subject: [PATCH 66/90] feat(yield): persist pools info --- .../LpBalancesAndAllowancesUpdater.tsx | 6 +- .../application/containers/App/Updaters.tsx | 2 + .../containers/LpTokenListsWidget/index.tsx | 26 ++++++-- .../pure/SelectTokenModal/index.tsx | 58 +++++++++-------- .../yield/hooks/useLpTokensWithBalances.ts | 35 ++++++++++ .../src/modules/yield/shared.ts | 1 + .../src/modules/yield/state/poolsInfoAtom.ts | 65 +++++++++++++++++++ .../yield/updaters/PoolsInfoUpdater/index.tsx | 55 ++++++++++++++++ .../updaters/PoolsInfoUpdater/mockPoolInfo.ts | 18 +++++ libs/tokens/src/index.ts | 1 + libs/tokens/src/types.ts | 4 +- 11 files changed, 234 insertions(+), 37 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/shared.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts create mode 100644 apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo.ts diff --git a/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx index 7661ea860b..f5b31128ac 100644 --- a/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx +++ b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react' import { usePersistBalancesAndAllowances } from '@cowprotocol/balances-and-allowances' import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { LP_TOKEN_LIST_CATEGORIES, useAllLpTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -16,15 +16,13 @@ const LP_MULTICALL_OPTIONS = { consequentExecution: true } // We start the updater with a delay const LP_UPDATER_START_DELAY = ms`3s` -const LP_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] - export interface BalancesAndAllowancesUpdaterProps { account: string | undefined chainId: SupportedChainId enablePolling: boolean } export function LpBalancesAndAllowancesUpdater({ account, chainId, enablePolling }: BalancesAndAllowancesUpdaterProps) { - const allLpTokens = useAllLpTokens(LP_CATEGORIES) + const allLpTokens = useAllLpTokens(LP_TOKEN_LIST_CATEGORIES) const [isUpdaterPaused, setIsUpdaterPaused] = useState(true) const lpTokenAddresses = useMemo(() => allLpTokens.map((token) => token.address), [allLpTokens]) diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx index 4b42b461c9..e9ca8a9b24 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx @@ -15,6 +15,7 @@ import { EthFlowDeadlineUpdater } from 'modules/swap/state/EthFlow/updaters' import { useOnTokenListAddingError } from 'modules/tokensList' import { TradeType, useTradeTypeInfo } from 'modules/trade' import { UsdPricesUpdater } from 'modules/usdAmount' +import { PoolsInfoUpdater } from 'modules/yield/shared' import { ProgressBarV2ExecutingOrdersUpdater } from 'common/hooks/orderProgressBarV2' import { TotalSurplusUpdater } from 'common/state/totalSurplusState' @@ -81,6 +82,7 @@ export function Updaters() { + ) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 3d0a99c4b1..af7a4a569f 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,28 +1,44 @@ -import { ReactNode, useState } from 'react' +import { ReactNode, useMemo, useState } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' -import { TokenListCategory, useAllLpTokens, useTokensByAddressMap } from '@cowprotocol/tokens' +import { + getTokenSearchFilter, + LP_TOKEN_LIST_CATEGORIES, + TokenListCategory, + useAllLpTokens, + useTokensByAddressMap, +} from '@cowprotocol/tokens' import { TabButton, TabsContainer } from './styled' import { LpTokenLists } from '../../pure/LpTokenLists' +import { tokensListSorter } from '../../utils/tokensListSorter' interface LpTokenListsProps { children: ReactNode + search: string } const tabs = [ { title: 'All', value: null }, - { title: 'Pool tokens', value: [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] }, + { title: 'Pool tokens', value: LP_TOKEN_LIST_CATEGORIES }, { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, ] -export function LpTokenListsWidget({ children }: LpTokenListsProps) { +export function LpTokenListsWidget({ search, children }: LpTokenListsProps) { const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) const tokensByAddress = useTokensByAddressMap() const balancesState = useTokensBalances() + const balances = balancesState.values + + const sortedTokens = useMemo(() => { + const filter = getTokenSearchFilter(search) + + return balances ? lpTokens.filter(filter).sort(tokensListSorter(balances)) : lpTokens + }, [lpTokens, balances, search]) + return ( <> @@ -45,7 +61,7 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { displayCreatePoolBanner={listsCategories === tabs[2].value} balancesState={balancesState} tokensByAddress={tokensByAddress} - lpTokens={lpTokens} + lpTokens={sortedTokens} /> )} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index e5248e3262..c03e7437f8 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -54,7 +54,7 @@ export function SelectTokenModal(props: SelectTokenModalProps) { onOpenManageWidget, onInputPressEnter, account, - displayLpTokenLists + displayLpTokenLists, } = props const [inputValue, setInputValue] = useState(defaultInputValue) @@ -64,31 +64,33 @@ export function SelectTokenModal(props: SelectTokenModalProps) { selectedToken, onSelectToken, unsupportedTokens, - permitCompatibleTokens + permitCompatibleTokens, } - const allListsContent = <> - - - - - {inputValue.trim() ? ( - - ) : ( - - )} - -
- - Manage Token Lists - -
- + const allListsContent = ( + <> + + + + + {inputValue.trim() ? ( + + ) : ( + + )} + +
+ + Manage Token Lists + +
+ + ) return ( @@ -106,9 +108,11 @@ export function SelectTokenModal(props: SelectTokenModalProps) { placeholder="Search name or paste address" /> - {displayLpTokenLists - ? {allListsContent} - : allListsContent} + {displayLpTokenLists ? ( + {allListsContent} + ) : ( + allListsContent + )} ) } diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts new file mode 100644 index 0000000000..2f3be9e3d7 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts @@ -0,0 +1,35 @@ +import { useMemo } from 'react' + +import { useTokensBalances } from '@cowprotocol/balances-and-allowances' +import { LpToken } from '@cowprotocol/common-const' +import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { BigNumber } from '@ethersproject/bignumber' + +export type LpTokenWithBalance = { + token: LpToken + balance: BigNumber +} +export const LP_CATEGORY = [TokenListCategory.LP] + +export function useLpTokensWithBalances() { + const lpTokens = useAllLpTokens(LP_CATEGORY) + const { values: balances } = useTokensBalances() + + return useMemo(() => { + if (lpTokens.length === 0) return undefined + + return lpTokens.reduce( + (acc, token) => { + const addressLower = token.address.toLowerCase() + const balance = balances[addressLower] + + if (balance && !balance.isZero()) { + acc[addressLower] = { token, balance } + } + + return acc + }, + {} as Record, + ) + }, [lpTokens, balances]) +} diff --git a/apps/cowswap-frontend/src/modules/yield/shared.ts b/apps/cowswap-frontend/src/modules/yield/shared.ts new file mode 100644 index 0000000000..6a76b31ddb --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/shared.ts @@ -0,0 +1 @@ +export { PoolsInfoUpdater } from './updaters/PoolsInfoUpdater' diff --git a/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts new file mode 100644 index 0000000000..0de513e81d --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts @@ -0,0 +1,65 @@ +import { atom } from 'jotai' +import { atomWithStorage } from 'jotai/utils' + +import { getJotaiIsolatedStorage } from '@cowprotocol/core' +import { SupportedChainId, mapSupportedNetworks } from '@cowprotocol/cow-sdk' +import { walletInfoAtom } from '@cowprotocol/wallet' + +export interface PoolInfo { + apy: number + tvl: number + feeTier: number + volume24h: number +} + +type PoolInfoState = { + info: PoolInfo + updatedAt: number +} + +type PoolInfoStates = Record + +type PoolInfoStatesPerAccount = Record + +type PoolsInfoState = Record + +const poolsInfoAtom = atomWithStorage( + 'poolsInfoAtom:v0', + mapSupportedNetworks({}), + getJotaiIsolatedStorage(), +) + +export const currentPoolsInfoAtom = atom((get) => { + const { chainId, account } = get(walletInfoAtom) + const poolsInfo = get(poolsInfoAtom) + + return account ? poolsInfo[chainId]?.[account] : undefined +}) + +export const upsertPoolsInfoAtom = atom(null, (get, set, update: Record) => { + const { chainId, account } = get(walletInfoAtom) + const poolsInfo = get(poolsInfoAtom) + + if (!account) return + + const currentState = poolsInfo[chainId]?.[account] + const updatedState = { + ...currentState, + ...Object.keys(update).reduce((acc, address) => { + acc[address] = { + info: update[address], + updatedAt: Date.now(), + } + + return acc + }, {} as PoolInfoStates), + } + + set(poolsInfoAtom, { + ...poolsInfo, + [chainId]: { + ...poolsInfo[chainId], + [account]: updatedState, + }, + }) +}) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx new file mode 100644 index 0000000000..09bd82ec7f --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -0,0 +1,55 @@ +import { useAtomValue, useSetAtom } from 'jotai' +import { useEffect, useMemo } from 'react' + +import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' + +import ms from 'ms.macro' + +import { MOCK_POOL_INFO } from './mockPoolInfo' + +import { useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' +import { currentPoolsInfoAtom, upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' + +const POOL_INFO_CACHE_TIME = ms`1h` + +const swrConfig = { + ...SWR_NO_REFRESH_OPTIONS, + // refreshInterval: POOL_INFO_CACHE_TIME, +} + +/** + * The API should return info about requested pools + alternative COW AMM pools + */ +function fetchPoolsInfo(tokenAddresses: string[]) { + console.log('TODO', tokenAddresses) + return Promise.resolve(MOCK_POOL_INFO) +} + +export function PoolsInfoUpdater() { + const poolsInfo = useAtomValue(currentPoolsInfoAtom) + const upsertPoolsInfo = useSetAtom(upsertPoolsInfoAtom) + + const lpTokensWithBalances = useLpTokensWithBalances() + + const tokensToUpdate = useMemo(() => { + return lpTokensWithBalances + ? Object.keys(lpTokensWithBalances).filter((address) => { + const state = poolsInfo?.[address] + + if (!state) return true + + return state.updatedAt + POOL_INFO_CACHE_TIME > Date.now() + }) + : null + }, [lpTokensWithBalances, poolsInfo]) + + const tokensKey = useMemo(() => tokensToUpdate?.join(','), [tokensToUpdate]) + + useEffect(() => { + if (tokensToUpdate && tokensToUpdate.length > 0) { + fetchPoolsInfo(tokensToUpdate).then(upsertPoolsInfo) + } + }, [tokensKey]) + + return null +} diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo.ts b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo.ts new file mode 100644 index 0000000000..efe5801784 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo.ts @@ -0,0 +1,18 @@ +import { PoolInfo } from '../../state/poolsInfoAtom' + +export const MOCK_POOL_INFO: Record = { + // Sushi AAVE/WETH + '0xd75ea151a61d06868e31f8988d28dfe5e9df57b4': { + apy: 1.89, + tvl: 157057, + feeTier: 0.3, + volume24h: 31.19, + }, + // CoW AMM AAVE/WETH + '0xf706c50513446d709f08d3e5126cd74fb6bfda19': { + apy: 0.07, + tvl: 52972, + feeTier: 0.3, + volume24h: 10, + }, +} diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index 6c61741e59..6100e61f21 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -46,3 +46,4 @@ export { useAllLpTokens } from './hooks/tokens/useAllLpTokens' export { getTokenListViewLink } from './utils/getTokenListViewLink' export { getTokenLogoUrls } from './utils/getTokenLogoUrls' export { fetchTokenFromBlockchain } from './utils/fetchTokenFromBlockchain' +export { getTokenSearchFilter } from './utils/getTokenSearchFilter' diff --git a/libs/tokens/src/types.ts b/libs/tokens/src/types.ts index d1e6d4a206..8cbad348c5 100644 --- a/libs/tokens/src/types.ts +++ b/libs/tokens/src/types.ts @@ -8,6 +8,8 @@ export enum TokenListCategory { COW_AMM_LP = 'COW_AMM_LP', } +export const LP_TOKEN_LIST_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] + export type ListSourceConfig = { widgetAppCode?: string priority?: number @@ -24,7 +26,7 @@ export type UnsupportedTokensState = { [tokenAddress: string]: { dateAdded: numb export type ListsEnabledState = { [listId: string]: boolean | undefined } -export interface ListState extends Pick{ +export interface ListState extends Pick { list: UniTokenList isEnabled?: boolean } From df6f4b11ae8d80c8a8f5aa688afa356762bec3a2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 14:52:33 +0500 Subject: [PATCH 67/90] feat(yield): allow using lp-tokens in widget --- .../containers/LpTokenListsWidget/index.tsx | 5 +++- .../tokensList/pure/LpTokenLists/index.tsx | 15 ++++++++--- .../pure/SelectTokenModal/index.tsx | 4 ++- .../trade/hooks/useAutoImportTokensState.ts | 6 ++--- .../hooks/useNavigateOnCurrencySelection.ts | 8 +++--- .../updaters/BalancesAndAllowancesUpdater.tsx | 7 +++-- libs/tokens/src/state/tokens/allTokensAtom.ts | 26 ++++++++++++++++++- 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index af7a4a569f..89b445390d 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,6 +1,7 @@ import { ReactNode, useMemo, useState } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' +import { TokenWithLogo } from '@cowprotocol/common-const' import { getTokenSearchFilter, LP_TOKEN_LIST_CATEGORIES, @@ -17,6 +18,7 @@ import { tokensListSorter } from '../../utils/tokensListSorter' interface LpTokenListsProps { children: ReactNode search: string + onSelectToken(token: TokenWithLogo): void } const tabs = [ @@ -25,7 +27,7 @@ const tabs = [ { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, ] -export function LpTokenListsWidget({ search, children }: LpTokenListsProps) { +export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenListsProps) { const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) const tokensByAddress = useTokensByAddressMap() @@ -62,6 +64,7 @@ export function LpTokenListsWidget({ search, children }: LpTokenListsProps) { balancesState={balancesState} tokensByAddress={tokensByAddress} lpTokens={sortedTokens} + onSelectToken={onSelectToken} /> )} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index bfb017735d..8f6cbc9b2a 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' -import { LpToken } from '@cowprotocol/common-const' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' import { InfoTooltip, LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' @@ -33,9 +33,16 @@ interface LpTokenListsProps { tokensByAddress: TokensByAddress balancesState: BalancesState displayCreatePoolBanner: boolean + onSelectToken(token: TokenWithLogo): void } -export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, displayCreatePoolBanner }: LpTokenListsProps) { +export function LpTokenLists({ + onSelectToken, + lpTokens, + tokensByAddress, + balancesState, + displayCreatePoolBanner, +}: LpTokenListsProps) { const { values: balances } = balancesState const getItemView = useCallback( @@ -47,7 +54,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, display const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined return ( - + onSelectToken(token)}>
@@ -74,7 +81,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, display ) }, - [balances, tokensByAddress], + [balances, tokensByAddress, onSelectToken], ) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index c03e7437f8..977bc2ccf1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -109,7 +109,9 @@ export function SelectTokenModal(props: SelectTokenModalProps) { /> {displayLpTokenLists ? ( - {allListsContent} + + {allListsContent} + ) : ( allListsContent )} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useAutoImportTokensState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useAutoImportTokensState.ts index b7037a012e..efaa76740a 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useAutoImportTokensState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useAutoImportTokensState.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' -import { TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { isTruthy } from '@cowprotocol/common-utils' import { useSearchNonExistentToken } from '@cowprotocol/tokens' @@ -14,13 +14,13 @@ interface AutoImportTokensState { } export function useAutoImportTokensState( inputToken: Nullish, - outputToken: Nullish + outputToken: Nullish, ): AutoImportTokensState { const foundInputToken = useSearchNonExistentToken(inputToken || null) const foundOutputToken = useSearchNonExistentToken(outputToken || null) const tokensToImport = useMemo(() => { - return [foundInputToken, foundOutputToken].filter(isTruthy) + return [foundInputToken, foundOutputToken].filter(isTruthy).filter((token) => !(token instanceof LpToken)) }, [foundInputToken, foundOutputToken]) const tokensToImportCount = tokensToImport.length diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts index d3da2666a1..f534c164e6 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts @@ -16,7 +16,7 @@ export type CurrencySelectionCallback = ( field: Field, currency: Currency | null, stateUpdateCallback?: Command, - searchParams?: TradeSearchParams + searchParams?: TradeSearchParams, ) => void function useResolveCurrencyAddressOrSymbol(): (currency: Currency | null) => string | null { @@ -28,7 +28,7 @@ function useResolveCurrencyAddressOrSymbol(): (currency: Currency | null) => str return areThereTokensWithSameSymbol(currency.symbol) ? (currency as Token).address : currency.symbol || null }, - [areThereTokensWithSameSymbol] + [areThereTokensWithSameSymbol], ) } @@ -63,11 +63,11 @@ export function useNavigateOnCurrencySelection(): CurrencySelectionCallback { inputCurrencyId: targetInputCurrencyId, outputCurrencyId: targetOutputCurrencyId, }, - searchParams + searchParams, ) stateUpdateCallback?.() }, - [navigate, chainId, state, resolveCurrencyAddressOrSymbol] + [navigate, chainId, state, resolveCurrencyAddressOrSymbol], ) } diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index e5cf06bff9..422502970d 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -1,7 +1,7 @@ import { useSetAtom } from 'jotai' import { useEffect, useMemo } from 'react' -import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' +import { LpToken, NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' import { useAllTokens } from '@cowprotocol/tokens' @@ -27,7 +27,10 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl const allTokens = useAllTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account) - const tokenAddresses = useMemo(() => allTokens.map((token) => token.address), [allTokens]) + const tokenAddresses = useMemo( + () => allTokens.filter((token) => !(token instanceof LpToken)).map((token) => token.address), + [allTokens], + ) usePersistBalancesAndAllowances({ account, diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index fde9b92bea..719fb6df02 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -6,7 +6,7 @@ import { TokenInfo } from '@cowprotocol/types' import { favoriteTokensAtom } from './favoriteTokensAtom' import { userAddedTokensAtom } from './userAddedTokensAtom' -import { TokensMap } from '../../types' +import { LP_TOKEN_LIST_CATEGORIES, TokensMap } from '../../types' import { lowerCaseTokensMap } from '../../utils/lowerCaseTokensMap' import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' @@ -58,6 +58,28 @@ export const tokensStateAtom = atom((get) => { ) }) +export const lpTokensMapAtom = atom((get) => { + const { chainId } = get(environmentAtom) + const listsStatesList = get(listsStatesListAtom) + + return listsStatesList.reduce((acc, list) => { + if (!list.category || !LP_TOKEN_LIST_CATEGORIES.includes(list.category)) { + return acc + } + + list.list.tokens.forEach((token) => { + const tokenInfo = parseTokenInfo(chainId, token) + const tokenAddressKey = tokenInfo?.address.toLowerCase() + + if (!tokenInfo || !tokenAddressKey) return + + acc[tokenAddressKey] = tokenInfo + }) + + return acc + }, {}) +}) + /** * Returns a list of tokens that are active and sorted alphabetically * The list includes: native token, user added tokens, favorite tokens and tokens from active lists @@ -69,12 +91,14 @@ export const activeTokensAtom = atom((get) => { const favoriteTokensState = get(favoriteTokensAtom) const tokensMap = get(tokensStateAtom) + const lpTokensMap = get(lpTokensMapAtom) const nativeToken = NATIVE_CURRENCIES[chainId] return tokenMapToListWithLogo( { [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, ...tokensMap.activeTokens, + ...lpTokensMap, ...lowerCaseTokensMap(userAddedTokens[chainId]), ...lowerCaseTokensMap(favoriteTokensState[chainId]), }, From c43fba14cdb07f9892c36b78c18b0ae601d2403e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 16:04:53 +0500 Subject: [PATCH 68/90] feat(yield): display lp-token logo in widget --- .../containers/LpTokenListsWidget/index.tsx | 10 +- .../tokensList/pure/LpTokenLists/index.tsx | 26 +---- .../tokensList/pure/LpTokenLists/styled.ts | 24 ----- .../hooks/tokens/useTokenBySymbolOrAddress.ts | 6 +- libs/tokens/src/pure/TokenLogo/index.tsx | 99 +++++++++++++++---- 5 files changed, 88 insertions(+), 77 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 89b445390d..c7543a9d99 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -2,13 +2,7 @@ import { ReactNode, useMemo, useState } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenWithLogo } from '@cowprotocol/common-const' -import { - getTokenSearchFilter, - LP_TOKEN_LIST_CATEGORIES, - TokenListCategory, - useAllLpTokens, - useTokensByAddressMap, -} from '@cowprotocol/tokens' +import { getTokenSearchFilter, LP_TOKEN_LIST_CATEGORIES, TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' import { TabButton, TabsContainer } from './styled' @@ -30,7 +24,6 @@ const tabs = [ export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenListsProps) { const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) - const tokensByAddress = useTokensByAddressMap() const balancesState = useTokensBalances() const balances = balancesState.values @@ -62,7 +55,6 @@ export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenL diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 8f6cbc9b2a..25a0a5e388 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' -import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' +import { TokenLogo } from '@cowprotocol/tokens' import { InfoTooltip, LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' @@ -16,7 +16,6 @@ import { ListHeader, ListItem, LpTokenInfo, - LpTokenLogo, LpTokenWrapper, NoPoolWrapper, Wrapper, @@ -30,40 +29,25 @@ const LoadingElement = ( interface LpTokenListsProps { lpTokens: LpToken[] - tokensByAddress: TokensByAddress balancesState: BalancesState displayCreatePoolBanner: boolean onSelectToken(token: TokenWithLogo): void } -export function LpTokenLists({ - onSelectToken, - lpTokens, - tokensByAddress, - balancesState, - displayCreatePoolBanner, -}: LpTokenListsProps) { +export function LpTokenLists({ onSelectToken, lpTokens, balancesState, displayCreatePoolBanner }: LpTokenListsProps) { const { values: balances } = balancesState const getItemView = useCallback( (lpTokens: LpToken[], item: VirtualItem) => { const token = lpTokens[item.index] - const token0 = token.tokens?.[0]?.toLowerCase() - const token1 = token.tokens?.[1]?.toLowerCase() + const balance = balances ? balances[token.address.toLowerCase()] : undefined const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined return ( onSelectToken(token)}> - -
- -
-
- -
-
+ @@ -81,7 +65,7 @@ export function LpTokenLists({
) }, - [balances, tokensByAddress, onSelectToken], + [balances, onSelectToken], ) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index bf9473d7b8..5f8a1c9f39 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -37,30 +37,6 @@ export const ListItem = styled.div` } ` -export const LpTokenLogo = styled.div` - --size: 36px; - --halfSize: 18px; - - width: var(--size); - height: var(--size); - position: relative; - - > div { - position: absolute; - width: var(--halfSize); - overflow: hidden; - } - - > div:last-child { - right: -1px; - } - - > div:last-child > div { - right: 100%; - position: relative; - } -` - export const LpTokenInfo = styled.div` display: flex; flex-direction: column; diff --git a/libs/tokens/src/hooks/tokens/useTokenBySymbolOrAddress.ts b/libs/tokens/src/hooks/tokens/useTokenBySymbolOrAddress.ts index fe3f1dd620..1d0f25f31c 100644 --- a/libs/tokens/src/hooks/tokens/useTokenBySymbolOrAddress.ts +++ b/libs/tokens/src/hooks/tokens/useTokenBySymbolOrAddress.ts @@ -3,10 +3,12 @@ import { useMemo } from 'react' import { TokenWithLogo } from '@cowprotocol/common-const' -import { tokensByAddressAtom, tokensBySymbolAtom } from '../../state/tokens/allTokensAtom' +import { useTokensByAddressMap } from './useTokensByAddressMap' + +import { tokensBySymbolAtom } from '../../state/tokens/allTokensAtom' export function useTokenBySymbolOrAddress(symbolOrAddress?: string | null): TokenWithLogo | null { - const tokensByAddress = useAtomValue(tokensByAddressAtom) + const tokensByAddress = useTokensByAddressMap() const tokensBySymbol = useAtomValue(tokensBySymbolAtom) return useMemo(() => { diff --git a/libs/tokens/src/pure/TokenLogo/index.tsx b/libs/tokens/src/pure/TokenLogo/index.tsx index 8a37abef83..41403e3446 100644 --- a/libs/tokens/src/pure/TokenLogo/index.tsx +++ b/libs/tokens/src/pure/TokenLogo/index.tsx @@ -1,7 +1,7 @@ import { atom, useAtom } from 'jotai' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' -import { cowprotocolTokenLogoUrl, NATIVE_CURRENCY_ADDRESS, TokenWithLogo } from '@cowprotocol/common-const' +import { cowprotocolTokenLogoUrl, LpToken, NATIVE_CURRENCY_ADDRESS, TokenWithLogo } from '@cowprotocol/common-const' import { uriToHttp } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Media, UI } from '@cowprotocol/ui' @@ -12,6 +12,7 @@ import styled, { css } from 'styled-components/macro' import { SingleLetterLogo } from './SingleLetterLogo' +import { useTokensByAddressMap } from '../../hooks/tokens/useTokensByAddressMap' import { getTokenLogoUrls } from '../../utils/getTokenLogoUrls' const invalidUrlsAtom = atom<{ [url: string]: boolean }>({}) @@ -23,12 +24,12 @@ export const TokenLogoWrapper = styled.div<{ size?: number; sizeMobile?: number justify-content: center; background: var(${UI.COLOR_DARK_IMAGE_PAPER}); color: var(${UI.COLOR_DARK_IMAGE_PAPER_TEXT}); - border-radius: ${({ size }) => size ?? defaultSize}px; - width: ${({ size }) => size ?? defaultSize}px; - height: ${({ size }) => size ?? defaultSize}px; - min-width: ${({ size }) => size ?? defaultSize}px; - min-height: ${({ size }) => size ?? defaultSize}px; - font-size: ${({ size }) => size ?? defaultSize}px; + border-radius: ${({ size = defaultSize }) => size}px; + width: ${({ size = defaultSize }) => size}px; + height: ${({ size = defaultSize }) => size}px; + min-width: ${({ size = defaultSize }) => size}px; + min-height: ${({ size = defaultSize }) => size}px; + font-size: ${({ size = defaultSize }) => size}px; overflow: hidden; > img, @@ -59,18 +60,53 @@ export const TokenLogoWrapper = styled.div<{ size?: number; sizeMobile?: number } ` +const LpTokenWrapper = styled.div<{ size?: number }>` + width: 100%; + height: 100%; + position: relative; + + > div { + width: 50%; + height: 100%; + overflow: hidden; + position: absolute; + } + + > div:last-child { + right: -1px; + } + + > div:last-child > img { + right: 100%; + position: relative; + } + + > div > img { + width: ${({ size = defaultSize }) => size}px; + height: ${({ size = defaultSize }) => size}px; + min-width: ${({ size = defaultSize }) => size}px; + min-height: ${({ size = defaultSize }) => size}px; + } +` + export interface TokenLogoProps { - token?: TokenWithLogo | Currency | null + token?: TokenWithLogo | LpToken | Currency | null logoURI?: string className?: string size?: number sizeMobile?: number + noWrap?: boolean } -export function TokenLogo({ logoURI, token, className, size = 36, sizeMobile }: TokenLogoProps) { +export function TokenLogo({ logoURI, token, className, size = 36, sizeMobile, noWrap }: TokenLogoProps) { + const tokensByAddress = useTokensByAddressMap() + const [invalidUrls, setInvalidUrls] = useAtom(invalidUrlsAtom) + const isLpToken = token instanceof LpToken const urls = useMemo(() => { + if (token instanceof LpToken) return + // TODO: get rid of Currency usage and remove type casting if (token) { if (token instanceof NativeCurrency) { @@ -83,25 +119,46 @@ export function TokenLogo({ logoURI, token, className, size = 36, sizeMobile }: return logoURI ? uriToHttp(logoURI) : [] }, [logoURI, token]) - const validUrls = useMemo(() => urls.filter((url) => !invalidUrls[url]), [urls, invalidUrls]) + const validUrls = useMemo(() => urls && urls.filter((url) => !invalidUrls[url]), [urls, invalidUrls]) - const currentUrl = validUrls[0] + const currentUrl = validUrls?.[0] + + const onError = useCallback(() => { + if (!currentUrl) return - const onError = () => { setInvalidUrls((state) => ({ ...state, [currentUrl]: true })) - } + }, [currentUrl, setInvalidUrls]) const initial = token?.symbol?.[0] || token?.name?.[0] + if (isLpToken) { + return ( + + +
+ +
+
+ +
+
+
+ ) + } + + const content = currentUrl ? ( + token logo + ) : initial ? ( + + ) : ( + + ) + + if (noWrap) return content + return ( - {currentUrl ? ( - token logo - ) : initial ? ( - - ) : ( - - )} + {content} ) } From d95fcf1b36ebccdae3e679a26d5a9c94fcb86cc3 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 16:27:07 +0500 Subject: [PATCH 69/90] feat(yield): display pool apy --- .../containers/LpTokenListsWidget/index.tsx | 4 ++++ .../tokensList/pure/LpTokenLists/index.tsx | 19 +++++++++++++++---- .../tokensList/pure/LpTokenLists/styled.ts | 2 ++ .../src/modules/yield/hooks/usePoolsInfo.ts | 7 +++++++ .../src/modules/yield/shared.ts | 2 ++ .../src/modules/yield/state/poolsInfoAtom.ts | 2 +- .../yield/updaters/PoolsInfoUpdater/index.tsx | 7 ++++--- 7 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/yield/hooks/usePoolsInfo.ts diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index c7543a9d99..c3bd5486f5 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -4,6 +4,8 @@ import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenWithLogo } from '@cowprotocol/common-const' import { getTokenSearchFilter, LP_TOKEN_LIST_CATEGORIES, TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { usePoolsInfo } from 'modules/yield/shared' + import { TabButton, TabsContainer } from './styled' import { LpTokenLists } from '../../pure/LpTokenLists' @@ -25,6 +27,7 @@ export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenL const [listsCategories, setListsCategories] = useState(null) const lpTokens = useAllLpTokens(listsCategories) const balancesState = useTokensBalances() + const poolsInfo = usePoolsInfo() const balances = balancesState.values @@ -57,6 +60,7 @@ export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenL balancesState={balancesState} lpTokens={sortedTokens} onSelectToken={onSelectToken} + poolsInfo={poolsInfo} /> )} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 25a0a5e388..5887b0e80b 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -8,6 +8,8 @@ import { CurrencyAmount } from '@uniswap/sdk-core' import { VirtualItem } from '@tanstack/react-virtual' +import type { PoolInfoStates } from 'modules/yield/shared' + import { VirtualList } from 'common/pure/VirtualList' import { @@ -31,18 +33,27 @@ interface LpTokenListsProps { lpTokens: LpToken[] balancesState: BalancesState displayCreatePoolBanner: boolean + poolsInfo: PoolInfoStates | undefined onSelectToken(token: TokenWithLogo): void } -export function LpTokenLists({ onSelectToken, lpTokens, balancesState, displayCreatePoolBanner }: LpTokenListsProps) { +export function LpTokenLists({ + onSelectToken, + lpTokens, + balancesState, + displayCreatePoolBanner, + poolsInfo, +}: LpTokenListsProps) { const { values: balances } = balancesState const getItemView = useCallback( (lpTokens: LpToken[], item: VirtualItem) => { const token = lpTokens[item.index] - const balance = balances ? balances[token.address.toLowerCase()] : undefined + const tokenAddressLower = token.address.toLowerCase() + const balance = balances ? balances[tokenAddressLower] : undefined const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined + const info = poolsInfo?.[tokenAddressLower]?.info return ( onSelectToken(token)}> @@ -58,14 +69,14 @@ export function LpTokenLists({ onSelectToken, lpTokens, balancesState, displayCr {balanceAmount ? : LoadingElement} - 40% + {info?.apy ? `${info.apy}%` : '-'} TODO ) }, - [balances, onSelectToken], + [balances, onSelectToken, poolsInfo], ) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index 5f8a1c9f39..df9d7f81c8 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -31,6 +31,8 @@ export const ListItem = styled.div` grid-template-columns: 1fr 100px 50px 20px; padding: 10px 20px; cursor: pointer; + font-size: 14px; + align-items: center; &:hover { background: var(${UI.COLOR_PAPER_DARKER}); diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/usePoolsInfo.ts b/apps/cowswap-frontend/src/modules/yield/hooks/usePoolsInfo.ts new file mode 100644 index 0000000000..8ed7cd1f24 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/yield/hooks/usePoolsInfo.ts @@ -0,0 +1,7 @@ +import { useAtomValue } from 'jotai' + +import { currentPoolsInfoAtom } from '../state/poolsInfoAtom' + +export function usePoolsInfo() { + return useAtomValue(currentPoolsInfoAtom) +} diff --git a/apps/cowswap-frontend/src/modules/yield/shared.ts b/apps/cowswap-frontend/src/modules/yield/shared.ts index 6a76b31ddb..24f46a452d 100644 --- a/apps/cowswap-frontend/src/modules/yield/shared.ts +++ b/apps/cowswap-frontend/src/modules/yield/shared.ts @@ -1 +1,3 @@ export { PoolsInfoUpdater } from './updaters/PoolsInfoUpdater' +export { usePoolsInfo } from './hooks/usePoolsInfo' +export type { PoolInfo, PoolInfoStates } from './state/poolsInfoAtom' diff --git a/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts index 0de513e81d..11e3bb7046 100644 --- a/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts +++ b/apps/cowswap-frontend/src/modules/yield/state/poolsInfoAtom.ts @@ -17,7 +17,7 @@ type PoolInfoState = { updatedAt: number } -type PoolInfoStates = Record +export type PoolInfoStates = Record type PoolInfoStatesPerAccount = Record diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx index 09bd82ec7f..c376181150 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -1,4 +1,4 @@ -import { useAtomValue, useSetAtom } from 'jotai' +import { useSetAtom } from 'jotai' import { useEffect, useMemo } from 'react' import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' @@ -8,7 +8,8 @@ import ms from 'ms.macro' import { MOCK_POOL_INFO } from './mockPoolInfo' import { useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' -import { currentPoolsInfoAtom, upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' +import { usePoolsInfo } from '../../hooks/usePoolsInfo' +import { upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' const POOL_INFO_CACHE_TIME = ms`1h` @@ -26,7 +27,7 @@ function fetchPoolsInfo(tokenAddresses: string[]) { } export function PoolsInfoUpdater() { - const poolsInfo = useAtomValue(currentPoolsInfoAtom) + const poolsInfo = usePoolsInfo() const upsertPoolsInfo = useSetAtom(upsertPoolsInfoAtom) const lpTokensWithBalances = useLpTokensWithBalances() From 564d0b20e9a545fd88629f11aef22beb962c4194 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 16:28:24 +0500 Subject: [PATCH 70/90] chore: fix lp token logo --- libs/tokens/src/pure/TokenLogo/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/tokens/src/pure/TokenLogo/index.tsx b/libs/tokens/src/pure/TokenLogo/index.tsx index 41403e3446..aca36412aa 100644 --- a/libs/tokens/src/pure/TokenLogo/index.tsx +++ b/libs/tokens/src/pure/TokenLogo/index.tsx @@ -76,12 +76,14 @@ const LpTokenWrapper = styled.div<{ size?: number }>` right: -1px; } - > div:last-child > img { + > div:last-child > img, + > div:last-child > svg { right: 100%; position: relative; } - > div > img { + > div > img, + > div > svg { width: ${({ size = defaultSize }) => size}px; height: ${({ size = defaultSize }) => size}px; min-width: ${({ size = defaultSize }) => size}px; From eeaa6bb679e110095ff19a98ce5d0101733f76d2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 21 Oct 2024 18:38:24 +0500 Subject: [PATCH 71/90] feat(yield): pool info page --- .../containers/LpTokenListsWidget/index.tsx | 17 ++- .../containers/LpTokenPage/index.tsx | 107 ++++++++++++++++++ .../containers/LpTokenPage/styled.tsx | 77 +++++++++++++ .../containers/SelectTokenWidget/index.tsx | 40 ++++++- .../tokensList/pure/LpTokenLists/index.tsx | 18 ++- .../tokensList/pure/ModalHeader/index.tsx | 4 +- .../pure/SelectTokenModal/index.tsx | 16 ++- .../tokensList/state/selectTokenWidgetAtom.ts | 3 +- .../yield/updaters/PoolsInfoUpdater/index.tsx | 7 -- 9 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/styled.tsx diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index c3bd5486f5..b3ec0df2fc 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useMemo, useState } from 'react' +import { ReactNode, useMemo } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenWithLogo } from '@cowprotocol/common-const' @@ -11,10 +11,12 @@ import { TabButton, TabsContainer } from './styled' import { LpTokenLists } from '../../pure/LpTokenLists' import { tokensListSorter } from '../../utils/tokensListSorter' -interface LpTokenListsProps { +interface LpTokenListsProps { children: ReactNode search: string onSelectToken(token: TokenWithLogo): void + openPoolPage(poolAddress: string): void + tokenListCategoryState: [T, (category: T) => void] } const tabs = [ @@ -23,8 +25,14 @@ const tabs = [ { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, ] -export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenListsProps) { - const [listsCategories, setListsCategories] = useState(null) +export function LpTokenListsWidget({ + search, + children, + onSelectToken, + openPoolPage, + tokenListCategoryState, +}: LpTokenListsProps) { + const [listsCategories, setListsCategories] = tokenListCategoryState const lpTokens = useAllLpTokens(listsCategories) const balancesState = useTokensBalances() const poolsInfo = usePoolsInfo() @@ -60,6 +68,7 @@ export function LpTokenListsWidget({ search, children, onSelectToken }: LpTokenL balancesState={balancesState} lpTokens={sortedTokens} onSelectToken={onSelectToken} + openPoolPage={openPoolPage} poolsInfo={poolsInfo} /> )} diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx new file mode 100644 index 0000000000..8173186efc --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx @@ -0,0 +1,107 @@ +import { TokenWithLogo } from '@cowprotocol/common-const' +import { ExplorerDataType, getExplorerLink, shortenAddress } from '@cowprotocol/common-utils' +import { TokenLogo, useTokensByAddressMap } from '@cowprotocol/tokens' +import { ExternalLink, TokenSymbol } from '@cowprotocol/ui' + +import { usePoolsInfo } from 'modules/yield/shared' + +import { + InfoRow, + InfoTable, + SelectButton, + StyledTokenName, + StyledTokenSymbol, + TokenInfoWrapper, + TokenWrapper, + Wrapper, +} from './styled' + +import { ModalHeader } from '../../pure/ModalHeader' + +interface LpTokenPageProps { + poolAddress: string + onBack(): void + onDismiss(): void + onSelectToken(token: TokenWithLogo): void +} + +export function LpTokenPage({ poolAddress, onBack, onDismiss, onSelectToken }: LpTokenPageProps) { + const poolsInfo = usePoolsInfo() + const tokensByAddress = useTokensByAddressMap() + + const token = tokensByAddress[poolAddress] + const info = poolsInfo?.[poolAddress]?.info + + return ( + + + Pool description + + {token && ( + + + +
+
+ +
+ +
+
+
+ { + onDismiss() + onSelectToken(token) + }} + > + Select + +
+
+ )} + + +
Symbol
+
+ +
+
+ +
Fee tier
+
+ {info?.feeTier ? `${info.feeTier}%` : '-'} +
+
+ +
Volume (24h)
+
+ ${info?.volume24h} +
+
+ +
APY
+
+ {info?.apy ? `${info.apy}%` : '-'} +
+
+ +
TVL
+
+ ${info?.tvl} +
+
+ +
Pool address
+
+ {token && ( + + {shortenAddress(token.address)} ↗ + + )} +
+
+
+
+ ) +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/styled.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/styled.tsx new file mode 100644 index 0000000000..67b458556b --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/styled.tsx @@ -0,0 +1,77 @@ +import { TokenName, TokenSymbol, UI } from '@cowprotocol/ui' + +import styled from 'styled-components/macro' + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + background: var(${UI.COLOR_PAPER}); + border-radius: 20px; +` + +export const TokenInfoWrapper = styled.div` + display: flex; + flex-direction: row; + width: 100%; + align-items: center; + gap: 10px; +` + +export const TokenWrapper = styled(TokenInfoWrapper)` + padding: 20px; + border-bottom: 1px solid var(${UI.COLOR_BORDER}); +` + +export const InfoTable = styled.div` + display: flex; + flex-direction: column; + padding: 0 20px; +` + +export const StyledTokenSymbol = styled(TokenSymbol)` + font-size: 21px; + font-weight: 600; +` + +export const StyledTokenName = styled(TokenName)` + font-size: 14px; + color: var(${UI.COLOR_TEXT_OPACITY_70}); +` + +export const InfoRow = styled.div` + display: grid; + grid-template-columns: 100px 1fr; + width: 100%; + gap: 10px; + border-bottom: 1px solid var(${UI.COLOR_BORDER}); + padding: 14px 0; + font-size: 14px; + + &:last-child { + border-bottom: none; + } + + > div:first-child { + color: var(${UI.COLOR_TEXT_OPACITY_50}); + } +` + +export const SelectButton = styled.button` + display: inline-block; + background: #bcec79; + color: #194d05; + font-size: 14px; + font-weight: bold; + border-radius: 24px; + padding: 10px 24px; + text-decoration: none; + border: 0; + outline: 0; + cursor: pointer; + + &:hover { + opacity: 0.8; + } +` diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx index 9a30f04cb0..4f7ecdcc87 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx @@ -5,6 +5,7 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { isInjectedWidget } from '@cowprotocol/common-utils' import { ListState, + TokenListCategory, useAddList, useAddUserToken, useAllListsList, @@ -26,6 +27,7 @@ import { useUpdateSelectTokenWidgetState } from '../../hooks/useUpdateSelectToke import { ImportListModal } from '../../pure/ImportListModal' import { ImportTokenModal } from '../../pure/ImportTokenModal' import { SelectTokenModal } from '../../pure/SelectTokenModal' +import { LpTokenPage } from '../LpTokenPage' import { ManageListsAndTokens } from '../ManageListsAndTokens' const Wrapper = styled.div` @@ -41,10 +43,11 @@ interface SelectTokenWidgetProps { displayLpTokenLists?: boolean } -export function SelectTokenWidget({displayLpTokenLists}: SelectTokenWidgetProps) { - const { open, onSelectToken, tokenToImport, listToImport, selectedToken, onInputPressEnter } = +export function SelectTokenWidget({ displayLpTokenLists }: SelectTokenWidgetProps) { + const { open, onSelectToken, tokenToImport, listToImport, selectedToken, onInputPressEnter, selectedPoolAddress } = useSelectTokenWidgetState() const [isManageWidgetOpen, setIsManageWidgetOpen] = useState(false) + const tokenListCategoryState = useState(null) const updateSelectTokenWidget = useUpdateSelectTokenWidgetState() const { account } = useWalletInfo() @@ -70,19 +73,31 @@ export function SelectTokenWidget({displayLpTokenLists}: SelectTokenWidgetProps) onSelectToken: undefined, tokenToImport: undefined, listToImport: undefined, + selectedPoolAddress: undefined, }) }, [updateSelectTokenWidget]) - const resetTokenImport = () => { + const openPoolPage = useCallback( + (selectedPoolAddress: string) => { + updateSelectTokenWidget({ selectedPoolAddress }) + }, + [updateSelectTokenWidget], + ) + + const closePoolPage = useCallback(() => { + updateSelectTokenWidget({ selectedPoolAddress: undefined }) + }, [updateSelectTokenWidget]) + + const resetTokenImport = useCallback(() => { updateSelectTokenWidget({ tokenToImport: undefined, }) - } + }, [updateSelectTokenWidget]) - const onDismiss = () => { + const onDismiss = useCallback(() => { setIsManageWidgetOpen(false) closeTokenSelectWidget() - } + }, [closeTokenSelectWidget]) const importTokenAndClose = (tokens: TokenWithLogo[]) => { importTokenCallback(tokens) @@ -139,6 +154,17 @@ export function SelectTokenWidget({displayLpTokenLists}: SelectTokenWidgetProps) ) } + if (selectedPoolAddress) { + return ( + + ) + } + return ( setIsManageWidgetOpen(true)} hideFavoriteTokensTooltip={isInjectedWidgetMode} + openPoolPage={openPoolPage} + tokenListCategoryState={tokenListCategoryState} account={account} /> ) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 5887b0e80b..1c6f591880 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -1,14 +1,15 @@ -import { useCallback } from 'react' +import { MouseEventHandler, useCallback } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { TokenLogo } from '@cowprotocol/tokens' -import { InfoTooltip, LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' import { VirtualItem } from '@tanstack/react-virtual' +import { Info } from 'react-feather' -import type { PoolInfoStates } from 'modules/yield/shared' +import { PoolInfoStates } from 'modules/yield/shared' import { VirtualList } from 'common/pure/VirtualList' @@ -35,10 +36,12 @@ interface LpTokenListsProps { displayCreatePoolBanner: boolean poolsInfo: PoolInfoStates | undefined onSelectToken(token: TokenWithLogo): void + openPoolPage(poolAddress: string): void } export function LpTokenLists({ onSelectToken, + openPoolPage, lpTokens, balancesState, displayCreatePoolBanner, @@ -55,6 +58,11 @@ export function LpTokenLists({ const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined const info = poolsInfo?.[tokenAddressLower]?.info + const onInfoClick: MouseEventHandler = (e) => { + e.stopPropagation() + openPoolPage(tokenAddressLower) + } + return ( onSelectToken(token)}> @@ -71,12 +79,12 @@ export function LpTokenLists({ {balanceAmount ? : LoadingElement} {info?.apy ? `${info.apy}%` : '-'} - TODO + ) }, - [balances, onSelectToken, poolsInfo], + [balances, onSelectToken, poolsInfo, openPoolPage], ) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/ModalHeader/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/ModalHeader/index.tsx index ad46d21ed0..63d921f2b1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/ModalHeader/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/ModalHeader/index.tsx @@ -1,3 +1,5 @@ +import { ReactNode } from 'react' + import { UI } from '@cowprotocol/ui' import { BackButton } from '@cowprotocol/ui' @@ -24,7 +26,7 @@ const Header = styled.div` ` export interface ModalHeaderProps { - children: string + children: ReactNode onBack?(): void onClose?(): void className?: string diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index 977bc2ccf1..e386b101b1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' import { TokenWithLogo } from '@cowprotocol/common-const' -import { UnsupportedTokensState } from '@cowprotocol/tokens' +import { TokenListCategory, UnsupportedTokensState } from '@cowprotocol/tokens' import { BackButton, SearchInput } from '@cowprotocol/ui' import { Edit } from 'react-feather' @@ -17,7 +17,7 @@ import { SelectTokenContext } from '../../types' import { FavoriteTokensList } from '../FavoriteTokensList' import { TokensVirtualList } from '../TokensVirtualList' -export interface SelectTokenModalProps { +export interface SelectTokenModalProps { allTokens: TokenWithLogo[] favoriteTokens: TokenWithLogo[] balancesState: BalancesState @@ -27,9 +27,12 @@ export interface SelectTokenModalProps { hideFavoriteTokensTooltip?: boolean displayLpTokenLists?: boolean account: string | undefined + tokenListCategoryState: [T, (category: T) => void] onSelectToken(token: TokenWithLogo): void + openPoolPage(poolAddress: string): void + onInputPressEnter?(): void defaultInputValue?: string @@ -55,6 +58,8 @@ export function SelectTokenModal(props: SelectTokenModalProps) { onInputPressEnter, account, displayLpTokenLists, + openPoolPage, + tokenListCategoryState, } = props const [inputValue, setInputValue] = useState(defaultInputValue) @@ -109,7 +114,12 @@ export function SelectTokenModal(props: SelectTokenModalProps) { /> {displayLpTokenLists ? ( - + {allListsContent} ) : ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts b/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts index 8dd317439d..5e01b5ab42 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts @@ -10,9 +10,10 @@ export const { atom: selectTokenWidgetAtom, updateAtom: updateSelectTokenWidgetA atom<{ open: boolean selectedToken?: string + selectedPoolAddress?: string tokenToImport?: TokenWithLogo listToImport?: ListState onSelectToken?: (currency: Currency) => void onInputPressEnter?: Command - }>({ open: false }) + }>({ open: false }), ) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx index c376181150..6025309648 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -1,8 +1,6 @@ import { useSetAtom } from 'jotai' import { useEffect, useMemo } from 'react' -import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' - import ms from 'ms.macro' import { MOCK_POOL_INFO } from './mockPoolInfo' @@ -13,11 +11,6 @@ import { upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' const POOL_INFO_CACHE_TIME = ms`1h` -const swrConfig = { - ...SWR_NO_REFRESH_OPTIONS, - // refreshInterval: POOL_INFO_CACHE_TIME, -} - /** * The API should return info about requested pools + alternative COW AMM pools */ From 4488bf76538de6ffbd324509f7d7a3ed0ea05fff Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 13:49:40 +0500 Subject: [PATCH 72/90] chore: fix build --- .../modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx index 2123dca8c6..6ac2160670 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx @@ -31,6 +31,7 @@ const defaultProps: SelectTokenModalProps = { unsupportedTokens, allTokens: allTokensMock, favoriteTokens: favoriteTokensMock, + tokenListCategoryState: [null, () => void 0], balancesState: { values: balances, isLoading: false, @@ -45,6 +46,9 @@ const defaultProps: SelectTokenModalProps = { onDismiss() { console.log('onDismiss') }, + openPoolPage() { + console.log('openPoolPage') + }, } const Fixtures = { From de38227380ae36ee56c1fb018f2df9a64885994e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 14:28:19 +0500 Subject: [PATCH 73/90] chore(yield): fix balance loading state --- .../tokensList/containers/LpTokenListsWidget/index.tsx | 3 +++ .../src/modules/tokensList/pure/LpTokenLists/index.tsx | 6 ++++-- .../src/modules/tokensList/pure/SelectTokenModal/index.tsx | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index b3ec0df2fc..22b81e6309 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -12,6 +12,7 @@ import { LpTokenLists } from '../../pure/LpTokenLists' import { tokensListSorter } from '../../utils/tokensListSorter' interface LpTokenListsProps { + account: string | undefined children: ReactNode search: string onSelectToken(token: TokenWithLogo): void @@ -26,6 +27,7 @@ const tabs = [ ] export function LpTokenListsWidget({ + account, search, children, onSelectToken, @@ -64,6 +66,7 @@ export function LpTokenListsWidget({ children ) : ( - {balanceAmount ? : LoadingElement} + {balanceAmount ? : account ? LoadingElement : null} {info?.apy ? `${info.apy}%` : '-'} @@ -84,7 +86,7 @@ export function LpTokenLists({ ) }, - [balances, onSelectToken, poolsInfo, openPoolPage], + [balances, onSelectToken, poolsInfo, openPoolPage, account], ) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index e386b101b1..26ff5898c5 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -115,6 +115,7 @@ export function SelectTokenModal(props: SelectTokenModalProps) { {displayLpTokenLists ? ( Date: Tue, 22 Oct 2024 15:03:57 +0500 Subject: [PATCH 74/90] chore(yield): fix pools info fetching --- .../yield/updaters/PoolsInfoUpdater/index.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx index 6025309648..c6a3804798 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -5,16 +5,19 @@ import ms from 'ms.macro' import { MOCK_POOL_INFO } from './mockPoolInfo' -import { useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' +import { LP_CATEGORY, useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' import { usePoolsInfo } from '../../hooks/usePoolsInfo' import { upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' +import { TradeType, useTradeTypeInfo } from '../../../trade' +import { LP_TOKEN_LIST_CATEGORIES, useAllLpTokens } from '@cowprotocol/tokens' const POOL_INFO_CACHE_TIME = ms`1h` /** * The API should return info about requested pools + alternative COW AMM pools + * When tokenAddresses is null, it should return info about all pools */ -function fetchPoolsInfo(tokenAddresses: string[]) { +function fetchPoolsInfo(tokenAddresses: string[] | null) { console.log('TODO', tokenAddresses) return Promise.resolve(MOCK_POOL_INFO) } @@ -22,6 +25,8 @@ function fetchPoolsInfo(tokenAddresses: string[]) { export function PoolsInfoUpdater() { const poolsInfo = usePoolsInfo() const upsertPoolsInfo = useSetAtom(upsertPoolsInfoAtom) + const tradeTypeInfo = useTradeTypeInfo() + const isYield = tradeTypeInfo?.tradeType === TradeType.YIELD const lpTokensWithBalances = useLpTokensWithBalances() @@ -40,10 +45,10 @@ export function PoolsInfoUpdater() { const tokensKey = useMemo(() => tokensToUpdate?.join(','), [tokensToUpdate]) useEffect(() => { - if (tokensToUpdate && tokensToUpdate.length > 0) { - fetchPoolsInfo(tokensToUpdate).then(upsertPoolsInfo) + if ((tokensToUpdate && tokensToUpdate.length > 0) || isYield) { + fetchPoolsInfo(isYield ? null : tokensToUpdate).then(upsertPoolsInfo) } - }, [tokensKey]) + }, [isYield, tokensKey]) return null } From 0cf8755a79fb172c0190d9600559e82e95db6ecf Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 15:08:27 +0500 Subject: [PATCH 75/90] chore: fix yield menu link --- .../containers/TradeWidgetLinks/index.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx index c0874b0fc3..f08534085a 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/index.tsx @@ -67,20 +67,21 @@ export function TradeWidgetLinks({ isDropdown = false }: TradeWidgetLinksProps) const isCurrentPathYield = location.pathname.startsWith(addChainIdToRoute(Routes.YIELD, chainId)) const itemTradeState = getTradeStateByType(item.route) - const routePath = isItemYield - ? addChainIdToRoute(item.route, chainId) - : parameterizeTradeRoute( - isCurrentPathYield - ? ({ - chainId, - inputCurrencyId: - itemTradeState.inputCurrencyId || (chainId && getDefaultTradeRawState(+chainId).inputCurrencyId), - outputCurrencyId: itemTradeState.outputCurrencyId, - } as TradeUrlParams) - : tradeContext, - item.route, - !isCurrentPathYield, - ) + const routePath = + isItemYield && !isCurrentPathYield + ? addChainIdToRoute(item.route, chainId) + : parameterizeTradeRoute( + isCurrentPathYield + ? ({ + chainId, + inputCurrencyId: + itemTradeState.inputCurrencyId || (chainId && getDefaultTradeRawState(+chainId).inputCurrencyId), + outputCurrencyId: itemTradeState.outputCurrencyId, + } as TradeUrlParams) + : tradeContext, + item.route, + !isCurrentPathYield, + ) const isActive = location.pathname.startsWith(routePath.split('?')[0]) From 36fae4667e6c798aa7492afbf361f02e2d7a0311 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 15:13:22 +0500 Subject: [PATCH 76/90] chore: format pool displayed values --- .../tokensList/containers/LpTokenPage/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx index 8173186efc..460ee8db8e 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenPage/index.tsx @@ -18,6 +18,10 @@ import { import { ModalHeader } from '../../pure/ModalHeader' +function renderValue(value: T | undefined, template: (v: T) => string, defaultValue?: string): string | undefined { + return value ? template(value) : defaultValue +} + interface LpTokenPageProps { poolAddress: string onBack(): void @@ -70,25 +74,25 @@ export function LpTokenPage({ poolAddress, onBack, onDismiss, onSelectToken }: L
Fee tier
- {info?.feeTier ? `${info.feeTier}%` : '-'} + {renderValue(info?.feeTier, (t) => `${t}%`, '-')}
Volume (24h)
- ${info?.volume24h} + {renderValue(info?.volume24h, (t) => `$${t}`, '-')}
APY
- {info?.apy ? `${info.apy}%` : '-'} + {renderValue(info?.apy, (t) => `${t}%`, '-')}
TVL
- ${info?.tvl} + {renderValue(info?.tvl, (t) => `$${t}`, '-')}
From f246521e7b29828c44d7d74db563f990493983e3 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 15:20:02 +0500 Subject: [PATCH 77/90] chore: always use address for lp-tokens in url --- .../modules/trade/hooks/useNavigateOnCurrencySelection.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts index f534c164e6..54d8edf293 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useNavigateOnCurrencySelection.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react' +import { LpToken } from '@cowprotocol/common-const' import { useAreThereTokensWithSameSymbol } from '@cowprotocol/tokens' import { Command } from '@cowprotocol/types' import { useWalletInfo } from '@cowprotocol/wallet' @@ -26,7 +27,9 @@ function useResolveCurrencyAddressOrSymbol(): (currency: Currency | null) => str (currency: Currency | null): string | null => { if (!currency) return null - return areThereTokensWithSameSymbol(currency.symbol) ? (currency as Token).address : currency.symbol || null + return currency instanceof LpToken || areThereTokensWithSameSymbol(currency.symbol) + ? (currency as Token).address + : currency.symbol || null }, [areThereTokensWithSameSymbol], ) From 4092dd7b3d3beec2285bc4912c4cf6ebf7bd0dea Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 15:49:04 +0500 Subject: [PATCH 78/90] fix: fix lp-tokens usage --- .../modules/application/containers/App/Updaters.tsx | 6 +++++- libs/tokens/src/const/tokensLists.ts | 4 +++- libs/tokens/src/state/environmentAtom.ts | 3 ++- .../src/state/tokenLists/tokenListsStateAtom.ts | 10 ++++++++-- libs/tokens/src/state/tokens/allTokensAtom.ts | 4 ++-- libs/tokens/src/updaters/TokensListsUpdater/index.ts | 11 ++++++++--- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx index e9ca8a9b24..e6753d580d 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx @@ -70,7 +70,11 @@ export function Updaters() { - + + export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = mapSupportedNetworks((chainId) => [ ...tokensList[chainId], - ...(lpTokensList as Array), + ...LP_TOKEN_LISTS, ]) export const UNISWAP_TOKENS_LIST = 'https://ipfs.io/ipns/tokens.uniswap.org' diff --git a/libs/tokens/src/state/environmentAtom.ts b/libs/tokens/src/state/environmentAtom.ts index 687c77dc8c..1dabe73245 100644 --- a/libs/tokens/src/state/environmentAtom.ts +++ b/libs/tokens/src/state/environmentAtom.ts @@ -6,11 +6,12 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' interface TokensModuleEnvironment { chainId: SupportedChainId useCuratedListOnly?: boolean + enableLpTokensByDefault?: boolean widgetAppCode?: string selectedLists?: string[] } export const { atom: environmentAtom, updateAtom: updateEnvironmentAtom } = atomWithPartialUpdate( atom({ chainId: getCurrentChainIdFromUrl(), - }) + }), ) diff --git a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts index 3728e1baa7..c5a9205a7c 100644 --- a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts +++ b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts @@ -8,6 +8,7 @@ import { ARBITRUM_ONE_TOKENS_LIST, DEFAULT_TOKENS_LISTS, GNOSIS_UNISWAP_TOKENS_LIST, + LP_TOKEN_LISTS, UNISWAP_TOKENS_LIST, } from '../../const/tokensLists' import { @@ -47,7 +48,7 @@ export const allListsSourcesAtom = atom((get) => { const userAddedTokenLists = get(userAddedListsSourcesAtom) if (useCuratedListOnly) { - return [get(curatedListSourceAtom), ...userAddedTokenLists[chainId]] + return [get(curatedListSourceAtom), ...LP_TOKEN_LISTS, ...userAddedTokenLists[chainId]] } return [...DEFAULT_TOKENS_LISTS[chainId], ...(userAddedTokenLists[chainId] || [])] @@ -86,8 +87,13 @@ export const listsStatesMapAtom = atom((get) => { return acc }, {}) + const lpTokenListSources = LP_TOKEN_LISTS.reduce<{ [key: string]: boolean }>((acc, list) => { + acc[list.source] = true + return acc + }, {}) + const listsSources = Object.keys(currentNetworkLists).filter((source) => { - return useCuratedListOnly ? userAddedListSources[source] : true + return useCuratedListOnly ? userAddedListSources[source] || lpTokenListSources[source] : true }) const lists = useCuratedListOnly ? [get(curatedListSourceAtom).source, ...listsSources] : listsSources diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index 719fb6df02..c243d4e762 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -86,7 +86,7 @@ export const lpTokensMapAtom = atom((get) => { * Native token is always the first element in the list */ export const activeTokensAtom = atom((get) => { - const { chainId } = get(environmentAtom) + const { chainId, enableLpTokensByDefault } = get(environmentAtom) const userAddedTokens = get(userAddedTokensAtom) const favoriteTokensState = get(favoriteTokensAtom) @@ -98,7 +98,7 @@ export const activeTokensAtom = atom((get) => { { [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, ...tokensMap.activeTokens, - ...lpTokensMap, + ...(enableLpTokensByDefault ? lpTokensMap : null), ...lowerCaseTokensMap(userAddedTokens[chainId]), ...lowerCaseTokensMap(favoriteTokensState[chainId]), }, diff --git a/libs/tokens/src/updaters/TokensListsUpdater/index.ts b/libs/tokens/src/updaters/TokensListsUpdater/index.ts index 7a882406e3..d14f61ae10 100644 --- a/libs/tokens/src/updaters/TokensListsUpdater/index.ts +++ b/libs/tokens/src/updaters/TokensListsUpdater/index.ts @@ -35,6 +35,7 @@ const NETWORKS_WITHOUT_RESTRICTIONS = [SupportedChainId.SEPOLIA] interface TokensListsUpdaterProps { chainId: SupportedChainId isGeoBlockEnabled: boolean + enableLpTokensByDefault: boolean } /** @@ -45,7 +46,11 @@ interface TokensListsUpdaterProps { */ const GEOBLOCK_ERRORS_TO_IGNORE = /(failed to fetch)|(load failed)/i -export function TokensListsUpdater({ chainId: currentChainId, isGeoBlockEnabled }: TokensListsUpdaterProps) { +export function TokensListsUpdater({ + chainId: currentChainId, + isGeoBlockEnabled, + enableLpTokensByDefault, +}: TokensListsUpdaterProps) { const { chainId } = useAtomValue(environmentAtom) const setEnvironment = useSetAtom(updateEnvironmentAtom) const allTokensLists = useAtomValue(allListsSourcesAtom) @@ -56,8 +61,8 @@ export function TokensListsUpdater({ chainId: currentChainId, isGeoBlockEnabled const upsertLists = useSetAtom(upsertListsAtom) useEffect(() => { - setEnvironment({ chainId: currentChainId }) - }, [setEnvironment, currentChainId]) + setEnvironment({ chainId: currentChainId, enableLpTokensByDefault }) + }, [setEnvironment, currentChainId, enableLpTokensByDefault]) // Fetch tokens lists once in 6 hours const { data: listsStates, isLoading } = useSWR( From 788c7f71ed04318d8ec4983f6285f1c5c11181dd Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 15:59:23 +0500 Subject: [PATCH 79/90] chore: fix lint --- .../src/modules/yield/updaters/PoolsInfoUpdater/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx index c6a3804798..66455246fe 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -3,13 +3,13 @@ import { useEffect, useMemo } from 'react' import ms from 'ms.macro' +import { TradeType, useTradeTypeInfo } from 'modules/trade' + import { MOCK_POOL_INFO } from './mockPoolInfo' -import { LP_CATEGORY, useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' +import { useLpTokensWithBalances } from '../../hooks/useLpTokensWithBalances' import { usePoolsInfo } from '../../hooks/usePoolsInfo' import { upsertPoolsInfoAtom } from '../../state/poolsInfoAtom' -import { TradeType, useTradeTypeInfo } from '../../../trade' -import { LP_TOKEN_LIST_CATEGORIES, useAllLpTokens } from '@cowprotocol/tokens' const POOL_INFO_CACHE_TIME = ms`1h` From f0c36215c9b589e642b61ea55a9725ffde2d8112 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 17:46:06 +0500 Subject: [PATCH 80/90] feat(yield): define token category by default for selection --- .../CurrencyInputPanel/CurrencyInputPanel.tsx | 10 +++-- .../containers/RescueFundsFromProxy/index.tsx | 6 +-- .../containers/LpTokenListsWidget/index.tsx | 14 +++++-- .../getDefaultTokenListCategories.ts | 22 ++++++++++ .../containers/SelectTokenWidget/index.tsx | 29 +++++++++++-- .../hooks/useOpenTokenSelectWidget.ts | 13 ++++-- .../pure/SelectTokenModal/index.tsx | 3 ++ .../tokensList/state/selectTokenWidgetAtom.ts | 6 ++- .../tokensList/utils/tokensListSorter.ts | 4 ++ .../TradeWidget/TradeWidgetForm.tsx | 19 ++++++++- .../yield/hooks/useLpTokensWithBalances.ts | 41 +++++++++++-------- .../src/modules/yield/shared.ts | 1 + .../yield/updaters/PoolsInfoUpdater/index.tsx | 18 ++++---- libs/tokens/src/types.ts | 1 + 14 files changed, 142 insertions(+), 45 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts diff --git a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx index bbad0b8fd6..8b0050c278 100644 --- a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx +++ b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx @@ -43,7 +43,11 @@ export interface CurrencyInputPanelProps extends Partial { subsidyAndBalance?: BalanceAndSubsidy onCurrencySelection: (field: Field, currency: Currency) => void onUserInput: (field: Field, typedValue: string) => void - openTokenSelectWidget(selectedToken: string | undefined, onCurrencySelection: (currency: Currency) => void): void + openTokenSelectWidget( + selectedToken: string | undefined, + field: Field | undefined, + onCurrencySelection: (currency: Currency) => void, + ): void topLabel?: string } @@ -84,7 +88,7 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) { setTypedValue(typedValue) onUserInput(field, typedValue) }, - [onUserInput, field] + [onUserInput, field], ) const handleMaxInput = useCallback(() => { if (!maxBalance) { @@ -136,7 +140,7 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) { }, [_priceImpactParams, bothCurrenciesSet]) const onTokenSelectClick = useCallback(() => { - openTokenSelectWidget(selectedTokenAddress, (currency) => onCurrencySelection(field, currency)) + openTokenSelectWidget(selectedTokenAddress, field, (currency) => onCurrencySelection(field, currency)) }, [openTokenSelectWidget, selectedTokenAddress, onCurrencySelection, field]) return ( diff --git a/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/index.tsx b/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/index.tsx index a5034eaa04..720a722644 100644 --- a/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/index.tsx +++ b/apps/cowswap-frontend/src/modules/hooksStore/containers/RescueFundsFromProxy/index.tsx @@ -32,7 +32,7 @@ const BALANCE_UPDATE_INTERVAL = ms`5s` const selectedCurrencyAtom = atom(undefined) export function RescueFundsFromProxy({ onDismiss }: { onDismiss: Command }) { - const [selectedCurrency, seSelectedCurrency] = useAtom(selectedCurrencyAtom) + const [selectedCurrency, setSelectedCurrency] = useAtom(selectedCurrencyAtom) const [tokenBalance, setTokenBalance] = useState | null>(null) const selectedTokenAddress = selectedCurrency ? getCurrencyAddress(selectedCurrency) : undefined @@ -81,8 +81,8 @@ export function RescueFundsFromProxy({ onDismiss }: { onDismiss: Command }) { }, [rescueFundsCallback, addTransaction, handleSetError]) const onCurrencySelectClick = useCallback(() => { - onSelectToken(selectedTokenAddress, seSelectedCurrency) - }, [onSelectToken, selectedTokenAddress, seSelectedCurrency]) + onSelectToken(selectedTokenAddress, undefined, undefined, setSelectedCurrency) + }, [onSelectToken, selectedTokenAddress, setSelectedCurrency]) return ( diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 22b81e6309..fc96368bfd 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -2,7 +2,13 @@ import { ReactNode, useMemo } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenWithLogo } from '@cowprotocol/common-const' -import { getTokenSearchFilter, LP_TOKEN_LIST_CATEGORIES, TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { + getTokenSearchFilter, + LP_TOKEN_LIST_CATEGORIES, + LP_TOKEN_LIST_COW_AMM_ONLY, + TokenListCategory, + useAllLpTokens, +} from '@cowprotocol/tokens' import { usePoolsInfo } from 'modules/yield/shared' @@ -15,6 +21,7 @@ interface LpTokenListsProps { account: string | undefined children: ReactNode search: string + disableErc20?: boolean onSelectToken(token: TokenWithLogo): void openPoolPage(poolAddress: string): void tokenListCategoryState: [T, (category: T) => void] @@ -23,7 +30,7 @@ interface LpTokenListsProps { const tabs = [ { title: 'All', value: null }, { title: 'Pool tokens', value: LP_TOKEN_LIST_CATEGORIES }, - { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, + { title: 'CoW AMM only', value: LP_TOKEN_LIST_COW_AMM_ONLY }, ] export function LpTokenListsWidget({ @@ -33,6 +40,7 @@ export function LpTokenListsWidget({ onSelectToken, openPoolPage, tokenListCategoryState, + disableErc20, }: LpTokenListsProps) { const [listsCategories, setListsCategories] = tokenListCategoryState const lpTokens = useAllLpTokens(listsCategories) @@ -50,7 +58,7 @@ export function LpTokenListsWidget({ return ( <> - {tabs.map((tab) => { + {(disableErc20 ? tabs.slice(1) : tabs).map((tab) => { return ( 0 ? LP_TOKEN_LIST_CATEGORIES : null +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx index 4f7ecdcc87..6c221910ab 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' -import { TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { isInjectedWidget } from '@cowprotocol/common-utils' import { ListState, @@ -18,8 +18,13 @@ import { useWalletInfo } from '@cowprotocol/wallet' import styled from 'styled-components/macro' +import { Field } from 'legacy/state/types' + import { addListAnalytics } from 'modules/analytics' import { usePermitCompatibleTokens } from 'modules/permit' +import { useLpTokensWithBalances } from 'modules/yield/shared' + +import { getDefaultTokenListCategories } from './getDefaultTokenListCategories' import { useOnTokenListAddingError } from '../../hooks/useOnTokenListAddingError' import { useSelectTokenWidgetState } from '../../hooks/useSelectTokenWidgetState' @@ -44,10 +49,25 @@ interface SelectTokenWidgetProps { } export function SelectTokenWidget({ displayLpTokenLists }: SelectTokenWidgetProps) { - const { open, onSelectToken, tokenToImport, listToImport, selectedToken, onInputPressEnter, selectedPoolAddress } = - useSelectTokenWidgetState() + const { + open, + onSelectToken, + tokenToImport, + listToImport, + selectedToken, + onInputPressEnter, + selectedPoolAddress, + field, + oppositeToken, + } = useSelectTokenWidgetState() + const { count: lpTokensWithBalancesCount } = useLpTokensWithBalances() + const [isManageWidgetOpen, setIsManageWidgetOpen] = useState(false) - const tokenListCategoryState = useState(null) + const isSellErc20Selected = field === Field.OUTPUT && !(oppositeToken instanceof LpToken) + + const tokenListCategoryState = useState( + getDefaultTokenListCategories(field, oppositeToken, lpTokensWithBalancesCount), + ) const updateSelectTokenWidget = useUpdateSelectTokenWidgetState() const { account } = useWalletInfo() @@ -181,6 +201,7 @@ export function SelectTokenWidget({ displayLpTokenLists }: SelectTokenWidgetProp hideFavoriteTokensTooltip={isInjectedWidgetMode} openPoolPage={openPoolPage} tokenListCategoryState={tokenListCategoryState} + disableErc20={isSellErc20Selected} account={account} /> ) diff --git a/apps/cowswap-frontend/src/modules/tokensList/hooks/useOpenTokenSelectWidget.ts b/apps/cowswap-frontend/src/modules/tokensList/hooks/useOpenTokenSelectWidget.ts index 88ca7efbd8..3ea5263e31 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/hooks/useOpenTokenSelectWidget.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/hooks/useOpenTokenSelectWidget.ts @@ -1,19 +1,26 @@ import { useCallback } from 'react' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { Currency } from '@uniswap/sdk-core' +import { Field } from 'legacy/state/types' + import { useUpdateSelectTokenWidgetState } from './useUpdateSelectTokenWidgetState' export function useOpenTokenSelectWidget(): ( selectedToken: string | undefined, - onSelectToken: (currency: Currency) => void + field: Field | undefined, + oppositeToken: TokenWithLogo | LpToken | Currency | undefined, + onSelectToken: (currency: Currency) => void, ) => void { const updateSelectTokenWidget = useUpdateSelectTokenWidgetState() return useCallback( - (selectedToken, onSelectToken) => { + (selectedToken, field, oppositeToken, onSelectToken) => { updateSelectTokenWidget({ selectedToken, + field, + oppositeToken, open: true, onSelectToken: (currency) => { updateSelectTokenWidget({ open: false }) @@ -21,6 +28,6 @@ export function useOpenTokenSelectWidget(): ( }, }) }, - [updateSelectTokenWidget] + [updateSelectTokenWidget], ) } diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index 26ff5898c5..c4c294dac6 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -26,6 +26,7 @@ export interface SelectTokenModalProps { permitCompatibleTokens: PermitCompatibleTokens hideFavoriteTokensTooltip?: boolean displayLpTokenLists?: boolean + disableErc20?: boolean account: string | undefined tokenListCategoryState: [T, (category: T) => void] @@ -60,6 +61,7 @@ export function SelectTokenModal(props: SelectTokenModalProps) { displayLpTokenLists, openPoolPage, tokenListCategoryState, + disableErc20, } = props const [inputValue, setInputValue] = useState(defaultInputValue) @@ -119,6 +121,7 @@ export function SelectTokenModal(props: SelectTokenModalProps) { search={inputValue} onSelectToken={onSelectToken} openPoolPage={openPoolPage} + disableErc20={disableErc20} tokenListCategoryState={tokenListCategoryState} > {allListsContent} diff --git a/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts b/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts index 5e01b5ab42..3d700fa927 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/state/selectTokenWidgetAtom.ts @@ -1,14 +1,18 @@ import { atom } from 'jotai' -import { TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { atomWithPartialUpdate } from '@cowprotocol/common-utils' import { ListState } from '@cowprotocol/tokens' import { Command } from '@cowprotocol/types' import { Currency } from '@uniswap/sdk-core' +import { Field } from 'legacy/state/types' + export const { atom: selectTokenWidgetAtom, updateAtom: updateSelectTokenWidgetAtom } = atomWithPartialUpdate( atom<{ open: boolean + field?: Field + oppositeToken?: TokenWithLogo | LpToken | Currency selectedToken?: string selectedPoolAddress?: string tokenToImport?: TokenWithLogo diff --git a/apps/cowswap-frontend/src/modules/tokensList/utils/tokensListSorter.ts b/apps/cowswap-frontend/src/modules/tokensList/utils/tokensListSorter.ts index abe65adedd..cc14d3e7d1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/utils/tokensListSorter.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/utils/tokensListSorter.ts @@ -18,6 +18,10 @@ export function tokensListSorter(balances: BalancesState['values']): (a: TokenWi return +bBalance.sub(aBalance) } + if (aBalance && !bBalance) { + return -1 + } + return 0 } } diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 7af3c59f62..434d1b6792 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -5,6 +5,7 @@ import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg' import { isInjectedWidget, maxAmountSpend } from '@cowprotocol/common-utils' import { BannerOrientation, ButtonOutlined, ClosableBanner, InlineBanner, MY_ORDERS_ID } from '@cowprotocol/ui' import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' +import { Currency } from '@uniswap/sdk-core' import { t } from '@lingui/macro' import SVG from 'react-inlinesvg' @@ -31,6 +32,7 @@ import { PoweredFooter } from 'common/pure/PoweredFooter' import * as styledEl from './styled' import { TradeWidgetProps } from './types' +import { Field } from '../../../../legacy/state/types' import { useTradeStateFromUrl } from '../../hooks/setupTradeState/useTradeStateFromUrl' import { useIsWrapOrUnwrap } from '../../hooks/useIsWrapOrUnwrap' import { useTradeTypeInfo } from '../../hooks/useTradeTypeInfo' @@ -138,10 +140,23 @@ export function TradeWidgetForm(props: TradeWidgetProps) { onCurrencySelection, onUserInput, allowsOffchainSigning, - openTokenSelectWidget, tokenSelectorDisabled: alternativeOrderModalVisible, } + const openSellTokenSelect = useCallback( + (selectedToken: string | undefined, field: Field | undefined, onSelectToken: (currency: Currency) => void) => { + openTokenSelectWidget(selectedToken, field, outputCurrencyInfo.currency || undefined, onSelectToken) + }, + [openTokenSelectWidget, outputCurrencyInfo.currency], + ) + + const openBuyTokenSelect = useCallback( + (selectedToken: string | undefined, field: Field | undefined, onSelectToken: (currency: Currency) => void) => { + openTokenSelectWidget(selectedToken, field, inputCurrencyInfo.currency || undefined, onSelectToken) + }, + [openTokenSelectWidget, inputCurrencyInfo.currency], + ) + const toggleAccountModal = useToggleAccountModal() const handleClick = useCallback(() => { @@ -182,6 +197,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { showSetMax={showSetMax} maxBalance={maxBalance} topLabel={isWrapOrUnwrap ? undefined : inputCurrencyInfo.label} + openTokenSelectWidget={openSellTokenSelect} {...currencyInputCommonProps} /> @@ -226,6 +242,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { currencyInfo={outputCurrencyInfo} priceImpactParams={!disablePriceImpact ? priceImpact : undefined} topLabel={isWrapOrUnwrap ? undefined : outputCurrencyInfo.label} + openTokenSelectWidget={openBuyTokenSelect} {...currencyInputCommonProps} />
diff --git a/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts b/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts index 2f3be9e3d7..ad360b0e5f 100644 --- a/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts +++ b/apps/cowswap-frontend/src/modules/yield/hooks/useLpTokensWithBalances.ts @@ -1,35 +1,42 @@ -import { useMemo } from 'react' - import { useTokensBalances } from '@cowprotocol/balances-and-allowances' -import { LpToken } from '@cowprotocol/common-const' +import { LpToken, SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' import { BigNumber } from '@ethersproject/bignumber' +import useSWR from 'swr' + export type LpTokenWithBalance = { token: LpToken balance: BigNumber } export const LP_CATEGORY = [TokenListCategory.LP] +const DEFAULT_STATE = { tokens: {} as Record, count: 0 } + export function useLpTokensWithBalances() { const lpTokens = useAllLpTokens(LP_CATEGORY) const { values: balances } = useTokensBalances() - return useMemo(() => { - if (lpTokens.length === 0) return undefined + return useSWR( + [lpTokens, balances, 'useLpTokensWithBalances'], + ([lpTokens, balances]) => { + if (lpTokens.length === 0) return DEFAULT_STATE - return lpTokens.reduce( - (acc, token) => { - const addressLower = token.address.toLowerCase() - const balance = balances[addressLower] + return lpTokens.reduce( + (acc, token) => { + const addressLower = token.address.toLowerCase() + const balance = balances[addressLower] - if (balance && !balance.isZero()) { - acc[addressLower] = { token, balance } - } + if (balance && !balance.isZero()) { + acc.count++ + acc.tokens[addressLower] = { token, balance } + } - return acc - }, - {} as Record, - ) - }, [lpTokens, balances]) + return acc + }, + { ...DEFAULT_STATE }, + ) + }, + { ...SWR_NO_REFRESH_OPTIONS, fallbackData: DEFAULT_STATE }, + ).data } diff --git a/apps/cowswap-frontend/src/modules/yield/shared.ts b/apps/cowswap-frontend/src/modules/yield/shared.ts index 24f46a452d..3ed6eae2de 100644 --- a/apps/cowswap-frontend/src/modules/yield/shared.ts +++ b/apps/cowswap-frontend/src/modules/yield/shared.ts @@ -1,3 +1,4 @@ export { PoolsInfoUpdater } from './updaters/PoolsInfoUpdater' export { usePoolsInfo } from './hooks/usePoolsInfo' +export { useLpTokensWithBalances } from './hooks/useLpTokensWithBalances' export type { PoolInfo, PoolInfoStates } from './state/poolsInfoAtom' diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx index 66455246fe..5b9561e3b3 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/PoolsInfoUpdater/index.tsx @@ -28,24 +28,22 @@ export function PoolsInfoUpdater() { const tradeTypeInfo = useTradeTypeInfo() const isYield = tradeTypeInfo?.tradeType === TradeType.YIELD - const lpTokensWithBalances = useLpTokensWithBalances() + const { tokens: lpTokensWithBalances } = useLpTokensWithBalances() const tokensToUpdate = useMemo(() => { - return lpTokensWithBalances - ? Object.keys(lpTokensWithBalances).filter((address) => { - const state = poolsInfo?.[address] + return Object.keys(lpTokensWithBalances).filter((address) => { + const state = poolsInfo?.[address] - if (!state) return true + if (!state) return true - return state.updatedAt + POOL_INFO_CACHE_TIME > Date.now() - }) - : null + return state.updatedAt + POOL_INFO_CACHE_TIME > Date.now() + }) }, [lpTokensWithBalances, poolsInfo]) - const tokensKey = useMemo(() => tokensToUpdate?.join(','), [tokensToUpdate]) + const tokensKey = useMemo(() => tokensToUpdate.join(','), [tokensToUpdate]) useEffect(() => { - if ((tokensToUpdate && tokensToUpdate.length > 0) || isYield) { + if (tokensToUpdate.length > 0 || isYield) { fetchPoolsInfo(isYield ? null : tokensToUpdate).then(upsertPoolsInfo) } }, [isYield, tokensKey]) diff --git a/libs/tokens/src/types.ts b/libs/tokens/src/types.ts index 8cbad348c5..b07e183a51 100644 --- a/libs/tokens/src/types.ts +++ b/libs/tokens/src/types.ts @@ -9,6 +9,7 @@ export enum TokenListCategory { } export const LP_TOKEN_LIST_CATEGORIES = [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] +export const LP_TOKEN_LIST_COW_AMM_ONLY = [TokenListCategory.COW_AMM_LP] export type ListSourceConfig = { widgetAppCode?: string From 930253bab0c86b8216eb26d4d8289c5e94fa0ffc Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 17:47:47 +0500 Subject: [PATCH 81/90] chore: fix lint --- .../modules/trade/containers/TradeWidget/TradeWidgetForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 434d1b6792..0aedc3e08e 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -12,6 +12,7 @@ import SVG from 'react-inlinesvg' import { AccountElement } from 'legacy/components/Header/AccountElement' import { upToLarge, useMediaQuery } from 'legacy/hooks/useMediaQuery' +import { Field } from 'legacy/state/types' import { useToggleAccountModal } from 'modules/account' import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders/hooks/useAdvancedOrdersDerivedState' @@ -32,7 +33,6 @@ import { PoweredFooter } from 'common/pure/PoweredFooter' import * as styledEl from './styled' import { TradeWidgetProps } from './types' -import { Field } from '../../../../legacy/state/types' import { useTradeStateFromUrl } from '../../hooks/setupTradeState/useTradeStateFromUrl' import { useIsWrapOrUnwrap } from '../../hooks/useIsWrapOrUnwrap' import { useTradeTypeInfo } from '../../hooks/useTradeTypeInfo' From a0bab7bc2da67cde2bb91e3e8663014657386759 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 18:33:50 +0500 Subject: [PATCH 82/90] feat: add isCoWAMM indicator to LpToken --- .../getDefaultTokenListCategories.ts | 16 ++- libs/common-const/src/types.ts | 4 +- .../tokens/src/hooks/tokens/useAllLpTokens.ts | 33 ++--- libs/tokens/src/state/tokens/allTokensAtom.ts | 124 +++++++++++++----- .../src/utils/tokenMapToListWithLogo.ts | 14 +- 5 files changed, 124 insertions(+), 67 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts index df355e5638..ab40ec454f 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts @@ -11,9 +11,19 @@ export function getDefaultTokenListCategories( ): TokenListCategory[] | null { // When select buy token if (field === Field.OUTPUT) { - // If sell token is LP token, then propose COW AMM pools by default as buy token - // If sell token is ERC-20 token, then propose all LP tokens by default as buy token - return oppositeToken instanceof LpToken ? LP_TOKEN_LIST_COW_AMM_ONLY : LP_TOKEN_LIST_CATEGORIES + // If sell token is LP token + if (oppositeToken instanceof LpToken) { + // And sell token is COW AMM LP token, propose all LP tokens by default as buy token + if (oppositeToken.isCowAmm) { + return LP_TOKEN_LIST_CATEGORIES + } else { + // And sell token is not COW AMM LP token, propose COW AMM LP tokens by default as buy token + return LP_TOKEN_LIST_COW_AMM_ONLY + } + } else { + // And sell token is not LP token, propose all LP tokens by default as buy token + return LP_TOKEN_LIST_COW_AMM_ONLY + } } // When select sell token diff --git a/libs/common-const/src/types.ts b/libs/common-const/src/types.ts index ed3d668c4a..60e82423ac 100644 --- a/libs/common-const/src/types.ts +++ b/libs/common-const/src/types.ts @@ -22,9 +22,10 @@ export class TokenWithLogo extends Token { } export class LpToken extends TokenWithLogo { - static fromToken(token: Token | TokenInfo): LpToken { + static fromTokenToLp(token: Token | TokenInfo, isCowAmm: boolean): LpToken { return new LpToken( token instanceof Token ? emptyTokens : token.tokens || emptyTokens, + isCowAmm, token.chainId, token.address, token.decimals, @@ -35,6 +36,7 @@ export class LpToken extends TokenWithLogo { constructor( public tokens: string[], + public isCowAmm: boolean, chainId: number, address: string, decimals: number, diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts index 68d94a767e..938ca1f6c3 100644 --- a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -4,39 +4,24 @@ import { LpToken, SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import useSWR from 'swr' -import { environmentAtom } from '../../state/environmentAtom' -import { listsStatesListAtom } from '../../state/tokenLists/tokenListsStateAtom' -import { TokenListCategory, TokensMap } from '../../types' -import { parseTokenInfo } from '../../utils/parseTokenInfo' -import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' +import { lpTokensByCategoryAtom } from '../../state/tokens/allTokensAtom' +import { TokenListCategory } from '../../types' const fallbackData: LpToken[] = [] export function useAllLpTokens(categories: TokenListCategory[] | null): LpToken[] { - const { chainId } = useAtomValue(environmentAtom) - const state = useAtomValue(listsStatesListAtom) + const lpTokensByCategory = useAtomValue(lpTokensByCategoryAtom) return useSWR( - categories ? [state, chainId, categories] : null, - ([state, chainId, categories]) => { - const tokensMap = state.reduce((acc, list) => { - if (!list.category || !categories.includes(list.category)) { - return acc + categories ? [lpTokensByCategory, categories] : null, + ([lpTokensByCategory, categories]) => { + return categories.reduce((acc, category) => { + if (category === TokenListCategory.LP || category === TokenListCategory.COW_AMM_LP) { + acc.push(...lpTokensByCategory[category]) } - list.list.tokens.forEach((token) => { - const tokenInfo = parseTokenInfo(chainId, token) - const tokenAddressKey = tokenInfo?.address.toLowerCase() - - if (!tokenInfo || !tokenAddressKey) return - - acc[tokenAddressKey] = tokenInfo - }) - return acc - }, {}) - - return tokenMapToListWithLogo(tokensMap, chainId) as LpToken[] + }, []) }, { ...SWR_NO_REFRESH_OPTIONS, fallbackData }, ).data diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index c243d4e762..ead0e86e21 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -1,12 +1,12 @@ import { atom } from 'jotai' -import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' +import { LpToken, NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' import { TokenInfo } from '@cowprotocol/types' import { favoriteTokensAtom } from './favoriteTokensAtom' import { userAddedTokensAtom } from './userAddedTokensAtom' -import { LP_TOKEN_LIST_CATEGORIES, TokensMap } from '../../types' +import { LP_TOKEN_LIST_CATEGORIES, TokenListCategory, TokensMap } from '../../types' import { lowerCaseTokensMap } from '../../utils/lowerCaseTokensMap' import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' @@ -21,12 +21,25 @@ export interface TokensBySymbol { [address: string]: TokenWithLogo[] } -export interface TokensState { - activeTokens: TokensMap - inactiveTokens: TokensMap +type TokenCategoryByMap = Record + +type LpTokensByCategory = { + [TokenListCategory.LP]: LpToken[] + [TokenListCategory.COW_AMM_LP]: LpToken[] +} + +interface TokensState { + activeTokens: TokenCategoryByMap + inactiveTokens: TokenCategoryByMap +} + +const DEFAULT_TOKEN_CATEGORY_BY_MAP = { + [TokenListCategory.ERC20]: {}, + [TokenListCategory.LP]: {}, + [TokenListCategory.COW_AMM_LP]: {}, } -export const tokensStateAtom = atom((get) => { +const tokensStateAtom = atom((get) => { const { chainId } = get(environmentAtom) const listsStatesList = get(listsStatesListAtom) const listsEnabledState = get(listsEnabledStateAtom) @@ -41,43 +54,62 @@ export const tokensStateAtom = atom((get) => { if (!tokenInfo || !tokenAddressKey) return + const category = list.category || TokenListCategory.ERC20 + if (isListEnabled) { - if (!acc.activeTokens[tokenAddressKey]) { - acc.activeTokens[tokenAddressKey] = tokenInfo + if (!acc.activeTokens[category][tokenAddressKey]) { + acc.activeTokens[category][tokenAddressKey] = tokenInfo } } else { - if (!acc.inactiveTokens[tokenAddressKey]) { - acc.inactiveTokens[tokenAddressKey] = tokenInfo + if (!acc.inactiveTokens[category][tokenAddressKey]) { + acc.inactiveTokens[category][tokenAddressKey] = tokenInfo } } }) return acc }, - { activeTokens: {}, inactiveTokens: {} }, + { activeTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP }, inactiveTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP } }, ) }) -export const lpTokensMapAtom = atom((get) => { +const lpTokensMapAtom = atom((get) => { const { chainId } = get(environmentAtom) const listsStatesList = get(listsStatesListAtom) - return listsStatesList.reduce((acc, list) => { - if (!list.category || !LP_TOKEN_LIST_CATEGORIES.includes(list.category)) { - return acc - } + return listsStatesList.reduce( + (acc, list) => { + const category = !list.category || !LP_TOKEN_LIST_CATEGORIES.includes(list.category) ? undefined : list.category - list.list.tokens.forEach((token) => { - const tokenInfo = parseTokenInfo(chainId, token) - const tokenAddressKey = tokenInfo?.address.toLowerCase() + if (!category) { + return acc + } - if (!tokenInfo || !tokenAddressKey) return + list.list.tokens.forEach((token) => { + const tokenInfo = parseTokenInfo(chainId, token) + const tokenAddressKey = tokenInfo?.address.toLowerCase() - acc[tokenAddressKey] = tokenInfo - }) + if (!tokenInfo || !tokenAddressKey) return - return acc - }, {}) + acc[category][tokenAddressKey] = tokenInfo + }) + + return acc + }, + { [TokenListCategory.COW_AMM_LP]: {}, [TokenListCategory.LP]: {} } as TokenCategoryByMap, + ) +}) + +export const lpTokensByCategoryAtom = atom((get) => { + const { chainId } = get(environmentAtom) + const lpTokensMap = get(lpTokensMapAtom) + const getTokensByCategory = (category: TokenListCategory) => + tokenMapToListWithLogo(lpTokensMap[category], category, chainId) as LpToken[] + + return { + [TokenListCategory.LP]: getTokensByCategory(TokenListCategory.LP), + [TokenListCategory.COW_AMM_LP]: getTokensByCategory(TokenListCategory.COW_AMM_LP), + } }) /** @@ -91,26 +123,46 @@ export const activeTokensAtom = atom((get) => { const favoriteTokensState = get(favoriteTokensAtom) const tokensMap = get(tokensStateAtom) - const lpTokensMap = get(lpTokensMapAtom) + const lpTokensByCategory = get(lpTokensByCategoryAtom) const nativeToken = NATIVE_CURRENCIES[chainId] - return tokenMapToListWithLogo( - { - [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, - ...tokensMap.activeTokens, - ...(enableLpTokensByDefault ? lpTokensMap : null), - ...lowerCaseTokensMap(userAddedTokens[chainId]), - ...lowerCaseTokensMap(favoriteTokensState[chainId]), - }, - chainId, - ) + return [ + // Native, user added and favorite tokens + ...tokenMapToListWithLogo( + { + [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, + ...lowerCaseTokensMap(userAddedTokens[chainId]), + ...lowerCaseTokensMap(favoriteTokensState[chainId]), + }, + TokenListCategory.ERC20, + chainId, + ), + // Tokens from active lists + ...Object.keys(tokensMap.activeTokens).reduce((acc, _category) => { + const category = _category as TokenListCategory + const categoryMap = tokensMap.activeTokens[category] + + acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) + return acc + }, []), + // LP tokens + ...(enableLpTokensByDefault + ? lpTokensByCategory[TokenListCategory.LP].concat(lpTokensByCategory[TokenListCategory.COW_AMM_LP]) + : []), + ] }) export const inactiveTokensAtom = atom((get) => { const { chainId } = get(environmentAtom) const tokensMap = get(tokensStateAtom) - return tokenMapToListWithLogo(tokensMap.inactiveTokens, chainId) + return Object.keys(tokensMap.inactiveTokens).reduce((acc, _category) => { + const category = _category as TokenListCategory + const categoryMap = tokensMap.inactiveTokens[category] + + acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) + return acc + }, []) }) export const tokensByAddressAtom = atom((get) => { diff --git a/libs/tokens/src/utils/tokenMapToListWithLogo.ts b/libs/tokens/src/utils/tokenMapToListWithLogo.ts index d66c8a1d73..7675d0c080 100644 --- a/libs/tokens/src/utils/tokenMapToListWithLogo.ts +++ b/libs/tokens/src/utils/tokenMapToListWithLogo.ts @@ -1,13 +1,21 @@ import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' -import { TokensMap } from '../types' +import { TokenListCategory, TokensMap } from '../types' /** * Convert a tokens map to a list of tokens and sort them alphabetically */ -export function tokenMapToListWithLogo(tokenMap: TokensMap, chainId: number): TokenWithLogo[] { +export function tokenMapToListWithLogo( + tokenMap: TokensMap, + category: TokenListCategory, + chainId: number, +): TokenWithLogo[] { + const isCoWAMM = category === TokenListCategory.COW_AMM_LP + return Object.values(tokenMap) .filter((token) => token.chainId === chainId) .sort((a, b) => a.symbol.localeCompare(b.symbol)) - .map((token) => (token.tokens ? LpToken.fromToken(token) : TokenWithLogo.fromToken(token, token.logoURI))) + .map((token) => + token.tokens ? LpToken.fromTokenToLp(token, isCoWAMM) : TokenWithLogo.fromToken(token, token.logoURI), + ) } From 9c5d5b57b74534a022a0323f460ccef9ba73724e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 20:15:38 +0500 Subject: [PATCH 83/90] refactor: fix lp tokens segregation --- .../updaters/orders/OrdersFromApiUpdater.ts | 12 +- .../containers/SelectTokenWidget/index.tsx | 4 +- .../updaters/BalancesAndAllowancesUpdater.tsx | 4 +- ...{useAllTokens.ts => useAllActiveTokens.ts} | 3 +- .../tokens/src/hooks/tokens/useAllLpTokens.ts | 23 +-- libs/tokens/src/index.ts | 2 +- libs/tokens/src/state/tokens/allTokensAtom.ts | 133 +++++------------- .../src/utils/tokenMapToListWithLogo.ts | 14 +- libs/types/src/common.ts | 2 + 9 files changed, 70 insertions(+), 127 deletions(-) rename libs/tokens/src/hooks/tokens/{useAllTokens.ts => useAllActiveTokens.ts} (78%) diff --git a/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts b/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts index 3d0a497c21..94bdd650f5 100644 --- a/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts +++ b/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' import { EnrichedOrder, EthflowData, OrderClass, SupportedChainId as ChainId } from '@cowprotocol/cow-sdk' -import { TokensByAddress, useAllTokens } from '@cowprotocol/tokens' +import { TokensByAddress, useAllActiveTokens } from '@cowprotocol/tokens' import { useIsSafeWallet, useWalletInfo } from '@cowprotocol/wallet' import { Order, OrderStatus } from 'legacy/state/orders/actions' @@ -32,7 +32,7 @@ const statusMapping: Record = { function _transformOrderBookOrderToStoreOrder( order: EnrichedOrder, chainId: ChainId, - allTokens: TokensByAddress + allTokens: TokensByAddress, ): Order | undefined { const { uid: id, @@ -63,7 +63,7 @@ function _transformOrderBookOrderToStoreOrder( console.warn( `OrdersFromApiUpdater::Tokens not found for order ${id}: sellToken ${ !inputToken ? sellToken : 'found' - } - buyToken ${!outputToken ? buyToken : 'found'}` + } - buyToken ${!outputToken ? buyToken : 'found'}`, ) return } @@ -110,7 +110,7 @@ function _getInputToken( isEthFlow: boolean, chainId: ChainId, sellToken: string, - allTokens: TokensByAddress + allTokens: TokensByAddress, ): ReturnType { return isEthFlow ? NATIVE_CURRENCIES[chainId] : getTokenFromMapping(sellToken, chainId, allTokens) } @@ -141,7 +141,7 @@ export function OrdersFromApiUpdater(): null { const clearOrderStorage = useClearOrdersStorage() const { account, chainId } = useWalletInfo() - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const tokensAreLoaded = useMemo(() => Object.keys(allTokens).length > 0, [allTokens]) const addOrUpdateOrders = useAddOrUpdateOrders() const updateApiOrders = useSetAtom(apiOrdersAtom) @@ -176,7 +176,7 @@ export function OrdersFromApiUpdater(): null { console.error(`OrdersFromApiUpdater::Failed to fetch orders`, e) } }, - [addOrUpdateOrders, ordersFromOrderBook, getTokensForOrdersList, isSafeWallet] + [addOrUpdateOrders, ordersFromOrderBook, getTokensForOrdersList, isSafeWallet], ) useEffect(() => { diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx index 6c221910ab..5d4edd86c9 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx @@ -9,7 +9,7 @@ import { useAddList, useAddUserToken, useAllListsList, - useAllTokens, + useAllActiveTokens, useFavoriteTokens, useUnsupportedTokens, useUserAddedTokens, @@ -75,7 +75,7 @@ export function SelectTokenWidget({ displayLpTokenLists }: SelectTokenWidgetProp const addCustomTokenLists = useAddList((source) => addListAnalytics('Success', source)) const importTokenCallback = useAddUserToken() - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const favoriteTokens = useFavoriteTokens() const userAddedTokens = useUserAddedTokens() const allTokenLists = useAllListsList() diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 422502970d..35d39d8006 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react' import { LpToken, NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { useAllTokens } from '@cowprotocol/tokens' +import { useAllActiveTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -24,7 +24,7 @@ export interface BalancesAndAllowancesUpdaterProps { export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAllowancesUpdaterProps) { const setBalances = useSetAtom(balancesAtom) - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account) const tokenAddresses = useMemo( diff --git a/libs/tokens/src/hooks/tokens/useAllTokens.ts b/libs/tokens/src/hooks/tokens/useAllActiveTokens.ts similarity index 78% rename from libs/tokens/src/hooks/tokens/useAllTokens.ts rename to libs/tokens/src/hooks/tokens/useAllActiveTokens.ts index 958701c96a..1e75125d32 100644 --- a/libs/tokens/src/hooks/tokens/useAllTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllActiveTokens.ts @@ -4,7 +4,6 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { activeTokensAtom } from '../../state/tokens/allTokensAtom' - -export function useAllTokens(): TokenWithLogo[] { +export function useAllActiveTokens(): TokenWithLogo[] { return useAtomValue(activeTokensAtom) } diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts index 938ca1f6c3..badabbd9f8 100644 --- a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -1,27 +1,28 @@ -import { useAtomValue } from 'jotai' +import { useAtomValue } from 'jotai/index' import { LpToken, SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import useSWR from 'swr' -import { lpTokensByCategoryAtom } from '../../state/tokens/allTokensAtom' +import { activeTokensAtom, inactiveTokensAtom } from '../../state/tokens/allTokensAtom' import { TokenListCategory } from '../../types' const fallbackData: LpToken[] = [] export function useAllLpTokens(categories: TokenListCategory[] | null): LpToken[] { - const lpTokensByCategory = useAtomValue(lpTokensByCategoryAtom) + const activeTokens = useAtomValue(activeTokensAtom) + const inactiveTokens = useAtomValue(inactiveTokensAtom) return useSWR( - categories ? [lpTokensByCategory, categories] : null, - ([lpTokensByCategory, categories]) => { - return categories.reduce((acc, category) => { - if (category === TokenListCategory.LP || category === TokenListCategory.COW_AMM_LP) { - acc.push(...lpTokensByCategory[category]) - } + categories ? [activeTokens, inactiveTokens, categories] : null, + ([activeTokens, inactiveTokens, categories]) => { + const allTokens = [...activeTokens, ...inactiveTokens] + const selectOnlyCoWAmm = categories?.length === 1 && categories.includes(TokenListCategory.COW_AMM_LP) - return acc - }, []) + return allTokens.filter((token) => { + const isLp = token instanceof LpToken + return isLp ? (selectOnlyCoWAmm ? token.isCowAmm : true) : false + }) as LpToken[] }, { ...SWR_NO_REFRESH_OPTIONS, fallbackData }, ).data diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index 6100e61f21..825141feff 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -16,7 +16,7 @@ export type { TokenSearchResponse } from './hooks/tokens/useSearchToken' // Hooks export { useAllListsList } from './hooks/lists/useAllListsList' export { useAddList } from './hooks/lists/useAddList' -export { useAllTokens } from './hooks/tokens/useAllTokens' +export { useAllActiveTokens } from './hooks/tokens/useAllActiveTokens' export { useVirtualLists } from './hooks/lists/useVirtualLists' export { useFavoriteTokens } from './hooks/tokens/favorite/useFavoriteTokens' export { useUserAddedTokens } from './hooks/tokens/userAdded/useUserAddedTokens' diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index ead0e86e21..d122bc6f30 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -1,12 +1,12 @@ import { atom } from 'jotai' -import { LpToken, NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' +import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' import { TokenInfo } from '@cowprotocol/types' import { favoriteTokensAtom } from './favoriteTokensAtom' import { userAddedTokensAtom } from './userAddedTokensAtom' -import { LP_TOKEN_LIST_CATEGORIES, TokenListCategory, TokensMap } from '../../types' +import { TokenListCategory, TokensMap } from '../../types' import { lowerCaseTokensMap } from '../../utils/lowerCaseTokensMap' import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' @@ -21,22 +21,9 @@ export interface TokensBySymbol { [address: string]: TokenWithLogo[] } -type TokenCategoryByMap = Record - -type LpTokensByCategory = { - [TokenListCategory.LP]: LpToken[] - [TokenListCategory.COW_AMM_LP]: LpToken[] -} - interface TokensState { - activeTokens: TokenCategoryByMap - inactiveTokens: TokenCategoryByMap -} - -const DEFAULT_TOKEN_CATEGORY_BY_MAP = { - [TokenListCategory.ERC20]: {}, - [TokenListCategory.LP]: {}, - [TokenListCategory.COW_AMM_LP]: {}, + activeTokens: TokensMap + inactiveTokens: TokensMap } const tokensStateAtom = atom((get) => { @@ -49,69 +36,38 @@ const tokensStateAtom = atom((get) => { const isListEnabled = listsEnabledState[list.source] list.list.tokens.forEach((token) => { + const category = list.category || TokenListCategory.ERC20 const tokenInfo = parseTokenInfo(chainId, token) const tokenAddressKey = tokenInfo?.address.toLowerCase() if (!tokenInfo || !tokenAddressKey) return - const category = list.category || TokenListCategory.ERC20 + if (category === TokenListCategory.LP) { + tokenInfo.isLpToken = true + } + + if (category === TokenListCategory.COW_AMM_LP) { + tokenInfo.isLpToken = true + tokenInfo.isCoWAmmToken = true + } if (isListEnabled) { - if (!acc.activeTokens[category][tokenAddressKey]) { - acc.activeTokens[category][tokenAddressKey] = tokenInfo + if (!acc.activeTokens[tokenAddressKey]) { + acc.activeTokens[tokenAddressKey] = tokenInfo } } else { - if (!acc.inactiveTokens[category][tokenAddressKey]) { - acc.inactiveTokens[category][tokenAddressKey] = tokenInfo + if (!acc.inactiveTokens[tokenAddressKey]) { + acc.inactiveTokens[tokenAddressKey] = tokenInfo } } }) return acc }, - { activeTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP }, inactiveTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP } }, + { activeTokens: {}, inactiveTokens: {} }, ) }) -const lpTokensMapAtom = atom((get) => { - const { chainId } = get(environmentAtom) - const listsStatesList = get(listsStatesListAtom) - - return listsStatesList.reduce( - (acc, list) => { - const category = !list.category || !LP_TOKEN_LIST_CATEGORIES.includes(list.category) ? undefined : list.category - - if (!category) { - return acc - } - - list.list.tokens.forEach((token) => { - const tokenInfo = parseTokenInfo(chainId, token) - const tokenAddressKey = tokenInfo?.address.toLowerCase() - - if (!tokenInfo || !tokenAddressKey) return - - acc[category][tokenAddressKey] = tokenInfo - }) - - return acc - }, - { [TokenListCategory.COW_AMM_LP]: {}, [TokenListCategory.LP]: {} } as TokenCategoryByMap, - ) -}) - -export const lpTokensByCategoryAtom = atom((get) => { - const { chainId } = get(environmentAtom) - const lpTokensMap = get(lpTokensMapAtom) - const getTokensByCategory = (category: TokenListCategory) => - tokenMapToListWithLogo(lpTokensMap[category], category, chainId) as LpToken[] - - return { - [TokenListCategory.LP]: getTokensByCategory(TokenListCategory.LP), - [TokenListCategory.COW_AMM_LP]: getTokensByCategory(TokenListCategory.COW_AMM_LP), - } -}) - /** * Returns a list of tokens that are active and sorted alphabetically * The list includes: native token, user added tokens, favorite tokens and tokens from active lists @@ -123,46 +79,35 @@ export const activeTokensAtom = atom((get) => { const favoriteTokensState = get(favoriteTokensAtom) const tokensMap = get(tokensStateAtom) - const lpTokensByCategory = get(lpTokensByCategoryAtom) const nativeToken = NATIVE_CURRENCIES[chainId] - return [ - // Native, user added and favorite tokens - ...tokenMapToListWithLogo( - { - [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, - ...lowerCaseTokensMap(userAddedTokens[chainId]), - ...lowerCaseTokensMap(favoriteTokensState[chainId]), - }, - TokenListCategory.ERC20, - chainId, - ), - // Tokens from active lists - ...Object.keys(tokensMap.activeTokens).reduce((acc, _category) => { - const category = _category as TokenListCategory - const categoryMap = tokensMap.activeTokens[category] - - acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) - return acc - }, []), - // LP tokens - ...(enableLpTokensByDefault - ? lpTokensByCategory[TokenListCategory.LP].concat(lpTokensByCategory[TokenListCategory.COW_AMM_LP]) - : []), - ] + return tokenMapToListWithLogo( + { + [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, + ...tokensMap.activeTokens, + ...lowerCaseTokensMap(userAddedTokens[chainId]), + ...lowerCaseTokensMap(favoriteTokensState[chainId]), + ...(enableLpTokensByDefault + ? Object.keys(tokensMap.inactiveTokens).reduce((acc, key) => { + const token = tokensMap.inactiveTokens[key] + + if (token.isLpToken) { + acc[key] = token + } + + return acc + }, {}) + : null), + }, + chainId, + ) }) export const inactiveTokensAtom = atom((get) => { const { chainId } = get(environmentAtom) const tokensMap = get(tokensStateAtom) - return Object.keys(tokensMap.inactiveTokens).reduce((acc, _category) => { - const category = _category as TokenListCategory - const categoryMap = tokensMap.inactiveTokens[category] - - acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) - return acc - }, []) + return tokenMapToListWithLogo(tokensMap.inactiveTokens, chainId) }) export const tokensByAddressAtom = atom((get) => { diff --git a/libs/tokens/src/utils/tokenMapToListWithLogo.ts b/libs/tokens/src/utils/tokenMapToListWithLogo.ts index 7675d0c080..b3c7e77081 100644 --- a/libs/tokens/src/utils/tokenMapToListWithLogo.ts +++ b/libs/tokens/src/utils/tokenMapToListWithLogo.ts @@ -1,21 +1,17 @@ import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' -import { TokenListCategory, TokensMap } from '../types' +import { TokensMap } from '../types' /** * Convert a tokens map to a list of tokens and sort them alphabetically */ -export function tokenMapToListWithLogo( - tokenMap: TokensMap, - category: TokenListCategory, - chainId: number, -): TokenWithLogo[] { - const isCoWAMM = category === TokenListCategory.COW_AMM_LP - +export function tokenMapToListWithLogo(tokenMap: TokensMap, chainId: number): TokenWithLogo[] { return Object.values(tokenMap) .filter((token) => token.chainId === chainId) .sort((a, b) => a.symbol.localeCompare(b.symbol)) .map((token) => - token.tokens ? LpToken.fromTokenToLp(token, isCoWAMM) : TokenWithLogo.fromToken(token, token.logoURI), + token.isLpToken + ? LpToken.fromTokenToLp(token, !!token.isCoWAmmToken) + : TokenWithLogo.fromToken(token, token.logoURI), ) } diff --git a/libs/types/src/common.ts b/libs/types/src/common.ts index 0228b65166..1b3e2ae88b 100644 --- a/libs/types/src/common.ts +++ b/libs/types/src/common.ts @@ -24,4 +24,6 @@ export type TokenInfo = { symbol: string logoURI?: string tokens?: string[] + isLpToken?: boolean + isCoWAmmToken?: boolean } From 1149c36aca7c6326f0b73d30141bb2ea34c0d79c Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 23 Oct 2024 11:28:44 +0500 Subject: [PATCH 84/90] chore: fix apr default value --- .../src/modules/tokensList/pure/LpTokenLists/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 6096abb5ef..b6e6c970e9 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -79,7 +79,7 @@ export function LpTokenLists({
{balanceAmount ? : account ? LoadingElement : null} - {info?.apy ? `${info.apy}%` : '-'} + {info?.apy ? `${info.apy}%` : ''} From 8048049bfc98dfcf6b47eeeeaceb256488920c93 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 23 Oct 2024 11:40:55 +0500 Subject: [PATCH 85/90] chore: remove lp tokens duplicates --- libs/tokens/src/hooks/tokens/useAllLpTokens.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts index badabbd9f8..2e53911261 100644 --- a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -16,7 +16,12 @@ export function useAllLpTokens(categories: TokenListCategory[] | null): LpToken[ return useSWR( categories ? [activeTokens, inactiveTokens, categories] : null, ([activeTokens, inactiveTokens, categories]) => { - const allTokens = [...activeTokens, ...inactiveTokens] + const activeTokensMap = activeTokens.reduce>((acc, token) => { + acc[token.address] = true + return acc + }, {}) + + const allTokens = [...activeTokens, ...inactiveTokens.filter((token) => !activeTokensMap[token.address])] const selectOnlyCoWAmm = categories?.length === 1 && categories.includes(TokenListCategory.COW_AMM_LP) return allTokens.filter((token) => { From 0c6f52c158256f7bbfe4ab994f7a801bd76a6f96 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 23 Oct 2024 11:57:33 +0500 Subject: [PATCH 86/90] fix: set lp tokens to sell wth cowamm is set as buy --- .../SelectTokenWidget/getDefaultTokenListCategories.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts index ab40ec454f..6cea4a365f 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/getDefaultTokenListCategories.ts @@ -9,10 +9,12 @@ export function getDefaultTokenListCategories( oppositeToken: Currency | LpToken | undefined, lpTokensWithBalancesCount: number, ): TokenListCategory[] | null { + const isOppositeLp = oppositeToken instanceof LpToken + // When select buy token if (field === Field.OUTPUT) { // If sell token is LP token - if (oppositeToken instanceof LpToken) { + if (isOppositeLp) { // And sell token is COW AMM LP token, propose all LP tokens by default as buy token if (oppositeToken.isCowAmm) { return LP_TOKEN_LIST_CATEGORIES @@ -26,6 +28,10 @@ export function getDefaultTokenListCategories( } } + if (isOppositeLp && oppositeToken.isCowAmm) { + return LP_TOKEN_LIST_CATEGORIES + } + // When select sell token // If there are LP tokens with balances, propose LP tokens by default as sell token return lpTokensWithBalancesCount > 0 ? LP_TOKEN_LIST_CATEGORIES : null From 98597c2b6b360d0519bfc26adce6895bee5a3c0a Mon Sep 17 00:00:00 2001 From: fairlight <31534717+fairlighteth@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:26:24 +0100 Subject: [PATCH 87/90] feat: modify pool token list layout (#5014) * feat: modify pool token list layout * feat: apply mobile widgetlinks menu * feat: add padding to pool token balance --------- Co-authored-by: Alexandr Kazachenko --- .../containers/LpTokenListsWidget/index.tsx | 45 ++++-- .../containers/LpTokenListsWidget/styled.tsx | 29 ++-- .../tokensList/pure/LpTokenLists/index.tsx | 126 ++++++++++++---- .../tokensList/pure/LpTokenLists/styled.ts | 140 +++++++++++++++--- .../pure/SelectTokenModal/styled.ts | 2 +- .../TradeWidget/TradeWidgetForm.tsx | 5 +- .../containers/TradeWidgetLinks/styled.ts | 10 +- libs/ui/src/enum.ts | 1 + libs/ui/src/pure/InfoTooltip/index.tsx | 28 ++-- libs/ui/src/theme/ThemeColorVars.tsx | 1 + 10 files changed, 293 insertions(+), 94 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx index 3d0a99c4b1..830fa46f17 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/LpTokenListsWidget/index.tsx @@ -2,6 +2,7 @@ import { ReactNode, useState } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenListCategory, useAllLpTokens, useTokensByAddressMap } from '@cowprotocol/tokens' +import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' import { TabButton, TabsContainer } from './styled' @@ -12,9 +13,24 @@ interface LpTokenListsProps { } const tabs = [ - { title: 'All', value: null }, - { title: 'Pool tokens', value: [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] }, - { title: 'CoW AMM only', value: [TokenListCategory.COW_AMM_LP] }, + { id: 'all', title: 'All', value: null }, + { id: 'pool', title: 'Pool tokens', value: [TokenListCategory.LP, TokenListCategory.COW_AMM_LP] }, + { + id: 'cow-amm', + title: ( + <> + {' '} + CoW AMM only + + ), + value: [TokenListCategory.COW_AMM_LP], + }, ] export function LpTokenListsWidget({ children }: LpTokenListsProps) { @@ -26,20 +42,21 @@ export function LpTokenListsWidget({ children }: LpTokenListsProps) { return ( <> - {tabs.map((tab) => { - return ( - setListsCategories(tab.value)} - > - {tab.title} - - ) - })} + {tabs.map((tab) => ( + setListsCategories(tab.value)}> + {tab.title} + + ))} {listsCategories === null ? ( children + ) : lpTokens.length === 0 ? ( + ) : ( ` +export const TabButton = styled.button<{ active$: boolean; isCowAmm?: boolean }>` cursor: pointer; border: 1px solid var(${UI.COLOR_BACKGROUND}); outline: none; padding: 8px 16px; border-radius: 32px; - font-size: 13px; - font-weight: 600; - color: var(${UI.COLOR_TEXT}); - background: ${({ active$ }) => active$ ? `var(${UI.COLOR_BACKGROUND})` : 'transparent'}; + font-size: 14px; + font-weight: ${({ active$ }) => (active$ ? '600' : '500')}; + color: ${({ active$ }) => (active$ ? `var(${UI.COLOR_TEXT})` : `var(${UI.COLOR_TEXT_OPACITY_70})`)}; + background: ${({ active$ }) => (active$ ? `var(${UI.COLOR_BACKGROUND})` : 'transparent')}; + display: flex; + align-items: center; + gap: 4px; + + ${Media.upToSmall()} { + padding: 8px 12px; + font-size: 13px; + } &:hover { - background : var(${UI.COLOR_BACKGROUND}); + background: var(${UI.COLOR_BACKGROUND}); } ` diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index bfb017735d..499775fe6c 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -2,8 +2,10 @@ import { useCallback } from 'react' import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken } from '@cowprotocol/common-const' +import { useMediaQuery } from '@cowprotocol/common-hooks' import { TokenLogo, TokensByAddress } from '@cowprotocol/tokens' import { InfoTooltip, LoadingRows, LoadingRowSmall, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { Media } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' import { VirtualItem } from '@tanstack/react-virtual' @@ -18,8 +20,16 @@ import { LpTokenInfo, LpTokenLogo, LpTokenWrapper, + LpTokenYieldPercentage, + LpTokenBalance, + LpTokenTooltip, NoPoolWrapper, Wrapper, + EmptyList, + MobileCard, + MobileCardRow, + MobileCardLabel, + MobileCardValue, } from './styled' const LoadingElement = ( @@ -28,6 +38,36 @@ const LoadingElement = (
) +const MobileCardRowItem: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => ( + + {label}: + {value} + +) + +const TokenInfo: React.FC<{ token: LpToken }> = ({ token }) => ( + <> + + + +

+ +

+ +) + +const LpTokenLogos: React.FC<{ token0: string; token1: string; tokensByAddress: TokensByAddress; size: number }> = ({ + token0, + token1, + tokensByAddress, + size, +}) => ( + + + + +) + interface LpTokenListsProps { lpTokens: LpToken[] tokensByAddress: TokensByAddress @@ -37,6 +77,7 @@ interface LpTokenListsProps { export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, displayCreatePoolBanner }: LpTokenListsProps) { const { values: balances } = balancesState + const isMobile = useMediaQuery(Media.upToSmall(false)) const getItemView = useCallback( (lpTokens: LpToken[], item: VirtualItem) => { @@ -46,46 +87,69 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, display const balance = balances ? balances[token.address.toLowerCase()] : undefined const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined + const commonContent = ( + <> + + + + + + ) + + if (isMobile) { + return ( + + {commonContent} + : LoadingElement} + /> + + + + TODO + + + } + /> + + ) + } + return ( - - -
- -
-
- -
-
- - - - -

- -

-
-
- {balanceAmount ? : LoadingElement} - 40% - - TODO - + {commonContent} + {balanceAmount ? : LoadingElement} + 40% + + TODO +
) }, - [balances, tokensByAddress], + [balances, tokensByAddress, isMobile], ) return ( - - Pool - Balance - APR - - - + {lpTokens.length > 0 ? ( + <> + {!isMobile && ( + + Pool + Balance + APR + + + )} + + + ) : ( + No pool tokens available + )} {displayCreatePoolBanner && (
Can’t find the pool you’re looking for?
diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index bf9473d7b8..7ed3509ead 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -1,13 +1,16 @@ -import { UI } from '@cowprotocol/ui' +import { TokenLogoWrapper } from '@cowprotocol/tokens' +import { Media, UI } from '@cowprotocol/ui' import { ArrowRight } from 'react-feather' import styled from 'styled-components/macro' export const Wrapper = styled.div` + --grid-columns: 1fr 100px 50px 20px; display: flex; flex-direction: column; overflow: auto; - margin-bottom: 15px; + margin: 0 0 20px; + height: 100%; ` export const LpTokenWrapper = styled.div` @@ -19,16 +22,16 @@ export const LpTokenWrapper = styled.div` export const ListHeader = styled.div` display: grid; - grid-template-columns: 1fr 100px 50px 30px; + grid-template-columns: var(--grid-columns); font-size: 12px; font-weight: 500; color: var(${UI.COLOR_TEXT_OPACITY_70}); - margin: 0 20px 15px 20px; + margin: 0 25px 15px 20px; ` export const ListItem = styled.div` display: grid; - grid-template-columns: 1fr 100px 50px 20px; + grid-template-columns: var(--grid-columns); padding: 10px 20px; cursor: pointer; @@ -39,48 +42,102 @@ export const ListItem = styled.div` export const LpTokenLogo = styled.div` --size: 36px; - --halfSize: 18px; - + position: relative; width: var(--size); height: var(--size); - position: relative; + object-fit: contain; - > div { - position: absolute; - width: var(--halfSize); - overflow: hidden; + ${Media.upToSmall()} { + --size: 32px; } - > div:last-child { - right: -1px; + ${TokenLogoWrapper} { + position: relative; + z-index: 1; + border-radius: var(--size); + width: var(--size); + height: var(--size); + min-width: var(--size); + min-height: var(--size); + font-size: var(--size); } - > div:last-child > div { - right: 100%; - position: relative; + ${TokenLogoWrapper}:nth-child(2) { + position: absolute; + left: 0; + top: 0; + z-index: 2; + clip-path: inset(0 0 0 50%); + } + + &::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + width: 2px; + height: 100%; + background-color: var(${UI.COLOR_PAPER}); + transform: translateX(-50%); + z-index: 3; } ` export const LpTokenInfo = styled.div` display: flex; flex-direction: column; + gap: 2px; + + > strong { + font-weight: 600; + } > p { margin: 0; - font-size: 13px; - color: var(${UI.COLOR_TEXT_OPACITY_70}); + font-size: 12px; + color: var(${UI.COLOR_TEXT_OPACITY_60}); + letter-spacing: -0.02rem; } ` +export const LpTokenYieldPercentage = styled.span` + display: flex; + align-items: center; + font-size: 16px; + font-weight: 600; +` + +export const LpTokenBalance = styled.span` + display: flex; + align-items: center; + font-size: 14px; + letter-spacing: -0.02rem; + color: var(${UI.COLOR_TEXT_OPACITY_70}); + padding: 0 8px 0 0; +` + +export const LpTokenTooltip = styled.div` + display: flex; + align-items: center; + margin: auto; +` + export const NoPoolWrapper = styled.div` border-top: 1px solid var(${UI.COLOR_BORDER}); color: var(${UI.COLOR_TEXT_OPACITY_70}); - padding: 20px 0 10px 0; + padding: 20px 20px 0; display: flex; - flex-direction: column; - gap: 16px; + flex-flow: row wrap; + width: 100%; + gap: 10px; align-items: center; + justify-content: space-between; font-size: 13px; + margin: auto 0 0; + + > div { + flex: 1; + } ` export const ArrowUpRight = styled(ArrowRight)` @@ -103,3 +160,42 @@ export const CreatePoolLink = styled.a` opacity: 0.8; } ` + +export const EmptyList = styled.div` + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; + font-size: 16px; + color: var(${UI.COLOR_TEXT_OPACITY_70}); +` + +export const MobileCard = styled.div` + display: flex; + flex-direction: column; + background-color: var(${UI.COLOR_PAPER}); + padding: 20px; + border-bottom: 1px solid var(${UI.COLOR_PAPER_DARKER}); +` + +export const MobileCardRow = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 8px; + gap: 8px; + + &:last-child { + margin-bottom: 0; + } +` + +export const MobileCardLabel = styled.span` + font-size: 14px; + color: var(${UI.COLOR_TEXT_OPACITY_70}); +` + +export const MobileCardValue = styled.span` + font-size: 16px; + font-weight: 600; +` diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/styled.ts index 92f88d3443..fd9c69560f 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/styled.ts @@ -13,7 +13,7 @@ export const Wrapper = styled.div` ` export const Row = styled.div` - margin: 0 20px 15px 20px; + margin: 0 20px 20px; ` export const Separator = styled.div` diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 7af3c59f62..4a7dec8610 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useMemo } from 'react' import ICON_ORDERS from '@cowprotocol/assets/svg/orders.svg' import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg' import { isInjectedWidget, maxAmountSpend } from '@cowprotocol/common-utils' -import { BannerOrientation, ButtonOutlined, ClosableBanner, InlineBanner, MY_ORDERS_ID } from '@cowprotocol/ui' +import { BannerOrientation, ButtonOutlined, ClosableBanner, InlineBanner, Media, MY_ORDERS_ID } from '@cowprotocol/ui' import { useIsSafeWallet, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { t } from '@lingui/macro' @@ -51,6 +51,7 @@ const scrollToMyOrders = () => { export function TradeWidgetForm(props: TradeWidgetProps) { const isInjectedWidgetMode = isInjectedWidget() const { standaloneMode, hideOrdersTable } = useInjectedWidgetParams() + const isMobile = useMediaQuery(Media.upToSmall(false)) const isAlternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const { pendingActivity } = useCategorizeRecentActivity() @@ -128,7 +129,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { (isLimitOrderMode && isUpToLarge && isLimitOrdersUnlocked) || (isAdvancedMode && isUpToLarge && isAdvancedOrdersUnlocked)) - const showDropdown = shouldShowMyOrdersButton || isInjectedWidgetMode + const showDropdown = shouldShowMyOrdersButton || isInjectedWidgetMode || isMobile const currencyInputCommonProps = { isChainIdUnsupported, diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/styled.ts b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/styled.ts index 770b0e0db9..e608a832d9 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/styled.ts +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidgetLinks/styled.ts @@ -84,25 +84,25 @@ export const MenuItem = styled.div<{ isActive?: boolean; isDropdownVisible: bool css` padding: 16px; width: 100%; - margin-bottom: 20px; + margin: 0 0 10px; `} + + } } ` export const SelectMenu = styled.div` - display: flex; - flex-flow: column wrap; + display: block; width: 100%; height: 100%; position: absolute; z-index: 100; left: 0; top: 0; - gap: ${({ theme }) => (theme.isInjectedWidgetMode ? '16px' : '24px')}; background: var(${UI.COLOR_PAPER}); border-radius: var(${UI.BORDER_RADIUS_NORMAL}); ` export const TradeWidgetContent = styled.div` - padding: 0 16px 16px 16px; + padding: 16px; ` diff --git a/libs/ui/src/enum.ts b/libs/ui/src/enum.ts index 7b083a0990..614921d50e 100644 --- a/libs/ui/src/enum.ts +++ b/libs/ui/src/enum.ts @@ -27,6 +27,7 @@ export enum UI { COLOR_TEXT_PAPER = '--cow-color-text-paper', COLOR_TEXT_OPACITY_70 = '--cow-color-text-opacity-70', + COLOR_TEXT_OPACITY_60 = '--cow-color-text-opacity-60', COLOR_TEXT_OPACITY_50 = '--cow-color-text-opacity-50', COLOR_TEXT_OPACITY_25 = '--cow-color-text-opacity-25', COLOR_TEXT_OPACITY_10 = '--cow-color-text-opacity-10', diff --git a/libs/ui/src/pure/InfoTooltip/index.tsx b/libs/ui/src/pure/InfoTooltip/index.tsx index 21c12d0b65..74481128d1 100644 --- a/libs/ui/src/pure/InfoTooltip/index.tsx +++ b/libs/ui/src/pure/InfoTooltip/index.tsx @@ -6,20 +6,26 @@ import styled from 'styled-components/macro' import { UI } from '../../enum' import { HoverTooltip, TooltipContainer } from '../Tooltip' -const StyledIcon = styled.div` - display: inline-block; +const StyledIcon = styled.div<{ size: number }>` + display: inline-flex; + align-items: center; color: inherit; + opacity: 0.6; + transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; + height: ${({ size }) => size}px; + + &:hover { + opacity: 1; + } + + > span { + margin-right: 4px; + } > svg { - opacity: 0.6; stroke: currentColor; line-height: 0; vertical-align: middle; - transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; - - :hover { - opacity: 1; - } } ` @@ -36,14 +42,16 @@ export interface InfoTooltipProps { children?: ReactNode size?: number className?: string + preText?: string } -export function InfoTooltip({ content, children, className, size = 16 }: InfoTooltipProps) { +export function InfoTooltip({ content, children, className, size = 16, preText }: InfoTooltipProps) { const tooltipContent = {children || content} return ( - + + {preText && {preText}} diff --git a/libs/ui/src/theme/ThemeColorVars.tsx b/libs/ui/src/theme/ThemeColorVars.tsx index 03acc39dcf..6a4c8d6a0f 100644 --- a/libs/ui/src/theme/ThemeColorVars.tsx +++ b/libs/ui/src/theme/ThemeColorVars.tsx @@ -41,6 +41,7 @@ export const ThemeColorVars = css` ${UI.COLOR_TEXT_PAPER}: ${({ theme }) => getContrastText(theme.paper, theme.text)}; ${UI.COLOR_TEXT_OPACITY_70}: ${({ theme }) => transparentize(theme.text, 0.3)}; + ${UI.COLOR_TEXT_OPACITY_60}: ${({ theme }) => transparentize(theme.text, 0.4)}; ${UI.COLOR_TEXT_OPACITY_50}: ${({ theme }) => transparentize(theme.text, 0.5)}; ${UI.COLOR_TEXT_OPACITY_25}: ${({ theme }) => transparentize(theme.text, 0.75)}; ${UI.COLOR_TEXT_OPACITY_10}: ${({ theme }) => transparentize(theme.text, 0.9)}; From 0350976951b1e02fcb812cab7bc2b8a499370bbc Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 28 Oct 2024 17:17:26 +0500 Subject: [PATCH 88/90] chore: link to create pool --- .../modules/tokensList/pure/LpTokenLists/index.tsx | 10 +--------- .../modules/tokensList/pure/LpTokenLists/styled.ts | 11 ++--------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 499775fe6c..2067456286 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -13,7 +13,6 @@ import { VirtualItem } from '@tanstack/react-virtual' import { VirtualList } from 'common/pure/VirtualList' import { - ArrowUpRight, CreatePoolLink, ListHeader, ListItem, @@ -153,14 +152,7 @@ export function LpTokenLists({ lpTokens, tokensByAddress, balancesState, display {displayCreatePoolBanner && (
Can’t find the pool you’re looking for?
- - Create a pool - - + Create a pool ↗
)}
diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index 7ed3509ead..578baf1b60 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -1,7 +1,6 @@ import { TokenLogoWrapper } from '@cowprotocol/tokens' -import { Media, UI } from '@cowprotocol/ui' +import { ExternalLink, Media, UI } from '@cowprotocol/ui' -import { ArrowRight } from 'react-feather' import styled from 'styled-components/macro' export const Wrapper = styled.div` @@ -140,13 +139,7 @@ export const NoPoolWrapper = styled.div` } ` -export const ArrowUpRight = styled(ArrowRight)` - transform: rotate(-45deg); - margin-left: 2px; - margin-bottom: -2px; -` - -export const CreatePoolLink = styled.a` +export const CreatePoolLink = styled(ExternalLink)` display: inline-block; background: #bcec79; color: #194d05; From 8d33d3b2f83a4e1a242d777d8638431aefa30aac Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 28 Oct 2024 17:41:50 +0500 Subject: [PATCH 89/90] chore: merge changes --- .../tokensList/pure/LpTokenLists/index.tsx | 23 +++++++++---------- .../tokensList/pure/LpTokenLists/styled.ts | 7 ++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx index 6d4fc887c8..acf4499d80 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/index.tsx @@ -4,7 +4,7 @@ import { BalancesState } from '@cowprotocol/balances-and-allowances' import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' import { useMediaQuery } from '@cowprotocol/common-hooks' import { TokenLogo } from '@cowprotocol/tokens' -import { InfoTooltip, LoadingRows, LoadingRowSmall, Media, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' +import { LoadingRows, LoadingRowSmall, Media, TokenAmount, TokenName, TokenSymbol } from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' import { VirtualItem } from '@tanstack/react-virtual' @@ -45,7 +45,6 @@ const MobileCardRowItem: React.FC<{ label: string; value: React.ReactNode }> = ( ) - interface LpTokenListsProps { account: string | undefined lpTokens: LpToken[] @@ -77,7 +76,7 @@ export function LpTokenLists({ const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined const info = poolsInfo?.[tokenAddressLower]?.info - const onInfoClick: MouseEventHandler = (e) => { + const onInfoClick: MouseEventHandler = (e) => { e.stopPropagation() openPoolPage(tokenAddressLower) } @@ -96,20 +95,20 @@ export function LpTokenLists({ ) + const BalanceDisplay = balanceAmount ? : LoadingElement + if (isMobile) { return ( {commonContent} - : LoadingElement} - /> + - + + Pool details + } /> @@ -120,10 +119,10 @@ export function LpTokenLists({ return ( onSelectToken(token)}> {commonContent} - {balanceAmount ? : LoadingElement} + {BalanceDisplay} {info?.apy ? `${info.apy}%` : ''} - - + + ) diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts index 173084f064..44971e2b5e 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/LpTokenLists/styled.ts @@ -77,6 +77,13 @@ export const LpTokenTooltip = styled.div` display: flex; align-items: center; margin: auto; + gap: 6px; + opacity: 0.8; + cursor: pointer; + + &:hover { + opacity: 1; + } ` export const NoPoolWrapper = styled.div` From 265b8709687b472f043abbb7993959a79723e6e5 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 28 Oct 2024 18:20:40 +0500 Subject: [PATCH 90/90] chore: fix balances cache --- .../src/updaters/BalancesAndAllowancesUpdater.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 422502970d..e4a81608d0 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -49,5 +49,5 @@ export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAl setBalances((state) => ({ ...state, values: { ...state.values, ...nativeBalanceState } })) }, [nativeTokenBalance, chainId, setBalances]) - return + return account ? : null }