feat(trading): add earmarked fees (#5123)

This commit is contained in:
m.ray 2023-10-27 14:09:01 +03:00 committed by GitHub
parent 1397aafd25
commit dac7142a98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 205 additions and 62 deletions

View File

@ -28,9 +28,10 @@ const headers = [
'Adjusted stake share',
'Share',
'Live supplied liquidity',
'Live time fraction on book',
'Fees accrued this epoch',
'Live time on book',
'Live liquidity quality score (%)',
'Last time fraction on the book',
'Last time on the book',
'Last fee penalty',
'Last bond penalty',
'Status',

View File

@ -3,17 +3,16 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
@ -26,6 +25,3 @@ NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket

View File

@ -4,7 +4,7 @@ fragment LiquidityProvisionFields on LiquidityProvision {
id
party {
id
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
accountsConnection(marketId: $marketId) {
edges {
node {
type
@ -22,11 +22,22 @@ fragment LiquidityProvisionFields on LiquidityProvision {
query LiquidityProvisions($marketId: ID!) {
market(id: $marketId) {
liquidityProvisionsConnection(live: true) {
liquiditySLAParameters {
priceRange
commitmentMinTimeFraction
performanceHysteresisEpochs
slaCompetitionFactor
}
liquidityProvisions(live: true) {
edges {
node {
current {
...LiquidityProvisionFields
}
pending {
...LiquidityProvisionFields
}
}
}
}
}

View File

@ -1,13 +1,15 @@
fragment MarketNode on Market {
id
liquidityProvisionsConnection(live: true) {
liquidityProvisions(live: true) {
edges {
node {
current {
commitmentAmount
fee
}
}
}
}
data {
targetStake
}

View File

@ -10,7 +10,7 @@ export type LiquidityProvisionsQueryVariables = Types.Exact<{
}>;
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', id: string, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquiditySLAParameters?: { __typename?: 'LiquiditySLAParameters', priceRange: string, commitmentMinTimeFraction: string, performanceHysteresisEpochs: number, slaCompetitionFactor: string } | null, liquidityProvisions?: { __typename?: 'LiquidityProvisionsWithPendingConnection', edges?: Array<{ __typename?: 'LiquidityProvisionWithPendingEdge', node: { __typename?: 'LiquidityProvisionWithPending', current: { __typename?: 'LiquidityProvision', id: string, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } }, pending?: { __typename?: 'LiquidityProvision', id: string, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } | null } } | null> | null } | null } | null };
export type LiquidityProviderFeeShareFieldsFragment = { __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, averageScore: string, virtualStake: string };
@ -30,7 +30,7 @@ export const LiquidityProvisionFieldsFragmentDoc = gql`
id
party {
id
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
accountsConnection(marketId: $marketId) {
edges {
node {
type
@ -82,11 +82,22 @@ ${LiquidityProviderSLAFieldsFragmentDoc}`;
export const LiquidityProvisionsDocument = gql`
query LiquidityProvisions($marketId: ID!) {
market(id: $marketId) {
liquidityProvisionsConnection(live: true) {
liquiditySLAParameters {
priceRange
commitmentMinTimeFraction
performanceHysteresisEpochs
slaCompetitionFactor
}
liquidityProvisions(live: true) {
edges {
node {
current {
...LiquidityProvisionFields
}
pending {
...LiquidityProvisionFields
}
}
}
}
}

View File

@ -3,24 +3,26 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type MarketNodeFragment = { __typename?: 'Market', id: string, liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', commitmentAmount: string, fee: string } } | null> | null } | null, data?: { __typename?: 'MarketData', targetStake?: string | null } | null };
export type MarketNodeFragment = { __typename?: 'Market', id: string, liquidityProvisions?: { __typename?: 'LiquidityProvisionsWithPendingConnection', edges?: Array<{ __typename?: 'LiquidityProvisionWithPendingEdge', node: { __typename?: 'LiquidityProvisionWithPending', current: { __typename?: 'LiquidityProvision', commitmentAmount: string, fee: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', targetStake?: string | null } | null };
export type LiquidityProvisionMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type LiquidityProvisionMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', commitmentAmount: string, fee: string } } | null> | null } | null, data?: { __typename?: 'MarketData', targetStake?: string | null } | null } }> } | null };
export type LiquidityProvisionMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, liquidityProvisions?: { __typename?: 'LiquidityProvisionsWithPendingConnection', edges?: Array<{ __typename?: 'LiquidityProvisionWithPendingEdge', node: { __typename?: 'LiquidityProvisionWithPending', current: { __typename?: 'LiquidityProvision', commitmentAmount: string, fee: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', targetStake?: string | null } | null } }> } | null };
export const MarketNodeFragmentDoc = gql`
fragment MarketNode on Market {
id
liquidityProvisionsConnection(live: true) {
liquidityProvisions(live: true) {
edges {
node {
current {
commitmentAmount
fee
}
}
}
}
data {
targetStake
}

View File

@ -1,13 +1,12 @@
import { AccountType } from '@vegaprotocol/types';
import type { LiquidityProvisionFields } from './liquidity-data-provider';
import { getLiquidityProvision } from './liquidity-data-provider';
import type {
LiquidityProviderFieldsFragment,
LiquidityProvisionFieldsFragment,
} from './__generated__/MarketLiquidity';
import type { LiquidityProviderFieldsFragment } from './__generated__/MarketLiquidity';
const input = {
liquidityProvisions: [
{
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
party: {
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
accountsConnection: {
@ -31,7 +30,11 @@ const input = {
fee: '0.001',
status: 'STATUS_ACTIVE',
__typename: 'LiquidityProvision',
} as LiquidityProvisionFieldsFragment,
priceRange: '0',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 5678,
slaCompetitionFactor: '0',
} as unknown as LiquidityProvisionFields,
],
liquidityProviders: [
{
@ -49,9 +52,12 @@ const input = {
const result = [
{
__typename: undefined,
balance: '1.8003328918633596575e+22',
balance: 1.8003328918633597e22,
earmarkedFees: 0,
commitmentAmount: '18003328918633596575000',
createdAt: '2022-12-16T09:28:29.071781Z',
commitmentMinTimeFraction: '0.5',
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
feeShare: {
equityLikeShare: '1',
__typename: 'LiquidityProviderFeeShare',
@ -59,6 +65,9 @@ const result = [
},
fee: '0.001',
partyId: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
performanceHysteresisEpochs: 5678,
priceRange: '0',
slaCompetitionFactor: '0',
party: {
__typename: 'Party',
accountsConnection: {
@ -106,7 +115,9 @@ describe('getLiquidityProvision', () => {
{
__typename: 'LiquidityProvision',
commitmentAmount: '18003328918633596575000',
commitmentMinTimeFraction: '0.5',
createdAt: '2022-12-16T09:28:29.071781Z',
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
fee: '0.001',
party: {
__typename: 'Party',
@ -125,6 +136,9 @@ describe('getLiquidityProvision', () => {
},
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
},
performanceHysteresisEpochs: 5678,
priceRange: '0',
slaCompetitionFactor: '0',
status: 'STATUS_ACTIVE',
updatedAt: '2023-01-04T22:13:27.761985Z',
},

View File

@ -20,20 +20,24 @@ import type {
LiquidityProvisionsQueryVariables,
} from './__generated__/MarketLiquidity';
export type LiquidityProvisionFields = LiquidityProvisionFieldsFragment &
Schema.LiquiditySLAParameters;
export const liquidityProvisionsDataProvider = makeDataProvider<
LiquidityProvisionsQuery,
LiquidityProvisionFieldsFragment[],
LiquidityProvisionFields[],
never,
never,
LiquidityProvisionsQueryVariables
>({
query: LiquidityProvisionsDocument,
getData: (responseData: LiquidityProvisionsQuery | null) => {
return (
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
(e) => e?.node
) ?? []
).filter((n) => !!n) as LiquidityProvisionFieldsFragment[];
return (responseData?.market?.liquidityProvisions?.edges
?.filter((n) => !!n)
.map((e) => ({
...e?.node.current,
...responseData.market?.liquiditySLAParameters,
})) ?? []) as LiquidityProvisionFields[];
},
});
@ -81,10 +85,7 @@ export const lpAggregatedDataProvider = makeDerivedDataProvider<
}
);
export const matchFilter = (
filter: Filter,
lp: LiquidityProvisionFieldsFragment
) => {
export const matchFilter = (filter: Filter, lp: LiquidityProvisionData) => {
if (filter.partyId && lp.party.id !== filter.partyId) {
return false;
}
@ -104,15 +105,18 @@ export const matchFilter = (
};
export interface LiquidityProvisionData
extends Omit<LiquidityProvisionFieldsFragment, '__typename'> {
extends Omit<LiquidityProvisionFieldsFragment, '__typename'>,
Partial<LiquidityProviderFieldsFragment>,
Omit<Schema.LiquiditySLAParameters, '__typename'> {
assetDecimalPlaces?: number;
balance?: string;
balance?: number;
averageEntryValuation?: string;
equityLikeShare?: string;
earmarkedFees?: number;
}
export const getLiquidityProvision = (
liquidityProvisions: LiquidityProvisionFieldsFragment[],
liquidityProvisions: LiquidityProvisionFields[],
liquidityProvider: LiquidityProviderFieldsFragment[],
filter?: Filter
): LiquidityProvisionData[] => {
@ -141,17 +145,29 @@ export const getLiquidityProvision = (
const bondAccounts = accounts?.filter(
(a) => a?.type === Schema.AccountType.ACCOUNT_TYPE_BOND
);
const feeAccounts = accounts?.filter(
(a) => a?.type === Schema.AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES
);
const balance =
bondAccounts
?.reduce(
(acc, a) => acc.plus(new BigNumber(a.balance ?? 0)),
new BigNumber(0)
)
.toString() || '0';
.toNumber() ?? 0;
const earmarkedFees =
feeAccounts
?.reduce(
(acc, a) => acc.plus(new BigNumber(a.balance ?? 0)),
new BigNumber(0)
)
.toNumber() ?? 0;
return {
...lp,
...lpObj,
balance,
earmarkedFees,
__typename: undefined,
};
});

View File

@ -55,9 +55,10 @@ describe('LiquidityTable', () => {
'Adjusted stake share',
'Share',
'Live supplied liquidity',
'Live time fraction on book',
'Fees accrued this epoch',
'Live time on book',
'Live liquidity quality score (%)',
'Last time fraction on the book',
'Last time on the book',
'Last fee penalty',
'Last bond penalty',
'Status',

View File

@ -96,6 +96,58 @@ export const LiquidityTable = ({
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`;
};
const feesAccruedTooltip = ({ value, data }: ITooltipParams) => {
if (!value) return '-';
const newValue = new BigNumber(value)
.times(Number(stakeToCcyVolume) || 1)
.toString();
let lessThanFull = false,
lessThanMinimum = false;
if (data.sla) {
lessThanFull =
data.sla &&
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).isLessThan(
1
);
lessThanMinimum =
data.sla &&
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).isLessThan(
data.commitmentMinTimeFraction
);
}
if (lessThanMinimum) {
return t(
`This LP's time on the book in the current epoch (%s) is less than the minimum required (%s), so they could lose all fee revenue for this epoch.`,
[
formatNumberPercentage(
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
2
),
formatNumberPercentage(
new BigNumber(data.commitmentMinTimeFraction).times(100),
2
),
]
);
}
if (lessThanFull) {
return t(
`This LP's time on the book in the current epoch (%s) is less than 100%, so they could lose some fees to a better performing LP.`,
[
formatNumberPercentage(
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
2
),
]
);
}
return addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0);
};
const stakeToCcyVolumeQuantumFormatter = ({
value,
}: ValueFormatterParams) => {
@ -178,7 +230,7 @@ export const LiquidityTable = ({
],
},
{
headerName: t('Live liquidity details'),
headerName: t('Live liquidity data'),
marryChildren: true,
children: [
{
@ -192,7 +244,41 @@ export const LiquidityTable = ({
tooltipValueGetter: stakeToCcyVolumeFormatter,
},
{
headerName: t(`Live time fraction on book`),
headerName: t('Fees accrued this epoch'),
field: 'earmarkedFees',
type: 'rightAligned',
headerTooltip: t(
`The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.`
),
valueFormatter: stakeToCcyVolumeQuantumFormatter,
tooltipValueGetter: feesAccruedTooltip,
cellClassRules: {
'text-warning': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return (
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(1) &&
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isGreaterThan(data.commitmentMinTimeFraction)
);
},
'text-red-500': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return (
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(data.commitmentMinTimeFraction) &&
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isGreaterThan(0)
);
},
},
},
{
headerName: t(`Live time on book`),
field: 'sla.currentEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t('Current epoch fraction of time on the book.'),
@ -212,7 +298,7 @@ export const LiquidityTable = ({
marryChildren: true,
children: [
{
headerName: t(`Last time fraction on the book`),
headerName: t(`Last time on the book`),
field: 'sla.lastEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t('Last epoch fraction of time on the book.'),
@ -275,7 +361,9 @@ export const LiquidityTable = ({
return (
<AgGrid
overlayNoRowsTemplate={t('No liquidity provisions')}
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
getRowId={({ data }: { data: LiquidityProvisionData }) => {
return data.id || '';
}}
tooltipShowDelay={500}
defaultColDef={defaultColDef}
{...props}

View File

@ -12,12 +12,14 @@ export const liquidityProvisionsQuery = (
): LiquidityProvisionsQuery => {
const defaultResult: LiquidityProvisionsQuery = {
market: {
liquidityProvisionsConnection: {
__typename: 'LiquidityProvisionsConnection',
liquidityProvisions: {
__typename: 'LiquidityProvisionsWithPendingConnection',
edges: liquidityFields.map((node) => {
return {
__typename: 'LiquidityProvisionsEdge',
node,
__typename: 'LiquidityProvisionWithPendingEdge',
node: {
current: node,
},
};
}),
},

View File

@ -106,10 +106,9 @@ export const getLiquidityForMarket = (
markets: LiquidityProvisionMarket[]
) => {
const liquidity =
markets.find((m) => m.id === marketId)?.liquidityProvisionsConnection
?.edges || [];
markets.find((m) => m.id === marketId)?.liquidityProvisions?.edges || [];
return liquidity.map((l) => l?.node);
return liquidity.map((l) => l?.node.current);
};
export const getTargetStake = (

View File

@ -1001,7 +1001,7 @@ export const LiquiditySLAParametersInfoPanel = ({
const { params: networkParams } = useNetworkParams([
NetworkParams.market_liquidity_bondPenaltyParameter,
NetworkParams.market_liquidity_nonPerformanceBondPenaltySlope,
NetworkParams.market_liquidity_sla_nonPerformanceBondPenaltySlope,
NetworkParams.market_liquidity_sla_nonPerformanceBondPenaltyMax,
NetworkParams.market_liquidity_maximumLiquidityFeeFactorLevel,
NetworkParams.market_liquidity_stakeToCcyVolume,
@ -1017,7 +1017,7 @@ export const LiquiditySLAParametersInfoPanel = ({
bondPenaltyParameter:
networkParams['market_liquidity_bondPenaltyParameter'],
nonPerformanceBondPenaltySlope:
networkParams['market_liquidity_nonPerformanceBondPenaltySlope'],
networkParams['market_liquidity_sla_nonPerformanceBondPenaltySlope'],
nonPerformanceBondPenaltyMax:
networkParams['market_liquidity_sla_nonPerformanceBondPenaltyMax'],
maxLiquidityFeeFactorLevel:

View File

@ -159,8 +159,8 @@ export const NetworkParams = {
'market_liquidity_targetstake_triggering_ratio',
market_liquidity_bondPenaltyParameter:
'market_liquidity_bondPenaltyParameter',
market_liquidity_nonPerformanceBondPenaltySlope:
'market_liquidity_nonPerformanceBondPenaltySlope',
market_liquidity_sla_nonPerformanceBondPenaltySlope:
'market_liquidity_sla_nonPerformanceBondPenaltySlope',
market_liquidity_sla_nonPerformanceBondPenaltyMax:
'market_liquidity_sla_nonPerformanceBondPenaltyMax',
market_liquidity_maximumLiquidityFeeFactorLevel: