diff --git a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx index 3aba60ae6..b657ff34e 100644 --- a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx @@ -1,13 +1,12 @@ import type { ReactNode } from 'react'; import type { FieldErrors } from 'react-hook-form'; import { useMemo } from 'react'; -import { t, toDecimal } from '@vegaprotocol/react-helpers'; +import { DataGrid, t, toDecimal } from '@vegaprotocol/react-helpers'; import { useVegaWallet } from '@vegaprotocol/wallet'; import * as Schema from '@vegaprotocol/types'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { - MarketDataGrid, compileGridData, MarginWarning, isMarketInAuction, @@ -216,9 +215,7 @@ export const useOrderValidation = ({ {t('This market is in auction until it reaches')}{' '} - } + description={} > {t('sufficient liquidity')} @@ -240,9 +237,7 @@ export const useOrderValidation = ({ {t('This market is in auction due to')}{' '} - } + description={} > {t('high price volatility')} @@ -281,9 +276,7 @@ export const useOrderValidation = ({ {t('This market is in auction until it reaches')}{' '} - } + description={} > {t('sufficient liquidity')} @@ -307,9 +300,7 @@ export const useOrderValidation = ({ {t('This market is in auction due to')}{' '} - } + description={} > {t('high price volatility')} diff --git a/apps/trading/client-pages/market/trade-market-header.tsx b/apps/trading/client-pages/market/trade-market-header.tsx index c6c25d49e..11117dd88 100644 --- a/apps/trading/client-pages/market/trade-market-header.tsx +++ b/apps/trading/client-pages/market/trade-market-header.tsx @@ -17,6 +17,7 @@ import { Last24hPriceChange } from '../../components/last-24h-price-change'; import { Last24hVolume } from '../../components/last-24h-volume'; import { MarketState } from '../../components/market-state'; import { MarketTradingMode } from '../../components/market-trading-mode'; +import { MarketLiquiditySupplied } from '../../components/liquidity-supplied'; interface TradeMarketHeaderProps { market: SingleMarketFieldsFragment | null; @@ -96,6 +97,10 @@ export const TradeMarketHeader = ({ ) : null} + ); }; diff --git a/apps/trading/components/liquidity-supplied/index.ts b/apps/trading/components/liquidity-supplied/index.ts new file mode 100644 index 000000000..db2d8192d --- /dev/null +++ b/apps/trading/components/liquidity-supplied/index.ts @@ -0,0 +1 @@ +export * from './liquidity-supplied'; diff --git a/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx b/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx new file mode 100644 index 000000000..e5e6a3815 --- /dev/null +++ b/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx @@ -0,0 +1,130 @@ +import { useCallback, useMemo, useState } from 'react'; +import { + addDecimalsFormatNumber, + formatNumberPercentage, + NetworkParams, + t, + useDataProvider, + useNetworkParams, +} from '@vegaprotocol/react-helpers'; +import type { + MarketData, + MarketDataUpdateFieldsFragment, + SingleMarketFieldsFragment, +} from '@vegaprotocol/market-list'; +import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list'; +import { HeaderStat } from '../header'; +import { Link } from '@vegaprotocol/ui-toolkit'; +import BigNumber from 'bignumber.js'; +import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity'; +import { DataGrid } from '@vegaprotocol/react-helpers'; + +interface Props { + marketId?: string; + noUpdate?: boolean; + assetDecimals: number; +} + +export const MarketLiquiditySupplied = ({ + marketId, + assetDecimals, + noUpdate = false, +}: Props) => { + const [market, setMarket] = useState(); + const { params } = useNetworkParams([ + NetworkParams.market_liquidity_stakeToCcySiskas, + NetworkParams.market_liquidity_targetstake_triggering_ratio, + ]); + + const stakeToCcyVolume = Number(params.market_liquidity_stakeToCcySiskas); + const triggeringRatio = Number( + params.market_liquidity_targetstake_triggering_ratio + ); + + const variables = useMemo( + () => ({ + marketId: marketId, + }), + [marketId] + ); + + const { data } = useDataProvider({ + dataProvider: marketProvider, + variables, + skip: !marketId, + }); + + const update = useCallback( + ({ data: marketData }: { data: MarketData | null }) => { + if (!noUpdate && marketData) { + setMarket(marketData); + } + return true; + }, + [noUpdate] + ); + + useDataProvider({ + dataProvider: marketDataProvider, + update, + variables, + skip: noUpdate || !marketId || !data, + }); + + const supplied = market?.suppliedStake + ? addDecimalsFormatNumber( + new BigNumber(market?.suppliedStake) + .multipliedBy(stakeToCcyVolume || 1) + .toString(), + assetDecimals + ) + : '-'; + + const { percentage } = useCheckLiquidityStatus({ + suppliedStake: market?.suppliedStake || 0, + targetStake: market?.targetStake || 0, + triggeringRatio, + }); + + const compiledGrid = [ + { + label: t('Supplied stake'), + value: market?.suppliedStake + ? addDecimalsFormatNumber( + new BigNumber(market?.suppliedStake).toString(), + assetDecimals + ) + : '-', + }, + { + label: t('Target stake'), + value: market?.targetStake + ? addDecimalsFormatNumber( + new BigNumber(market?.targetStake).toString(), + assetDecimals + ) + : '-', + }, + ]; + + const description = ( +
+ {compiledGrid && } +
+ + {t('View liquidity provision table')} + +
+ ); + + return ( + + {/* */} + {supplied} ({formatNumberPercentage(percentage, 2)}) + + ); +}; diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx index dd3c660ee..f56fac56e 100644 --- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx @@ -6,10 +6,10 @@ import { Tooltip, } from '@vegaprotocol/ui-toolkit'; import * as Schema from '@vegaprotocol/types'; -import { t } from '@vegaprotocol/react-helpers'; +import { DataGrid, t } from '@vegaprotocol/react-helpers'; import { timeInForceLabel } from '@vegaprotocol/orders'; import type { MarketDealTicket } from '@vegaprotocol/market-list'; -import { compileGridData, MarketDataGrid } from '../trading-mode-tooltip'; +import { compileGridData } from '../trading-mode-tooltip'; import { MarketModeValidationType } from '../../constants'; interface TimeInForceSelectorProps { @@ -78,9 +78,7 @@ export const TimeInForceSelector = ({ return ( {t('This market is in auction until it reaches')}{' '} - } - > + }> {t('sufficient liquidity')} {'. '} @@ -95,9 +93,7 @@ export const TimeInForceSelector = ({ return ( {t('This market is in auction due to')}{' '} - } - > + }> {t('high price volatility')} {'. '} diff --git a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx index fe67e371e..973c2c5ff 100644 --- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx @@ -1,9 +1,9 @@ import { FormGroup, InputError, Tooltip } from '@vegaprotocol/ui-toolkit'; -import { t } from '@vegaprotocol/react-helpers'; +import { DataGrid, t } from '@vegaprotocol/react-helpers'; import * as Schema from '@vegaprotocol/types'; import { Toggle } from '@vegaprotocol/ui-toolkit'; import type { MarketDealTicket } from '@vegaprotocol/market-list'; -import { compileGridData, MarketDataGrid } from '../trading-mode-tooltip'; +import { compileGridData } from '../trading-mode-tooltip'; import { MarketModeValidationType } from '../../constants'; interface TypeSelectorProps { @@ -33,9 +33,7 @@ export const TypeSelector = ({ return ( {t('This market is in auction until it reaches')}{' '} - } - > + }> {t('sufficient liquidity')} {'. '} @@ -48,9 +46,7 @@ export const TypeSelector = ({ return ( {t('This market is in auction due to')}{' '} - } - > + }> {t('high price volatility')} {'. '} diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx index 573efa392..1ce35ceb9 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx @@ -1,3 +1,4 @@ +import type { DataGridProps } from '@vegaprotocol/react-helpers'; import { t, getDateTimeFormat, @@ -6,7 +7,6 @@ import { import * as Schema from '@vegaprotocol/types'; import { Link as UILink } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; -import type { MarketDataGridProps } from './market-data-grid'; import { Link } from 'react-router-dom'; import type { MarketDealTicket } from '@vegaprotocol/market-list'; @@ -14,7 +14,7 @@ export const compileGridData = ( market: MarketDealTicket, onSelect?: (id: string) => void ): { label: ReactNode; value?: ReactNode }[] => { - const grid: MarketDataGridProps['grid'] = []; + const grid: DataGridProps['grid'] = []; const isLiquidityMonitoringAuction = market.data.marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/index.ts b/libs/deal-ticket/src/components/trading-mode-tooltip/index.ts index e22a46214..b55039e70 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/index.ts +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/index.ts @@ -1,3 +1,2 @@ -export * from './market-data-grid'; export * from './trading-mode-tooltip'; export * from './compile-grid-data'; diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx index 0033eda00..d60b52e8a 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx @@ -1,11 +1,10 @@ import type { ReactNode } from 'react'; import classNames from 'classnames'; import { useEnvironment } from '@vegaprotocol/environment'; -import { t } from '@vegaprotocol/react-helpers'; +import { DataGrid, t } from '@vegaprotocol/react-helpers'; import * as Schema from '@vegaprotocol/types'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { createDocsLinks } from '@vegaprotocol/react-helpers'; -import { MarketDataGrid } from './market-data-grid'; type TradingModeTooltipProps = { tradingMode: Schema.MarketTradingMode | null; @@ -46,7 +45,7 @@ export const TradingModeTooltip = ({ )}

- {compiledGrid && } + {compiledGrid && } ); } @@ -72,7 +71,7 @@ export const TradingModeTooltip = ({ )}

- {compiledGrid && } + {compiledGrid && } ); } @@ -94,7 +93,7 @@ export const TradingModeTooltip = ({ )}

- {compiledGrid && } + {compiledGrid && } ); } diff --git a/libs/liquidity/src/lib/utils/liquidity-utils.spec.tsx b/libs/liquidity/src/lib/utils/liquidity-utils.spec.tsx index d2f936dfc..a8b1c7566 100644 --- a/libs/liquidity/src/lib/utils/liquidity-utils.spec.tsx +++ b/libs/liquidity/src/lib/utils/liquidity-utils.spec.tsx @@ -1,3 +1,6 @@ +import { renderHook } from '@testing-library/react'; +import { Intent } from '@vegaprotocol/ui-toolkit'; +import BigNumber from 'bignumber.js'; import { formatWithAsset, sumLiquidityCommitted, @@ -6,6 +9,7 @@ import { getCandle24hAgo, getChange, EMPTY_VALUE, + useCheckLiquidityStatus, } from './liquidity-utils'; const CANDLES_1 = [ @@ -118,3 +122,50 @@ describe('getChange', () => { expect(result).toEqual(EMPTY_VALUE); }); }); + +describe('useCheckLiquidityStatus', () => { + it('should return amber if liquidity is enough', () => { + const { result } = renderHook(() => + useCheckLiquidityStatus({ + suppliedStake: '60', + targetStake: '100', + triggeringRatio: '0.5', + }) + ); + + expect(result.current).toEqual({ + status: Intent.Warning, + percentage: new BigNumber('60'), + }); + }); + + it('should return red if liquidity is not enough', () => { + const { result } = renderHook(() => + useCheckLiquidityStatus({ + suppliedStake: '60', + targetStake: '100', + triggeringRatio: '1', + }) + ); + + expect(result.current).toEqual({ + status: Intent.Danger, + percentage: new BigNumber('60'), + }); + }); + + it('should return green if liquidity is enough', () => { + const { result } = renderHook(() => + useCheckLiquidityStatus({ + suppliedStake: '101', + targetStake: '100', + triggeringRatio: '1', + }) + ); + + expect(result.current).toEqual({ + status: Intent.Success, + percentage: new BigNumber('101'), + }); + }); +}); diff --git a/libs/liquidity/src/lib/utils/liquidity-utils.ts b/libs/liquidity/src/lib/utils/liquidity-utils.ts index bbf66b255..49b052fde 100644 --- a/libs/liquidity/src/lib/utils/liquidity-utils.ts +++ b/libs/liquidity/src/lib/utils/liquidity-utils.ts @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'; import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers'; import type { MarketNodeFragment } from './../__generated__/MarketsLiquidity'; +import { Intent } from '@vegaprotocol/ui-toolkit'; export type LiquidityProvisionMarket = MarketNodeFragment; @@ -117,3 +118,46 @@ export const getTargetStake = ( ) => { return markets.find((m) => m.id === marketId)?.data?.targetStake || '0'; }; + +export const useCheckLiquidityStatus = ({ + suppliedStake, + targetStake, + triggeringRatio, +}: { + suppliedStake: string | number; + targetStake: string | number; + triggeringRatio: string | number; +}): { + status: Intent; + percentage: BigNumber; +} => { + // percentage supplied + const percentage = new BigNumber(suppliedStake) + .dividedBy(targetStake) + .multipliedBy(100); + // IF supplied_stake >= target_stake THEN + if (new BigNumber(suppliedStake).gte(new BigNumber(targetStake))) { + // show a green status, e.g. "🟢 $13,666,999 liquidity supplied" + return { + status: Intent.Success, + percentage, + }; + // ELSE IF supplied_stake > NETPARAM[market.liquidity.targetstake.triggering.ratio] * target_stake THEN + } else if ( + new BigNumber(suppliedStake).gte( + new BigNumber(targetStake).multipliedBy(triggeringRatio) + ) + ) { + // show an amber status, e.g. "🟠 $3,456,123 liquidity supplied" + return { + status: Intent.Warning, + percentage, + }; + // ELSE show a red status, e.g. "🔴 $600,002 liquidity supplied" + } else { + return { + status: Intent.Danger, + percentage, + }; + } +}; diff --git a/libs/market-list/src/lib/__generated__/market-data.ts b/libs/market-list/src/lib/__generated__/market-data.ts index 73a645272..5139ded13 100644 --- a/libs/market-list/src/lib/__generated__/market-data.ts +++ b/libs/market-list/src/lib/__generated__/market-data.ts @@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, bestBidPrice: string, bestOfferPrice: string, markPrice: string, trigger: Types.AuctionTrigger, staticMidPrice: string, marketTradingMode: Types.MarketTradingMode, marketState: Types.MarketState, indicativeVolume: string, indicativePrice: string, bestStaticBidPrice: string, bestStaticOfferPrice: string }; +export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, bestBidPrice: string, bestOfferPrice: string, markPrice: string, trigger: Types.AuctionTrigger, staticMidPrice: string, marketTradingMode: Types.MarketTradingMode, marketState: Types.MarketState, indicativeVolume: string, indicativePrice: string, bestStaticBidPrice: string, bestStaticOfferPrice: string, targetStake?: string | null, suppliedStake?: string | null }; export type MarketDataUpdateSubscriptionVariables = Types.Exact<{ marketId: Types.Scalars['ID']; }>; -export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, bestBidPrice: string, bestOfferPrice: string, markPrice: string, trigger: Types.AuctionTrigger, staticMidPrice: string, marketTradingMode: Types.MarketTradingMode, marketState: Types.MarketState, indicativeVolume: string, indicativePrice: string, bestStaticBidPrice: string, bestStaticOfferPrice: string }> }; +export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, bestBidPrice: string, bestOfferPrice: string, markPrice: string, trigger: Types.AuctionTrigger, staticMidPrice: string, marketTradingMode: Types.MarketTradingMode, marketState: Types.MarketState, indicativeVolume: string, indicativePrice: string, bestStaticBidPrice: string, bestStaticOfferPrice: string, targetStake?: string | null, suppliedStake?: string | null }> }; export type MarketDataFieldsFragment = { __typename?: 'MarketData', bestBidPrice: string, bestOfferPrice: string, markPrice: string, trigger: Types.AuctionTrigger, staticMidPrice: string, marketTradingMode: Types.MarketTradingMode, marketState: Types.MarketState, indicativeVolume: string, indicativePrice: string, bestStaticBidPrice: string, bestStaticOfferPrice: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, market: { __typename?: 'Market', id: string } }; @@ -35,6 +35,8 @@ export const MarketDataUpdateFieldsFragmentDoc = gql` indicativePrice bestStaticBidPrice bestStaticOfferPrice + targetStake + suppliedStake } `; export const MarketDataFieldsFragmentDoc = gql` diff --git a/libs/market-list/src/lib/market-data.graphql b/libs/market-list/src/lib/market-data.graphql index e01819086..9f86ae4bb 100644 --- a/libs/market-list/src/lib/market-data.graphql +++ b/libs/market-list/src/lib/market-data.graphql @@ -11,6 +11,8 @@ fragment MarketDataUpdateFields on ObservableMarketData { indicativePrice bestStaticBidPrice bestStaticOfferPrice + targetStake + suppliedStake } subscription MarketDataUpdate($marketId: ID!) { diff --git a/libs/react-helpers/src/hooks/use-network-params.ts b/libs/react-helpers/src/hooks/use-network-params.ts index 24efccf30..9c20da7fd 100644 --- a/libs/react-helpers/src/hooks/use-network-params.ts +++ b/libs/react-helpers/src/hooks/use-network-params.ts @@ -102,6 +102,9 @@ export const NetworkParams = { spam_protection_voting_min_tokens: 'spam_protection_voting_min_tokens', spam_protection_proposal_min_tokens: 'spam_protection_proposal_min_tokens', market_liquidity_stakeToCcySiskas: 'market_liquidity_stakeToCcySiskas', + market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume', + market_liquidity_targetstake_triggering_ratio: + 'market_liquidity_targetstake_triggering_ratio', } as const; type Params = typeof NetworkParams; diff --git a/libs/react-helpers/src/index.ts b/libs/react-helpers/src/index.ts index 65b755edf..754470ae4 100644 --- a/libs/react-helpers/src/index.ts +++ b/libs/react-helpers/src/index.ts @@ -14,3 +14,4 @@ export * from './lib/links'; export * from './lib/is-asset-erc20'; export * from './lib/remove-pagination-wrapper'; export * from './lib/__generated__/ChainId'; +export * from './lib/data-grid'; diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/market-data-grid.tsx b/libs/react-helpers/src/lib/data-grid/data-grid.tsx similarity index 83% rename from libs/deal-ticket/src/components/trading-mode-tooltip/market-data-grid.tsx rename to libs/react-helpers/src/lib/data-grid/data-grid.tsx index 00f3d46d6..4be0f9122 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/market-data-grid.tsx +++ b/libs/react-helpers/src/lib/data-grid/data-grid.tsx @@ -1,13 +1,13 @@ import type { ReactNode } from 'react'; -export type MarketDataGridProps = { +export type DataGridProps = { grid: { label: string | ReactNode; value?: ReactNode; }[]; }; -export const MarketDataGrid = ({ grid }: MarketDataGridProps) => { +export const DataGrid = ({ grid }: DataGridProps) => { return ( <> {grid.map( diff --git a/libs/react-helpers/src/lib/data-grid/index.ts b/libs/react-helpers/src/lib/data-grid/index.ts new file mode 100644 index 000000000..143006fef --- /dev/null +++ b/libs/react-helpers/src/lib/data-grid/index.ts @@ -0,0 +1 @@ +export * from './data-grid'; diff --git a/libs/react-helpers/src/lib/index.ts b/libs/react-helpers/src/lib/index.ts index 3002277f6..b07836439 100644 --- a/libs/react-helpers/src/lib/index.ts +++ b/libs/react-helpers/src/lib/index.ts @@ -11,3 +11,4 @@ export * from './remove-0x'; export * from './time'; export * from './links'; export * from './remove-pagination-wrapper'; +export * from './data-grid';