Compare commits

...
8 changed files with 256 additions and 2 deletions
@@ -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,11 @@ export const TradeMarketHeader = ({
</HeaderStat>
) : null}
<MarketProposalNotification marketId={market?.id} />
<MarketLiquiditySupplied
marketId={market?.id}
assetDecimals={asset?.decimals || 0}
isHeader
/>
</Header>
);
};
@@ -0,0 +1 @@
export * from './liquidity-supplied';
@@ -0,0 +1,145 @@
import { useCallback, useMemo, useState } from 'react';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
NetworkParams,
t,
useDataProvider,
useNetworkParams,
} from '@vegaprotocol/react-helpers';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import { Indicator, Link, Tooltip } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
import { MarketDataGrid } from '@vegaprotocol/deal-ticket';
interface Props {
marketId?: string;
onSelect?: (marketId: string) => void;
isHeader?: boolean;
noUpdate?: boolean;
assetDecimals: number;
}
export const MarketLiquiditySupplied = ({
marketId,
assetDecimals,
onSelect,
isHeader = false,
noUpdate = false,
}: Props) => {
const [market, setMarket] = useState<MarketDealTicket>();
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<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const update = useCallback(
({ data: marketData }: { data: MarketData | null }) => {
if (!noUpdate && marketData) {
setMarket({
...data,
data: marketData,
} as MarketDealTicket);
}
return true;
},
[noUpdate, data]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
skip: noUpdate || !marketId || !data,
});
const supplied = market?.data.suppliedStake
? addDecimalsFormatNumber(
new BigNumber(market?.data.suppliedStake)
.multipliedBy(stakeToCcyVolume || 1)
.toString(),
assetDecimals
)
: '-';
const { percentage, status } = useCheckLiquidityStatus({
suppliedStake: market?.data.suppliedStake || 0,
targetStake: market?.data.targetStake || 0,
triggeringRatio,
});
const compiledGrid = [
{
label: t('Supplied stake'),
value: market?.data.suppliedStake
? addDecimalsFormatNumber(
new BigNumber(market?.data.suppliedStake).toString(),
assetDecimals
)
: '-',
},
{
label: t('Target stake'),
value: market?.data.targetStake
? addDecimalsFormatNumber(
new BigNumber(market?.data.targetStake).toString(),
assetDecimals
)
: '-',
},
];
const description = (
<section>
{compiledGrid && <MarketDataGrid grid={compiledGrid} />}
<br />
<Link href={`/#/liquidity/${marketId}`} data-testid="view-liquidity-link">
{t('View liquidity provision table')}
</Link>
</section>
);
return isHeader ? (
<HeaderStat
heading={t('Liquidity supplied')}
description={description}
testId="liquidity-supplied"
>
<div className="flex flex-inline gap-1">
<Indicator variant={status} />
<span>({formatNumberPercentage(percentage, 2)})</span>
<span>{supplied}</span>
</div>
</HeaderStat>
) : (
<Tooltip description={description}>
<span>{supplied}</span>
</Tooltip>
);
};
@@ -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 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'),
});
});
});
@@ -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,
};
}
};
+4 -2
View File
@@ -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`
@@ -11,6 +11,8 @@ fragment MarketDataUpdateFields on ObservableMarketData {
indicativePrice
bestStaticBidPrice
bestStaticOfferPrice
targetStake
suppliedStake
}
subscription MarketDataUpdate($marketId: ID!) {
@@ -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;