From 44ea47799dacba28a6f8c64882e657566c9cf2f0 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:50:00 +0100 Subject: [PATCH 01/10] chore: temporarily using local version of cow-sdk --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6d26d07d41..c6d0d9072e 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@cowprotocol/cms": "^0.9.0", "@cowprotocol/contracts": "^1.3.1", "@cowprotocol/cow-runner-game": "^0.2.9", - "@cowprotocol/cow-sdk": "^5.8.0", + "@cowprotocol/cow-sdk": "file:.yalc/@cowprotocol/cow-sdk", "@cowprotocol/ethflowcontract": "cowprotocol/ethflowcontract.git#main-artifacts", "@davatar/react": "1.8.1", "@emotion/react": "^11.11.1", @@ -343,4 +343,4 @@ "vite-tsconfig-paths": "~4.3.2", "vitest": "~0.32.0" } -} \ No newline at end of file +} From fdef42305a95369188e9f79a911361da487ea6ed Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:50:33 +0100 Subject: [PATCH 02/10] feat: add new types to Trade and Order --- apps/explorer/src/api/operator/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/explorer/src/api/operator/types.ts b/apps/explorer/src/api/operator/types.ts index 8ef8bf3dba..2734b39e37 100644 --- a/apps/explorer/src/api/operator/types.ts +++ b/apps/explorer/src/api/operator/types.ts @@ -37,6 +37,8 @@ export type Order = Pick< executedFeeAmount: BigNumber executedSurplusFee: BigNumber | null totalFee: BigNumber + networkCosts?: BigNumber + protocolFees?: BigNumber cancelled: boolean status: OrderStatus partiallyFilled: boolean @@ -55,7 +57,7 @@ export type RawTrade = TradeMetaData /** * Enriched Trade type */ -export type Trade = Pick & { +export type Trade = Pick & { orderId: string kind?: OrderKind buyAmount: BigNumber From ade7e194e518b554c50f3b8f4b8e4c68ed862be1 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:50:56 +0100 Subject: [PATCH 03/10] feat: clean up NumbersBreakdown --- .../orders/NumbersBreakdown/index.tsx | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 apps/explorer/src/components/orders/NumbersBreakdown/index.tsx diff --git a/apps/explorer/src/components/orders/NumbersBreakdown/index.tsx b/apps/explorer/src/components/orders/NumbersBreakdown/index.tsx new file mode 100644 index 0000000000..f3f6af4a15 --- /dev/null +++ b/apps/explorer/src/components/orders/NumbersBreakdown/index.tsx @@ -0,0 +1,62 @@ +import { PropsWithChildren } from 'react' + +import { Media } from '@cowprotocol/ui' + +import useSafeState from 'hooks/useSafeState' +import styled from 'styled-components/macro' + +const ShowMoreButton = styled.button` + font-size: 1.4rem; + border: none; + background: none; + color: ${({ theme }) => theme.textActive1}; + margin: 0; + padding: 0; + + &:hover { + text-decoration: underline; + cursor: pointer; + } +` + +const DetailsWrapper = styled.div` + display: flex; + margin: 1.8rem 0 1rem; + border-radius: 0.6rem; + line-height: 1.6; + width: max-content; + align-items: flex-start; + word-break: break-all; + overflow: auto; + border: 1px solid rgb(151 151 184 / 10%); + padding: 0.6rem; + background: rgb(151 151 184 / 10%); + + ${Media.upToSmall()} { + width: 100%; + } + + table { + width: 100%; + border-collapse: collapse; + } +` + +type BreakdownProps = { + showExpanded?: boolean +} & PropsWithChildren + +export const NumbersBreakdown = ({ children, showExpanded = false }: BreakdownProps): React.ReactNode => { + const [showDetails, setShowDetails] = useSafeState(showExpanded) + + const handleToggle = async (): Promise => { + setShowDetails(!showDetails) + } + + return ( + <> + {showDetails ? '[-] Show less' : '[+] Show more'} + {showDetails && {children}} + + ) +} From 13e7eb512a77ff3c521fbec6130ab188f256dc51 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:51:58 +0100 Subject: [PATCH 04/10] feat: add initial implementation of getFees --- apps/explorer/src/utils/operator.ts | 62 ++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/apps/explorer/src/utils/operator.ts b/apps/explorer/src/utils/operator.ts index c0bc291eef..90b5ddc10f 100644 --- a/apps/explorer/src/utils/operator.ts +++ b/apps/explorer/src/utils/operator.ts @@ -1,12 +1,11 @@ import { isSellOrder } from '@cowprotocol/common-utils' -import { Trade as TradeMetaData } from '@cowprotocol/cow-sdk' import { calculatePrice, invertPrice, TokenErc20 } from '@gnosis.pm/dex-js' import BigNumber from 'bignumber.js' import { ZERO_BIG_NUMBER } from 'const' import { formatSmartMaxPrecision, formattingAmountPrecision } from 'utils' -import { Order, OrderStatus, RawOrder, Trade } from 'api/operator/types' +import { Order, OrderStatus, RawOrder, RawTrade, Trade } from 'api/operator/types' import { PENDING_ORDERS_BUFFER } from '../explorer/const' @@ -341,7 +340,7 @@ export enum FormatAmountPrecision { export function formattedAmount( erc20: TokenErc20 | null | undefined, amount: BigNumber, - typePrecision: FormatAmountPrecision = FormatAmountPrecision.maxPrecision + typePrecision: FormatAmountPrecision = FormatAmountPrecision.maxPrecision, ): string { if (!isTokenErc20(erc20)) return '-' @@ -407,13 +406,13 @@ export function transformOrder(rawOrder: RawOrder): Order { filledPercentage, surplusAmount, surplusPercentage, - } as Order + } } /** * Transforms a RawTrade into a Trade object */ -export function transformTrade(rawTrade: TradeMetaData, order: Order, executionTimestamp?: number): Trade { +export function transformTrade(rawTrade: RawTrade, order: Order, executionTimestamp?: number): Trade { const { orderUid, buyAmount, sellAmount, sellAmountBeforeFees, buyToken, sellToken, ...rest } = rawTrade const { amount, percentage } = getTradeSurplus(rawTrade, order) @@ -432,7 +431,7 @@ export function transformTrade(rawTrade: TradeMetaData, order: Order, executionT } } -export function getTradeSurplus(rawTrade: TradeMetaData, order: Order): Surplus { +export function getTradeSurplus(rawTrade: RawTrade, order: Order): Surplus { const params: PartialFillSurplusParams = { sellAmount: order.sellAmount, buyAmount: order.buyAmount, @@ -444,3 +443,54 @@ export function getTradeSurplus(rawTrade: TradeMetaData, order: Order): Surplus return surplus || ZERO_SURPLUS } + +export type OrderFees = { networkCosts: BigNumber | undefined; protocolFees: BigNumber | undefined } + +export function getFees(order: Order, trades: Trade[]): OrderFees { + // No trades, no fees + if (trades.length < 1) { + return { networkCosts: undefined, protocolFees: undefined } + } + // This should always be at most 1, but just in case, let's do a map for each token + const feesMap = new Map() + + trades.forEach(({ executedProtocolFees }) => { + executedProtocolFees?.forEach(({ amount, token, policy: _policy }) => { + if (!amount || !token) { + return + } + const tokenFee = feesMap.get(token) || ZERO_BIG_NUMBER + const feeAmount = new BigNumber(amount).plus(tokenFee) + feesMap.set(token, feeAmount) + }) + }) + + // No fees collected for this order + if (feesMap.size === 0) { + console.debug(`[getFees] No fees for order: ${order.uid}`) + return { networkCosts: ZERO_BIG_NUMBER, protocolFees: ZERO_BIG_NUMBER } + } + + if (feesMap.size > 1) { + console.warn(`[getFees] Order has fees in more than once currency: ${order.uid}`) + // TODO: deal with this case. Not sure it's even possible. Maybe pick only the surplus token, or convert all to a single currency? + return { networkCosts: undefined, protocolFees: undefined } + } + + // TODO: get surplus token and check it maches the fee token + + const [[_surplusToken, protocolFees]] = Array.from(feesMap.entries()) + + const networkCosts = order.totalFee.minus(protocolFees) + + console.debug( + `[getFees] Fees for order: ${order.uid}`, + _surplusToken, + isSellOrder(order.kind) ? _surplusToken === order.buyTokenAddress : _surplusToken === order.sellTokenAddress, + protocolFees.toString(10), + networkCosts.toString(10), + order.totalFee.toString(10), + ) + + return { protocolFees, networkCosts } +} From 9d07af71d0e55f458fe28dd83c222fff7c3d450b Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:52:13 +0100 Subject: [PATCH 05/10] feat: pass down trades fees to order --- .../components/orders/OrderDetails/index.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/explorer/src/components/orders/OrderDetails/index.tsx b/apps/explorer/src/components/orders/OrderDetails/index.tsx index debf07668d..9b2394ae81 100644 --- a/apps/explorer/src/components/orders/OrderDetails/index.tsx +++ b/apps/explorer/src/components/orders/OrderDetails/index.tsx @@ -20,7 +20,7 @@ import { useQuery, useUpdateQueryString } from 'hooks/useQuery' import { useNetworkId } from 'state/network' import styled from 'styled-components/macro' import { Errors } from 'types' -import { formatPercentage } from 'utils' +import { formatPercentage, getFees } from 'utils' import { Order, Trade } from 'api/operator' @@ -92,7 +92,7 @@ const tabItems = ( isPriceInverted: boolean, invertPrice: Command, ): TabItemInterface[] => { - const order = getOrderWithTxHash(_order, trades) + const order = addTradesDataToOrder(_order, trades) const areTokensLoaded = order?.buyToken && order?.sellToken const isLoadingForTheFirstTime = isOrderLoading && !areTokensLoaded const filledPercentage = order?.filledPercentage && formatPercentage(order.filledPercentage) @@ -145,10 +145,25 @@ const tabItems = ( * * That is the case for any filled fill or kill or a partial fill that has a single trade */ -function getOrderWithTxHash(order: Order | null, trades: Trade[]): Order | null { - if (order && trades.length === 1) { - return { ...order, txHash: trades[0].txHash || undefined, executionDate: trades[0].executionTime || undefined } +function addTradesDataToOrder(order: Order | null, trades: Trade[]): Order | null { + if (order) { + let txHash: undefined | string = undefined + let executionDate: undefined | Date = undefined + + // Only if single trade, otherwise this info will be in the fills tab + if (trades.length === 1) { + // txHash + txHash = trades[0].txHash || undefined + // executionDate + executionDate = trades[0].executionTime || undefined + } + + // fee related + const { networkCosts, protocolFees } = getFees(order, trades) + + return { ...order, txHash, executionDate, networkCosts, protocolFees } } + return order } From e4f71a28957f70a91ec0e0f52f6aaad1f35d03b9 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 23 Oct 2024 10:54:13 +0100 Subject: [PATCH 06/10] feat: initial display of networks costs and protocol fees --- .../components/orders/GasFeeDisplay/index.tsx | 147 +++++++++++------- 1 file changed, 94 insertions(+), 53 deletions(-) diff --git a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx index 161939edb9..f6e2373bca 100644 --- a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx +++ b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx @@ -1,12 +1,21 @@ // TODO: Enable once API is ready // import { NumbersBreakdown } from 'components/orders/NumbersBreakdown' +import { useMemo } from 'react' + +import { isSellOrder } from '@cowprotocol/common-utils' +import { Nullish } from '@cowprotocol/ui' + +import { TokenErc20 } from '@gnosis.pm/dex-js' +import BigNumber from 'bignumber.js' import { ZERO_BIG_NUMBER } from 'const' import styled from 'styled-components/macro' import { formatSmartMaxPrecision, safeTokenName } from 'utils' import { Order } from 'api/operator' +import { NumbersBreakdown } from '../NumbersBreakdown' + const Wrapper = styled.div` > span { margin: 0 0.5rem 0 0; @@ -15,55 +24,11 @@ const Wrapper = styled.div` export type Props = { order: Order } -// TODO: Enable once API is ready -// const fetchFeeBreakdown = async (initialFee: string): Promise => { -// // TODO: Simulating API call to fetch fee breakdown data -// return new Promise((resolve) => { -// resolve({ -// networkCosts: 'TODO: Get network costs here', -// fee: 'TODO: Get fee here', -// total: initialFee, -// }) -// }) -// } +export function GasFeeDisplay({ order }: Props): React.ReactNode | null { + const { feeAmount, totalFee, fullyFilled } = order -// TODO: Enable once API is ready -// const renderFeeBreakdown = (data: any): React.ReactNode => { -// return ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -//
Network Costs:{data.networkCosts}
Fee:{data.fee}
Total Costs & Fees:{data.total}
-// ) -// } - -export function GasFeeDisplay(props: Props): React.ReactNode | null { - const { - order: { feeAmount, sellToken, sellTokenAddress, fullyFilled, totalFee }, - } = props - - let formattedExecutedFee: string = totalFee.toString(10) - let formattedTotalFee: string = feeAmount.toString(10) - let quoteSymbol: string = sellTokenAddress - - if (sellToken) { - formattedExecutedFee = formatSmartMaxPrecision(totalFee, sellToken) - formattedTotalFee = formatSmartMaxPrecision(feeAmount, sellToken) - - quoteSymbol = safeTokenName(sellToken) - } + const { quoteSymbol, formattedNetworkCosts, formattedProtocolFees, formattedExecutedFee, formattedTotalFee } = + useMemo(() => getFeeDisplayAmounts(order), [order]) const noFee = feeAmount.isZero() && totalFee.isZero() @@ -83,11 +48,87 @@ export function GasFeeDisplay(props: Props): React.ReactNode | null { return ( {FeeElement} - {/*TODO: Enable once API is ready*/} - {/* fetchFeeBreakdown(`${formattedExecutedFee} ${quoteSymbol}`)}*/} - {/* renderContent={renderFeeBreakdown}*/} - {/*/>*/} + + + + {formattedNetworkCosts && ( + + + + + )} + {formattedProtocolFees && ( + + + + + )} + + + + + +
Network Costs:{formattedNetworkCosts}
Fee:{formattedProtocolFees}
Total Costs & Fees:{formattedExecutedFee}
+
) } + +function getFeeDisplayAmounts(order: Order) { + const { + kind, + networkCosts, + protocolFees, + sellToken, + sellTokenAddress, + buyToken, + buyTokenAddress, + totalFee, + feeAmount, + } = order + + const isSell = isSellOrder(kind) + + let quoteSymbol = '' + let formattedNetworkCosts = '' + let formattedProtocolFees = '' + let formattedExecutedFee = '' + let formattedTotalFee = '' + + // When these 2 are set, for sure we have new style fees + if (networkCosts || protocolFees) { + if (isSell) { + quoteSymbol = buyToken ? safeTokenName(buyToken) : buyTokenAddress + formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, buyToken) + formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, buyToken) + formattedExecutedFee = getFormattedAmount(totalFee, buyToken) + formattedTotalFee = getFormattedAmount(feeAmount, buyToken) + } else { + quoteSymbol = sellToken ? safeTokenName(sellToken) : sellTokenAddress + formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, sellToken) + formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, sellToken) + formattedExecutedFee = getFormattedAmount(totalFee, sellToken) + formattedTotalFee = getFormattedAmount(feeAmount, sellToken) + } + } else { + // Otherwise, it can have no fees OR be old style fees, without the policies + // TODO: handle old and new styles, as the fee token will vary! (always sell for old vs surplus token for new) + quoteSymbol = sellToken ? safeTokenName(sellToken) : sellTokenAddress + formattedNetworkCosts = '' + formattedProtocolFees = '' + formattedExecutedFee = getFormattedAmount(totalFee, sellToken) + formattedTotalFee = getFormattedAmount(feeAmount, sellToken) + } + + return { + quoteSymbol, + formattedNetworkCosts, + formattedProtocolFees, + formattedExecutedFee, + formattedTotalFee, + } +} + +function getFormattedAmount(amount: BigNumber, token: Nullish): string { + return token ? formatSmartMaxPrecision(amount, token) : amount.toString(10) +} From 4473ea21ed541ec2ab284410e2510ea050e872fe Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Mon, 30 Dec 2024 13:23:25 +0000 Subject: [PATCH 07/10] chore: remove todo --- apps/explorer/src/components/orders/GasFeeDisplay/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx index f6e2373bca..6e17bd7d20 100644 --- a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx +++ b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx @@ -1,6 +1,3 @@ -// TODO: Enable once API is ready -// import { NumbersBreakdown } from 'components/orders/NumbersBreakdown' - import { useMemo } from 'react' import { isSellOrder } from '@cowprotocol/common-utils' @@ -8,13 +5,13 @@ import { Nullish } from '@cowprotocol/ui' import { TokenErc20 } from '@gnosis.pm/dex-js' import BigNumber from 'bignumber.js' +import { NumbersBreakdown } from 'components/orders/NumbersBreakdown' import { ZERO_BIG_NUMBER } from 'const' import styled from 'styled-components/macro' import { formatSmartMaxPrecision, safeTokenName } from 'utils' import { Order } from 'api/operator' -import { NumbersBreakdown } from '../NumbersBreakdown' const Wrapper = styled.div` > span { From 5ca53e00e5a0b5f983ad5bc3a12c9199e2bc09a5 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Mon, 30 Dec 2024 17:27:41 +0000 Subject: [PATCH 08/10] feat: replace executedSurplusFee with executedSuplus Also use totalFee where applicable --- .../src/legacy/state/orders/actions.ts | 11 +++++-- .../src/legacy/state/orders/reducer.ts | 17 +++++----- .../pure/ReceiptModal/FeeField.tsx | 33 +++++++++++++++---- .../src/utils/orderUtils/parseOrder.ts | 12 +++++-- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts index 4630be0d51..89bd865995 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts @@ -107,7 +107,9 @@ export type OrderInfoApi = Pick< | 'executedSellAmount' | 'executedSellAmountBeforeFees' | 'executedFeeAmount' - | 'executedSurplusFee' + | 'executedFee' + | 'executedFeeToken' + | 'totalFee' | 'invalidated' | 'ethflowData' | 'onchainOrderData' @@ -139,6 +141,7 @@ export interface AddPendingOrderParams { order: SerializedOrder isSafeWallet: boolean } + export type ChangeOrderStatusParams = { id: UID; chainId: ChainId } export type SetOrderCancellationHashParams = ChangeOrderStatusParams & { hash: string } @@ -177,11 +180,13 @@ export interface BatchOrdersUpdateParams { } export type PresignedOrdersParams = BatchOrdersUpdateParams + export interface UpdatePresignGnosisSafeTxParams { orderId: UID chainId: ChainId safeTransaction: SafeMultisigTransactionResponse } + export type ExpireOrdersBatchParams = BatchOrdersUpdateParams export type InvalidateOrdersBatchParams = BatchOrdersUpdateParams export type CancelOrdersBatchParams = BatchOrdersUpdateParams @@ -196,7 +201,7 @@ export const fulfillOrdersBatch = createAction('order/ export const preSignOrders = createAction('order/presignOrders') export const updatePresignGnosisSafeTx = createAction( - 'order/updatePresignGnosisSafeTx' + 'order/updatePresignGnosisSafeTx', ) export const expireOrdersBatch = createAction('order/expireOrdersBatch') @@ -214,7 +219,7 @@ export const deleteOrders = createAction('order/deleteOrders export const clearOrders = createAction<{ chainId: ChainId }>('order/clearOrders') export const updateLastCheckedBlock = createAction<{ chainId: ChainId; lastCheckedBlock: number }>( - 'order/updateLastCheckedBlock' + 'order/updateLastCheckedBlock', ) export const clearOrdersStorage = createAction('order/clearOrdersStorage') diff --git a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts index 4e32f45fd1..f3068c7554 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts @@ -121,7 +121,7 @@ export function getDefaultNetworkState(chainId: ChainId): OrdersStateNetwork { // makes sure there's always an object at state[chainId], state[chainId].pending | .fulfilled function prefillState( state: Writable, - { payload: { chainId } }: PayloadAction + { payload: { chainId } }: PayloadAction, ): asserts state is Required { const stateAtChainId = state[chainId] @@ -174,7 +174,7 @@ function addOrderToState( id: string, status: OrderTypeKeys, order: SerializedOrder, - isSafeWallet: boolean + isSafeWallet: boolean, ): void { // Attempt to fix `TypeError: Cannot add property , object is not extensible` // seen on https://user-images.githubusercontent.com/34510341/138450105-bb94a2d1-656e-4e15-ae99-df9fb33c8ca4.png @@ -200,7 +200,7 @@ function cancelOrderInState( state: Required, chainId: ChainId, orderObject: OrderObject, - isSafeWallet: boolean + isSafeWallet: boolean, ) { const id = orderObject.id @@ -368,12 +368,13 @@ export default createReducer(initialState, (builder) => orderObject.order.apiAdditionalInfo = { creationDate: order.creationDate, - availableBalance: order.availableBalance, executedBuyAmount: order.executedBuyAmount, executedSellAmount: order.executedSellAmount, executedSellAmountBeforeFees: order.executedSellAmountBeforeFees, executedFeeAmount: order.executedFeeAmount, - executedSurplusFee: order.executedSurplusFee, + executedFee: order.executedFee, + executedFeeToken: order.executedFeeToken, + totalFee: order.totalFee, invalidated: order.invalidated, ethflowData: order.ethflowData, onchainOrderData: order.onchainOrderData, @@ -458,7 +459,7 @@ export default createReducer(initialState, (builder) => const allOrdersMap = flatOrdersStateNetwork(state[chainId]) const children = Object.values(allOrdersMap).filter( - (item) => item?.order.composableCowInfo?.parentId === id + (item) => item?.order.composableCowInfo?.parentId === id, ) children.forEach((child) => { @@ -544,12 +545,12 @@ export default createReducer(initialState, (builder) => orderListByChain[status] = ordersCleaned }) }) - }) + }), ) function reClassifyOrder( newOrder: SerializedOrder, - existingOrder: OrderObject | undefined + existingOrder: OrderObject | undefined, ): { status: OrderStatus; isCancelling: boolean | undefined } { // Onchain cancellations are considered final // Still, the order classification at apps/cowswap-frontend/src/legacy/state/orders/utils.ts can't tell diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx index eed9c96389..4e3edc6e8b 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx @@ -7,15 +7,34 @@ import * as styledEl from './styled' export type Props = { order: ParsedOrder } +// TODO: get rid of this once https://github.com/cowprotocol/services/pull/3184 is complete +const HAS_BACKEND_MIGRATED = false + +function getFeeToken(order: ParsedOrder) { + if (!HAS_BACKEND_MIGRATED) { + return order.inputToken + } + + const { inputToken, outputToken } = order + const { executedFeeToken } = order.executionData + + if (inputToken?.address.toLowerCase() === executedFeeToken?.toLowerCase()) { + return inputToken + } + if (outputToken?.address.toLowerCase() === executedFeeToken?.toLowerCase()) { + return outputToken + } + return undefined +} + export function FeeField({ order }: Props): JSX.Element | null { - const { inputToken } = order - const { executedFeeAmount, executedSurplusFee } = order.executionData + const { totalFee } = order.executionData + const feeToken = getFeeToken(order) - if (!inputToken) return + if (!feeToken) return - // TODO: use the value from SDK - const totalFee = CurrencyAmount.fromRawAmount(inputToken, (executedSurplusFee ?? executedFeeAmount) || 0) - const quoteSymbol = inputToken.symbol + const totalFeeAmount = CurrencyAmount.fromRawAmount(feeToken, totalFee || 0) + const quoteSymbol = feeToken.symbol return ( @@ -23,7 +42,7 @@ export function FeeField({ order }: Props): JSX.Element | null { - ) : ( - + )} diff --git a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts index 8031e391f3..f6a3d0b4e2 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts @@ -24,7 +24,9 @@ export interface ParsedOrderExecutionData { surplusAmount: BigNumber surplusPercentage: BigNumber executedFeeAmount: string | undefined - executedSurplusFee: string | null + executedFee: string | null + executedFeeToken: string | null + totalFee: string | null filledPercentDisplay: string executedPrice: Price | null activityId: string | undefined @@ -60,7 +62,9 @@ export const parseOrder = (order: Order): ParsedOrder => { const { executedBuyAmount, executedSellAmount } = getOrderExecutedAmounts(order) const expirationTime = new Date(Number(order.validTo) * 1000) const executedFeeAmount = order.apiAdditionalInfo?.executedFeeAmount - const executedSurplusFee = order.apiAdditionalInfo?.executedSurplusFee || null + const executedFee = order.apiAdditionalInfo?.executedFee || null + const executedFeeToken = order.apiAdditionalInfo?.executedFeeToken || null + const totalFee = order.apiAdditionalInfo?.totalFee || null const creationTime = new Date(order.creationTime) const fullyFilled = isOrderFilled(order) const partiallyFilled = isPartiallyFilled(order) @@ -80,6 +84,7 @@ export const parseOrder = (order: Order): ParsedOrder => { const activityTitle = showCreationTxLink ? 'Creation transaction' : 'Order ID' const executionData: ParsedOrderExecutionData = { + executedFeeToken, executedBuyAmount, executedSellAmount, filledAmount, @@ -88,7 +93,8 @@ export const parseOrder = (order: Order): ParsedOrder => { surplusAmount, surplusPercentage, executedFeeAmount, - executedSurplusFee, + executedFee, + totalFee, executedPrice, fullyFilled, partiallyFilled, From b0857c2a4dc44bb02eab15ff798175296f238899 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Mon, 30 Dec 2024 17:35:24 +0000 Subject: [PATCH 09/10] feat: do same as previous, but on Explorer --- apps/explorer/src/api/operator/types.ts | 14 +++++++++++--- apps/explorer/src/test/data/operator.ts | 4 ++-- apps/explorer/src/utils/operator.ts | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/explorer/src/api/operator/types.ts b/apps/explorer/src/api/operator/types.ts index 2734b39e37..4e5c5f690b 100644 --- a/apps/explorer/src/api/operator/types.ts +++ b/apps/explorer/src/api/operator/types.ts @@ -18,7 +18,15 @@ export type RawOrder = EnrichedOrder */ export type Order = Pick< RawOrder, - 'owner' | 'uid' | 'appData' | 'kind' | 'partiallyFillable' | 'signature' | 'class' | 'fullAppData' + | 'owner' + | 'uid' + | 'appData' + | 'kind' + | 'partiallyFillable' + | 'signature' + | 'class' + | 'fullAppData' + | 'executedFeeToken' > & { receiver: string txHash?: string @@ -35,7 +43,7 @@ export type Order = Pick< executedSellAmount: BigNumber feeAmount: BigNumber executedFeeAmount: BigNumber - executedSurplusFee: BigNumber | null + executedFee: BigNumber | null totalFee: BigNumber networkCosts?: BigNumber protocolFees?: BigNumber @@ -62,7 +70,7 @@ export type Trade = Pick Date: Mon, 30 Dec 2024 17:35:44 +0000 Subject: [PATCH 10/10] feat: implement explorer fee break down --- .../components/orders/GasFeeDisplay/index.tsx | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx index 6e17bd7d20..eeafff84f3 100644 --- a/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx +++ b/apps/explorer/src/components/orders/GasFeeDisplay/index.tsx @@ -1,6 +1,5 @@ import { useMemo } from 'react' -import { isSellOrder } from '@cowprotocol/common-utils' import { Nullish } from '@cowprotocol/ui' import { TokenErc20 } from '@gnosis.pm/dex-js' @@ -12,7 +11,6 @@ import { formatSmartMaxPrecision, safeTokenName } from 'utils' import { Order } from 'api/operator' - const Wrapper = styled.div` > span { margin: 0 0.5rem 0 0; @@ -71,51 +69,53 @@ export function GasFeeDisplay({ order }: Props): React.ReactNode | null { ) } -function getFeeDisplayAmounts(order: Order) { - const { - kind, - networkCosts, - protocolFees, - sellToken, - sellTokenAddress, - buyToken, - buyTokenAddress, - totalFee, - feeAmount, - } = order - - const isSell = isSellOrder(kind) - - let quoteSymbol = '' - let formattedNetworkCosts = '' - let formattedProtocolFees = '' - let formattedExecutedFee = '' - let formattedTotalFee = '' - - // When these 2 are set, for sure we have new style fees - if (networkCosts || protocolFees) { - if (isSell) { - quoteSymbol = buyToken ? safeTokenName(buyToken) : buyTokenAddress - formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, buyToken) - formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, buyToken) - formattedExecutedFee = getFormattedAmount(totalFee, buyToken) - formattedTotalFee = getFormattedAmount(feeAmount, buyToken) - } else { - quoteSymbol = sellToken ? safeTokenName(sellToken) : sellTokenAddress - formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, sellToken) - formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, sellToken) - formattedExecutedFee = getFormattedAmount(totalFee, sellToken) - formattedTotalFee = getFormattedAmount(feeAmount, sellToken) - } - } else { - // Otherwise, it can have no fees OR be old style fees, without the policies - // TODO: handle old and new styles, as the fee token will vary! (always sell for old vs surplus token for new) - quoteSymbol = sellToken ? safeTokenName(sellToken) : sellTokenAddress - formattedNetworkCosts = '' - formattedProtocolFees = '' - formattedExecutedFee = getFormattedAmount(totalFee, sellToken) - formattedTotalFee = getFormattedAmount(feeAmount, sellToken) +function getFeeToken(order: Order) { + const { sellToken, buyToken } = order + const { executedFeeToken } = order + if (sellToken?.address.toLowerCase() === executedFeeToken?.toLowerCase()) { + return sellToken + } + if (buyToken?.address.toLowerCase() === executedFeeToken?.toLowerCase()) { + return buyToken } + return undefined +} + +function getFeeDisplayAmounts(order: Order) { + const { networkCosts, protocolFees, totalFee, executedFeeToken, feeAmount } = order + + const feeToken = getFeeToken(order) + + const quoteSymbol = feeToken ? safeTokenName(feeToken) : executedFeeToken + const formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, feeToken) + const formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, feeToken) + const formattedExecutedFee = getFormattedAmount(totalFee, feeToken) + const formattedTotalFee = getFormattedAmount(feeAmount, feeToken) + // + // // When these 2 are set, for sure we have new style fees + // if (networkCosts || protocolFees) { + // if (isSell) { + // quoteSymbol = feeToken ? safeTokenName(feeToken) : executedFeeToken + // formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, feeToken) + // formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, feeToken) + // formattedExecutedFee = getFormattedAmount(totalFee, feeToken) + // formattedTotalFee = getFormattedAmount(feeAmount, feeToken) + // } else { + // quoteSymbol = feeToken ? safeTokenName(feeToken) : executedFeeToken + // formattedNetworkCosts = getFormattedAmount(networkCosts || ZERO_BIG_NUMBER, feeToken) + // formattedProtocolFees = getFormattedAmount(protocolFees || ZERO_BIG_NUMBER, feeToken) + // formattedExecutedFee = getFormattedAmount(totalFee, feeToken) + // formattedTotalFee = getFormattedAmount(feeAmount, feeToken) + // } + // } else { + // // Otherwise, it can have no fees OR be old style fees, without the policies + // // TODO: handle old and new styles, as the fee token will vary! (always sell for old vs surplus token for new) + // quoteSymbol = sellToken ? safeTokenName(sellToken) : sellTokenAddress + // formattedNetworkCosts = '' + // formattedProtocolFees = '' + // formattedExecutedFee = getFormattedAmount(totalFee, sellToken) + // formattedTotalFee = getFormattedAmount(feeAmount, sellToken) + // } return { quoteSymbol,