Skip to content

Commit

Permalink
feat: foxwifhat discount (#8663)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeOMakinG authored Jan 28, 2025
1 parent 111e1dc commit 0a69e2a
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 23 deletions.
16 changes: 16 additions & 0 deletions src/components/FeeExplainer/FeeExplainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +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 { bnOrZero } from '@shapeshiftoss/chain-adapters'
import { LinearGradient } from '@visx/gradient'
import { GridColumns, GridRows } from '@visx/grid'
Expand Down Expand Up @@ -31,6 +32,7 @@ import {
selectIsSnapshotApiQueriesRejected,
selectVotingPower,
} from 'state/apis/snapshot/selectors'
import { selectPortfolioCryptoBalanceBaseUnitByFilter } from 'state/slices/common-selectors'
import { useAppSelector } from 'state/store'

import { CHART_TRADE_SIZE_MAX_USD } from './common'
Expand Down Expand Up @@ -149,6 +151,9 @@ const FeeChart: React.FC<FeeChartProps> = ({ foxHolding, tradeSize, feeModel })
foxHeld: bn(debouncedFoxHolding),
feeModel,
isSnapshotApiQueriesRejected,
// This is for feeExplainer which is not supporting anything else than FOX discount for now
foxWifHatHeldCryptoBaseUnit: bn(0),
thorHeld: bn(0),
}).feeBpsFloat.toNumber()
return { x: trade, y: feeBps }
})
Expand All @@ -161,6 +166,9 @@ const FeeChart: React.FC<FeeChartProps> = ({ foxHolding, tradeSize, feeModel })
foxHeld: bn(debouncedFoxHolding),
feeModel,
isSnapshotApiQueriesRejected,
// This is for feeExplainer which is not supporting anything else than FOX discount for now
foxWifHatHeldCryptoBaseUnit: bn(0),
thorHeld: bn(0),
}).feeBpsFloat.toNumber()

return [{ x: tradeSize, y: feeBps }]
Expand Down Expand Up @@ -262,12 +270,20 @@ type FeeOutputProps = {

export const FeeOutput: React.FC<FeeOutputProps> = ({ tradeSizeUSD, foxHolding, feeModel }) => {
const isSnapshotApiQueriesRejected = useAppSelector(selectIsSnapshotApiQueriesRejected)

const foxWifHatHeld = useAppSelector(state =>
selectPortfolioCryptoBalanceBaseUnitByFilter(state, { assetId: foxWifHatAssetId }),
)

const { feeUsd, feeBps, foxDiscountPercent, feeUsdBeforeDiscount, feeBpsBeforeDiscount } =
calculateFees({
tradeAmountUsd: bn(tradeSizeUSD),
foxHeld: bn(foxHolding),
feeModel,
isSnapshotApiQueriesRejected,
foxWifHatHeldCryptoBaseUnit: bn(foxWifHatHeld),
// @TODO: remove this when thor swap discount is removed
thorHeld: bn(0),
})

const basedOnFeeTranslation: TextPropTypes['translation'] = useMemo(
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 { fromAccountId } from '@shapeshiftoss/caip'
import { foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type {
GetTradeQuoteInput,
Expand Down Expand Up @@ -36,6 +36,7 @@ import { swapperApi } from 'state/apis/swapper/swapperApi'
import type { ApiQuote, TradeQuoteError } from 'state/apis/swapper/types'
import {
selectPortfolioAccountMetadataByAccountId,
selectPortfolioCryptoBalanceBaseUnitByFilter,
selectUsdRateByAssetId,
} from 'state/slices/selectors'
import {
Expand Down Expand Up @@ -213,6 +214,9 @@ export const useGetTradeQuotes = () => {

const votingPower = useAppSelector(state => selectVotingPower(state, votingPowerParams))
const thorVotingPower = useAppSelector(state => selectVotingPower(state, thorVotingPowerParams))
const foxWifHatHeld = useAppSelector(state =>
selectPortfolioCryptoBalanceBaseUnitByFilter(state, { assetId: foxWifHatAssetId }),
)

const walletSupportsBuyAssetChain = useWalletSupportsChain(buyAsset.chainId, wallet)
const isBuyAssetChainSupported = walletSupportsBuyAssetChain
Expand Down Expand Up @@ -273,6 +277,7 @@ export const useGetTradeQuotes = () => {
tradeAmountUsd,
foxHeld: bnOrZero(votingPower),
thorHeld: bnOrZero(thorVotingPower),
foxWifHatHeldCryptoBaseUnit: bnOrZero(foxWifHatHeld),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand Down Expand Up @@ -321,6 +326,7 @@ export const useGetTradeQuotes = () => {
sellAsset,
sellAssetUsdRate,
thorVotingPower,
foxWifHatHeld,
userSlippageTolerancePercentageDecimal,
votingPower,
wallet,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { skipToken } from '@reduxjs/toolkit/dist/query'
import { fromAccountId } from '@shapeshiftoss/caip'
import { foxWifHatAssetId, fromAccountId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type { GetTradeRateInput, TradeRate } from '@shapeshiftoss/swapper'
import {
Expand Down Expand Up @@ -28,6 +28,7 @@ import {
} from 'state/apis/snapshot/selectors'
import { swapperApi } from 'state/apis/swapper/swapperApi'
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 {
Expand Down Expand Up @@ -135,6 +136,10 @@ export const useGetTradeRates = () => {
const sellAccountId = useAppSelector(selectFirstHopSellAccountId)
const buyAccountId = useAppSelector(selectLastHopBuyAccountId)

const foxWifHatHeld = useAppSelector(state =>
selectPortfolioCryptoBalanceBaseUnitByFilter(state, { assetId: foxWifHatAssetId }),
)

const userSlippageTolerancePercentageDecimal = useAppSelector(selectUserSlippagePercentageDecimal)

const sellAccountMetadataFilter = useMemo(
Expand Down Expand Up @@ -193,6 +198,7 @@ export const useGetTradeRates = () => {
// referentially invalidate, while ensuring the *initial* connection of a wallet when quotes were gotten without one, doesn't invalidate anything
sellAccountMetadata,
votingPower,
foxWifHatHeld,
thorVotingPower,
receiveAccountMetadata,
sellAccountId,
Expand Down Expand Up @@ -226,6 +232,7 @@ export const useGetTradeRates = () => {
tradeAmountUsd,
foxHeld: bnOrZero(votingPower),
thorHeld: bnOrZero(thorVotingPower),
foxWifHatHeldCryptoBaseUnit: bnOrZero(foxWifHatHeld),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand Down
9 changes: 9 additions & 0 deletions src/lib/fees/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { toBaseUnit } from 'lib/math'

// This timestamp is the 24th of January 2025
export const FOX_WIF_HAT_CAMPAIGN_STARTING_TIME_MS = 1737707777000
// Supposed to end the 5th of May 2025
export const FOX_WIF_HAT_CAMPAIGN_ENDING_TIME_MS = 1746396000000

// Assuming foxwifhat is 18 decimals
export const FOX_WIF_HAT_MINIMUM_AMOUNT_BASE_UNIT = toBaseUnit(10000, 18)
16 changes: 16 additions & 0 deletions src/lib/fees/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ describe('calculateFees', () => {
const { feeBps } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -49,6 +51,8 @@ describe('calculateFees', () => {
const { feeBps } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -62,6 +66,8 @@ describe('calculateFees', () => {
const { feeBps } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -75,6 +81,8 @@ describe('calculateFees', () => {
const { feeBps } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -88,6 +96,8 @@ describe('calculateFees', () => {
const { feeBps } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -101,6 +111,8 @@ describe('calculateFees', () => {
const { feeBps, foxDiscountPercent } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -115,6 +127,8 @@ describe('calculateFees', () => {
const { feeBps, foxDiscountPercent } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand All @@ -131,6 +145,8 @@ describe('calculateFees', () => {
const { feeBps, foxDiscountPercent } = calculateFees({
tradeAmountUsd,
foxHeld,
thorHeld: bn(0),
foxWifHatHeldCryptoBaseUnit: bn(0),
feeModel: 'SWAPPER',
isSnapshotApiQueriesRejected,
})
Expand Down
63 changes: 53 additions & 10 deletions src/lib/fees/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import BigNumber from 'bignumber.js'
import { getConfig } from 'config'
import { bn, bnOrZero } from 'lib/bignumber/bignumber'

import {
FOX_WIF_HAT_CAMPAIGN_ENDING_TIME_MS,
FOX_WIF_HAT_CAMPAIGN_STARTING_TIME_MS,
FOX_WIF_HAT_MINIMUM_AMOUNT_BASE_UNIT,
} from './constant'
import { FEE_CURVE_PARAMETERS } from './parameters'
import type { ParameterModel } from './parameters/types'

Expand All @@ -11,7 +16,8 @@ export const THORSWAP_MAXIMUM_YEAR_TRESHOLD = 2025
type CalculateFeeBpsArgs = {
tradeAmountUsd: BigNumber
foxHeld: BigNumber
thorHeld?: BigNumber
thorHeld: BigNumber
foxWifHatHeldCryptoBaseUnit: BigNumber
feeModel: ParameterModel
isSnapshotApiQueriesRejected: boolean
}
Expand Down Expand Up @@ -43,6 +49,7 @@ export const calculateFees: CalculateFeeBps = ({
foxHeld,
feeModel,
thorHeld,
foxWifHatHeldCryptoBaseUnit,
isSnapshotApiQueriesRejected,
}) => {
const {
Expand All @@ -59,13 +66,39 @@ export const calculateFees: CalculateFeeBps = ({
const midpointUsd = bn(FEE_CURVE_MIDPOINT_USD)
const feeCurveSteepness = bn(FEE_CURVE_STEEPNESS_K)
const isThorFreeEnabled = getConfig().REACT_APP_FEATURE_THOR_FREE_FEES
const isFoxWifHatEnabled = getConfig().REACT_APP_FEATURE_FOX_PAGE_FOX_WIF_HAT_SECTION

const isFoxWifHatCampaignActive =
new Date().getTime() >= FOX_WIF_HAT_CAMPAIGN_STARTING_TIME_MS &&
new Date().getTime() <= FOX_WIF_HAT_CAMPAIGN_ENDING_TIME_MS &&
isFoxWifHatEnabled

const isFoxWifHatDiscountEligible =
isFoxWifHatCampaignActive &&
foxWifHatHeldCryptoBaseUnit &&
foxWifHatHeldCryptoBaseUnit?.gte(FOX_WIF_HAT_MINIMUM_AMOUNT_BASE_UNIT)

const currentFoxWifHatDiscountPercent = (() => {
if (!isFoxWifHatCampaignActive) return bn(0)
if (!isFoxWifHatDiscountEligible) return bn(0)

const currentTime = new Date().getTime()
const totalCampaignDuration =
FOX_WIF_HAT_CAMPAIGN_ENDING_TIME_MS - FOX_WIF_HAT_CAMPAIGN_STARTING_TIME_MS
const timeElapsed = currentTime - FOX_WIF_HAT_CAMPAIGN_STARTING_TIME_MS
const remainingPercentage = bn(100).times(
bn(1).minus(bn(timeElapsed).div(totalCampaignDuration)),
)

return BigNumber.maximum(BigNumber.minimum(remainingPercentage, bn(100)), bn(0))
})()

// trades below the fee threshold are free.
const isFree = tradeAmountUsd.lt(noFeeThresholdUsd)

const isThorFree =
isThorFreeEnabled &&
thorHeld?.isGreaterThanOrEqualTo(THORSWAP_UNIT_THRESHOLD) &&
thorHeld.gte(THORSWAP_UNIT_THRESHOLD) &&
new Date().getUTCFullYear() < THORSWAP_MAXIMUM_YEAR_TRESHOLD

// failure to fetch fox discount results in free trades.
Expand All @@ -76,18 +109,22 @@ export const calculateFees: CalculateFeeBps = ({
if (isFree) return bn(100)
// THOR holder before TIP014 are trade free until 2025
if (isThorFree) return bn(100)
// No discount if we cannot fetch FOX holdings
if (isFallbackFees) return bn(0)

return BigNumber.minimum(
bn(100),
bnOrZero(foxHeld).times(100).div(bn(FEE_CURVE_FOX_MAX_DISCOUNT_THRESHOLD)),
)
const foxDiscountPercent = bnOrZero(foxHeld)
.times(100)
.div(bn(FEE_CURVE_FOX_MAX_DISCOUNT_THRESHOLD))

console.log({ foxDiscountPercent, currentFoxWifHatDiscountPercent })

// No discount if we cannot fetch FOX holdings and we are not eligible for the WIF HAT campaign
if (isFallbackFees && !isFoxWifHatDiscountEligible) return bn(0)

return BigNumber.maximum(foxDiscountPercent, currentFoxWifHatDiscountPercent)
})()

// the fee bps before the fox discount is applied, as a floating point number
const feeBpsBeforeDiscountFloat =
isFallbackFees && !isFree && !isThorFree
isFallbackFees && !isFree && !isThorFree && !isFoxWifHatDiscountEligible
? bn(FEE_CURVE_MAX_FEE_BPS)
: minFeeBps.plus(
maxFeeBps
Expand All @@ -107,7 +144,7 @@ export const calculateFees: CalculateFeeBps = ({
)

const feeBpsFloat =
isFallbackFees && !isFree && !isThorFree
isFallbackFees && !isFree && !isThorFree && !isFoxWifHatDiscountEligible
? bn(FEE_CURVE_MAX_FEE_BPS)
: BigNumber.maximum(
feeBpsBeforeDiscountFloat.multipliedBy(bn(1).minus(foxBaseDiscountPercent.div(100))),
Expand All @@ -121,6 +158,12 @@ export const calculateFees: CalculateFeeBps = ({
.div(feeBpsBeforeDiscountFloat)
.times(100)

console.log({
foxDiscountPercent: foxDiscountPercent.toFixed(2),
feeBpsFloat: feeBpsFloat.toFixed(2),
feeBpsBeforeDiscountFloat: feeBpsBeforeDiscountFloat.toFixed(2),
})

const feeBps = feeBpsAfterDiscount
const feeUsdBeforeDiscount = tradeAmountUsd.multipliedBy(feeBpsBeforeDiscount.div(bn(10000)))
const feeUsdDiscount = feeUsdBeforeDiscount.multipliedBy(foxDiscountPercent.div(100))
Expand Down
1 change: 0 additions & 1 deletion src/pages/Fox/components/FoxWifHat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const FoxWifHat = () => {
accountId={accountId}
amountCryptoBaseUnit={bnOrZero(claim.amount).toFixed()}
assetId={foxWifHatAssetId}
discountPercentDecimal={0.72}
// eslint-disable-next-line react-memo/require-usememo
onClaim={() => handleClaimModalOpen(accountId)}
/>
Expand Down
Loading

0 comments on commit 0a69e2a

Please sign in to comment.