Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(limit-orders): allow partner fee (variant B) #4467

Merged
merged 31 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
43bbaa7
refactor: separate hook for partner fee
shoom3301 May 20, 2024
ce6e455
refactor: simplify ReceiveAmountInfo interface
shoom3301 May 20, 2024
90da149
refactor: move ReceiveAmountInfo to trade module
shoom3301 May 20, 2024
d145a77
feat(limit-orders): display receive amount info after fees
shoom3301 May 20, 2024
3ad8c64
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 May 21, 2024
83a9550
refactor: clean up TradeRatesProps
shoom3301 May 21, 2024
809a157
refactor: unify RowFeeContent
shoom3301 May 21, 2024
4057caf
refactor: extract RowPartnerFee and RowFeeContent to trade module
shoom3301 May 21, 2024
2b0e85d
feat(limit-orders): display TradeRates with partner fee
shoom3301 May 21, 2024
e84d753
feat(limit-orders): take fees into account for trade rates
shoom3301 May 21, 2024
3f5c7ed
fix(limit-orders): adjust captions when partner fee is set
shoom3301 May 22, 2024
24c4f49
feat(limit-orders): display amount after fees in review modal
shoom3301 May 22, 2024
fc02261
feat(limit-orders): include fees into signed orders
shoom3301 May 22, 2024
48f1abc
feat(limit-orders): enable partnerFee for limit orders
shoom3301 May 22, 2024
27e5f3f
feat: take partner fee into account for displayed order price
shoom3301 May 22, 2024
851a972
feat: display partner fee in order receipt modal
shoom3301 May 22, 2024
056dbef
chore: fix build
shoom3301 May 22, 2024
151bb46
chore: fix build
shoom3301 May 22, 2024
6ae667f
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 May 23, 2024
f61e0aa
chore: update yarn lock
shoom3301 May 23, 2024
c309f0f
chore: fix build
shoom3301 May 23, 2024
40da539
fix: adjust limit orders titles
shoom3301 May 23, 2024
4ef5a44
fix: accordion state when no fee
shoom3301 May 23, 2024
b75f7e1
chore: fix limit order details style
shoom3301 May 23, 2024
60aedf2
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 May 27, 2024
564947d
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 May 27, 2024
6fd4e88
Merge branch 'develop' into feat/limit-orders-partner-fee
shoom3301 Jun 5, 2024
95c5e7b
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Jun 11, 2024
dad5ffe
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Jun 13, 2024
51b9da2
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Jun 18, 2024
a608bcc
chore: fix merge
shoom3301 Jun 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ export const defaultCurrencyInputPanelProps: CurrencyInputPanelProps & { priceIm
isIndependent: false,
receiveAmountInfo: {
type: 'from',
amountBeforeFees: '30',
amountAfterFees: '20',
amountAfterFeesRaw: CurrencyAmount.fromRawAmount(currency, 20 * 10 ** 18),
feeAmount: '10',
feeAmountRaw: CurrencyAmount.fromRawAmount(currency, 10 * 10 ** 18),
partnerFeeAmount: '0',
partnerFeeAmountRaw: CurrencyAmount.fromRawAmount(currency, 0),
amountBeforeFees: CurrencyAmount.fromRawAmount(currency, 30 * 10 ** 18),
amountAfterFees: CurrencyAmount.fromRawAmount(currency, 20 * 10 ** 18),
feeAmount: CurrencyAmount.fromRawAmount(currency, 10 * 10 ** 18),
partnerFeeAmount: CurrencyAmount.fromRawAmount(currency, 0),
},
currency,
balance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'

import { Field } from 'legacy/state/types'

import { ReceiveAmountInfo } from 'modules/swap/helpers/tradeReceiveAmount'
import { ReceiveAmountInfo } from 'modules/trade/types'

export interface CurrencyInfo {
label?: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState } from 'react'

import CarretIcon from '@cowprotocol/assets/cow-swap/carret-down.svg'
import { UI } from '@cowprotocol/ui'
import { FiatAmount, TokenAmount, UI } from '@cowprotocol/ui'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'

import SVG from 'react-inlinesvg'
import styled from 'styled-components/macro'
Expand Down Expand Up @@ -90,12 +91,19 @@ const ToggleIcon = styled.div<{ isOpen: boolean }>`

interface TradeDetailsAccordionProps {
rateInfo: React.ReactNode
feeSummary?: React.ReactNode // TODO: pass actual CurrencyAmount<Currency> here
feeTotalAmount: CurrencyAmount<Currency> | null
feeUsdTotalAmount: CurrencyAmount<Currency> | null
children?: React.ReactNode
open?: boolean
}

export const TradeDetailsAccordion = ({ rateInfo, feeSummary, children, open = false }: TradeDetailsAccordionProps) => {
export const TradeDetailsAccordion = ({
rateInfo,
feeTotalAmount,
feeUsdTotalAmount,
children,
open = false,
}: TradeDetailsAccordionProps) => {
const [isOpen, setIsOpen] = useState(open)

const toggleAccordion = () => {
Expand All @@ -121,7 +129,23 @@ export const TradeDetailsAccordion = ({ rateInfo, feeSummary, children, open = f
tabIndex={0}
isOpen={isOpen}
>
{!isOpen && feeSummary}
{!isOpen && (
<>
{feeUsdTotalAmount && feeUsdTotalAmount.greaterThan(0) ? (
<>
Fee <FiatAmount amount={feeUsdTotalAmount} />
</>
) : (
<>
{feeTotalAmount && (
<>
Fee <TokenAmount amount={feeTotalAmount} tokenSymbol={feeTotalAmount?.currency} />
</>
)}
</>
)}
</>
)}
<ToggleIcon isOpen={isOpen}>
<SVG src={CarretIcon} title={isOpen ? 'Close' : 'Open'} />
</ToggleIcon>
Expand Down
6 changes: 3 additions & 3 deletions apps/cowswap-frontend/src/legacy/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {

export default function Header() {
const { account, chainId } = useWalletInfo()
const injectedWidgetParams = useInjectedWidgetParams()
const { hideNetworkSelector, hideLogo } = useInjectedWidgetParams()
const isChainIdUnsupported = useIsProviderNetworkUnsupported()
const { pendingActivity } = useCategorizeRecentActivity()
const [darkMode, toggleDarkModeAux] = useDarkModeManager()
Expand Down Expand Up @@ -108,7 +108,7 @@ export default function Header() {
<Wrapper isMobileMenuOpen={isMobileMenuOpen}>
<HeaderModWrapper>
<HeaderRow>
{!injectedWidgetParams.hideLogo && (
{!hideLogo && (
<Title href={Routes.HOME} isMobileMenuOpen={isMobileMenuOpen}>
<UniIcon>
<LogoImage isMobileMenuOpen={isMobileMenuOpen}>
Expand All @@ -135,7 +135,7 @@ export default function Header() {
</HeaderRow>

<HeaderControls>
{!injectedWidgetParams.hideNetworkSelector && <NetworkSelector />}
{!hideNetworkSelector && <NetworkSelector />}

<HeaderElement>
{!isChainIdUnsupported && (isMobileMenuOpen || !isUpToLarge || isUpToTiny) && (
Expand Down
65 changes: 59 additions & 6 deletions apps/cowswap-frontend/src/legacy/state/orders/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import type { LatestAppDataDocVersion } from '@cowprotocol/app-data'
import { ONE_HUNDRED_PERCENT, PENDING_ORDERS_BUFFER, ZERO_FRACTION } from '@cowprotocol/common-const'
import { buildPriceFromCurrencyAmounts, isSellOrder } from '@cowprotocol/common-utils'
import { bpsToPercent, buildPriceFromCurrencyAmounts, isSellOrder } from '@cowprotocol/common-utils'
import { EnrichedOrder, OrderKind, OrderStatus } from '@cowprotocol/cow-sdk'
import { UiOrderType } from '@cowprotocol/types'
import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core'
import type { PartnerFee } from '@cowprotocol/widget-lib'
import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core'

import JSBI from 'jsbi'

import { decodeAppData } from 'modules/appData/utils/decodeAppData'

import { getIsComposableCowParentOrder } from 'utils/orderUtils/getIsComposableCowParentOrder'
import { getOrderSurplus } from 'utils/orderUtils/getOrderSurplus'
import { getUiOrderType } from 'utils/orderUtils/getUiOrderType'
import type { ParsedOrder } from 'utils/orderUtils/parseOrder'

import { Order, updateOrder, UpdateOrderParams as UpdateOrderParamsAction } from './actions'
import { OUT_OF_MARKET_PRICE_DELTA_PERCENTAGE } from './consts'
Expand Down Expand Up @@ -224,10 +229,8 @@ export function getEstimatedExecutionPrice(
): Price<Currency, Currency> | null {
// Build CurrencyAmount and Price instances
const feeAmount = CurrencyAmount.fromRawAmount(order.inputToken, fee)
// Always use original amounts for building the limit price, as this will never change
const inputAmount = CurrencyAmount.fromRawAmount(order.inputToken, order.sellAmount.toString())
const outputAmount = CurrencyAmount.fromRawAmount(order.outputToken, order.buyAmount.toString())
const limitPrice = buildPriceFromCurrencyAmounts(inputAmount, outputAmount)
// Take partner fee into account when calculating the limit price
const limitPrice = getOrderLimitPriceWithPartnerFee(order)

if (getUiOrderType(order) === UiOrderType.SWAP) {
return limitPrice
Expand Down Expand Up @@ -374,3 +377,53 @@ export function partialOrderUpdate({ chainId, order, isSafeWallet }: UpdateOrder
}
dispatch(updateOrder(params))
}

export function getOrderPartnerFee(fullAppData: EnrichedOrder['fullAppData']): PartnerFee | undefined {
const appData = decodeAppData(fullAppData) as LatestAppDataDocVersion

return appData?.metadata?.partnerFee
}

export function getOrderAmountsWithPartnerFee(
fullAppData: EnrichedOrder['fullAppData'],
sellAmount: CurrencyAmount<Token>,
buyAmount: CurrencyAmount<Token>,
isSellOrder: boolean
): { inputCurrencyAmount: CurrencyAmount<Token>; outputCurrencyAmount: CurrencyAmount<Token> } {
const partnerFee = getOrderPartnerFee(fullAppData)

if (!partnerFee) {
return {
inputCurrencyAmount: sellAmount,
outputCurrencyAmount: buyAmount,
}
}

const partnerFeePercent = bpsToPercent(partnerFee.bps)

if (isSellOrder) {
return {
inputCurrencyAmount: sellAmount,
outputCurrencyAmount: buyAmount.divide(ONE_HUNDRED_PERCENT.subtract(partnerFeePercent)),
}
}

return {
inputCurrencyAmount: sellAmount.divide(ONE_HUNDRED_PERCENT.add(partnerFeePercent)),
outputCurrencyAmount: buyAmount,
}
}

export function getOrderLimitPriceWithPartnerFee(order: Order | ParsedOrder): Price<Currency, Currency> {
const inputAmount = CurrencyAmount.fromRawAmount(order.inputToken, order.sellAmount.toString())
const outputAmount = CurrencyAmount.fromRawAmount(order.outputToken, order.buyAmount.toString())

const { inputCurrencyAmount, outputCurrencyAmount } = getOrderAmountsWithPartnerFee(
order.fullAppData,
inputAmount,
outputAmount,
isSellOrder(order.kind)
)

return buildPriceFromCurrencyAmounts(inputCurrencyAmount, outputCurrencyAmount)
}
22 changes: 8 additions & 14 deletions apps/cowswap-frontend/src/mocks/tradeStateMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@ export const inputCurrencyInfoMock: CurrencyInfo = {
isIndependent: false,
receiveAmountInfo: {
type: 'from',
amountBeforeFees: '30',
amountAfterFees: '20',
amountAfterFeesRaw: CurrencyAmount.fromRawAmount(inputCurrency, 20 * 10 ** 18),
feeAmount: '10',
feeAmountRaw: CurrencyAmount.fromRawAmount(inputCurrency, 10 * 10 ** 18),
partnerFeeAmount: '25',
partnerFeeAmountRaw: CurrencyAmount.fromRawAmount(inputCurrency, 0),
amountBeforeFees: CurrencyAmount.fromRawAmount(inputCurrency, 30 * 10 ** 18),
amountAfterFees: CurrencyAmount.fromRawAmount(inputCurrency, 20 * 10 ** 18),
feeAmount: CurrencyAmount.fromRawAmount(inputCurrency, 10 * 10 ** 18),
partnerFeeAmount: CurrencyAmount.fromRawAmount(inputCurrency, 0),
},
currency: inputCurrency,
balance: CurrencyAmount.fromRawAmount(inputCurrency, 250 * 10 ** 18),
Expand All @@ -36,13 +33,10 @@ export const outputCurrencyInfoMock: CurrencyInfo = {
isIndependent: false,
receiveAmountInfo: {
type: 'from',
amountBeforeFees: '30',
amountAfterFees: '20',
amountAfterFeesRaw: CurrencyAmount.fromRawAmount(outputCurrency, 20 * 10 ** 18),
feeAmount: '10',
feeAmountRaw: CurrencyAmount.fromRawAmount(outputCurrency, 10 * 10 ** 18),
partnerFeeAmount: '0',
partnerFeeAmountRaw: CurrencyAmount.fromRawAmount(outputCurrency, 0),
amountBeforeFees: CurrencyAmount.fromRawAmount(outputCurrency, 30 * 10 ** 18),
amountAfterFees: CurrencyAmount.fromRawAmount(outputCurrency, 20 * 10 ** 18),
feeAmount: CurrencyAmount.fromRawAmount(outputCurrency, 10 * 10 ** 18),
partnerFeeAmount: CurrencyAmount.fromRawAmount(outputCurrency, 0),
},
currency: outputCurrency,
balance: CurrencyAmount.fromRawAmount(outputCurrency, 250 * 10 ** 18),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useWalletInfo } from '@cowprotocol/wallet'
import { PartnerFee } from '@cowprotocol/widget-lib'
import { Percent } from '@uniswap/sdk-core'

import { useInjectedWidgetParams } from 'modules/injectedWidget'
import { useWidgetPartnerFee } from 'modules/injectedWidget'
import { useAppCodeWidgetAware } from 'modules/injectedWidget/hooks/useAppCodeWidgetAware'
import { useReplacedOrderUid } from 'modules/trade/state/alternativeOrder'
import { useUtm } from 'modules/utm'
Expand All @@ -17,7 +17,7 @@ import { AppDataInfoUpdater, UseAppDataParams } from './AppDataInfoUpdater'
import { useAppCode, useAppDataHooks } from '../hooks'
import { AppDataOrderClass } from '../types'

const ORDERS_WITH_PARTNER_FEE: AppDataOrderClass[] = ['market']
const ORDERS_WITH_PARTNER_FEE: AppDataOrderClass[] = ['market', 'limit']

const mapPartnerFee = (
partnerFee: PartnerFee | undefined,
Expand Down Expand Up @@ -47,7 +47,7 @@ export const AppDataUpdater = React.memo(({ slippage, orderClass }: AppDataUpdat
const utm = useUtm()
const hooks = useAppDataHooks()
const appCodeWithWidgetMetadata = useAppCodeWidgetAware(appCode)
const widgetParams = useInjectedWidgetParams()
const partnerFee = useWidgetPartnerFee()
const replacedOrderUid = useReplacedOrderUid()

if (!chainId) return null
Expand All @@ -60,7 +60,7 @@ export const AppDataUpdater = React.memo(({ slippage, orderClass }: AppDataUpdat
orderClass={orderClass}
utm={utm}
hooks={hooks}
partnerFee={mapPartnerFee(widgetParams.partnerFee, orderClass, chainId)}
partnerFee={mapPartnerFee(partnerFee, orderClass, chainId)}
replacedOrderUid={replacedOrderUid}
/>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { useAtomValue } from 'jotai'

import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetAppParams, PartnerFee } from '@cowprotocol/widget-lib'

import { injectedWidgetParamsAtom } from '../state/injectedWidgetParamsAtom'
import { injectedWidgetParamsAtom, injectedWidgetPartnerFeeAtom } from '../state/injectedWidgetParamsAtom'

export function useInjectedWidgetParams(): Partial<CowSwapWidgetAppParams> {
const { params } = useAtomValue(injectedWidgetParamsAtom)

return params
}

export function useWidgetPartnerFee(): PartnerFee | undefined {
return useAtomValue(injectedWidgetPartnerFeeAtom)
}
2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/modules/injectedWidget/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { InjectedWidgetUpdater } from './updaters/InjectedWidgetUpdater'
export { CowEventsUpdater } from './updaters/CowEventsUpdater'
export { useInjectedWidgetParams } from './hooks/useInjectedWidgetParams'
export { useInjectedWidgetParams, useWidgetPartnerFee } from './hooks/useInjectedWidgetParams'
export { useInjectedWidgetMetaData } from './hooks/useInjectedWidgetMetaData'
export { useInjectedWidgetPalette } from './hooks/useInjectedWidgetPalette'
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export const injectedWidgetParamsAtom = atom<{ params: Partial<CowSwapWidgetAppP
params: {},
errors: {},
})

export const injectedWidgetPartnerFeeAtom = atom((get) => get(injectedWidgetParamsAtom).params.partnerFee)
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ import { executionPriceAtom } from 'modules/limitOrders/state/executionPriceAtom
import { limitOrdersSettingsAtom } from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { limitRateAtom } from 'modules/limitOrders/state/limitRateAtom'
import { partiallyFillableOverrideAtom } from 'modules/limitOrders/state/partiallyFillableOverride'
import { TradeConfirmation, TradeConfirmModal, useTradeConfirmActions } from 'modules/trade'
import { ReceiveAmountInfo, TradeConfirmation, TradeConfirmModal, useTradeConfirmActions } from 'modules/trade'

import { useIsSafeApprovalBundle } from 'common/hooks/useIsSafeApprovalBundle'
import { useRateInfoParams } from 'common/hooks/useRateInfoParams'
import { CurrencyPreviewInfo } from 'common/pure/CurrencyAmountPreview'

import { LOW_RATE_THRESHOLD_PERCENT } from '../../const/trade'
Expand All @@ -32,11 +31,12 @@ export interface LimitOrdersConfirmModalProps {
inputCurrencyInfo: CurrencyPreviewInfo
outputCurrencyInfo: CurrencyPreviewInfo
priceImpact: PriceImpact
receiveAmountInfo: ReceiveAmountInfo | null
recipient: string | null
}

export function LimitOrdersConfirmModal(props: LimitOrdersConfirmModalProps) {
const { inputCurrencyInfo, outputCurrencyInfo, tradeContext: tradeContextInitial, priceImpact, recipient } = props
const { inputCurrencyInfo, outputCurrencyInfo, receiveAmountInfo, tradeContext: tradeContextInitial, priceImpact, recipient } = props

/**
* This is a very important part of the code.
Expand All @@ -55,10 +55,8 @@ export function LimitOrdersConfirmModal(props: LimitOrdersConfirmModalProps) {
const partiallyFillableOverride = useAtom(partiallyFillableOverrideAtom)

const { amount: inputAmount } = inputCurrencyInfo
const { amount: outputAmount } = outputCurrencyInfo

const rateImpact = useRateImpact()
const rateInfoParams = useRateInfoParams(inputAmount, outputAmount)

const tradeConfirmActions = useTradeConfirmActions()

Expand Down Expand Up @@ -97,7 +95,7 @@ export function LimitOrdersConfirmModal(props: LimitOrdersConfirmModalProps) {
<LimitOrdersDetails
limitRateState={limitRateState}
tradeContext={tradeContext}
rateInfoParams={rateInfoParams}
receiveAmountInfo={receiveAmountInfo}
settingsState={settingsState}
executionPrice={executionPrice}
partiallyFillableOverride={partiallyFillableOverride}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) {
useLimitOrdersDerivedState()
const tradeQuote = useTradeQuote()
const priceImpactParams = useTradePriceImpact()
const widgetParams = useInjectedWidgetParams()
const { banners: widgetBanners } = useInjectedWidgetParams()

const isBundling = primaryFormValidation && FORM_STATES_TO_SHOW_BUNDLE_BANNER.includes(primaryFormValidation)

Expand All @@ -89,7 +89,7 @@ export function LimitOrdersWarnings(props: LimitOrdersWarningsProps) {
!showApprovalBundlingBanner &&
isSafeViaWc &&
primaryFormValidation === TradeFormValidation.ApproveRequired &&
!widgetParams?.banners?.hideSafeWebAppBanner
!widgetBanners?.hideSafeWebAppBanner

// TODO: implement Safe App EthFlow bundling for LIMIT and disable the warning in that case
const showNativeSellWarning = primaryFormValidation === TradeFormValidation.SellNativeToken
Expand Down
Loading
Loading