feat: 1486 add details of expected fees margin close out to deal ticket (#1771)
* fix: #1486 move deal ticket hooks from console-lite to be re-used in trading app for console v2 * fix: #1486 typo * feat: #1486 deal ticket query update, console-lite fix * feat: #1486 console-lite fix * feat: #1486 initial hook to get fee details * feat: #1486 add tooltips * feat: #1486 add fees cell from market-info in tooltip * fix: #1486 edit deal-ticket.spec.ts titles and index.ts of deal ticket hooks * feat: #1486 move all hooks for slippage into deal ticket * fix: #1486 fix linting deal-ticket issue * fix: set price, fix NaN percentage, watch full order object * fix: update only when market price is updated * feat: #1486 add fees from est. order query, fees breakdown, fix BigNumber NaN issue * feat: #1486 add fee factors in generate deal ticket query * fix: #1486 show margin on short * fix: #1486 format price and fix dal ticket use order margin import * fix: #1486 fix price memo * feat: #1486 update estimate ordr query with order price or mark price * fix: #1486 revert apps/console-lite/.env * fix: #1486 fix NaN value on close out * fix: #1486 revert close out calculation * fix: #1486 prevent NaN close out * fix: #1486 revert close out * feat: #1486 add fee factor percentages in tooltip and fix NaN * fix: #1486 fix deal-ticket-steps est close out null handling * fix: #1486 fix deal-ticket-steps est close out null handling * fix: #1486 add tooltip for fees * fix: #1486 fix console-lite formatting on notional size and close out * fix: #1486 total fees formatting inside the hook * feat: #1486 add qutote to fees tooltip * fix: #1486 update hook, price, console-lite and styling * chore: fix mock types * fix: #1486 fix tests in console-lite * fix: #1486 add declaration.d.ts to console-lite-e2e * fix: #1486 fix deal ticket test * fix: #1486 fix deal ticket test Co-authored-by: Rado <szpiechrados@gmail.com>
This commit is contained in:
parent
513c7f2b1a
commit
bf34f1c060
1
apps/console-lite-e2e/declaration.d.ts
vendored
Normal file
1
apps/console-lite-e2e/declaration.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module '*.scss';
|
@ -27,7 +27,7 @@ describe('market selector', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'MarketTags', generateMarketTags());
|
||||
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
||||
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
||||
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
|
||||
aliasQuery(req, 'PartyBalance', generatePartyBalance());
|
||||
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
||||
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
||||
aliasQuery(req, 'MarketNames', generateMarketNames());
|
||||
|
@ -28,7 +28,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'MarketTags', generateMarketTags());
|
||||
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
||||
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
||||
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
|
||||
aliasQuery(req, 'PartyBalance', generatePartyBalance());
|
||||
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
||||
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
||||
aliasQuery(req, 'MarketDepth', generateMarketDepth());
|
||||
|
@ -1,13 +1,21 @@
|
||||
export const generateDealTicket = () => {
|
||||
return {
|
||||
import type { DealTicketQuery } from '@vegaprotocol/deal-ticket';
|
||||
import { MarketTradingMode, MarketState } from '@vegaprotocol/types';
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
export const generateDealTicket = (
|
||||
override?: PartialDeep<DealTicketQuery>
|
||||
): DealTicketQuery => {
|
||||
const defaultResult: DealTicketQuery = {
|
||||
market: {
|
||||
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 0,
|
||||
state: 'STATE_ACTIVE',
|
||||
tradingMode: 'Continuous',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d',
|
||||
name: 'AAVEDAI Monthly (30 Jun 2022)',
|
||||
product: {
|
||||
quoteName: 'DAI',
|
||||
@ -15,6 +23,7 @@ export const generateDealTicket = () => {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
name: 'tDAI TEST',
|
||||
decimals: 5,
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Future',
|
||||
@ -27,7 +36,17 @@ export const generateDealTicket = () => {
|
||||
lastTrade: { price: '9893006', __typename: 'Trade' },
|
||||
__typename: 'MarketDepth',
|
||||
},
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: '0.0002',
|
||||
infrastructureFee: '0.0005',
|
||||
liquidityFee: '0.001',
|
||||
__typename: 'FeeFactors',
|
||||
},
|
||||
__typename: 'Fees',
|
||||
},
|
||||
__typename: 'Market',
|
||||
},
|
||||
};
|
||||
return merge(defaultResult, override);
|
||||
};
|
||||
|
@ -1,10 +1,17 @@
|
||||
export const generatePartyBalance = () => {
|
||||
return {
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartyBalanceQuery } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
|
||||
export const generatePartyBalance = (
|
||||
override?: PartialDeep<PartyBalanceQuery>
|
||||
): PartyBalanceQuery => {
|
||||
const defaultResult: PartyBalanceQuery = {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '88474051',
|
||||
type: 'ACCOUNT_TYPE_GENERAL',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
@ -16,7 +23,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '100000000',
|
||||
type: 'ACCOUNT_TYPE_GENERAL',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: {
|
||||
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
|
||||
symbol: 'tEURO',
|
||||
@ -28,7 +35,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '3412867',
|
||||
type: 'ACCOUNT_TYPE_GENERAL',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
@ -40,7 +47,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '70007',
|
||||
type: 'ACCOUNT_TYPE_GENERAL',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
@ -54,4 +61,6 @@ export const generatePartyBalance = () => {
|
||||
__typename: 'Party',
|
||||
},
|
||||
};
|
||||
|
||||
return merge(defaultResult, override);
|
||||
};
|
||||
|
@ -9,5 +9,5 @@
|
||||
"allowJs": true,
|
||||
"types": ["cypress", "node", "cypress-real-events", "cypress-grep"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"]
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"]
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: PartyBalanceQuery
|
||||
// ====================================================
|
||||
|
||||
export interface PartyBalanceQuery_party_accounts_asset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The ID of the asset
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The symbol of the asset (e.g: GBP)
|
||||
*/
|
||||
symbol: string;
|
||||
/**
|
||||
* The full name of the asset (e.g: Great British Pound)
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
|
||||
*/
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface PartyBalanceQuery_party_accounts {
|
||||
__typename: "Account";
|
||||
/**
|
||||
* Account type (General, Margin, etc)
|
||||
*/
|
||||
type: AccountType;
|
||||
/**
|
||||
* Balance as string - current account balance (approx. as balances can be updated several times per second)
|
||||
*/
|
||||
balance: string;
|
||||
/**
|
||||
* Asset, the 'currency'
|
||||
*/
|
||||
asset: PartyBalanceQuery_party_accounts_asset;
|
||||
}
|
||||
|
||||
export interface PartyBalanceQuery_party {
|
||||
__typename: "Party";
|
||||
/**
|
||||
* Collateral accounts relating to a party
|
||||
*/
|
||||
accounts: PartyBalanceQuery_party_accounts[] | null;
|
||||
}
|
||||
|
||||
export interface PartyBalanceQuery {
|
||||
/**
|
||||
* An entity that is trading on the Vega network
|
||||
*/
|
||||
party: PartyBalanceQuery_party | null;
|
||||
}
|
||||
|
||||
export interface PartyBalanceQueryVariables {
|
||||
partyId: string;
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartyBalanceQuery_party_accounts } from './__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from '../../hooks/use-settlement-account';
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type {
|
||||
AccountFragment,
|
||||
DealTicketMarketFragment,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { useSettlementAccount } from '@vegaprotocol/deal-ticket';
|
||||
|
||||
interface DealTicketBalanceProps {
|
||||
settlementAsset: DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||
accounts: PartyBalanceQuery_party_accounts[];
|
||||
accounts: AccountFragment[];
|
||||
isWalletConnected: boolean;
|
||||
className?: string;
|
||||
}
|
||||
@ -26,7 +27,7 @@ export const DealTicketBalance = ({
|
||||
accounts,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL
|
||||
);
|
||||
const formatedNumber =
|
||||
const formattedNumber =
|
||||
settlementAccount?.balance &&
|
||||
settlementAccount.asset.decimals &&
|
||||
addDecimalsFormatNumber(
|
||||
@ -37,7 +38,7 @@ export const DealTicketBalance = ({
|
||||
const balance = (
|
||||
<p className="text-blue text-lg font-semibold">
|
||||
{settlementAccount
|
||||
? t(`${formatedNumber}`)
|
||||
? t(`${formattedNumber}`)
|
||||
: `No ${settlementAssetSymbol} left to trade`}
|
||||
</p>
|
||||
);
|
||||
|
@ -1,9 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import {
|
||||
DealTicketManager,
|
||||
DealTicketContainer as Container,
|
||||
usePartyBalanceQuery,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
@ -11,41 +10,20 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { DealTicketSteps } from './deal-ticket-steps';
|
||||
import { DealTicketBalance } from './deal-ticket-balance';
|
||||
import Baubles from './baubles-decor';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
import ConnectWallet from '../wallet-connector';
|
||||
|
||||
const tempEmptyText = (
|
||||
<p>{t('Please select a market from the markets page')}</p>
|
||||
);
|
||||
|
||||
const PARTY_BALANCE_QUERY = gql`
|
||||
query PartyBalanceQuery($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accounts {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DealTicketContainer = () => {
|
||||
const { marketId } = useParams<{ marketId: string }>();
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const { data: partyData, loading } = useQuery<PartyBalanceQuery>(
|
||||
PARTY_BALANCE_QUERY,
|
||||
{
|
||||
variables: { partyId: pubKey },
|
||||
skip: !pubKey,
|
||||
}
|
||||
);
|
||||
const { data: partyData, loading } = usePartyBalanceQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const loader = <Loader />;
|
||||
|
||||
@ -70,7 +48,7 @@ export const DealTicketContainer = () => {
|
||||
return (
|
||||
<DealTicketManager market={data.market}>
|
||||
{loading ? loader : balance}
|
||||
<DealTicketSteps market={data.market} partyData={partyData} />
|
||||
<DealTicketSteps market={data.market} />
|
||||
</DealTicketManager>
|
||||
);
|
||||
}}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { DealTicketEstimates } from './deal-ticket-estimates';
|
||||
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket';
|
||||
import { DealTicketSizeInput } from './deal-ticket-size-input';
|
||||
|
||||
interface DealTicketSizeProps {
|
||||
|
@ -1,11 +1,19 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import * as constants from './constants';
|
||||
import { TrafficLight } from '../traffic-light';
|
||||
import { Dialog, Icon, Intent, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Dialog,
|
||||
Icon,
|
||||
Intent,
|
||||
Tooltip,
|
||||
TrafficLight,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { InputSetter } from '../../components/input-setter';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { DataTitle, ValueTooltipRow } from './deal-ticket-estimates';
|
||||
import {
|
||||
DataTitle,
|
||||
EST_SLIPPAGE,
|
||||
ValueTooltipRow,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
|
||||
interface DealTicketSlippageProps {
|
||||
step?: number;
|
||||
@ -40,12 +48,12 @@ export const DealTicketSlippage = ({
|
||||
const formLabel = (
|
||||
<label className="flex items-center mb-1">
|
||||
<span className="mr-1">{t('Adjust slippage tolerance')}</span>
|
||||
<Tooltip align="center" description={constants.EST_SLIPPAGE}>
|
||||
<Tooltip align="center" description={EST_SLIPPAGE}>
|
||||
<div className="cursor-help" tabIndex={-1}>
|
||||
<Icon
|
||||
name={IconNames.ISSUE}
|
||||
className="block rotate-180"
|
||||
ariaLabel={constants.EST_SLIPPAGE}
|
||||
ariaLabel={EST_SLIPPAGE}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@ -83,7 +91,7 @@ export const DealTicketSlippage = ({
|
||||
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
|
||||
<div className="flex">
|
||||
<div className="mr-1">
|
||||
<ValueTooltipRow description={constants.EST_SLIPPAGE}>
|
||||
<ValueTooltipRow description={EST_SLIPPAGE}>
|
||||
<TrafficLight value={value} q1={1} q2={5}>
|
||||
{value}%
|
||||
</TrafficLight>
|
||||
|
@ -3,6 +3,13 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Stepper } from '../stepper';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import {
|
||||
useOrderCloseOut,
|
||||
useOrderMargin,
|
||||
usePartyBalanceQuery,
|
||||
useMaximumPositionSize,
|
||||
useCalculateSlippage,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import {
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
@ -12,12 +19,13 @@ import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { MarketSelector } from '@vegaprotocol/deal-ticket';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
t,
|
||||
addDecimalsFormatNumber,
|
||||
toDecimal,
|
||||
removeDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useOrderSubmit,
|
||||
@ -30,23 +38,14 @@ import { DealTicketSize } from './deal-ticket-size';
|
||||
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||
import SideSelector, { SIDE_NAMES } from './side-selector';
|
||||
import ReviewTrade from './review-trade';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
import useOrderCloseOut from '../../hooks/use-order-closeout';
|
||||
import useOrderMargin from '../../hooks/use-order-margin';
|
||||
import useMaximumPositionSize from '../../hooks/use-maximum-position-size';
|
||||
import useCalculateSlippage from '../../hooks/use-calculate-slippage';
|
||||
import { Side, OrderType } from '@vegaprotocol/types';
|
||||
import { DealTicketSlippage } from './deal-ticket-slippage';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
market: DealTicketMarketFragment;
|
||||
partyData?: PartyBalanceQuery;
|
||||
}
|
||||
|
||||
export const DealTicketSteps = ({
|
||||
market,
|
||||
partyData,
|
||||
}: DealTicketMarketProps) => {
|
||||
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
const navigate = useNavigate();
|
||||
const setMarket = useCallback(
|
||||
(marketId: string) => {
|
||||
@ -68,15 +67,11 @@ export const DealTicketSteps = ({
|
||||
|
||||
const emptyString = ' - ';
|
||||
const step = toDecimal(market.positionDecimalPlaces);
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
const orderSide = watch('side');
|
||||
const orderSize = watch('size');
|
||||
const order = watch();
|
||||
const { message: invalidText, isDisabled } = useOrderValidation({
|
||||
market,
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
orderType: order.type,
|
||||
orderTimeInForce: order.timeInForce,
|
||||
fieldErrors: errors,
|
||||
});
|
||||
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
|
||||
@ -87,9 +82,14 @@ export const DealTicketSteps = ({
|
||||
partyId: pubKey || '',
|
||||
});
|
||||
|
||||
const { data: partyBalance } = usePartyBalanceQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const maxTrade = useMaximumPositionSize({
|
||||
partyId: pubKey || '',
|
||||
accounts: partyData?.party?.accounts || [],
|
||||
accounts: partyBalance?.party?.accounts || [],
|
||||
marketId: market.id,
|
||||
settlementAssetId:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||
@ -97,7 +97,11 @@ export const DealTicketSteps = ({
|
||||
order,
|
||||
});
|
||||
|
||||
const estCloseOut = useOrderCloseOut({ order, market, partyData });
|
||||
const estCloseOut = useOrderCloseOut({
|
||||
order,
|
||||
market,
|
||||
partyData: partyBalance,
|
||||
});
|
||||
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
||||
const [slippageValue, setSlippageValue] = useState(
|
||||
slippage ? parseFloat(slippage) : 0
|
||||
@ -130,26 +134,26 @@ export const DealTicketSteps = ({
|
||||
|
||||
const notionalSize = useMemo(() => {
|
||||
if (price) {
|
||||
const size = new BigNumber(price).multipliedBy(orderSize).toNumber();
|
||||
const size = new BigNumber(price).multipliedBy(order.size).toNumber();
|
||||
|
||||
return addDecimalsFormatNumber(size, market.decimalPlaces);
|
||||
}
|
||||
return null;
|
||||
}, [market.decimalPlaces, orderSize, price]);
|
||||
}, [market.decimalPlaces, order.size, price]);
|
||||
|
||||
const fees = useMemo(() => {
|
||||
if (estMargin?.fees && notionalSize) {
|
||||
const percentage = new BigNumber(estMargin?.fees)
|
||||
if (estMargin?.totalFees && notionalSize) {
|
||||
const percentage = new BigNumber(estMargin?.totalFees)
|
||||
.dividedBy(notionalSize)
|
||||
.multipliedBy(100)
|
||||
.decimalPlaces(2)
|
||||
.toString();
|
||||
|
||||
return `${estMargin.fees} (${percentage}%)`;
|
||||
return `${estMargin.totalFees} (${percentage}%)`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [estMargin?.fees, notionalSize]);
|
||||
}, [estMargin?.totalFees, notionalSize]);
|
||||
|
||||
const max = useMemo(() => {
|
||||
return new BigNumber(maxTrade)
|
||||
@ -157,6 +161,10 @@ export const DealTicketSteps = ({
|
||||
.toNumber();
|
||||
}, [market.positionDecimalPlaces, maxTrade]);
|
||||
|
||||
useEffect(() => {
|
||||
setSlippageValue(slippage ? parseFloat(slippage) : 0);
|
||||
}, [slippage]);
|
||||
|
||||
const onSizeChange = useCallback(
|
||||
(value: number) => {
|
||||
const newVal = new BigNumber(value)
|
||||
@ -185,7 +193,7 @@ export const DealTicketSteps = ({
|
||||
|
||||
setValue('price', bestAskPrice);
|
||||
|
||||
if (orderType === OrderType.TYPE_MARKET) {
|
||||
if (order.type === OrderType.TYPE_MARKET) {
|
||||
setValue('type', OrderType.TYPE_LIMIT);
|
||||
}
|
||||
} else {
|
||||
@ -199,7 +207,8 @@ export const DealTicketSteps = ({
|
||||
market.decimalPlaces,
|
||||
market?.depth?.lastTrade?.price,
|
||||
order.side,
|
||||
orderType,
|
||||
order.type,
|
||||
setSlippageValue,
|
||||
setValue,
|
||||
]
|
||||
);
|
||||
@ -246,7 +255,7 @@ export const DealTicketSteps = ({
|
||||
)}
|
||||
/>
|
||||
),
|
||||
value: SIDE_NAMES[orderSide] || '',
|
||||
value: SIDE_NAMES[order.side] || '',
|
||||
},
|
||||
{
|
||||
label: t('Choose Position Size'),
|
||||
@ -258,7 +267,7 @@ export const DealTicketSteps = ({
|
||||
min={step}
|
||||
max={max}
|
||||
onSizeChange={onSizeChange}
|
||||
size={new BigNumber(orderSize).toNumber()}
|
||||
size={new BigNumber(order.size).toNumber()}
|
||||
name="size"
|
||||
price={formattedPrice || emptyString}
|
||||
positionDecimalPlaces={market.positionDecimalPlaces}
|
||||
@ -267,7 +276,7 @@ export const DealTicketSteps = ({
|
||||
.symbol
|
||||
}
|
||||
notionalSize={notionalSize || emptyString}
|
||||
estCloseOut={estCloseOut}
|
||||
estCloseOut={estCloseOut || emptyString}
|
||||
fees={fees || emptyString}
|
||||
estMargin={estMargin?.margin || emptyString}
|
||||
/>
|
||||
@ -277,9 +286,9 @@ export const DealTicketSteps = ({
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
'loading...'
|
||||
t('Loading...')
|
||||
),
|
||||
value: orderSize,
|
||||
value: order.size,
|
||||
},
|
||||
{
|
||||
label: t('Review Trade'),
|
||||
@ -297,7 +306,7 @@ export const DealTicketSteps = ({
|
||||
isDisabled={isDisabled}
|
||||
transactionStatus={transactionStatus}
|
||||
order={order}
|
||||
estCloseOut={estCloseOut}
|
||||
estCloseOut={estCloseOut || emptyString}
|
||||
estMargin={estMargin?.margin || emptyString}
|
||||
price={formattedPrice || emptyString}
|
||||
quoteName={
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { SIDE_NAMES } from './side-selector';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
@ -14,7 +14,6 @@ import type {
|
||||
MarketTags,
|
||||
MarketTagsVariables,
|
||||
} from './__generated__/MarketTags';
|
||||
import { DealTicketEstimates } from './deal-ticket-estimates';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { MarketExpires } from '@vegaprotocol/market-info';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useNavigate, useParams, Link } from 'react-router-dom';
|
||||
import {
|
||||
@ -14,11 +14,11 @@ import {
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import useMarketFiltersData from '../../hooks/use-markets-filter';
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { HorizontalMenu } from '../horizontal-menu';
|
||||
import type { HorizontalMenuItem } from '../horizontal-menu';
|
||||
import * as constants from './constants';
|
||||
import { useMarketFilters } from '../../hooks/use-markets-filter';
|
||||
|
||||
interface Props {
|
||||
data: Market[];
|
||||
@ -27,7 +27,7 @@ interface Props {
|
||||
const SimpleMarketToolbar = ({ data }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const { products, assetsPerProduct } = useMarketFiltersData(data);
|
||||
const { products, assetsPerProduct } = useMarketFilters(data);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
const onStateChange = useCallback(
|
||||
|
@ -1,52 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketMarkPrice
|
||||
// ====================================================
|
||||
|
||||
export interface MarketMarkPrice_market_data {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* The mark price (an unsigned integer)
|
||||
*/
|
||||
markPrice: string;
|
||||
}
|
||||
|
||||
export interface MarketMarkPrice_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* The number of decimal places that an integer must be shifted by in order to get a correct
|
||||
* number denominated in the currency of the market. (uint64)
|
||||
*
|
||||
* Examples:
|
||||
* Currency Balance decimalPlaces Real Balance
|
||||
* GBP 100 0 GBP 100
|
||||
* GBP 100 2 GBP 1.00
|
||||
* GBP 100 4 GBP 0.01
|
||||
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||
*
|
||||
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||
*/
|
||||
decimalPlaces: number;
|
||||
/**
|
||||
* marketData for the given market
|
||||
*/
|
||||
data: MarketMarkPrice_market_data | null;
|
||||
}
|
||||
|
||||
export interface MarketMarkPrice {
|
||||
/**
|
||||
* An instrument that is trading on the Vega network
|
||||
*/
|
||||
market: MarketMarkPrice_market | null;
|
||||
}
|
||||
|
||||
export interface MarketMarkPriceVariables {
|
||||
marketId: string;
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: PartyMarketData
|
||||
// ====================================================
|
||||
|
||||
export interface PartyMarketData_party_accounts_asset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The ID of the asset
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
|
||||
*/
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_accounts_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_accounts {
|
||||
__typename: "Account";
|
||||
/**
|
||||
* Account type (General, Margin, etc)
|
||||
*/
|
||||
type: AccountType;
|
||||
/**
|
||||
* Balance as string - current account balance (approx. as balances can be updated several times per second)
|
||||
*/
|
||||
balance: string;
|
||||
/**
|
||||
* Asset, the 'currency'
|
||||
*/
|
||||
asset: PartyMarketData_party_accounts_asset;
|
||||
/**
|
||||
* Market (only relevant to margin accounts)
|
||||
*/
|
||||
market: PartyMarketData_party_accounts_market | null;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_marginsConnection_edges_node_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_marginsConnection_edges_node {
|
||||
__typename: "MarginLevels";
|
||||
/**
|
||||
* Market in which the margin is required for this party
|
||||
*/
|
||||
market: PartyMarketData_party_marginsConnection_edges_node_market;
|
||||
/**
|
||||
* This is the minimum margin required for a party to place a new order on the network (unsigned integer)
|
||||
*/
|
||||
initialLevel: string;
|
||||
/**
|
||||
* Minimal margin for the position to be maintained in the network (unsigned integer)
|
||||
*/
|
||||
maintenanceLevel: string;
|
||||
/**
|
||||
* If the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer)
|
||||
*/
|
||||
searchLevel: string;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_marginsConnection_edges {
|
||||
__typename: "MarginEdge";
|
||||
node: PartyMarketData_party_marginsConnection_edges_node;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party_marginsConnection {
|
||||
__typename: "MarginConnection";
|
||||
/**
|
||||
* The margin levels in this connection
|
||||
*/
|
||||
edges: PartyMarketData_party_marginsConnection_edges[] | null;
|
||||
}
|
||||
|
||||
export interface PartyMarketData_party {
|
||||
__typename: "Party";
|
||||
/**
|
||||
* Party identifier
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Collateral accounts relating to a party
|
||||
*/
|
||||
accounts: PartyMarketData_party_accounts[] | null;
|
||||
/**
|
||||
* Margin levels for a market
|
||||
*/
|
||||
marginsConnection: PartyMarketData_party_marginsConnection | null;
|
||||
}
|
||||
|
||||
export interface PartyMarketData {
|
||||
/**
|
||||
* An entity that is trading on the Vega network
|
||||
*/
|
||||
party: PartyMarketData_party | null;
|
||||
}
|
||||
|
||||
export interface PartyMarketDataVariables {
|
||||
partyId: string;
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: EstimateOrder
|
||||
// ====================================================
|
||||
|
||||
export interface EstimateOrder_estimateOrder_fee {
|
||||
__typename: "TradeFee";
|
||||
/**
|
||||
* The maker fee, paid by the aggressive party to the other party (the one who had an order in the book)
|
||||
*/
|
||||
makerFee: string;
|
||||
/**
|
||||
* The infrastructure fee, a fee paid to the validators to maintain the Vega network
|
||||
*/
|
||||
infrastructureFee: string;
|
||||
/**
|
||||
* The fee paid to the liquidity providers that committed liquidity to the market
|
||||
*/
|
||||
liquidityFee: string;
|
||||
}
|
||||
|
||||
export interface EstimateOrder_estimateOrder_marginLevels {
|
||||
__typename: "MarginLevels";
|
||||
/**
|
||||
* This is the minimum margin required for a party to place a new order on the network (unsigned integer)
|
||||
*/
|
||||
initialLevel: string;
|
||||
}
|
||||
|
||||
export interface EstimateOrder_estimateOrder {
|
||||
__typename: "OrderEstimate";
|
||||
/**
|
||||
* The estimated fee if the order was to trade
|
||||
*/
|
||||
fee: EstimateOrder_estimateOrder_fee;
|
||||
/**
|
||||
* The margin requirement for this order
|
||||
*/
|
||||
marginLevels: EstimateOrder_estimateOrder_marginLevels;
|
||||
}
|
||||
|
||||
export interface EstimateOrder {
|
||||
/**
|
||||
* Return an estimation of the potential cost for a new order
|
||||
*/
|
||||
estimateOrder: EstimateOrder_estimateOrder;
|
||||
}
|
||||
|
||||
export interface EstimateOrderVariables {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
price?: string | null;
|
||||
size: string;
|
||||
side: Side;
|
||||
timeInForce: OrderTimeInForce;
|
||||
expiration?: string | null;
|
||||
type: OrderType;
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketPositions
|
||||
// ====================================================
|
||||
|
||||
export interface MarketPositions_party_accounts_asset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
|
||||
*/
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_accounts_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_accounts {
|
||||
__typename: "Account";
|
||||
/**
|
||||
* Account type (General, Margin, etc)
|
||||
*/
|
||||
type: AccountType;
|
||||
/**
|
||||
* Balance as string - current account balance (approx. as balances can be updated several times per second)
|
||||
*/
|
||||
balance: string;
|
||||
/**
|
||||
* Asset, the 'currency'
|
||||
*/
|
||||
asset: MarketPositions_party_accounts_asset;
|
||||
/**
|
||||
* Market (only relevant to margin accounts)
|
||||
*/
|
||||
market: MarketPositions_party_accounts_market | null;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges_node_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges_node {
|
||||
__typename: "Position";
|
||||
/**
|
||||
* Open volume (int64)
|
||||
*/
|
||||
openVolume: string;
|
||||
/**
|
||||
* Market relating to this position
|
||||
*/
|
||||
market: MarketPositions_party_positionsConnection_edges_node_market;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges {
|
||||
__typename: "PositionEdge";
|
||||
/**
|
||||
* The position
|
||||
*/
|
||||
node: MarketPositions_party_positionsConnection_edges_node;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection {
|
||||
__typename: "PositionConnection";
|
||||
/**
|
||||
* The positions in this connection
|
||||
*/
|
||||
edges: MarketPositions_party_positionsConnection_edges[] | null;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party {
|
||||
__typename: "Party";
|
||||
/**
|
||||
* Party identifier
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Collateral accounts relating to a party
|
||||
*/
|
||||
accounts: MarketPositions_party_accounts[] | null;
|
||||
/**
|
||||
* Trading positions relating to a party
|
||||
*/
|
||||
positionsConnection: MarketPositions_party_positionsConnection | null;
|
||||
}
|
||||
|
||||
export interface MarketPositions {
|
||||
/**
|
||||
* An entity that is trading on the Vega network
|
||||
*/
|
||||
party: MarketPositions_party | null;
|
||||
}
|
||||
|
||||
export interface MarketPositionsVariables {
|
||||
partyId: string;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import type {
|
||||
MarketMarkPrice,
|
||||
MarketMarkPriceVariables,
|
||||
} from './__generated__/MarketMarkPrice';
|
||||
|
||||
const MARKET_MARK_PRICE = gql`
|
||||
query MarketMarkPrice($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
decimalPlaces
|
||||
data {
|
||||
markPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default (marketId: string) => {
|
||||
const memoRef = useRef<MarketMarkPrice | null>(null);
|
||||
const { data } = useQuery<MarketMarkPrice, MarketMarkPriceVariables>(
|
||||
MARKET_MARK_PRICE,
|
||||
{
|
||||
pollInterval: 5000,
|
||||
variables: { marketId },
|
||||
skip: !marketId,
|
||||
}
|
||||
);
|
||||
return useMemo(() => {
|
||||
if (
|
||||
data &&
|
||||
data.market?.data?.markPrice !== memoRef.current?.market?.data?.markPrice
|
||||
) {
|
||||
memoRef.current = data;
|
||||
}
|
||||
return memoRef.current;
|
||||
}, [data, memoRef]);
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type {
|
||||
MarketPositions,
|
||||
MarketPositionsVariables,
|
||||
} from './__generated__/marketPositions';
|
||||
|
||||
const MARKET_POSITIONS_QUERY = gql`
|
||||
query MarketPositions($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accounts {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
}
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
positionsConnection {
|
||||
edges {
|
||||
node {
|
||||
openVolume
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
export type PositionMargin = {
|
||||
openVolume: BigNumber;
|
||||
balance: BigNumber;
|
||||
balanceDecimals?: number;
|
||||
} | null;
|
||||
|
||||
export default ({ marketId, partyId }: Props): PositionMargin => {
|
||||
const { data } = useQuery<MarketPositions, MarketPositionsVariables>(
|
||||
MARKET_POSITIONS_QUERY,
|
||||
{
|
||||
pollInterval: 5000,
|
||||
variables: { partyId },
|
||||
skip: !partyId,
|
||||
fetchPolicy: 'no-cache',
|
||||
}
|
||||
);
|
||||
|
||||
const account = data?.party?.accounts?.find(
|
||||
(nodes) => nodes.market?.id === marketId
|
||||
);
|
||||
|
||||
if (account) {
|
||||
const balance = new BigNumber(account.balance || 0);
|
||||
const openVolume = new BigNumber(
|
||||
data?.party?.positionsConnection?.edges?.find(
|
||||
(nodes) => nodes.node.market.id === marketId
|
||||
)?.node.openVolume || 0
|
||||
);
|
||||
if (!balance.isZero() && !openVolume.isZero()) {
|
||||
return {
|
||||
balance,
|
||||
balanceDecimals: account?.asset.decimals,
|
||||
openVolume,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useMarketFilters = (data: Market[]) => {
|
||||
export const useMarketFilters = (data: Market[]) => {
|
||||
const [products, setProducts] = useState<string[]>([]);
|
||||
const [assetsPerProduct, setAssetsPerProduct] = useState<
|
||||
Record<string, string[]>
|
||||
@ -36,5 +36,3 @@ const useMarketFilters = (data: Market[]) => {
|
||||
}, [data]);
|
||||
return { products, assetsPerProduct };
|
||||
};
|
||||
|
||||
export default useMarketFilters;
|
||||
|
@ -1,119 +0,0 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import type {
|
||||
EstimateOrder,
|
||||
EstimateOrderVariables,
|
||||
EstimateOrder_estimateOrder_fee,
|
||||
} from './__generated__/estimateOrder';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { addDecimal, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import useMarketPositions from './use-market-positions';
|
||||
import useMarketData from './use-market-data';
|
||||
|
||||
export const ESTIMATE_ORDER_QUERY = gql`
|
||||
query EstimateOrder(
|
||||
$marketId: ID!
|
||||
$partyId: ID!
|
||||
$price: String
|
||||
$size: String!
|
||||
$side: Side!
|
||||
$timeInForce: OrderTimeInForce!
|
||||
$expiration: String
|
||||
$type: OrderType!
|
||||
) {
|
||||
estimateOrder(
|
||||
marketId: $marketId
|
||||
partyId: $partyId
|
||||
price: $price
|
||||
size: $size
|
||||
side: $side
|
||||
timeInForce: $timeInForce
|
||||
expiration: $expiration
|
||||
type: $type
|
||||
) {
|
||||
fee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: DealTicketMarketFragment;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
const addFees = (feeObj: EstimateOrder_estimateOrder_fee) => {
|
||||
return new BigNumber(feeObj.makerFee)
|
||||
.plus(feeObj.liquidityFee)
|
||||
.plus(feeObj.infrastructureFee);
|
||||
};
|
||||
|
||||
export interface OrderMargin {
|
||||
margin: string;
|
||||
fees: string | null;
|
||||
}
|
||||
|
||||
const useOrderMargin = ({
|
||||
order,
|
||||
market,
|
||||
partyId,
|
||||
}: Props): OrderMargin | null => {
|
||||
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
|
||||
const markPriceData = useMarketData(market.id);
|
||||
const { data } = useQuery<EstimateOrder, EstimateOrderVariables>(
|
||||
ESTIMATE_ORDER_QUERY,
|
||||
{
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
partyId,
|
||||
price: markPriceData?.market?.data?.markPrice || '',
|
||||
size: removeDecimal(
|
||||
BigNumber.maximum(
|
||||
0,
|
||||
new BigNumber(marketPositions?.openVolume || 0)[
|
||||
order.side === Side.SIDE_BUY ? 'plus' : 'minus'
|
||||
](order.size)
|
||||
).toString(),
|
||||
market.positionDecimalPlaces
|
||||
),
|
||||
side: order.side === Side.SIDE_BUY ? Side.SIDE_BUY : Side.SIDE_SELL,
|
||||
timeInForce: order.timeInForce,
|
||||
type: order.type,
|
||||
},
|
||||
skip:
|
||||
!partyId ||
|
||||
!market.id ||
|
||||
!order.size ||
|
||||
!markPriceData?.market?.data?.markPrice,
|
||||
}
|
||||
);
|
||||
|
||||
if (data?.estimateOrder.marginLevels.initialLevel) {
|
||||
const fees =
|
||||
data?.estimateOrder?.fee && addFees(data.estimateOrder.fee).toString();
|
||||
return {
|
||||
margin: addDecimal(
|
||||
BigNumber.maximum(
|
||||
0,
|
||||
new BigNumber(data.estimateOrder.marginLevels.initialLevel).minus(
|
||||
marketPositions?.balance || 0
|
||||
)
|
||||
).toString(),
|
||||
market.decimalPlaces
|
||||
),
|
||||
fees: addDecimal(fees, market.decimalPlaces),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default useOrderMargin;
|
@ -230,7 +230,7 @@ describe('deal ticket validation', { tags: '@smoke' }, () => {
|
||||
|
||||
it('order connect vega wallet button should connect', () => {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.getByTestId(orderPriceField).type('101');
|
||||
cy.getByTestId(orderPriceField).clear().type('101');
|
||||
cy.getByTestId('order-connect-wallet').click();
|
||||
cy.getByTestId('dialog-content').should('be.visible');
|
||||
cy.getByTestId('connectors-list')
|
||||
|
@ -14,6 +14,13 @@ export const generateDealTicketQuery = (
|
||||
positionDecimalPlaces: 0,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: '0.0002',
|
||||
infrastructureFee: '0.0005',
|
||||
liquidityFee: '0.0005',
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
PriceCell,
|
||||
signedNumberCssClass,
|
||||
t,
|
||||
@ -13,19 +12,15 @@ import {
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import { PriceCellChange, Sparkline, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
calcCandleHigh,
|
||||
calcCandleLow,
|
||||
totalFees,
|
||||
} from '@vegaprotocol/market-list';
|
||||
import { calcCandleHigh, calcCandleLow } from '@vegaprotocol/market-list';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
import type {
|
||||
MarketWithData,
|
||||
MarketWithCandles,
|
||||
} from '@vegaprotocol/market-list';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { FeesCell } from '@vegaprotocol/market-info';
|
||||
|
||||
type Market = MarketWithData & MarketWithCandles;
|
||||
|
||||
@ -502,43 +497,3 @@ export const columnsPositionMarkets = (
|
||||
];
|
||||
return selectMarketColumns;
|
||||
};
|
||||
|
||||
const FeesCell = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors: Market['fees']['factors'];
|
||||
}) => (
|
||||
<Tooltip description={<FeesBreakdown feeFactors={feeFactors} />}>
|
||||
<span>{totalFees(feeFactors) ?? '-'}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const FeesBreakdown = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors?: Market['fees']['factors'];
|
||||
}) => {
|
||||
if (!feeFactors) return null;
|
||||
return (
|
||||
<dl className="grid grid-cols-2 gap-x-2">
|
||||
<dt>{t('Infrastructure fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Liquidity fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Maker fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(new BigNumber(feeFactors.makerFee).times(100))}
|
||||
</dd>
|
||||
<dt>{t('Total fees')}</dt>
|
||||
<dd className="text-right">{totalFees(feeFactors)}</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon, Tooltip, TrafficLight } from '@vegaprotocol/ui-toolkit';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import * as constants from './constants';
|
||||
import { TrafficLight } from '../traffic-light';
|
||||
|
||||
interface DealTicketEstimatesProps {
|
||||
quoteName?: string;
|
||||
@ -17,45 +16,6 @@ interface DealTicketEstimatesProps {
|
||||
slippage?: string;
|
||||
}
|
||||
|
||||
interface DataTitleProps {
|
||||
children: ReactNode;
|
||||
quoteName?: string;
|
||||
}
|
||||
|
||||
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
|
||||
<dt>
|
||||
{children}
|
||||
{quoteName && <small> ({quoteName})</small>}
|
||||
</dt>
|
||||
);
|
||||
|
||||
interface ValueTooltipProps {
|
||||
value?: string;
|
||||
children?: ReactNode;
|
||||
description: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const ValueTooltipRow = ({
|
||||
value,
|
||||
children,
|
||||
description,
|
||||
id,
|
||||
}: ValueTooltipProps) => (
|
||||
<dd className="flex gap-x-2 items-center">
|
||||
{value || children}
|
||||
<Tooltip align="center" description={description}>
|
||||
<div className="cursor-help" id={id || ''} tabIndex={-1}>
|
||||
<Icon
|
||||
name={IconNames.ISSUE}
|
||||
className="block rotate-180"
|
||||
ariaLabel={description}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</dd>
|
||||
);
|
||||
|
||||
export const DealTicketEstimates = ({
|
||||
price,
|
||||
quoteName,
|
||||
@ -131,3 +91,42 @@ export const DealTicketEstimates = ({
|
||||
)}
|
||||
</dl>
|
||||
);
|
||||
|
||||
interface DataTitleProps {
|
||||
children: ReactNode;
|
||||
quoteName?: string;
|
||||
}
|
||||
|
||||
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
|
||||
<dt>
|
||||
{children}
|
||||
{quoteName && <small> ({quoteName})</small>}
|
||||
</dt>
|
||||
);
|
||||
|
||||
interface ValueTooltipProps {
|
||||
value?: string;
|
||||
children?: ReactNode;
|
||||
description: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const ValueTooltipRow = ({
|
||||
value,
|
||||
children,
|
||||
description,
|
||||
id,
|
||||
}: ValueTooltipProps) => (
|
||||
<dd className="flex gap-x-2 items-center">
|
||||
{value || children}
|
||||
<Tooltip align="center" description={description}>
|
||||
<div className="cursor-help" id={id || ''} tabIndex={-1}>
|
||||
<Icon
|
||||
name={IconNames.ISSUE}
|
||||
className="block rotate-180"
|
||||
ariaLabel={description}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</dd>
|
||||
);
|
@ -12,7 +12,7 @@ import type { ValidationProps } from './use-order-validation';
|
||||
import { marketTranslations } from './use-order-validation';
|
||||
import { useOrderValidation } from './use-order-validation';
|
||||
import { ERROR_SIZE_DECIMAL } from './validate-size';
|
||||
import type { DealTicketMarketFragment } from '../deal-ticket/__generated__/DealTicket';
|
||||
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
|
||||
|
||||
jest.mock('@vegaprotocol/wallet');
|
||||
|
||||
|
@ -16,6 +16,13 @@ fragment DealTicketMarket on Market {
|
||||
auctionEnd
|
||||
trigger
|
||||
}
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
|
@ -3,14 +3,14 @@ import { Schema as Types } from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type DealTicketMarketFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } };
|
||||
export type DealTicketMarketFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } };
|
||||
|
||||
export type DealTicketQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DealTicketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null };
|
||||
export type DealTicketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null };
|
||||
|
||||
export const DealTicketMarketFragmentDoc = gql`
|
||||
fragment DealTicketMarket on Market {
|
||||
@ -31,6 +31,13 @@ export const DealTicketMarketFragmentDoc = gql`
|
||||
auctionEnd
|
||||
trigger
|
||||
}
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
|
@ -0,0 +1,37 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface DealTicketFeeDetailsProps {
|
||||
details: DealTicketFeeDetails[];
|
||||
}
|
||||
|
||||
export interface DealTicketFeeDetails {
|
||||
label: string;
|
||||
value?: string | number | null;
|
||||
labelDescription?: string | ReactNode;
|
||||
quoteName?: string;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
details,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
return (
|
||||
<div>
|
||||
{details.map(({ label, value, labelDescription, quoteName }) => (
|
||||
<div
|
||||
key={label}
|
||||
className="text-sm mt-2 flex justify-between items-center gap-4 flex-wrap"
|
||||
>
|
||||
<div>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-300">{`${
|
||||
value ?? '-'
|
||||
} ${quoteName || ''}`}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { FormGroup, Input, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { t, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { formatNumber, t, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
import { validateSize } from '../deal-ticket-validation/validate-size';
|
||||
import { isMarketInAuction } from '../deal-ticket-validation/use-order-validation';
|
||||
@ -52,7 +52,7 @@ export const DealTicketMarketAmount = ({
|
||||
<div className="text-sm text-right">
|
||||
{price && quoteName ? (
|
||||
<>
|
||||
~{price} {quoteName}
|
||||
~{formatNumber(price, market.decimalPlaces)} {quoteName}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
|
@ -1,10 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import type { DealTicketMarketFragment } from './__generated___/DealTicket';
|
||||
import { Schema } from '@vegaprotocol/types';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { ChainIdQuery } from '@vegaprotocol/react-helpers';
|
||||
import { ChainIdDocument, addDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const market: DealTicketMarketFragment = {
|
||||
__typename: 'Market',
|
||||
@ -32,6 +36,13 @@ const market: DealTicketMarketFragment = {
|
||||
},
|
||||
},
|
||||
},
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: '0.001',
|
||||
infrastructureFee: '0.002',
|
||||
liquidityFee: '0.003',
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: {
|
||||
@ -43,22 +54,37 @@ const market: DealTicketMarketFragment = {
|
||||
const submit = jest.fn();
|
||||
const transactionStatus = 'default';
|
||||
|
||||
const mockChainId = 'chain-id';
|
||||
|
||||
function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
|
||||
const chainIdMock: MockedResponse<ChainIdQuery> = {
|
||||
request: {
|
||||
query: ChainIdDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
statistics: {
|
||||
chainId: mockChainId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
<VegaWalletContext.Provider value={{} as any}>
|
||||
<DealTicket
|
||||
defaultOrder={order}
|
||||
market={market}
|
||||
submit={submit}
|
||||
transactionStatus={transactionStatus}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
<MockedProvider mocks={[chainIdMock]}>
|
||||
<VegaWalletContext.Provider value={{} as any}>
|
||||
<DealTicket
|
||||
defaultOrder={order}
|
||||
market={market}
|
||||
submit={submit}
|
||||
transactionStatus={transactionStatus}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('DealTicket', () => {
|
||||
it('Displays ticket defaults', () => {
|
||||
it('should display ticket defaults', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Assert defaults are used
|
||||
@ -87,7 +113,7 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Can edit deal ticket', async () => {
|
||||
it('can edit deal ticket', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
// BUY is selected by default
|
||||
@ -119,7 +145,7 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Handles TIF select box dependent on order type', () => {
|
||||
it('handles TIF select box dependent on order type', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Check only IOC and
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import {
|
||||
t,
|
||||
addDecimalsFormatNumber,
|
||||
removeDecimal,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { t, removeDecimal, addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
@ -20,6 +16,11 @@ import {
|
||||
isMarketInAuction,
|
||||
useOrderValidation,
|
||||
} from '../deal-ticket-validation/use-order-validation';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import {
|
||||
useFeeDealTicketDetails,
|
||||
getFeeDetailsValues,
|
||||
} from '../../hooks/use-fee-deal-ticket-details';
|
||||
|
||||
export type TransactionStatus = 'default' | 'pending';
|
||||
|
||||
@ -44,18 +45,17 @@ export const DealTicket = ({
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<OrderSubmissionBody['orderSubmission']>({
|
||||
mode: 'onChange',
|
||||
defaultValues: getDefaultOrder(market),
|
||||
});
|
||||
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
const order = watch();
|
||||
const { message, isDisabled: disabled } = useOrderValidation({
|
||||
market,
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
orderType: order.type,
|
||||
orderTimeInForce: order.timeInForce,
|
||||
fieldErrors: errors,
|
||||
});
|
||||
const isDisabled = transactionStatus === 'pending' || disabled;
|
||||
@ -78,20 +78,31 @@ export const DealTicket = ({
|
||||
[isDisabled, submit, market.decimalPlaces, market.positionDecimalPlaces]
|
||||
);
|
||||
|
||||
const getPrice = () => {
|
||||
const getEstimatedMarketPrice = () => {
|
||||
if (isMarketInAuction(market)) {
|
||||
// 0 can never be a valid uncrossing price as it would require there being orders on the book at that price.
|
||||
// 0 can never be a valid uncrossing price
|
||||
// as it would require there being orders on the book at that price.
|
||||
if (
|
||||
market.data?.indicativePrice &&
|
||||
BigInt(market.data?.indicativePrice) !== BigInt(0)
|
||||
) {
|
||||
return market.data.indicativePrice;
|
||||
}
|
||||
return '-';
|
||||
return undefined;
|
||||
}
|
||||
return market.depth.lastTrade?.price;
|
||||
};
|
||||
const price = getPrice();
|
||||
const marketPrice = getEstimatedMarketPrice();
|
||||
const marketPriceFormatted =
|
||||
marketPrice && addDecimal(marketPrice, market.decimalPlaces);
|
||||
useEffect(() => {
|
||||
if (marketPriceFormatted && order.type === OrderType.TYPE_MARKET) {
|
||||
setValue('price', marketPriceFormatted);
|
||||
}
|
||||
}, [marketPriceFormatted, order.type, setValue]);
|
||||
|
||||
const feeDetails = useFeeDealTicketDetails(order, market);
|
||||
const details = getFeeDetailsValues(feeDetails);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
|
||||
@ -110,14 +121,10 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
<DealTicketAmount
|
||||
orderType={orderType}
|
||||
orderType={order.type}
|
||||
market={market}
|
||||
register={register}
|
||||
price={
|
||||
price
|
||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
price={order.price}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
<Controller
|
||||
@ -126,13 +133,13 @@ export const DealTicket = ({
|
||||
render={({ field }) => (
|
||||
<TimeInForceSelector
|
||||
value={field.value}
|
||||
orderType={orderType}
|
||||
orderType={order.type}
|
||||
onSelect={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{orderType === OrderType.TYPE_LIMIT &&
|
||||
orderTimeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||
{order.type === OrderType.TYPE_LIMIT &&
|
||||
order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
@ -174,6 +181,7 @@ export const DealTicket = ({
|
||||
{t('Connect wallet')}
|
||||
</Button>
|
||||
)}
|
||||
<DealTicketFeeDetails details={details} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-validation';
|
||||
export * from './trading-mode-tooltip';
|
||||
export * from './deal-ticket-estimates';
|
||||
export * from './constants';
|
||||
|
@ -26,5 +26,6 @@ query EstimateOrder(
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@ query MarketMarkPrice($marketId: ID!) {
|
||||
decimalPlaces
|
||||
data {
|
||||
markPrice
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
libs/deal-ticket/src/hooks/PartyBalance.graphql
Normal file
18
libs/deal-ticket/src/hooks/PartyBalance.graphql
Normal file
@ -0,0 +1,18 @@
|
||||
query PartyBalance($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accounts {
|
||||
...Account
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment Account on Account {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
decimals
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ export type EstimateOrderQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type EstimateOrderQuery = { __typename?: 'Query', estimateOrder: { __typename?: 'OrderEstimate', fee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, marginLevels: { __typename?: 'MarginLevels', initialLevel: string } } };
|
||||
export type EstimateOrderQuery = { __typename?: 'Query', estimateOrder: { __typename?: 'OrderEstimate', totalFeeAmount: string, fee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, marginLevels: { __typename?: 'MarginLevels', initialLevel: string } } };
|
||||
|
||||
|
||||
export const EstimateOrderDocument = gql`
|
||||
@ -38,6 +38,7 @@ export const EstimateOrderDocument = gql`
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
}
|
||||
`;
|
@ -8,7 +8,7 @@ export type MarketMarkPriceQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type MarketMarkPriceQuery = { __typename?: 'Query', market?: { __typename?: 'Market', decimalPlaces: number, data?: { __typename?: 'MarketData', markPrice: string } | null } | null };
|
||||
export type MarketMarkPriceQuery = { __typename?: 'Query', market?: { __typename?: 'Market', decimalPlaces: number, data?: { __typename?: 'MarketData', markPrice: string, market: { __typename?: 'Market', id: string } } | null } | null };
|
||||
|
||||
|
||||
export const MarketMarkPriceDocument = gql`
|
||||
@ -17,6 +17,9 @@ export const MarketMarkPriceDocument = gql`
|
||||
decimalPlaces
|
||||
data {
|
||||
markPrice
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
libs/deal-ticket/src/hooks/__generated__/PartyBalance.ts
generated
Normal file
63
libs/deal-ticket/src/hooks/__generated__/PartyBalance.ts
generated
Normal file
@ -0,0 +1,63 @@
|
||||
import { Schema as Types } from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type PartyBalanceQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PartyBalanceQuery = { __typename?: 'Query', party?: { __typename?: 'Party', accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } }> | null } | null };
|
||||
|
||||
export type AccountFragment = { __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } };
|
||||
|
||||
export const AccountFragmentDoc = gql`
|
||||
fragment Account on Account {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
decimals
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const PartyBalanceDocument = gql`
|
||||
query PartyBalance($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accounts {
|
||||
...Account
|
||||
}
|
||||
}
|
||||
}
|
||||
${AccountFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __usePartyBalanceQuery__
|
||||
*
|
||||
* To run a query within a React component, call `usePartyBalanceQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `usePartyBalanceQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = usePartyBalanceQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function usePartyBalanceQuery(baseOptions: Apollo.QueryHookOptions<PartyBalanceQuery, PartyBalanceQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<PartyBalanceQuery, PartyBalanceQueryVariables>(PartyBalanceDocument, options);
|
||||
}
|
||||
export function usePartyBalanceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyBalanceQuery, PartyBalanceQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<PartyBalanceQuery, PartyBalanceQueryVariables>(PartyBalanceDocument, options);
|
||||
}
|
||||
export type PartyBalanceQueryHookResult = ReturnType<typeof usePartyBalanceQuery>;
|
||||
export type PartyBalanceLazyQueryHookResult = ReturnType<typeof usePartyBalanceLazyQuery>;
|
||||
export type PartyBalanceQueryResult = Apollo.QueryResult<PartyBalanceQuery, PartyBalanceQueryVariables>;
|
13
libs/deal-ticket/src/hooks/index.ts
Normal file
13
libs/deal-ticket/src/hooks/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export * from './__generated__/EstimateOrder';
|
||||
export * from './__generated__/MarketMarkPrice';
|
||||
export * from './__generated__/MarketPositions';
|
||||
export * from './__generated__/PartyBalance';
|
||||
export * from './__generated__/PartyMarketData';
|
||||
export * from './use-calculate-slippage';
|
||||
export * from './use-fee-deal-ticket-details';
|
||||
export * from './use-market-data-mark-price';
|
||||
export * from './use-market-positions';
|
||||
export * from './use-maximum-position-size';
|
||||
export * from './use-order-closeout';
|
||||
export * from './use-order-margin';
|
||||
export * from './use-settlement-account';
|
@ -2,7 +2,7 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import useCalculateSlippage from './use-calculate-slippage';
|
||||
import { useCalculateSlippage } from './use-calculate-slippage';
|
||||
|
||||
const mockData = {
|
||||
decimalPlaces: 0,
|
@ -16,7 +16,7 @@ interface Props {
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
}
|
||||
|
||||
const useCalculateSlippage = ({ marketId, order }: Props) => {
|
||||
export const useCalculateSlippage = ({ marketId, order }: Props) => {
|
||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||
const { data } = useOrderBookData({
|
||||
variables,
|
||||
@ -69,5 +69,3 @@ const useCalculateSlippage = ({ marketId, order }: Props) => {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default useCalculateSlippage;
|
142
libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx
Normal file
142
libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { FeesBreakdown } from '@vegaprotocol/market-info';
|
||||
import { formatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useMemo } from 'react';
|
||||
import type { DealTicketMarketFragment } from '../components';
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
EST_MARGIN_TOOLTIP_TEXT,
|
||||
EST_CLOSEOUT_TOOLTIP_TEXT,
|
||||
} from '../components/constants';
|
||||
import { useCalculateSlippage } from './use-calculate-slippage';
|
||||
import { useOrderCloseOut } from './use-order-closeout';
|
||||
import type { OrderMargin } from './use-order-margin';
|
||||
import { useOrderMargin } from './use-order-margin';
|
||||
import { usePartyBalanceQuery } from './__generated__/PartyBalance';
|
||||
|
||||
export const useFeeDealTicketDetails = (
|
||||
order: OrderSubmissionBody['orderSubmission'],
|
||||
market: DealTicketMarketFragment
|
||||
) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
||||
|
||||
const price = useMemo(() => {
|
||||
const estPrice = order.price || market.depth.lastTrade?.price;
|
||||
if (estPrice) {
|
||||
if (slippage && parseFloat(slippage) !== 0) {
|
||||
const isLong = order.side === Side.SIDE_BUY;
|
||||
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
|
||||
parseFloat(slippage) / 100
|
||||
);
|
||||
return new BigNumber(estPrice).multipliedBy(multiplier).toNumber();
|
||||
}
|
||||
return order.price;
|
||||
}
|
||||
return null;
|
||||
}, [market.depth.lastTrade?.price, order.price, order.side, slippage]);
|
||||
|
||||
const estMargin: OrderMargin | null = useOrderMargin({
|
||||
order,
|
||||
market,
|
||||
partyId: pubKey || '',
|
||||
});
|
||||
|
||||
const { data: partyBalance } = usePartyBalanceQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const estCloseOut = useOrderCloseOut({
|
||||
order,
|
||||
market,
|
||||
partyData: partyBalance,
|
||||
});
|
||||
|
||||
const notionalSize = useMemo(() => {
|
||||
if (order.price && order.size) {
|
||||
return new BigNumber(order.size).multipliedBy(order.price).toString();
|
||||
}
|
||||
return null;
|
||||
}, [order.price, order.size]);
|
||||
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return {
|
||||
market,
|
||||
quoteName,
|
||||
notionalSize,
|
||||
estMargin,
|
||||
estCloseOut,
|
||||
slippage,
|
||||
price,
|
||||
partyData: partyBalance,
|
||||
};
|
||||
};
|
||||
|
||||
export interface FeeDetails {
|
||||
market: DealTicketMarketFragment;
|
||||
quoteName: string;
|
||||
notionalSize: string | null;
|
||||
estMargin: OrderMargin | null;
|
||||
estCloseOut: string | null;
|
||||
slippage: string | null;
|
||||
price?: string | number | null;
|
||||
}
|
||||
|
||||
export const getFeeDetailsValues = ({
|
||||
quoteName,
|
||||
notionalSize,
|
||||
estMargin,
|
||||
estCloseOut,
|
||||
market,
|
||||
}: FeeDetails) => {
|
||||
const formatValue = (value: string | number | null | undefined): string => {
|
||||
return value && !isNaN(Number(value))
|
||||
? formatNumber(value, market.decimalPlaces)
|
||||
: '-';
|
||||
};
|
||||
return [
|
||||
{
|
||||
label: t('Notional value'),
|
||||
value: formatValue(notionalSize),
|
||||
quoteName,
|
||||
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
},
|
||||
{
|
||||
label: t('Fees'),
|
||||
value: estMargin?.totalFees && `~${formatValue(estMargin?.totalFees)}`,
|
||||
labelDescription: (
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
'The most you would be expected to pay in fees, the actual amount may vary.'
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={estMargin?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
quoteName,
|
||||
},
|
||||
{
|
||||
label: t('Margin required'),
|
||||
value: estMargin?.margin && `~${formatValue(estMargin?.margin)}`,
|
||||
quoteName,
|
||||
labelDescription: EST_MARGIN_TOOLTIP_TEXT,
|
||||
},
|
||||
{
|
||||
label: t('Liquidation price (variable)'),
|
||||
value: formatValue(estCloseOut),
|
||||
quoteName,
|
||||
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT,
|
||||
},
|
||||
];
|
||||
};
|
21
libs/deal-ticket/src/hooks/use-market-data-mark-price.ts
Normal file
21
libs/deal-ticket/src/hooks/use-market-data-mark-price.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import type { MarketMarkPriceQuery } from './__generated__/MarketMarkPrice';
|
||||
import { useMarketMarkPriceQuery } from './__generated__/MarketMarkPrice';
|
||||
|
||||
export const useMarketDataMarkPrice = (marketId: string) => {
|
||||
const memoRef = useRef<MarketMarkPriceQuery | null>(null);
|
||||
const { data } = useMarketMarkPriceQuery({
|
||||
pollInterval: 5000,
|
||||
variables: { marketId },
|
||||
skip: !marketId,
|
||||
});
|
||||
return useMemo(() => {
|
||||
if (
|
||||
data &&
|
||||
data.market?.data?.markPrice !== memoRef.current?.market?.data?.markPrice
|
||||
) {
|
||||
memoRef.current = data;
|
||||
}
|
||||
return memoRef.current;
|
||||
}, [data, memoRef]);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import useMarketPositions from './use-market-positions';
|
||||
import { useMarketPositions } from './use-market-positions';
|
||||
|
||||
let mockNotEmptyData = {
|
||||
party: {
|
46
libs/deal-ticket/src/hooks/use-market-positions.ts
Normal file
46
libs/deal-ticket/src/hooks/use-market-positions.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { useMarketPositionsQuery } from './__generated__/MarketPositions';
|
||||
interface Props {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
export type PositionMargin = {
|
||||
openVolume: BigNumber;
|
||||
balance: BigNumber;
|
||||
balanceDecimals?: number;
|
||||
} | null;
|
||||
|
||||
export const useMarketPositions = ({
|
||||
marketId,
|
||||
partyId,
|
||||
}: Props): PositionMargin => {
|
||||
const { data } = useMarketPositionsQuery({
|
||||
pollInterval: 5000,
|
||||
variables: { partyId },
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
const account = data?.party?.accounts?.find(
|
||||
(nodes) => nodes.market?.id === marketId
|
||||
);
|
||||
|
||||
if (account) {
|
||||
const positionConnectionNode =
|
||||
data?.party?.positionsConnection?.edges?.find(
|
||||
(nodes) => nodes.node.market.id === marketId
|
||||
);
|
||||
const balance = new BigNumber(account.balance || 0);
|
||||
const openVolume = new BigNumber(
|
||||
positionConnectionNode?.node.openVolume || 0
|
||||
);
|
||||
if (!balance.isZero() && !openVolume.isZero()) {
|
||||
return {
|
||||
balance,
|
||||
balanceDecimals: account?.asset.decimals,
|
||||
openVolume,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
@ -1,6 +1,4 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import useMaximumPositionSize from './use-maximum-position-size';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import {
|
||||
AccountType,
|
||||
OrderTimeInForce,
|
||||
@ -9,6 +7,9 @@ import {
|
||||
} from '@vegaprotocol/types';
|
||||
import type { PositionMargin } from './use-market-positions';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { useMaximumPositionSize } from './use-maximum-position-size';
|
||||
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
|
||||
const defaultMockMarketPositions = {
|
||||
openVolume: new BigNumber(1),
|
||||
@ -17,7 +18,7 @@ const defaultMockMarketPositions = {
|
||||
|
||||
let mockMarketPositions: PositionMargin | null = defaultMockMarketPositions;
|
||||
|
||||
const mockAccount: PartyBalanceQuery_party_accounts = {
|
||||
const mockAccount: Account = {
|
||||
__typename: 'Account',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
balance: '200000',
|
||||
@ -30,11 +31,12 @@ const mockAccount: PartyBalanceQuery_party_accounts = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockOrder = {
|
||||
const mockOrder: OrderSubmissionBody['orderSubmission'] = {
|
||||
type: OrderType.TYPE_MARKET,
|
||||
size: '1',
|
||||
side: Side.SIDE_BUY,
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
marketId: 'market-id',
|
||||
};
|
||||
|
||||
jest.mock('./use-settlement-account', () => {
|
||||
@ -42,9 +44,18 @@ jest.mock('./use-settlement-account', () => {
|
||||
useSettlementAccount: jest.fn(() => mockAccount),
|
||||
};
|
||||
});
|
||||
jest.mock('./use-market-positions', () => jest.fn(() => mockMarketPositions));
|
||||
|
||||
describe('useMaximumPositionSize Hook', () => {
|
||||
jest.mock('./use-market-positions', () => ({
|
||||
useMarketPositions: ({
|
||||
marketId,
|
||||
partyId,
|
||||
}: {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
}) => mockMarketPositions,
|
||||
}));
|
||||
|
||||
describe('useMaximumPositionSize', () => {
|
||||
it('should return correct size when no open positions', () => {
|
||||
mockMarketPositions = null;
|
||||
const price = '50';
|
@ -1,13 +1,13 @@
|
||||
import useMarketPositions from './use-market-positions';
|
||||
import { useMarketPositions } from './use-market-positions';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import { AccountType, Side } from '@vegaprotocol/types';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||
|
||||
interface Props {
|
||||
partyId: string;
|
||||
accounts: PartyBalanceQuery_party_accounts[];
|
||||
accounts: Account[];
|
||||
marketId: string;
|
||||
price?: string;
|
||||
settlementAssetId: string;
|
||||
@ -17,7 +17,7 @@ interface Props {
|
||||
const getSize = (balance: string, price: string) =>
|
||||
new BigNumber(balance).dividedBy(new BigNumber(price));
|
||||
|
||||
export default ({
|
||||
export const useMaximumPositionSize = ({
|
||||
marketId,
|
||||
accounts,
|
||||
partyId,
|
@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import useOrderCloseOut from './use-order-closeout';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalance';
|
||||
import { useOrderCloseOut } from './use-order-closeout';
|
||||
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||
|
||||
jest.mock('@vegaprotocol/wallet', () => ({
|
||||
...jest.requireActual('@vegaprotocol/wallet'),
|
||||
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
|
||||
}));
|
||||
|
||||
describe('useOrderCloseOut Hook', () => {
|
||||
describe('useOrderCloseOut', () => {
|
||||
const order = { size: '2', side: 'SIDE_BUY' };
|
||||
const market = {
|
||||
decimalPlaces: 5,
|
||||
@ -44,7 +44,7 @@ describe('useOrderCloseOut Hook', () => {
|
||||
},
|
||||
};
|
||||
|
||||
it('return proper buy value', () => {
|
||||
it('should return proper null value', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useOrderCloseOut({
|
||||
@ -58,10 +58,10 @@ describe('useOrderCloseOut Hook', () => {
|
||||
),
|
||||
}
|
||||
);
|
||||
expect(result.current).toEqual(' - ');
|
||||
expect(result.current).toEqual(null);
|
||||
});
|
||||
|
||||
it('return proper sell value', () => {
|
||||
it('should return proper sell value', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useOrderCloseOut({
|
||||
@ -81,7 +81,7 @@ describe('useOrderCloseOut Hook', () => {
|
||||
expect(result.current).toEqual('1.00000');
|
||||
});
|
||||
|
||||
it('return proper empty value', () => {
|
||||
it('should return proper empty value', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useOrderCloseOut({
|
@ -1,49 +1,14 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import useMarketPositions from './use-market-positions';
|
||||
import useMarketData from './use-market-data';
|
||||
import type {
|
||||
PartyMarketData,
|
||||
PartyMarketDataVariables,
|
||||
} from './__generated__/PartyMarketData';
|
||||
import { useMarketPositions } from './use-market-positions';
|
||||
import { useMarketDataMarkPrice } from './use-market-data-mark-price';
|
||||
import { usePartyMarketDataQuery } from './__generated__/PartyMarketData';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
|
||||
const CLOSEOUT_PRICE_QUERY = gql`
|
||||
query PartyMarketData($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accounts {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
id
|
||||
decimals
|
||||
}
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
marginsConnection {
|
||||
edges {
|
||||
node {
|
||||
market {
|
||||
id
|
||||
}
|
||||
initialLevel
|
||||
maintenanceLevel
|
||||
searchLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalance';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
|
||||
interface Props {
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
@ -51,22 +16,23 @@ interface Props {
|
||||
partyData?: PartyBalanceQuery;
|
||||
}
|
||||
|
||||
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
||||
export const useOrderCloseOut = ({
|
||||
order,
|
||||
market,
|
||||
partyData,
|
||||
}: Props): string | null => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const account = useSettlementAccount(
|
||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||
partyData?.party?.accounts || []
|
||||
);
|
||||
const { data } = useQuery<PartyMarketData, PartyMarketDataVariables>(
|
||||
CLOSEOUT_PRICE_QUERY,
|
||||
{
|
||||
pollInterval: 5000,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
}
|
||||
);
|
||||
const { data } = usePartyMarketDataQuery({
|
||||
pollInterval: 5000,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const markPriceData = useMarketData(market.id);
|
||||
const markPriceData = useMarketDataMarkPrice(market.id);
|
||||
const marketPositions = useMarketPositions({
|
||||
marketId: market.id,
|
||||
partyId: pubKey || '',
|
||||
@ -94,7 +60,7 @@ const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
||||
);
|
||||
const volume = new BigNumber(
|
||||
addDecimal(
|
||||
marketPositions?.openVolume.toNumber() || 0,
|
||||
marketPositions?.openVolume.toString() || '0',
|
||||
market.positionDecimalPlaces
|
||||
)
|
||||
)[order.side === Side.SIDE_BUY ? 'plus' : 'minus'](order.size);
|
||||
@ -112,7 +78,5 @@ const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
||||
if (closeOut.isPositive()) {
|
||||
return formatNumber(closeOut, market.decimalPlaces);
|
||||
}
|
||||
return ' - ';
|
||||
return null;
|
||||
};
|
||||
|
||||
export default useOrderCloseOut;
|
@ -2,9 +2,9 @@ import { renderHook } from '@testing-library/react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||
import type { PositionMargin } from './use-market-positions';
|
||||
import useOrderMargin from './use-order-margin';
|
||||
import { useOrderMargin } from './use-order-margin';
|
||||
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||
|
||||
let mockEstimateData = {
|
||||
estimateOrder: {
|
||||
@ -27,9 +27,18 @@ let mockMarketPositions: PositionMargin = {
|
||||
openVolume: new BigNumber(1),
|
||||
balance: new BigNumber(100000),
|
||||
};
|
||||
jest.mock('./use-market-positions', () => jest.fn(() => mockMarketPositions));
|
||||
|
||||
describe('useOrderMargin Hook', () => {
|
||||
jest.mock('./use-market-positions', () => ({
|
||||
useMarketPositions: ({
|
||||
marketId,
|
||||
partyId,
|
||||
}: {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
}) => mockMarketPositions,
|
||||
}));
|
||||
|
||||
describe('useOrderMargin', () => {
|
||||
const order = {
|
||||
size: '2',
|
||||
side: 'SIDE_BUY',
|
||||
@ -50,7 +59,7 @@ describe('useOrderMargin Hook', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('margin should be properly calculated', () => {
|
||||
it('should calculate margin correctly', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
order: order as OrderSubmissionBody['orderSubmission'],
|
||||
@ -68,7 +77,7 @@ describe('useOrderMargin Hook', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('fees should be properly calculated', () => {
|
||||
it('should calculate fees correctly', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
order: order as OrderSubmissionBody['orderSubmission'],
|
||||
@ -76,10 +85,10 @@ describe('useOrderMargin Hook', () => {
|
||||
partyId,
|
||||
})
|
||||
);
|
||||
expect(result.current?.fees).toEqual('300000');
|
||||
expect(result.current?.totalFees).toEqual('300000');
|
||||
});
|
||||
|
||||
it('if there is no positions initialMargin should not be subtracted', () => {
|
||||
it('should not subtract initialMargin if there is no position', () => {
|
||||
mockMarketPositions = null;
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
@ -95,7 +104,7 @@ describe('useOrderMargin Hook', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('if api fails, should return empty value', () => {
|
||||
it('should return empty value if API fails', () => {
|
||||
mockEstimateData = {
|
||||
estimateOrder: {
|
||||
fee: {
|
89
libs/deal-ticket/src/hooks/use-order-margin.ts
Normal file
89
libs/deal-ticket/src/hooks/use-order-margin.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { addDecimal, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useMarketPositions } from './use-market-positions';
|
||||
import { useMarketDataMarkPrice } from './use-market-data-mark-price';
|
||||
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
|
||||
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
|
||||
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||
|
||||
interface Props {
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: DealTicketMarketFragment;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
const addFees = (feeObj: EstimateOrderQuery['estimateOrder']['fee']) => {
|
||||
return new BigNumber(feeObj.makerFee)
|
||||
.plus(feeObj.liquidityFee)
|
||||
.plus(feeObj.infrastructureFee);
|
||||
};
|
||||
|
||||
export interface OrderMargin {
|
||||
margin: string;
|
||||
totalFees: string | null;
|
||||
fees: {
|
||||
makerFee: string;
|
||||
liquidityFee: string;
|
||||
infrastructureFee: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const useOrderMargin = ({
|
||||
order,
|
||||
market,
|
||||
partyId,
|
||||
}: Props): OrderMargin | null => {
|
||||
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
|
||||
const markPriceData = useMarketDataMarkPrice(market.id);
|
||||
const { data } = useEstimateOrderQuery({
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
partyId,
|
||||
price: order.price
|
||||
? removeDecimal(order.price, market.decimalPlaces)
|
||||
: markPriceData?.market?.data?.markPrice || '',
|
||||
size: removeDecimal(
|
||||
BigNumber.maximum(
|
||||
0,
|
||||
new BigNumber(marketPositions?.openVolume || 0)
|
||||
[order.side === Side.SIDE_BUY ? 'plus' : 'minus'](order.size)
|
||||
.absoluteValue()
|
||||
).toString(),
|
||||
market.positionDecimalPlaces
|
||||
),
|
||||
side: order.side === Side.SIDE_BUY ? Side.SIDE_BUY : Side.SIDE_SELL,
|
||||
timeInForce: order.timeInForce,
|
||||
type: order.type,
|
||||
},
|
||||
skip:
|
||||
!partyId ||
|
||||
!market.id ||
|
||||
!order.size ||
|
||||
!markPriceData?.market?.data?.markPrice,
|
||||
});
|
||||
|
||||
if (data?.estimateOrder.marginLevels.initialLevel) {
|
||||
const fees =
|
||||
data?.estimateOrder?.fee && addFees(data.estimateOrder.fee).toString();
|
||||
const margin = BigNumber.maximum(
|
||||
0,
|
||||
new BigNumber(data.estimateOrder.marginLevels.initialLevel).minus(
|
||||
marketPositions?.balance || 0
|
||||
)
|
||||
).toString();
|
||||
const { makerFee, liquidityFee, infrastructureFee } =
|
||||
data.estimateOrder.fee;
|
||||
return {
|
||||
margin: addDecimal(margin, market.decimalPlaces),
|
||||
totalFees: addDecimal(fees, market.decimalPlaces),
|
||||
fees: {
|
||||
makerFee: addDecimal(makerFee, market.decimalPlaces),
|
||||
liquidityFee: addDecimal(liquidityFee, market.decimalPlaces),
|
||||
infrastructureFee: addDecimal(infrastructureFee, market.decimalPlaces),
|
||||
},
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
@ -1,11 +1,11 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||
|
||||
describe('useSettlementAccount Hook', () => {
|
||||
it('should filter accounts by settlementAssetId', () => {
|
||||
const accounts: PartyBalanceQuery_party_accounts[] = [
|
||||
const accounts: Account[] = [
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
@ -75,12 +75,12 @@ describe('useSettlementAccount Hook', () => {
|
||||
});
|
||||
|
||||
it('should return null if no accounts', () => {
|
||||
const accounts: PartyBalanceQuery_party_accounts[] = [];
|
||||
const accounts: Account[] = [];
|
||||
const settlementAssetId =
|
||||
'6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61';
|
||||
const { result } = renderHook(() =>
|
||||
useSettlementAccount(settlementAssetId, accounts)
|
||||
);
|
||||
expect(result.current).toBe(undefined);
|
||||
expect(result.current).toBe(null);
|
||||
});
|
||||
});
|
@ -1,12 +1,12 @@
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import { useMemo } from 'react';
|
||||
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||
|
||||
export const useSettlementAccount = (
|
||||
settlementAssetId: string,
|
||||
accounts: PartyBalanceQuery_party_accounts[],
|
||||
accounts: Account[],
|
||||
type?: AccountType
|
||||
): PartyBalanceQuery_party_accounts | null => {
|
||||
): Account | null => {
|
||||
const callback = () =>
|
||||
accounts.find((account) => {
|
||||
if (type) {
|
||||
@ -16,5 +16,5 @@ export const useSettlementAccount = (
|
||||
return account.asset.id === settlementAssetId;
|
||||
});
|
||||
const account = useMemo(callback, [accounts, settlementAssetId, type]);
|
||||
return account as PartyBalanceQuery_party_accounts;
|
||||
return account || null;
|
||||
};
|
@ -1 +1,2 @@
|
||||
export * from './components';
|
||||
export * from './hooks';
|
||||
|
@ -0,0 +1,109 @@
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { totalFeesPercentage } from '@vegaprotocol/market-list';
|
||||
import { t, formatNumberPercentage } from '@vegaprotocol/react-helpers';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const FeesCell = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors: Market['fees']['factors'];
|
||||
}) => (
|
||||
<Tooltip description={<FeesBreakdownPercentage feeFactors={feeFactors} />}>
|
||||
<span>{totalFeesPercentage(feeFactors) ?? '-'}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const FeesBreakdownPercentage = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors?: Market['fees']['factors'];
|
||||
}) => {
|
||||
if (!feeFactors) return null;
|
||||
return (
|
||||
<dl className="grid grid-cols-2 gap-x-2">
|
||||
<dt>{t('Infrastructure fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Liquidity fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Maker fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(new BigNumber(feeFactors.makerFee).times(100))}
|
||||
</dd>
|
||||
<dt>{t('Total fees')}</dt>
|
||||
<dd className="text-right">{totalFeesPercentage(feeFactors)}</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeesBreakdown = ({
|
||||
fees,
|
||||
feeFactors,
|
||||
quoteName,
|
||||
}: {
|
||||
fees?: {
|
||||
infrastructureFee: string;
|
||||
liquidityFee: string;
|
||||
makerFee: string;
|
||||
};
|
||||
feeFactors?: Market['fees']['factors'];
|
||||
quoteName?: string;
|
||||
}) => {
|
||||
if (!fees) return null;
|
||||
const totalFees = new BigNumber(fees.makerFee)
|
||||
.plus(fees.infrastructureFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.toString();
|
||||
return (
|
||||
<dl className="grid grid-cols-3 gap-x-3">
|
||||
<dt>{t('Infrastructure fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right">
|
||||
{fees.infrastructureFee} {quoteName || ''}
|
||||
</dd>
|
||||
<dt>{t('Liquidity fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right">
|
||||
{fees.liquidityFee} {quoteName || ''}
|
||||
</dd>
|
||||
<dt>{t('Maker fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.makerFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right">
|
||||
{fees.makerFee} {quoteName || ''}
|
||||
</dd>
|
||||
<dt>{t('Total fees')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right">{totalFeesPercentage(feeFactors)}</dd>
|
||||
)}
|
||||
<dd className="text-right">
|
||||
{totalFees} {quoteName || ''}
|
||||
</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
1
libs/market-info/src/components/fees-breakdown/index.ts
Normal file
1
libs/market-info/src/components/fees-breakdown/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './fees-breakdown';
|
@ -1,2 +1,3 @@
|
||||
export * from './market-expires';
|
||||
export * from './market-info';
|
||||
export * from './fees-breakdown';
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
import { AsyncRenderer, Splash, Accordion } from '@vegaprotocol/ui-toolkit';
|
||||
import pick from 'lodash/pick';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { totalFees } from '@vegaprotocol/market-list';
|
||||
import { totalFeesPercentage } from '@vegaprotocol/market-list';
|
||||
import {
|
||||
AccountType,
|
||||
Interval,
|
||||
@ -101,7 +101,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
||||
<MarketInfoTable
|
||||
data={{
|
||||
...market.fees.factors,
|
||||
totalFees: totalFees(market.fees.factors),
|
||||
totalFees: totalFeesPercentage(market.fees.factors),
|
||||
}}
|
||||
asPercentage={true}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Market } from '../markets-provider';
|
||||
import { filterAndSortMarkets, totalFees } from './market-utils';
|
||||
import { filterAndSortMarkets, totalFeesPercentage } from './market-utils';
|
||||
|
||||
const MARKET_A: Partial<Market> = {
|
||||
id: '1',
|
||||
@ -73,6 +73,6 @@ describe('totalFees', () => {
|
||||
{ i: createFee(0.01, 0.056782, 0.003), o: '6.9782%' },
|
||||
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
|
||||
])('adds fees correctly', ({ i, o }) => {
|
||||
expect(totalFees(i)).toEqual(o);
|
||||
expect(totalFeesPercentage(i)).toEqual(o);
|
||||
});
|
||||
});
|
||||
|
@ -5,15 +5,17 @@ import orderBy from 'lodash/orderBy';
|
||||
import type { Market, Candle } from '../';
|
||||
|
||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
||||
if (!fees) {
|
||||
return undefined;
|
||||
}
|
||||
return formatNumberPercentage(
|
||||
new BigNumber(fees.makerFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.plus(fees.infrastructureFee)
|
||||
.times(100)
|
||||
);
|
||||
return fees
|
||||
? new BigNumber(fees.makerFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.plus(fees.infrastructureFee)
|
||||
.times(100)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const totalFeesPercentage = (fees: Market['fees']['factors']) => {
|
||||
const total = fees && totalFees(fees);
|
||||
return total ? formatNumberPercentage(total) : undefined;
|
||||
};
|
||||
|
||||
export const filterAndSortMarkets = (markets: Market[]) => {
|
||||
|
@ -35,3 +35,4 @@ export * from './toggle';
|
||||
export * from './tooltip';
|
||||
export * from './vega-icons';
|
||||
export * from './vega-logo';
|
||||
export * from './traffic-light';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ReactNode } from 'react';
|
||||
import * as constants from '../constants';
|
||||
import {
|
||||
VEGA_WALLET_CONCEPTS_URL,
|
||||
VEGA_WALLET_RELEASE_URL,
|
||||
} from '../constants';
|
||||
|
||||
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
@ -25,13 +28,9 @@ export const ConnectDialogFooter = ({ children }: { children?: ReactNode }) => {
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
<Link href={constants.VEGA_WALLET_RELEASE_URL}>
|
||||
{t('Get a Vega Wallet')}
|
||||
</Link>
|
||||
<Link href={VEGA_WALLET_RELEASE_URL}>{t('Get a Vega Wallet')}</Link>
|
||||
{' | '}
|
||||
<Link href={constants.VEGA_WALLET_CONCEPTS_URL}>
|
||||
{t('Having trouble?')}
|
||||
</Link>
|
||||
<Link href={VEGA_WALLET_CONCEPTS_URL}>{t('Having trouble?')}</Link>
|
||||
</>
|
||||
)}
|
||||
</footer>
|
||||
|
Loading…
Reference in New Issue
Block a user