Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56303e6cb5 | ||
|
|
b137e024c6 | ||
|
|
6651ee52b4 | ||
|
|
0d14722c5d | ||
|
|
4968b537b8 | ||
|
|
5f5c45f3b9 | ||
|
|
6b10df015d | ||
|
|
6d7e24732b | ||
|
|
b33cf04e16 | ||
|
|
66ebb26b0f | ||
|
|
51c88d2fce | ||
|
|
2786a984b0 | ||
|
|
39c53a7326 | ||
|
|
3371e8e2cc | ||
|
|
5843a36851 | ||
|
|
68b4909dff | ||
|
|
da8e382454 | ||
|
|
81ebefc329 | ||
|
|
1ed7d6fda2 | ||
|
|
a2e4f34b0d |
@@ -208,7 +208,7 @@ jobs:
|
||||
name: '(CI) console python'
|
||||
uses: ./.github/workflows/console-test-run.yml
|
||||
secrets: inherit
|
||||
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }} && ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') }}
|
||||
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading')
|
||||
with:
|
||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import {
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
LiquiditySLAParametersInfoPanel,
|
||||
MarginScalingFactorsPanel,
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
getDataSourceSpecForSettlementData,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
OracleInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||
@@ -69,8 +69,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
<MetadataInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
|
||||
<MarginScalingFactorsPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel market={market} />
|
||||
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
|
||||
|
||||
@@ -22,7 +22,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_REFERRALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
|
||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -35,4 +35,6 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_GOVERNANCE_TRANSFERS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -35,3 +35,5 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -27,3 +27,5 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -26,3 +26,5 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -25,3 +25,5 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -23,4 +23,6 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_GOVERNANCE_TRANSFERS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -28,3 +28,5 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -24,3 +24,5 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -709,6 +709,8 @@
|
||||
"NewMarketProposal": "New market proposal",
|
||||
"UpdateMarketProposal": "Update market proposal",
|
||||
"UpdateMarketStateProposal": "Update market state proposal",
|
||||
"UpdateReferralProgramProposal": "Update referral program proposal",
|
||||
"UpdateVolumeDiscountProgramProposal": "Update volume discount program proposal",
|
||||
"MarketChange": "Market change",
|
||||
"MarketStateChange": "Market state change",
|
||||
"MarketDetails": "Market details",
|
||||
@@ -732,6 +734,8 @@
|
||||
"NewMarketSpotProduct": "New market - spot",
|
||||
"UpdateMarket": "Update market",
|
||||
"UpdateMarketState": "Update market state",
|
||||
"UpdateReferralProgram": "Update referral program",
|
||||
"UpdateVolumeDiscountProgram": "Update volume discount program",
|
||||
"NewAsset": "New asset",
|
||||
"UpdateAsset": "Update asset",
|
||||
"AssetID": "Asset ID",
|
||||
@@ -890,5 +894,25 @@
|
||||
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
|
||||
"HowToProposeRawStep3": "3. Submit on-chain below",
|
||||
"proposalTransferDetails": "New governance transfer details",
|
||||
"proposalCancelTransferDetails": "Cancel governance transfer details"
|
||||
"proposalCancelTransferDetails": "Cancel governance transfer details",
|
||||
"BenefitTiers": "Benefit tiers",
|
||||
"BenefitTierMinimumEpochs": "Minimum epochs",
|
||||
"BenefitTierMinimumEpochsDescription": "The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit",
|
||||
"BenefitTierMinimumRunningNotionalTakerVolume": "Minimum running notional taker volume",
|
||||
"BenefitTierMinimumRunningNotionalTakerVolumeDescription": "The minimum running notional for the given benefit tier",
|
||||
"BenefitTierReferralDiscountFactor": "Referral discount factor",
|
||||
"BenefitTierReferralDiscountFactorDescription": "The proportion of the referee's taker fees to be discounted",
|
||||
"BenefitTierReferralRewardFactor": "Referral reward factor",
|
||||
"BenefitTierReferralRewardFactorDescription": "The proportion of the referee's taker fees to be rewarded to the referrer",
|
||||
"StakingTiers": "Staking tiers",
|
||||
"StakingTierMinimumStakedTokens": "Minimum staked tokens",
|
||||
"StakingTierMinimumStakedTokensDescription": "Required number of governance tokens ($VEGA) a referrer must have staked to receive the multiplier",
|
||||
"StakingTierReferralRewardMultiplier": "Referral reward multiplier",
|
||||
"StakingTierReferralRewardMultiplierDescription": "Multiplier applied to the referral reward factor when calculating referral rewards due to the referrer",
|
||||
"WindowLength": "Window length",
|
||||
"WindowLengthDescription": "Number of epochs over which to evaluate a referral set's running volume",
|
||||
"EndOfProgramTimestamp": "End of program",
|
||||
"EndOfProgramTimestampDescription": "Time after which when the current epoch ends, the programs will end and benefits will be disabled.",
|
||||
"BenefitTierVolumeDiscountFactor": "Volume discount factor",
|
||||
"BenefitTierVolumeDiscountFactorDescription": "Discount given to those in this benefit tier"
|
||||
}
|
||||
|
||||
@@ -188,6 +188,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+10
@@ -98,6 +98,16 @@ export const ProposalHeader = ({
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
proposalType = 'UpdateReferralProgram';
|
||||
fallbackTitle = t('UpdateReferralProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
proposalType = 'UpdateVolumeDiscountProgram';
|
||||
fallbackTitle = t('UpdateVolumeDiscountProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'NewAsset': {
|
||||
proposalType = 'NewAsset';
|
||||
fallbackTitle = t('NewAssetProposal');
|
||||
|
||||
+5
-3
@@ -12,12 +12,12 @@ import {
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
getDataSourceSpecForSettlementSchedule,
|
||||
getDataSourceSpecForSettlementData,
|
||||
getDataSourceSpecForTradingTermination,
|
||||
getSigners,
|
||||
MarginScalingFactorsPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Button,
|
||||
@@ -219,8 +219,10 @@ export const ProposalMarketData = ({
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Margin scaling factors')}
|
||||
</h2>
|
||||
<MarginScalingFactorsPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './proposal-referral-program-details';
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {
|
||||
formatMinimumRunningNotionalTakerVolume,
|
||||
formatReferralDiscountFactor,
|
||||
formatReferralRewardFactor,
|
||||
formatMinimumStakedTokens,
|
||||
formatReferralRewardMultiplier,
|
||||
ProposalReferralProgramDetails,
|
||||
} from './proposal-referral-program-details';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
appState: {
|
||||
decimals: 2,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('ProposalReferralProgramDetails helper functions', () => {
|
||||
it('should format minimum running notional taker volume correctly', () => {
|
||||
const input = '1000';
|
||||
const formatted = formatMinimumRunningNotionalTakerVolume(input);
|
||||
expect(formatted).toBe('1,000');
|
||||
});
|
||||
|
||||
it('should format referral discount factor correctly', () => {
|
||||
const input = '0.05';
|
||||
const formatted = formatReferralDiscountFactor(input);
|
||||
expect(formatted).toBe('5.00%');
|
||||
});
|
||||
|
||||
it('should format referral reward factor correctly', () => {
|
||||
const input = '0.1';
|
||||
const formatted = formatReferralRewardFactor(input);
|
||||
expect(formatted).toBe('10.00%');
|
||||
});
|
||||
|
||||
it('should format minimum staked tokens correctly', () => {
|
||||
const input = '15';
|
||||
const decimals = 18;
|
||||
const formatted = formatMinimumStakedTokens(input, decimals);
|
||||
expect(formatted).toBe('0.000000000000000015');
|
||||
});
|
||||
|
||||
it('should format referral reward multiplier correctly', () => {
|
||||
const input = '3';
|
||||
const formatted = formatReferralRewardMultiplier(input);
|
||||
expect(formatted).toBe('3x');
|
||||
});
|
||||
});
|
||||
|
||||
const mockReferralProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateReferralProgram',
|
||||
changes: {
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumEpochs: 6,
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
referralDiscountFactor: '0.001',
|
||||
referralRewardFactor: '0.001',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 24,
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
referralDiscountFactor: '0.005',
|
||||
referralRewardFactor: '0.005',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 48,
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '2026-10-03T10:34:34Z',
|
||||
windowLength: 3,
|
||||
stakingTiers: [
|
||||
{
|
||||
minimumStakedTokens: '1',
|
||||
referralRewardMultiplier: '1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '2',
|
||||
referralRewardMultiplier: '2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '5',
|
||||
referralRewardMultiplier: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('<ProposalReferralProgramDetails />', () => {
|
||||
it('should not render if proposal is null', () => {
|
||||
render(<ProposalReferralProgramDetails proposal={null} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if __typename is not UpdateReferralProgram', () => {
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
render(<ProposalReferralProgramDetails proposal={updateMarketProposal} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are no relevant fields', () => {
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateReferralProgram',
|
||||
changes: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<ProposalReferralProgramDetails proposal={incompleteProposal} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should render relevant fields if present', () => {
|
||||
render(<ProposalReferralProgramDetails proposal={mockReferralProposal} />);
|
||||
expect(
|
||||
screen.getByTestId('proposal-referral-program-window-length')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('proposal-referral-program-end-of-program-timestamp')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('proposal-referral-program-benefit-tiers')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('proposal-referral-program-benefit-tiers')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
RoundedWrapper,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber } from '../../../../lib/format-number';
|
||||
import {
|
||||
formatDateWithLocalTimezone,
|
||||
formatNumberPercentage,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
}
|
||||
|
||||
export const formatEndOfProgramTimestamp = (value: string) => {
|
||||
return formatDateWithLocalTimezone(new Date(value));
|
||||
};
|
||||
|
||||
export const formatMinimumRunningNotionalTakerVolume = (value: string) => {
|
||||
return formatNumber(toBigNum(value, 0), 0);
|
||||
};
|
||||
|
||||
export const formatReferralDiscountFactor = (value: string) => {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
};
|
||||
|
||||
export const formatReferralRewardFactor = (value: string) => {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
};
|
||||
|
||||
export const formatMinimumStakedTokens = (value: string, decimals: number) => {
|
||||
return formatNumber(toBigNum(value, decimals));
|
||||
};
|
||||
|
||||
export const formatReferralRewardMultiplier = (value: string) => {
|
||||
return `${value}x`;
|
||||
};
|
||||
|
||||
export const ProposalReferralProgramDetails = ({
|
||||
proposal,
|
||||
}: ProposalReferralProgramDetailsProps) => {
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
const { t } = useTranslation();
|
||||
if (proposal?.terms?.change?.__typename !== 'UpdateReferralProgram') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiers = proposal?.terms?.change?.changes?.benefitTiers;
|
||||
const stakingTiers = proposal?.terms?.change?.changes?.stakingTiers;
|
||||
const windowLength = proposal?.terms?.change?.changes?.windowLength;
|
||||
const endOfProgramTimestamp =
|
||||
proposal?.terms?.change?.changes?.endOfProgramTimestamp;
|
||||
|
||||
if (
|
||||
!benefitTiers &&
|
||||
!stakingTiers &&
|
||||
!windowLength &&
|
||||
!endOfProgramTimestamp
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="proposal-referral-program-details">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
{windowLength && (
|
||||
<div data-testid="proposal-referral-program-window-length">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<Tooltip description={t('WindowLengthDescription')}>
|
||||
<span>{t('WindowLength')}</span>
|
||||
</Tooltip>
|
||||
{windowLength}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{endOfProgramTimestamp && (
|
||||
<div
|
||||
className="mb-6"
|
||||
data-testid="proposal-referral-program-end-of-program-timestamp"
|
||||
>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<Tooltip description={t('EndOfProgramTimestampDescription')}>
|
||||
<span>{t('EndOfProgramTimestamp')}</span>
|
||||
</Tooltip>
|
||||
{formatEndOfProgramTimestamp(endOfProgramTimestamp)}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{benefitTiers && (
|
||||
<div
|
||||
className="mb-6"
|
||||
data-testid="proposal-referral-program-benefit-tiers"
|
||||
>
|
||||
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||
{t('BenefitTiers')}
|
||||
</h3>
|
||||
<KeyValueTable>
|
||||
{benefitTiers
|
||||
.sort((a, b) => a.minimumEpochs - b.minimumEpochs)
|
||||
.map((benefitTier, index) => (
|
||||
<div className="mb-4" key={index}>
|
||||
<h4 className="font-semibold uppercase">
|
||||
Tier {index + 1}
|
||||
</h4>
|
||||
{benefitTier.minimumEpochs && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t('BenefitTierMinimumEpochsDescription')}
|
||||
>
|
||||
<span>{t('BenefitTierMinimumEpochs')}</span>
|
||||
</Tooltip>
|
||||
{benefitTier.minimumEpochs}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{benefitTier.minimumRunningNotionalTakerVolume && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'BenefitTierMinimumRunningNotionalTakerVolumeDescription'
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{t('BenefitTierMinimumRunningNotionalTakerVolume')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{formatMinimumRunningNotionalTakerVolume(
|
||||
benefitTier.minimumRunningNotionalTakerVolume
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{benefitTier.referralDiscountFactor && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'BenefitTierReferralDiscountFactorDescription'
|
||||
)}
|
||||
>
|
||||
<span>{t('BenefitTierReferralDiscountFactor')}</span>
|
||||
</Tooltip>
|
||||
{formatReferralDiscountFactor(
|
||||
benefitTier.referralDiscountFactor
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{benefitTier.referralRewardFactor && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'BenefitTierReferralRewardFactorDescription'
|
||||
)}
|
||||
>
|
||||
<span>{t('BenefitTierReferralRewardFactor')}</span>
|
||||
</Tooltip>
|
||||
{formatReferralRewardFactor(
|
||||
benefitTier.referralRewardFactor
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{stakingTiers && (
|
||||
<div data-testid="proposal-referral-program-staking-tiers">
|
||||
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||
{t('StakingTiers')}
|
||||
</h3>
|
||||
<KeyValueTable>
|
||||
{stakingTiers
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(a.minimumStakedTokens) -
|
||||
Number(b.minimumStakedTokens)
|
||||
)
|
||||
.map((stakingTier, index) => (
|
||||
<div className="mb-4" key={index}>
|
||||
{stakingTier.referralRewardMultiplier && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'StakingTierReferralRewardMultiplierDescription'
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{t('StakingTierReferralRewardMultiplier')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{formatReferralRewardMultiplier(
|
||||
stakingTier.referralRewardMultiplier
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{stakingTier.minimumStakedTokens && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'StakingTierMinimumStakedTokensFactorDescription'
|
||||
)}
|
||||
>
|
||||
<span>{t('StakingTierMinimumStakedTokens')}</span>
|
||||
</Tooltip>
|
||||
{formatMinimumStakedTokens(
|
||||
stakingTier.minimumStakedTokens,
|
||||
decimals
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './proposal-volume-discount-program-details';
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposalVolumeDiscountProgramDetails } from './proposal-volume-discount-program-details';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
appState: {
|
||||
decimals: 2,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockReferralProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateVolumeDiscountProgram',
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
volumeDiscountFactor: '0.05',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '50000',
|
||||
volumeDiscountFactor: '0.1',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '100000',
|
||||
volumeDiscountFactor: '0.15',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '250000',
|
||||
volumeDiscountFactor: '0.2',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
volumeDiscountFactor: '0.25',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
volumeDiscountFactor: '0.3',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1500000',
|
||||
volumeDiscountFactor: '0.35',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '2000000',
|
||||
volumeDiscountFactor: '0.4',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '1970-01-01T00:00:01.791568493Z',
|
||||
windowLength: 7,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('ProposalVolumeDiscountProgramDetails', () => {
|
||||
it('should not render if proposal is null', () => {
|
||||
render(<ProposalVolumeDiscountProgramDetails proposal={null} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if __typename is not UpdateVolumeDiscountProgram', () => {
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={updateMarketProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are no relevant fields', () => {
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateVolumeDiscountProgram',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={incompleteProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should render relevant fields if present', () => {
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={mockReferralProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('proposal-volume-discount-program-window-length')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(
|
||||
'proposal-volume-discount-program-end-of-program-timestamp'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('proposal-volume-discount-program-benefit-tiers')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
RoundedWrapper,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
formatEndOfProgramTimestamp,
|
||||
formatMinimumRunningNotionalTakerVolume,
|
||||
} from '../proposal-referral-program-details';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
}
|
||||
|
||||
export const formatVolumeDiscountFactor = (value: string) => {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
};
|
||||
|
||||
export const ProposalVolumeDiscountProgramDetails = ({
|
||||
proposal,
|
||||
}: ProposalReferralProgramDetailsProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (proposal?.terms?.change?.__typename !== 'UpdateVolumeDiscountProgram') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||
const windowLength = proposal?.terms?.change?.windowLength;
|
||||
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgramTimestamp;
|
||||
|
||||
if (!benefitTiers && !windowLength && !endOfProgramTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="proposal-volume-discount-program-details">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
{windowLength && (
|
||||
<div data-testid="proposal-volume-discount-program-window-length">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<Tooltip description={t('WindowLengthDescription')}>
|
||||
<span>{t('WindowLength')}</span>
|
||||
</Tooltip>
|
||||
{windowLength}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{endOfProgramTimestamp && (
|
||||
<div
|
||||
className="mb-6"
|
||||
data-testid="proposal-volume-discount-program-end-of-program-timestamp"
|
||||
>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<Tooltip description={t('EndOfProgramTimestampDescription')}>
|
||||
<span>{t('EndOfProgramTimestamp')}</span>
|
||||
</Tooltip>
|
||||
{formatEndOfProgramTimestamp(endOfProgramTimestamp)}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{benefitTiers && (
|
||||
<div
|
||||
className="mb-6"
|
||||
data-testid="proposal-volume-discount-program-benefit-tiers"
|
||||
>
|
||||
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||
{t('BenefitTiers')}
|
||||
</h3>
|
||||
<KeyValueTable>
|
||||
{benefitTiers
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(a.minimumRunningNotionalTakerVolume) -
|
||||
Number(b.minimumRunningNotionalTakerVolume)
|
||||
)
|
||||
.map((benefitTier, index) => (
|
||||
<div className="mb-4" key={index}>
|
||||
<h4 className="font-semibold uppercase">
|
||||
Tier {index + 1}
|
||||
</h4>
|
||||
{benefitTier.minimumRunningNotionalTakerVolume && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'BenefitTierMinimumRunningNotionalTakerVolumeDescription'
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{t('BenefitTierMinimumRunningNotionalTakerVolume')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{formatMinimumRunningNotionalTakerVolume(
|
||||
benefitTier.minimumRunningNotionalTakerVolume
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{benefitTier.volumeDiscountFactor && (
|
||||
<KeyValueTableRow>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'BenefitTierVolumeDiscountFactorDescription'
|
||||
)}
|
||||
>
|
||||
<span>{t('BenefitTierVolumeDiscountFactor')}</span>
|
||||
</Tooltip>
|
||||
{formatVolumeDiscountFactor(
|
||||
benefitTier.volumeDiscountFactor
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
)}
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,8 @@ import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||
import { ProposalReferralProgramDetails } from '../proposal-referral-program-details';
|
||||
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||
import { UserVote } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
@@ -113,6 +115,15 @@ export const Proposal = ({
|
||||
// TODO: check minVoterBalance for 'CancelTransfer'
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateReferralProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_referralProgram_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateVolumeDiscountProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +231,18 @@ export const Proposal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateReferralProgram' && (
|
||||
<div className="mb-4">
|
||||
<ProposalReferralProgramDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateVolumeDiscountProgram' && (
|
||||
<div className="mb-4">
|
||||
<ProposalVolumeDiscountProgramDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{governanceTransferDetails}
|
||||
|
||||
<div className="mb-10">
|
||||
|
||||
@@ -19,6 +19,8 @@ export const useProposalNetworkParams = ({
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_market_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_asset_requiredParticipation,
|
||||
@@ -26,6 +28,8 @@ export const useProposalNetworkParams = ({
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||
]);
|
||||
|
||||
const fallback = {
|
||||
@@ -91,6 +95,22 @@ export const useProposalNetworkParams = ({
|
||||
params.governance_proposal_freeform_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'UpdateReferralProgram':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_referralProgram_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_referralProgram_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'UpdateVolumeDiscountProgram':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredParticipation
|
||||
),
|
||||
};
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -43,10 +43,50 @@ fragment UpdateMarketState on Proposal {
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateReferralProgram on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateReferralProgram {
|
||||
changes {
|
||||
benefitTiers {
|
||||
minimumEpochs
|
||||
minimumRunningNotionalTakerVolume
|
||||
referralDiscountFactor
|
||||
referralRewardFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
stakingTiers {
|
||||
minimumStakedTokens
|
||||
referralRewardMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateVolumeDiscountProgram on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateVolumeDiscountProgram {
|
||||
benefitTiers {
|
||||
minimumRunningNotionalTakerVolume
|
||||
volumeDiscountFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query Proposal(
|
||||
$proposalId: ID!
|
||||
$includeNewMarketProductField: Boolean!
|
||||
$includeUpdateMarketState: Boolean!
|
||||
$includeUpdateReferralProgram: Boolean!
|
||||
$includeUpdateVolumeDiscountProgram: Boolean!
|
||||
) {
|
||||
proposal(id: $proposalId) {
|
||||
id
|
||||
@@ -64,6 +104,9 @@ query Proposal(
|
||||
errorDetails
|
||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||
...UpdateVolumeDiscountProgram
|
||||
@include(if: $includeUpdateVolumeDiscountProgram)
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -36,6 +36,8 @@ export const ProposalContainer = () => {
|
||||
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
|
||||
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
|
||||
NetworkParams.governance_proposal_freeform_minVoterBalance,
|
||||
NetworkParams.governance_proposal_referralProgram_minVoterBalance,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance,
|
||||
NetworkParams.spam_protection_voting_min_tokens,
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
@@ -44,6 +46,8 @@ export const ProposalContainer = () => {
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
]);
|
||||
|
||||
const {
|
||||
@@ -57,6 +61,8 @@ export const ProposalContainer = () => {
|
||||
proposalId: params.proposalId || '',
|
||||
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountProgram: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
skip: !params.proposalId,
|
||||
});
|
||||
|
||||
@@ -43,6 +43,44 @@ fragment UpdateMarketStates on Proposal {
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateReferralPrograms on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateReferralProgram {
|
||||
changes {
|
||||
benefitTiers {
|
||||
minimumEpochs
|
||||
minimumRunningNotionalTakerVolume
|
||||
referralDiscountFactor
|
||||
referralRewardFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
stakingTiers {
|
||||
minimumStakedTokens
|
||||
referralRewardMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateVolumeDiscountPrograms on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateVolumeDiscountProgram {
|
||||
benefitTiers {
|
||||
minimumRunningNotionalTakerVolume
|
||||
volumeDiscountFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ProposalFields on Proposal {
|
||||
id
|
||||
rationale {
|
||||
@@ -127,6 +165,8 @@ fragment ProposalFields on Proposal {
|
||||
query Proposals(
|
||||
$includeNewMarketProductFields: Boolean!
|
||||
$includeUpdateMarketStates: Boolean!
|
||||
$includeUpdateReferralPrograms: Boolean!
|
||||
$includeUpdateVolumeDiscountPrograms: Boolean!
|
||||
) {
|
||||
proposalsConnection {
|
||||
edges {
|
||||
@@ -134,6 +174,9 @@ query Proposals(
|
||||
...ProposalFields
|
||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
||||
...UpdateReferralPrograms @include(if: $includeUpdateReferralPrograms)
|
||||
...UpdateVolumeDiscountPrograms
|
||||
@include(if: $includeUpdateVolumeDiscountPrograms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-3
@@ -7,15 +7,21 @@ export type NewMarketProductFieldsFragment = { __typename?: 'Proposal', terms: {
|
||||
|
||||
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||
|
||||
export type UpdateReferralProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram', changes: { __typename?: 'ReferralProgram', endOfProgramTimestamp: string, windowLength: number, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||
|
||||
export type UpdateVolumeDiscountProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } } };
|
||||
|
||||
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
|
||||
|
||||
export type ProposalsQueryVariables = Types.Exact<{
|
||||
includeNewMarketProductFields: Types.Scalars['Boolean'];
|
||||
includeUpdateMarketStates: Types.Scalars['Boolean'];
|
||||
includeUpdateReferralPrograms: Types.Scalars['Boolean'];
|
||||
includeUpdateVolumeDiscountPrograms: Types.Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
||||
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', changes: { __typename?: 'ReferralProgram', endOfProgramTimestamp: string, windowLength: number, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
||||
|
||||
export const NewMarketProductFieldsFragmentDoc = gql`
|
||||
fragment NewMarketProductFields on Proposal {
|
||||
@@ -64,6 +70,46 @@ export const UpdateMarketStatesFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const UpdateReferralProgramsFragmentDoc = gql`
|
||||
fragment UpdateReferralPrograms on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateReferralProgram {
|
||||
changes {
|
||||
benefitTiers {
|
||||
minimumEpochs
|
||||
minimumRunningNotionalTakerVolume
|
||||
referralDiscountFactor
|
||||
referralRewardFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
stakingTiers {
|
||||
minimumStakedTokens
|
||||
referralRewardMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const UpdateVolumeDiscountProgramsFragmentDoc = gql`
|
||||
fragment UpdateVolumeDiscountPrograms on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateVolumeDiscountProgram {
|
||||
benefitTiers {
|
||||
minimumRunningNotionalTakerVolume
|
||||
volumeDiscountFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ProposalFieldsFragmentDoc = gql`
|
||||
fragment ProposalFields on Proposal {
|
||||
id
|
||||
@@ -147,20 +193,24 @@ export const ProposalFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const ProposalsDocument = gql`
|
||||
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!) {
|
||||
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!, $includeUpdateReferralPrograms: Boolean!, $includeUpdateVolumeDiscountPrograms: Boolean!) {
|
||||
proposalsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ProposalFields
|
||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
||||
...UpdateReferralPrograms @include(if: $includeUpdateReferralPrograms)
|
||||
...UpdateVolumeDiscountPrograms @include(if: $includeUpdateVolumeDiscountPrograms)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ProposalFieldsFragmentDoc}
|
||||
${NewMarketProductFieldsFragmentDoc}
|
||||
${UpdateMarketStatesFragmentDoc}`;
|
||||
${UpdateMarketStatesFragmentDoc}
|
||||
${UpdateReferralProgramsFragmentDoc}
|
||||
${UpdateVolumeDiscountProgramsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useProposalsQuery__
|
||||
@@ -176,6 +226,8 @@ ${UpdateMarketStatesFragmentDoc}`;
|
||||
* variables: {
|
||||
* includeNewMarketProductFields: // value for 'includeNewMarketProductFields'
|
||||
* includeUpdateMarketStates: // value for 'includeUpdateMarketStates'
|
||||
* includeUpdateReferralPrograms: // value for 'includeUpdateReferralPrograms'
|
||||
* includeUpdateVolumeDiscountPrograms: // value for 'includeUpdateVolumeDiscountPrograms'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,8 @@ export const ProposalsContainer = () => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ export const RejectedProposalsContainer = () => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('Market trading page', () => {
|
||||
// 6002-MDET-003
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketPrice).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Price');
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Mark Price');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('oracle information', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
undefined,
|
||||
'COMPROMISED'
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
});
|
||||
@@ -1,127 +0,0 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
|
||||
const dialogClose = 'dialog-close';
|
||||
|
||||
describe('accounts', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
});
|
||||
|
||||
it('should open usage breakdown dialog when clicked on used', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
// 7001-COLL-009
|
||||
cy.get('[col-id="used"]').contains('1.01').click();
|
||||
const headers = ['Market', 'Account type', 'Balance', 'Margin health'];
|
||||
cy.getByTestId('usage-breakdown').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
cy.getByTestId(dialogClose).click();
|
||||
});
|
||||
|
||||
describe('sorting by ag-grid columns should work well', () => {
|
||||
before(() => {
|
||||
const dialogs = Cypress.$('[data-testid="dialog-close"]:visible');
|
||||
if (dialogs.length > 0) {
|
||||
dialogs.each((btn) => {
|
||||
cy.wrap(btn).click();
|
||||
});
|
||||
}
|
||||
cy.contains('Loading...').should('not.exist');
|
||||
});
|
||||
// 7001-COLL-010
|
||||
it('sorting by asset', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = ['tBTC', 'tEURO', 'tDAI', 'tBTC'];
|
||||
const marketsSortedAsc = ['tBTC', 'tBTC', 'tDAI', 'tEURO'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
checkSorting(
|
||||
'asset.symbol',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
|
||||
it('sorting by total', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'total',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
|
||||
it('sorting by used', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
// concat actual value with percentage value
|
||||
// as cypress will pick up the entire cell contes
|
||||
// textContent
|
||||
const marketsSortedDefault = [
|
||||
'0.00' + '0.00%',
|
||||
'0.01' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.01' + '0.00%',
|
||||
];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
checkSorting(
|
||||
'used',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
|
||||
it('sorting by available', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00002',
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'available',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,186 +0,0 @@
|
||||
import {
|
||||
TIFlist,
|
||||
orderTIFDropDown,
|
||||
toggleLimit,
|
||||
toggleMarket,
|
||||
} from '../support/deal-ticket';
|
||||
|
||||
const tooltipContent = 'tooltip-content';
|
||||
const reduceOnly = 'reduce-only';
|
||||
const postOnly = 'post-only';
|
||||
|
||||
describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.clearAllLocalStorage();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('must have market order set up to IOC by default', function () {
|
||||
// 7002-SORD-030
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
TIFlist.filter((item) => item.code === 'IOC')[0].text
|
||||
);
|
||||
});
|
||||
|
||||
it('must have time in force set to GTC for limit order', function () {
|
||||
// 7002-SORD-031
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
TIFlist.filter((item) => item.code === 'GTC')[0].text
|
||||
);
|
||||
});
|
||||
|
||||
it('selections should be remembered', () => {
|
||||
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
TIFlist.filter((item) => item.code === 'IOC')[0].text
|
||||
);
|
||||
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
TIFlist.filter((item) => item.code === 'GTT')[0].text
|
||||
);
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
TIFlist.filter((item) => item.code === 'FOK')[0].text
|
||||
);
|
||||
});
|
||||
|
||||
describe('limit order', () => {
|
||||
before(() => {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
});
|
||||
const validTIF = TIFlist;
|
||||
validTIF.forEach((tif) => {
|
||||
// 7002-SORD-023
|
||||
// 7002-SORD-024
|
||||
// 7002-SORD-025
|
||||
// 7002-SORD-026
|
||||
// 7002-SORD-027
|
||||
// 7002-SORD-028
|
||||
|
||||
it(`must be able to select ${tif.code}`, function () {
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('market order', () => {
|
||||
before(() => {
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
});
|
||||
|
||||
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
|
||||
const invalidTIF = TIFlist.filter(
|
||||
(tif) => !['FOK', 'IOC'].includes(tif.code)
|
||||
);
|
||||
|
||||
validTIF.forEach((tif) => {
|
||||
// 7002-SORD-025
|
||||
// 7002-SORD-026
|
||||
|
||||
it(`must be able to select ${tif.code}`, function () {
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
invalidTIF.forEach((tif) => {
|
||||
// 7002-SORD-023
|
||||
// 7002-SORD-024
|
||||
// 7002-SORD-027
|
||||
// 7002-SORD-028
|
||||
it(`must not be able to select ${tif.code}`, function () {
|
||||
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('post and reduce - market order', () => {
|
||||
before(() => {
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
});
|
||||
|
||||
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
|
||||
|
||||
validTIF.forEach((tif) => {
|
||||
// 7002-SORD-025
|
||||
// 7002-SORD-026
|
||||
|
||||
it(`post and reduce order market for ${tif.code}`, function () {
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-056
|
||||
// 7003-SORD-057
|
||||
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
cy.getByTestId(postOnly).should('be.disabled');
|
||||
cy.getByTestId(reduceOnly).should('be.enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('post and reduce - limit order', () => {
|
||||
before(() => {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
});
|
||||
|
||||
const validTIFLimit = TIFlist.filter((tif) =>
|
||||
['GFA', 'GFN', 'GTC', 'GTT'].includes(tif.code)
|
||||
);
|
||||
|
||||
validTIFLimit.forEach((tif) => {
|
||||
it(`post and reduce order for limit ${tif.code}`, function () {
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-056
|
||||
// 7003-SORD-057
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
cy.getByTestId(postOnly).should('be.enabled');
|
||||
cy.getByTestId(reduceOnly).should('be.disabled');
|
||||
});
|
||||
});
|
||||
it(`can see explanation of what post only and reduce only is/does`, function () {
|
||||
// 7003-SORD-058
|
||||
cy.get('[for="post-only"]').should('have.text', 'Post only').realHover();
|
||||
cy.getByTestId(tooltipContent).should(
|
||||
'contain.text',
|
||||
`"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.`
|
||||
);
|
||||
cy.get('[for="reduce-only"]')
|
||||
.should('have.text', 'Reduce only')
|
||||
.realHover();
|
||||
cy.getByTestId(tooltipContent).should(
|
||||
'contain.text',
|
||||
`"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { partyAssetsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
describe('Portfolio page', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'PartyAssets', partyAssetsQuery());
|
||||
});
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
describe('Ledger entries', () => {
|
||||
it('Download form should be properly rendered', () => {
|
||||
// 7007-LEEN-001
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('"Ledger entries"').click();
|
||||
cy.getByTestId('tab-ledger-entries').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.getByTestId('ledger-download-button')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
const colHeader = '.ag-header-cell-text';
|
||||
const colIdPrice = '[col-id=price]';
|
||||
const colIdSize = '[col-id=size]';
|
||||
const colIdCreatedAt = '[col-id=createdAt]';
|
||||
const tradesTab = 'Trades';
|
||||
const tradesTable = 'tab-trades';
|
||||
|
||||
describe('trades', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.intercept('POST', '/graphql', (req) => {
|
||||
if (req.body.operationName === 'Trades') {
|
||||
req.alias = '@Trades';
|
||||
}
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(tradesTab).click();
|
||||
cy.wait('@Trades');
|
||||
});
|
||||
|
||||
it('show trades', () => {
|
||||
// 6005-THIS-001
|
||||
// 6005-THIS-002
|
||||
cy.getByTestId(tradesTab).should('be.visible');
|
||||
cy.getByTestId(tradesTable).should('be.visible');
|
||||
cy.getByTestId(tradesTable).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('show trades prices', () => {
|
||||
// 6005-THIS-003
|
||||
cy.getByTestId(tradesTable)
|
||||
.get(`${colIdPrice} ${colHeader}`)
|
||||
.first()
|
||||
.should('have.text', 'Price');
|
||||
cy.getByTestId(tradesTable)
|
||||
.get(colIdPrice)
|
||||
.each(($tradePrice) => {
|
||||
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('show trades sizes', () => {
|
||||
// 6005-THIS-004
|
||||
cy.getByTestId(tradesTable)
|
||||
.get(`${colIdSize} ${colHeader}`)
|
||||
.first()
|
||||
.should('have.text', 'Size');
|
||||
cy.getByTestId(tradesTable)
|
||||
.get(colIdSize)
|
||||
.each(($tradeSize) => {
|
||||
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('show trades date and time', () => {
|
||||
// 6005-THIS-005
|
||||
cy.getByTestId(tradesTable) // order table shares identical col id
|
||||
.find(`${colIdCreatedAt} ${colHeader}`)
|
||||
.should('have.text', 'Created at');
|
||||
const dateTimeRegex = /(\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
cy.getByTestId(tradesTable)
|
||||
.get(`.ag-center-cols-container ${colIdCreatedAt}`)
|
||||
.each(($tradeDateTime) => {
|
||||
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
it('trades are sorted descending by datetime', () => {
|
||||
// 6005-THIS-006
|
||||
const dateTimes: Date[] = [];
|
||||
cy.getByTestId(tradesTable)
|
||||
.find(colIdCreatedAt)
|
||||
.each(($tradeDateTime, index) => {
|
||||
if (index != 0) {
|
||||
//ignore header
|
||||
dateTimes.push(new Date($tradeDateTime.text()));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
expect(dateTimes).to.deep.equal(
|
||||
dateTimes.sort((a, b) => b.getTime() - a.getTime())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('copy price to deal ticket form', () => {
|
||||
// 6005-THIS-007
|
||||
cy.getByTestId('order-type-Limit').click();
|
||||
cy.get(colIdPrice).last().should('be.visible').click();
|
||||
cy.getByTestId('order-price').should('have.value', '171.16898');
|
||||
});
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const amountField = 'input[name="amount"]';
|
||||
const amountShortName = 'input[name="amount"] + div + span.text-xs';
|
||||
const assetSelection = 'select-asset';
|
||||
const assetBalance = 'asset-balance';
|
||||
const assetOption = 'rich-select-option';
|
||||
const openTransferButton = 'open-transfer';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const transferForm = 'transfer-form';
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
const collateralTab = 'Collateral';
|
||||
const toastCloseBtn = 'toast-close';
|
||||
const toastContent = 'toast-content';
|
||||
|
||||
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(collateralTab).click();
|
||||
cy.getByTestId(openTransferButton).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('key to key transfers by select key', () => {
|
||||
// 1003-TRAN-001
|
||||
// 1003-TRAN-006
|
||||
// 1003-TRAN-007
|
||||
// 1003-TRAN-008
|
||||
// 1003-TRAN-009
|
||||
// 1003-TRAN-010
|
||||
// 1003-TRAN-023
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.getByTestId(transferForm).find(toAddressField).select(1);
|
||||
|
||||
cy.getByTestId(assetSelection).click();
|
||||
cy.getByTestId(assetOption);
|
||||
cy.getByTestId(assetBalance).should('not.be.empty');
|
||||
cy.getByTestId(assetOption).should('have.length.gt', 4);
|
||||
|
||||
let optionText: string;
|
||||
cy.getByTestId(assetOption)
|
||||
.eq(2)
|
||||
.invoke('text')
|
||||
.then((text: string) => {
|
||||
optionText = text;
|
||||
cy.getByTestId(assetOption).eq(2).click();
|
||||
cy.getByTestId(assetSelection).should('have.text', optionText);
|
||||
});
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
cy.getByTestId(transferForm).find(amountShortName).should('not.be.empty');
|
||||
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
'Awaiting confirmation'
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
});
|
||||
|
||||
it('key to key transfers by enter manual key', () => {
|
||||
//1003-TRAN-005
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
'Awaiting confirmation'
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { DocsLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
fromNanoSeconds,
|
||||
getExpiryDate,
|
||||
getMarketExpiryDate,
|
||||
@@ -14,9 +15,12 @@ import {
|
||||
Last24hVolume,
|
||||
getAsset,
|
||||
getDataSourceSpecForSettlementSchedule,
|
||||
isMarketInAuction,
|
||||
marketInfoProvider,
|
||||
useFundingPeriodsQuery,
|
||||
useFundingRate,
|
||||
useMarketTradingMode,
|
||||
useExternalTwap,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketState as State } from '@vegaprotocol/types';
|
||||
import { HeaderStat } from '../../components/header';
|
||||
@@ -26,6 +30,7 @@ import { MarketState } from '../../components/market-state';
|
||||
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { PriceCell } from '@vegaprotocol/datagrid';
|
||||
|
||||
interface MarketHeaderStatsProps {
|
||||
market: Market;
|
||||
@@ -38,34 +43,8 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
const asset = getAsset(market);
|
||||
|
||||
return (
|
||||
<>
|
||||
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
||||
<HeaderStat
|
||||
heading={t('Expiry')}
|
||||
description={
|
||||
<ExpiryTooltipContent
|
||||
market={market}
|
||||
explorerUrl={VEGA_EXPLORER_URL}
|
||||
/>
|
||||
}
|
||||
testId="market-expiry"
|
||||
>
|
||||
<ExpiryLabel market={market} />
|
||||
</HeaderStat>
|
||||
)}
|
||||
{market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' && (
|
||||
<HeaderStat
|
||||
heading={`${t('Funding')} / ${t('Countdown')}`}
|
||||
testId="market-funding"
|
||||
>
|
||||
<div className="flex justify-between gap-2">
|
||||
<FundingRate marketId={market.id} />
|
||||
<FundingCountdown marketId={market.id} />
|
||||
</div>
|
||||
</HeaderStat>
|
||||
)}
|
||||
<HeaderStat heading={t('Price')} testId="market-price">
|
||||
<div className="flex gap-8">
|
||||
<HeaderStat heading={t('Mark Price')} testId="market-price">
|
||||
<MarketMarkPrice
|
||||
marketId={market.id}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
@@ -108,8 +87,64 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
marketId={market.id}
|
||||
assetDecimals={asset?.decimals || 0}
|
||||
/>
|
||||
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
||||
<HeaderStat
|
||||
heading={t('Expiry')}
|
||||
description={
|
||||
<ExpiryTooltipContent
|
||||
market={market}
|
||||
explorerUrl={VEGA_EXPLORER_URL}
|
||||
/>
|
||||
}
|
||||
testId="market-expiry"
|
||||
>
|
||||
<ExpiryLabel market={market} />
|
||||
</HeaderStat>
|
||||
)}
|
||||
{market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' && (
|
||||
<HeaderStat
|
||||
heading={`${t('Funding Rate')} / ${t('Countdown')}`}
|
||||
testId="market-funding"
|
||||
>
|
||||
<div className="flex justify-between gap-2">
|
||||
<FundingRate marketId={market.id} />
|
||||
<FundingCountdown marketId={market.id} />
|
||||
</div>
|
||||
</HeaderStat>
|
||||
)}
|
||||
{market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' && (
|
||||
<HeaderStat
|
||||
heading={`${t('Index Price')}`}
|
||||
description={
|
||||
<div className="p1">
|
||||
{t(
|
||||
'The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.'
|
||||
)}
|
||||
{DocsLinks && (
|
||||
<ExternalLink
|
||||
href={DocsLinks.ETH_DATA_SOURCES}
|
||||
className="mt-2"
|
||||
>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
testId="index-price"
|
||||
>
|
||||
<IndexPrice
|
||||
marketId={market.id}
|
||||
decimalPlaces={
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
/>
|
||||
</HeaderStat>
|
||||
)}
|
||||
<MarketProposalNotification marketId={market.id} />
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -126,6 +161,24 @@ export const FundingRate = ({ marketId }: { marketId: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexPrice = ({
|
||||
marketId,
|
||||
decimalPlaces,
|
||||
}: {
|
||||
marketId: string;
|
||||
decimalPlaces?: number;
|
||||
}) => {
|
||||
const { data: externalTwap } = useExternalTwap(marketId);
|
||||
return externalTwap && decimalPlaces ? (
|
||||
<PriceCell
|
||||
value={Number(externalTwap)}
|
||||
valueFormatted={addDecimalsFormatNumber(externalTwap, decimalPlaces)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
};
|
||||
|
||||
const useNow = () => {
|
||||
const [now, setNow] = useState(Date.now());
|
||||
useEffect(() => {
|
||||
@@ -136,9 +189,11 @@ const useNow = () => {
|
||||
};
|
||||
|
||||
const useEvery = (marketId: string) => {
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const { data: marketInfo } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
variables: { marketId },
|
||||
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||
});
|
||||
let every: number | undefined = undefined;
|
||||
const sourceType =
|
||||
|
||||
@@ -3,7 +3,7 @@ import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { ExternalLink, Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { getAsset, marketDataProvider, useMarket } from '@vegaprotocol/markets';
|
||||
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||
import { TradeGrid } from './trade-grid';
|
||||
@@ -68,7 +68,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const { data, error, loading } = useMarket(marketId);
|
||||
const { data, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -110,7 +110,15 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
}
|
||||
}, [largeScreen, data, pinnedAsset]);
|
||||
|
||||
if (!data && marketId) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Splash>
|
||||
<span className="flex flex-col items-center gap-2">
|
||||
@@ -120,7 +128,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
<p className="justify-center text-sm">
|
||||
{t(`Please choose another market from the`)}{' '}
|
||||
<ExternalLink onClick={() => navigate(Links.MARKETS())}>
|
||||
market list
|
||||
{t('market list')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
</span>
|
||||
@@ -129,18 +137,13 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data || undefined}
|
||||
noDataCondition={(data) => false}
|
||||
>
|
||||
<>
|
||||
<TitleUpdater
|
||||
marketId={data?.id}
|
||||
marketName={data?.tradableInstrument.instrument.name}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
{tradeView}
|
||||
</AsyncRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TradingViews } from './trade-views';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
@@ -72,10 +73,19 @@ const MainGrid = memo(
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding" name={t('Funding')}>
|
||||
<Tab id="funding-history" name={t('Funding history')}>
|
||||
<TradingViews.funding.component marketId={marketId} />
|
||||
</Tab>
|
||||
) : null}
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<TradingViews.fundingPayments.component
|
||||
marketId={marketId}
|
||||
/>
|
||||
</Tab>
|
||||
) : null}
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
</ResizableGridPanel>
|
||||
@@ -169,6 +179,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div className="min-h-0 p-0.5">
|
||||
|
||||
@@ -11,6 +11,7 @@ import classNames from 'classnames';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
@@ -59,6 +60,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PositionsContainer } from '../../components/positions-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import { FundingContainer } from '../../components/funding-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
@@ -55,6 +56,10 @@ export const TradingViews = {
|
||||
label: 'Funding',
|
||||
component: requiresMarket(FundingContainer),
|
||||
},
|
||||
fundingPayments: {
|
||||
label: 'Funding Payments',
|
||||
component: FundingPaymentsContainer,
|
||||
},
|
||||
orderbook: {
|
||||
label: 'Orderbook',
|
||||
component: requiresMarket(OrderbookContainer),
|
||||
|
||||
@@ -9,7 +9,9 @@ import { usePageTitleStore } from '../../stores';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { DepositsContainer } from '../../components/deposits-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
@@ -68,7 +70,11 @@ export const Portfolio = () => {
|
||||
<Tab id="account-history" name={t('Account history')}>
|
||||
<AccountHistoryContainer />
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<Tab
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<PositionsMenu />}
|
||||
>
|
||||
<PositionsContainer allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
@@ -77,6 +83,9 @@ export const Portfolio = () => {
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<FillsContainer />
|
||||
</Tab>
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<FundingPaymentsContainer />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<LedgerContainer />
|
||||
</Tab>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { FundingPaymentsManager } from '@vegaprotocol/funding-payments';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const FundingPaymentsContainer = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const gridStore = useFundingPaymentsStore((store) => store.gridStore);
|
||||
const updateGridStore = useFundingPaymentsStore(
|
||||
(store) => store.updateGridStore
|
||||
);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FundingPaymentsManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useFundingPaymentsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_funding_payments_store',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './funding-payments-container';
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './market-successor-banner';
|
||||
export * from './market-successor-proposal-banner';
|
||||
export * from './market-termination-banner';
|
||||
|
||||
@@ -3,12 +3,17 @@ import type { SingleExecutionResult } from '@apollo/client';
|
||||
import type { MockedResponse } from '@apollo/react-testing';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
|
||||
import type { SuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import { SuccessorProposalsListDocument } from '@vegaprotocol/proposals';
|
||||
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
|
||||
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
||||
const marketProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||
request: {
|
||||
query: SuccessorProposalsListDocument,
|
||||
query: MarketViewProposalsDocument,
|
||||
variables: {
|
||||
inState: Types.ProposalState.STATE_OPEN,
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
@@ -18,8 +23,11 @@ const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-1',
|
||||
state: Types.ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
closingDatetime: '2023-09-27',
|
||||
enactmentDatetime: '2023-09-28',
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
instrument: {
|
||||
@@ -66,12 +74,13 @@ describe('MarketSuccessorProposalBanner', () => {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
...((
|
||||
marketProposalMock?.result as SingleExecutionResult<SuccessorProposalsListQuery>
|
||||
marketProposalMock?.result as SingleExecutionResult<MarketViewProposalsQuery>
|
||||
)?.data?.proposalsConnection?.edges ?? []),
|
||||
{
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-2',
|
||||
state: Types.ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
change: {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import type {
|
||||
SuccessorProposalListFieldsFragment,
|
||||
NewMarketSuccessorFieldsFragment,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useSuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
@@ -11,23 +8,33 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
export const MarketSuccessorProposalBanner = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const { data: proposals } = useSuccessorProposalsListQuery({
|
||||
const proposals = useMarketViewProposals({
|
||||
skip: !marketId,
|
||||
inState: Types.ProposalState.STATE_OPEN,
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
typename: 'NewMarket',
|
||||
});
|
||||
|
||||
const successors =
|
||||
proposals?.proposalsConnection?.edges
|
||||
?.map((item) => item?.node as SuccessorProposalListFieldsFragment)
|
||||
.filter(
|
||||
(item: SuccessorProposalListFieldsFragment) =>
|
||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||
?.successorConfiguration?.parentMarketId === marketId
|
||||
) ?? [];
|
||||
proposals?.filter((item) => {
|
||||
if (item.terms.change.__typename === 'NewMarket') {
|
||||
const newMarket = item.terms.change;
|
||||
if (
|
||||
newMarket.successorConfiguration?.parentMarketId === marketId &&
|
||||
item.state === Types.ProposalState.STATE_OPEN
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) ?? [];
|
||||
const [visible, setVisible] = useState(true);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
if (visible && successors.length) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
|
||||
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { MarketTerminationBanner } from './market-termination-banner';
|
||||
|
||||
const marketMock = {
|
||||
id: 'market-1',
|
||||
decimalPlaces: 3,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'tDAI',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Market;
|
||||
|
||||
const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||
request: {
|
||||
query: MarketViewProposalsDocument,
|
||||
variables: { inState: Types.ProposalState.STATE_PASSED },
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'first-id',
|
||||
state: Types.ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: '2023-09-27T11:48:18Z',
|
||||
enactmentDatetime: '2023-09-30T11:48:18',
|
||||
change: {
|
||||
__typename: 'UpdateMarketState',
|
||||
updateType:
|
||||
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||
price: '',
|
||||
market: {
|
||||
id: 'market-1',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'Market one name',
|
||||
code: 'Market one',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'second-id',
|
||||
state: Types.ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: '2023-09-27T11:48:18Z',
|
||||
enactmentDatetime: '2023-10-01T11:48:18',
|
||||
change: {
|
||||
__typename: 'UpdateMarketState',
|
||||
updateType:
|
||||
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||
price: '',
|
||||
market: {
|
||||
id: 'market-2',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'Market two name',
|
||||
code: 'Market two',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const mocks: MockedResponse[] = [proposalMock];
|
||||
|
||||
describe('MarketTerminationBanner', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-09-28T10:10:10.000Z'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be properly rendered', async () => {
|
||||
const { container } = render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MarketTerminationBanner market={marketMock} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container).not.toBeEmptyDOMElement();
|
||||
});
|
||||
expect(
|
||||
screen.getByTestId('termination-warning-banner-market-1')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useState } from 'react';
|
||||
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
||||
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { getQuoteName } from '@vegaprotocol/markets';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
export const MarketTerminationBanner = ({
|
||||
market,
|
||||
}: {
|
||||
market: Market | null;
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const skip = !market || !visible;
|
||||
const proposalsData = useMarketViewProposals({
|
||||
skip,
|
||||
inState: Types.ProposalState.STATE_PASSED,
|
||||
typename: 'UpdateMarketState',
|
||||
});
|
||||
|
||||
if (!market) return null;
|
||||
const marketFound = (proposalsData || []).find(
|
||||
(item) =>
|
||||
item.terms.change.__typename === 'UpdateMarketState' &&
|
||||
item.terms.change.market.id === market.id &&
|
||||
item.terms.change.updateType ===
|
||||
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
||||
item.state === Types.ProposalState.STATE_PASSED // subscription doesn't have state parameter
|
||||
);
|
||||
|
||||
const enactmentDatetime = new Date(marketFound?.terms.enactmentDatetime);
|
||||
const name =
|
||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
||||
? marketFound.terms.change.market.tradableInstrument.instrument.code
|
||||
: '';
|
||||
|
||||
if (name && enactmentDatetime.getTime() > Date.now()) {
|
||||
const dayMonthDate = format(enactmentDatetime, 'dd MMMM');
|
||||
const duration = intervalToDuration({
|
||||
start: new Date(),
|
||||
end: enactmentDatetime,
|
||||
});
|
||||
const formattedDuration = formatDuration(duration, {
|
||||
format: ['days', 'hours'],
|
||||
});
|
||||
const price =
|
||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
||||
? marketFound.terms.change.price
|
||||
: '';
|
||||
const assetSymbol = getQuoteName(market);
|
||||
return (
|
||||
<NotificationBanner
|
||||
intent={Intent.Warning}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
data-testid={`termination-warning-banner-${market.id}`}
|
||||
>
|
||||
<div className="uppercase mb-1">
|
||||
{t('Trading on Market %s will stop on %s', [name, dayMonthDate])}
|
||||
</div>
|
||||
<div>
|
||||
{t(
|
||||
'You will no longer be able to hold a position on this market when it closes in %s.',
|
||||
[formattedDuration]
|
||||
)}{' '}
|
||||
{price &&
|
||||
assetSymbol &&
|
||||
t('The final price will be %s %s.', [
|
||||
addDecimalsFormatNumber(price, market.decimalPlaces),
|
||||
assetSymbol,
|
||||
])}
|
||||
</div>
|
||||
</NotificationBanner>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { getAccountData } from './accounts-data-provider';
|
||||
import { AccountTable } from './accounts-table';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const singleRow = {
|
||||
__typename: 'AccountBalance',
|
||||
@@ -24,6 +25,26 @@ const singleRow = {
|
||||
} as AccountFields;
|
||||
const singleRowData = [singleRow];
|
||||
|
||||
const secondRow = {
|
||||
__typename: 'AccountBalance',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
balance: '125600002',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'aBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
available: '125600001',
|
||||
used: '125600001',
|
||||
total: '251200002',
|
||||
} as AccountFields;
|
||||
const multiRowData = [singleRow, secondRow];
|
||||
|
||||
describe('AccountsTable', () => {
|
||||
it('should render correct columns', async () => {
|
||||
// 7001-COLL-001
|
||||
@@ -68,6 +89,25 @@ describe('AccountsTable', () => {
|
||||
expect(rows?.childElementCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort assets', async () => {
|
||||
// 7001-COLL-010
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
rowData={multiRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const headerCell = screen.getByText('Asset');
|
||||
await userEvent.click(headerCell);
|
||||
const rows = container.querySelectorAll(
|
||||
'.ag-center-cols-container .ag-row'
|
||||
);
|
||||
expect(rows[0].textContent).toContain('aBTC');
|
||||
expect(rows[1].textContent).toContain('tBTC');
|
||||
});
|
||||
|
||||
it('should apply correct formatting in view as user mode', async () => {
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
|
||||
@@ -9,6 +9,7 @@ import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||
@@ -33,6 +34,59 @@ describe('TransferForm', () => {
|
||||
submitTransfer: jest.fn(),
|
||||
};
|
||||
|
||||
it('form tooltips correctly displayed', async () => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
render(<TransferForm {...props} />);
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
// set valid amount
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amount },
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Include transfer fee'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Transfer fee'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Amount to be transferred'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Total amount (with fee)'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
it('validates a manually entered address', async () => {
|
||||
render(<TransferForm {...props} />);
|
||||
submit();
|
||||
@@ -174,6 +228,7 @@ describe('TransferForm', () => {
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
// 1003-TRAN-022
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
@@ -190,6 +245,7 @@ describe('TransferForm', () => {
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||
// 1003-TRAN-020
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
expectedAmount
|
||||
@@ -201,6 +257,8 @@ describe('TransferForm', () => {
|
||||
submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
@@ -263,6 +321,7 @@ describe('TransferForm', () => {
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
// 1003-TRAN-021
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from '../deposits/src/lib/deposit.mock';
|
||||
export * from '../environment/src/utils/node.mock';
|
||||
export * from '../environment/src/components/node-guard/node-guard.mock';
|
||||
export * from '../fills/src/lib/fills.mock';
|
||||
export * from '../funding-payments/src/lib/funding-payments.mock';
|
||||
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
||||
export * from '../market-depth/src/lib/market-depth.mock';
|
||||
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
||||
|
||||
@@ -153,6 +153,7 @@ describe('DealTicket', () => {
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
|
||||
// 7002-SORD-031
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
|
||||
);
|
||||
@@ -235,7 +236,7 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should set values for a non-persistent reduce only order and disable post only checkbox', () => {
|
||||
it('should set values for a non-persistent reduce only order and disable post only checkbox', async () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -273,13 +274,28 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||
expectedOrder.price
|
||||
);
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-057
|
||||
expect(screen.getByTestId('post-only')).toBeDisabled();
|
||||
expect(screen.getByTestId('reduce-only')).toBeEnabled();
|
||||
expect(screen.getByTestId('reduce-only')).toBeChecked();
|
||||
expect(screen.getByTestId('post-only')).not.toBeChecked();
|
||||
userEvent.hover(screen.getByText('Reduce only'));
|
||||
// 7003-SORD-056
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Post only'));
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
it('should set values for a persistent post only order and disable reduce only checkbox', () => {
|
||||
it('should set values for a persistent post only order and disable reduce only checkbox', async () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -318,10 +334,49 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||
expectedOrder.price
|
||||
);
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-057
|
||||
expect(screen.getByTestId('post-only')).toBeEnabled();
|
||||
expect(screen.getByTestId('reduce-only')).toBeDisabled();
|
||||
expect(screen.getByTestId('post-only')).toBeChecked();
|
||||
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
|
||||
|
||||
userEvent.hover(screen.getByText('Reduce only'));
|
||||
// 7003-SORD-056
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
userEvent.hover(screen.getByText('Post only'));
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
it('should see an explanation of post only', async () => {
|
||||
render(generateJsx());
|
||||
userEvent.hover(screen.getByText('Post only'));
|
||||
// 7003-SORD-058
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toHaveTextContent(
|
||||
`"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see an explanation of reduce only', async () => {
|
||||
render(generateJsx());
|
||||
userEvent.hover(screen.getByText('Reduce only'));
|
||||
// 7003-SORD-058
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toHaveTextContent(
|
||||
`"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set values for a persistent post only iceberg order and disable reduce only checkbox', () => {
|
||||
@@ -430,6 +485,12 @@ describe('DealTicket', () => {
|
||||
});
|
||||
|
||||
// Only FOK and IOC should be present for type market order
|
||||
// 7002-SORD-023
|
||||
// 7002-SORD-024
|
||||
// 7002-SORD-025
|
||||
// 7002-SORD-026
|
||||
// 7002-SORD-027
|
||||
// 7002-SORD-028
|
||||
expect(
|
||||
Array.from(screen.getByTestId('order-tif').children).map(
|
||||
(o) => o.textContent
|
||||
@@ -437,6 +498,7 @@ describe('DealTicket', () => {
|
||||
).toEqual(['Fill or Kill (FOK)', 'Immediate or Cancel (IOC)']);
|
||||
|
||||
// IOC should be default
|
||||
// 7002-SORD-030
|
||||
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
|
||||
'Immediate or Cancel (IOC)'
|
||||
);
|
||||
@@ -451,6 +513,12 @@ describe('DealTicket', () => {
|
||||
);
|
||||
|
||||
// Switch to type limit order -> all TIF options should be shown
|
||||
// 7002-SORD-023
|
||||
// 7002-SORD-024
|
||||
// 7002-SORD-025
|
||||
// 7002-SORD-026
|
||||
// 7002-SORD-027
|
||||
// 7002-SORD-028
|
||||
await userEvent.click(screen.getByTestId('order-type-Limit'));
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(Schema.OrderTimeInForce).length
|
||||
|
||||
@@ -429,6 +429,12 @@ function compileFeatureFlags(): FeatureFlags {
|
||||
process.env['NX_GOVERNANCE_TRANSFERS']
|
||||
) as string
|
||||
),
|
||||
VOLUME_DISCOUNTS: TRUTHY.includes(
|
||||
windowOrDefault(
|
||||
'NX_VOLUME_DISCOUNTS',
|
||||
process.env['NX_VOLUME_DISCOUNTS']
|
||||
) as string
|
||||
),
|
||||
};
|
||||
const EXPLORER_FLAGS = {
|
||||
EXPLORER_ASSETS: TRUTHY.includes(
|
||||
|
||||
@@ -81,6 +81,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
WITHDRAWAL_LIMITS: `${VEGA_DOCS_URL}/concepts/assets/deposits-withdrawals#withdrawal-limits`,
|
||||
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
|
||||
MARKET_LIFECYCLE: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-lifecycle`,
|
||||
ETH_DATA_SOURCES: `${VEGA_DOCS_URL}/concepts/trading-on-vega/data-sources#ethereum-data-sources`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export type CosmicElevatorFlags = Pick<
|
||||
| 'REFERRALS'
|
||||
| 'UPDATE_MARKET_STATE'
|
||||
| 'GOVERNANCE_TRANSFERS'
|
||||
| 'VOLUME_DISCOUNTS'
|
||||
>;
|
||||
export type Configuration = z.infer<typeof tomlConfigSchema>;
|
||||
export const CUSTOM_NODE_KEY = 'custom' as const;
|
||||
|
||||
@@ -81,6 +81,7 @@ const COSMIC_ELEVATOR_FLAGS = {
|
||||
REFERRALS: z.optional(z.boolean()),
|
||||
UPDATE_MARKET_STATE: z.optional(z.boolean()),
|
||||
GOVERNANCE_TRANSFERS: z.optional(z.boolean()),
|
||||
VOLUME_DISCOUNTS: z.optional(z.boolean()),
|
||||
};
|
||||
|
||||
const EXPLORER_FLAGS = {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"sourceType": "unambiguous",
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# funding-payments
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test funding-payments` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
@@ -0,0 +1,6 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'funding-payments',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {},
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/funding-payments',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "funding-payments",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/funding-payments/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/funding-payments/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/funding-payments"],
|
||||
"options": {
|
||||
"jestConfig": "libs/funding-payments/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": [],
|
||||
"options": {
|
||||
"command": "yarn tsc --project ./libs/funding-payments/tsconfig.spec.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './lib/funding-payments-manager';
|
||||
export * from './lib/funding-payments-data-provider';
|
||||
export * from './lib/__generated__/FundingPayments';
|
||||
@@ -0,0 +1,28 @@
|
||||
fragment FundingPaymentFields on FundingPayment {
|
||||
marketId
|
||||
partyId
|
||||
fundingPeriodSeq
|
||||
amount
|
||||
timestamp
|
||||
}
|
||||
|
||||
query FundingPayments($partyId: ID!, $pagination: Pagination, $marketId: ID) {
|
||||
fundingPayments(
|
||||
partyId: $partyId
|
||||
pagination: $pagination
|
||||
marketId: $marketId
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...FundingPaymentFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type FundingPaymentFieldsFragment = { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any };
|
||||
|
||||
export type FundingPaymentsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||
marketId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type FundingPaymentsQuery = { __typename?: 'Query', fundingPayments: { __typename?: 'FundingPaymentConnection', edges: Array<{ __typename?: 'FundingPaymentEdge', cursor: string, node: { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
|
||||
|
||||
export const FundingPaymentFieldsFragmentDoc = gql`
|
||||
fragment FundingPaymentFields on FundingPayment {
|
||||
marketId
|
||||
partyId
|
||||
fundingPeriodSeq
|
||||
amount
|
||||
timestamp
|
||||
}
|
||||
`;
|
||||
export const FundingPaymentsDocument = gql`
|
||||
query FundingPayments($partyId: ID!, $pagination: Pagination, $marketId: ID) {
|
||||
fundingPayments(partyId: $partyId, pagination: $pagination, marketId: $marketId) {
|
||||
edges {
|
||||
node {
|
||||
...FundingPaymentFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
${FundingPaymentFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useFundingPaymentsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFundingPaymentsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFundingPaymentsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useFundingPaymentsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* pagination: // value for 'pagination'
|
||||
* marketId: // value for 'marketId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFundingPaymentsQuery(baseOptions: Apollo.QueryHookOptions<FundingPaymentsQuery, FundingPaymentsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FundingPaymentsQuery, FundingPaymentsQueryVariables>(FundingPaymentsDocument, options);
|
||||
}
|
||||
export function useFundingPaymentsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FundingPaymentsQuery, FundingPaymentsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FundingPaymentsQuery, FundingPaymentsQueryVariables>(FundingPaymentsDocument, options);
|
||||
}
|
||||
export type FundingPaymentsQueryHookResult = ReturnType<typeof useFundingPaymentsQuery>;
|
||||
export type FundingPaymentsLazyQueryHookResult = ReturnType<typeof useFundingPaymentsLazyQuery>;
|
||||
export type FundingPaymentsQueryResult = Apollo.QueryResult<FundingPaymentsQuery, FundingPaymentsQueryVariables>;
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import { FundingPaymentsDocument } from './__generated__/FundingPayments';
|
||||
import type {
|
||||
FundingPaymentsQuery,
|
||||
FundingPaymentsQueryVariables,
|
||||
FundingPaymentFieldsFragment,
|
||||
} from './__generated__/FundingPayments';
|
||||
|
||||
export type FundingPayment = Omit<FundingPaymentFieldsFragment, 'market'> & {
|
||||
market?: Market;
|
||||
};
|
||||
|
||||
const getData = (
|
||||
responseData: FundingPaymentsQuery | null
|
||||
): (FundingPaymentFieldsFragment & Cursor)[] =>
|
||||
responseData?.fundingPayments?.edges.map<
|
||||
FundingPaymentFieldsFragment & Cursor
|
||||
>((edge) => ({
|
||||
...edge.node,
|
||||
cursor: edge.cursor,
|
||||
})) || [];
|
||||
|
||||
const getPageInfo = (
|
||||
responseData: FundingPaymentsQuery | null
|
||||
): PageInfo | null => responseData?.fundingPayments?.pageInfo || null;
|
||||
|
||||
export const fundingPaymentsProvider = makeDataProvider<
|
||||
Parameters<typeof getData>['0'],
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
never,
|
||||
FundingPaymentsQueryVariables
|
||||
>({
|
||||
query: FundingPaymentsDocument,
|
||||
getData,
|
||||
pagination: {
|
||||
getPageInfo,
|
||||
append,
|
||||
first: 100,
|
||||
},
|
||||
});
|
||||
|
||||
export const fundingPaymentsWithMarketProvider = makeDerivedDataProvider<
|
||||
FundingPayment[],
|
||||
never,
|
||||
FundingPaymentsQueryVariables
|
||||
>(
|
||||
[
|
||||
fundingPaymentsProvider,
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): FundingPayment[] | null => {
|
||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||
(fundingPayment) => ({
|
||||
...fundingPayment,
|
||||
market: (partsData[1] as Record<string, Market>)[
|
||||
fundingPayment.marketId
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useRef } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FundingPaymentsTable } from './funding-payments-table';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider';
|
||||
|
||||
interface FundingPaymentsManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const FundingPaymentsManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
gridProps,
|
||||
}: FundingPaymentsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: fundingPaymentsWithMarketProvider,
|
||||
update: ({ data }) => {
|
||||
if (data?.length && gridRef.current?.api) {
|
||||
gridRef.current?.api.setRowData(data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
variables: { partyId, marketId },
|
||||
});
|
||||
|
||||
return (
|
||||
<FundingPaymentsTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
onMarketClick={onMarketClick}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No funding payments')}
|
||||
{...gridProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { FundingPayment } from './funding-payments-data-provider';
|
||||
import { FundingPaymentsTable } from './funding-payments-table';
|
||||
import { generateFundingPayment } from './test-helpers';
|
||||
|
||||
describe('FundingPaymentsTable', () => {
|
||||
let defaultFundingPayment: PartialDeep<FundingPayment>;
|
||||
|
||||
beforeEach(() => {
|
||||
defaultFundingPayment = {
|
||||
marketId:
|
||||
'69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c',
|
||||
partyId:
|
||||
'02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
fundingPeriodSeq: 84,
|
||||
amount: '100',
|
||||
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||
market: {
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'test market',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
settlementAsset: {
|
||||
decimals: 2,
|
||||
symbol: 'BTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('correct columns are rendered', async () => {
|
||||
await act(async () => {
|
||||
render(<FundingPaymentsTable rowData={[generateFundingPayment()]} />);
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = ['Market', 'Amount', 'Date'];
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||
});
|
||||
|
||||
it('formats positive cells', async () => {
|
||||
const fundingPayment = generateFundingPayment({
|
||||
...defaultFundingPayment,
|
||||
});
|
||||
render(<FundingPaymentsTable rowData={[fundingPayment]} />);
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
fundingPayment.market?.tradableInstrument.instrument.code || '',
|
||||
'1.00 BTC',
|
||||
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
|
||||
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount');
|
||||
expect(
|
||||
amountCell?.querySelector('.ag-cell-value')?.firstElementChild
|
||||
).toHaveClass('text-market-green-600');
|
||||
});
|
||||
|
||||
it('formats negative cells', async () => {
|
||||
const fundingPayment = generateFundingPayment({
|
||||
...defaultFundingPayment,
|
||||
amount: `-${defaultFundingPayment.amount}`,
|
||||
});
|
||||
render(<FundingPaymentsTable rowData={[fundingPayment]} />);
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
fundingPayment.market?.tradableInstrument.instrument.code || '',
|
||||
'-1.00 BTC',
|
||||
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
|
||||
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount');
|
||||
expect(
|
||||
amountCell?.querySelector('.ag-cell-value')?.firstElementChild
|
||||
).toHaveClass('text-market-red dark:text-market-red');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useMemo } from 'react';
|
||||
import type {
|
||||
AgGridReact,
|
||||
AgGridReactProps,
|
||||
AgReactUiProps,
|
||||
} from 'ag-grid-react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import {
|
||||
AgGrid,
|
||||
DateRangeFilter,
|
||||
MarketNameCell,
|
||||
negativeClassNames,
|
||||
positiveClassNames,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { FundingPayment } from './funding-payments-data-provider';
|
||||
|
||||
import { getAsset } from '@vegaprotocol/markets';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const defaultColDef = {
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
};
|
||||
|
||||
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
};
|
||||
|
||||
const formatAmount = ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<FundingPayment, 'amount'>) => {
|
||||
if (!data?.market || !isNumeric(value)) {
|
||||
return '-';
|
||||
}
|
||||
const { symbol: assetSymbol, decimals: assetDecimals } = getAsset(
|
||||
data.market
|
||||
);
|
||||
const valueFormatted = addDecimalsFormatNumber(value, assetDecimals);
|
||||
return `${valueFormatted} ${assetSymbol}`;
|
||||
};
|
||||
|
||||
export const FundingPaymentsTable = forwardRef<AgGridReact, Props>(
|
||||
({ onMarketClick, ...props }, ref) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
filter: true,
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Amount'),
|
||||
field: 'amount',
|
||||
valueFormatter: formatAmount,
|
||||
type: 'rightAligned',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<FundingPayment>) =>
|
||||
data?.amount && data?.market
|
||||
? toBigNum(data.amount, getAsset(data.market).decimals).toNumber()
|
||||
: 0,
|
||||
cellRenderer: ({ data }: { data: FundingPayment }) => {
|
||||
if (!data?.market || !isNumeric(data.amount)) {
|
||||
return '-';
|
||||
}
|
||||
const { symbol: assetSymbol, decimals: assetDecimals } = getAsset(
|
||||
data.market
|
||||
);
|
||||
const valueFormatted = addDecimalsFormatNumber(
|
||||
data.amount,
|
||||
assetDecimals
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classNames({
|
||||
[positiveClassNames]: !data?.amount?.startsWith('-'),
|
||||
[negativeClassNames]: !!data?.amount?.startsWith('-'),
|
||||
})}
|
||||
>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
{` ${assetSymbol}`}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Date'),
|
||||
field: 'timestamp',
|
||||
type: 'rightAligned',
|
||||
filter: DateRangeFilter,
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<FundingPayment, 'timestamp'>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||
},
|
||||
},
|
||||
],
|
||||
[onMarketClick]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
overlayNoRowsTemplate={t('No funding payments')}
|
||||
getRowId={({ data }: { data?: FundingPayment }) =>
|
||||
`${data?.marketId}-${data?.fundingPeriodSeq}`
|
||||
}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
FundingPaymentsQuery,
|
||||
FundingPaymentFieldsFragment,
|
||||
} from './__generated__/FundingPayments';
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
export const fundingPaymentsQuery = (
|
||||
override?: PartialDeep<FundingPaymentsQuery>,
|
||||
vegaPublicKey?: string
|
||||
): FundingPaymentsQuery => {
|
||||
const defaultResult: FundingPaymentsQuery = {
|
||||
fundingPayments: {
|
||||
__typename: 'FundingPaymentConnection',
|
||||
edges: fundingPayments(vegaPublicKey).map((node) => ({
|
||||
__typename: 'FundingPaymentEdge',
|
||||
cursor: '3',
|
||||
node,
|
||||
})),
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
startCursor: '1',
|
||||
endCursor: '2',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return merge(defaultResult, override);
|
||||
};
|
||||
|
||||
export const generateFundingPayment = (
|
||||
override?: PartialDeep<FundingPaymentFieldsFragment>
|
||||
) => {
|
||||
const defaultFundingPayment: FundingPaymentFieldsFragment = {
|
||||
marketId: 'market-0',
|
||||
partyId: 'partyId',
|
||||
fundingPeriodSeq: 84,
|
||||
amount: '126973',
|
||||
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||
};
|
||||
|
||||
return merge(defaultFundingPayment, override);
|
||||
};
|
||||
|
||||
const fundingPayments = (
|
||||
partyId = 'partyId'
|
||||
): FundingPaymentFieldsFragment[] => [
|
||||
generateFundingPayment({
|
||||
partyId,
|
||||
fundingPeriodSeq: 78,
|
||||
amount: '92503',
|
||||
timestamp: '2023-10-06T04:06:43.652759Z',
|
||||
}),
|
||||
generateFundingPayment({
|
||||
partyId,
|
||||
fundingPeriodSeq: 77,
|
||||
amount: '-37841',
|
||||
timestamp: '2023-10-06T03:36:43.437139Z',
|
||||
}),
|
||||
generateFundingPayment({
|
||||
partyId,
|
||||
fundingPeriodSeq: 76,
|
||||
amount: '32838',
|
||||
timestamp: '2023-10-06T03:06:43.430384Z',
|
||||
}),
|
||||
generateFundingPayment({
|
||||
partyId,
|
||||
fundingPeriodSeq: 75,
|
||||
amount: '-298259',
|
||||
timestamp: '2023-10-06T02:36:43.51153Z',
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,102 @@
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { FundingPayment } from './funding-payments-data-provider';
|
||||
|
||||
const { MarketState, MarketTradingMode } = Schema;
|
||||
|
||||
export const generateFundingPayment = (
|
||||
override?: PartialDeep<FundingPayment>
|
||||
) => {
|
||||
const defaultFundingPayment: FundingPayment = {
|
||||
marketId:
|
||||
'69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c',
|
||||
partyId: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
fundingPeriodSeq: 84,
|
||||
amount: '126973',
|
||||
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
positionDecimalPlaces: 0,
|
||||
decimalPlaces: 5,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
liquidityMonitoringParameters: {
|
||||
triggeringRatio: '1',
|
||||
},
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
__typename: 'FeeFactors',
|
||||
infrastructureFee: '0.1',
|
||||
liquidityFee: '0.1',
|
||||
makerFee: '0.1',
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2005-04-02T19:37:00.000Z',
|
||||
close: '2005-04-02T19:37:00.000Z',
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
id: 'instrument-id',
|
||||
code: 'instrument-code',
|
||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||
metadata: {
|
||||
__typename: 'InstrumentMetadata',
|
||||
tags: ['tag-a'],
|
||||
},
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
settlementAsset: {
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id',
|
||||
name: 'asset-id',
|
||||
symbol: 'SYM',
|
||||
decimals: 18,
|
||||
quantum: '1',
|
||||
},
|
||||
quoteName: '',
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceSpec',
|
||||
id: 'oracleId',
|
||||
data: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceSpec',
|
||||
id: 'oracleId',
|
||||
data: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
tradingTerminationProperty: 'trading-termination-property',
|
||||
settlementDataProperty: 'settlement-data-property',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return merge(defaultFundingPayment, override);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
@@ -0,0 +1,17 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
const theme = require('../tailwindcss-config/src/theme');
|
||||
const vegaCustomClasses = require('../tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
join(__dirname, 'src/**/*.{ts,tsx,html,mdx}'),
|
||||
join(__dirname, '.storybook/preview.js'),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: theme,
|
||||
},
|
||||
plugins: [vegaCustomClasses],
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"**/*.stories.ts",
|
||||
"**/*.stories.js",
|
||||
"**/*.stories.jsx",
|
||||
"**/*.stories.tsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts",
|
||||
"jest.config.ts"
|
||||
]
|
||||
}
|
||||
@@ -138,6 +138,8 @@ query MarketInfo($marketId: ID!) {
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
linearSlippageFactor
|
||||
quadraticSlippageFactor
|
||||
proposal {
|
||||
id
|
||||
rationale {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -44,8 +44,6 @@ export const Row = ({
|
||||
// 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) => {
|
||||
if (typeof value !== 'string' && typeof value !== 'number') return value;
|
||||
if (unformatted || isNaN(Number(value))) {
|
||||
@@ -75,8 +73,8 @@ export const Row = ({
|
||||
key={field}
|
||||
inline={true}
|
||||
noBorder={noBorder}
|
||||
dtClassName={className}
|
||||
ddClassName={className}
|
||||
dtClassName="text-xs"
|
||||
ddClassName="text-xs"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Tooltip description={tooltipMapping[field]} align="start">
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
LiquidityMonitoringParametersInfoPanel,
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
LiquiditySLAParametersInfoPanel,
|
||||
MarginScalingFactorsPanel,
|
||||
MarketPriceInfoPanel,
|
||||
MarketVolumeInfoPanel,
|
||||
MetadataInfoPanel,
|
||||
@@ -39,7 +40,6 @@ import {
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
} from './market-info-panels';
|
||||
@@ -236,9 +236,9 @@ export const MarketInfoAccordion = ({
|
||||
content={<RiskModelInfoPanel market={market} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-parameters"
|
||||
title={t('Risk parameters')}
|
||||
content={<RiskParametersInfoPanel market={market} />}
|
||||
itemId="margin-scaling-factors"
|
||||
title={t('Margin scaling factors')}
|
||||
content={<MarginScalingFactorsPanel market={market} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-factors"
|
||||
|
||||
@@ -45,6 +45,7 @@ import type {
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
ConditionOperatorMapping,
|
||||
MarketStateMapping,
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
@@ -220,58 +221,78 @@ export const KeyDetailsInfoPanel = ({
|
||||
const assetDecimals = getAsset(market).decimals;
|
||||
|
||||
return (
|
||||
<MarketInfoTable
|
||||
data={
|
||||
FLAGS.SUCCESSOR_MARKETS
|
||||
? {
|
||||
name: market.tradableInstrument.instrument.name,
|
||||
marketID: market.id,
|
||||
parentMarketID: parentMarketIdData?.market?.parentMarketID || '-',
|
||||
insurancePoolFraction:
|
||||
(successorProposalDetails?.proposal?.terms.change.__typename ===
|
||||
'NewMarket' &&
|
||||
successorProposalDetails.proposal.terms.change
|
||||
.successorConfiguration?.insurancePoolFraction) ||
|
||||
'-',
|
||||
tradingMode:
|
||||
market.tradingMode &&
|
||||
MarketTradingModeMapping[market.tradingMode],
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
}
|
||||
: {
|
||||
name: market.tradableInstrument.instrument.name,
|
||||
marketID: market.id,
|
||||
tradingMode:
|
||||
market.tradingMode &&
|
||||
MarketTradingModeMapping[market.tradingMode],
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
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: assetDecimals,
|
||||
<>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow noBorder>
|
||||
<div>{t('Market ID')}</div>
|
||||
<CopyWithTooltip text={market.id}>
|
||||
<button
|
||||
data-testid="copy-eth-oracle-address"
|
||||
className="uppercase text-right"
|
||||
>
|
||||
<span className="flex gap-1">
|
||||
{truncateMiddle(market.id)}
|
||||
<VegaIcon name={VegaIconNames.COPY} size={16} />
|
||||
</span>
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
<MarketInfoTable
|
||||
data={
|
||||
FLAGS.SUCCESSOR_MARKETS
|
||||
? {
|
||||
name: market.tradableInstrument.instrument.name,
|
||||
parentMarketID:
|
||||
parentMarketIdData?.market?.parentMarketID || '-',
|
||||
insurancePoolFraction:
|
||||
(successorProposalDetails?.proposal?.terms.change
|
||||
.__typename === 'NewMarket' &&
|
||||
successorProposalDetails.proposal.terms.change
|
||||
.successorConfiguration?.insurancePoolFraction) ||
|
||||
'-',
|
||||
status: market.state && MarketStateMapping[market.state],
|
||||
tradingMode:
|
||||
market.tradingMode &&
|
||||
MarketTradingModeMapping[market.tradingMode],
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
}
|
||||
: {
|
||||
name: market.tradableInstrument.instrument.name,
|
||||
status: market.state && MarketStateMapping[market.state],
|
||||
tradingMode:
|
||||
market.tradingMode &&
|
||||
MarketTradingModeMapping[market.tradingMode],
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
parentData={
|
||||
parentMarket && {
|
||||
name: parentMarket?.tradableInstrument?.instrument?.name,
|
||||
parentMarketID: grandparentMarketIdData?.market?.parentMarketID,
|
||||
insurancePoolFraction:
|
||||
parentSuccessorProposalDetails?.proposal?.terms.change
|
||||
.__typename === 'NewMarket' &&
|
||||
parentSuccessorProposalDetails.proposal.terms.change
|
||||
.successorConfiguration?.insurancePoolFraction,
|
||||
status:
|
||||
parentMarket?.state && MarketStateMapping[parentMarket.state],
|
||||
tradingMode:
|
||||
parentMarket?.tradingMode &&
|
||||
MarketTradingModeMapping[
|
||||
parentMarket.tradingMode as MarketTradingMode
|
||||
],
|
||||
marketDecimalPlaces: parentMarket?.decimalPlaces,
|
||||
positionDecimalPlaces: parentMarket?.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -432,8 +453,8 @@ export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
|
||||
asset={asset}
|
||||
inline={true}
|
||||
noBorder={true}
|
||||
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
|
||||
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
|
||||
dtClassName="text-black dark:text-white text-ui !px-0 text-xs"
|
||||
ddClassName="text-black dark:text-white text-ui !px-0 max-w-full text-xs"
|
||||
/>
|
||||
<p className="mt-4 text-xs">
|
||||
{t(
|
||||
@@ -505,18 +526,53 @@ export const RiskModelInfoPanel = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<MarketInfoTable
|
||||
data={{ tau, riskAversionParameter }}
|
||||
parentData={parentData}
|
||||
unformatted
|
||||
/>
|
||||
<>
|
||||
<MarketInfoTable
|
||||
data={{ tau, riskAversionParameter }}
|
||||
parentData={parentData}
|
||||
unformatted
|
||||
/>
|
||||
<RiskParametersInfoPanel market={market} parentMarket={parentMarket} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const RiskParametersInfoPanel = ({
|
||||
export const MarginScalingFactorsPanel = ({
|
||||
market,
|
||||
parentMarket,
|
||||
}: MarketInfoProps) => {
|
||||
const data = {
|
||||
linearSlippageFactor: market.linearSlippageFactor,
|
||||
quadraticSlippageFactor: market.quadraticSlippageFactor,
|
||||
searchLevel:
|
||||
market.tradableInstrument.marginCalculator?.scalingFactors.searchLevel,
|
||||
initialMargin:
|
||||
market.tradableInstrument.marginCalculator?.scalingFactors.initialMargin,
|
||||
collateralRelease:
|
||||
market.tradableInstrument.marginCalculator?.scalingFactors
|
||||
.collateralRelease,
|
||||
};
|
||||
|
||||
const parentData = parentMarket
|
||||
? {
|
||||
linearSlippageFactor: parentMarket?.linearSlippageFactor,
|
||||
quadraticSlippageFactor: parentMarket?.quadraticSlippageFactor,
|
||||
searchLevel:
|
||||
parentMarket?.tradableInstrument.marginCalculator?.scalingFactors
|
||||
.searchLevel,
|
||||
initialMargin:
|
||||
parentMarket?.tradableInstrument.marginCalculator?.scalingFactors
|
||||
.initialMargin,
|
||||
collateralRelease:
|
||||
parentMarket?.tradableInstrument.marginCalculator?.scalingFactors
|
||||
.collateralRelease,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return <MarketInfoTable data={data} parentData={parentData} unformatted />;
|
||||
};
|
||||
|
||||
const RiskParametersInfoPanel = ({ market, parentMarket }: MarketInfoProps) => {
|
||||
const marketType = market.tradableInstrument.riskModel.__typename;
|
||||
|
||||
let data, parentData;
|
||||
@@ -562,27 +618,66 @@ export const RiskFactorsInfoPanel = ({
|
||||
market,
|
||||
parentMarket,
|
||||
}: MarketInfoProps) => {
|
||||
if (!market.riskFactors) {
|
||||
return null;
|
||||
}
|
||||
const getLeverageFactors = (market: MarketInfo) => {
|
||||
if (!market.riskFactors) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { short, long } = market.riskFactors;
|
||||
const { short, long } = market.riskFactors;
|
||||
|
||||
let parentData;
|
||||
const maxLeverageLong = new BigNumber(1).dividedBy(
|
||||
new BigNumber(market.linearSlippageFactor).plus(long)
|
||||
);
|
||||
|
||||
if (parentMarket?.riskFactors) {
|
||||
const parentShort = parentMarket.riskFactors.short;
|
||||
const parentLong = parentMarket.riskFactors.long;
|
||||
parentData = { short: parentShort, long: parentLong };
|
||||
}
|
||||
const maxLeverageShort = new BigNumber(1).dividedBy(
|
||||
new BigNumber(market.linearSlippageFactor).plus(short)
|
||||
);
|
||||
|
||||
return (
|
||||
<MarketInfoTable
|
||||
data={{ short, long }}
|
||||
parentData={parentData}
|
||||
unformatted
|
||||
/>
|
||||
);
|
||||
const maxInitialLeverageLong = !market.tradableInstrument.marginCalculator
|
||||
? undefined
|
||||
: new BigNumber(1)
|
||||
.dividedBy(
|
||||
market.tradableInstrument.marginCalculator.scalingFactors
|
||||
.initialMargin
|
||||
)
|
||||
.times(maxLeverageLong);
|
||||
|
||||
const maxInitialLeverageShort = !market.tradableInstrument.marginCalculator
|
||||
? undefined
|
||||
: new BigNumber(1)
|
||||
.dividedBy(
|
||||
market.tradableInstrument.marginCalculator.scalingFactors
|
||||
.initialMargin
|
||||
)
|
||||
.times(maxLeverageShort);
|
||||
|
||||
const formatValue = (number: BigNumber | string | undefined) => {
|
||||
if (!number) return undefined;
|
||||
const value = new BigNumber(number);
|
||||
if (value.gte(10)) {
|
||||
return value.toFixed(3);
|
||||
} else {
|
||||
return value.toFixed(5);
|
||||
}
|
||||
};
|
||||
|
||||
const data = {
|
||||
long: formatValue(long),
|
||||
short: formatValue(short),
|
||||
maxLeverageLong: formatValue(maxLeverageLong),
|
||||
maxLeverageShort: formatValue(maxLeverageShort),
|
||||
maxInitialLeverageLong: formatValue(maxInitialLeverageLong),
|
||||
maxInitialLeverageShort: formatValue(maxInitialLeverageShort),
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
const data = getLeverageFactors(market);
|
||||
const parentData = parentMarket
|
||||
? getLeverageFactors(parentMarket)
|
||||
: undefined;
|
||||
|
||||
return <MarketInfoTable data={data} parentData={parentData} unformatted />;
|
||||
};
|
||||
|
||||
export const PriceMonitoringBoundsInfoPanel = ({
|
||||
|
||||
@@ -23,6 +23,8 @@ export const marketInfoQuery = (
|
||||
description: '',
|
||||
},
|
||||
},
|
||||
linearSlippageFactor: '0.01',
|
||||
quadraticSlippageFactor: '0.0001',
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-11-15T02:15:24.543614154Z',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { OracleBanner } from './oracle-banner';
|
||||
|
||||
const mockOracleData = {
|
||||
data: {
|
||||
dataSourceSpecId: 'someId',
|
||||
provider: {
|
||||
name: 'Test oracle',
|
||||
url: 'https://zombo.com',
|
||||
description_markdown:
|
||||
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||
oracle: {
|
||||
status: 'COMPROMISED',
|
||||
status_reason: 'Reason here',
|
||||
first_verified: '2021-01-01T00:00:00Z',
|
||||
last_verified: '2022-01-01T00:00:00Z',
|
||||
type: 'public_key',
|
||||
public_key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
|
||||
},
|
||||
proofs: [
|
||||
{
|
||||
format: 'url',
|
||||
available: true,
|
||||
type: 'web',
|
||||
url: 'https://proofweb.com',
|
||||
},
|
||||
{
|
||||
format: 'signed_message',
|
||||
available: true,
|
||||
type: 'public_key',
|
||||
public_key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
|
||||
message: 'SOMEHEX',
|
||||
},
|
||||
],
|
||||
github_link:
|
||||
'https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/eth_address-0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC.toml',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../../hooks', () => ({
|
||||
...jest.requireActual('../../hooks'),
|
||||
useMarketOracle: jest.fn((...args) => {
|
||||
return mockOracleData;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('OracleBanner', () => {
|
||||
it('should render successfully', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={[]} addTypename={false}>
|
||||
<OracleBanner marketId="someMarketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('oracle-banner-status')).toHaveTextContent(
|
||||
'COMPROMISED'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('oracle-banner-dialog-trigger')
|
||||
).toHaveTextContent('Show more');
|
||||
fireEvent.click(screen.getByTestId('oracle-banner-dialog-trigger'));
|
||||
expect(screen.getByTestId('oracle-full-profile')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
MarketDataQueryVariables,
|
||||
} from './__generated__/market-data';
|
||||
import { getMarketPrice } from './get-price';
|
||||
import { isMarketInAuction } from './is-market-in-auction';
|
||||
|
||||
export type MarketData = MarketDataFieldsFragment;
|
||||
|
||||
@@ -115,14 +116,25 @@ export const staticMarketDataProvider = makeDerivedDataProvider<
|
||||
});
|
||||
});
|
||||
|
||||
export const marketTradingModeProvider = makeDerivedDataProvider<
|
||||
MarketDataFieldsFragment['marketTradingMode'] | undefined,
|
||||
never,
|
||||
MarketDataQueryVariables
|
||||
>(
|
||||
[marketDataProvider],
|
||||
(parts, variables, prevData) =>
|
||||
(parts[0] as ReturnType<typeof getData>)?.marketTradingMode
|
||||
);
|
||||
|
||||
export const fundingRateProvider = makeDerivedDataProvider<
|
||||
string,
|
||||
never,
|
||||
MarketDataQueryVariables
|
||||
>([marketDataProvider], (parts) => {
|
||||
return (
|
||||
(parts[0] as ReturnType<typeof getData>)?.productData?.fundingRate || null
|
||||
);
|
||||
const marketData = parts[0] as ReturnType<typeof getData>;
|
||||
return marketData && !isMarketInAuction(marketData.marketTradingMode)
|
||||
? marketData?.productData?.fundingRate || null
|
||||
: null;
|
||||
});
|
||||
|
||||
export const useFundingRate = (marketId?: string, skip?: boolean) =>
|
||||
@@ -132,6 +144,23 @@ export const useFundingRate = (marketId?: string, skip?: boolean) =>
|
||||
skip: skip || !marketId,
|
||||
});
|
||||
|
||||
export const externalTwapProvider = makeDerivedDataProvider<
|
||||
string,
|
||||
never,
|
||||
MarketDataQueryVariables
|
||||
>([marketDataProvider], (parts) => {
|
||||
return (
|
||||
(parts[0] as ReturnType<typeof getData>)?.productData?.externalTwap || null
|
||||
);
|
||||
});
|
||||
|
||||
export const useExternalTwap = (marketId?: string, skip?: boolean) =>
|
||||
useDataProvider({
|
||||
dataProvider: externalTwapProvider,
|
||||
variables: { marketId: marketId || '' },
|
||||
skip: skip || !marketId,
|
||||
});
|
||||
|
||||
export const useStaticMarketData = (marketId?: string, skip?: boolean) => {
|
||||
return useDataProvider({
|
||||
dataProvider: staticMarketDataProvider,
|
||||
@@ -139,3 +168,11 @@ export const useStaticMarketData = (marketId?: string, skip?: boolean) => {
|
||||
skip: skip || !marketId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useMarketTradingMode = (marketId?: string, skip?: boolean) => {
|
||||
return useDataProvider({
|
||||
dataProvider: marketTradingModeProvider,
|
||||
variables: { marketId: marketId || '' },
|
||||
skip: skip || !marketId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -100,6 +100,34 @@ export const NetworkParams = {
|
||||
'governance_proposal_freeform_requiredMajority',
|
||||
governance_proposal_freeform_minProposerBalance:
|
||||
'governance_proposal_freeform_minProposerBalance',
|
||||
governance_proposal_referralProgram_minClose:
|
||||
'governance_proposal_referralProgram_minClose',
|
||||
governance_proposal_referralProgram_minEnact:
|
||||
'governance_proposal_referralProgram_minEnact',
|
||||
governance_proposal_referralProgram_minProposerBalance:
|
||||
'governance_proposal_referralProgram_minProposerBalance',
|
||||
governance_proposal_referralProgram_minVoterBalance:
|
||||
'governance_proposal_referralProgram_minVoterBalance',
|
||||
governance_proposal_referralProgram_requiredMajority:
|
||||
'governance_proposal_referralProgram_requiredMajority',
|
||||
governance_proposal_referralProgram_requiredParticipation:
|
||||
'governance_proposal_referralProgram_requiredParticipation',
|
||||
governance_proposal_VolumeDiscountProgram_maxClose:
|
||||
'governance_proposal_VolumeDiscountProgram_maxClose',
|
||||
governance_proposal_VolumeDiscountProgram_maxEnact:
|
||||
'governance_proposal_VolumeDiscountProgram_maxEnact',
|
||||
governance_proposal_VolumeDiscountProgram_minClose:
|
||||
'governance_proposal_VolumeDiscountProgram_minClose',
|
||||
governance_proposal_VolumeDiscountProgram_minEnact:
|
||||
'governance_proposal_VolumeDiscountProgram_minEnact',
|
||||
governance_proposal_VolumeDiscountProgram_minProposerBalance:
|
||||
'governance_proposal_VolumeDiscountProgram_minProposerBalance',
|
||||
governance_proposal_VolumeDiscountProgram_minVoterBalance:
|
||||
'governance_proposal_VolumeDiscountProgram_minVoterBalance',
|
||||
governance_proposal_VolumeDiscountProgram_requiredMajority:
|
||||
'governance_proposal_VolumeDiscountProgram_requiredMajority',
|
||||
governance_proposal_VolumeDiscountProgram_requiredParticipation:
|
||||
'governance_proposal_VolumeDiscountProgram_requiredParticipation',
|
||||
validators_delegation_minAmount: 'validators_delegation_minAmount',
|
||||
spam_protection_minimumWithdrawalQuantumMultiple:
|
||||
'spam_protection_minimumWithdrawalQuantumMultiple',
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
fragment UpdateMarketStateFields on UpdateMarketState {
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
updateType
|
||||
price
|
||||
}
|
||||
|
||||
fragment NewMarketFields on NewMarket {
|
||||
instrument {
|
||||
name
|
||||
@@ -438,10 +452,17 @@ fragment NewMarketSuccessorFields on NewMarket {
|
||||
}
|
||||
}
|
||||
|
||||
fragment SuccessorProposalListFields on Proposal {
|
||||
fragment MarketViewProposalFields on Proposal {
|
||||
id
|
||||
state
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
__typename
|
||||
... on UpdateMarketState {
|
||||
...UpdateMarketStateFields
|
||||
}
|
||||
... on NewMarket {
|
||||
...NewMarketSuccessorFields
|
||||
}
|
||||
@@ -449,12 +470,21 @@ fragment SuccessorProposalListFields on Proposal {
|
||||
}
|
||||
}
|
||||
|
||||
query SuccessorProposalsList {
|
||||
proposalsConnection(proposalType: TYPE_NEW_MARKET, inState: STATE_OPEN) {
|
||||
query MarketViewProposals(
|
||||
$proposalType: ProposalType
|
||||
$inState: ProposalState
|
||||
) {
|
||||
proposalsConnection(proposalType: $proposalType, inState: $inState) {
|
||||
edges {
|
||||
node {
|
||||
...SuccessorProposalListFields
|
||||
...MarketViewProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscription MarketViewLiveProposals {
|
||||
proposals {
|
||||
...MarketViewProposalFields
|
||||
}
|
||||
}
|
||||
|
||||
+88
-24
@@ -3,6 +3,8 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type UpdateMarketStateFieldsFragment = { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } };
|
||||
|
||||
export type NewMarketFieldsFragment = { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | null }, riskParameters: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null };
|
||||
|
||||
export type UpdateMarketFieldsFragment = { __typename?: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', metadata?: Array<string | null> | null, instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'UpdatePerpetualProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, riskParameters: { __typename: 'UpdateMarketLogNormalRiskModel', logNormal?: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | null } | { __typename: 'UpdateMarketSimpleRiskModel', simple?: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } | null } } };
|
||||
@@ -29,12 +31,20 @@ export type ProposalsListQuery = { __typename?: 'Query', proposalsConnection?: {
|
||||
|
||||
export type NewMarketSuccessorFieldsFragment = { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null };
|
||||
|
||||
export type SuccessorProposalListFieldsFragment = { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||
export type MarketViewProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } };
|
||||
|
||||
export type SuccessorProposalsListQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
export type MarketViewProposalsQueryVariables = Types.Exact<{
|
||||
proposalType?: Types.InputMaybe<Types.ProposalType>;
|
||||
inState?: Types.InputMaybe<Types.ProposalState>;
|
||||
}>;
|
||||
|
||||
|
||||
export type SuccessorProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } } | null> | null } | null };
|
||||
export type MarketViewProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } } } | null> | null } | null };
|
||||
|
||||
export type MarketViewLiveProposalsSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type MarketViewLiveProposalsSubscription = { __typename?: 'Subscription', proposals: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } } };
|
||||
|
||||
export const NewTransferFieldsFragmentDoc = gql`
|
||||
fragment NewTransferFields on NewTransfer {
|
||||
@@ -451,6 +461,21 @@ ${UpdateMarketFieldsFragmentDoc}
|
||||
${NewAssetFieldsFragmentDoc}
|
||||
${UpdateAssetFieldsFragmentDoc}
|
||||
${UpdateNetworkParameterFieldsFragmentDoc}`;
|
||||
export const UpdateMarketStateFieldsFragmentDoc = gql`
|
||||
fragment UpdateMarketStateFields on UpdateMarketState {
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
updateType
|
||||
price
|
||||
}
|
||||
`;
|
||||
export const NewMarketSuccessorFieldsFragmentDoc = gql`
|
||||
fragment NewMarketSuccessorFields on NewMarket {
|
||||
instrument {
|
||||
@@ -461,18 +486,26 @@ export const NewMarketSuccessorFieldsFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const SuccessorProposalListFieldsFragmentDoc = gql`
|
||||
fragment SuccessorProposalListFields on Proposal {
|
||||
export const MarketViewProposalFieldsFragmentDoc = gql`
|
||||
fragment MarketViewProposalFields on Proposal {
|
||||
id
|
||||
state
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
__typename
|
||||
... on UpdateMarketState {
|
||||
...UpdateMarketStateFields
|
||||
}
|
||||
... on NewMarket {
|
||||
...NewMarketSuccessorFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${NewMarketSuccessorFieldsFragmentDoc}`;
|
||||
${UpdateMarketStateFieldsFragmentDoc}
|
||||
${NewMarketSuccessorFieldsFragmentDoc}`;
|
||||
export const ProposalsListDocument = gql`
|
||||
query ProposalsList($proposalType: ProposalType, $inState: ProposalState) {
|
||||
proposalsConnection(proposalType: $proposalType, inState: $inState) {
|
||||
@@ -513,41 +546,72 @@ export function useProposalsListLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti
|
||||
export type ProposalsListQueryHookResult = ReturnType<typeof useProposalsListQuery>;
|
||||
export type ProposalsListLazyQueryHookResult = ReturnType<typeof useProposalsListLazyQuery>;
|
||||
export type ProposalsListQueryResult = Apollo.QueryResult<ProposalsListQuery, ProposalsListQueryVariables>;
|
||||
export const SuccessorProposalsListDocument = gql`
|
||||
query SuccessorProposalsList {
|
||||
proposalsConnection(proposalType: TYPE_NEW_MARKET, inState: STATE_OPEN) {
|
||||
export const MarketViewProposalsDocument = gql`
|
||||
query MarketViewProposals($proposalType: ProposalType, $inState: ProposalState) {
|
||||
proposalsConnection(proposalType: $proposalType, inState: $inState) {
|
||||
edges {
|
||||
node {
|
||||
...SuccessorProposalListFields
|
||||
...MarketViewProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${SuccessorProposalListFieldsFragmentDoc}`;
|
||||
${MarketViewProposalFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useSuccessorProposalsListQuery__
|
||||
* __useMarketViewProposalsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSuccessorProposalsListQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSuccessorProposalsListQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* To run a query within a React component, call `useMarketViewProposalsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useMarketViewProposalsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSuccessorProposalsListQuery({
|
||||
* const { data, loading, error } = useMarketViewProposalsQuery({
|
||||
* variables: {
|
||||
* proposalType: // value for 'proposalType'
|
||||
* inState: // value for 'inState'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useMarketViewProposalsQuery(baseOptions?: Apollo.QueryHookOptions<MarketViewProposalsQuery, MarketViewProposalsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<MarketViewProposalsQuery, MarketViewProposalsQueryVariables>(MarketViewProposalsDocument, options);
|
||||
}
|
||||
export function useMarketViewProposalsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketViewProposalsQuery, MarketViewProposalsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<MarketViewProposalsQuery, MarketViewProposalsQueryVariables>(MarketViewProposalsDocument, options);
|
||||
}
|
||||
export type MarketViewProposalsQueryHookResult = ReturnType<typeof useMarketViewProposalsQuery>;
|
||||
export type MarketViewProposalsLazyQueryHookResult = ReturnType<typeof useMarketViewProposalsLazyQuery>;
|
||||
export type MarketViewProposalsQueryResult = Apollo.QueryResult<MarketViewProposalsQuery, MarketViewProposalsQueryVariables>;
|
||||
export const MarketViewLiveProposalsDocument = gql`
|
||||
subscription MarketViewLiveProposals {
|
||||
proposals {
|
||||
...MarketViewProposalFields
|
||||
}
|
||||
}
|
||||
${MarketViewProposalFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useMarketViewLiveProposalsSubscription__
|
||||
*
|
||||
* To run a query within a React component, call `useMarketViewLiveProposalsSubscription` and pass it any options that fit your needs.
|
||||
* When your component renders, `useMarketViewLiveProposalsSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useMarketViewLiveProposalsSubscription({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSuccessorProposalsListQuery(baseOptions?: Apollo.QueryHookOptions<SuccessorProposalsListQuery, SuccessorProposalsListQueryVariables>) {
|
||||
export function useMarketViewLiveProposalsSubscription(baseOptions?: Apollo.SubscriptionHookOptions<MarketViewLiveProposalsSubscription, MarketViewLiveProposalsSubscriptionVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<SuccessorProposalsListQuery, SuccessorProposalsListQueryVariables>(SuccessorProposalsListDocument, options);
|
||||
return Apollo.useSubscription<MarketViewLiveProposalsSubscription, MarketViewLiveProposalsSubscriptionVariables>(MarketViewLiveProposalsDocument, options);
|
||||
}
|
||||
export function useSuccessorProposalsListLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SuccessorProposalsListQuery, SuccessorProposalsListQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<SuccessorProposalsListQuery, SuccessorProposalsListQueryVariables>(SuccessorProposalsListDocument, options);
|
||||
}
|
||||
export type SuccessorProposalsListQueryHookResult = ReturnType<typeof useSuccessorProposalsListQuery>;
|
||||
export type SuccessorProposalsListLazyQueryHookResult = ReturnType<typeof useSuccessorProposalsListLazyQuery>;
|
||||
export type SuccessorProposalsListQueryResult = Apollo.QueryResult<SuccessorProposalsListQuery, SuccessorProposalsListQueryVariables>;
|
||||
export type MarketViewLiveProposalsSubscriptionHookResult = ReturnType<typeof useMarketViewLiveProposalsSubscription>;
|
||||
export type MarketViewLiveProposalsSubscriptionResult = Apollo.SubscriptionResult<MarketViewLiveProposalsSubscription>;
|
||||
@@ -1,10 +1,20 @@
|
||||
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
||||
import produce from 'immer';
|
||||
import type {
|
||||
ProposalsListQuery,
|
||||
ProposalsListQueryVariables,
|
||||
ProposalListFieldsFragment,
|
||||
MarketViewLiveProposalsSubscription,
|
||||
MarketViewProposalFieldsFragment,
|
||||
MarketViewProposalsQuery,
|
||||
MarketViewProposalsQueryVariables,
|
||||
} from './__generated__/Proposals';
|
||||
import { ProposalsListDocument } from './__generated__/Proposals';
|
||||
import {
|
||||
MarketViewLiveProposalsDocument,
|
||||
MarketViewProposalsDocument,
|
||||
ProposalsListDocument,
|
||||
} from './__generated__/Proposals';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
|
||||
const getData = (responseData: ProposalsListQuery | null) =>
|
||||
responseData?.proposalsConnection?.edges
|
||||
@@ -31,3 +41,42 @@ export const proposalsDataProvider = makeDataProvider<
|
||||
errorPolicyGuard: (errors) =>
|
||||
errors.every((e) => e.message.match(/failed to get asset for ID/)),
|
||||
});
|
||||
|
||||
const update = (
|
||||
data: MarketViewProposalFieldsFragment[] | null,
|
||||
delta: MarketViewProposalFieldsFragment
|
||||
) => {
|
||||
const updateData = produce(data || [], (draft) => {
|
||||
const { id } = delta;
|
||||
const index = draft.findIndex((item) => item.id === id);
|
||||
if (index === -1) {
|
||||
draft.unshift(delta);
|
||||
} else {
|
||||
const currNode = draft[index];
|
||||
draft[index] = {
|
||||
...currNode,
|
||||
...delta,
|
||||
};
|
||||
}
|
||||
});
|
||||
return updateData;
|
||||
};
|
||||
|
||||
const getMarketProposalsData = (
|
||||
responseData: MarketViewProposalsQuery | null
|
||||
) => removePaginationWrapper(responseData?.proposalsConnection?.edges) || [];
|
||||
|
||||
export const marketViewProposalsDataProvider = makeDataProvider<
|
||||
MarketViewProposalsQuery,
|
||||
MarketViewProposalFieldsFragment[],
|
||||
MarketViewLiveProposalsSubscription,
|
||||
MarketViewProposalFieldsFragment,
|
||||
MarketViewProposalsQueryVariables
|
||||
>({
|
||||
query: MarketViewProposalsDocument,
|
||||
subscriptionQuery: MarketViewLiveProposalsDocument,
|
||||
update,
|
||||
getDelta: (subscriptionData: MarketViewLiveProposalsSubscription) =>
|
||||
subscriptionData.proposals,
|
||||
getData: getMarketProposalsData,
|
||||
});
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './use-update-network-paramaters-toasts';
|
||||
export * from './use-successor-market-proposal-details';
|
||||
export * from './use-new-transfer-proposal-details';
|
||||
export * from './use-cancel-transfer-proposal-details';
|
||||
export * from './use-market-view-proposals';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketViewProposalsDataProvider } from '../proposals-data-provider';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
|
||||
export const useMarketViewProposals = ({
|
||||
skip = false,
|
||||
typename,
|
||||
proposalType,
|
||||
inState,
|
||||
}: {
|
||||
typename: Types.ProposalChange['__typename'];
|
||||
skip?: boolean;
|
||||
inState: Types.ProposalState;
|
||||
proposalType?: Types.ProposalType;
|
||||
}) => {
|
||||
const variables = {
|
||||
inState,
|
||||
...(proposalType ? { proposalType } : null),
|
||||
};
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketViewProposalsDataProvider,
|
||||
skip,
|
||||
variables,
|
||||
});
|
||||
return (data || []).filter(
|
||||
(item) => item.terms.change.__typename === typename
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,8 @@ describe('TradesTable', () => {
|
||||
await act(async () => {
|
||||
render(<TradesTable rowData={[trade]} />);
|
||||
});
|
||||
// 6005-THIS-001
|
||||
// 6005-THIS-002
|
||||
const expectedHeaders = ['Price', 'Size', 'Created at'];
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
@@ -31,6 +33,8 @@ describe('TradesTable', () => {
|
||||
});
|
||||
|
||||
it('should format number and data columns', async () => {
|
||||
// 6005-THIS-003
|
||||
// 6005-THIS-004
|
||||
await act(async () => {
|
||||
render(<TradesTable rowData={[trade]} />);
|
||||
});
|
||||
@@ -73,4 +77,34 @@ describe('TradesTable', () => {
|
||||
expect(sizeCells[1]).not.toHaveClass(SELL_CLASS);
|
||||
expect(sizeCells[1]).not.toHaveClass(BUY_CLASS);
|
||||
});
|
||||
|
||||
it('should be in created at desc order', async () => {
|
||||
// 6005-THIS-005
|
||||
// 6005-THIS-006
|
||||
const earlierDate = new Date(trade.createdAt);
|
||||
earlierDate.setMinutes(earlierDate.getMinutes() - 1);
|
||||
const trade2 = {
|
||||
...trade,
|
||||
id: 'trade-id-2',
|
||||
price: (Number(trade.price) + 10).toString(),
|
||||
size: (Number(trade.size) - 10).toString(),
|
||||
createdAt: earlierDate.toISOString(),
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
render(<TradesTable rowData={[trade2, trade]} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
|
||||
const createdAtCells = cells.filter(
|
||||
(cell) => cell.getAttribute('col-id') === 'createdAt'
|
||||
);
|
||||
|
||||
const [firstDateCell, secondDateCell] = createdAtCells;
|
||||
|
||||
// Compare the times as strings
|
||||
expect(firstDateCell.textContent).toBe('6:59:00 PM');
|
||||
expect(secondDateCell.textContent).toBe('7:00:00 PM');
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+141
-30
@@ -1287,6 +1287,39 @@ export type Filter = {
|
||||
key: PropertyKey;
|
||||
};
|
||||
|
||||
/** The funding payment from a perpetual market. */
|
||||
export type FundingPayment = {
|
||||
__typename?: 'FundingPayment';
|
||||
/** Amount transferred */
|
||||
amount?: Maybe<Scalars['String']>;
|
||||
/** Sequence number of the funding period the funding payment belongs to. */
|
||||
fundingPeriodSeq: Scalars['Int'];
|
||||
/** Market the funding payment applies to. */
|
||||
marketId: Scalars['ID'];
|
||||
/** Party the funding payment applies to. */
|
||||
partyId: Scalars['ID'];
|
||||
/** RFC3339Nano timestamp when the data point was received. */
|
||||
timestamp: Scalars['Timestamp'];
|
||||
};
|
||||
|
||||
/** Connection type for funding payment */
|
||||
export type FundingPaymentConnection = {
|
||||
__typename?: 'FundingPaymentConnection';
|
||||
/** List of funding payments */
|
||||
edges: Array<FundingPaymentEdge>;
|
||||
/** Pagination information */
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
/** Edge type for funding payment */
|
||||
export type FundingPaymentEdge = {
|
||||
__typename?: 'FundingPaymentEdge';
|
||||
/** Cursor identifying the funding payment */
|
||||
cursor: Scalars['String'];
|
||||
/** The funding payment */
|
||||
node: FundingPayment;
|
||||
};
|
||||
|
||||
/** Details of a funding interval for a perpetual market. */
|
||||
export type FundingPeriod = {
|
||||
__typename?: 'FundingPeriod';
|
||||
@@ -4097,6 +4130,8 @@ export type Query = {
|
||||
coreSnapshots?: Maybe<CoreSnapshotConnection>;
|
||||
/** Get the current referral program */
|
||||
currentReferralProgram?: Maybe<CurrentReferralProgram>;
|
||||
/** Get the current volume discount program */
|
||||
currentVolumeDiscountProgram?: Maybe<VolumeDiscountProgram>;
|
||||
/** Find a deposit using its ID */
|
||||
deposit?: Maybe<Deposit>;
|
||||
/** Fetch all deposits */
|
||||
@@ -4128,6 +4163,8 @@ export type Query = {
|
||||
estimatePosition?: Maybe<PositionEstimate>;
|
||||
/** Query for historic ethereum key rotations */
|
||||
ethereumKeyRotations: EthereumKeyRotationsConnection;
|
||||
/** Funding payment for perpetual markets. */
|
||||
fundingPayments: FundingPaymentConnection;
|
||||
/**
|
||||
* Funding period data points for a perpetual market. The data points within a funding period are used to calculate the
|
||||
* time-weighted average price (TWAP), funding rate and funding payments for each funding period.
|
||||
@@ -4196,6 +4233,8 @@ export type Query = {
|
||||
/** Get referrer fee and discount stats */
|
||||
referralFeeStats?: Maybe<ReferralSetFeeStats>;
|
||||
referralSetReferees: ReferralSetRefereeConnection;
|
||||
/** Get referral set statistics */
|
||||
referralSetStats: ReferralSetStatsConnection;
|
||||
/** List referral sets */
|
||||
referralSets: ReferralSetConnection;
|
||||
/** Get statistics about the Vega node */
|
||||
@@ -4224,6 +4263,8 @@ export type Query = {
|
||||
transfer?: Maybe<Transfer>;
|
||||
/** Get a list of all transfers for a public key */
|
||||
transfersConnection?: Maybe<TransferConnection>;
|
||||
/** Get volume discount statistics */
|
||||
volumeDiscountStats: VolumeDiscountStatsConnection;
|
||||
/** Find a withdrawal using its ID */
|
||||
withdrawal?: Maybe<Withdrawal>;
|
||||
/** Fetch all withdrawals */
|
||||
@@ -4369,6 +4410,14 @@ export type QueryethereumKeyRotationsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QueryfundingPaymentsArgs = {
|
||||
marketId?: InputMaybe<Scalars['ID']>;
|
||||
pagination?: InputMaybe<Pagination>;
|
||||
partyId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QueryfundingPeriodDataPointsArgs = {
|
||||
dateRange?: InputMaybe<DateRange>;
|
||||
@@ -4568,6 +4617,15 @@ export type QueryreferralSetRefereesArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QueryreferralSetStatsArgs = {
|
||||
epoch?: InputMaybe<Scalars['Int']>;
|
||||
id: Scalars['ID'];
|
||||
pagination?: InputMaybe<Pagination>;
|
||||
partyId?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QueryreferralSetsArgs = {
|
||||
id?: InputMaybe<Scalars['ID']>;
|
||||
@@ -4642,6 +4700,14 @@ export type QuerytransfersConnectionArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QueryvolumeDiscountStatsArgs = {
|
||||
epoch?: InputMaybe<Scalars['Int']>;
|
||||
pagination?: InputMaybe<Pagination>;
|
||||
partyId?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
|
||||
/** Queries allow a caller to read data and filter data via GraphQL. */
|
||||
export type QuerywithdrawalArgs = {
|
||||
id: Scalars['ID'];
|
||||
@@ -4700,18 +4766,6 @@ export type RecurringTransfer = {
|
||||
startEpoch: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type RefereeStats = {
|
||||
__typename?: 'RefereeStats';
|
||||
/** Discount factor applied to the party. */
|
||||
discountFactor: Scalars['String'];
|
||||
/** Current referee notional taker volume */
|
||||
epochNotionalTakerVolume: Scalars['String'];
|
||||
/** Unique ID of the party. */
|
||||
partyId: Scalars['ID'];
|
||||
/** Reward factor applied to the party. */
|
||||
rewardFactor: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Referral program information */
|
||||
export type ReferralProgram = {
|
||||
__typename?: 'ReferralProgram';
|
||||
@@ -4742,22 +4796,10 @@ export type ReferralSet = {
|
||||
id: Scalars['ID'];
|
||||
/** Party that created the set. */
|
||||
referrer: Scalars['ID'];
|
||||
/**
|
||||
* Referral set statistics for the latest or specific epoch.
|
||||
* If provided the results can be filtered for a specific referee
|
||||
*/
|
||||
stats?: Maybe<ReferralSetStats>;
|
||||
/** Timestamp as RFC3339Nano when the referral set was updated. */
|
||||
updatedAt: Scalars['Timestamp'];
|
||||
};
|
||||
|
||||
|
||||
/** Data relating to a referral set. */
|
||||
export type ReferralSetstatsArgs = {
|
||||
epoch?: InputMaybe<Scalars['Int']>;
|
||||
referee?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
/** Connection type for retrieving cursor-based paginated referral set information */
|
||||
export type ReferralSetConnection = {
|
||||
__typename?: 'ReferralSetConnection';
|
||||
@@ -4828,14 +4870,36 @@ export type ReferralSetRefereeEdge = {
|
||||
|
||||
export type ReferralSetStats = {
|
||||
__typename?: 'ReferralSetStats';
|
||||
/** Epoch at which the set's statistics are updated. */
|
||||
atEpoch?: Maybe<Scalars['Int']>;
|
||||
/** Referees' statistics for that epoch. */
|
||||
referees_stats: Array<RefereeStats>;
|
||||
/** Epoch at which the statistics are updated. */
|
||||
atEpoch: Scalars['Int'];
|
||||
/** Discount factor applied to the party. */
|
||||
discountFactor: Scalars['String'];
|
||||
/** Current referee notional taker volume */
|
||||
epochNotionalTakerVolume: Scalars['String'];
|
||||
/** Unique ID of the party. */
|
||||
partyId: Scalars['ID'];
|
||||
/** Running volume for the set based on the window length of the current referral program. */
|
||||
referralSetRunningNotionalTakerVolume: Scalars['String'];
|
||||
/** Unique ID of the set */
|
||||
setId: Scalars['ID'];
|
||||
/** Reward factor applied to the party. */
|
||||
rewardFactor: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Connection type for retrieving cursor-based paginated referral set statistics information */
|
||||
export type ReferralSetStatsConnection = {
|
||||
__typename?: 'ReferralSetStatsConnection';
|
||||
/** The referral set statistics in this connection */
|
||||
edges: Array<Maybe<ReferralSetStatsEdge>>;
|
||||
/** The pagination information */
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
/** Edge type containing the referral set statistics and cursor information returned by a ReferralSetStatsConnection */
|
||||
export type ReferralSetStatsEdge = {
|
||||
__typename?: 'ReferralSetStatsEdge';
|
||||
/** The cursor for this referral set statistics */
|
||||
cursor: Scalars['String'];
|
||||
/** The referral set statistics */
|
||||
node: ReferralSetStats;
|
||||
};
|
||||
|
||||
/** Rewards generated for referrers by each of their referees */
|
||||
@@ -6137,6 +6201,53 @@ export type VolumeBenefitTier = {
|
||||
volumeDiscountFactor: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Volume discount program information */
|
||||
export type VolumeDiscountProgram = {
|
||||
__typename?: 'VolumeDiscountProgram';
|
||||
/** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */
|
||||
benefitTiers: Array<VolumeBenefitTier>;
|
||||
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the programs will end and benefits will be disabled. */
|
||||
endOfProgramTimestamp: Scalars['Timestamp'];
|
||||
/** Timestamp as RFC3339Nano when the program ended. If present, the current program has ended and no program is currently running. */
|
||||
endedAt?: Maybe<Scalars['Timestamp']>;
|
||||
/** Unique ID generated from the proposal that created this program. */
|
||||
id: Scalars['ID'];
|
||||
/** Incremental version of the program. It is incremented each time the volume discount program is edited. */
|
||||
version: Scalars['Int'];
|
||||
/** Number of epochs over which to evaluate parties' running volume. */
|
||||
windowLength: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type VolumeDiscountStats = {
|
||||
__typename?: 'VolumeDiscountStats';
|
||||
/** Epoch at which the statistics are updated. */
|
||||
atEpoch: Scalars['Int'];
|
||||
/** Discount factor applied to the party. */
|
||||
discountFactor: Scalars['String'];
|
||||
/** Unique ID of the party. */
|
||||
partyId: Scalars['ID'];
|
||||
/** Party's running volume. */
|
||||
runningVolume: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Connection type for retrieving cursor-based paginated volume discount statistics information */
|
||||
export type VolumeDiscountStatsConnection = {
|
||||
__typename?: 'VolumeDiscountStatsConnection';
|
||||
/** The volume discount statistics in this connection */
|
||||
edges: Array<Maybe<VolumeDiscountStatsEdge>>;
|
||||
/** The pagination information */
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
/** Edge type containing the volume discount statistics and cursor information returned by a VolumeDiscountStatsConnection */
|
||||
export type VolumeDiscountStatsEdge = {
|
||||
__typename?: 'VolumeDiscountStatsEdge';
|
||||
/** The cursor for this volume discount statistics */
|
||||
cursor: Scalars['String'];
|
||||
/** The volume discount statistics */
|
||||
node: VolumeDiscountStats;
|
||||
};
|
||||
|
||||
export type Vote = {
|
||||
__typename?: 'Vote';
|
||||
/** RFC3339Nano time and date when the vote reached Vega network */
|
||||
|
||||
@@ -74,9 +74,13 @@ export const KeyValueTableRow = ({
|
||||
{ 'flex-row items-center': inline },
|
||||
className
|
||||
);
|
||||
const dtClassNames = `break-words ${dtClassName}`;
|
||||
const dtClassNames = classNames(
|
||||
'break-words',
|
||||
dtClassName,
|
||||
'text-neutral-500 dark:text-neutral-300'
|
||||
);
|
||||
const ddClassNames = classNames(
|
||||
'break-words text-neutral-500 dark:text-neutral-300',
|
||||
'break-words',
|
||||
{
|
||||
'font-mono': numerical,
|
||||
},
|
||||
|
||||
@@ -90,12 +90,22 @@ When I look into market info I **Must** see following specification:
|
||||
- Risk model: (<a name="6002-MDET-209" href="#6002-MDET-209">6002-MDET-209</a>)
|
||||
- Tau
|
||||
- Risk Aversion Parameter
|
||||
- Risk parameters: (<a name="6002-MDET-210" href="#6002-MDET-210">6002-MDET-210</a>)
|
||||
- R
|
||||
- Sigma
|
||||
- Margin scaling factors (<a name="6002-MDET-210" href="#6002-MDET-210">6002-MDET-210</a>)
|
||||
- Linear Slippage Factor
|
||||
- Quadratic Slippage Factor
|
||||
- Search Level
|
||||
- Initial Margin
|
||||
- Collateral Release
|
||||
- Risk factors: (<a name="6002-MDET-211" href="#6002-MDET-211">6002-MDET-211</a>)
|
||||
- Short
|
||||
- Long
|
||||
- price monitoring bounds <i>(multiple bounds possible)</i>: (<a name="6002-MDET-212" href="#6002-MDET-212">6002-MDET-212</a>)
|
||||
- Short
|
||||
- Max Leverage Long
|
||||
- Max Leverage Short
|
||||
- Max Initial Leverage Long
|
||||
- Max Initial Leverage Short
|
||||
- Price monitoring bounds <i>(multiple bounds possible)</i>: (<a name="6002-MDET-212" href="#6002-MDET-212">6002-MDET-212</a>)
|
||||
- Highest Price
|
||||
- Lowest Price
|
||||
- Liquidity monitoring parameters: (<a name="6002-MDET-213" href="#6002-MDET-213">6002-MDET-213</a>)
|
||||
@@ -128,7 +138,7 @@ When I look into market info I **Must** see following governance information:
|
||||
|
||||
When I look at the succession line list I **Should** easily distinguish which market is the currently viewed market so I can see the ancestor-descendant relations between the current and other markets on the list.
|
||||
|
||||
## Market successor
|
||||
## Market successor & warning for termination
|
||||
|
||||
When I'm tranding on the market, I **Must** see there are:
|
||||
|
||||
@@ -139,3 +149,8 @@ When I'm tranding on the market, I **Must** see there are:
|
||||
- Proposal for the market successor : (<a name="6002-MDET-402" href="#6002-MDET-402">6002-MDET-402</a>)
|
||||
- link to governance proposal
|
||||
- name of the proposed market
|
||||
- Enacted proposal for the market termination: (<a name="6002-MDET-403" href="#6002-MDET-403">6002-MDET-403</a>)
|
||||
- I should be informed immediately when a close market proposal for a market I have a position in is successful
|
||||
- I can tell how much time remains before the market will close and/or the exact time of closure
|
||||
- I can tell what the final price will be at the time of closing
|
||||
- The notification is showed regardless of market type
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user