feat(deal-ticket): amend fee estimates for postOnly orders and when market is in auction (#4843)

This commit is contained in:
Bartłomiej Głownia 2023-09-26 17:57:47 +02:00 committed by GitHub
parent a7e8b0eb01
commit 8a9b1c7874
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 12 deletions

View File

@ -38,14 +38,16 @@ export interface DealTicketFeeDetailsProps {
assetSymbol: string; assetSymbol: string;
order: OrderSubmissionBody['orderSubmission']; order: OrderSubmissionBody['orderSubmission'];
market: Market; market: Market;
isMarketInAuction?: boolean;
} }
export const DealTicketFeeDetails = ({ export const DealTicketFeeDetails = ({
assetSymbol, assetSymbol,
order, order,
market, market,
isMarketInAuction,
}: DealTicketFeeDetailsProps) => { }: DealTicketFeeDetailsProps) => {
const feeEstimate = useEstimateFees(order); const feeEstimate = useEstimateFees(order, isMarketInAuction);
const { settlementAsset: asset } = const { settlementAsset: asset } =
market.tradableInstrument.instrument.product; market.tradableInstrument.instrument.product;
const { decimals: assetDecimals, quantum } = asset; const { decimals: assetDecimals, quantum } = asset;
@ -65,7 +67,7 @@ export const DealTicketFeeDetails = ({
<> <>
<span> <span>
{t( {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.`
)} )}
</span> </span>
<FeesBreakdown <FeesBreakdown

View File

@ -36,7 +36,7 @@ import {
formatValue, formatValue,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { activeOrdersProvider } from '@vegaprotocol/orders'; import { activeOrdersProvider } from '@vegaprotocol/orders';
import { getDerivedPrice } from '@vegaprotocol/markets'; import { getDerivedPrice, isMarketInAuction } from '@vegaprotocol/markets';
import { import {
validateExpiration, validateExpiration,
validateMarketState, validateMarketState,
@ -182,6 +182,7 @@ export const DealTicket = ({
const iceberg = watch('iceberg'); const iceberg = watch('iceberg');
const peakSize = watch('peakSize'); const peakSize = watch('peakSize');
const expiresAt = watch('expiresAt'); const expiresAt = watch('expiresAt');
const postOnly = watch('postOnly');
useEffect(() => { useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size; const size = storedFormValues?.[dealTicketType]?.size;
@ -211,6 +212,7 @@ export const DealTicket = ({
size: rawSize, size: rawSize,
timeInForce, timeInForce,
type, type,
postOnly,
}, },
market.id, market.id,
market.decimalPlaces, market.decimalPlaces,
@ -219,8 +221,7 @@ export const DealTicket = ({
const price = const price =
normalizedOrder && normalizedOrder &&
marketPrice && getDerivedPrice(normalizedOrder, marketPrice ?? undefined);
getDerivedPrice(normalizedOrder, marketPrice);
const notionalSize = getNotionalSize( const notionalSize = getNotionalSize(
price, price,
@ -474,6 +475,7 @@ export const DealTicket = ({
} }
assetSymbol={assetSymbol} assetSymbol={assetSymbol}
market={market} market={market}
isMarketInAuction={isMarketInAuction(marketData.marketTradingMode)}
/> />
</div> </div>
<Controller <Controller

View File

@ -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',
},
});
});
});

View File

@ -1,13 +1,16 @@
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
export const useEstimateFees = ( const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
isMarketInAuction?: boolean
): EstimateFeesQuery['estimateFees'] | undefined => {
const { pubKey } = useVegaWallet();
const { data } = useEstimateFeesQuery({ const { data } = useEstimateFeesQuery({
variables: order && { variables: order && {
marketId: order.marketId, marketId: order.marketId,
@ -19,7 +22,28 @@ export const useEstimateFees = (
type: order.type, type: order.type,
}, },
fetchPolicy: 'no-cache', 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;
}; };

View File

@ -60,6 +60,7 @@ export const FeesBreakdown = ({
.plus(fees.infrastructureFee) .plus(fees.infrastructureFee)
.plus(fees.liquidityFee) .plus(fees.liquidityFee)
.toString(); .toString();
if (totalFees === '0') return null;
const formatValue = (value: string | number | null | undefined): string => { const formatValue = (value: string | number | null | undefined): string => {
return value && !isNaN(Number(value)) return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals) ? addDecimalsFormatNumber(value, decimals)

View File

@ -33,7 +33,7 @@ export const getDerivedPrice = (
type: Schema.OrderType; type: Schema.OrderType;
price?: string | undefined; price?: string | undefined;
}, },
marketPrice: string marketPrice?: string
) => { ) => {
// If order type is market we should use either the mark price // If order type is market we should use either the mark price
// or the uncrossing price. If order type is limit use the price // or the uncrossing price. If order type is limit use the price