feat: liquidity provisions view (#1133)

* feat(#473): add positions metrics data provider

* feat(#473) add positions stats

* feat(#473) add positions stats

* feat(#473): add positions stats

* feat(#473): add positions stats

* feat(#473): position metrics, test and refactoring

* feat(#473): add unit tests to positions table

* feat(#473): fix spelling, order positions by updated at desc

* feat(#473): protect from division by 0

* feat(#473): fix trading positions e2e tests

* feat(#473): fix e2e data mocks

* feat(#473): post code review clean up

* feat(#993): dependencies handling in data provider

* feat(#993): fix e2e tests data mocks

* feat(#993): remove position metrics mocks, add market data market id

* feat: #994 add price monitoring bounds and candles update interface

* fix: move best bid price to diff section

* feat(#993): add missing mocks, fix combine function

* fix: add insurance pool and calc volume 24h

* feat: display some oracle min info, asset id, insurance pool, move open interest and 24hVol

* fix: add  market-info.cy.ts case

* fix: remove # from numbered price monitoring settings

* fix: add insurance pool test

* fix: format 24hvol

* feat(#993): set loading initially to true, add unit tests

* feat(#993): cleanup, add comments

* feat(#993): remove undefined from client type

* fix: remove indicativeVolume and oracleSpecBinding from market info

* feat(#993): cosmetic changes

* fix: add oracleSpecBinding back

* Update libs/deal-ticket/src/components/info-market.tsx

Co-authored-by: botond <105208209+notbot00@users.noreply.github.com>

* feat: add initial queries

* fix: memo yesterday's timestamp

* fix: add back info

* fix: update query

* fix: add view full oracle details link and update mappings

* fix: regen code, make link reactnode, fix index.ts

* feat: add liquidity lib, refactor info market

* fix: remove liquidity query from deal-ticket

* feat:(#993): pass informaton about update callback cause

* fix: small ui tweaks

* fix: display in grid

* feat: navigate to oracle by termination id

* feat: #491 add use liquidity provision merging

* fix: remove logs, add extra check on my liquidity provision

* fix: type number trivially inferred from a number literal, remove type annotation

* fix: cypress tests and formatting for market info

* Update libs/deal-ticket/src/components/market-info/info-market.tsx

* fix: use position decimal places for stake and market value proxy

* fix: #491 use size/position decimal places for obligation, supplied and commitment amount

* fix: add size component and use decimal places

* fix: update readme liquidity

* fix: #491 add correct asset decimal formatters

* Update libs/deal-ticket/src/components/market-info/tooltip-mapping.tsx

Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>

* fix: make link instead of button to open liquidity

* fix: #491 add liquidity page, link to trading mode tooltip, tabs hidden or choose active

* fix: remove LP dialog, use only link to page

* fix: add market id in LP view

* fix: follow trade grid design

* fix: add one line tabs , remove link styling, remove any, add value formatters

* fix: remove falsy check LP undefined

* fix: keep date formatter in LP table

* fix: add generic type market info, hooks in body function

* fix: revert number formatters

* fix: use one param only in network params query

* fix:  use network param in web3 lib

* fix: move lp container to trading app

* fix: remove resizable panel

* feat: add header component, remove isEstimate

* chore: remove unnecessary type cast

* fix: fix build with children map clone element

* chore: lint

* fix: move use network params to react helpers

* fix: add const for LP tabs

* fix: fix formatting on LP page

* fix: only show tilde for liquidity monitoring auction end date

* fix: market id being rendered twice in market info

* chore: fix lint

* fix: types for generate withdraw form query

* chore: fix intermittent failing withdrawal test

* Update libs/deal-ticket/src/components/market-info/info-market.tsx

* chore: add another wait for market

Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: botond <105208209+notbot00@users.noreply.github.com>
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
m.ray 2022-09-07 12:05:28 +01:00 committed by GitHub
parent fa6ddcfdf1
commit cae6162a7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 1612 additions and 447 deletions

View File

@ -1,27 +0,0 @@
import { useQuery } from '@apollo/client';
import { gql } from '@apollo/client';
import type { NetworkParams } from './__generated__/NetworkParams';
export const NETWORK_PARAMS_QUERY = gql`
query NetworkParams {
networkParameters {
key
value
}
}
`;
export function useNetworkParam(params: string[]) {
const { data, loading, error } = useQuery<NetworkParams, never>(
NETWORK_PARAMS_QUERY
);
const foundParams = data?.networkParameters
?.filter((p) => params.includes(p.key))
.sort((a, b) => params.indexOf(a.key) - params.indexOf(b.key));
return {
data: foundParams ? foundParams.map((f) => f.value) : null,
loading,
error,
};
}

View File

@ -1,8 +1,8 @@
import { useNetworkParams } from '@vegaprotocol/react-helpers';
import React from 'react';
import { NetworkParams } from '../../../config';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import { useNetworkParam } from '../../../hooks/use-network-param';
import { BigNumber } from '../../../lib/bignumber';
import { addDecimal } from '../../../lib/decimals';
import type {
@ -16,7 +16,7 @@ const useProposalNetworkParams = ({
}: {
proposal: Proposals_proposals;
}) => {
const { data, loading } = useNetworkParam([
const { data, loading } = useNetworkParams([
NetworkParams.GOV_UPDATE_MARKET_REQUIRED_MAJORITY,
NetworkParams.GOV_UPDATE_MARKET_REQUIRED_PARTICIPATION,
NetworkParams.GOV_NEW_MARKET_REQUIRED_MAJORITY,

View File

@ -1,7 +1,7 @@
import { useQuery } from '@apollo/client';
import { Button, Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { formatDistance } from 'date-fns';
// @ts-ignore No types availabel for duration-js
// @ts-ignore No types available for duration-js
import Duration from 'duration-js';
import gql from 'graphql-tag';
import React from 'react';
@ -15,10 +15,10 @@ import {
AppStateActionType,
useAppState,
} from '../../../contexts/app-state/app-state-context';
import { useNetworkParam } from '../../../hooks/use-network-param';
import type { Rewards } from './__generated__/Rewards';
import { RewardInfo } from './reward-info';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useNetworkParams } from '@vegaprotocol/react-helpers';
export const REWARDS_QUERY = gql`
query Rewards($partyId: ID!) {
@ -77,7 +77,7 @@ export const RewardsIndex = () => {
data: rewardAssetData,
loading: rewardAssetLoading,
error: rewardAssetError,
} = useNetworkParam([
} = useNetworkParams([
NetworkParams.REWARD_ASSET,
NetworkParams.REWARD_PAYOUT_DURATION,
]);

View File

@ -7,7 +7,6 @@ import { useNavigate } from 'react-router-dom';
import { TokenInput } from '../../components/token-input';
import { NetworkParams } from '../../config';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { useNetworkParam } from '../../hooks/use-network-param';
import { useSearchParams } from '../../hooks/use-search-params';
import { BigNumber } from '../../lib/bignumber';
import { addDecimal, removeDecimal } from '../../lib/decimals';
@ -31,6 +30,7 @@ import type {
UndelegateSubmissionBody,
} from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useNetworkParam } from '@vegaprotocol/react-helpers';
export const PARTY_DELEGATIONS_QUERY = gql`
query PartyDelegations($partyId: ID!) {
@ -103,9 +103,9 @@ export const StakingForm = ({
setAmount('');
}, [action, setAmount]);
const { data } = useNetworkParam([
NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT,
]);
const { data } = useNetworkParam(
NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT
);
const minTokensWithDecimals = React.useMemo(() => {
const minTokens = new BigNumber(data && data.length === 1 ? data[0] : '');

View File

@ -1,4 +1,8 @@
import { MarketState } from '@vegaprotocol/types';
import {
MarketState,
MarketStateMapping,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
const marketInfoBtn = 'Info';
@ -51,8 +55,12 @@ describe('market info is displayed', () => {
validateMarketDataRow(0, 'Name', 'ETHBTC Quarterly (30 Jun 2022)');
validateMarketDataRow(1, 'Decimal Places', '2');
validateMarketDataRow(2, 'Position Decimal Places', '0');
validateMarketDataRow(3, 'Trading Mode', 'Trading mode continuous');
validateMarketDataRow(4, 'State', 'STATE_ACTIVE');
validateMarketDataRow(
3,
'Trading Mode',
MarketTradingModeMapping.TRADING_MODE_CONTINUOUS
);
validateMarketDataRow(4, 'State', MarketStateMapping.STATE_ACTIVE);
validateMarketDataRow(5, 'Market ID', 'market-0');
});

View File

@ -10,6 +10,7 @@ describe('markets table', () => {
it('renders markets correctly', () => {
cy.visit('/');
cy.wait('@Market');
cy.wait('@MarketList');
cy.get('[data-testid^="market-link-"]')
.should('not.be.empty')

View File

@ -1,5 +1,6 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { generateAccounts } from '../support/mocks/generate-accounts';
import { generateNetworkParameters } from '../support/mocks/generate-network-parameters';
import { generateWithdrawFormQuery } from '../support/mocks/generate-withdraw-page-query';
import { generateWithdrawals } from '../support/mocks/generate-withdrawals';
@ -20,6 +21,7 @@ describe('withdraw', () => {
aliasQuery(req, 'Withdrawals', generateWithdrawals());
aliasQuery(req, 'NetworkParamsQuery', generateNetworkParameters());
aliasQuery(req, 'WithdrawFormQuery', generateWithdrawFormQuery());
aliasQuery(req, 'Accounts', generateAccounts());
});
cy.visit('/portfolio');

View File

@ -71,6 +71,18 @@ export const generateAccounts = (
decimals: 5,
},
},
{
__typename: 'Account',
type: AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
market: null,
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
decimals: 5,
},
},
],
},
};

View File

@ -86,7 +86,9 @@ export const generateMarketInfoQuery = (
id: '54b78c1b877e106842ae156332ccec740ad98d6bad43143ac6a029501dd7c6e0',
},
markPrice: '5749',
indicativeVolume: '0',
suppliedStake: '56767',
marketValueProxy: '677678',
targetStake: '56789',
bestBidVolume: '5',
bestOfferVolume: '1',
bestStaticBidVolume: '5',
@ -146,6 +148,7 @@ export const generateMarketInfoQuery = (
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
name: 'tBTC TEST',
decimals: 1,
},
oracleSpecForSettlementPrice: {
__typename: 'OracleSpec',

View File

@ -1,66 +1,69 @@
import { AccountType } from '@vegaprotocol/types';
import type {
WithdrawFormQuery,
WithdrawFormQuery_assetsConnection_edges,
WithdrawFormQuery_party_accounts,
WithdrawFormQuery_party_withdrawals,
} from '@vegaprotocol/withdraws';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const generateWithdrawFormQuery = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override?: PartialDeep<any>
) => {
override?: PartialDeep<WithdrawFormQuery>
): WithdrawFormQuery => {
const withdrawal: WithdrawFormQuery_party_withdrawals = {
id: 'withdrawal-0',
txHash: null,
__typename: 'Withdrawal',
};
const account: WithdrawFormQuery_party_accounts = {
type: AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
},
__typename: 'Account',
};
const assetEdge1: WithdrawFormQuery_assetsConnection_edges = {
node: {
id: 'asset-0',
symbol: 'AST0',
name: 'Asset 0',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
__typename: 'AssetEdge',
};
const assetEdge2: WithdrawFormQuery_assetsConnection_edges = {
node: {
id: 'asset-1',
symbol: 'AST1',
name: 'Asset 1',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
__typename: 'AssetEdge',
};
const defaultResult = {
party: {
id: 'party-0',
withdrawals: [
{
id: 'withdrawal-0',
txHash: null,
__typename: 'Withdrawal',
},
],
accounts: [
{
type: AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
},
__typename: 'Account',
},
],
withdrawals: [withdrawal],
accounts: [account],
__typename: 'Party',
},
assetsConnection: {
edges: [
{
node: {
id: 'asset-0',
symbol: 'AST0',
name: 'Asset 0',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
__typename: 'AssetEdge',
},
{
node: {
id: 'asset-1',
symbol: 'AST1',
name: 'Asset 1',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
__typename: 'AssetEdge',
},
],
__typename: 'AssetsConnection',
edges: [assetEdge1, assetEdge2],
},
};

View File

@ -0,0 +1,53 @@
import type { ReactElement, ReactNode } from 'react';
import { Children } from 'react';
import { cloneElement } from 'react';
interface TradeMarketHeaderProps {
title: ReactNode;
children: ReactElement[];
}
export const Header = ({ title, children }: TradeMarketHeaderProps) => {
return (
<header className="w-screen xl:px-4 pt-4 border-b border-neutral-300 dark:border-neutral-700">
<div className="xl:flex xl:gap-4 items-start">
<div className="px-4 mb-2 xl:mb-0 sm:text-lg md:text-xl lg:text-2xl">
{title}
</div>
<div
data-testid="market-summary"
className="flex flex-nowrap items-start xl:flex-1 w-full overflow-x-auto text-xs "
>
{Children.map(children, (child, index) => {
return cloneElement(child, {
id: `header-stat-${index}`,
});
})}
</div>
</div>
</header>
);
};
export const HeaderStat = ({
children,
heading,
id,
}: {
children: ReactNode;
heading: string;
id?: string;
}) => {
const itemClass =
'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-neutral-300 dark:border-neutral-700';
const itemHeading = 'text-neutral-400';
return (
<div className={itemClass}>
<div id={id} className={itemHeading}>
{heading}
</div>
<div aria-labelledby={id}>{children}</div>
</div>
);
};

View File

@ -0,0 +1 @@
export * from './header';

View File

@ -10,9 +10,8 @@ import type { Market_market } from '../../pages/markets/__generated__/Market';
type MarketDataGridProps = {
grid: {
label: string;
label: string | ReactNode;
value?: ReactNode;
isEstimate?: boolean;
}[];
};
@ -20,12 +19,11 @@ const MarketDataGrid = ({ grid }: MarketDataGridProps) => {
return (
<>
{grid.map(
({ label, value, isEstimate }, index) =>
({ label, value }, index) =>
value && (
<div key={index} className="grid grid-cols-2">
<span data-testid="tooltip-label">{label}</span>
<span data-testid="tooltip-value">
{isEstimate && <span className="ml-[-0.625em]">{'~'}</span>}
<span data-testid="tooltip-value" className="text-right">
{value}
</span>
</div>
@ -61,12 +59,14 @@ const compileGridData = (market: Market_market) => {
}
if (market.data?.auctionEnd) {
const endDate = getDateTimeFormat().format(
new Date(market.data.auctionEnd)
);
grid.push({
label: isLiquidityMonitoringAuction
? t('Est auction end')
: t('Auction end'),
value: getDateTimeFormat().format(new Date(market.data.auctionEnd)),
isEstimate: isLiquidityMonitoringAuction ? true : false,
value: isLiquidityMonitoringAuction ? `~${endDate}` : endDate,
});
}
@ -79,8 +79,11 @@ const compileGridData = (market: Market_market) => {
if (isLiquidityMonitoringAuction && market.data?.suppliedStake) {
grid.push({
label: t('Current liquidity'),
// @TODO: link this to liquidity view when https://github.com/vegaprotocol/frontend-monorepo/issues/491 is done
label: (
<Link href={`/liquidity/${market.id}`} target="_blank">
{t('Current liquidity')}
</Link>
),
value: formatStake(market.data.suppliedStake, market),
});
}
@ -88,22 +91,24 @@ const compileGridData = (market: Market_market) => {
if (market.data?.indicativePrice) {
grid.push({
label: t('Est uncrossing price'),
value: addDecimalsFormatNumber(
market.data.indicativePrice,
market.positionDecimalPlaces
),
isEstimate: true,
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativePrice,
market.positionDecimalPlaces
),
});
}
if (market.data?.indicativeVolume) {
grid.push({
label: t('Est uncrossing vol'),
value: addDecimalsFormatNumber(
market.data.indicativeVolume,
market.positionDecimalPlaces
),
isEstimate: true,
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativeVolume,
market.positionDecimalPlaces
),
});
}
@ -128,7 +133,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => {
case MarketTradingMode.TRADING_MODE_OPENING_AUCTION: {
return (
<>
<p className="mb-16">
<p className="mb-4">
<span>
{t(
'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.'
@ -150,7 +155,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => {
case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
return (
<>
<p data-testid="tooltip-market-info" className="mb-16">
<p data-testid="tooltip-market-info" className="mb-4">
<span>
{t(
'This market is in auction until it reaches sufficient liquidity.'
@ -170,7 +175,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => {
case AuctionTrigger.AUCTION_TRIGGER_PRICE: {
return (
<>
<p className="mb-16">
<p className="mb-4">
<span>
{t('This market is in auction due to high price volatility.')}
</span>{' '}

View File

@ -0,0 +1,103 @@
import { LiquidityTable, useLiquidityProvision } from '@vegaprotocol/liquidity';
import { t } from '@vegaprotocol/react-helpers';
import { LiquidityProvisionStatus } from '@vegaprotocol/types';
import { AsyncRenderer, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { AgGridReact } from 'ag-grid-react';
import { Header, HeaderStat } from '../../components/header';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useRef, useMemo } from 'react';
const LiquidityPage = ({ id }: { id?: string }) => {
const { query } = useRouter();
const { keypair } = useVegaWallet();
const gridRef = useRef<AgGridReact | null>(null);
const partyId = keypair?.pub;
// Default to first marketId query item if found
const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const {
data: { liquidityProviders, suppliedStake, targetStake, code, symbol },
loading,
error,
} = useLiquidityProvision({ marketId });
const myLpEdges = useMemo(
() => liquidityProviders.filter((e) => e.party === partyId),
[liquidityProviders, partyId]
);
const activeEdges = useMemo(
() =>
liquidityProviders.filter(
(e) => e.status === LiquidityProvisionStatus.STATUS_ACTIVE
),
[liquidityProviders]
);
const inactiveEdges = useMemo(
() =>
liquidityProviders.filter(
(e) => e.status !== LiquidityProvisionStatus.STATUS_ACTIVE
),
[liquidityProviders]
);
const enum LiquidityTabs {
Active = 'active',
Inactive = 'inactive',
MyLiquidityProvision = 'myLP',
}
const getActiveDefaultId = () => {
if (myLpEdges?.length > 0) return LiquidityTabs.MyLiquidityProvision;
if (activeEdges?.length) return LiquidityTabs.Active;
else if (inactiveEdges?.length > 0) return LiquidityTabs.Inactive;
return LiquidityTabs.Active;
};
return (
<AsyncRenderer loading={loading} error={error} data={liquidityProviders}>
<div className="h-full grid grid-rows-[min-content_1fr]">
<Header
title={
<Link href={`/markets/${marketId}`}>
{`${code} ${t('liquidity provision')}`}
</Link>
}
>
<HeaderStat heading={t('Target stake')}>
<div>{`${targetStake} ${symbol}`}</div>
</HeaderStat>
<HeaderStat heading={t('Supplied stake')}>
<div>{`${suppliedStake} ${symbol}`}</div>
</HeaderStat>
<HeaderStat heading={t('Market ID')}>
<div className="break-word">{marketId}</div>
</HeaderStat>
</Header>
<Tabs active={getActiveDefaultId()}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!partyId}
>
<LiquidityTable ref={gridRef} data={myLpEdges} />
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityTable ref={gridRef} data={activeEdges} />
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityTable ref={gridRef} data={inactiveEdges} />
</Tab>
</Tabs>
</div>
</AsyncRenderer>
);
};
LiquidityPage.getInitialProps = () => ({
page: 'liquidity',
});
export default LiquidityPage;

View File

@ -0,0 +1 @@
export * from './[marketId].page';

View File

@ -5,7 +5,7 @@ import { Interval } from '@vegaprotocol/types';
import { Splash } from '@vegaprotocol/ui-toolkit';
import debounce from 'lodash/debounce';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { PageQueryContainer } from '../../components/page-query-container';
import { useGlobalStore } from '../../stores';
import { TradeGrid, TradePanels } from './trade-grid';
@ -91,10 +91,19 @@ const MarketPage = ({ id }: { id?: string }) => {
// Cache timestamp for yesterday to prevent full unmount of market page when
// a rerender occurs
const [yTimestamp] = useState(() => {
const yTimestamp = useMemo(() => {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
return new Date(yesterday * 1000).toISOString();
});
}, []);
const variables = useMemo(
() => ({
marketId: marketId || '',
interval: Interval.INTERVAL_I1H,
since: yTimestamp,
}),
[marketId, yTimestamp]
);
if (!marketId) {
return (
@ -109,11 +118,7 @@ const MarketPage = ({ id }: { id?: string }) => {
query={MARKET_QUERY}
data-testid="market"
options={{
variables: {
marketId,
interval: Interval.INTERVAL_I1H,
since: yTimestamp,
},
variables,
fetchPolicy: 'network-only',
}}
render={({ market }) => {

View File

@ -1,48 +1,47 @@
import 'allotment/dist/style.css';
import {
DealTicketContainer,
MarketInfoContainer,
} from '@vegaprotocol/deal-ticket';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { SelectMarketPopover } from '@vegaprotocol/market-list';
import { OrderListContainer } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills';
import { PositionsContainer } from '@vegaprotocol/positions';
import {
addDecimalsFormatNumber,
getDateFormat,
t,
} from '@vegaprotocol/react-helpers';
import { TradesContainer } from '@vegaprotocol/trades';
import {
AuctionTrigger,
AuctionTriggerMapping,
MarketTradingMode,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { LayoutPriority } from 'allotment';
import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useState } from 'react';
import type { ReactNode } from 'react';
import type { Market_market } from './__generated__/Market';
import type { CandleClose } from '@vegaprotocol/types';
import { useGlobalStore } from '../../stores';
import { AccountsContainer } from '@vegaprotocol/accounts';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment';
import {
Tab,
Tabs,
ResizableGrid,
ResizableGridPanel,
ButtonLink,
Tooltip,
PriceCellChange,
Link,
Tooltip,
ResizableGrid,
ButtonLink,
ResizableGridPanel,
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
getDateFormat,
t,
} from '@vegaprotocol/react-helpers';
import { SelectMarketPopover } from '@vegaprotocol/market-list';
import { useGlobalStore } from '../../stores';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment';
import type { CandleClose } from '@vegaprotocol/types';
import {
AuctionTrigger,
AuctionTriggerMapping,
MarketTradingMode,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { TradingModeTooltip } from '../../components/trading-mode-tooltip';
const TradingViews = {

View File

@ -0,0 +1,2 @@
export * from './DealTicketQuery';
export * from './MarketNames';

View File

@ -48,7 +48,10 @@ export const DealTicketLimitAmount = ({
type="number"
step={priceStep}
data-testid="order-price"
{...register('price', { required: true, min: 0 })}
{...register('price', {
required: true,
min: 0,
})}
/>
</FormGroup>
</div>

View File

@ -0,0 +1,12 @@
export * from './__generated__';
export * from './deal-ticket-amount';
export * from './deal-ticket-container';
export * from './deal-ticket-limit-amount';
export * from './deal-ticket-manager';
export * from './deal-ticket-market-amount';
export * from './deal-ticket';
export * from './expiry-selector';
export * from './market-selector';
export * from './side-selector';
export * from './time-in-force-selector';
export * from './type-selector';

View File

@ -1,15 +1,2 @@
export * from './__generated__/DealTicketQuery';
export * from './__generated__/MarketInfoQuery';
export * from './__generated__/MarketNames';
export * from './deal-ticket-amount';
export * from './deal-ticket-container';
export * from './deal-ticket-limit-amount';
export * from './deal-ticket-manager';
export * from './deal-ticket-market-amount';
export * from './deal-ticket';
export * from './expiry-selector';
export * from './info-market';
export * from './side-selector';
export * from './time-in-force-selector';
export * from './type-selector';
export * from './market-selector';
export * from './market-info';

View File

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { Interval, MarketState, AccountType, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types";
import { Interval, MarketState, MarketTradingMode, AccountType, AuctionTrigger } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: MarketInfoQuery
@ -191,10 +191,6 @@ export interface MarketInfoQuery_market_data {
* the mark price (an unsigned integer)
*/
markPrice: string;
/**
* indicative volume if the auction ended now, 0 if not in auction mode
*/
indicativeVolume: string;
/**
* the aggregated volume being bid at the best bid price.
*/
@ -211,10 +207,6 @@ export interface MarketInfoQuery_market_data {
* the aggregated volume being offered at the best static offer price, excluding pegged orders.
*/
bestStaticOfferVolume: string;
/**
* the sum of the size of all positions greater than 0.
*/
openInterest: string;
/**
* the highest price level on an order book for buy orders.
*/
@ -227,6 +219,22 @@ export interface MarketInfoQuery_market_data {
* what triggered an auction (if an auction was started)
*/
trigger: AuctionTrigger;
/**
* the sum of the size of all positions greater than 0.
*/
openInterest: string;
/**
* the supplied stake for the market
*/
suppliedStake: string | null;
/**
* the amount of stake targeted for this market
*/
targetStake: string | null;
/**
* the market value proxy
*/
marketValueProxy: string;
/**
* a list of valid price ranges per associated trigger
*/
@ -287,6 +295,10 @@ export interface MarketInfoQuery_market_tradableInstrument_instrument_product_se
* 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 MarketInfoQuery_market_tradableInstrument_instrument_product_oracleSpecForSettlementPrice {
@ -454,14 +466,14 @@ export interface MarketInfoQuery_market {
/**
* decimalPlaces indicates 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 )
@ -479,6 +491,10 @@ export interface MarketInfoQuery_market {
* Current state of the market
*/
state: MarketState;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* The proposal that initiated this market
*/
@ -487,10 +503,6 @@ export interface MarketInfoQuery_market {
* Get account for a party or market
*/
accounts: MarketInfoQuery_market_accounts[] | null;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* Fees related data
*/

View File

@ -0,0 +1 @@
export * from './MarketInfoQuery';

View File

@ -0,0 +1,3 @@
export * from './__generated__';
export * from './info-market-query';
export * from './info-market';

View File

@ -0,0 +1,110 @@
import {
t,
addDecimalsFormatNumber,
formatNumberPercentage,
formatNumber,
} from '@vegaprotocol/react-helpers';
import {
KeyValueTableRow,
KeyValueTable,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import startCase from 'lodash/startCase';
import type { ReactNode } from 'react';
import { tooltipMapping } from './tooltip-mapping';
interface RowProps {
field: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
assetSymbol?: string;
}
const Row = ({
field,
value,
decimalPlaces,
asPercentage,
unformatted,
assetSymbol = '',
}: RowProps) => {
const isNumber = typeof value === 'number' || !isNaN(Number(value));
const isPrimitive = typeof value === 'string' || isNumber;
const className = 'text-black dark:text-white text-ui !px-0 !font-normal';
let formattedValue = value;
if (isNumber && !unformatted) {
if (decimalPlaces) {
formattedValue = `${addDecimalsFormatNumber(
value,
decimalPlaces
)} ${assetSymbol}`;
} else if (asPercentage && value) {
formattedValue = formatNumberPercentage(new BigNumber(value).times(100));
} else {
formattedValue = `${formatNumber(Number(value))} ${assetSymbol}`;
}
}
if (isPrimitive) {
return (
<KeyValueTableRow
key={field}
inline={isPrimitive}
noBorder={true}
dtClassName={className}
ddClassName={className}
>
<Tooltip description={tooltipMapping[field]} align="start">
<div tabIndex={-1}>{startCase(t(field))}</div>
</Tooltip>
<span style={{ wordBreak: 'break-word' }}>{formattedValue}</span>
</KeyValueTableRow>
);
}
return null;
};
export interface MarketInfoTableProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
omits?: string[];
link?: ReactNode;
assetSymbol?: string;
}
export const MarketInfoTable = ({
data,
decimalPlaces,
asPercentage,
unformatted,
omits = ['__typename'],
link,
assetSymbol,
}: MarketInfoTableProps) => {
return (
<>
<KeyValueTable>
{Object.entries(data)
.filter(([key]) => !omits.includes(key))
.map(([key, value]) => (
<Row
key={key}
field={key}
value={value}
decimalPlaces={decimalPlaces}
assetSymbol={assetSymbol}
asPercentage={asPercentage}
unformatted={unformatted || key.toLowerCase().includes('volume')}
/>
))}
</KeyValueTable>
{link}
</>
);
};

View File

@ -8,6 +8,7 @@ export const MARKET_INFO_QUERY = gql`
decimalPlaces
positionDecimalPlaces
state
tradingMode
proposal {
id
rationale {
@ -49,15 +50,18 @@ export const MARKET_INFO_QUERY = gql`
id
}
markPrice
indicativeVolume
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
openInterest
bestBidPrice
bestOfferPrice
trigger
openInterest
suppliedStake
openInterest
targetStake
marketValueProxy
priceMonitoringBounds {
minValidPrice
maxValidPrice
@ -94,6 +98,7 @@ export const MARKET_INFO_QUERY = gql`
id
symbol
name
decimals
}
oracleSpecForSettlementPrice {
id

View File

@ -1,31 +1,23 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import {
addDecimalsFormatNumber,
formatLabel,
formatNumber,
formatNumberPercentage,
t,
} from '@vegaprotocol/react-helpers';
import {
KeyValueTable,
KeyValueTableRow,
AsyncRenderer,
Splash,
Accordion,
Tooltip,
Link,
} from '@vegaprotocol/ui-toolkit';
import startCase from 'lodash/startCase';
import { formatNumber, t } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Splash, Accordion } from '@vegaprotocol/ui-toolkit';
import pick from 'lodash/pick';
import omit from 'lodash/omit';
import type { MarketInfoQuery, MarketInfoQuery_market } from './';
import BigNumber from 'bignumber.js';
import { useQuery } from '@apollo/client';
import { totalFees } from '@vegaprotocol/market-list';
import { AccountType, Interval } from '@vegaprotocol/types';
import {
AccountType,
Interval,
MarketStateMapping,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { MARKET_INFO_QUERY } from './info-market-query';
import type {
MarketInfoQuery,
MarketInfoQuery_market,
MarketInfoQuery_market_candles,
} from './__generated__/MarketInfoQuery';
import { MarketInfoTable } from './info-key-value-table';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { generatePath } from 'react-router-dom';
@ -42,11 +34,11 @@ export interface InfoProps {
export const calcCandleVolume = (
m: MarketInfoQuery_market
): string | undefined => {
return m?.candles
?.reduce((acc, c) => {
return m.candles
?.reduce((acc: BigNumber, c: MarketInfoQuery_market_candles | null) => {
return acc.plus(new BigNumber(c?.volume ?? 0));
}, new BigNumber(0))
.toString();
}, new BigNumber(m.candles?.[0]?.volume ?? 0))
?.toString();
};
export interface MarketInfoContainerProps {
@ -57,10 +49,16 @@ export const MarketInfoContainer = ({ marketId }: MarketInfoContainerProps) => {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
return new Date(yesterday * 1000).toISOString();
}, []);
const { data, loading, error } = useQuery(MARKET_INFO_QUERY, {
variables: { marketId, interval: Interval.INTERVAL_I1H, since: yTimestamp },
});
const variables = useMemo(
() => ({ marketId, since: yTimestamp, interval: Interval.INTERVAL_I1H }),
[marketId, yTimestamp]
);
const { data, loading, error } = useQuery<MarketInfoQuery>(
MARKET_INFO_QUERY,
{
variables,
}
);
return (
<AsyncRenderer<MarketInfoQuery> data={data} loading={loading} error={error}>
@ -79,6 +77,8 @@ export const Info = ({ market }: InfoProps) => {
const { VEGA_TOKEN_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg';
const dayVolume = calcCandleVolume(market);
const assetSymbol =
market.tradableInstrument.instrument.product?.settlementAsset.symbol;
const marketDataPanels = [
{
title: t('Current fees'),
@ -137,28 +137,33 @@ export const Info = ({ market }: InfoProps) => {
},
...(market.accounts || [])
.filter((a) => a.type === AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a, i) => ({
.map((a) => ({
title: t(`Insurance pool`),
content: (
<MarketInfoTable
data={{
balance: `${a.balance}
${market.tradableInstrument.instrument.product?.settlementAsset.symbol}`,
balance: a.balance,
}}
assetSymbol={assetSymbol}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
),
})),
];
const keyDetails = pick(
market,
'name',
'decimalPlaces',
'positionDecimalPlaces',
'tradingMode',
'state',
'id' as 'marketId'
);
const { VEGA_EXPLORER_URL } = useEnvironment();
const keyDetails = {
...pick(
market,
'name',
'decimalPlaces',
'positionDecimalPlaces',
'tradingMode'
),
state: MarketStateMapping[market.state],
};
const marketSpecPanels = [
{
title: t('Key details'),
@ -166,10 +171,10 @@ export const Info = ({ market }: InfoProps) => {
<MarketInfoTable
data={{
...keyDetails,
marketID: keyDetails.id,
id: undefined,
marketID: market.id,
tradingMode:
keyDetails.tradingMode && formatLabel(keyDetails.tradingMode),
keyDetails.tradingMode &&
MarketTradingModeMapping[keyDetails.tradingMode],
}}
/>
),
@ -251,6 +256,12 @@ export const Info = ({ market }: InfoProps) => {
<MarketInfoTable data={trigger} decimalPlaces={market.decimalPlaces} />
),
})),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<MarketInfoTable data={trigger} decimalPlaces={market.decimalPlaces} />
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
@ -263,6 +274,28 @@ export const Info = ({ market }: InfoProps) => {
/>
),
},
{
title: t('Liquidity'),
content: (
<MarketInfoTable
data={{
targetStake: market.data && market.data.targetStake,
suppliedStake: market.data && market.data?.suppliedStake,
marketValueProxy: market.data && market.data.marketValueProxy,
}}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
assetSymbol={assetSymbol}
link={
<ExternalLink href={`/liquidity/${market.id}`}>
{t('View liquidity provision table')}
</ExternalLink>
}
/>
),
},
{
title: t('Oracle'),
content: (
@ -276,6 +309,13 @@ export const Info = ({ market }: InfoProps) => {
market.tradableInstrument.instrument.product
.oracleSpecForTradingTermination.id,
}}
link={
<ExternalLink
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.oracleSpecForTradingTermination.id}`}
>
{t('View full oracle details')}
</ExternalLink>
}
/>
),
},
@ -323,172 +363,3 @@ export const Info = ({ market }: InfoProps) => {
</div>
);
};
const tooltipMapping: Record<string, ReactNode> = {
makerFee: t(
'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).'
),
liquidityFee: t(
'Liquidity portion of the fee is paid to market makers for providing liquidity, and is transferred to the market-maker fee pool for the market.'
),
infrastructureFee: t(
'Fees paid to validators as a reward for running the infrastructure of the network.'
),
markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
),
openInterest: t(
'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).'
),
indicativeVolume: t(
'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).'
),
bestBidVolume: t(
'The aggregated volume being bid at the best bid price on the market.'
),
bestOfferVolume: t(
'The aggregated volume being offered at the best offer price on the market.'
),
bestStaticBidVolume: t(
'The aggregated volume being bid at the best static bid price on the market.'
),
bestStaticOfferVolume: t(
'The aggregated volume being offered at the best static offer price on the market.'
),
decimalPlaces: t('The smallest price increment on the book.'),
positionDecimalPlaces: t(
'How big the smallest order / position on the market can be.'
),
tradingMode: t('The trading mode the market is currently running.'),
state: t('The current state of the market'),
base: t(
'The first currency in a pair for a currency-based derivatives market.'
),
quote: t(
'The second currency in a pair for a currency-based derivatives market.'
),
class: t(
'The classification of the product. Examples: shares, commodities, crypto, FX.'
),
sector: t(
'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.'
),
short: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
long: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
tau: (
<span>
{t('Projection horizon measured as a year fraction used in ')}
<Link
href="https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7"
target="__blank"
>
{t('Expected Shortfall')}
</Link>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
riskAversionParameter: (
<span>
{t('Probability level used in ')}
<Link
href="https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7"
target="__blank"
>
{t('Expected Shortfall')}
</Link>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
horizonSecs: t('Time horizon of the price projection in seconds.'),
probability: t(
'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.'
),
auctionExtensionSecs: t(
'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.'
),
triggeringRatio: t('The triggering ratio for entering liquidity auction.'),
timeWindow: t('The length of time over which open interest is measured.'),
scalingFactor: t(
'The scaling between the liquidity demand estimate, based on open interest and target stake.'
),
};
interface RowProps {
field: string;
value: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
}
const Row = ({
field,
value,
decimalPlaces,
asPercentage,
unformatted,
}: RowProps) => {
const isNumber = typeof value === 'number' || !isNaN(Number(value));
const isPrimitive = typeof value === 'string' || isNumber;
if (isPrimitive) {
return (
<KeyValueTableRow key={field} inline={isPrimitive} noBorder={true}>
<Tooltip description={tooltipMapping[field]} align="start">
<div tabIndex={-1}>{startCase(t(field))}</div>
</Tooltip>
<span style={{ wordBreak: 'break-word' }}>
{isNumber && !unformatted
? decimalPlaces
? addDecimalsFormatNumber(value, decimalPlaces)
: asPercentage
? formatNumberPercentage(new BigNumber(value * 100))
: formatNumber(Number(value))
: value}
</span>
</KeyValueTableRow>
);
}
return null;
};
export interface MarketInfoTableProps {
data: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
omits?: string[];
}
export const MarketInfoTable = ({
data,
decimalPlaces,
asPercentage,
unformatted,
omits = ['__typename'],
}: MarketInfoTableProps) => {
return (
<KeyValueTable>
{Object.entries(omit(data, ...omits) || []).map(([key, value]) => (
<Row
key={key}
field={key}
value={value}
decimalPlaces={decimalPlaces}
asPercentage={asPercentage}
unformatted={unformatted || key.toLowerCase().includes('volume')}
/>
))}
</KeyValueTable>
);
};

View File

@ -0,0 +1,103 @@
import { t } from '@vegaprotocol/react-helpers';
import { Link } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
export const tooltipMapping: Record<string, ReactNode> = {
makerFee: t(
'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).'
),
liquidityFee: t(
'Liquidity portion of the fee is paid to market makers for providing liquidity, and is transferred to the liquidity fee pool for the market.'
),
infrastructureFee: t(
'Fees paid to validators as a reward for running the infrastructure of the network.'
),
markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
),
openInterest: t(
'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).'
),
indicativeVolume: t(
'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).'
),
bestBidVolume: t(
'The aggregated volume being bid at the best bid price on the market.'
),
bestOfferVolume: t(
'The aggregated volume being offered at the best offer price on the market.'
),
bestStaticBidVolume: t(
'The aggregated volume being bid at the best static bid price on the market.'
),
bestStaticOfferVolume: t(
'The aggregated volume being offered at the best static offer price on the market.'
),
decimalPlaces: t('The smallest price increment on the book.'),
positionDecimalPlaces: t(
'How big the smallest order / position on the market can be.'
),
tradingMode: t('The trading mode the market is currently running.'),
state: t('The current state of the market'),
base: t(
'The first currency in a pair for a currency-based derivatives market.'
),
quote: t(
'The second currency in a pair for a currency-based derivatives market.'
),
class: t(
'The classification of the product. Examples: shares, commodities, crypto, FX.'
),
sector: t(
'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.'
),
short: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
long: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
tau: (
<span>
{t('Projection horizon measured as a year fraction used in ')}
<Link
href="https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7"
target="__blank"
>
{t('Expected Shortfall')}
</Link>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
riskAversionParameter: (
<span>
{t('Probability level used in ')}
<Link
href="https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7"
target="__blank"
>
{t('Expected Shortfall')}
</Link>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
horizonSecs: t('Time horizon of the price projection in seconds.'),
probability: t(
'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.'
),
auctionExtensionSecs: t(
'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.'
),
triggeringRatio: t('The triggering ratio for entering liquidity auction.'),
timeWindow: t('The length of time over which open interest is measured.'),
scalingFactor: t(
'The scaling between the liquidity demand estimate, based on open interest and target stake.'
),
};

4
libs/liquidity/.babelrc Normal file
View File

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/next/babel"],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

7
libs/liquidity/README.md Normal file
View File

@ -0,0 +1,7 @@
# Liquidity
This library contains liquidity provision data providers and view containers.
## Running unit tests
Run `nx test liquidity` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,15 @@
module.exports = {
displayName: 'positions',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/positions',
setupFilesAfterEnv: ['./src/setup-tests.ts'],
};

View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/liquidity",
"version": "0.0.1"
}

View File

@ -0,0 +1,43 @@
{
"root": "libs/liquidity",
"sourceRoot": "libs/liquidity/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/liquidity",
"tsConfig": "libs/liquidity/tsconfig.lib.json",
"project": "libs/liquidity/package.json",
"entryFile": "libs/liquidity/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/liquidity/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/liquidity/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/liquidity"],
"options": {
"jestConfig": "libs/liquidity/jest.config.js",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1 @@
export * from './lib';

View File

@ -0,0 +1,252 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType, LiquidityProvisionStatus } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: MarketLiquidity
// ====================================================
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges_node {
__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;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges {
__typename: "AccountEdge";
/**
* The account
*/
node: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges_node;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection {
__typename: "AccountsConnection";
/**
* List of accounts available for the connection
*/
edges: (MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges | null)[] | null;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* Collateral accounts relating to a party
*/
accountsConnection: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node {
__typename: "LiquidityProvision";
/**
* Unique identifier for the order (set by the system after consensus)
*/
id: string | null;
/**
* The Id of the party making this commitment
*/
party: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party;
/**
* When the liquidity provision was initially created (formatted RFC3339)
*/
createdAt: string;
/**
* RFC3339Nano time of when the liquidity provision was updated
*/
updatedAt: string | null;
/**
* Specified as a unit-less number that represents the amount of settlement asset of the market.
*/
commitmentAmount: string;
/**
* Nominated liquidity fee factor, which is an input to the calculation of maker fees on the market, as per setting fees and rewarding liquidity providers.
*/
fee: string;
/**
* The current status of this liquidity provision
*/
status: LiquidityProvisionStatus;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection_edges {
__typename: "LiquidityProvisionsEdge";
node: MarketLiquidity_market_liquidityProvisionsConnection_edges_node;
}
export interface MarketLiquidity_market_liquidityProvisionsConnection {
__typename: "LiquidityProvisionsConnection";
edges: (MarketLiquidity_market_liquidityProvisionsConnection_edges | null)[] | null;
}
export interface MarketLiquidity_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The ID of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: 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 MarketLiquidity_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The name of the asset (string)
*/
settlementAsset: MarketLiquidity_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface MarketLiquidity_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: MarketLiquidity_market_tradableInstrument_instrument_product;
}
export interface MarketLiquidity_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of, or reference to, a fully specified instrument.
*/
instrument: MarketLiquidity_market_tradableInstrument_instrument;
}
export interface MarketLiquidity_market_data_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface MarketLiquidity_market_data_liquidityProviderFeeShare_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface MarketLiquidity_market_data_liquidityProviderFeeShare {
__typename: "LiquidityProviderFeeShare";
/**
* The liquidity provider party ID
*/
party: MarketLiquidity_market_data_liquidityProviderFeeShare_party;
/**
* The share owned by this liquidity provider (float)
*/
equityLikeShare: string;
/**
* The average entry valuation of the liquidity provider for the market
*/
averageEntryValuation: string;
}
export interface MarketLiquidity_market_data {
__typename: "MarketData";
/**
* market ID of the associated mark price
*/
market: MarketLiquidity_market_data_market;
/**
* the supplied stake for the market
*/
suppliedStake: string | null;
/**
* the sum of the size of all positions greater than 0.
*/
openInterest: string;
/**
* the amount of stake targeted for this market
*/
targetStake: string | null;
/**
* the market value proxy
*/
marketValueProxy: string;
/**
* the equity like share of liquidity fee for each liquidity provider
*/
liquidityProviderFeeShare: MarketLiquidity_market_data_liquidityProviderFeeShare[] | null;
}
export interface MarketLiquidity_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* decimalPlaces indicates 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;
/**
* positionDecimalPlaces indicates the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
* This sets how big the smallest order / position on the market can be.
*/
positionDecimalPlaces: number;
/**
* The list of the liquidity provision commitments for this market
*/
liquidityProvisionsConnection: MarketLiquidity_market_liquidityProvisionsConnection;
/**
* An instance of, or reference to, a tradable instrument.
*/
tradableInstrument: MarketLiquidity_market_tradableInstrument;
/**
* marketData for the given market
*/
data: MarketLiquidity_market_data | null;
}
export interface MarketLiquidity {
/**
* An instrument that is trading on the Vega network
*/
market: MarketLiquidity_market | null;
}
export interface MarketLiquidityVariables {
marketId: string;
partyId?: string | null;
}

View File

@ -0,0 +1 @@
export * from './MarketLiquidity';

View File

@ -0,0 +1,3 @@
export * from './__generated__';
export * from './liquidity-data-provider';
export * from './liquidity-table';

View File

@ -0,0 +1,172 @@
import { gql, useQuery } from '@apollo/client';
import type { LiquidityProvisionStatus } from '@vegaprotocol/types';
import { AccountType } from '@vegaprotocol/types';
import { useNetworkParam } from '@vegaprotocol/react-helpers';
import BigNumber from 'bignumber.js';
import type {
MarketLiquidity,
MarketLiquidity_market_data_liquidityProviderFeeShare,
} from './__generated__';
const SISKA_NETWORK_PARAMETER = 'market.liquidity.stakeToCcySiskas';
const MARKET_LIQUIDITY_QUERY = gql`
query MarketLiquidity($marketId: ID!, $partyId: String) {
market(id: $marketId) {
id
decimalPlaces
positionDecimalPlaces
liquidityProvisionsConnection(party: $partyId) {
edges {
node {
id
party {
id
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
edges {
node {
type
balance
}
}
}
}
createdAt
updatedAt
commitmentAmount
fee
status
}
}
}
tradableInstrument {
instrument {
code
product {
... on Future {
settlementAsset {
id
symbol
decimals
}
}
}
}
}
data {
market {
id
}
suppliedStake
openInterest
targetStake
marketValueProxy
liquidityProviderFeeShare {
party {
id
}
equityLikeShare
averageEntryValuation
}
}
}
}
`;
export interface LiquidityProvision {
party: string;
commitmentAmount: string | undefined;
fee: string | undefined;
equityLikeShare: string;
averageEntryValuation: string;
obligation: string | null;
supplied: string | null;
status?: LiquidityProvisionStatus;
createdAt: string | undefined;
updatedAt: string | null | undefined;
}
export interface LiquidityData {
liquidityProviders: LiquidityProvision[];
suppliedStake?: string | null;
targetStake?: string | null;
code?: string;
symbol?: string;
decimalPlaces?: number;
positionDecimalPlaces?: number;
assetDecimalPlaces?: number;
}
export const useLiquidityProvision = ({
marketId,
partyId,
}: {
partyId?: string;
marketId?: string;
}) => {
const { data: stakeToCcySiskas } = useNetworkParam(SISKA_NETWORK_PARAMETER);
const stakeToCcySiska = stakeToCcySiskas && stakeToCcySiskas[0];
const { data, loading, error } = useQuery<MarketLiquidity>(
MARKET_LIQUIDITY_QUERY,
{
variables: { marketId },
}
);
const liquidityProviders = (
data?.market?.data?.liquidityProviderFeeShare || []
)
?.filter(
(p: MarketLiquidity_market_data_liquidityProviderFeeShare) =>
!partyId || p.party.id === partyId
) // if partyId is provided, filter out other parties
.map((provider: MarketLiquidity_market_data_liquidityProviderFeeShare) => {
const liquidityProvisionConnection =
data?.market?.liquidityProvisionsConnection.edges?.find(
(e) => e?.node.party.id === provider.party.id
);
const balance =
liquidityProvisionConnection?.node?.party.accountsConnection.edges?.reduce(
(acc, e) => {
return e?.node.type === AccountType.ACCOUNT_TYPE_BOND // just an extra check to make sure we only use bond accounts
? acc.plus(new BigNumber(e?.node.balance ?? 0))
: acc;
},
new BigNumber(0)
);
const obligation =
stakeToCcySiska &&
new BigNumber(stakeToCcySiska)
.times(liquidityProvisionConnection?.node?.commitmentAmount ?? 1)
.toString();
const supplied =
stakeToCcySiska &&
new BigNumber(stakeToCcySiska).times(balance ?? 1).toString();
return {
party: provider.party.id,
createdAt: liquidityProvisionConnection?.node?.createdAt,
updatedAt: liquidityProvisionConnection?.node?.updatedAt,
commitmentAmount: liquidityProvisionConnection?.node?.commitmentAmount,
fee: liquidityProvisionConnection?.node?.fee,
status: liquidityProvisionConnection?.node?.status,
equityLikeShare: provider.equityLikeShare,
averageEntryValuation: provider.averageEntryValuation,
obligation,
supplied,
};
});
const liquidityData: LiquidityData = {
liquidityProviders,
suppliedStake: data?.market?.data?.suppliedStake,
targetStake: data?.market?.data?.targetStake,
decimalPlaces: data?.market?.decimalPlaces,
positionDecimalPlaces: data?.market?.positionDecimalPlaces,
code: data?.market?.tradableInstrument.instrument.code,
assetDecimalPlaces:
data?.market?.tradableInstrument.instrument.product.settlementAsset
.decimals,
symbol:
data?.market?.tradableInstrument.instrument.product.settlementAsset
.symbol,
};
return { data: liquidityData, loading, error };
};

View File

@ -0,0 +1,53 @@
import LiquidityTable from './liquidity-table';
import { act, render, screen, waitFor } from '@testing-library/react';
import { LiquidityProvisionStatus } from '@vegaprotocol/types';
import type { LiquidityProvision } from './liquidity-data-provider';
const singleRow: LiquidityProvision = {
party: 'a3f762f0a6e998e1d0c6e73017a13ec8a22386c30f7f64a1bdca47330bc592dd',
createdAt: '2022-08-19T17:18:36.257028Z',
updatedAt: '2022-08-19T17:18:36.257028Z',
commitmentAmount: '56298653179',
fee: '0.001',
status: LiquidityProvisionStatus.STATUS_ACTIVE,
equityLikeShare: '0.5',
averageEntryValuation: '0.5',
supplied: '67895',
obligation: '56785',
};
const singleRowData = [singleRow];
describe('LiquidityTable', () => {
it('should render successfully', async () => {
await act(async () => {
const { baseElement } = render(<LiquidityTable data={[]} />);
expect(baseElement).toBeTruthy();
});
});
it('should render correct columns', async () => {
act(async () => {
render(<LiquidityTable data={singleRowData} />);
await waitFor(async () => {
const headers = await screen.getAllByRole('columnheader');
expect(headers).toHaveLength(9);
expect(
headers.map((h) =>
h.querySelector('[ref="eText"]')?.textContent?.trim()
)
).toEqual([
'Party',
'Average entry valuation',
'Updated',
'Created',
'Supplied (siskas)',
'Obligation (siskas)',
'Share',
'Fee',
'Status',
]);
});
});
});
});

View File

@ -0,0 +1,117 @@
import { forwardRef } from 'react';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
getDateTimeFormat,
t,
} from '@vegaprotocol/react-helpers';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import type { LiquidityProvision } from './liquidity-data-provider';
import type { ValueFormatterParams } from 'ag-grid-community';
import BigNumber from 'bignumber.js';
import type { LiquidityProvisionStatus } from '@vegaprotocol/types';
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
const assetDecimalsFormatter = ({ value, data }: ValueFormatterParams) => {
if (!value) return '-';
return addDecimalsFormatNumber(value, data.assetDecimalPlaces);
};
const percentageFormatter = ({ value }: ValueFormatterParams) => {
if (!value) return '-';
return formatNumberPercentage(new BigNumber(value).times(100), 4) || '-';
};
const dateValueFormatter = ({ value }: { value?: string | null }) => {
if (!value) {
return '-';
}
return getDateTimeFormat().format(new Date(value));
};
export interface LiquidityTableProps {
data: LiquidityProvision[];
}
export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
(props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate="No liquidity provisions"
getRowId={({ data }) => data.party}
rowHeight={34}
ref={ref}
defaultColDef={{
flex: 1,
resizable: true,
minWidth: 100,
}}
rowData={props.data}
{...props}
>
<AgGridColumn headerName={t('Party')} field="party" />
<AgGridColumn
headerName={t('Commitment')}
field="commitmentAmount"
type="rightAligned"
valueFormatter={assetDecimalsFormatter}
/>
<AgGridColumn
headerName={t('Share')}
field="equityLikeShare"
type="rightAligned"
valueFormatter={percentageFormatter}
/>
<AgGridColumn
headerName={t('Fee')}
field="fee"
type="rightAligned"
valueFormatter={percentageFormatter}
/>
<AgGridColumn
headerName={t('Average entry valuation')}
field="averageEntryValuation"
type="rightAligned"
valueFormatter={assetDecimalsFormatter}
/>
<AgGridColumn
headerName={t('Obligation (siskas)')}
field="obligation"
type="rightAligned"
valueFormatter={assetDecimalsFormatter}
/>
<AgGridColumn
headerName={t('Supplied (siskas)')}
field="supplied"
type="rightAligned"
valueFormatter={assetDecimalsFormatter}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
valueFormatter={({ value }: { value: LiquidityProvisionStatus }) => {
if (!value) return value;
return LiquidityProvisionStatusMapping[value];
}}
/>
<AgGridColumn
headerName={t('Created')}
field="createdAt"
type="rightAligned"
valueFormatter={dateValueFormatter}
/>
<AgGridColumn
headerName={t('Updated')}
field="updatedAt"
type="rightAligned"
valueFormatter={dateValueFormatter}
/>
</AgGrid>
);
}
);
export default LiquidityTable;

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/next/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts",
"../react-helpers/src/lib/grid-cells/summary-row.spec.ts"
]
}

View File

@ -100,8 +100,7 @@ export const SelectMarketPopover = ({
marketName: string;
onSelect: (id: string) => void;
}) => {
const triggerClasses =
'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-4 whitespace-nowrap';
const triggerClasses = 'flex items-center gap-4 whitespace-nowrap';
const { keypair } = useVegaWallet();
const [open, setOpen] = useState(false);
const { data, loading: marketsLoading } = useMarketList();

View File

@ -83,7 +83,7 @@ describe('OrderFeedback', () => {
'1.00'
);
expect(screen.getByText('Size').nextElementSibling).toHaveTextContent(
`+ 200`
`+200`
);
});
});

View File

@ -1,20 +1,15 @@
import { useEnvironment } from '@vegaprotocol/environment';
import type { OrderEvent_busEvents_event_Order } from '../../order-hooks/__generated__/OrderEvent';
import {
addDecimalsFormatNumber,
t,
positiveClassNames,
negativeClassNames,
} from '@vegaprotocol/react-helpers';
import { addDecimalsFormatNumber, Size, t } from '@vegaprotocol/react-helpers';
import {
OrderRejectionReasonMapping,
OrderStatus,
OrderStatusMapping,
OrderTimeInForceMapping,
OrderType,
Side,
} from '@vegaprotocol/types';
import type { VegaTxState } from '@vegaprotocol/wallet';
import { Link } from '@vegaprotocol/ui-toolkit';
export interface OrderFeedbackProps {
transaction: VegaTxState;
@ -51,20 +46,12 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
)}
<div>
<p className={labelClass}>{t(`Size`)}</p>
<p
className={
order.side === Side.SIDE_BUY
? positiveClassNames
: negativeClassNames
}
>
{`${
order.side === Side.SIDE_BUY ? '+' : '-'
} ${addDecimalsFormatNumber(
order.size,
order.market?.positionDecimalPlaces ?? 0
)}
`}
<p>
<Size
value={order.size}
side={order.side}
positionDecimalPlaces={order.market.positionDecimalPlaces}
/>
</p>
</div>
</div>
@ -72,8 +59,7 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
{transaction.txHash && (
<div>
<p className={labelClass}>{t('Transaction')}</p>
<a
className="underline"
<Link
style={{ wordBreak: 'break-word' }}
data-testid="tx-block-explorer"
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
@ -81,7 +67,7 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
rel="noreferrer"
>
{transaction.txHash}
</a>
</Link>
</div>
)}

View File

@ -2,8 +2,9 @@ import {
t,
addDecimalsFormatNumber,
toDecimal,
Size,
} from '@vegaprotocol/react-helpers';
import { OrderType, Side } from '@vegaprotocol/types';
import { OrderType } from '@vegaprotocol/types';
import {
FormGroup,
Input,
@ -64,16 +65,15 @@ export const OrderEditDialog = ({
</div>
)}
<div>
<p className={headerClassName}>{t(`Remaining size`)}</p>
<p
className={
order.side === Side.SIDE_BUY
? 'text-dark-green dark:text-vega-green'
: 'text-red dark:text-vega-red'
<p className={headerClassName}>{t(`Size`)}</p>
<p>
{
<Size
value={order.size}
side={order.side}
positionDecimalPlaces={order.market.positionDecimalPlaces}
/>
}
>
{order.side === Side.SIDE_BUY ? '+' : '-'}
{order.size}
</p>
</div>
</div>

View File

@ -1,6 +1,6 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"ignorePatterns": ["!**/*", "__generated__"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],

View File

@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: NetworkParams
// ====================================================
export interface NetworkParams_networkParameters {
__typename: "NetworkParameter";
/**
* The name of the network parameter
*/
key: string;
/**
* The value of the network parameter
*/
value: string;
}
export interface NetworkParams {
/**
* return the full list of network parameters
*/
networkParameters: NetworkParams_networkParameters[] | null;
}

View File

@ -0,0 +1 @@
export * from './NetworkParams';

View File

@ -1,7 +1,8 @@
export * from './use-apply-grid-transaction';
export * from './use-data-provider';
export * from './use-theme-switcher';
export * from './use-fetch';
export * from './use-resize';
export * from './use-network-params';
export * from './use-outside-click';
export * from './use-resize';
export * from './use-screen-dimensions';
export * from './use-theme-switcher';

View File

@ -0,0 +1,37 @@
import { gql, useQuery } from '@apollo/client';
import type { NetworkParams } from './__generated__/NetworkParams';
export const NETWORK_PARAMETERS_QUERY = gql`
query NetworkParams {
networkParameters {
key
value
}
}
`;
export const useNetworkParam = (param: string) => {
const { data, loading, error } = useQuery<NetworkParams, never>(
NETWORK_PARAMETERS_QUERY
);
const foundParams = data?.networkParameters?.filter((p) => param === p.key);
return {
data: foundParams ? foundParams.map((f) => f.value) : null,
loading,
error,
};
};
export const useNetworkParams = (params: string[]) => {
const { data, loading, error } = useQuery<NetworkParams, never>(
NETWORK_PARAMETERS_QUERY
);
const foundParams = data?.networkParameters
?.filter((p) => params.includes(p.key))
.sort((a, b) => params.indexOf(a.key) - params.indexOf(b.key));
return {
data: foundParams ? foundParams.map((f) => f.value) : null,
loading,
error,
};
};

View File

@ -1,5 +1,5 @@
export * from './date';
export * from './number';
export * from './truncate';
export * from './size';
export * from './truncate';
export * from './utils';

View File

@ -3,5 +3,6 @@ export * from './cumulative-vol-cell';
export * from './flash-cell';
export * from './price-cell';
export * from './price-flash-cell';
export * from './size';
export * from './summary-rows';
export * from './vol-cell';

View File

@ -0,0 +1,47 @@
import { Side } from '@vegaprotocol/types';
import type { ICellRendererParams } from 'ag-grid-community';
import classNames from 'classnames';
import { addDecimalsFormatNumber } from '../format';
import { negativeClassNames, positiveClassNames } from './cell-class-rules';
export const Size = ({
value,
side,
positionDecimalPlaces = 0,
}: {
value: string;
side: Side;
positionDecimalPlaces?: number;
}) => {
return (
<span
data-testid="size"
className={classNames('text-right', {
[positiveClassNames]: side === Side.SIDE_BUY,
[negativeClassNames]: side === Side.SIDE_SELL,
})}
>
{side === Side.SIDE_BUY ? '+' : side === Side.SIDE_SELL ? '-' : ''}
{addDecimalsFormatNumber(value, positionDecimalPlaces)}
</span>
);
};
export interface ISizeCellProps extends ICellRendererParams {
value: number | string;
}
export const SizeCell = ({ value, data }: ISizeCellProps) => {
if ((!value && value !== 0) || isNaN(Number(value))) {
return <span data-testid="size">-</span>;
}
return (
<Size
value={value.toString()}
side={data.side}
positionDecimalPlaces={data.positionDecimalPlaces}
/>
);
};
SizeCell.displayName = 'SizeCell';

View File

@ -0,0 +1,12 @@
export * from './context';
export * from './format';
export * from './grid';
export * from './storage';
export * from './validate';
export * from './assets';
export * from './determine-id';
export * from './generic-data-provider';
export * from './i18n';
export * from './pagination';
export * from './remove-0x';
export * from './time';

View File

@ -98,6 +98,18 @@ export enum Interval {
INTERVAL_I6H = "INTERVAL_I6H",
}
/**
* Status of a liquidity provision order
*/
export enum LiquidityProvisionStatus {
STATUS_ACTIVE = "STATUS_ACTIVE",
STATUS_CANCELLED = "STATUS_CANCELLED",
STATUS_PENDING = "STATUS_PENDING",
STATUS_REJECTED = "STATUS_REJECTED",
STATUS_STOPPED = "STATUS_STOPPED",
STATUS_UNDEPLOYED = "STATUS_UNDEPLOYED",
}
/**
* The current state of a market
*/

View File

@ -17,6 +17,18 @@ export enum AccountTypeMapping {
ACCOUNT_TYPE_SETTLEMENT = 'Settlement',
}
/**
* Status of a liquidity provision order
*/
export enum LiquidityProvisionStatusMapping {
STATUS_ACTIVE = 'Active',
STATUS_CANCELLED = 'Cancelled',
STATUS_PENDING = 'Pending',
STATUS_REJECTED = 'Rejected',
STATUS_STOPPED = 'Stopped',
STATUS_UNDEPLOYED = 'Undeployed',
}
export enum AuctionTriggerMapping {
AUCTION_TRIGGER_BATCH = 'batch',
AUCTION_TRIGGER_LIQUIDITY = 'liquidity',
@ -25,17 +37,6 @@ export enum AuctionTriggerMapping {
AUCTION_TRIGGER_UNSPECIFIED = 'unspecified',
}
/**
* Comparator describes the type of comparison.
*/
export enum ConditionOperatorMapping {
OPERATOR_EQUALS = 'EQUALS',
OPERATOR_GREATER_THAN = 'GREATER_THAN',
OPERATOR_GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL',
OPERATOR_LESS_THAN = 'LESS_THAN',
OPERATOR_LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL',
}
/**
* The status of a deposit
*/
@ -201,7 +202,7 @@ export enum ProposalRejectionReasonMapping {
PROPOSAL_ERROR_INVALID_SHAPE = 'Invalid shape',
PROPOSAL_ERROR_MAJORITY_THRESHOLD_NOT_REACHED = 'Majority threshold not reached',
PROPOSAL_ERROR_MARKET_MISSING_LIQUIDITY_COMMITMENT = 'Market missing liquidity commitment',
PROPOSAL_ERROR_MISSING_BUILTIN_ASSET_FIELD = 'Missing builtin asset field',
PROPOSAL_ERROR_MISSING_BUILTIN_ASSET_FIELD = 'Missing built-in asset field',
PROPOSAL_ERROR_MISSING_COMMITMENT_AMOUNT = 'Missing commitment amount',
PROPOSAL_ERROR_MISSING_ERC20_CONTRACT_ADDRESS = 'Missing ERC20 contract address',
PROPOSAL_ERROR_NETWORK_PARAMETER_INVALID_KEY = 'Network parameter invalid key',

View File

@ -5,11 +5,12 @@ import { Children, isValidElement, useState } from 'react';
interface TabsProps {
children: ReactElement<TabProps>[];
active?: string;
}
export const Tabs = ({ children }: TabsProps) => {
export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
const [activeTab, setActiveTab] = useState<string>(() => {
return children[0].props.id;
return activeDefaultId ?? children[0].props.id;
});
return (
@ -24,7 +25,7 @@ export const Tabs = ({ children }: TabsProps) => {
role="tablist"
>
{Children.map(children, (child) => {
if (!isValidElement(child)) return null;
if (!isValidElement(child) || child.props.hidden) return null;
const isActive = child.props.id === activeTab;
const triggerClass = classNames(
'relative px-4 py-2 border-r border-neutral-300 dark:border-neutral-700',
@ -51,7 +52,7 @@ export const Tabs = ({ children }: TabsProps) => {
</div>
<div className="h-full overflow-auto">
{Children.map(children, (child) => {
if (!isValidElement(child)) return null;
if (!isValidElement(child) || child.props.hidden) return null;
return (
<TabsPrimitive.Content
value={child.props.id}
@ -71,6 +72,7 @@ interface TabProps {
children: ReactNode;
id: string;
name: string;
hidden?: boolean;
}
export const Tab = ({ children, ...props }: TabProps) => {

View File

@ -10,3 +10,4 @@ export * from './lib/use-verify-withdrawal';
export * from './lib/use-withdrawals';
export * from './lib/__generated__/Withdrawals';
export * from './lib/__generated__/WithdrawalFields';
export * from './lib/__generated__/WithdrawFormQuery';

View File

@ -25,6 +25,7 @@
"@vegaprotocol/environment": ["libs/environment/src/index.ts"],
"@vegaprotocol/fills": ["libs/fills/src/index.ts"],
"@vegaprotocol/governance": ["libs/governance/src/index.ts"],
"@vegaprotocol/liquidity": ["libs/liquidity/src/index.ts"],
"@vegaprotocol/market-depth": ["libs/market-depth/src/index.ts"],
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
"@vegaprotocol/network-info": ["libs/network-info/src/index.ts"],

View File

@ -14,6 +14,7 @@
"explorer-e2e": "apps/explorer-e2e",
"fills": "libs/fills",
"governance": "libs/governance",
"liquidity": "libs/liquidity",
"market-depth": "libs/market-depth",
"market-list": "libs/market-list",
"network-info": "libs/network-info",