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, 'MarketTags', generateMarketTags());
|
||||||
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
||||||
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
||||||
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
|
aliasQuery(req, 'PartyBalance', generatePartyBalance());
|
||||||
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
||||||
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
||||||
aliasQuery(req, 'MarketNames', generateMarketNames());
|
aliasQuery(req, 'MarketNames', generateMarketNames());
|
||||||
|
@ -28,7 +28,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
|||||||
aliasQuery(req, 'MarketTags', generateMarketTags());
|
aliasQuery(req, 'MarketTags', generateMarketTags());
|
||||||
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
||||||
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
||||||
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
|
aliasQuery(req, 'PartyBalance', generatePartyBalance());
|
||||||
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
|
||||||
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
|
||||||
aliasQuery(req, 'MarketDepth', generateMarketDepth());
|
aliasQuery(req, 'MarketDepth', generateMarketDepth());
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
export const generateDealTicket = () => {
|
import type { DealTicketQuery } from '@vegaprotocol/deal-ticket';
|
||||||
return {
|
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: {
|
market: {
|
||||||
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
|
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
|
||||||
decimalPlaces: 5,
|
decimalPlaces: 5,
|
||||||
positionDecimalPlaces: 0,
|
positionDecimalPlaces: 0,
|
||||||
state: 'STATE_ACTIVE',
|
state: MarketState.STATE_ACTIVE,
|
||||||
tradingMode: 'Continuous',
|
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
|
id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d',
|
||||||
name: 'AAVEDAI Monthly (30 Jun 2022)',
|
name: 'AAVEDAI Monthly (30 Jun 2022)',
|
||||||
product: {
|
product: {
|
||||||
quoteName: 'DAI',
|
quoteName: 'DAI',
|
||||||
@ -15,6 +23,7 @@ export const generateDealTicket = () => {
|
|||||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||||
symbol: 'tDAI',
|
symbol: 'tDAI',
|
||||||
name: 'tDAI TEST',
|
name: 'tDAI TEST',
|
||||||
|
decimals: 5,
|
||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
__typename: 'Future',
|
__typename: 'Future',
|
||||||
@ -27,7 +36,17 @@ export const generateDealTicket = () => {
|
|||||||
lastTrade: { price: '9893006', __typename: 'Trade' },
|
lastTrade: { price: '9893006', __typename: 'Trade' },
|
||||||
__typename: 'MarketDepth',
|
__typename: 'MarketDepth',
|
||||||
},
|
},
|
||||||
|
fees: {
|
||||||
|
factors: {
|
||||||
|
makerFee: '0.0002',
|
||||||
|
infrastructureFee: '0.0005',
|
||||||
|
liquidityFee: '0.001',
|
||||||
|
__typename: 'FeeFactors',
|
||||||
|
},
|
||||||
|
__typename: 'Fees',
|
||||||
|
},
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
return merge(defaultResult, override);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
export const generatePartyBalance = () => {
|
import merge from 'lodash/merge';
|
||||||
return {
|
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: {
|
party: {
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
balance: '88474051',
|
balance: '88474051',
|
||||||
type: 'ACCOUNT_TYPE_GENERAL',
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
asset: {
|
asset: {
|
||||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||||
symbol: 'tDAI',
|
symbol: 'tDAI',
|
||||||
@ -16,7 +23,7 @@ export const generatePartyBalance = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
balance: '100000000',
|
balance: '100000000',
|
||||||
type: 'ACCOUNT_TYPE_GENERAL',
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
asset: {
|
asset: {
|
||||||
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
|
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
|
||||||
symbol: 'tEURO',
|
symbol: 'tEURO',
|
||||||
@ -28,7 +35,7 @@ export const generatePartyBalance = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
balance: '3412867',
|
balance: '3412867',
|
||||||
type: 'ACCOUNT_TYPE_GENERAL',
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
asset: {
|
asset: {
|
||||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||||
symbol: 'tDAI',
|
symbol: 'tDAI',
|
||||||
@ -40,7 +47,7 @@ export const generatePartyBalance = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
balance: '70007',
|
balance: '70007',
|
||||||
type: 'ACCOUNT_TYPE_GENERAL',
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
asset: {
|
asset: {
|
||||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||||
symbol: 'tDAI',
|
symbol: 'tDAI',
|
||||||
@ -54,4 +61,6 @@ export const generatePartyBalance = () => {
|
|||||||
__typename: 'Party',
|
__typename: 'Party',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return merge(defaultResult, override);
|
||||||
};
|
};
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"types": ["cypress", "node", "cypress-real-events", "cypress-grep"]
|
"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 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 { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
import type {
|
||||||
|
AccountFragment,
|
||||||
|
DealTicketMarketFragment,
|
||||||
|
} from '@vegaprotocol/deal-ticket';
|
||||||
|
import { useSettlementAccount } from '@vegaprotocol/deal-ticket';
|
||||||
|
|
||||||
interface DealTicketBalanceProps {
|
interface DealTicketBalanceProps {
|
||||||
settlementAsset: DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset'];
|
settlementAsset: DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||||
accounts: PartyBalanceQuery_party_accounts[];
|
accounts: AccountFragment[];
|
||||||
isWalletConnected: boolean;
|
isWalletConnected: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@ -26,7 +27,7 @@ export const DealTicketBalance = ({
|
|||||||
accounts,
|
accounts,
|
||||||
AccountType.ACCOUNT_TYPE_GENERAL
|
AccountType.ACCOUNT_TYPE_GENERAL
|
||||||
);
|
);
|
||||||
const formatedNumber =
|
const formattedNumber =
|
||||||
settlementAccount?.balance &&
|
settlementAccount?.balance &&
|
||||||
settlementAccount.asset.decimals &&
|
settlementAccount.asset.decimals &&
|
||||||
addDecimalsFormatNumber(
|
addDecimalsFormatNumber(
|
||||||
@ -37,7 +38,7 @@ export const DealTicketBalance = ({
|
|||||||
const balance = (
|
const balance = (
|
||||||
<p className="text-blue text-lg font-semibold">
|
<p className="text-blue text-lg font-semibold">
|
||||||
{settlementAccount
|
{settlementAccount
|
||||||
? t(`${formatedNumber}`)
|
? t(`${formattedNumber}`)
|
||||||
: `No ${settlementAssetSymbol} left to trade`}
|
: `No ${settlementAssetSymbol} left to trade`}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { gql, useQuery } from '@apollo/client';
|
|
||||||
import {
|
import {
|
||||||
DealTicketManager,
|
DealTicketManager,
|
||||||
DealTicketContainer as Container,
|
DealTicketContainer as Container,
|
||||||
|
usePartyBalanceQuery,
|
||||||
} from '@vegaprotocol/deal-ticket';
|
} from '@vegaprotocol/deal-ticket';
|
||||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
@ -11,41 +10,20 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
|||||||
import { DealTicketSteps } from './deal-ticket-steps';
|
import { DealTicketSteps } from './deal-ticket-steps';
|
||||||
import { DealTicketBalance } from './deal-ticket-balance';
|
import { DealTicketBalance } from './deal-ticket-balance';
|
||||||
import Baubles from './baubles-decor';
|
import Baubles from './baubles-decor';
|
||||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
|
||||||
import ConnectWallet from '../wallet-connector';
|
import ConnectWallet from '../wallet-connector';
|
||||||
|
|
||||||
const tempEmptyText = (
|
const tempEmptyText = (
|
||||||
<p>{t('Please select a market from the markets page')}</p>
|
<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 = () => {
|
export const DealTicketContainer = () => {
|
||||||
const { marketId } = useParams<{ marketId: string }>();
|
const { marketId } = useParams<{ marketId: string }>();
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
const { data: partyData, loading } = useQuery<PartyBalanceQuery>(
|
const { data: partyData, loading } = usePartyBalanceQuery({
|
||||||
PARTY_BALANCE_QUERY,
|
variables: { partyId: pubKey || '' },
|
||||||
{
|
skip: !pubKey,
|
||||||
variables: { partyId: pubKey },
|
});
|
||||||
skip: !pubKey,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const loader = <Loader />;
|
const loader = <Loader />;
|
||||||
|
|
||||||
@ -70,7 +48,7 @@ export const DealTicketContainer = () => {
|
|||||||
return (
|
return (
|
||||||
<DealTicketManager market={data.market}>
|
<DealTicketManager market={data.market}>
|
||||||
{loading ? loader : balance}
|
{loading ? loader : balance}
|
||||||
<DealTicketSteps market={data.market} partyData={partyData} />
|
<DealTicketSteps market={data.market} />
|
||||||
</DealTicketManager>
|
</DealTicketManager>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket';
|
||||||
import { DealTicketEstimates } from './deal-ticket-estimates';
|
|
||||||
import { DealTicketSizeInput } from './deal-ticket-size-input';
|
import { DealTicketSizeInput } from './deal-ticket-size-input';
|
||||||
|
|
||||||
interface DealTicketSizeProps {
|
interface DealTicketSizeProps {
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import * as constants from './constants';
|
import {
|
||||||
import { TrafficLight } from '../traffic-light';
|
Dialog,
|
||||||
import { Dialog, Icon, Intent, Tooltip } from '@vegaprotocol/ui-toolkit';
|
Icon,
|
||||||
|
Intent,
|
||||||
|
Tooltip,
|
||||||
|
TrafficLight,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { InputSetter } from '../../components/input-setter';
|
import { InputSetter } from '../../components/input-setter';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import { DataTitle, ValueTooltipRow } from './deal-ticket-estimates';
|
import {
|
||||||
|
DataTitle,
|
||||||
|
EST_SLIPPAGE,
|
||||||
|
ValueTooltipRow,
|
||||||
|
} from '@vegaprotocol/deal-ticket';
|
||||||
|
|
||||||
interface DealTicketSlippageProps {
|
interface DealTicketSlippageProps {
|
||||||
step?: number;
|
step?: number;
|
||||||
@ -40,12 +48,12 @@ export const DealTicketSlippage = ({
|
|||||||
const formLabel = (
|
const formLabel = (
|
||||||
<label className="flex items-center mb-1">
|
<label className="flex items-center mb-1">
|
||||||
<span className="mr-1">{t('Adjust slippage tolerance')}</span>
|
<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}>
|
<div className="cursor-help" tabIndex={-1}>
|
||||||
<Icon
|
<Icon
|
||||||
name={IconNames.ISSUE}
|
name={IconNames.ISSUE}
|
||||||
className="block rotate-180"
|
className="block rotate-180"
|
||||||
ariaLabel={constants.EST_SLIPPAGE}
|
ariaLabel={EST_SLIPPAGE}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -83,7 +91,7 @@ export const DealTicketSlippage = ({
|
|||||||
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
|
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-1">
|
<div className="mr-1">
|
||||||
<ValueTooltipRow description={constants.EST_SLIPPAGE}>
|
<ValueTooltipRow description={EST_SLIPPAGE}>
|
||||||
<TrafficLight value={value} q1={1} q2={5}>
|
<TrafficLight value={value} q1={1} q2={5}>
|
||||||
{value}%
|
{value}%
|
||||||
</TrafficLight>
|
</TrafficLight>
|
||||||
|
@ -3,6 +3,13 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { Stepper } from '../stepper';
|
import { Stepper } from '../stepper';
|
||||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||||
|
import {
|
||||||
|
useOrderCloseOut,
|
||||||
|
useOrderMargin,
|
||||||
|
usePartyBalanceQuery,
|
||||||
|
useMaximumPositionSize,
|
||||||
|
useCalculateSlippage,
|
||||||
|
} from '@vegaprotocol/deal-ticket';
|
||||||
import {
|
import {
|
||||||
getDefaultOrder,
|
getDefaultOrder,
|
||||||
useOrderValidation,
|
useOrderValidation,
|
||||||
@ -12,12 +19,13 @@ import { InputError } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import { MarketSelector } from '@vegaprotocol/deal-ticket';
|
import { MarketSelector } from '@vegaprotocol/deal-ticket';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet, VegaTxStatus } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
t,
|
t,
|
||||||
addDecimalsFormatNumber,
|
|
||||||
toDecimal,
|
toDecimal,
|
||||||
removeDecimal,
|
removeDecimal,
|
||||||
|
addDecimalsFormatNumber,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
useOrderSubmit,
|
useOrderSubmit,
|
||||||
@ -30,23 +38,14 @@ import { DealTicketSize } from './deal-ticket-size';
|
|||||||
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||||
import SideSelector, { SIDE_NAMES } from './side-selector';
|
import SideSelector, { SIDE_NAMES } from './side-selector';
|
||||||
import ReviewTrade from './review-trade';
|
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 { Side, OrderType } from '@vegaprotocol/types';
|
||||||
import { DealTicketSlippage } from './deal-ticket-slippage';
|
import { DealTicketSlippage } from './deal-ticket-slippage';
|
||||||
|
|
||||||
interface DealTicketMarketProps {
|
interface DealTicketMarketProps {
|
||||||
market: DealTicketMarketFragment;
|
market: DealTicketMarketFragment;
|
||||||
partyData?: PartyBalanceQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketSteps = ({
|
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||||
market,
|
|
||||||
partyData,
|
|
||||||
}: DealTicketMarketProps) => {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setMarket = useCallback(
|
const setMarket = useCallback(
|
||||||
(marketId: string) => {
|
(marketId: string) => {
|
||||||
@ -68,15 +67,11 @@ export const DealTicketSteps = ({
|
|||||||
|
|
||||||
const emptyString = ' - ';
|
const emptyString = ' - ';
|
||||||
const step = toDecimal(market.positionDecimalPlaces);
|
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 order = watch();
|
||||||
const { message: invalidText, isDisabled } = useOrderValidation({
|
const { message: invalidText, isDisabled } = useOrderValidation({
|
||||||
market,
|
market,
|
||||||
orderType,
|
orderType: order.type,
|
||||||
orderTimeInForce,
|
orderTimeInForce: order.timeInForce,
|
||||||
fieldErrors: errors,
|
fieldErrors: errors,
|
||||||
});
|
});
|
||||||
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
|
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
|
||||||
@ -87,9 +82,14 @@ export const DealTicketSteps = ({
|
|||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: partyBalance } = usePartyBalanceQuery({
|
||||||
|
variables: { partyId: pubKey || '' },
|
||||||
|
skip: !pubKey,
|
||||||
|
});
|
||||||
|
|
||||||
const maxTrade = useMaximumPositionSize({
|
const maxTrade = useMaximumPositionSize({
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
accounts: partyData?.party?.accounts || [],
|
accounts: partyBalance?.party?.accounts || [],
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
settlementAssetId:
|
settlementAssetId:
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||||
@ -97,7 +97,11 @@ export const DealTicketSteps = ({
|
|||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
|
|
||||||
const estCloseOut = useOrderCloseOut({ order, market, partyData });
|
const estCloseOut = useOrderCloseOut({
|
||||||
|
order,
|
||||||
|
market,
|
||||||
|
partyData: partyBalance,
|
||||||
|
});
|
||||||
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
||||||
const [slippageValue, setSlippageValue] = useState(
|
const [slippageValue, setSlippageValue] = useState(
|
||||||
slippage ? parseFloat(slippage) : 0
|
slippage ? parseFloat(slippage) : 0
|
||||||
@ -130,26 +134,26 @@ export const DealTicketSteps = ({
|
|||||||
|
|
||||||
const notionalSize = useMemo(() => {
|
const notionalSize = useMemo(() => {
|
||||||
if (price) {
|
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 addDecimalsFormatNumber(size, market.decimalPlaces);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [market.decimalPlaces, orderSize, price]);
|
}, [market.decimalPlaces, order.size, price]);
|
||||||
|
|
||||||
const fees = useMemo(() => {
|
const fees = useMemo(() => {
|
||||||
if (estMargin?.fees && notionalSize) {
|
if (estMargin?.totalFees && notionalSize) {
|
||||||
const percentage = new BigNumber(estMargin?.fees)
|
const percentage = new BigNumber(estMargin?.totalFees)
|
||||||
.dividedBy(notionalSize)
|
.dividedBy(notionalSize)
|
||||||
.multipliedBy(100)
|
.multipliedBy(100)
|
||||||
.decimalPlaces(2)
|
.decimalPlaces(2)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
return `${estMargin.fees} (${percentage}%)`;
|
return `${estMargin.totalFees} (${percentage}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [estMargin?.fees, notionalSize]);
|
}, [estMargin?.totalFees, notionalSize]);
|
||||||
|
|
||||||
const max = useMemo(() => {
|
const max = useMemo(() => {
|
||||||
return new BigNumber(maxTrade)
|
return new BigNumber(maxTrade)
|
||||||
@ -157,6 +161,10 @@ export const DealTicketSteps = ({
|
|||||||
.toNumber();
|
.toNumber();
|
||||||
}, [market.positionDecimalPlaces, maxTrade]);
|
}, [market.positionDecimalPlaces, maxTrade]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSlippageValue(slippage ? parseFloat(slippage) : 0);
|
||||||
|
}, [slippage]);
|
||||||
|
|
||||||
const onSizeChange = useCallback(
|
const onSizeChange = useCallback(
|
||||||
(value: number) => {
|
(value: number) => {
|
||||||
const newVal = new BigNumber(value)
|
const newVal = new BigNumber(value)
|
||||||
@ -185,7 +193,7 @@ export const DealTicketSteps = ({
|
|||||||
|
|
||||||
setValue('price', bestAskPrice);
|
setValue('price', bestAskPrice);
|
||||||
|
|
||||||
if (orderType === OrderType.TYPE_MARKET) {
|
if (order.type === OrderType.TYPE_MARKET) {
|
||||||
setValue('type', OrderType.TYPE_LIMIT);
|
setValue('type', OrderType.TYPE_LIMIT);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -199,7 +207,8 @@ export const DealTicketSteps = ({
|
|||||||
market.decimalPlaces,
|
market.decimalPlaces,
|
||||||
market?.depth?.lastTrade?.price,
|
market?.depth?.lastTrade?.price,
|
||||||
order.side,
|
order.side,
|
||||||
orderType,
|
order.type,
|
||||||
|
setSlippageValue,
|
||||||
setValue,
|
setValue,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -246,7 +255,7 @@ export const DealTicketSteps = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
value: SIDE_NAMES[orderSide] || '',
|
value: SIDE_NAMES[order.side] || '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('Choose Position Size'),
|
label: t('Choose Position Size'),
|
||||||
@ -258,7 +267,7 @@ export const DealTicketSteps = ({
|
|||||||
min={step}
|
min={step}
|
||||||
max={max}
|
max={max}
|
||||||
onSizeChange={onSizeChange}
|
onSizeChange={onSizeChange}
|
||||||
size={new BigNumber(orderSize).toNumber()}
|
size={new BigNumber(order.size).toNumber()}
|
||||||
name="size"
|
name="size"
|
||||||
price={formattedPrice || emptyString}
|
price={formattedPrice || emptyString}
|
||||||
positionDecimalPlaces={market.positionDecimalPlaces}
|
positionDecimalPlaces={market.positionDecimalPlaces}
|
||||||
@ -267,7 +276,7 @@ export const DealTicketSteps = ({
|
|||||||
.symbol
|
.symbol
|
||||||
}
|
}
|
||||||
notionalSize={notionalSize || emptyString}
|
notionalSize={notionalSize || emptyString}
|
||||||
estCloseOut={estCloseOut}
|
estCloseOut={estCloseOut || emptyString}
|
||||||
fees={fees || emptyString}
|
fees={fees || emptyString}
|
||||||
estMargin={estMargin?.margin || emptyString}
|
estMargin={estMargin?.margin || emptyString}
|
||||||
/>
|
/>
|
||||||
@ -277,9 +286,9 @@ export const DealTicketSteps = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'loading...'
|
t('Loading...')
|
||||||
),
|
),
|
||||||
value: orderSize,
|
value: order.size,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('Review Trade'),
|
label: t('Review Trade'),
|
||||||
@ -297,7 +306,7 @@ export const DealTicketSteps = ({
|
|||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
transactionStatus={transactionStatus}
|
transactionStatus={transactionStatus}
|
||||||
order={order}
|
order={order}
|
||||||
estCloseOut={estCloseOut}
|
estCloseOut={estCloseOut || emptyString}
|
||||||
estMargin={estMargin?.margin || emptyString}
|
estMargin={estMargin?.margin || emptyString}
|
||||||
price={formattedPrice || emptyString}
|
price={formattedPrice || emptyString}
|
||||||
quoteName={
|
quoteName={
|
||||||
|
@ -4,9 +4,9 @@ import {
|
|||||||
KeyValueTable,
|
KeyValueTable,
|
||||||
KeyValueTableRow,
|
KeyValueTableRow,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
||||||
|
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { SIDE_NAMES } from './side-selector';
|
import { SIDE_NAMES } from './side-selector';
|
||||||
import { gql, useQuery } from '@apollo/client';
|
import { gql, useQuery } from '@apollo/client';
|
||||||
@ -14,7 +14,6 @@ import type {
|
|||||||
MarketTags,
|
MarketTags,
|
||||||
MarketTagsVariables,
|
MarketTagsVariables,
|
||||||
} from './__generated__/MarketTags';
|
} from './__generated__/MarketTags';
|
||||||
import { DealTicketEstimates } from './deal-ticket-estimates';
|
|
||||||
import { Side } from '@vegaprotocol/types';
|
import { Side } from '@vegaprotocol/types';
|
||||||
import { MarketExpires } from '@vegaprotocol/market-info';
|
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 classNames from 'classnames';
|
||||||
import { useNavigate, useParams, Link } from 'react-router-dom';
|
import { useNavigate, useParams, Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
@ -14,11 +14,11 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
import useMarketFiltersData from '../../hooks/use-markets-filter';
|
|
||||||
import type { Market } from '@vegaprotocol/market-list';
|
import type { Market } from '@vegaprotocol/market-list';
|
||||||
import { HorizontalMenu } from '../horizontal-menu';
|
import { HorizontalMenu } from '../horizontal-menu';
|
||||||
import type { HorizontalMenuItem } from '../horizontal-menu';
|
import type { HorizontalMenuItem } from '../horizontal-menu';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
|
import { useMarketFilters } from '../../hooks/use-markets-filter';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Market[];
|
data: Market[];
|
||||||
@ -27,7 +27,7 @@ interface Props {
|
|||||||
const SimpleMarketToolbar = ({ data }: Props) => {
|
const SimpleMarketToolbar = ({ data }: Props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { products, assetsPerProduct } = useMarketFiltersData(data);
|
const { products, assetsPerProduct } = useMarketFilters(data);
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
|
|
||||||
const onStateChange = useCallback(
|
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 type { Market } from '@vegaprotocol/market-list';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const useMarketFilters = (data: Market[]) => {
|
export const useMarketFilters = (data: Market[]) => {
|
||||||
const [products, setProducts] = useState<string[]>([]);
|
const [products, setProducts] = useState<string[]>([]);
|
||||||
const [assetsPerProduct, setAssetsPerProduct] = useState<
|
const [assetsPerProduct, setAssetsPerProduct] = useState<
|
||||||
Record<string, string[]>
|
Record<string, string[]>
|
||||||
@ -36,5 +36,3 @@ const useMarketFilters = (data: Market[]) => {
|
|||||||
}, [data]);
|
}, [data]);
|
||||||
return { products, assetsPerProduct };
|
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', () => {
|
it('order connect vega wallet button should connect', () => {
|
||||||
cy.getByTestId(toggleLimit).click();
|
cy.getByTestId(toggleLimit).click();
|
||||||
cy.getByTestId(orderPriceField).type('101');
|
cy.getByTestId(orderPriceField).clear().type('101');
|
||||||
cy.getByTestId('order-connect-wallet').click();
|
cy.getByTestId('order-connect-wallet').click();
|
||||||
cy.getByTestId('dialog-content').should('be.visible');
|
cy.getByTestId('dialog-content').should('be.visible');
|
||||||
cy.getByTestId('connectors-list')
|
cy.getByTestId('connectors-list')
|
||||||
|
@ -14,6 +14,13 @@ export const generateDealTicketQuery = (
|
|||||||
positionDecimalPlaces: 0,
|
positionDecimalPlaces: 0,
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||||
|
fees: {
|
||||||
|
factors: {
|
||||||
|
makerFee: '0.0002',
|
||||||
|
infrastructureFee: '0.0005',
|
||||||
|
liquidityFee: '0.0005',
|
||||||
|
},
|
||||||
|
},
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
__typename: 'TradableInstrument',
|
__typename: 'TradableInstrument',
|
||||||
instrument: {
|
instrument: {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
formatNumberPercentage,
|
|
||||||
PriceCell,
|
PriceCell,
|
||||||
signedNumberCssClass,
|
signedNumberCssClass,
|
||||||
t,
|
t,
|
||||||
@ -13,19 +12,15 @@ import {
|
|||||||
MarketTradingModeMapping,
|
MarketTradingModeMapping,
|
||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import { PriceCellChange, Sparkline, Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { PriceCellChange, Sparkline, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import { calcCandleHigh, calcCandleLow } from '@vegaprotocol/market-list';
|
||||||
calcCandleHigh,
|
|
||||||
calcCandleLow,
|
|
||||||
totalFees,
|
|
||||||
} from '@vegaprotocol/market-list';
|
|
||||||
import type { CandleClose } from '@vegaprotocol/types';
|
import type { CandleClose } from '@vegaprotocol/types';
|
||||||
import type {
|
import type {
|
||||||
MarketWithData,
|
MarketWithData,
|
||||||
MarketWithCandles,
|
MarketWithCandles,
|
||||||
} from '@vegaprotocol/market-list';
|
} from '@vegaprotocol/market-list';
|
||||||
import isNil from 'lodash/isNil';
|
import isNil from 'lodash/isNil';
|
||||||
|
import { FeesCell } from '@vegaprotocol/market-info';
|
||||||
|
|
||||||
type Market = MarketWithData & MarketWithCandles;
|
type Market = MarketWithData & MarketWithCandles;
|
||||||
|
|
||||||
@ -502,43 +497,3 @@ export const columnsPositionMarkets = (
|
|||||||
];
|
];
|
||||||
return selectMarketColumns;
|
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 React from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
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 { IconNames } from '@blueprintjs/icons';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
import { TrafficLight } from '../traffic-light';
|
|
||||||
|
|
||||||
interface DealTicketEstimatesProps {
|
interface DealTicketEstimatesProps {
|
||||||
quoteName?: string;
|
quoteName?: string;
|
||||||
@ -17,45 +16,6 @@ interface DealTicketEstimatesProps {
|
|||||||
slippage?: string;
|
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 = ({
|
export const DealTicketEstimates = ({
|
||||||
price,
|
price,
|
||||||
quoteName,
|
quoteName,
|
||||||
@ -131,3 +91,42 @@ export const DealTicketEstimates = ({
|
|||||||
)}
|
)}
|
||||||
</dl>
|
</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 { marketTranslations } from './use-order-validation';
|
||||||
import { useOrderValidation } from './use-order-validation';
|
import { useOrderValidation } from './use-order-validation';
|
||||||
import { ERROR_SIZE_DECIMAL } from './validate-size';
|
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');
|
jest.mock('@vegaprotocol/wallet');
|
||||||
|
|
||||||
|
@ -16,6 +16,13 @@ fragment DealTicketMarket on Market {
|
|||||||
auctionEnd
|
auctionEnd
|
||||||
trigger
|
trigger
|
||||||
}
|
}
|
||||||
|
fees {
|
||||||
|
factors {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
|
}
|
||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
id
|
id
|
||||||
|
@ -3,14 +3,14 @@ import { Schema as Types } from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
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<{
|
export type DealTicketQueryVariables = Types.Exact<{
|
||||||
marketId: Types.Scalars['ID'];
|
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`
|
export const DealTicketMarketFragmentDoc = gql`
|
||||||
fragment DealTicketMarket on Market {
|
fragment DealTicketMarket on Market {
|
||||||
@ -31,6 +31,13 @@ export const DealTicketMarketFragmentDoc = gql`
|
|||||||
auctionEnd
|
auctionEnd
|
||||||
trigger
|
trigger
|
||||||
}
|
}
|
||||||
|
fees {
|
||||||
|
factors {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
|
}
|
||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
id
|
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 { 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 type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||||
import { validateSize } from '../deal-ticket-validation/validate-size';
|
import { validateSize } from '../deal-ticket-validation/validate-size';
|
||||||
import { isMarketInAuction } from '../deal-ticket-validation/use-order-validation';
|
import { isMarketInAuction } from '../deal-ticket-validation/use-order-validation';
|
||||||
@ -52,7 +52,7 @@ export const DealTicketMarketAmount = ({
|
|||||||
<div className="text-sm text-right">
|
<div className="text-sm text-right">
|
||||||
{price && quoteName ? (
|
{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 { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
|
||||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||||
import { DealTicket } from './deal-ticket';
|
import { DealTicket } from './deal-ticket';
|
||||||
import type { DealTicketMarketFragment } from './__generated___/DealTicket';
|
import type { DealTicketMarketFragment } from './__generated___/DealTicket';
|
||||||
import { Schema } from '@vegaprotocol/types';
|
import { Schema } from '@vegaprotocol/types';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
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 = {
|
const market: DealTicketMarketFragment = {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
@ -32,6 +36,13 @@ const market: DealTicketMarketFragment = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fees: {
|
||||||
|
factors: {
|
||||||
|
makerFee: '0.001',
|
||||||
|
infrastructureFee: '0.002',
|
||||||
|
liquidityFee: '0.003',
|
||||||
|
},
|
||||||
|
},
|
||||||
depth: {
|
depth: {
|
||||||
__typename: 'MarketDepth',
|
__typename: 'MarketDepth',
|
||||||
lastTrade: {
|
lastTrade: {
|
||||||
@ -43,22 +54,37 @@ const market: DealTicketMarketFragment = {
|
|||||||
const submit = jest.fn();
|
const submit = jest.fn();
|
||||||
const transactionStatus = 'default';
|
const transactionStatus = 'default';
|
||||||
|
|
||||||
|
const mockChainId = 'chain-id';
|
||||||
|
|
||||||
function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
|
function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
|
||||||
|
const chainIdMock: MockedResponse<ChainIdQuery> = {
|
||||||
|
request: {
|
||||||
|
query: ChainIdDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
statistics: {
|
||||||
|
chainId: mockChainId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
<MockedProvider mocks={[chainIdMock]}>
|
||||||
<VegaWalletContext.Provider value={{} as any}>
|
<VegaWalletContext.Provider value={{} as any}>
|
||||||
<DealTicket
|
<DealTicket
|
||||||
defaultOrder={order}
|
defaultOrder={order}
|
||||||
market={market}
|
market={market}
|
||||||
submit={submit}
|
submit={submit}
|
||||||
transactionStatus={transactionStatus}
|
transactionStatus={transactionStatus}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('DealTicket', () => {
|
describe('DealTicket', () => {
|
||||||
it('Displays ticket defaults', () => {
|
it('should display ticket defaults', () => {
|
||||||
render(generateJsx());
|
render(generateJsx());
|
||||||
|
|
||||||
// Assert defaults are used
|
// Assert defaults are used
|
||||||
@ -87,7 +113,7 @@ describe('DealTicket', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can edit deal ticket', async () => {
|
it('can edit deal ticket', async () => {
|
||||||
render(generateJsx());
|
render(generateJsx());
|
||||||
|
|
||||||
// BUY is selected by default
|
// 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());
|
render(generateJsx());
|
||||||
|
|
||||||
// Check only IOC and
|
// 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 { useForm, Controller } from 'react-hook-form';
|
||||||
import {
|
import { t, removeDecimal, addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
t,
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
removeDecimal,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||||
import { TypeSelector } from './type-selector';
|
import { TypeSelector } from './type-selector';
|
||||||
import { SideSelector } from './side-selector';
|
import { SideSelector } from './side-selector';
|
||||||
@ -20,6 +16,11 @@ import {
|
|||||||
isMarketInAuction,
|
isMarketInAuction,
|
||||||
useOrderValidation,
|
useOrderValidation,
|
||||||
} from '../deal-ticket-validation/use-order-validation';
|
} 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';
|
export type TransactionStatus = 'default' | 'pending';
|
||||||
|
|
||||||
@ -44,18 +45,17 @@ export const DealTicket = ({
|
|||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
watch,
|
||||||
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<OrderSubmissionBody['orderSubmission']>({
|
} = useForm<OrderSubmissionBody['orderSubmission']>({
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: getDefaultOrder(market),
|
defaultValues: getDefaultOrder(market),
|
||||||
});
|
});
|
||||||
|
const order = watch();
|
||||||
const orderType = watch('type');
|
|
||||||
const orderTimeInForce = watch('timeInForce');
|
|
||||||
const { message, isDisabled: disabled } = useOrderValidation({
|
const { message, isDisabled: disabled } = useOrderValidation({
|
||||||
market,
|
market,
|
||||||
orderType,
|
orderType: order.type,
|
||||||
orderTimeInForce,
|
orderTimeInForce: order.timeInForce,
|
||||||
fieldErrors: errors,
|
fieldErrors: errors,
|
||||||
});
|
});
|
||||||
const isDisabled = transactionStatus === 'pending' || disabled;
|
const isDisabled = transactionStatus === 'pending' || disabled;
|
||||||
@ -78,20 +78,31 @@ export const DealTicket = ({
|
|||||||
[isDisabled, submit, market.decimalPlaces, market.positionDecimalPlaces]
|
[isDisabled, submit, market.decimalPlaces, market.positionDecimalPlaces]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getPrice = () => {
|
const getEstimatedMarketPrice = () => {
|
||||||
if (isMarketInAuction(market)) {
|
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 (
|
if (
|
||||||
market.data?.indicativePrice &&
|
market.data?.indicativePrice &&
|
||||||
BigInt(market.data?.indicativePrice) !== BigInt(0)
|
BigInt(market.data?.indicativePrice) !== BigInt(0)
|
||||||
) {
|
) {
|
||||||
return market.data.indicativePrice;
|
return market.data.indicativePrice;
|
||||||
}
|
}
|
||||||
return '-';
|
return undefined;
|
||||||
}
|
}
|
||||||
return market.depth.lastTrade?.price;
|
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 (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
|
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
|
||||||
@ -110,14 +121,10 @@ export const DealTicket = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DealTicketAmount
|
<DealTicketAmount
|
||||||
orderType={orderType}
|
orderType={order.type}
|
||||||
market={market}
|
market={market}
|
||||||
register={register}
|
register={register}
|
||||||
price={
|
price={order.price}
|
||||||
price
|
|
||||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
@ -126,13 +133,13 @@ export const DealTicket = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TimeInForceSelector
|
<TimeInForceSelector
|
||||||
value={field.value}
|
value={field.value}
|
||||||
orderType={orderType}
|
orderType={order.type}
|
||||||
onSelect={field.onChange}
|
onSelect={field.onChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{orderType === OrderType.TYPE_LIMIT &&
|
{order.type === OrderType.TYPE_LIMIT &&
|
||||||
orderTimeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||||
<Controller
|
<Controller
|
||||||
name="expiresAt"
|
name="expiresAt"
|
||||||
control={control}
|
control={control}
|
||||||
@ -174,6 +181,7 @@ export const DealTicket = ({
|
|||||||
{t('Connect wallet')}
|
{t('Connect wallet')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<DealTicketFeeDetails details={details} />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
export * from './deal-ticket';
|
export * from './deal-ticket';
|
||||||
export * from './deal-ticket-validation';
|
export * from './deal-ticket-validation';
|
||||||
export * from './trading-mode-tooltip';
|
export * from './trading-mode-tooltip';
|
||||||
|
export * from './deal-ticket-estimates';
|
||||||
|
export * from './constants';
|
||||||
|
@ -26,5 +26,6 @@ query EstimateOrder(
|
|||||||
marginLevels {
|
marginLevels {
|
||||||
initialLevel
|
initialLevel
|
||||||
}
|
}
|
||||||
|
totalFeeAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,9 @@ query MarketMarkPrice($marketId: ID!) {
|
|||||||
decimalPlaces
|
decimalPlaces
|
||||||
data {
|
data {
|
||||||
markPrice
|
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`
|
export const EstimateOrderDocument = gql`
|
||||||
@ -38,6 +38,7 @@ export const EstimateOrderDocument = gql`
|
|||||||
marginLevels {
|
marginLevels {
|
||||||
initialLevel
|
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`
|
export const MarketMarkPriceDocument = gql`
|
||||||
@ -17,6 +17,9 @@ export const MarketMarkPriceDocument = gql`
|
|||||||
decimalPlaces
|
decimalPlaces
|
||||||
data {
|
data {
|
||||||
markPrice
|
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 { renderHook } from '@testing-library/react';
|
||||||
import { Side } from '@vegaprotocol/types';
|
import { Side } from '@vegaprotocol/types';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import useCalculateSlippage from './use-calculate-slippage';
|
import { useCalculateSlippage } from './use-calculate-slippage';
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
decimalPlaces: 0,
|
decimalPlaces: 0,
|
@ -16,7 +16,7 @@ interface Props {
|
|||||||
order: OrderSubmissionBody['orderSubmission'];
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCalculateSlippage = ({ marketId, order }: Props) => {
|
export const useCalculateSlippage = ({ marketId, order }: Props) => {
|
||||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||||
const { data } = useOrderBookData({
|
const { data } = useOrderBookData({
|
||||||
variables,
|
variables,
|
||||||
@ -69,5 +69,3 @@ const useCalculateSlippage = ({ marketId, order }: Props) => {
|
|||||||
}
|
}
|
||||||
return null;
|
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 { renderHook } from '@testing-library/react';
|
||||||
import useMarketPositions from './use-market-positions';
|
import { useMarketPositions } from './use-market-positions';
|
||||||
|
|
||||||
let mockNotEmptyData = {
|
let mockNotEmptyData = {
|
||||||
party: {
|
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 { renderHook } from '@testing-library/react';
|
||||||
import useMaximumPositionSize from './use-maximum-position-size';
|
|
||||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
|
||||||
import {
|
import {
|
||||||
AccountType,
|
AccountType,
|
||||||
OrderTimeInForce,
|
OrderTimeInForce,
|
||||||
@ -9,6 +7,9 @@ import {
|
|||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import type { PositionMargin } from './use-market-positions';
|
import type { PositionMargin } from './use-market-positions';
|
||||||
import { BigNumber } from 'bignumber.js';
|
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 = {
|
const defaultMockMarketPositions = {
|
||||||
openVolume: new BigNumber(1),
|
openVolume: new BigNumber(1),
|
||||||
@ -17,7 +18,7 @@ const defaultMockMarketPositions = {
|
|||||||
|
|
||||||
let mockMarketPositions: PositionMargin | null = defaultMockMarketPositions;
|
let mockMarketPositions: PositionMargin | null = defaultMockMarketPositions;
|
||||||
|
|
||||||
const mockAccount: PartyBalanceQuery_party_accounts = {
|
const mockAccount: Account = {
|
||||||
__typename: 'Account',
|
__typename: 'Account',
|
||||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
balance: '200000',
|
balance: '200000',
|
||||||
@ -30,11 +31,12 @@ const mockAccount: PartyBalanceQuery_party_accounts = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockOrder = {
|
const mockOrder: OrderSubmissionBody['orderSubmission'] = {
|
||||||
type: OrderType.TYPE_MARKET,
|
type: OrderType.TYPE_MARKET,
|
||||||
size: '1',
|
size: '1',
|
||||||
side: Side.SIDE_BUY,
|
side: Side.SIDE_BUY,
|
||||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_IOC,
|
timeInForce: OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||||
|
marketId: 'market-id',
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('./use-settlement-account', () => {
|
jest.mock('./use-settlement-account', () => {
|
||||||
@ -42,9 +44,18 @@ jest.mock('./use-settlement-account', () => {
|
|||||||
useSettlementAccount: jest.fn(() => mockAccount),
|
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', () => {
|
it('should return correct size when no open positions', () => {
|
||||||
mockMarketPositions = null;
|
mockMarketPositions = null;
|
||||||
const price = '50';
|
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 { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
|
||||||
import { useSettlementAccount } from './use-settlement-account';
|
import { useSettlementAccount } from './use-settlement-account';
|
||||||
import { AccountType, Side } from '@vegaprotocol/types';
|
import { AccountType, Side } from '@vegaprotocol/types';
|
||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
|
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
accounts: PartyBalanceQuery_party_accounts[];
|
accounts: Account[];
|
||||||
marketId: string;
|
marketId: string;
|
||||||
price?: string;
|
price?: string;
|
||||||
settlementAssetId: string;
|
settlementAssetId: string;
|
||||||
@ -17,7 +17,7 @@ interface Props {
|
|||||||
const getSize = (balance: string, price: string) =>
|
const getSize = (balance: string, price: string) =>
|
||||||
new BigNumber(balance).dividedBy(new BigNumber(price));
|
new BigNumber(balance).dividedBy(new BigNumber(price));
|
||||||
|
|
||||||
export default ({
|
export const useMaximumPositionSize = ({
|
||||||
marketId,
|
marketId,
|
||||||
accounts,
|
accounts,
|
||||||
partyId,
|
partyId,
|
@ -1,17 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import useOrderCloseOut from './use-order-closeout';
|
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
import type { PartyBalanceQuery } from './__generated__/PartyBalance';
|
||||||
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
import { useOrderCloseOut } from './use-order-closeout';
|
||||||
|
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/wallet', () => ({
|
jest.mock('@vegaprotocol/wallet', () => ({
|
||||||
...jest.requireActual('@vegaprotocol/wallet'),
|
...jest.requireActual('@vegaprotocol/wallet'),
|
||||||
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
|
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('useOrderCloseOut Hook', () => {
|
describe('useOrderCloseOut', () => {
|
||||||
const order = { size: '2', side: 'SIDE_BUY' };
|
const order = { size: '2', side: 'SIDE_BUY' };
|
||||||
const market = {
|
const market = {
|
||||||
decimalPlaces: 5,
|
decimalPlaces: 5,
|
||||||
@ -44,7 +44,7 @@ describe('useOrderCloseOut Hook', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('return proper buy value', () => {
|
it('should return proper null value', () => {
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useOrderCloseOut({
|
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(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useOrderCloseOut({
|
useOrderCloseOut({
|
||||||
@ -81,7 +81,7 @@ describe('useOrderCloseOut Hook', () => {
|
|||||||
expect(result.current).toEqual('1.00000');
|
expect(result.current).toEqual('1.00000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('return proper empty value', () => {
|
it('should return proper empty value', () => {
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useOrderCloseOut({
|
useOrderCloseOut({
|
@ -1,49 +1,14 @@
|
|||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
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 { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
|
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
|
||||||
import { gql, useQuery } from '@apollo/client';
|
import { useMarketPositions } from './use-market-positions';
|
||||||
import useMarketPositions from './use-market-positions';
|
import { useMarketDataMarkPrice } from './use-market-data-mark-price';
|
||||||
import useMarketData from './use-market-data';
|
import { usePartyMarketDataQuery } from './__generated__/PartyMarketData';
|
||||||
import type {
|
|
||||||
PartyMarketData,
|
|
||||||
PartyMarketDataVariables,
|
|
||||||
} from './__generated__/PartyMarketData';
|
|
||||||
import { Side } from '@vegaprotocol/types';
|
import { Side } from '@vegaprotocol/types';
|
||||||
|
import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated___/DealTicket';
|
||||||
const CLOSEOUT_PRICE_QUERY = gql`
|
import type { PartyBalanceQuery } from './__generated__/PartyBalance';
|
||||||
query PartyMarketData($partyId: ID!) {
|
import { useSettlementAccount } from './use-settlement-account';
|
||||||
party(id: $partyId) {
|
|
||||||
id
|
|
||||||
accounts {
|
|
||||||
type
|
|
||||||
balance
|
|
||||||
asset {
|
|
||||||
id
|
|
||||||
decimals
|
|
||||||
}
|
|
||||||
market {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
marginsConnection {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
market {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
initialLevel
|
|
||||||
maintenanceLevel
|
|
||||||
searchLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
order: OrderSubmissionBody['orderSubmission'];
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
@ -51,22 +16,23 @@ interface Props {
|
|||||||
partyData?: PartyBalanceQuery;
|
partyData?: PartyBalanceQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
export const useOrderCloseOut = ({
|
||||||
|
order,
|
||||||
|
market,
|
||||||
|
partyData,
|
||||||
|
}: Props): string | null => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const account = useSettlementAccount(
|
const account = useSettlementAccount(
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||||
partyData?.party?.accounts || []
|
partyData?.party?.accounts || []
|
||||||
);
|
);
|
||||||
const { data } = useQuery<PartyMarketData, PartyMarketDataVariables>(
|
const { data } = usePartyMarketDataQuery({
|
||||||
CLOSEOUT_PRICE_QUERY,
|
pollInterval: 5000,
|
||||||
{
|
variables: { partyId: pubKey || '' },
|
||||||
pollInterval: 5000,
|
skip: !pubKey,
|
||||||
variables: { partyId: pubKey || '' },
|
});
|
||||||
skip: !pubKey,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const markPriceData = useMarketData(market.id);
|
const markPriceData = useMarketDataMarkPrice(market.id);
|
||||||
const marketPositions = useMarketPositions({
|
const marketPositions = useMarketPositions({
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
@ -94,7 +60,7 @@ const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
|||||||
);
|
);
|
||||||
const volume = new BigNumber(
|
const volume = new BigNumber(
|
||||||
addDecimal(
|
addDecimal(
|
||||||
marketPositions?.openVolume.toNumber() || 0,
|
marketPositions?.openVolume.toString() || '0',
|
||||||
market.positionDecimalPlaces
|
market.positionDecimalPlaces
|
||||||
)
|
)
|
||||||
)[order.side === Side.SIDE_BUY ? 'plus' : 'minus'](order.size);
|
)[order.side === Side.SIDE_BUY ? 'plus' : 'minus'](order.size);
|
||||||
@ -112,7 +78,5 @@ const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
|||||||
if (closeOut.isPositive()) {
|
if (closeOut.isPositive()) {
|
||||||
return formatNumber(closeOut, market.decimalPlaces);
|
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 { useQuery } from '@apollo/client';
|
||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
|
|
||||||
import type { PositionMargin } from './use-market-positions';
|
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 = {
|
let mockEstimateData = {
|
||||||
estimateOrder: {
|
estimateOrder: {
|
||||||
@ -27,9 +27,18 @@ let mockMarketPositions: PositionMargin = {
|
|||||||
openVolume: new BigNumber(1),
|
openVolume: new BigNumber(1),
|
||||||
balance: new BigNumber(100000),
|
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 = {
|
const order = {
|
||||||
size: '2',
|
size: '2',
|
||||||
side: 'SIDE_BUY',
|
side: 'SIDE_BUY',
|
||||||
@ -50,7 +59,7 @@ describe('useOrderMargin Hook', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('margin should be properly calculated', () => {
|
it('should calculate margin correctly', () => {
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useOrderMargin({
|
useOrderMargin({
|
||||||
order: order as OrderSubmissionBody['orderSubmission'],
|
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(() =>
|
const { result } = renderHook(() =>
|
||||||
useOrderMargin({
|
useOrderMargin({
|
||||||
order: order as OrderSubmissionBody['orderSubmission'],
|
order: order as OrderSubmissionBody['orderSubmission'],
|
||||||
@ -76,10 +85,10 @@ describe('useOrderMargin Hook', () => {
|
|||||||
partyId,
|
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;
|
mockMarketPositions = null;
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useOrderMargin({
|
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 = {
|
mockEstimateData = {
|
||||||
estimateOrder: {
|
estimateOrder: {
|
||||||
fee: {
|
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 { renderHook } from '@testing-library/react';
|
||||||
import { useSettlementAccount } from './use-settlement-account';
|
import { useSettlementAccount } from './use-settlement-account';
|
||||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||||
|
|
||||||
describe('useSettlementAccount Hook', () => {
|
describe('useSettlementAccount Hook', () => {
|
||||||
it('should filter accounts by settlementAssetId', () => {
|
it('should filter accounts by settlementAssetId', () => {
|
||||||
const accounts: PartyBalanceQuery_party_accounts[] = [
|
const accounts: Account[] = [
|
||||||
{
|
{
|
||||||
__typename: 'Account',
|
__typename: 'Account',
|
||||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
@ -75,12 +75,12 @@ describe('useSettlementAccount Hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if no accounts', () => {
|
it('should return null if no accounts', () => {
|
||||||
const accounts: PartyBalanceQuery_party_accounts[] = [];
|
const accounts: Account[] = [];
|
||||||
const settlementAssetId =
|
const settlementAssetId =
|
||||||
'6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61';
|
'6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61';
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useSettlementAccount(settlementAssetId, accounts)
|
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 type { AccountType } from '@vegaprotocol/types';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import type { AccountFragment as Account } from './__generated__/PartyBalance';
|
||||||
|
|
||||||
export const useSettlementAccount = (
|
export const useSettlementAccount = (
|
||||||
settlementAssetId: string,
|
settlementAssetId: string,
|
||||||
accounts: PartyBalanceQuery_party_accounts[],
|
accounts: Account[],
|
||||||
type?: AccountType
|
type?: AccountType
|
||||||
): PartyBalanceQuery_party_accounts | null => {
|
): Account | null => {
|
||||||
const callback = () =>
|
const callback = () =>
|
||||||
accounts.find((account) => {
|
accounts.find((account) => {
|
||||||
if (type) {
|
if (type) {
|
||||||
@ -16,5 +16,5 @@ export const useSettlementAccount = (
|
|||||||
return account.asset.id === settlementAssetId;
|
return account.asset.id === settlementAssetId;
|
||||||
});
|
});
|
||||||
const account = useMemo(callback, [accounts, settlementAssetId, type]);
|
const account = useMemo(callback, [accounts, settlementAssetId, type]);
|
||||||
return account as PartyBalanceQuery_party_accounts;
|
return account || null;
|
||||||
};
|
};
|
@ -1 +1,2 @@
|
|||||||
export * from './components';
|
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-expires';
|
||||||
export * from './market-info';
|
export * from './market-info';
|
||||||
|
export * from './fees-breakdown';
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
import { AsyncRenderer, Splash, Accordion } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Splash, Accordion } from '@vegaprotocol/ui-toolkit';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { totalFees } from '@vegaprotocol/market-list';
|
import { totalFeesPercentage } from '@vegaprotocol/market-list';
|
||||||
import {
|
import {
|
||||||
AccountType,
|
AccountType,
|
||||||
Interval,
|
Interval,
|
||||||
@ -101,7 +101,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
<MarketInfoTable
|
<MarketInfoTable
|
||||||
data={{
|
data={{
|
||||||
...market.fees.factors,
|
...market.fees.factors,
|
||||||
totalFees: totalFees(market.fees.factors),
|
totalFees: totalFeesPercentage(market.fees.factors),
|
||||||
}}
|
}}
|
||||||
asPercentage={true}
|
asPercentage={true}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import type { Market } from '../markets-provider';
|
import type { Market } from '../markets-provider';
|
||||||
import { filterAndSortMarkets, totalFees } from './market-utils';
|
import { filterAndSortMarkets, totalFeesPercentage } from './market-utils';
|
||||||
|
|
||||||
const MARKET_A: Partial<Market> = {
|
const MARKET_A: Partial<Market> = {
|
||||||
id: '1',
|
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.003), o: '6.9782%' },
|
||||||
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
|
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
|
||||||
])('adds fees correctly', ({ i, o }) => {
|
])('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 '../';
|
import type { Market, Candle } from '../';
|
||||||
|
|
||||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
export const totalFees = (fees: Market['fees']['factors']) => {
|
||||||
if (!fees) {
|
return fees
|
||||||
return undefined;
|
? new BigNumber(fees.makerFee)
|
||||||
}
|
.plus(fees.liquidityFee)
|
||||||
return formatNumberPercentage(
|
.plus(fees.infrastructureFee)
|
||||||
new BigNumber(fees.makerFee)
|
.times(100)
|
||||||
.plus(fees.liquidityFee)
|
: undefined;
|
||||||
.plus(fees.infrastructureFee)
|
};
|
||||||
.times(100)
|
|
||||||
);
|
export const totalFeesPercentage = (fees: Market['fees']['factors']) => {
|
||||||
|
const total = fees && totalFees(fees);
|
||||||
|
return total ? formatNumberPercentage(total) : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterAndSortMarkets = (markets: Market[]) => {
|
export const filterAndSortMarkets = (markets: Market[]) => {
|
||||||
|
@ -35,3 +35,4 @@ export * from './toggle';
|
|||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
export * from './vega-icons';
|
export * from './vega-icons';
|
||||||
export * from './vega-logo';
|
export * from './vega-logo';
|
||||||
|
export * from './traffic-light';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
@ -1,7 +1,10 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { ReactNode } from 'react';
|
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 }) => {
|
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
@ -25,13 +28,9 @@ export const ConnectDialogFooter = ({ children }: { children?: ReactNode }) => {
|
|||||||
children
|
children
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Link href={constants.VEGA_WALLET_RELEASE_URL}>
|
<Link href={VEGA_WALLET_RELEASE_URL}>{t('Get a Vega Wallet')}</Link>
|
||||||
{t('Get a Vega Wallet')}
|
|
||||||
</Link>
|
|
||||||
{' | '}
|
{' | '}
|
||||||
<Link href={constants.VEGA_WALLET_CONCEPTS_URL}>
|
<Link href={VEGA_WALLET_CONCEPTS_URL}>{t('Having trouble?')}</Link>
|
||||||
{t('Having trouble?')}
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
|
Loading…
Reference in New Issue
Block a user