feat(governance,markets,ui-toolkit): highlight successor market changes (#4449)

Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
Sam Keen 2023-08-09 14:05:37 +01:00 committed by GitHub
parent 26d5a67604
commit 8d31510d5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 652 additions and 95 deletions

View File

@ -730,6 +730,7 @@ context(
.getByTestId('key-value-table-row')
.contains(heading)
.parent()
.parent()
.siblings();
}
}

View File

@ -45,8 +45,10 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
export const ProposalMarketData = ({
marketData,
parentMarketData,
}: {
marketData: MarketInfoWithData;
parentMarketData?: MarketInfoWithData;
}) => {
const { t } = useTranslation();
const { isOpen, open, close } = useMarketDataDialogStore();
@ -58,8 +60,21 @@ export const ProposalMarketData = ({
const settlementData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const parentSettlementData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForSettlementData?.data;
const terminationData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const parentTerminationData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForTradingTermination?.data;
const isParentSettlementDataEqual =
parentSettlementData !== undefined &&
isEqual(settlementData, parentSettlementData);
const isParentTerminationDataEqual =
parentTerminationData !== undefined &&
isEqual(terminationData, parentTerminationData);
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
@ -97,12 +112,22 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="key-details"
title={t('Key details')}
content={<KeyDetailsInfoPanel market={marketData} />}
content={
<KeyDetailsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="instrument"
title={t('Instrument')}
content={<InstrumentInfoPanel market={marketData} />}
content={
<InstrumentInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
{isEqual(
getSigners(settlementData),
@ -115,6 +140,11 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@ -127,6 +157,11 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@ -135,11 +170,21 @@ export const ProposalMarketData = ({
itemId="termination-oracle"
title={t('Termination Oracle')}
content={
<OracleInfoPanel market={marketData} type="termination" />
<OracleInfoPanel
market={marketData}
type="termination"
parentMarket={
isParentTerminationDataEqual
? undefined
: parentMarketData
}
/>
}
/>
</>
)}
{/*Note: successor markets will not differ in their settlement*/}
{/*assets, so no need to pass in parent market data for comparison.*/}
<AccordionItem
itemId="settlement-asset"
title={t('Settlement asset')}
@ -148,22 +193,42 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="metadata"
title={t('Metadata')}
content={<MetadataInfoPanel market={marketData} />}
content={
<MetadataInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-model"
title={t('Risk model')}
content={<RiskModelInfoPanel market={marketData} />}
content={
<RiskModelInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-parameters"
title={t('Risk parameters')}
content={<RiskParametersInfoPanel market={marketData} />}
content={
<RiskParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-factors"
title={t('Risk factors')}
content={<RiskFactorsInfoPanel market={marketData} />}
content={
<RiskFactorsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
{(
marketData.priceMonitoringSettings?.parameters?.triggers || []
@ -174,6 +239,7 @@ export const ProposalMarketData = ({
content={
<PriceMonitoringBoundsInfoPanel
market={marketData}
parentMarket={parentMarketData}
triggerIndex={triggerIndex}
/>
}
@ -183,13 +249,21 @@ export const ProposalMarketData = ({
itemId="liqudity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
content={
<LiquidityMonitoringParametersInfoPanel market={marketData} />
<LiquidityMonitoringParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={<LiquidityPriceRangeInfoPanel market={marketData} />}
content={
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
</Accordion>
</div>

View File

@ -32,6 +32,7 @@ export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
newMarketData?: MarketInfoWithData | null;
parentMarketData?: MarketInfoWithData | null;
assetData?: AssetQuery | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
restData: any;
@ -46,6 +47,7 @@ export const Proposal = ({
networkParams,
restData,
newMarketData,
parentMarketData,
assetData,
originalMarketProposalRestData,
mostRecentlyEnactedAssociatedMarketProposal,
@ -157,7 +159,10 @@ export const Proposal = ({
{newMarketData && (
<div className="mb-4">
<ProposalMarketData marketData={newMarketData} />
<ProposalMarketData
marketData={newMarketData}
parentMarketData={parentMarketData ? parentMarketData : undefined}
/>
</div>
)}

View File

@ -14,6 +14,9 @@ import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { FLAGS } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
export const ProposalContainer = () => {
const [
@ -54,6 +57,10 @@ export const ProposalContainer = () => {
skip: !params.proposalId,
});
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
const {
state: {
data: originalMarketProposalRestData,
@ -96,6 +103,36 @@ export const ProposalContainer = () => {
},
});
const {
data: parentMarketId,
loading: parentMarketIdLoading,
error: parentMarketIdError,
} = useParentMarketIdQuery({
variables: {
marketId: newMarketData?.data?.market?.id || '',
},
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!isSuccessor ||
!newMarketData?.data?.market?.id,
});
const {
data: parentMarketData,
loading: parentMarketLoading,
error: parentMarketError,
} = useDataProvider({
dataProvider: marketInfoWithDataProvider,
skipUpdates: true,
variables: {
marketId: parentMarketId?.market?.parentMarketID || '',
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!isSuccessor ||
!parentMarketId?.market?.parentMarketID,
},
});
const {
data: assetData,
loading: assetLoading,
@ -160,6 +197,8 @@ export const ProposalContainer = () => {
newMarketLoading ||
assetLoading ||
networkParamsLoading ||
parentMarketIdLoading ||
parentMarketLoading ||
(restLoading ? (restLoading as boolean) : false) ||
(originalMarketProposalRestLoading
? (originalMarketProposalRestLoading as boolean)
@ -172,15 +211,18 @@ export const ProposalContainer = () => {
error ||
newMarketError ||
assetError ||
networkParamsError ||
parentMarketIdError ||
parentMarketError ||
restError ||
originalMarketProposalRestError ||
previouslyEnactedMarketProposalsRestError ||
networkParamsError
previouslyEnactedMarketProposalsRestError
}
data={{
...data,
...networkParams,
...(newMarketData ? { newMarketData } : {}),
...(parentMarketData ? { parentMarketData } : {}),
...(assetData ? { assetData } : {}),
...(restData ? { restData } : {}),
...(originalMarketProposalRestData
@ -197,6 +239,7 @@ export const ProposalContainer = () => {
networkParams={networkParams}
restData={restData}
newMarketData={newMarketData}
parentMarketData={parentMarketData}
assetData={assetData}
originalMarketProposalRestData={originalMarketProposalRestData}
mostRecentlyEnactedAssociatedMarketProposal={

View File

@ -28,10 +28,10 @@ export const assetProvider = makeDataProvider<
getData,
});
export const useAssetDataProvider = (assetId: string) => {
export const useAssetDataProvider = (assetId: string, skip?: boolean) => {
return useDataProvider({
dataProvider: assetProvider,
variables: { assetId: assetId || '' },
skip: !assetId,
skip: !assetId || skip,
});
};

View File

@ -4,8 +4,10 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
Intent,
KeyValueTable,
KeyValueTableRow,
Lozenge,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
@ -22,9 +24,11 @@ interface RowProps {
unformatted?: boolean;
assetSymbol?: string;
noBorder?: boolean;
parentValue?: ReactNode;
hasParentData?: boolean;
}
const Row = ({
export const Row = ({
field,
value,
decimalPlaces,
@ -32,7 +36,14 @@ const Row = ({
unformatted,
assetSymbol = '',
noBorder = true,
parentValue,
hasParentData,
}: RowProps) => {
// Note: we need both 'parentValue' and 'hasParentData' to do a conditional
// check to differentiate between when parentData itself is missing and when
// a specific parentValue is missing. These values are only used when we
// have successor market parent data.
const className = 'text-sm';
const getFormattedValue = (value: ReactNode) => {
@ -55,6 +66,10 @@ const Row = ({
const formattedValue = getFormattedValue(value);
if (!formattedValue) return null;
const newValueInSuccessorMarket = hasParentData && value && !parentValue;
const valueDiffersFromParentMarket = parentValue && parentValue !== value;
return (
<KeyValueTableRow
key={field}
@ -63,10 +78,35 @@ const Row = ({
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>
<div className="flex items-center gap-3">
<Tooltip description={tooltipMapping[field]} align="start">
<div tabIndex={-1}>{startCase(t(field))}</div>
</Tooltip>
{valueDiffersFromParentMarket && (
<Lozenge className="py-0" variant={Intent.Primary}>
{t('Updated')}
</Lozenge>
)}
{newValueInSuccessorMarket && (
<Lozenge className="py-0" variant={Intent.Primary}>
{t('Added')}
</Lozenge>
)}
</div>
<div style={{ wordBreak: 'break-word' }}>
{valueDiffersFromParentMarket ? (
<div className="flex items-center gap-3">
<span className="line-through">
{getFormattedValue(parentValue)}
</span>
<span>{formattedValue}</span>
</div>
) : (
formattedValue
)}
</div>
</KeyValueTableRow>
);
};
@ -79,6 +119,7 @@ export interface MarketInfoTableProps {
children?: ReactNode;
assetSymbol?: string;
noBorder?: boolean;
parentData?: Record<string, ReactNode> | null | undefined;
}
export const MarketInfoTable = ({
@ -89,25 +130,33 @@ export const MarketInfoTable = ({
children,
assetSymbol,
noBorder,
parentData,
}: MarketInfoTableProps) => {
if (!data || typeof data !== 'object') {
return null;
}
const hasParentData = parentData !== undefined;
return (
<>
<KeyValueTable>
{Object.entries(data).map(([key, value]) => (
<Row
key={key}
field={key}
value={value}
decimalPlaces={decimalPlaces}
assetSymbol={assetSymbol}
asPercentage={asPercentage}
unformatted={unformatted}
noBorder={noBorder}
/>
))}
<>
{Object.entries(data).map(([key, value]) => (
<Row
key={key}
field={key}
value={value}
decimalPlaces={decimalPlaces}
assetSymbol={assetSymbol}
asPercentage={asPercentage}
unformatted={unformatted}
noBorder={noBorder}
parentValue={parentData?.[key]}
hasParentData={hasParentData}
/>
))}
</>
</KeyValueTable>
<div className="flex flex-col gap-2">{children}</div>
</>

View File

@ -1,3 +1,4 @@
import isEqual from 'lodash/isEqual';
import type { ReactNode } from 'react';
import { Fragment, useState } from 'react';
import { useMemo } from 'react';
@ -47,11 +48,14 @@ import {
useSuccessorMarketQuery,
} from '../../__generated__';
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
import type { MarketTradingMode } from '@vegaprotocol/types';
import type { Signer } from '@vegaprotocol/types';
import classNames from 'classnames';
import compact from 'lodash/compact';
type MarketInfoProps = {
market: MarketInfo;
parentMarket?: MarketInfo;
children?: ReactNode;
};
@ -156,21 +160,43 @@ export const InsurancePoolInfoPanel = ({
);
};
export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
const { data: parentData } = useParentMarketIdQuery({
export const KeyDetailsInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const { data: parentMarketIdData } = useParentMarketIdQuery({
variables: {
marketId: market.id,
},
skip: !FLAGS.SUCCESSOR_MARKETS,
});
const { data: successor } = useSuccessorMarketProposalDetailsQuery({
const { data: successorProposalDetails } =
useSuccessorMarketProposalDetailsQuery({
variables: {
proposalId: market.proposal?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !market.proposal?.id,
});
// The following queries are needed as the parent market could also have been a successor market.
// Note: the parent market is only passed to this component if the successor markets flag is enabled,
// so that check is not needed in the skip.
const { data: grandparentMarketIdData } = useParentMarketIdQuery({
variables: {
proposalId: market.proposal?.id || '',
marketId: parentMarket?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !market.proposal?.id,
skip: !parentMarket?.id,
});
const { data: parentSuccessorProposalDetails } =
useSuccessorMarketProposalDetailsQuery({
variables: {
proposalId: parentMarket?.proposal?.id || '',
},
skip: !parentMarket?.proposal?.id,
});
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
@ -181,11 +207,12 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
? {
name: market.tradableInstrument.instrument.name,
marketID: market.id,
parentMarketID: parentData?.market?.parentMarketID || '-',
parentMarketID: parentMarketIdData?.market?.parentMarketID || '-',
insurancePoolFraction:
(successor?.proposal?.terms.change.__typename === 'NewMarket' &&
successor.proposal.terms.change.successorConfiguration
?.insurancePoolFraction) ||
(successorProposalDetails?.proposal?.terms.change.__typename ===
'NewMarket' &&
successorProposalDetails.proposal.terms.change
.successorConfiguration?.insurancePoolFraction) ||
'-',
tradingMode:
market.tradingMode &&
@ -205,6 +232,28 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
settlementAssetDecimalPlaces: assetDecimals,
}
}
parentData={
parentMarket && {
name: parentMarket?.tradableInstrument?.instrument?.name,
marketID: parentMarket?.id,
parentMarketID: grandparentMarketIdData?.market?.parentMarketID,
insurancePoolFraction:
parentSuccessorProposalDetails?.proposal?.terms.change
.__typename === 'NewMarket' &&
parentSuccessorProposalDetails.proposal.terms.change
.successorConfiguration?.insurancePoolFraction,
tradingMode:
parentMarket?.tradingMode &&
MarketTradingModeMapping[
parentMarket.tradingMode as MarketTradingMode
],
marketDecimalPlaces: parentMarket?.decimalPlaces,
positionDecimalPlaces: parentMarket?.positionDecimalPlaces,
settlementAssetDecimalPlaces:
parentMarket?.tradableInstrument?.instrument?.product
?.settlementAsset?.decimals,
}
}
/>
);
};
@ -329,7 +378,10 @@ export const SuccessionLineInfoPanel = ({
);
};
export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
export const InstrumentInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => (
<MarketInfoTable
data={{
marketName: market.tradableInstrument.instrument.name,
@ -337,6 +389,16 @@ export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
productType: market.tradableInstrument.instrument.product.__typename,
quoteName: market.tradableInstrument.instrument.product.quoteName,
}}
parentData={
parentMarket && {
marketName: parentMarket?.tradableInstrument?.instrument?.name,
code: parentMarket?.tradableInstrument?.instrument?.code,
productType:
parentMarket?.tradableInstrument?.instrument?.product?.__typename,
quoteName:
parentMarket?.tradableInstrument?.instrument?.product?.quoteName,
}
}
/>
);
@ -349,6 +411,7 @@ export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
);
const { data: asset } = useAssetDataProvider(assetId ?? '');
return asset ? (
<>
@ -371,54 +434,148 @@ export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
);
};
export const MetadataInfoPanel = ({ market }: MarketInfoProps) => (
const getMarketMetadata = (market: MarketInfo) =>
market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {});
export const MetadataInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => (
<MarketInfoTable
data={{
expiryDate: getMarketExpiryDateFormatted(
market.tradableInstrument.instrument.metadata.tags
),
...market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
...(getMarketMetadata(market) || {}),
}}
parentData={
parentMarket && {
expiryDate: getMarketExpiryDateFormatted(
parentMarket.tradableInstrument.instrument.metadata.tags
),
...(getMarketMetadata(parentMarket) || {}),
}
}
/>
);
export const RiskModelInfoPanel = ({ market }: MarketInfoProps) => {
export const RiskModelInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') {
return null;
}
const { tau, riskAversionParameter } = market.tradableInstrument.riskModel;
return <MarketInfoTable data={{ tau, riskAversionParameter }} unformatted />;
let parentData;
if (
parentMarket?.tradableInstrument?.riskModel?.__typename ===
'LogNormalRiskModel'
) {
const {
tau: parentTau,
riskAversionParameter: parentRiskAversionParameter,
} = market.tradableInstrument.riskModel;
parentData = {
tau: parentTau,
riskAversionParameter: parentRiskAversionParameter,
};
}
return (
<MarketInfoTable
data={{ tau, riskAversionParameter }}
parentData={parentData}
unformatted
/>
);
};
export const RiskParametersInfoPanel = ({ market }: MarketInfoProps) => {
if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') {
export const RiskParametersInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const marketType = market.tradableInstrument.riskModel.__typename;
let data, parentData;
if (marketType === 'LogNormalRiskModel') {
const { r, sigma, mu } = market.tradableInstrument.riskModel.params;
return <MarketInfoTable data={{ r, sigma, mu }} unformatted />;
}
if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') {
data = { r, sigma, mu };
if (
parentMarket?.tradableInstrument?.riskModel.__typename ===
'LogNormalRiskModel'
) {
const parentParams = parentMarket.tradableInstrument.riskModel.params;
parentData = {
r: parentParams.r,
sigma: parentParams.sigma,
mu: parentParams.mu,
};
}
} else if (marketType === 'SimpleRiskModel') {
const { factorLong, factorShort } =
market.tradableInstrument.riskModel.params;
return <MarketInfoTable data={{ factorLong, factorShort }} unformatted />;
data = { factorLong, factorShort };
if (
parentMarket?.tradableInstrument?.riskModel.__typename ===
'SimpleRiskModel'
) {
const parentParams = parentMarket.tradableInstrument.riskModel.params;
parentData = {
factorLong: parentParams.factorLong,
factorShort: parentParams.factorShort,
};
}
}
return null;
if (!data) return null;
return <MarketInfoTable data={data} parentData={parentData} unformatted />;
};
export const RiskFactorsInfoPanel = ({ market }: MarketInfoProps) => {
export const RiskFactorsInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
if (!market.riskFactors) {
return null;
}
const { short, long } = market.riskFactors;
return <MarketInfoTable data={{ short, long }} unformatted />;
let parentData;
if (parentMarket?.riskFactors) {
const parentShort = parentMarket.riskFactors.short;
const parentLong = parentMarket.riskFactors.long;
parentData = { short: parentShort, long: parentLong };
}
return (
<MarketInfoTable
data={{ short, long }}
parentData={parentData}
unformatted
/>
);
};
export const PriceMonitoringBoundsInfoPanel = ({
market,
triggerIndex,
parentMarket,
}: MarketInfoProps & {
triggerIndex: number;
}) => {
@ -426,11 +583,35 @@ export const PriceMonitoringBoundsInfoPanel = ({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const { data: parentData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: parentMarket?.id || '' },
skip:
!parentMarket ||
!parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[
triggerIndex
],
});
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const parentQuoteUnit =
parentMarket?.tradableInstrument.instrument.product?.quoteName || '';
const isParentQuoteUnitEqual = quoteUnit === parentQuoteUnit;
const trigger =
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const parentTrigger =
parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const isParentTriggerEqual = isEqual(trigger, parentTrigger);
const bounds = data?.priceMonitoringBounds?.[triggerIndex];
const parentBounds = parentData?.priceMonitoringBounds?.[triggerIndex];
const shouldShowParentData =
isParentQuoteUnitEqual && isParentTriggerEqual && !!parentBounds;
if (!trigger) {
console.error(
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
@ -457,6 +638,14 @@ export const PriceMonitoringBoundsInfoPanel = ({
highestPrice: bounds.maxValidPrice,
lowestPrice: bounds.minValidPrice,
}}
parentData={
shouldShowParentData
? {
highestPrice: parentBounds.maxValidPrice,
lowestPrice: parentBounds.minValidPrice,
}
: undefined
}
decimalPlaces={market.decimalPlaces}
assetSymbol={quoteUnit}
/>
@ -472,18 +661,31 @@ export const PriceMonitoringBoundsInfoPanel = ({
export const LiquidityMonitoringParametersInfoPanel = ({
market,
}: MarketInfoProps) => (
<MarketInfoTable
data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
timeWindow:
market.liquidityMonitoringParameters.targetStakeParameters.timeWindow,
scalingFactor:
market.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}}
/>
);
parentMarket,
}: MarketInfoProps) => {
const marketData = {
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
timeWindow:
market.liquidityMonitoringParameters.targetStakeParameters.timeWindow,
scalingFactor:
market.liquidityMonitoringParameters.targetStakeParameters.scalingFactor,
};
const parentMarketData = parentMarket
? {
triggeringRatio:
parentMarket.liquidityMonitoringParameters.triggeringRatio,
timeWindow:
parentMarket.liquidityMonitoringParameters.targetStakeParameters
.timeWindow,
scalingFactor:
parentMarket.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}
: {};
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
};
export const LiquidityInfoPanel = ({ market, children }: MarketInfoProps) => {
const assetDecimals =
@ -510,16 +712,61 @@ export const LiquidityInfoPanel = ({ market, children }: MarketInfoProps) => {
);
};
export const LiquidityPriceRangeInfoPanel = ({ market }: MarketInfoProps) => {
export const LiquidityPriceRangeInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const parentQuoteUnit =
parentMarket?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const parentLiquidityPriceRange = parentMarket
? formatNumberPercentage(
new BigNumber(parentMarket.lpPriceRange).times(100)
)
: null;
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const { data: parentMarketData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: parentMarket?.id || '' },
skip: !parentMarket,
});
let parentData;
if (parentMarket && parentMarketData && quoteUnit === parentQuoteUnit) {
parentData = {
liquidityPriceRange: `${parentLiquidityPriceRange} of mid price`,
lowestPrice:
parentMarketData?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(parentMarket.lpPriceRange)
.times(parentMarketData.midPrice)
.toString(),
parentMarket.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
parentMarketData?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(parentMarket.lpPriceRange)
.times(parentMarketData.midPrice)
.toString(),
parentMarket.decimalPlaces
)} ${quoteUnit}`,
};
}
return (
<>
<p className="text-sm mb-2">
@ -552,6 +799,7 @@ export const LiquidityPriceRangeInfoPanel = ({ market }: MarketInfoProps) => {
market.decimalPlaces
)} ${quoteUnit}`,
}}
parentData={parentData}
/>
</>
);
@ -560,8 +808,12 @@ export const LiquidityPriceRangeInfoPanel = ({ market }: MarketInfoProps) => {
export const OracleInfoPanel = ({
market,
type,
parentMarket,
}: MarketInfoProps & { type: 'settlementData' | 'termination' }) => {
// If this is a successor market, this component will only receive parent market
// data if the termination or settlement data is different from the parent.
const product = market.tradableInstrument.instrument.product;
const parentProduct = parentMarket?.tradableInstrument?.instrument?.product;
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
const { data } = useOracleProofs(ORACLE_PROOFS_URL);
@ -570,12 +822,33 @@ export const OracleInfoPanel = ({
? product.dataSourceSpecForSettlementData.id
: product.dataSourceSpecForTradingTermination.id;
const parentDataSourceSpecId =
type === 'settlementData'
? parentProduct?.dataSourceSpecForSettlementData?.id
: parentProduct?.dataSourceSpecForTradingTermination?.id;
const dataSourceSpec = (
type === 'settlementData'
? product.dataSourceSpecForSettlementData.data
: product.dataSourceSpecForTradingTermination.data
) as DataSourceDefinition;
const parentDataSourceSpec =
type === 'settlementData'
? parentProduct?.dataSourceSpecForSettlementData?.data
: (parentProduct?.dataSourceSpecForTradingTermination
?.data as DataSourceDefinition);
const isParentDataSourceSpecEqual =
parentDataSourceSpec !== undefined &&
dataSourceSpec === parentDataSourceSpec;
const isParentDataSourceSpecIdEqual =
parentDataSourceSpecId !== undefined &&
dataSourceSpecId === parentDataSourceSpecId;
// We'll only provide successor parent data (if it differs) to the
// DataSourceProof component. Having an old external link struck through
// is unlikely to be useful.
return (
<div className="flex flex-col gap-2">
<DataSourceProof
@ -584,7 +857,14 @@ export const OracleInfoPanel = ({
providers={data}
type={type}
dataSourceSpecId={dataSourceSpecId}
parentData={
isParentDataSourceSpecEqual ? undefined : parentDataSourceSpec
}
parentDataSourceSpecId={
isParentDataSourceSpecIdEqual ? undefined : parentDataSourceSpecId
}
/>
<ExternalLink
data-testid="oracle-spec-links"
href={`${VEGA_EXPLORER_URL}/oracles/${
@ -606,14 +886,28 @@ export const DataSourceProof = ({
providers,
type,
dataSourceSpecId,
parentData,
parentDataSourceSpecId,
}: {
data: DataSourceDefinition;
providers: Provider[] | undefined;
type: 'settlementData' | 'termination';
dataSourceSpecId: string;
parentData?: DataSourceDefinition;
parentDataSourceSpecId?: string;
}) => {
// If this is a successor market, we'll only pass parent data to child
// components for comparison if the data differs from the parent market.
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
const signers = data.sourceType.sourceType.signers || [];
let parentSigners: Signer[];
if (
parentData &&
parentData.sourceType.__typename === 'DataSourceDefinitionExternal'
) {
parentSigners = parentData.sourceType.sourceType?.signers || [];
}
if (!providers?.length) {
return <NoOracleProof type={type} />;
@ -622,7 +916,14 @@ export const DataSourceProof = ({
return (
<div className="flex flex-col gap-2">
{signers.map(({ signer }, i) => {
return (
const parentSigner = parentSigners?.find(
({ signer: ParentSigner }) =>
ParentSigner.__typename === signer.__typename
)?.signer;
const isParentSignerEqual = isEqual(signer, parentSigner);
return isParentSignerEqual ? (
<OracleLink
key={i}
providers={providers}
@ -630,6 +931,16 @@ export const DataSourceProof = ({
type={type}
dataSourceSpecId={dataSourceSpecId}
/>
) : (
<OracleLink
key={i}
providers={providers}
signer={signer}
type={type}
dataSourceSpecId={dataSourceSpecId}
parentSigner={parentSigner}
parentDataSourceSpecId={parentDataSourceSpecId}
/>
);
})}
</div>
@ -663,18 +974,8 @@ export const DataSourceProof = ({
return <div>{t('Invalid data source')}</div>;
};
const OracleLink = ({
providers,
signer,
type,
dataSourceSpecId,
}: {
providers: Provider[];
signer: SignerKind;
type: 'settlementData' | 'termination';
dataSourceSpecId: string;
}) => {
const signerProviders = providers.filter((p) => {
const getSignerProviders = (signer: SignerKind, providers: Provider[]) =>
providers.filter((p) => {
if (signer.__typename === 'PubKey') {
if (
p.oracle.type === 'public_key' &&
@ -696,19 +997,62 @@ const OracleLink = ({
return false;
});
const OracleLink = ({
providers,
signer,
type,
dataSourceSpecId,
parentSigner,
parentDataSourceSpecId,
}: {
providers: Provider[];
signer: SignerKind;
type: 'settlementData' | 'termination';
dataSourceSpecId: string;
parentSigner?: SignerKind;
parentDataSourceSpecId?: string;
}) => {
// If this is a successor market, the parent market data will only have been passed
// in if it differs from the current data.
const signerProviders = getSignerProviders(signer, providers);
const parentSignerProviders = parentSigner
? getSignerProviders(parentSigner, providers)
: [];
if (!signerProviders.length) {
return <NoOracleProof type={type} />;
}
return (
<div className="mt-2">
{signerProviders.map((provider) => (
<OracleProfile
key={dataSourceSpecId}
provider={provider}
dataSourceSpecId={dataSourceSpecId}
/>
))}
{signerProviders.map((provider) => {
// Making the assumption here that if the provider name is the same,
// that it is the same provider that the parent market used.
const parentProvider = parentSignerProviders.find(
(p) => p.name === provider.name
);
const isParentProviderEqual =
parentProvider !== undefined && isEqual(provider, parentProvider);
// We only want to pass the parent data to the child component if the
// data differs from the parent market.
return isParentProviderEqual ? (
<OracleProfile
key={dataSourceSpecId}
provider={provider}
dataSourceSpecId={dataSourceSpecId}
/>
) : (
<OracleProfile
key={dataSourceSpecId}
provider={provider}
dataSourceSpecId={dataSourceSpecId}
parentProvider={parentProvider}
parentDataSourceSpecId={parentDataSourceSpecId}
/>
);
})}
</div>
);
};
@ -731,13 +1075,18 @@ const NoOracleProof = ({
const OracleProfile = (props: {
provider: Provider;
dataSourceSpecId: string;
parentProvider?: Provider;
parentDataSourceSpecId?: string;
}) => {
// If this is a successor market, the parent market data will only have been passed
// in if it differs from the current data.
const [open, onChange] = useState(false);
return (
<div key={props.provider.name}>
<OracleBasicProfile
provider={props.provider}
onClick={() => onChange(!open)}
parentProvider={props.parentProvider}
/>
<OracleDialog {...props} open={open} onChange={onChange} />
</div>

View File

@ -5,6 +5,7 @@ import {
ExternalLink,
Icon,
Intent,
Lozenge,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
@ -59,10 +60,12 @@ export const OracleBasicProfile = ({
provider,
onClick,
markets: oracleMarkets,
parentProvider,
}: {
provider: Provider;
markets?: OracleMarketSpecFieldsFragment[] | undefined;
onClick?: (value?: boolean) => void;
parentProvider?: Provider;
}) => {
const { icon, message, intent } = getVerifiedStatusIcon(provider);
@ -78,8 +81,14 @@ export const OracleBasicProfile = ({
icon: getLinkIcon(proof.type),
}));
// If this is a successor market and there's a different parent provider,
// we'll just show that there's been a change, rather than add old data
// in alongside the new provider.
return (
<>
{parentProvider && (
<Lozenge variant={Intent.Primary}>{t('Updated')}</Lozenge>
)}
<span className="flex gap-1">
{provider.url && (
<span className="flex align-items-bottom text-md gap-1">

View File

@ -11,16 +11,27 @@ export const OracleDialog = ({
dataSourceSpecId,
open,
onChange,
parentProvider,
}: {
dataSourceSpecId: string;
provider: Provider;
open: boolean;
onChange?: (isOpen: boolean) => void;
parentProvider?: Provider;
}) => {
// If this is a successor market, the parent market data will only have been passed
// in if it differs from the current data. We'll pass this on to the title component
// to show a change, but the full profile showing changes is unwieldy - it's enough
// to know from the title that the oracle has changed.
const oracleMarkets = useOracleMarkets(provider);
return (
<Dialog
title={<OracleProfileTitle provider={provider} />}
title={
<OracleProfileTitle
provider={provider}
parentProvider={parentProvider}
/>
}
aria-labelledby="oracle-proof-dialog"
open={open}
onChange={onChange}

View File

@ -6,6 +6,7 @@ import {
ExternalLink,
Icon,
Intent,
Lozenge,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
@ -18,15 +19,30 @@ import type { OracleMarketSpecFieldsFragment } from '../../__generated__/OracleM
import ReactMarkdown from 'react-markdown';
import { useState } from 'react';
export const OracleProfileTitle = ({ provider }: { provider: Provider }) => {
export const OracleProfileTitle = ({
provider,
parentProvider,
}: {
provider: Provider;
parentProvider?: Provider;
}) => {
// If this is a successor market, the parent provider will only have been passed
// in if it differs from the current provider. If it is different, we'll just
// show the change in name, not icons and proofs.
const { icon, intent } = getVerifiedStatusIcon(provider);
const verifiedProofs = provider.proofs.filter(
(proof) => proof.available === true
);
return (
<span className="flex gap-1">
{parentProvider && (
<Lozenge variant={Intent.Primary}>{t('Updated')}</Lozenge>
)}
{provider.url && (
<span>
{parentProvider && parentProvider.name && (
<span className="line-through">{parentProvider.name}</span>
)}
<span className="pr-1">{provider.name}</span>
<span className="dark:text-vega-light-300 text-vega-dark-300">
({verifiedProofs.length})

View File

@ -22,7 +22,7 @@ export const getIntentBackground = (intent?: Intent) => {
return {
'bg-neutral-200 dark:bg-neutral-800': intent === undefined,
'bg-black dark:bg-white': intent === Intent.None,
'bg-vega-blue-300 dark:bg-vega-blue-700': intent === Intent.Primary,
'bg-vega-blue-300 dark:bg-vega-blue-650': intent === Intent.Primary,
'bg-danger': intent === Intent.Danger,
'bg-warning': intent === Intent.Warning,
// contrast issues with light mode