From dbe8838ffe7e81ce91bebf493403a2070122dfd9 Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:16:31 +1100 Subject: [PATCH] fix: attach fox discount info to swapper state before executing trade --- .../components/TradeInput/TradeInput.tsx | 17 +++++++++-- src/state/slices/tradeQuoteSlice/constants.ts | 1 + src/state/slices/tradeQuoteSlice/selectors.ts | 30 ++++++++----------- .../slices/tradeQuoteSlice/tradeQuoteSlice.ts | 3 ++ src/state/slices/tradeQuoteSlice/types.ts | 4 +++ src/test/mocks/store.ts | 1 + 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx index bcd7a42c510..46728b4c6c6 100644 --- a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx +++ b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx @@ -25,7 +25,7 @@ import { useWallet } from 'hooks/useWallet/useWallet' import { fromBaseUnit } from 'lib/math' import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton' import { MixPanelEvent } from 'lib/mixpanel/types' -import { selectIsVotingPowerLoading } from 'state/apis/snapshot/selectors' +import { selectCalculatedFees, selectIsVotingPowerLoading } from 'state/apis/snapshot/selectors' import type { ApiQuote } from 'state/apis/swapper/types' import { selectIsAnyAccountMetadataLoadedForChainId, @@ -49,11 +49,12 @@ import { selectFirstHop, selectIsTradeQuoteRequestAborted, selectIsUnsafeActiveQuote, + selectQuoteSellAmountUsd, selectShouldShowTradeQuoteOrAwaitInput, selectSortedTradeQuotes, } from 'state/slices/tradeQuoteSlice/selectors' import { tradeQuoteSlice } from 'state/slices/tradeQuoteSlice/tradeQuoteSlice' -import { store, useAppDispatch, useAppSelector } from 'state/store' +import { store, useAppDispatch, useAppSelector, useSelectorWithArgs } from 'state/store' import { useAccountIds } from '../../hooks/useAccountIds' import { SharedTradeInput } from '../SharedTradeInput/SharedTradeInput' @@ -107,6 +108,7 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput const activeQuoteMeta = useAppSelector(selectActiveQuoteMeta) const sellAmountCryptoPrecision = useAppSelector(selectInputSellAmountCryptoPrecision) const sellAmountUserCurrency = useAppSelector(selectInputSellAmountUserCurrency) + const sellAmountUsd = useAppSelector(selectQuoteSellAmountUsd) const buyAmountAfterFeesCryptoPrecision = useAppSelector(selectBuyAmountAfterFeesCryptoPrecision) const buyAmountAfterFeesUserCurrency = useAppSelector(selectBuyAmountAfterFeesUserCurrency) const shouldShowTradeQuoteOrAwaitInput = useAppSelector(selectShouldShowTradeQuoteOrAwaitInput) @@ -126,6 +128,14 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput selectIsAnyAccountMetadataLoadedForChainId(state, isAnyAccountMetadataLoadedForChainIdFilter), ) const walletId = useAppSelector(selectWalletId) + const calculatedFeesParams = useMemo( + () => ({ + feeModel: 'SWAPPER', + inputAmountUsd: sellAmountUsd, + }), + [sellAmountUsd], + ) + const calculatedFees = useSelectorWithArgs(selectCalculatedFees, calculatedFeesParams) const sellAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, sellAsset.assetId)) const buyAssetUsdRate = useAppSelector(state => selectUsdRateByAssetId(state, buyAsset.assetId)) @@ -257,7 +267,7 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput // Set the confirmed quote for execution, with a snapshot of the affiliate fees for display after the trade is executed. // This is done to handle the fox power calculation changing due to FOX balance changes after the trade is executed. - dispatch(tradeQuoteSlice.actions.setConfirmedQuote({ quote: activeQuote })) + dispatch(tradeQuoteSlice.actions.setConfirmedQuote({ quote: activeQuote, calculatedFees })) dispatch(tradeQuoteSlice.actions.clearQuoteExecutionState(activeQuote.id)) if (isLedger(wallet)) { @@ -275,6 +285,7 @@ export const TradeInput = ({ isCompact, tradeInputRef, onChangeTab }: TradeInput }, [ activeQuote, activeQuoteMeta, + calculatedFees, dispatch, handleConnect, history, diff --git a/src/state/slices/tradeQuoteSlice/constants.ts b/src/state/slices/tradeQuoteSlice/constants.ts index aef62d69ee2..aeed808cbd4 100644 --- a/src/state/slices/tradeQuoteSlice/constants.ts +++ b/src/state/slices/tradeQuoteSlice/constants.ts @@ -30,6 +30,7 @@ export const initialTradeExecutionState = { export const initialState: TradeQuoteSliceState = { activeQuoteMeta: undefined, confirmedQuote: undefined, + confirmedFees: undefined, activeStep: undefined, tradeExecution: {}, tradeQuotes: {}, diff --git a/src/state/slices/tradeQuoteSlice/selectors.ts b/src/state/slices/tradeQuoteSlice/selectors.ts index c4995131aec..b820c09d417 100644 --- a/src/state/slices/tradeQuoteSlice/selectors.ts +++ b/src/state/slices/tradeQuoteSlice/selectors.ts @@ -16,8 +16,8 @@ import type { Asset } from '@shapeshiftoss/types' import { identity } from 'lodash' import type { Selector } from 'reselect' import { bn, bnOrZero } from 'lib/bignumber/bignumber' +import type { CalculateFeeBpsReturn } from 'lib/fees/model' import { fromBaseUnit } from 'lib/math' -import { selectCalculatedFees } from 'state/apis/snapshot/selectors' import { validateQuoteRequest } from 'state/apis/swapper/helpers/validateQuoteRequest' import { selectIsTradeQuoteApiQueryPending } from 'state/apis/swapper/selectors' import type { ApiQuote, ErrorWithMeta, TradeQuoteError } from 'state/apis/swapper/types' @@ -205,6 +205,11 @@ export const selectConfirmedQuote: Selector = + createDeepEqualOutputSelector(selectTradeQuoteSlice, tradeQuoteState => { + return tradeQuoteState.confirmedFees + }) + export const selectActiveQuoteMetaOrDefault: Selector< ReduxState, { swapperName: SwapperName; identifier: string } | undefined @@ -564,14 +569,10 @@ export const selectActiveQuoteAffiliateBps: Selector - selectCalculatedFees(state, { - feeModel: 'SWAPPER', - inputAmountUsd: selectQuoteSellAmountUsd(state), - }), + selectConfirmedFees, selectActiveQuoteAffiliateBps, (calculatedFees, affiliateBps) => { - if (!affiliateBps) return + if (!affiliateBps || !calculatedFees) return if (affiliateBps === '0') return bn(0) return calculatedFees.feeUsd @@ -579,17 +580,10 @@ export const selectTradeQuoteAffiliateFeeAfterDiscountUsd = createSelector( ) export const selectTradeQuoteAffiliateFeeDiscountUsd = createSelector( - (state: ReduxState) => - selectCalculatedFees(state, { - feeModel: 'SWAPPER', - inputAmountUsd: selectQuoteSellAmountUsd(state), - }), - selectActiveQuoteAffiliateBps, - (calculatedFees, affiliateBps) => { - if (!affiliateBps) return - if (affiliateBps === '0') return bn(0) - - return calculatedFees.foxDiscountUsd + selectConfirmedFees, + confirmedFees => { + if (!confirmedFees) return + return confirmedFees.foxDiscountUsd }, ) diff --git a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts index 39c805a3c02..1b207ce3c97 100644 --- a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts +++ b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts @@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit' import type { SwapperName, TradeQuote, TradeRate } from '@shapeshiftoss/swapper' import { orderBy, uniqBy } from 'lodash' import type { InterpolationOptions } from 'node-polyglot' +import type { CalculateFeeBpsReturn } from 'lib/fees/model' import type { ApiQuote } from 'state/apis/swapper/types' import { initialState, initialTradeExecutionState } from './constants' @@ -56,9 +57,11 @@ export const tradeQuoteSlice = createSlice({ state, action: PayloadAction<{ quote: TradeQuote | TradeRate + calculatedFees: CalculateFeeBpsReturn }>, ) => { state.confirmedQuote = action.payload.quote + state.confirmedFees = action.payload.calculatedFees }, clearQuoteExecutionState: (state, action: PayloadAction) => { state.tradeExecution[action.payload] = initialTradeExecutionState diff --git a/src/state/slices/tradeQuoteSlice/types.ts b/src/state/slices/tradeQuoteSlice/types.ts index 43c5236816f..f6e76e92e30 100644 --- a/src/state/slices/tradeQuoteSlice/types.ts +++ b/src/state/slices/tradeQuoteSlice/types.ts @@ -1,6 +1,7 @@ import type { SwapperName, TradeQuote, TradeRate } from '@shapeshiftoss/swapper' import type { PartialRecord } from '@shapeshiftoss/types' import type { InterpolationOptions } from 'node-polyglot' +import type { CalculateFeeBpsReturn } from 'lib/fees/model' import type { ApiQuote } from 'state/apis/swapper/types' export type ActiveQuoteMeta = { @@ -12,6 +13,9 @@ export type TradeQuoteSliceState = { activeStep: number | undefined // Make sure to actively check for undefined vs. falsy here. 0 is the first step, undefined means no active step yet activeQuoteMeta: ActiveQuoteMeta | undefined // the selected quote metadata used to find the active quote in the api responses confirmedQuote: TradeQuote | TradeRate | undefined // the quote being executed + // Used to display the "You saved" message in the TradeSuccess component. This needs to be stored + // here because trading fox will affect the calculation after the trade has been executed. + confirmedFees: CalculateFeeBpsReturn | undefined tradeExecution: Record tradeQuotes: PartialRecord> // mapping from swapperName to quoteId to ApiQuote tradeQuoteDisplayCache: ApiQuote[] diff --git a/src/test/mocks/store.ts b/src/test/mocks/store.ts index 7b7ca9d7b76..46371415f3b 100644 --- a/src/test/mocks/store.ts +++ b/src/test/mocks/store.ts @@ -267,6 +267,7 @@ export const mockStore: ReduxState = { tradeQuoteSlice: { activeQuoteMeta: undefined, confirmedQuote: undefined, + confirmedFees: undefined, activeStep: undefined, tradeExecution: {}, tradeQuotes: {},