Skip to content

Commit

Permalink
feat: no fees for fox buys (#8965)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeOMakinG authored Mar 7, 2025
1 parent 85621b4 commit 6cd4884
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 34 deletions.
4 changes: 4 additions & 0 deletions packages/caip/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const foxOnGnosisAssetId: AssetId =
export const foxOnArbitrumOneAssetId: AssetId =
'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73'
export const foxAssetId: AssetId = 'eip155:1/erc20:0xc770eefad204b5180df6a14ee197d99d808ee52d'
export const foxOnOptimismAssetId: AssetId =
'eip155:10/erc20:0xf1a0da3367bc7aa04f8d94ba57b862ff37ced174'
export const foxOnPolygonAssetId: AssetId =
'eip155:137/erc20:0x65a05db8322701724c197af82c9cae41195b0aa8'
export const usdtAssetId: AssetId = 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7'
export const usdcAssetId: AssetId = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
export const usdcOnArbitrumOneAssetId: AssetId =
Expand Down
3 changes: 2 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2530,7 +2530,8 @@
"underThreshold": "Low Trade",
"thorHolder": "THORSwap Holder",
"foxHolder": "FOX Holder",
"foxWifHatHolder": "FOX Wif Hat Holder"
"foxWifHatHolder": "FOX Wif Hat Holder",
"foxBuy": "FOX Buy"
},
"chart": {
"interval": {
Expand Down
40 changes: 33 additions & 7 deletions src/components/FeeExplainer/FeeExplainer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CardProps, StackProps } from '@chakra-ui/react'
import { Box, Card, CardBody, Flex, Heading, Stack, useToken } from '@chakra-ui/react'
import { foxWifHatAssetId } from '@shapeshiftoss/caip'
import { foxAssetId, foxWifHatAssetId } from '@shapeshiftoss/caip'
import { bnOrZero } from '@shapeshiftoss/chain-adapters'
import { LinearGradient } from '@visx/gradient'
import { GridColumns, GridRows } from '@visx/grid'
Expand Down Expand Up @@ -37,7 +37,9 @@ import {
selectVotingPower,
} from '@/state/apis/snapshot/selectors'
import { selectPortfolioCryptoBalanceBaseUnitByFilter } from '@/state/slices/common-selectors'
import { useAppSelector } from '@/state/store'
import { selectRelatedAssetIdsInclusiveSorted } from '@/state/slices/related-assets-selectors'
import { selectInputBuyAsset } from '@/state/slices/tradeInputSlice/selectors'
import { useAppSelector, useSelectorWithArgs } from '@/state/store'

type FeeChartProps = {
tradeSize: number
Expand Down Expand Up @@ -275,6 +277,26 @@ export const FeeOutput: React.FC<FeeOutputProps> = ({ tradeSizeUSD, foxHolding,
selectPortfolioCryptoBalanceBaseUnitByFilter(state, { assetId: foxWifHatAssetId }),
)

const buyAsset = useAppSelector(selectInputBuyAsset)

const relatedAssetIdsFilter = useMemo(
() => ({
assetId: foxAssetId,
onlyConnectedChains: false,
}),
[],
)

const relatedAssetIds = useSelectorWithArgs(
selectRelatedAssetIdsInclusiveSorted,
relatedAssetIdsFilter,
)

const isFoxBuyAsset = useMemo(
() => relatedAssetIds.includes(buyAsset?.assetId),
[relatedAssetIds, buyAsset?.assetId],
)

const {
feeUsd,
feeBps,
Expand All @@ -300,15 +322,19 @@ export const FeeOutput: React.FC<FeeOutputProps> = ({ tradeSizeUSD, foxHolding,
[feeUsdBeforeDiscount, feeBpsBeforeDiscount],
)

const isFree = useMemo(() => bnOrZero(feeUsd).lte(0), [feeUsd])
const isFree = useMemo(() => bnOrZero(feeUsd).lte(0) || isFoxBuyAsset, [feeUsd, isFoxBuyAsset])

const discountTypeTranslation = useMemo(() => {
return translate(appliedDiscountType)
}, [appliedDiscountType, translate])
if (isFoxBuyAsset) return translate('foxDiscounts.foxBuy')

return translate(`foxDiscounts.${appliedDiscountType}`)
}, [appliedDiscountType, isFoxBuyAsset, translate])

const discountLabelTranslation: TextPropTypes['translation'] = useMemo(() => {
if (feeBps.isZero()) return 'foxDiscounts.breakdownHeader'

return [`foxDiscounts.discountLabel`, { discountType: discountTypeTranslation }]
}, [discountTypeTranslation])
}, [discountTypeTranslation, feeBps])

return (
<Flex fontWeight='medium' pb={0}>
Expand Down Expand Up @@ -340,7 +366,7 @@ export const FeeOutput: React.FC<FeeOutputProps> = ({ tradeSizeUSD, foxHolding,
<Text color='text.subtle' translation={discountLabelTranslation} />
<Amount.Percent
fontSize='3xl'
value={foxDiscountPercent.div(100).toNumber()}
value={isFree ? 1 : foxDiscountPercent.div(100).toNumber()}
color={'green.500'}
/>
</Box>
Expand Down
42 changes: 35 additions & 7 deletions src/components/FeeModal/FeeBreakdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Divider, Heading, Stack, useColorModeValue } from '@chakra-ui/react'
import { foxAssetId } from '@shapeshiftoss/caip'
import { useMemo } from 'react'
import { useTranslate } from 'react-polyglot'

Expand All @@ -9,7 +10,9 @@ import { BigNumber } from '@/lib/bignumber/bignumber'
import { FEE_MODEL_TO_FEATURE_NAME } from '@/lib/fees/parameters'
import type { ParameterModel } from '@/lib/fees/parameters/types'
import { selectAppliedDiscountType, selectCalculatedFees } from '@/state/apis/snapshot/selectors'
import { useAppSelector } from '@/state/store'
import { selectRelatedAssetIdsInclusiveSorted } from '@/state/slices/related-assets-selectors'
import { selectInputBuyAsset } from '@/state/slices/tradeInputSlice/selectors'
import { useAppSelector, useSelectorWithArgs } from '@/state/store'

const divider = <Divider />

Expand All @@ -33,16 +36,40 @@ export const FeeBreakdown = ({ feeModel, inputAmountUsd }: FeeBreakdownProps) =>
)
const greenColor = useColorModeValue('green.500', 'green.300')

const buyAsset = useAppSelector(selectInputBuyAsset)

const relatedAssetIdsFilter = useMemo(
() => ({
assetId: foxAssetId,
onlyConnectedChains: false,
}),
[],
)

const relatedAssetIds = useSelectorWithArgs(
selectRelatedAssetIdsInclusiveSorted,
relatedAssetIdsFilter,
)

const isFoxBuyAsset = useMemo(
() => relatedAssetIds.includes(buyAsset?.assetId),
[relatedAssetIds, buyAsset?.assetId],
)

const discountTypeTranslation = useMemo(() => {
if (isFoxBuyAsset) return translate('foxDiscounts.foxBuy')

return translate(`foxDiscounts.${appliedDiscountType}`)
}, [appliedDiscountType, translate])
}, [appliedDiscountType, isFoxBuyAsset, translate])

const discountLabelTranslation = useMemo(() => {
if (feeBps.isZero()) return translate('foxDiscounts.breakdownHeader')

return translate(`foxDiscounts.discountLabel`, { discountType: discountTypeTranslation })
}, [discountTypeTranslation, translate])
}, [discountTypeTranslation, translate, feeBps])

const totalAmount = useMemo(() => {
if (feeBps.isZero())
if (feeBps.isZero() || isFoxBuyAsset)
return (
<Row.Value fontSize='lg'>
<Text translation='trade.free' fontWeight='semibold' color={greenColor} />
Expand All @@ -57,13 +84,14 @@ export const FeeBreakdown = ({ feeModel, inputAmountUsd }: FeeBreakdownProps) =>
/>
</Row.Value>
)
}, [affiliateFeeAmountUsd, feeBps, greenColor])
}, [affiliateFeeAmountUsd, feeBps, greenColor, isFoxBuyAsset])

const totalDiscount = useMemo(() => {
if (feeBps.isZero()) return feeUsdBeforeDiscount.toFixed(2, BigNumber.ROUND_HALF_UP)
if (feeBps.isZero() || isFoxBuyAsset)
return feeUsdBeforeDiscount.toFixed(2, BigNumber.ROUND_HALF_UP)

return foxDiscountUsd.toFixed(2, BigNumber.ROUND_HALF_UP)
}, [feeUsdBeforeDiscount, foxDiscountUsd, feeBps])
}, [feeUsdBeforeDiscount, foxDiscountUsd, feeBps, isFoxBuyAsset])

return (
<Stack spacing={0}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ import {
Stack,
useDisclosure,
} from '@chakra-ui/react'
import {
foxAssetId,
foxOnArbitrumOneAssetId,
foxOnGnosisAssetId,
fromAssetId,
toAccountId,
} from '@shapeshiftoss/caip'
import { foxAssetId, fromAssetId, toAccountId } from '@shapeshiftoss/caip'
import type { Asset } from '@shapeshiftoss/types'
import { TransferType } from '@shapeshiftoss/unchained-client'
import type { InterpolationOptions } from 'node-polyglot'
Expand All @@ -37,6 +31,7 @@ import { Text } from '@/components/Text'
import { useTxDetails, useTxDetailsQuery } from '@/hooks/useTxDetails/useTxDetails'
import { bnOrZero } from '@/lib/bignumber/bignumber'
import { fromBaseUnit } from '@/lib/math'
import { selectRelatedAssetIdsInclusiveSorted } from '@/state/slices/related-assets-selectors'
import {
selectActiveQuote,
selectConfirmedTradeExecution,
Expand All @@ -47,7 +42,7 @@ import {
selectTradeQuoteAffiliateFeeDiscountUserCurrency,
} from '@/state/slices/tradeQuoteSlice/selectors'
import { serializeTxIndex } from '@/state/slices/txHistorySlice/utils'
import { useAppSelector } from '@/state/store'
import { useAppSelector, useSelectorWithArgs } from '@/state/store'

export type TradeSuccessProps = {
handleBack: () => void
Expand Down Expand Up @@ -130,6 +125,19 @@ export const TradeSuccess = ({
: undefined
}, [transfers, buyAsset])

const relatedAssetIdsFilter = useMemo(
() => ({
assetId: foxAssetId,
onlyConnectedChains: false,
}),
[],
)

const relatedAssetIds = useSelectorWithArgs(
selectRelatedAssetIdsInclusiveSorted,
relatedAssetIdsFilter,
)

const AmountsLine = useCallback(() => {
if (!(sellAsset && buyAsset)) return null
if (!(sellAmountCryptoPrecision && quoteBuyAmountCryptoPrecision)) return null
Expand Down Expand Up @@ -167,8 +175,7 @@ export const TradeSuccess = ({
// values because the amount of FOX held in the wallet will have changed.
// See https://github.com/shapeshift/web/issues/8028 for more details.
const enableFoxDiscountSummary = useMemo(() => {
const foxAssetIds = [foxAssetId, foxOnGnosisAssetId, foxOnArbitrumOneAssetId]
const didTradeFox = foxAssetIds.some(assetId => {
const didTradeFox = relatedAssetIds.some(assetId => {
return (
firstHop?.buyAsset.assetId === assetId ||
firstHop?.sellAsset.assetId === assetId ||
Expand All @@ -178,7 +185,7 @@ export const TradeSuccess = ({
})

return !didTradeFox
}, [firstHop, lastHop])
}, [firstHop, lastHop, relatedAssetIds])

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { skipToken as reduxSkipToken } from '@reduxjs/toolkit/query'
import { foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { foxAssetId, foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type {
GetTradeQuoteInput,
Expand Down Expand Up @@ -38,6 +38,7 @@ import {
} from '@/state/apis/snapshot/selectors'
import { swapperApi } from '@/state/apis/swapper/swapperApi'
import type { ApiQuote, TradeQuoteError } from '@/state/apis/swapper/types'
import { selectRelatedAssetIdsInclusiveSorted } from '@/state/slices/related-assets-selectors'
import {
selectPortfolioAccountMetadataByAccountId,
selectPortfolioCryptoBalanceBaseUnitByFilter,
Expand All @@ -62,7 +63,7 @@ import {
} from '@/state/slices/tradeQuoteSlice/selectors'
import { tradeQuoteSlice } from '@/state/slices/tradeQuoteSlice/tradeQuoteSlice'
import { HopExecutionState, TransactionExecutionState } from '@/state/slices/tradeQuoteSlice/types'
import { store, useAppDispatch, useAppSelector } from '@/state/store'
import { store, useAppDispatch, useAppSelector, useSelectorWithArgs } from '@/state/store'

type MixPanelQuoteMeta = {
swapperName: SwapperName
Expand Down Expand Up @@ -217,6 +218,19 @@ export const useGetTradeQuotes = () => {
selectPortfolioCryptoBalanceBaseUnitByFilter(state, { assetId: foxWifHatAssetId }),
)

const relatedAssetIdsFilter = useMemo(
() => ({
assetId: foxAssetId,
onlyConnectedChains: false,
}),
[],
)

const relatedAssetIds = useSelectorWithArgs(
selectRelatedAssetIdsInclusiveSorted,
relatedAssetIdsFilter,
)

const walletSupportsBuyAssetChain = useWalletSupportsChain(buyAsset.chainId, wallet)
const isBuyAssetChainSupported = walletSupportsBuyAssetChain

Expand Down Expand Up @@ -283,6 +297,8 @@ export const useGetTradeQuotes = () => {
const potentialAffiliateBps = feeBpsBeforeDiscount.toFixed(0)
const affiliateBps = feeBps.toFixed(0)

const isFoxBuyAsset = relatedAssetIds.includes(buyAsset.assetId)

if (sellAccountNumber === undefined) throw new Error('sellAccountNumber is required')
if (!receiveAddress) throw new Error('receiveAddress is required')

Expand All @@ -298,8 +314,8 @@ export const useGetTradeQuotes = () => {
receiveAddress,
sellAmountBeforeFeesCryptoPrecision: sellAmountCryptoPrecision,
allowMultiHop: true,
affiliateBps,
potentialAffiliateBps,
affiliateBps: isFoxBuyAsset ? '0' : affiliateBps,
potentialAffiliateBps: isFoxBuyAsset ? '0' : potentialAffiliateBps,
// Pass in the user's slippage preference if it's set, else let the swapper use its default
slippageTolerancePercentageDecimal: userSlippageTolerancePercentageDecimal,
pubKey:
Expand Down Expand Up @@ -327,6 +343,7 @@ export const useGetTradeQuotes = () => {
userSlippageTolerancePercentageDecimal,
votingPower,
wallet,
relatedAssetIds,
])

const { data: tradeQuoteInput } = useQuery({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { skipToken } from '@reduxjs/toolkit/dist/query'
import { foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { foxAssetId, foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type { GetTradeRateInput, TradeRate } from '@shapeshiftoss/swapper'
import {
Expand Down Expand Up @@ -35,6 +35,7 @@ import type { ApiQuote, TradeQuoteError } from '@/state/apis/swapper/types'
import { selectPortfolioCryptoBalanceBaseUnitByFilter } from '@/state/slices/common-selectors'
import { selectUsdRateByAssetId } from '@/state/slices/marketDataSlice/selectors'
import { selectPortfolioAccountMetadataByAccountId } from '@/state/slices/portfolioSlice/selectors'
import { selectRelatedAssetIdsInclusiveSorted } from '@/state/slices/related-assets-selectors'
import {
selectFirstHopSellAccountId,
selectInputBuyAsset,
Expand All @@ -50,7 +51,7 @@ import {
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'

type MixPanelQuoteMeta = {
swapperName: SwapperName
Expand Down Expand Up @@ -179,6 +180,19 @@ export const useGetTradeRates = () => {

const shouldRefetchTradeQuotes = useMemo(() => hasFocus, [hasFocus])

const relatedAssetIdsFilter = useMemo(
() => ({
assetId: foxAssetId,
onlyConnectedChains: false,
}),
[],
)

const relatedAssetIds = useSelectorWithArgs(
selectRelatedAssetIdsInclusiveSorted,
relatedAssetIdsFilter,
)

const isSnapshotApiQueriesRejected = useAppSelector(selectIsSnapshotApiQueriesRejected)
const { manualReceiveAddress, walletReceiveAddress } = useTradeReceiveAddress()
const receiveAddress = manualReceiveAddress ?? walletReceiveAddress
Expand Down Expand Up @@ -234,6 +248,8 @@ export const useGetTradeRates = () => {
isSnapshotApiQueriesRejected,
})

const isFoxBuyAsset = relatedAssetIds.includes(buyAsset.assetId)

const potentialAffiliateBps = feeBpsBeforeDiscount.toFixed(0)
const affiliateBps = feeBps.toFixed(0)

Expand All @@ -247,8 +263,8 @@ export const useGetTradeRates = () => {
receiveAddress,
sellAmountBeforeFeesCryptoPrecision: sellAmountCryptoPrecision,
allowMultiHop: true,
affiliateBps,
potentialAffiliateBps,
affiliateBps: isFoxBuyAsset ? '0' : affiliateBps,
potentialAffiliateBps: isFoxBuyAsset ? '0' : potentialAffiliateBps,
// Pass in the user's slippage preference if it's set, else let the swapper use its default
slippageTolerancePercentageDecimal: userSlippageTolerancePercentageDecimal,
pubKey:
Expand Down

0 comments on commit 6cd4884

Please sign in to comment.