diff --git a/apps/trading/components/header/header.tsx b/apps/trading/components/header/header.tsx index 58207e1b5..6ff4d50ee 100644 --- a/apps/trading/components/header/header.tsx +++ b/apps/trading/components/header/header.tsx @@ -53,7 +53,7 @@ export const HeaderStat = ({
{heading}
- +
{ - const feeEstimate = useEstimateFees(order); + const feeEstimate = useEstimateFees(order, isMarketInAuction); const asset = getAsset(market); const { decimals: assetDecimals, quantum } = asset; @@ -63,7 +66,7 @@ export const DealTicketFeeDetails = ({ <> {t( - `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` + `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.` )} void; assetSymbol: string; positionEstimate: EstimatePositionQuery['estimatePosition']; + side: Schema.Side; } export const DealTicketMarginDetails = ({ @@ -95,6 +99,7 @@ export const DealTicketMarginDetails = ({ market, onMarketClick, positionEstimate, + side, }: DealTicketMarginDetailsProps) => { const [breakdownDialog, setBreakdownDialog] = useState(false); const { pubKey: partyId } = useVegaWallet(); @@ -163,10 +168,7 @@ export const DealTicketMarginDetails = ({ : '0', assetDecimals )} - formattedValue={formatRange( - deductionFromCollateralBestCase > 0 - ? deductionFromCollateralBestCase.toString() - : '0', + formattedValue={formatValue( deductionFromCollateralWorstCase > 0 ? deductionFromCollateralWorstCase.toString() : '0', @@ -185,8 +187,7 @@ export const DealTicketMarginDetails = ({ marginEstimate?.worstCase.initialLevel, assetDecimals )} - formattedValue={formatRange( - marginEstimate?.bestCase.initialLevel, + formattedValue={formatValue( marginEstimate?.worstCase.initialLevel, assetDecimals, quantum @@ -198,6 +199,7 @@ export const DealTicketMarginDetails = ({ } let liquidationPriceEstimate = emptyValue; + let liquidationPriceEstimateRange = emptyValue; if (liquidationEstimate) { const liquidationEstimateBestCaseIncludingBuyOrders = BigInt( @@ -207,8 +209,7 @@ export const DealTicketMarginDetails = ({ liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '') ); const liquidationEstimateBestCase = - liquidationEstimateBestCaseIncludingBuyOrders > - liquidationEstimateBestCaseIncludingSellOrders + side === Schema.Side.SIDE_BUY ? liquidationEstimateBestCaseIncludingBuyOrders : liquidationEstimateBestCaseIncludingSellOrders; @@ -219,14 +220,19 @@ export const DealTicketMarginDetails = ({ liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') ); const liquidationEstimateWorstCase = - liquidationEstimateWorstCaseIncludingBuyOrders > - liquidationEstimateWorstCaseIncludingSellOrders + side === Schema.Side.SIDE_BUY ? liquidationEstimateWorstCaseIncludingBuyOrders : liquidationEstimateWorstCaseIncludingSellOrders; // The estimate order query API gives us the liquidation price in formatted by asset decimals. // We need to calculate it with asset decimals, but display it with market decimals precision until the API changes. - liquidationPriceEstimate = formatRange( + liquidationPriceEstimate = formatValue( + liquidationEstimateWorstCase.toString(), + assetDecimals, + undefined, + market.decimalPlaces + ); + liquidationPriceEstimateRange = formatRange( (liquidationEstimateBestCase < liquidationEstimateWorstCase ? liquidationEstimateBestCase : liquidationEstimateWorstCase @@ -249,7 +255,7 @@ export const DealTicketMarginDetails = ({ const quoteName = getQuoteName(market); return ( -
+
@@ -282,11 +288,9 @@ export const DealTicketMarginDetails = ({ assetDecimals ) ?? '-' } - noUnderline >
- {formatRange( - marginRequiredBestCase, + {formatValue( marginRequiredWorstCase, assetDecimals, quantum @@ -298,7 +302,7 @@ export const DealTicketMarginDetails = ({ } > -
+
{ const size = storedFormValues?.[dealTicketType]?.size; @@ -198,7 +204,7 @@ export const DealTicket = ({ }, [storedFormValues, dealTicketType, rawPrice, setValue]); useEffect(() => { - const subscription = watch((value, { name, type }) => { + const subscription = watch((value) => { updateStoredFormValues(market.id, value); }); return () => subscription.unsubscribe(); @@ -211,6 +217,7 @@ export const DealTicket = ({ size: rawSize, timeInForce, type, + postOnly, }, market.id, market.decimalPlaces, @@ -219,8 +226,7 @@ export const DealTicket = ({ const price = normalizedOrder && - marketPrice && - getDerivedPrice(normalizedOrder, marketPrice); + getDerivedPrice(normalizedOrder, marketPrice ?? undefined); const notionalSize = getNotionalSize( price, @@ -459,7 +465,7 @@ export const DealTicket = ({ )} /> )} -
+
{label}
- + {valueElement}
diff --git a/libs/deal-ticket/src/hooks/use-estimate-fees.spec.tsx b/libs/deal-ticket/src/hooks/use-estimate-fees.spec.tsx new file mode 100644 index 000000000..7a7097d70 --- /dev/null +++ b/libs/deal-ticket/src/hooks/use-estimate-fees.spec.tsx @@ -0,0 +1,78 @@ +import { renderHook } from '@testing-library/react'; +import { useEstimateFees } from './use-estimate-fees'; +import { Side, OrderTimeInForce, OrderType } from '@vegaprotocol/types'; + +import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; + +const data: EstimateFeesQuery = { + estimateFees: { + totalFeeAmount: '12', + fees: { + infrastructureFee: '2', + liquidityFee: '4', + makerFee: '6', + }, + }, +}; + +const mockUseEstimateFeesQuery = jest.fn((...args) => ({ + data, +})); + +jest.mock('./__generated__/EstimateOrder', () => ({ + ...jest.requireActual('@vegaprotocol/data-provider'), + useEstimateFeesQuery: jest.fn((...args) => mockUseEstimateFeesQuery(...args)), +})); + +jest.mock('@vegaprotocol/wallet', () => ({ + useVegaWallet: () => ({ pubKey: 'pubKey' }), +})); + +describe('useEstimateFees', () => { + it('returns 0 as estimated values if order is postOnly', () => { + const { result } = renderHook(() => + useEstimateFees({ + marketId: 'marketId', + side: Side.SIDE_BUY, + size: '1', + price: '1', + timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK, + type: OrderType.TYPE_LIMIT, + postOnly: true, + }) + ); + expect(result.current).toEqual({ + totalFeeAmount: '0', + fees: { + infrastructureFee: '0', + liquidityFee: '0', + makerFee: '0', + }, + }); + expect(mockUseEstimateFeesQuery.mock.lastCall?.[0].skip).toBeTruthy(); + }); + + it('divide values by 2 if market is in auction', () => { + const { result } = renderHook(() => + useEstimateFees( + { + marketId: 'marketId', + side: Side.SIDE_BUY, + size: '1', + price: '1', + timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK, + type: OrderType.TYPE_LIMIT, + }, + true + ) + ); + expect(result.current).toEqual({ + totalFeeAmount: '6', + fees: { + infrastructureFee: '1', + liquidityFee: '2', + makerFee: '3', + }, + }); + }); +}); diff --git a/libs/deal-ticket/src/hooks/use-estimate-fees.tsx b/libs/deal-ticket/src/hooks/use-estimate-fees.tsx index 9ad2b0b5a..1d83415fd 100644 --- a/libs/deal-ticket/src/hooks/use-estimate-fees.tsx +++ b/libs/deal-ticket/src/hooks/use-estimate-fees.tsx @@ -1,13 +1,16 @@ import { useVegaWallet } from '@vegaprotocol/wallet'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; -export const useEstimateFees = ( - order?: OrderSubmissionBody['orderSubmission'] -) => { - const { pubKey } = useVegaWallet(); +const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString(); +export const useEstimateFees = ( + order?: OrderSubmissionBody['orderSubmission'], + isMarketInAuction?: boolean +): EstimateFeesQuery['estimateFees'] | undefined => { + const { pubKey } = useVegaWallet(); const { data } = useEstimateFeesQuery({ variables: order && { marketId: order.marketId, @@ -19,7 +22,28 @@ export const useEstimateFees = ( type: order.type, }, fetchPolicy: 'no-cache', - skip: !pubKey || !order?.size || !order?.price, + skip: !pubKey || !order?.size || !order?.price || order.postOnly, }); - return data?.estimateFees; + if (order?.postOnly) { + return { + totalFeeAmount: '0', + fees: { + infrastructureFee: '0', + liquidityFee: '0', + makerFee: '0', + }, + }; + } + return isMarketInAuction && data?.estimateFees + ? { + totalFeeAmount: divideByTwo(data.estimateFees.totalFeeAmount), + fees: { + infrastructureFee: divideByTwo( + data.estimateFees.fees.infrastructureFee + ), + liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee), + makerFee: divideByTwo(data.estimateFees.fees.makerFee), + }, + } + : data?.estimateFees; }; diff --git a/libs/market-depth/src/lib/orderbook.tsx b/libs/market-depth/src/lib/orderbook.tsx index 8d4878817..0691487d3 100644 --- a/libs/market-depth/src/lib/orderbook.tsx +++ b/libs/market-depth/src/lib/orderbook.tsx @@ -175,7 +175,7 @@ export const Orderbook = ({ ); return ( -
+
{({ width, height }) => { diff --git a/libs/markets/src/lib/components/fees-breakdown/fees-breakdown.tsx b/libs/markets/src/lib/components/fees-breakdown/fees-breakdown.tsx index 8b600db14..a7890a3fc 100644 --- a/libs/markets/src/lib/components/fees-breakdown/fees-breakdown.tsx +++ b/libs/markets/src/lib/components/fees-breakdown/fees-breakdown.tsx @@ -60,6 +60,7 @@ export const FeesBreakdown = ({ .plus(fees.infrastructureFee) .plus(fees.liquidityFee) .toString(); + if (totalFees === '0') return null; const formatValue = (value: string | number | null | undefined): string => { return value && !isNaN(Number(value)) ? addDecimalsFormatNumber(value, decimals) diff --git a/libs/markets/src/lib/get-price.ts b/libs/markets/src/lib/get-price.ts index 763acbfcf..e93b3e248 100644 --- a/libs/markets/src/lib/get-price.ts +++ b/libs/markets/src/lib/get-price.ts @@ -33,7 +33,7 @@ export const getDerivedPrice = ( type: Schema.OrderType; price?: string | undefined; }, - marketPrice: string + marketPrice?: string ) => { // If order type is market we should use either the mark price // or the uncrossing price. If order type is limit use the price diff --git a/libs/ui-toolkit/src/components/tooltip/tooltip.tsx b/libs/ui-toolkit/src/components/tooltip/tooltip.tsx index 1b3d8f30c..769301c84 100644 --- a/libs/ui-toolkit/src/components/tooltip/tooltip.tsx +++ b/libs/ui-toolkit/src/components/tooltip/tooltip.tsx @@ -19,14 +19,14 @@ export interface TooltipProps { align?: 'start' | 'center' | 'end'; side?: 'top' | 'right' | 'bottom' | 'left'; sideOffset?: number; - noUnderline?: boolean; + underline?: boolean; } -export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) => - classNames( - { 'underline underline-offset-2': !noUnderline }, - 'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed' - ); +export const TOOLTIP_TRIGGER_CLASS_NAME = (underline?: boolean) => + classNames({ + 'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed': + underline, + }); // Conditionally rendered tooltip if description content is provided. export const Tooltip = ({ @@ -36,12 +36,12 @@ export const Tooltip = ({ sideOffset, align = 'start', side = 'bottom', - noUnderline, + underline, }: TooltipProps) => description ? ( - + {children} {description && (