feat(trading): add LP fee settings (#5773)

Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
This commit is contained in:
m.ray 2024-02-09 21:44:15 +02:00 committed by GitHub
parent 44189591fc
commit 0d850bd8b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 203 additions and 25 deletions

View File

@ -1,6 +1,11 @@
import { type TransferNode } from '@vegaprotocol/types'; import { type TransferNode } from '@vegaprotocol/types';
import { ActiveRewardCard } from '../rewards-container/active-rewards'; import {
ActiveRewardCard,
isActiveReward,
} from '../rewards-container/active-rewards';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useMarketsMapProvider } from '@vegaprotocol/markets';
export const GamesContainer = ({ export const GamesContainer = ({
data, data,
@ -10,8 +15,35 @@ export const GamesContainer = ({
currentEpoch: number; currentEpoch: number;
}) => { }) => {
const t = useT(); const t = useT();
// Re-load markets and assets in the games container to ensure that the
// the cards are updated (not grayed out) when the user navigates to the games page
const { data: assets } = useAssetsMapProvider();
const { data: markets } = useMarketsMapProvider();
if (!data || data.length === 0) { const enrichedTransfers = data
.filter((node) => isActiveReward(node, currentEpoch))
.map((node) => {
if (node.transfer.kind.__typename !== 'RecurringTransfer') {
return node;
}
const asset =
assets &&
assets[
node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || ''
];
const marketsInScope =
node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map(
(id) => markets && markets[id]
);
return { ...node, asset, markets: marketsInScope };
});
if (!enrichedTransfers || !enrichedTransfers.length) return null;
if (!enrichedTransfers || enrichedTransfers.length === 0) {
return ( return (
<p className="mb-6 text-muted"> <p className="mb-6 text-muted">
{t('There are currently no games available.')} {t('There are currently no games available.')}
@ -21,7 +53,7 @@ export const GamesContainer = ({
return ( return (
<div className="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{data.map((game, i) => { {enrichedTransfers.map((game, i) => {
// TODO: Remove `kind` prop from ActiveRewardCard // TODO: Remove `kind` prop from ActiveRewardCard
const { transfer } = game; const { transfer } = game;
if ( if (

View File

@ -468,7 +468,7 @@ export const ActiveRewardCard = ({
} }
</div> </div>
{dispatchStrategy?.dispatchMetric && ( {dispatchStrategy?.dispatchMetric && (
<span className="text-muted text-sm h-[2rem]"> <span className="text-muted text-sm h-[3rem]">
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])} {t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
</span> </span>
)} )}

View File

@ -288,7 +288,7 @@ def test_game_card(competitions_page: Page):
expect(game_1.get_by_test_id("distribution-strategy") expect(game_1.get_by_test_id("distribution-strategy")
).to_have_text("Pro rata") ).to_have_text("Pro rata")
expect(game_1.get_by_test_id("dispatch-metric-info") expect(game_1.get_by_test_id("dispatch-metric-info")
).to_have_text("Price maker fees paid • ") ).to_have_text("Price maker fees paid • tDAI")
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs") expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
expect(game_1.get_by_test_id("scope")).to_have_text("In team") expect(game_1.get_by_test_id("scope")).to_have_text("In team")
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00") expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00")

View File

@ -86,6 +86,7 @@ export const DocsLinks = VEGA_DOCS_URL
POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`, POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`,
QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`, QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`,
REFERRALS: `${VEGA_DOCS_URL}/tutorials/proposals/referral-program-proposal`, REFERRALS: `${VEGA_DOCS_URL}/tutorials/proposals/referral-program-proposal`,
LIQUIDITY_FEE_PERCENTAGE: `${VEGA_DOCS_URL}/concepts/liquidity/rewards-penalties#determining-the-liquidity-fee-percentage`,
} }
: undefined; : undefined;

View File

@ -110,6 +110,7 @@
"Fills": "Fills", "Fills": "Fills",
"Final commission rate": "Final commission rate", "Final commission rate": "Final commission rate",
"Find out more": "Find out more", "Find out more": "Find out more",
"For more info, visit the documentation": "For more info, visit the documentation",
"Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.": "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.", "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.": "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.",
"From epoch": "From epoch", "From epoch": "From epoch",
"Fully decentralised high performance peer-to-network trading.": "Fully decentralised high performance peer-to-network trading.", "Fully decentralised high performance peer-to-network trading.": "Fully decentralised high performance peer-to-network trading.",

File diff suppressed because one or more lines are too long

View File

@ -171,6 +171,10 @@ query MarketInfo($marketId: ID!) {
infrastructureFee infrastructureFee
liquidityFee liquidityFee
} }
liquidityFeeSettings {
feeConstant
method
}
} }
priceMonitoringSettings { priceMonitoringSettings {
parameters { parameters {

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@ import {
InsurancePoolInfoPanel, InsurancePoolInfoPanel,
KeyDetailsInfoPanel, KeyDetailsInfoPanel,
LiquidationStrategyInfoPanel, LiquidationStrategyInfoPanel,
LiquidityFeesSettings,
LiquidityInfoPanel, LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel, LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel, LiquidityPriceRangeInfoPanel,
@ -300,6 +301,11 @@ export const MarketInfoAccordion = ({
} }
content={<LiquiditySLAParametersInfoPanel market={market} />} content={<LiquiditySLAParametersInfoPanel market={market} />}
/> />
<AccordionItem
itemId="lp-fee-settings"
title={t('Liquidity fee settings')}
content={<LiquidityFeesSettings market={market} />}
/>
<AccordionItem <AccordionItem
itemId="liquidity" itemId="liquidity"
title={t('Liquidity')} title={t('Liquidity')}

View File

@ -44,6 +44,8 @@ import type {
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { import {
ConditionOperatorMapping, ConditionOperatorMapping,
LiquidityFeeMethodMapping,
LiquidityFeeMethodMappingDescription,
MarketStateMapping, MarketStateMapping,
MarketTradingModeMapping, MarketTradingModeMapping,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
@ -54,6 +56,7 @@ import {
TOKEN_PROPOSAL, TOKEN_PROPOSAL,
useEnvironment, useEnvironment,
useLinks, useLinks,
DocsLinks,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import type { Provider } from '../../oracle-schema'; import type { Provider } from '../../oracle-schema';
import { OracleBasicProfile } from '../../components/oracle-basic-profile'; import { OracleBasicProfile } from '../../components/oracle-basic-profile';
@ -110,6 +113,44 @@ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => {
); );
}; };
export const LiquidityFeesSettings = ({ market }: MarketInfoProps) => {
const t = useT();
return (
<>
<MarketInfoTable
data={{
feeConstant: market.fees.liquidityFeeSettings?.feeConstant,
method: market.fees.liquidityFeeSettings && (
<Tooltip
description={
LiquidityFeeMethodMappingDescription[
market.fees.liquidityFeeSettings?.method
]
}
>
<span>
{
LiquidityFeeMethodMapping[
market.fees.liquidityFeeSettings?.method
]
}
</span>
</Tooltip>
),
}}
/>
<p className="text-xs">
<ExternalLink
href={DocsLinks?.LIQUIDITY_FEE_PERCENTAGE}
className="mt-2"
>
{t('Fore more info, visit the documentation')}
</ExternalLink>
</p>
</>
);
};
export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => { export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
const t = useT(); const t = useT();
const assetSymbol = getAsset(market).symbol; const assetSymbol = getAsset(market).symbol;

View File

@ -16,7 +16,6 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => {
infrastructureFee: t( infrastructureFee: t(
'Fees paid to validators as a reward for running the infrastructure of the network.' 'Fees paid to validators as a reward for running the infrastructure of the network.'
), ),
markPrice: t( markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.' 'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
), ),
@ -154,5 +153,9 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => {
minProbabilityOfTradingLPOrders: t( minProbabilityOfTradingLPOrders: t(
'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.' 'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.'
), ),
method: t(`The method used to calculate the market's liquidity fee.`),
feeConstant: t(
'The constant liquidity fee used when using the constant fee method .'
),
}; };
}; };

View File

@ -240,7 +240,7 @@ export const OracleFullProfile = ({
<div className="font-alpha calt dark:text-vega-light-300 text-vega-dark-300 mb-2 grid grid-cols-4 gap-1 uppercase"> <div className="font-alpha calt dark:text-vega-light-300 text-vega-dark-300 mb-2 grid grid-cols-4 gap-1 uppercase">
<div className="col-span-1">{t('Market')}</div> <div className="col-span-1">{t('Market')}</div>
<div className="col-span-1">{t('Status')}</div> <div className="col-span-1">{t('Status')}</div>
<div className="col-span-1">{t('Specifications')}</div> <div className="col-span-2">{t('Specifications')}</div>
</div> </div>
<div className="max-h-60 overflow-auto"> <div className="max-h-60 overflow-auto">
{oracleMarkets?.map((market) => ( {oracleMarkets?.map((market) => (

View File

@ -12,6 +12,10 @@ fragment MarketFields on Market {
infrastructureFee infrastructureFee
liquidityFee liquidityFee
} }
liquidityFeeSettings {
feeConstant
method
}
} }
tradableInstrument { tradableInstrument {
instrument { instrument {

View File

@ -52,6 +52,11 @@ export const createMarketFragment = (
infrastructureFee: '', infrastructureFee: '',
liquidityFee: '', liquidityFee: '',
}, },
liquidityFeeSettings: {
__typename: 'LiquidityFeeSettings',
method: Schema.LiquidityFeeMethod.METHOD_MARGINAL_COST,
feeConstant: '',
},
}, },
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {

View File

@ -14,6 +14,32 @@ export type Scalars = {
Timestamp: any; Timestamp: any;
}; };
/** Margins for a hypothetical position not related to any existing party */
export type AbstractMarginLevels = {
__typename?: 'AbstractMarginLevels';
/** Asset for the current margins */
asset: Asset;
/**
* If the margin of the party is greater than this level, then collateral will be released from the margin account into
* the general account of the party for the given asset.
*/
collateralReleaseLevel: Scalars['String'];
/** This is the minimum margin required for a party to place a new order on the network, expressed as unsigned integer */
initialLevel: Scalars['String'];
/** Minimal margin for the position to be maintained in the network (unsigned integer) */
maintenanceLevel: Scalars['String'];
/** Margin factor, only relevant for isolated margin mode, else 0 */
marginFactor: Scalars['String'];
/** Margin mode of the party, cross margin or isolated margin */
marginMode: MarginMode;
/** Market in which the margin is required for this party */
market: Market;
/** When in isolated margin, the required order margin level, otherwise, 0 */
orderMarginLevel: Scalars['String'];
/** If the margin is between maintenance and search, the network will initiate a collateral search, expressed as unsigned integer */
searchLevel: Scalars['String'];
};
/** An account record */ /** An account record */
export type AccountBalance = { export type AccountBalance = {
__typename?: 'AccountBalance'; __typename?: 'AccountBalance';
@ -359,6 +385,8 @@ export enum AuctionTrigger {
export type BatchProposal = { export type BatchProposal = {
__typename?: 'BatchProposal'; __typename?: 'BatchProposal';
/** Terms of all the proposals in the batch */
batchTerms?: Maybe<BatchProposalTerms>;
/** RFC3339Nano time and date when the proposal reached the network */ /** RFC3339Nano time and date when the proposal reached the network */
datetime: Scalars['Timestamp']; datetime: Scalars['Timestamp'];
/** Details of the rejection reason */ /** Details of the rejection reason */
@ -389,10 +417,10 @@ export type BatchProposal = {
votes: ProposalVotes; votes: ProposalVotes;
}; };
/** The rationale for the proposal */ /** The terms for the batch proposal */
export type BatchProposalTerms = { export type BatchProposalTerms = {
__typename?: 'BatchProposalTerms'; __typename?: 'BatchProposalTerms';
/** Actual changes being introduced by the proposal - actions the proposal triggers if passed and enacted. */ /** Actual changes being introduced by the batch proposal - actions the proposal triggers if passed and enacted. */
changes: Array<Maybe<BatchProposalTermsChange>>; changes: Array<Maybe<BatchProposalTermsChange>>;
/** /**
* RFC3339Nano time and date when voting closes for this proposal. * RFC3339Nano time and date when voting closes for this proposal.
@ -531,6 +559,22 @@ export type CompositePriceConfiguration = {
decayWeight: Scalars['String']; decayWeight: Scalars['String'];
}; };
export type CompositePriceSource = {
__typename?: 'CompositePriceSource';
/** The source of the price */
PriceSource: Scalars['String'];
/** The last time the price source was updated in RFC3339Nano */
lastUpdated: Scalars['Timestamp'];
/** The current value of the composite source price */
price: Scalars['String'];
};
export type CompositePriceState = {
__typename?: 'CompositePriceState';
/** Underlying state of the composite price */
priceSources?: Maybe<Array<CompositePriceSource>>;
};
export enum CompositePriceType { export enum CompositePriceType {
/** Composite price is set to the last trade (legacy) */ /** Composite price is set to the last trade (legacy) */
COMPOSITE_PRICE_TYPE_LAST_TRADE = 'COMPOSITE_PRICE_TYPE_LAST_TRADE', COMPOSITE_PRICE_TYPE_LAST_TRADE = 'COMPOSITE_PRICE_TYPE_LAST_TRADE',
@ -2165,9 +2209,9 @@ export type MarginEdge = {
export type MarginEstimate = { export type MarginEstimate = {
__typename?: 'MarginEstimate'; __typename?: 'MarginEstimate';
/** Margin level estimate assuming no slippage */ /** Margin level estimate assuming no slippage */
bestCase: MarginLevels; bestCase: AbstractMarginLevels;
/** Margin level estimate assuming slippage cap is applied */ /** Margin level estimate assuming slippage cap is applied */
worstCase: MarginLevels; worstCase: AbstractMarginLevels;
}; };
/** Margins for a given a party */ /** Margins for a given a party */
@ -2439,6 +2483,8 @@ export type MarketData = {
liquidityProviderSla?: Maybe<Array<LiquidityProviderSLA>>; liquidityProviderSla?: Maybe<Array<LiquidityProviderSLA>>;
/** The mark price (an unsigned integer) */ /** The mark price (an unsigned integer) */
markPrice: Scalars['String']; markPrice: Scalars['String'];
/** State of the underlying internal composite price */
markPriceState?: Maybe<CompositePriceState>;
/** The methodology used for the calculation of the mark price */ /** The methodology used for the calculation of the mark price */
markPriceType: CompositePriceType; markPriceType: CompositePriceType;
/** Market of the associated mark price */ /** Market of the associated mark price */
@ -3053,6 +3099,8 @@ export type ObservableMarketData = {
liquidityProviderSla?: Maybe<Array<ObservableLiquidityProviderSLA>>; liquidityProviderSla?: Maybe<Array<ObservableLiquidityProviderSLA>>;
/** The mark price (an unsigned integer) */ /** The mark price (an unsigned integer) */
markPrice: Scalars['String']; markPrice: Scalars['String'];
/** State of the underlying internal composite price */
markPriceState?: Maybe<CompositePriceState>;
/** The methodology used to calculated mark price */ /** The methodology used to calculated mark price */
markPriceType: CompositePriceType; markPriceType: CompositePriceType;
/** The market growth factor for the last market time window */ /** The market growth factor for the last market time window */
@ -4021,6 +4069,8 @@ export type PerpetualData = {
fundingRate?: Maybe<Scalars['String']>; fundingRate?: Maybe<Scalars['String']>;
/** Internal composite price used as input to the internal VWAP */ /** Internal composite price used as input to the internal VWAP */
internalCompositePrice: Scalars['String']; internalCompositePrice: Scalars['String'];
/** The internal state of the underlying internal composite price */
internalCompositePriceState?: Maybe<CompositePriceState>;
/** The methodology used to calculated internal composite price for perpetual markets */ /** The methodology used to calculated internal composite price for perpetual markets */
internalCompositePriceType: CompositePriceType; internalCompositePriceType: CompositePriceType;
/** Time-weighted average price calculated from data points for this period from the internal data source. */ /** Time-weighted average price calculated from data points for this period from the internal data source. */
@ -4031,6 +4081,8 @@ export type PerpetualData = {
seqNum: Scalars['Int']; seqNum: Scalars['Int'];
/** Time at which the funding period started */ /** Time at which the funding period started */
startTime: Scalars['Timestamp']; startTime: Scalars['Timestamp'];
/** The last value from the external oracle */
underlyingIndexPrice: Scalars['String'];
}; };
export type PerpetualProduct = { export type PerpetualProduct = {
@ -4328,7 +4380,7 @@ export type ProposalDetail = {
__typename?: 'ProposalDetail'; __typename?: 'ProposalDetail';
/** Batch proposal ID that is provided by Vega once proposal reaches the network */ /** Batch proposal ID that is provided by Vega once proposal reaches the network */
batchId?: Maybe<Scalars['ID']>; batchId?: Maybe<Scalars['ID']>;
/** Terms of the proposal for a batch proposal */ /** Terms of all the proposals in the batch */
batchTerms?: Maybe<BatchProposalTerms>; batchTerms?: Maybe<BatchProposalTerms>;
/** RFC3339Nano time and date when the proposal reached the Vega network */ /** RFC3339Nano time and date when the proposal reached the Vega network */
datetime: Scalars['Timestamp']; datetime: Scalars['Timestamp'];
@ -4354,7 +4406,7 @@ export type ProposalDetail = {
requiredParticipation: Scalars['String']; requiredParticipation: Scalars['String'];
/** State of the proposal */ /** State of the proposal */
state: ProposalState; state: ProposalState;
/** Terms of the proposal for proposal */ /** Terms of the proposal */
terms?: Maybe<ProposalTerms>; terms?: Maybe<ProposalTerms>;
}; };

View File

@ -1,12 +1,13 @@
import type { import {
ConditionOperator, type LiquidityFeeMethod,
EntityScope, type ConditionOperator,
GovernanceTransferKind, type EntityScope,
GovernanceTransferType, type GovernanceTransferKind,
IndividualScope, type GovernanceTransferType,
PeggedReference, type IndividualScope,
ProposalChange, type PeggedReference,
TransferStatus, type ProposalChange,
type TransferStatus,
} from './__generated__/types'; } from './__generated__/types';
import type { AccountType } from './__generated__/types'; import type { AccountType } from './__generated__/types';
import type { import type {
@ -734,3 +735,23 @@ export const ProposalProductTypeShortName: Record<ProposalProductType, string> =
SpotProduct: 'Spot', SpotProduct: 'Spot',
PerpetualProduct: 'Perp', PerpetualProduct: 'Perp',
}; };
export const LiquidityFeeMethodMapping: { [e in LiquidityFeeMethod]: string } =
{
/** Fee is set by the market to a constant value irrespective of any liquidity provider's nominated fee */
METHOD_CONSTANT: 'Constant',
/** Fee is smallest value of all bids, such that liquidity providers with nominated fees less than or equal to this value still have sufficient commitment to fulfil the market's target stake. */
METHOD_MARGINAL_COST: 'Marginal cost',
METHOD_UNSPECIFIED: 'Unspecified',
/** Fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment */
METHOD_WEIGHTED_AVERAGE: 'Weighted average',
};
export const LiquidityFeeMethodMappingDescription: {
[e in LiquidityFeeMethod]: string;
} = {
METHOD_CONSTANT: `This liquidity fee is a constant value, set in the market parameters, and overrides the liquidity providers' nominated fees.`,
METHOD_MARGINAL_COST: `This liquidity fee factor is determined by sorting all LP fee bids from lowest to highest, with LPs' commitments tallied up to the point of fulfilling the market's target stake. The last LP's bid becomes the fee factor.`,
METHOD_UNSPECIFIED: 'Unspecified',
METHOD_WEIGHTED_AVERAGE: `This liquidity fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment.`,
};