Compare commits

..
140 changed files with 1757 additions and 2994 deletions
+5 -5
View File
@@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V
This repository is managed using [Nx](https://nx.dev).
# 🔎 Applications in this repo
## 🔎 Applications in this repo
### [Block explorer](./apps/explorer)
@@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts.
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
# 🧱 Libraries in this repo
## 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit)
@@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve
Generic react helpers that can be used across multiple applications, along with other utilities.
# 💻 Develop
## 💻 Develop
### Set up
@@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 🐋 Hosting a console
## 🐋 Hosting a console
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
@@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
```
# 📑 License
## 📑 License
[MIT](./LICENSE)
@@ -1,11 +1,9 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
rationale {
title
description
}
id
rationale {
title
description
}
}
}
@@ -8,18 +8,16 @@ export type ExplorerProposalQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
rationale {
title
description
}
id
rationale {
title
description
}
}
}
@@ -3,11 +3,7 @@ import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import ProposalLink from './proposal-link';
import {
ExplorerProposalDocument,
type ExplorerProposalQuery,
type ExplorerProposalQueryVariables,
} from './__generated__/Proposal';
import { ExplorerProposalDocument } from './__generated__/Proposal';
import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) {
@@ -27,10 +23,7 @@ describe('Proposal link component', () => {
});
it('Renders the ID on error', async () => {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -47,22 +40,17 @@ describe('Proposal link component', () => {
});
it('Renders the proposal title when the query returns a result', async () => {
const proposalId = '123';
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: proposalId,
id: '123',
},
},
result: {
data: {
proposal: {
__typename: 'Proposal',
id: proposalId,
id: '123',
rationale: {
title: 'test-title',
description: 'test description',
@@ -72,16 +60,13 @@ describe('Proposal link component', () => {
},
};
const res = render(renderComponent(proposalId, [mock]));
expect(res.getByText(proposalId)).toBeInTheDocument();
const res = render(renderComponent('123', [mock]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument();
});
it('Leaves the proposal id when the market is not found', async () => {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -1,11 +1,7 @@
import {
useExplorerProposalQuery,
type ExplorerProposalQuery,
} from './__generated__/Proposal';
import { useExplorerProposalQuery } from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
text?: string;
@@ -20,13 +16,8 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
variables: { id },
});
const proposal = data?.proposal as Extract<
ExplorerProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const base = ENV.dataSources.governanceUrl;
const label = proposal?.rationale.title || id;
const label = data?.proposal?.rationale.title || id;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
@@ -1,7 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidationStrategyInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
@@ -95,8 +94,6 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
</>
)
)}
<h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
<LiquidationStrategyInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
@@ -1,9 +1,7 @@
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
state
rejectionReason
}
id
state
rejectionReason
}
}
@@ -8,17 +8,15 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
state
rejectionReason
}
id
state
rejectionReason
}
}
`;
@@ -14,18 +14,16 @@ export function format(date: string | undefined, def: string) {
return new Date().toLocaleDateString() || def;
}
type Proposal = Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
export function getDate(
data: ExplorerProposalStatusQuery | undefined,
terms: Terms
): string {
const DEFAULT = t('Unknown');
if (!proposal?.state) {
if (!data?.proposal?.state) {
return DEFAULT;
}
switch (proposal.state) {
switch (data.proposal.state) {
case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED':
@@ -64,11 +62,9 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
},
});
const proposal = data?.proposal as Proposal;
return (
<Lozenge className="font-sans text-xs float-right">
{getDate(proposal, terms)}
{getDate(data, terms)}
</Lozenge>
);
};
@@ -2,8 +2,17 @@ import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import type * as Apollo from '@apollo/client';
import type * as Types from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
type ProposalQueryResult = Apollo.QueryResult<
ExplorerProposalStatusQuery,
Types.Exact<{
id: string;
}>
>;
interface ProposalStatusIconProps {
id: string;
}
@@ -20,38 +29,29 @@ type IconAndLabel = {
* @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name
*/
export function useIconAndLabelForStatus(id: string): IconAndLabel {
const { data, loading, error } = useExplorerProposalStatusQuery({
variables: {
id,
},
});
const proposal = data?.proposal as Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getIconAndLabelForStatus(
res: ProposalQueryResult
): IconAndLabel {
const DEFAULT: IconAndLabel = {
icon: 'error',
label: t('Proposal state unknown'),
};
if (loading) {
if (res.loading) {
return {
icon: 'more',
label: t('Loading data'),
};
}
if (!data?.proposal || error) {
if (!res?.data?.proposal || res.error) {
return {
icon: 'error',
label: error?.message || DEFAULT.label,
label: res.error?.message || DEFAULT.label,
};
}
switch (proposal.state) {
switch (res.data.proposal.state) {
case 'STATE_DECLINED':
return {
icon: 'stop',
@@ -99,7 +99,13 @@ export function useIconAndLabelForStatus(id: string): IconAndLabel {
/**
*/
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = useIconAndLabelForStatus(id);
const { icon, label } = getIconAndLabelForStatus(
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
return (
<div className="float-left mr-3">
@@ -61,4 +61,53 @@ describe('TxsListNavigation', () => {
expect(nextPageMock).toHaveBeenCalledTimes(1);
});
it('disables "Older" button if hasMoreTxs is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Older')).toBeDisabled();
});
it('disables "Newer" button if hasPreviousPage is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
});
it('disables both buttons when more and previous are false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
expect(screen.getByText('Older')).toBeDisabled();
});
});
@@ -10,8 +10,7 @@ export interface TxListNavigationProps {
loading?: boolean;
hasPreviousPage: boolean;
hasMoreTxs: boolean;
children?: React.ReactNode;
isEmpty?: boolean;
children: React.ReactNode;
}
/**
* Displays a list of transactions with filters and controls to navigate through the list.
@@ -22,8 +21,9 @@ export const TxsListNavigation = ({
refreshTxs,
nextPage,
previousPage,
hasMoreTxs,
hasPreviousPage,
children,
isEmpty,
loading = false,
}: TxListNavigationProps) => {
return (
@@ -35,6 +35,7 @@ export const TxsListNavigation = ({
<Button
className="mr-2"
size="xs"
disabled={!hasPreviousPage || loading}
onClick={() => {
previousPage();
}}
@@ -43,7 +44,7 @@ export const TxsListNavigation = ({
</Button>
<Button
size="xs"
disabled={isEmpty}
disabled={!hasMoreTxs}
onClick={() => {
nextPage();
}}
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count);
url.searchParams.append('after', params.after);
} else {
url.searchParams.append('first', count);
url.searchParams.append('last', count);
}
// Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10,
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?first=10';
const expectedUrl = 'https://example.com/transactions?last=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions',
};
const expectedUrl =
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
+6 -6
View File
@@ -31,14 +31,14 @@ export interface IUseTxsData {
}
export const useTxsData = ({
count = 50,
count = 25,
before,
after,
filters,
party,
}: IUseTxsData) => {
const [, setSearchParams] = useSearchParams();
let hasMoreTxs = false;
let hasMoreTxs = true;
let txsData: BlockExplorerTransactionResult[] = [];
const url = getTxsDataUrl({
@@ -60,8 +60,8 @@ export const useTxsData = ({
}
const nextPage = useCallback(() => {
const before = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { before };
const after = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { after };
if (filters) {
params.filters = Array.from(filters).join(',');
}
@@ -69,8 +69,8 @@ export const useTxsData = ({
}, [filters, data, setSearchParams]);
const previousPage = useCallback(() => {
const after = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { after };
const before = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { before };
if (filters && filters.size > 0 && filters.size === 1) {
params.filters = Array.from(filters)[0];
}
@@ -51,10 +51,9 @@ export const TxsListFiltered = () => {
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasMoreTxs}
hasPreviousPage={true}
loading={loading}
hasMoreTxs={hasMoreTxs}
isEmpty={txsData.length === 0}
>
<TxsFilter
filters={filters}
@@ -71,16 +70,7 @@ export const TxsListFiltered = () => {
txs={txsData}
loadMoreTxs={nextPage}
error={error}
className="mb-4 w-full min-w-[400px]"
/>
<TxsListNavigation
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasMoreTxs}
loading={loading}
hasMoreTxs={hasMoreTxs}
isEmpty={txsData.length === 0}
className="mb-28 w-full min-w-[400px]"
/>
</>
);
@@ -215,7 +215,7 @@ context(
});
// 3003-PMAN-001
it.skip(
it(
'Able to submit valid new market proposal',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
+2 -2
View File
@@ -31,7 +31,7 @@ import {
orderByUpgradeBlockHeight,
} from '../proposals/components/proposals-list/proposals-list';
import { BigNumber } from '../../lib/bignumber';
import { type Proposal } from '../proposals/types';
import type { ProposalQuery } from '../proposals/proposal/__generated__/Proposal';
const nodesToShow = 6;
@@ -39,7 +39,7 @@ const HomeProposals = ({
proposals,
protocolUpgradeProposals,
}: {
proposals: Proposal[];
proposals: ProposalQuery['proposal'][];
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
}) => {
const { t } = useTranslation();
@@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalInfoLabel } from '../proposal-info-label';
import { type ReactNode } from 'react';
import { type ProposalInfoLabelVariant } from '../proposal-info-label';
import { type Proposal } from '../../types';
import type { ReactNode } from 'react';
import type { ProposalInfoLabelVariant } from '../proposal-info-label';
export const CurrentProposalState = ({ proposal }: { proposal: Proposal }) => {
export const CurrentProposalState = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
let proposalStatus: ReactNode;
let variant = 'tertiary' as ProposalInfoLabelVariant;
@@ -0,0 +1,272 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { CurrentProposalStatus } from './current-proposal-status';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: {
query: NetworkParamsDocument,
},
result: {
data: {
networkParametersConnection: {
edges: [
{
node: {
__typename: 'NetworkParameter',
key: 'governance.proposal.updateNetParam.requiredMajority',
value: '0.00000001',
},
},
{
node: {
__typename: 'NetworkParameter',
key: 'governance.proposal.updateNetParam.requiredParticipation',
value: '0.000000001',
},
},
],
},
},
},
};
const renderComponent = ({
proposal,
}: {
proposal: ProposalQuery['proposal'];
}) => {
render(
<AppStateProvider>
<MockedProvider mocks={[networkParamsQueryMock]}>
<CurrentProposalStatus proposal={proposal} />
</MockedProvider>
</AppStateProvider>
);
};
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(60 * 60 * 1000);
});
afterEach(() => {
jest.useRealTimers();
});
it('Proposal open - renders will fail state if the proposal will fail', async () => {
const failedProposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal: failedProposal });
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('fail.')).toBeInTheDocument();
});
it('Proposal open - renders will pass state if the proposal will pass', async () => {
const proposal = generateProposal();
renderComponent({ proposal });
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('pass.')).toBeInTheDocument();
});
it('Proposal enacted - renders vote passed and time since enactment', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
enactmentDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal passed - renders vote passed and time since vote closed', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal waiting for node vote - will pass - renders if the vote will pass and status', async () => {
const failedProposal = generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal: failedProposal });
expect(
await screen.findByText('Waiting for nodes to validate asset.')
).toBeInTheDocument();
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('fail.')).toBeInTheDocument();
});
it('Proposal waiting for node vote - will fail - renders if the vote will pass and status', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
});
renderComponent({ proposal });
expect(
await screen.findByText('Waiting for nodes to validate asset.')
).toBeInTheDocument();
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('pass.')).toBeInTheDocument();
});
it('Proposal failed - renders vote failed reason and vote closed ago', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
errorDetails: 'foo',
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('foo')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders rejection reason there are no error details', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
rejectionReason: ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(
await screen.findByText('PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE')
).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders unknown reason if there are no error details or rejection reason', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('unknown reason')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders participation not met if participation is not met', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('Participation not met')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders majority not met if majority is not met', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '1',
totalTokens: '25242474195500835440000',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('Majority not met')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
@@ -0,0 +1,143 @@
import type { ReactNode } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { ProposalState } from '@vegaprotocol/types';
import { useVoteInformation } from '../../hooks';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
export const StatusPass = ({ children }: { children: ReactNode }) => (
<span className="text-vega-green">{children}</span>
);
export const StatusFail = ({ children }: { children: ReactNode }) => (
<span className="text-danger">{children}</span>
);
const WillPass = ({
willPass,
children,
}: {
willPass: boolean;
children?: ReactNode;
}) => {
const { t } = useTranslation();
if (willPass) {
return (
<>
{children}
<StatusPass>{t('pass')}.</StatusPass>
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
</>
);
} else {
return (
<>
{children}
<StatusFail>{t('fail')}.</StatusFail>
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
</>
);
}
};
export const CurrentProposalStatus = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { willPassByTokenVote, majorityMet, participationMet } =
useVoteInformation({
proposal,
});
const { t } = useTranslation();
const daysClosedAgo = formatDistanceToNow(
new Date(proposal?.terms.closingDatetime),
{ addSuffix: true }
);
const daysEnactedAgo =
proposal?.terms.enactmentDatetime &&
formatDistanceToNow(new Date(proposal.terms.enactmentDatetime), {
addSuffix: true,
});
if (proposal?.state === ProposalState.STATE_OPEN) {
return (
<WillPass willPass={willPassByTokenVote}>{t('currentlySetTo')}</WillPass>
);
}
if (
proposal?.state === ProposalState.STATE_FAILED ||
proposal?.state === ProposalState.STATE_DECLINED ||
proposal?.state === ProposalState.STATE_REJECTED
) {
if (!participationMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('participationNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
if (!majorityMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('majorityNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>
{proposal?.errorDetails ||
proposal?.rejectionReason ||
t('unknownReason')}
</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
if (
proposal?.state === ProposalState.STATE_ENACTED ||
proposal?.state === ProposalState.STATE_PASSED
) {
return (
<>
<span>{t('votePassed')}</span>
<StatusPass>
&nbsp;
{proposal?.state === ProposalState.STATE_ENACTED
? t('Enacted')
: t('Passed')}
</StatusPass>
<span>
&nbsp;
{proposal?.state === ProposalState.STATE_ENACTED
? daysEnactedAgo
: daysClosedAgo}
</span>
</>
);
}
if (proposal?.state === ProposalState.STATE_WAITING_FOR_NODE_VOTE) {
return (
<WillPass willPass={willPassByTokenVote}>
<span>{t('WaitingForNodeVote')}</span>{' '}
<span>{t('currentlySetTo')}</span>
</WillPass>
);
}
return null;
};
@@ -0,0 +1 @@
export { CurrentProposalStatus } from './current-proposal-status';
@@ -6,10 +6,11 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { type Proposal } from '../../types';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface ProposalChangeTableProps {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
@@ -23,8 +23,8 @@ import { useFeatureFlags } from '@vegaprotocol/environment';
import { BrowserRouter } from 'react-router-dom';
import { VoteState } from '../vote-details/use-user-vote';
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
import { type MockedResponse } from '@apollo/client/testing';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
jest.mock('@vegaprotocol/proposals', () => ({
...jest.requireActual('@vegaprotocol/proposals'),
@@ -36,7 +36,7 @@ jest.mock('@vegaprotocol/proposals', () => ({
}));
const renderComponent = (
proposal: Proposal,
proposal: ProposalQuery['proposal'],
isListItem = true,
mocks: MockedResponse[] = [],
voteState?: VoteState
@@ -64,7 +64,6 @@ describe('Proposal header', () => {
it('Renders New market proposal', () => {
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'New some market',
@@ -103,7 +102,6 @@ describe('Proposal header', () => {
it('Renders Update market proposal', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'New market id',
@@ -132,7 +130,6 @@ describe('Proposal header', () => {
it('Renders New asset proposal - ERC20', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'New asset: Fake currency',
@@ -162,7 +159,6 @@ describe('Proposal header', () => {
it('Renders New asset proposal - BuiltInAsset', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
terms: {
change: {
@@ -188,7 +184,6 @@ describe('Proposal header', () => {
it('Renders Update network', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'Network parameter',
@@ -218,7 +213,6 @@ describe('Proposal header', () => {
it('Renders Freeform proposal - short rationale', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
id: 'short',
rationale: {
@@ -240,7 +234,6 @@ describe('Proposal header', () => {
it('Renders Freeform proposal - long rationale (105 chars) - listing', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
id: 'long',
rationale: {
@@ -266,7 +259,6 @@ describe('Proposal header', () => {
// Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
id: 'freeform id',
rationale: {
@@ -288,7 +280,6 @@ describe('Proposal header', () => {
it('Renders asset change proposal header', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
terms: {
change: {
@@ -306,7 +297,6 @@ describe('Proposal header', () => {
it("Renders unknown proposal if it's a different proposal type", () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
terms: {
change: {
@@ -323,7 +313,6 @@ describe('Proposal header', () => {
it('Renders proposal state: Enacted', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
@@ -336,7 +325,6 @@ describe('Proposal header', () => {
it('Renders proposal state: Passed', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
@@ -350,7 +338,6 @@ describe('Proposal header', () => {
it('Renders proposal state: Waiting for node vote', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
terms: {
@@ -365,7 +352,6 @@ describe('Proposal header', () => {
it('Renders proposal state: Open', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_OPEN,
votes: {
@@ -8,7 +8,8 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import { type ReactNode } from 'react';
import type { ReactNode } from 'react';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
@@ -25,17 +26,16 @@ import {
} from '@vegaprotocol/environment';
import Routes from '../../../routes';
import { Link } from 'react-router-dom';
import { type VoteState } from '../vote-details/use-user-vote';
import type { VoteState } from '../vote-details/use-user-vote';
import { VoteBreakdown } from '../vote-breakdown';
import { GovernanceTransferKindMapping } from '@vegaprotocol/types';
import { type Proposal } from '../../types';
export const ProposalHeader = ({
proposal,
isListItem = true,
voteState,
}: {
proposal: Proposal;
proposal: ProposalQuery['proposal'];
isListItem?: boolean;
voteState?: VoteState | null;
}) => {
@@ -53,7 +53,7 @@ export const ProposalHeader = ({
const titleContent = shorten(title ?? '', 100);
const getAsset = (proposal: Proposal) => {
const getAsset = (proposal: ProposalQuery['proposal']) => {
const terms = proposal?.terms;
if (
terms?.change.__typename === 'NewMarket' &&
@@ -266,6 +266,7 @@ export const ProposalMarketData = ({
/>
</>
))}
<h2 className={marketDataHeaderStyles}>
{t('Liquidity monitoring parameters')}
</h2>
@@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -13,10 +14,9 @@ import {
} from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { type Proposal } from '../../types';
interface ProposalReferralProgramDetailsProps {
proposal: Proposal | null;
proposal: ProposalQuery['proposal'];
}
export const formatEndOfProgramTimestamp = (value: string) => {
@@ -1,4 +1,6 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import { useCancelTransferProposalDetails } from '@vegaprotocol/proposals';
import {
KeyValueTable,
@@ -6,12 +8,11 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { type Proposal } from '../../types';
export const ProposalCancelTransferDetails = ({
proposal,
}: {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const details = useCancelTransferProposalDetails(proposal?.id);
@@ -1,4 +1,6 @@
import { useState } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
@@ -19,12 +21,11 @@ import {
addDecimalsFormatNumberQuantum,
formatDateWithLocalTimezone,
} from '@vegaprotocol/utils';
import { type Proposal } from '../../types';
export const ProposalTransferDetails = ({
proposal,
}: {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const [show, setShow] = useState(false);
@@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -11,7 +12,6 @@ import {
} from '../proposal-referral-program-details';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { type Proposal } from '../../types';
// These types are not generated as it's not known how dynamic these are
type VestingBenefitTier = {
@@ -43,7 +43,7 @@ export const formatVolumeDiscountFactor = (value: string) => {
};
interface ProposalReferralProgramDetailsProps {
proposal: Proposal | null;
proposal: ProposalQuery['proposal'];
}
/**
@@ -5,13 +5,13 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { Row } from '@vegaprotocol/markets';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { useState } from 'react';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { type Proposal } from '../../types';
interface ProposalUpdateMarketStateProps {
proposal: Proposal | null;
proposal: ProposalQuery['proposal'];
}
export const ProposalUpdateMarketState = ({
@@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -11,10 +12,9 @@ import {
} from '../proposal-referral-program-details';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { type Proposal } from '../../types';
interface ProposalReferralProgramDetailsProps {
proposal: Proposal | null;
proposal: ProposalQuery['proposal'];
}
export const formatVolumeDiscountFactor = (value: string) => {
@@ -1,13 +1,13 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { mockNetworkParams } from '../../test-helpers/mocks';
import { type Proposal as IProposal } from '../../types';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -51,14 +51,14 @@ const vegaWalletConfig: VegaWalletConfig = {
chainId: 'VEGA_CHAIN_ID',
};
const renderComponent = (proposal: IProposal) => {
const renderComponent = (proposal: ProposalQuery['proposal']) => {
render(
<MemoryRouter>
<MockedProvider>
<VegaWalletProvider config={vegaWalletConfig}>
<Proposal
restData={{}}
proposal={proposal}
proposal={proposal as ProposalQuery['proposal']}
networkParams={mockNetworkParams}
/>
</VegaWalletProvider>
@@ -12,13 +12,14 @@ import { UserVote } from '../vote-details';
import { ListAsset } from '../list-asset';
import Routes from '../../../routes';
import { ProposalMarketData } from '../proposal-market-data';
import { type MarketInfo } from '@vegaprotocol/markets';
import { type AssetQuery } from '@vegaprotocol/assets';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MarketInfo } from '@vegaprotocol/markets';
import type { AssetQuery } from '@vegaprotocol/assets';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalMarketChanges } from '../proposal-market-changes';
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
import { type NetworkParamsResult } from '@vegaprotocol/network-parameters';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
import { useVoteSubmit } from '@vegaprotocol/proposals';
import { useUserVote } from '../vote-details/use-user-vote';
import {
@@ -27,10 +28,9 @@ import {
} from '../proposal-transfer';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
import { type Proposal as IProposal } from '../../types';
export interface ProposalProps {
proposal: IProposal;
proposal: ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
marketData?: MarketInfo | null;
parentMarketData?: MarketInfo | null;
@@ -1,7 +1,7 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { type MockedResponse } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react';
import { format } from 'date-fns';
@@ -18,10 +18,10 @@ import {
lastWeek,
nextWeek,
} from '../../test-helpers/mocks';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
const renderComponent = (
proposal: Proposal,
proposal: ProposalQuery['proposal'],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -1,20 +1,21 @@
import { type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit';
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { ReactNode } from 'react';
import {
ProposalRejectionReasonMapping,
ProposalState,
} from '@vegaprotocol/types';
import Routes from '../../../routes';
import { type Proposal } from '../../types';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
export const ProposalsListItemDetails = ({
proposal,
}: {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const state = proposal?.state;
@@ -2,10 +2,10 @@ import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { useUserVote } from '../vote-details/use-user-vote';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface ProposalsListItemProps {
proposal?: Proposal | null;
proposal?: ProposalQuery['proposal'] | null;
}
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
@@ -17,8 +17,8 @@ import {
lastMonth,
nextMonth,
} from '../../test-helpers/mocks';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
const openProposalClosesNextMonth = generateProposal({
id: 'proposal1',
@@ -63,7 +63,7 @@ const closedProtocolUpgradeProposal = generateProtocolUpgradeProposal({
});
const renderComponent = (
proposals: Proposal[],
proposals: ProposalQuery['proposal'][],
protocolUpgradeProposals?: ProtocolUpgradeProposalFieldsFragment[]
) => (
<Router>
@@ -10,20 +10,20 @@ import Routes from '../../../routes';
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { ExternalLinks } from '@vegaprotocol/environment';
import { type ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../../types';
interface ProposalsListProps {
proposals: Proposal[];
proposals: Array<ProposalQuery['proposal']>;
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
lastBlockHeight?: string;
}
interface SortedProposalsProps {
open: Proposal[];
closed: Proposal[];
open: ProposalQuery['proposal'][];
closed: ProposalQuery['proposal'][];
}
interface SortedProtocolUpgradeProposalsProps {
@@ -31,7 +31,7 @@ interface SortedProtocolUpgradeProposalsProps {
closed: ProtocolUpgradeProposalFieldsFragment[];
}
export const orderByDate = (arr: Proposal[]) =>
export const orderByDate = (arr: ProposalQuery['proposal'][]) =>
orderBy(
arr,
[
@@ -91,10 +91,14 @@ export const ProposalsList = ({
);
return {
open:
initialSorting.open.length > 0 ? orderByDate(initialSorting.open) : [],
initialSorting.open.length > 0
? orderByDate(initialSorting.open as ProposalQuery['proposal'][])
: [],
closed:
initialSorting.closed.length > 0
? orderByDate(initialSorting.closed).reverse()
? orderByDate(
initialSorting.closed as ProposalQuery['proposal'][]
).reverse()
: [],
};
}, [proposals]);
@@ -121,7 +125,9 @@ export const ProposalsList = ({
};
}, [protocolUpgradeProposals, lastBlockHeight]);
const filterPredicate = (p: ProposalFieldsFragment | Proposal) =>
const filterPredicate = (
p: ProposalFieldsFragment | ProposalQuery['proposal']
) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -12,7 +12,7 @@ import {
nextWeek,
lastMonth,
} from '../../test-helpers/mocks';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
const rejectedProposalClosesNextWeek = generateProposal({
id: 'rejected1',
@@ -35,7 +35,7 @@ const rejectedProposalClosedLastMonth = generateProposal({
},
});
const renderComponent = (proposals: Proposal[]) => (
const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
<Router>
<MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider>
@@ -3,17 +3,17 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import { type Proposal } from '../../types';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface ProposalsListProps {
proposals: Proposal[];
proposals: ProposalQuery['proposal'][];
}
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const filterPredicate = (p: Proposal) =>
const filterPredicate = (p: ProposalQuery['proposal']) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -9,7 +9,8 @@ import {
nextWeek,
} from '../../test-helpers/mocks';
import { CompactVotes, VoteBreakdown } from './vote-breakdown';
import { type MockedResponse } from '@apollo/client/testing';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import {
generateNoVotes,
generateProposal,
@@ -17,8 +18,7 @@ import {
} from '../../test-helpers/generate-proposals';
import { ProposalState } from '@vegaprotocol/types';
import { BigNumber } from '../../../../lib/bignumber';
import { type AppState } from '../../../../contexts/app-state/app-state-context';
import { type Proposal } from '../../types';
import type { AppState } from '../../../../contexts/app-state/app-state-context';
const mockTotalSupply = new BigNumber(100);
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
@@ -41,7 +41,7 @@ jest.mock('../../../../contexts/app-state/app-state-context', () => ({
}));
const renderComponent = (
proposal: Proposal,
proposal: ProposalQuery['proposal'],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -1,4 +1,3 @@
import { type ReactNode } from 'react';
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
@@ -6,8 +5,10 @@ import { useVoteInformation } from '../../hooks';
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
import { ProposalState } from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { CompactNumber } from '@vegaprotocol/react-helpers';
import { type Proposal } from '../../types';
export const CompactVotes = ({ number }: { number: BigNumber }) => (
<CompactNumber
@@ -19,7 +20,7 @@ export const CompactVotes = ({ number }: { number: BigNumber }) => (
);
interface VoteBreakdownProps {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}
interface VoteProgressProps {
@@ -5,13 +5,14 @@ import { ProposalState } from '@vegaprotocol/types';
import { ConnectToVega } from '../../../../components/connect-to-vega';
import { VoteButtonsContainer } from './vote-buttons';
import { SubHeading } from '../../../../components/heading';
import { type VoteValue } from '@vegaprotocol/types';
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
import { type VoteState } from './use-user-vote';
import { type Proposal } from '../../types';
import type { VoteValue } from '@vegaprotocol/types';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { VoteState } from './use-user-vote';
interface UserVoteProps {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
transaction: VegaTxState | null;
@@ -3,12 +3,13 @@ import {
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { BigNumber } from '../../../lib/bignumber';
import { type Proposal } from '../types';
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
export const useProposalNetworkParams = ({
proposal,
}: {
proposal: Proposal;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_updateMarket_requiredMajority,
@@ -2,10 +2,15 @@ import { useMemo } from 'react';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../lib/bignumber';
import { useProposalNetworkParams } from './use-proposal-network-params';
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
import { addDecimal } from '@vegaprotocol/utils';
import { type Proposal } from '../types';
export const useVoteInformation = ({ proposal }: { proposal: Proposal }) => {
export const useVoteInformation = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const {
appState: { totalSupply, decimals },
} = useAppState();
@@ -86,65 +86,141 @@ query Proposal(
$includeUpdateReferralProgram: Boolean!
) {
proposal(id: $proposalId) {
... on Proposal {
id
rationale {
title
description
}
reference
state
datetime
rejectionReason
party {
id
rationale {
title
description
}
reference
state
datetime
rejectionReason
party {
id
}
errorDetails
...NewMarketProductField @include(if: $includeNewMarketProductField)
...UpdateMarketState @include(if: $includeUpdateMarketState)
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
...UpdateVolumeDiscountProgram
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
decimalPlaces
metadata
riskParameters {
... on LogNormalRiskModel {
riskAversionParameter
tau
params {
mu
r
sigma
}
}
... on SimpleRiskModel {
params {
factorLong
factorShort
}
}
errorDetails
...NewMarketProductField @include(if: $includeNewMarketProductField)
...UpdateMarketState @include(if: $includeUpdateMarketState)
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
...UpdateVolumeDiscountProgram
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
decimalPlaces
metadata
riskParameters {
... on LogNormalRiskModel {
riskAversionParameter
tau
params {
mu
r
sigma
}
}
... on SimpleRiskModel {
params {
factorLong
factorShort
}
}
}
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
}
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
}
}
}
priceMonitoringParameters {
triggers {
horizonSecs
probability
auctionExtensionSecs
}
}
liquidityMonitoringParameters {
targetStakeParameters {
timeWindow
scalingFactor
}
}
positionDecimalPlaces
linearSlippageFactor
}
... on UpdateMarket {
marketId
updateMarketConfiguration {
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
... on UpdateFutureProduct {
quoteName
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
@@ -185,19 +261,101 @@ query Proposal(
}
}
}
}
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
# dataSourceSpecForTradingTermination {
# sourceType {
# ... on DataSourceDefinitionInternal {
# sourceType {
# ... on DataSourceSpecConfigurationTime {
# conditions {
# operator
# value
# }
# }
# }
# }
# ... on DataSourceDefinitionExternal {
# sourceType {
# ... on DataSourceSpecConfiguration {
# signers {
# signer {
# ... on PubKey {
# key
# }
# ... on ETHAddress {
# address
# }
# }
# }
# filters {
# key {
# name
# type
# }
# conditions {
# operator
# value
# }
# }
# }
# }
# }
# }
# }
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
... on UpdatePerpetualProduct {
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
dataSourceSpecBinding {
settlementDataProperty
settlementScheduleProperty
}
}
}
}
metadata
priceMonitoringParameters {
triggers {
horizonSecs
@@ -211,231 +369,71 @@ query Proposal(
scalingFactor
}
}
positionDecimalPlaces
linearSlippageFactor
}
... on UpdateMarket {
marketId
updateMarketConfiguration {
instrument {
code
product {
... on UpdateFutureProduct {
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
# dataSourceSpecForTradingTermination {
# sourceType {
# ... on DataSourceDefinitionInternal {
# sourceType {
# ... on DataSourceSpecConfigurationTime {
# conditions {
# operator
# value
# }
# }
# }
# }
# ... on DataSourceDefinitionExternal {
# sourceType {
# ... on DataSourceSpecConfiguration {
# signers {
# signer {
# ... on PubKey {
# key
# }
# ... on ETHAddress {
# address
# }
# }
# }
# filters {
# key {
# name
# type
# }
# conditions {
# operator
# value
# }
# }
# }
# }
# }
# }
# }
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
... on UpdatePerpetualProduct {
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
dataSourceSpecBinding {
settlementDataProperty
settlementScheduleProperty
}
}
riskParameters {
... on UpdateMarketSimpleRiskModel {
simple {
factorLong
factorShort
}
}
metadata
priceMonitoringParameters {
triggers {
horizonSecs
probability
auctionExtensionSecs
}
}
liquidityMonitoringParameters {
targetStakeParameters {
timeWindow
scalingFactor
}
}
riskParameters {
... on UpdateMarketSimpleRiskModel {
simple {
factorLong
factorShort
}
}
... on UpdateMarketLogNormalRiskModel {
logNormal {
riskAversionParameter
tau
params {
r
sigma
mu
}
... on UpdateMarketLogNormalRiskModel {
logNormal {
riskAversionParameter
tau
params {
r
sigma
mu
}
}
}
}
}
... on NewAsset {
name
symbol
decimals
quantum
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
}
}
... on NewAsset {
name
symbol
decimals
quantum
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
... on UpdateAsset {
quantum
assetId
source {
... on UpdateERC20 {
lifetimeLimit
withdrawThreshold
}
}
... on UpdateAsset {
quantum
assetId
source {
... on UpdateERC20 {
lifetimeLimit
withdrawThreshold
}
}
}
}
votes {
yes {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
no {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
}
votes {
yes {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
no {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
}
}
File diff suppressed because one or more lines are too long
@@ -17,7 +17,6 @@ import {
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
import { type Proposal as IProposal } from '../types';
export const ProposalContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
@@ -68,8 +67,6 @@ export const ProposalContainer = () => {
skip: !params.proposalId,
});
const proposal = data?.proposal as IProposal;
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
@@ -82,12 +79,12 @@ export const ProposalContainer = () => {
},
} = useFetch(
`${ENV.rest}governance?proposalId=${
proposal?.terms.change.__typename === 'UpdateMarket' &&
proposal.terms.change.marketId
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
data?.proposal.terms.change.marketId
}`,
undefined,
true,
proposal?.terms.change.__typename !== 'UpdateMarket'
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -100,7 +97,7 @@ export const ProposalContainer = () => {
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
undefined,
true,
proposal?.terms.change.__typename !== 'UpdateMarket'
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -111,8 +108,8 @@ export const ProposalContainer = () => {
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: proposal?.id || '',
skip: !proposal?.id,
marketId: data?.proposal?.id || '',
skip: !data?.proposal?.id,
},
});
@@ -151,22 +148,23 @@ export const ProposalContainer = () => {
fetchPolicy: 'network-only',
variables: {
assetId:
(proposal?.terms.change.__typename === 'NewAsset' && proposal?.id) ||
(proposal?.terms.change.__typename === 'UpdateAsset' &&
proposal.terms.change.assetId) ||
(data?.proposal?.terms.change.__typename === 'NewAsset' &&
data?.proposal?.id) ||
(data?.proposal?.terms.change.__typename === 'UpdateAsset' &&
data.proposal.terms.change.assetId) ||
'',
},
skip: !['NewAsset', 'UpdateAsset'].includes(
proposal?.terms?.change?.__typename || ''
data?.proposal?.terms?.change?.__typename || ''
),
});
useEffect(() => {
if (
previouslyEnactedMarketProposalsRestData &&
proposal?.terms.change.__typename === 'UpdateMarket'
data?.proposal?.terms.change.__typename === 'UpdateMarket'
) {
const change = proposal?.terms?.change as { marketId: string };
const change = data?.proposal?.terms?.change as { marketId: string };
const filteredProposals =
// @ts-ignore rest data is not typed
@@ -190,8 +188,8 @@ export const ProposalContainer = () => {
}, [
previouslyEnactedMarketProposalsRestData,
params.proposalId,
proposal?.terms.change.__typename,
proposal?.terms.change,
data?.proposal?.terms.change.__typename,
data?.proposal?.terms.change,
]);
useEffect(() => {
@@ -244,7 +242,7 @@ export const ProposalContainer = () => {
>
{data?.proposal ? (
<Proposal
proposal={proposal}
proposal={data.proposal}
networkParams={networkParams}
restData={restData}
marketData={marketData}
@@ -8,7 +8,6 @@ import mergeWith from 'lodash/mergeWith';
import { type PartialDeep } from 'type-fest';
import { type ProposalQuery } from '../proposal/__generated__/Proposal';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../types';
export function generateProtocolUpgradeProposal(
override: PartialDeep<ProtocolUpgradeProposalFieldsFragment> = {}
@@ -44,8 +43,8 @@ export function generateProtocolUpgradeProposal(
}
export function generateProposal(
override: PartialDeep<Proposal> = {}
): Proposal {
override: PartialDeep<ProposalQuery['proposal']> = {}
): ProposalQuery['proposal'] {
const defaultProposal: ProposalQuery['proposal'] = {
__typename: 'Proposal',
id: faker.datatype.uuid(),
@@ -93,16 +92,15 @@ export function generateProposal(
},
};
return mergeWith<Proposal, PartialDeep<Proposal>>(
defaultProposal,
override,
(objValue, srcValue) => {
if (!isArray(objValue)) {
return;
}
return srcValue;
return mergeWith<
ProposalQuery['proposal'],
PartialDeep<ProposalQuery['proposal']>
>(defaultProposal, override, (objValue, srcValue) => {
if (!isArray(objValue)) {
return;
}
);
return srcValue;
});
}
type Vote = Pick<Schema.Vote, '__typename' | 'value' | 'party' | 'datetime'>;
@@ -1,11 +0,0 @@
import type { ProposalQuery } from './proposal/__generated__/Proposal';
/**
* The default Proposal type needs extracting from the ProposalNode union type
* as lots of fields on the original type don't exist on BatchProposal. Eventually
* we will support BatchProposal but for now we don't
*/
export type Proposal = Extract<
ProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
-1
View File
@@ -21,7 +21,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
-1
View File
@@ -21,7 +21,6 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_STOP_ORDERS=false
NX_ISOLATED_MARGIN=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
-1
View File
@@ -20,7 +20,6 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
+2 -3
View File
@@ -21,7 +21,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=false
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
@@ -30,5 +29,5 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
# NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
# NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
-1
View File
@@ -21,7 +21,6 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=false
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
-1
View File
@@ -21,7 +21,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
-1
View File
@@ -22,7 +22,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
-1
View File
@@ -22,7 +22,6 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
@@ -11,7 +11,6 @@ import {
} from '@vegaprotocol/candles-chart';
import { useEnvironment } from '@vegaprotocol/environment';
import { useChartSettings, STUDY_SIZE } from './use-chart-settings';
import { SUPPORTED_INTERVALS, type SupportedInterval } from './constants';
/**
* Renders either the pennant chart or the tradingview chart
@@ -37,7 +36,7 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
const pennantChart = (
<CandlesChartContainer
marketId={marketId}
interval={toPennantInterval(interval as SupportedInterval)}
interval={toPennantInterval(interval)}
chartType={chartType}
overlays={overlays}
studies={studies}
@@ -64,7 +63,7 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
libraryPath={CHARTING_LIBRARY_PATH}
libraryHash={CHARTING_LIBRARY_HASH}
marketId={marketId}
interval={toTradingViewResolution(interval as SupportedInterval)}
interval={toTradingViewResolution(interval)}
onIntervalChange={(newInterval) => {
setInterval(fromTradingViewResolution(newInterval));
}}
@@ -84,11 +83,7 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
}
};
const toTradingViewResolution = (interval: SupportedInterval) => {
if (!SUPPORTED_INTERVALS.includes(interval)) {
throw new Error(`interval ${interval} is not supported`);
}
const toTradingViewResolution = (interval: Interval) => {
const resolution = TRADINGVIEW_INTERVAL_MAP[interval];
if (!resolution) {
@@ -112,11 +107,7 @@ const fromTradingViewResolution = (resolution: string) => {
return interval as Interval;
};
const toPennantInterval = (interval: SupportedInterval) => {
if (!SUPPORTED_INTERVALS.includes(interval)) {
throw new Error(`interval ${interval} is not supported`);
}
const toPennantInterval = (interval: Interval) => {
const pennantInterval = PENNANT_INTERVAL_MAP[interval];
if (!pennantInterval) {
@@ -18,13 +18,21 @@ import {
TradingDropdownTrigger,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { type Interval } from '@vegaprotocol/types';
import { Interval } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment';
import { ALLOWED_TRADINGVIEW_HOSTNAMES } from '@vegaprotocol/trading-view';
import { IconNames, type IconName } from '@blueprintjs/icons';
import { useChartSettings } from './use-chart-settings';
import { useT } from '../../lib/use-t';
import { SUPPORTED_INTERVALS } from './constants';
const INTERVALS = [
Interval.INTERVAL_I1M,
Interval.INTERVAL_I5M,
Interval.INTERVAL_I15M,
Interval.INTERVAL_I1H,
Interval.INTERVAL_I6H,
Interval.INTERVAL_I1D,
];
const chartTypeIcon = new Map<ChartType, IconName>([
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
@@ -86,7 +94,7 @@ export const ChartMenu = () => {
setInterval(value as Interval);
}}
>
{SUPPORTED_INTERVALS.map((timeInterval) => (
{INTERVALS.map((timeInterval) => (
<TradingDropdownRadioItem
key={timeInterval}
inset
@@ -1,12 +0,0 @@
import { Interval } from '@vegaprotocol/types';
export type SupportedInterval = typeof SUPPORTED_INTERVALS[number];
export const SUPPORTED_INTERVALS = [
Interval.INTERVAL_I1M,
Interval.INTERVAL_I5M,
Interval.INTERVAL_I15M,
Interval.INTERVAL_I1H,
Interval.INTERVAL_I6H,
Interval.INTERVAL_I1D,
] as const;
@@ -309,49 +309,29 @@ export const ActiveRewardCard = ({
].includes(m.state)
);
const assetInSettledMarket =
allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return (
m?.state &&
[
MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_SETTLED,
MarketState.STATE_CANCELLED,
MarketState.STATE_CLOSED,
].includes(m.state)
);
}
return false;
});
if (marketSettled) {
return null;
}
// Gray out the cards that are related to suspended markets
const suspended = transferNode.markets?.some(
const assetInActiveMarket =
allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return m?.state && MarketState.STATE_ACTIVE === m.state;
}
return false;
});
const marketSuspended = transferNode.markets?.some(
(m) =>
m?.state === MarketState.STATE_SUSPENDED ||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
);
const assetInSuspendedMarket =
allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return (
m?.state === MarketState.STATE_SUSPENDED ||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
);
}
return false;
});
// Gray out the cards that are related to suspended markets
// Or settlement assets in markets that are not active and eligible for rewards
const { gradientClassName, mainClassName } =
suspended || assetInSuspendedMarket || assetInSettledMarket
marketSuspended || !assetInActiveMarket
? {
gradientClassName: 'from-vega-cdark-500 to-vega-clight-400',
mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%',
@@ -437,12 +417,12 @@ export const ActiveRewardCard = ({
<span>
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
<Tooltip
underline={suspended}
underline={marketSuspended}
description={
(suspended || assetInSuspendedMarket) &&
(marketSuspended || !assetInActiveMarket) &&
(specificMarkets
? t('Eligible market(s) currently suspended')
: assetInSuspendedMarket
: !assetInActiveMarket
? t('Currently no markets eligible for reward')
: '')
}
@@ -81,6 +81,7 @@ export const Settings = () => {
intent={Intent.Primary}
onClick={() => {
localStorage.clear();
sessionStorage.clear();
window.location.reload();
}}
>
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.0-preview.6
VEGA_VERSION=v0.74.0-preview.2
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.74.0-preview.6
VEGA_VERSION=v0.74.0-preview.2
LOCAL_SERVER=false
+1 -1
View File
@@ -1161,7 +1161,7 @@ profile = ["pytest-profiling", "snakeviz"]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "HEAD"
resolved_reference = "026976549c21e59f6f9c48f06ab15a210c5a5bf3"
resolved_reference = "2aed8c94b25d8fa2e376d3b63ca1f9193d28cdfd"
[[package]]
name = "websocket-client"
@@ -1,53 +0,0 @@
import pytest
from playwright.sync_api import Page, expect
from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order
from actions.utils import next_epoch, wait_for_toast_confirmation
tooltip_content = "tooltip-content"
leverage_input = "#leverage-input"
tab_positions = "tab-positions"
margin_row = '[col-id="margin"]'
def create_position(vega: VegaServiceNull, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
vega.wait_fn(1)
vega.wait_for_total_catchup
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_switch_cross_isolated_margin(
continuous_market, vega: VegaServiceNull, page: Page):
create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}")
expect(page.locator(margin_row).nth(1)).to_have_text("874.21992Cross1.0x")
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_test_id(tab_positions).get_by_text("Cross").hover()
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
"Liquidation: 582.81328Margin: 874.21992General account: 998,084.95183"
)
page.get_by_role("button", name="Isolated 10x").click()
page.locator(leverage_input).clear()
page.locator(leverage_input).type("1")
page.get_by_role("button", name="Confirm").click()
wait_for_toast_confirmation(page)
next_epoch(vega=vega)
expect(page.get_by_test_id("toast-content")).to_have_text(
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 1.0x")
expect(page.locator(margin_row).nth(1)
).to_have_text("11,109.99996Isolated1.0x")
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_test_id(tab_positions).get_by_text("Isolated").hover()
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
"Liquidation: 583.62409Margin: 11,109.99996Order: 11,000.00"
)
page.get_by_role("button", name="Cross").click()
page.get_by_role("button", name="Confirm").click()
wait_for_toast_confirmation(page)
next_epoch(vega=vega)
expect(page.locator(margin_row).nth(1)).to_have_text(
"22,109.99996Cross1.0x")
@@ -11,35 +11,27 @@ place_order = "place-order"
deal_ticket_warning_margin = "deal-ticket-warning-margin"
deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.skip("marked id issue #5681")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("200000")
page.get_by_test_id(order_price).fill("20")
# 7002-SORD-060
expect(page.get_by_test_id(deal_ticket_warning_margin)).to_have_text(
"You may not have enough margin available to open this position.")
expect(page.get_by_test_id(deal_ticket_warning_margin)).to_have_text("You may not have enough margin available to open this position.")
page.get_by_test_id(deal_ticket_warning_margin).hover()
expect(page.get_by_test_id("tooltip-content").nth(0)).to_have_text(
"1,661,896.6317 tDAI is currently required.You have only 1,000,000.00.Deposit tDAI")
expect(page.get_by_test_id("tooltip-content").nth(0)).to_have_text("1,661,896.6317 tDAI is currently required.You have only 1,000,000.00.Deposit tDAI")
page.get_by_test_id(deal_ticket_deposit_dialog_button).nth(0).click()
expect(page.get_by_test_id("sidebar-content")
).to_contain_text("DepositFrom")
expect(page.get_by_test_id("sidebar-content")).to_contain_text("DepositFrom")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -50,6 +42,5 @@ def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: V
# 7002-SORD-060
expect(page.get_by_test_id(place_order)).to_be_enabled()
# 7002-SORD-003
expect(page.get_by_test_id("deal-ticket-error-message-zero-balance")
).to_have_text("You need tDAI in your wallet to trade in this market.Make a deposit")
expect(page.get_by_test_id(deal_ticket_deposit_dialog_button)).to_be_visible()
expect(page.get_by_test_id("deal-ticket-error-message-zero-balance")).to_have_text("You need tDAI in your wallet to trade in this market.Make a deposit")
expect(page.get_by_test_id(deal_ticket_deposit_dialog_button)).to_be_visible()
@@ -10,18 +10,15 @@ import logging
logger = logging.getLogger()
@pytest.fixture(scope="class")
def vega():
with init_vega() as vega:
yield vega
@pytest.fixture(scope="class")
def simple_market(vega: VegaServiceNull):
return setup_simple_market(vega)
class TestGetStarted:
def test_get_started_interactive(self, vega: VegaServiceNull, page: Page):
page.goto("/")
@@ -33,8 +30,7 @@ class TestGetStarted:
expect(page.locator(".list-none")).to_contain_text(
"1.Connect2.Deposit funds3.Open a position"
)
# This is the default wallet name within VegaServiceNull and CANNOT be changed
DEFAULT_WALLET_NAME = "MarketSim"
DEFAULT_WALLET_NAME = "MarketSim" # This is the default wallet name within VegaServiceNull and CANNOT be changed
# Calling get_keypairs will internally call _load_tokens for the given wallet
keypairs = vega.wallet.get_keypairs(DEFAULT_WALLET_NAME)
@@ -141,8 +137,7 @@ class TestGetStarted:
def test_get_started_seen_already(self, simple_market, page: Page):
page.goto(f"/#/markets/{simple_market}")
get_started_locator = page.get_by_test_id("connect-vega-wallet")
page.wait_for_selector(
'[data-testid="connect-vega-wallet"]', state="attached")
page.wait_for_selector('[data-testid="connect-vega-wallet"]', state="attached")
expect(get_started_locator).to_be_enabled
expect(get_started_locator).to_be_visible
# 0007-FUGS-015
@@ -36,19 +36,16 @@ def validate_info_section(page: Page, fields: [[str, str]]):
for rowNumber, field in enumerate(fields):
name, value = field
expect(
page.get_by_test_id(
"key-value-table-row").nth(rowNumber).locator("dt")
page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dt")
).to_contain_text(name)
expect(
page.get_by_test_id(
"key-value-table-row").nth(rowNumber).locator("dd")
page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dd")
).to_contain_text(value)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_current_fees(page: Page):
# 6002-MDET-101
page.get_by_test_id(market_title_test_id).get_by_text(
"Current fees").click()
page.get_by_test_id(market_title_test_id).get_by_text("Current fees").click()
fields = [
["Maker Fee", "10%"],
["Infrastructure Fee", "0.05%"],
@@ -57,11 +54,10 @@ def test_market_info_current_fees(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_price(page: Page):
# 6002-MDET-102
page.get_by_test_id(market_title_test_id).get_by_text(
"Market price").click()
page.get_by_test_id(market_title_test_id).get_by_text("Market price").click()
fields = [
["Mark Price", "107.50"],
["Best Bid Price", "101.50"],
@@ -70,11 +66,10 @@ def test_market_info_market_price(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_volume(page: Page):
# 6002-MDET-103
page.get_by_test_id(market_title_test_id).get_by_text(
"Market volume").click()
page.get_by_test_id(market_title_test_id).get_by_text("Market volume").click()
fields = [
["24 Hour Volume", "-"],
["Open Interest", "1"],
@@ -85,32 +80,17 @@ def test_market_info_market_volume(page: Page):
]
validate_info_section(page, fields)
def test_market_info_liquidation_strategy(page: Page):
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidation strategy").click()
fields = [
["Disposal Fraction", "1"],
["Disposal Time Step", "1"],
["Full Disposal Size", "1,000,000,000"],
["Max Fraction Consumed", "0.5"],
]
validate_info_section(page, fields)
def test_market_info_liquidation(page: Page):
@pytest.mark.skip("tbd-market-sim")
def test_market_info_insurance_pool(page: Page):
# 6002-MDET-104
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidations").click()
fields = [["Insurance Pool Balance", "0.00 tDAI"]]
page.get_by_test_id(market_title_test_id).get_by_text("Insurance pool").click()
fields = [["Balance", "0.00 tDAI"]]
validate_info_section(page, fields)
@pytest.mark.skip("core issue #5681")
@pytest.mark.skip("tbd-market-sim")
def test_market_info_key_details(page: Page, vega: VegaServiceNull):
# 6002-MDET-201
page.get_by_test_id(market_title_test_id).get_by_text(
"Key details").click()
page.get_by_test_id(market_title_test_id).get_by_text("Key details").click()
market_id = vega.find_market_id("BTC:DAI_2023")
short_market_id = market_id[:6] + "" + market_id[-4:]
fields = [
@@ -126,7 +106,7 @@ def test_market_info_key_details(page: Page, vega: VegaServiceNull):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_instrument(page: Page):
# 6002-MDET-202
page.get_by_test_id(market_title_test_id).get_by_text("Instrument").click()
@@ -141,7 +121,7 @@ def test_market_info_instrument(page: Page):
# @pytest.mark.skip("oracle test to be fixed")
@pytest.mark.skip("tbd-market-sim")
def test_market_info_oracle(page: Page):
# 6002-MDET-203
page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click()
@@ -155,11 +135,10 @@ def test_market_info_oracle(page: Page):
# "href", re.compile(rf'(\/oracles\/{vega.find_market_id("BTC:DAI_2023")})')
# )
@pytest.mark.skip("tbd-market-sim")
def test_market_info_settlement_asset(page: Page, vega: VegaServiceNull):
# 6002-MDET-206
page.get_by_test_id(market_title_test_id).get_by_text(
"Settlement asset").click()
page.get_by_test_id(market_title_test_id).get_by_text("Settlement asset").click()
tdai_id = vega.find_asset_id("tDAI")
tdai_id_short = tdai_id[:6] + "" + tdai_id[-4:]
fields = [
@@ -176,7 +155,7 @@ def test_market_info_settlement_asset(page: Page, vega: VegaServiceNull):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_metadata(page: Page):
# 6002-MDET-207
page.get_by_test_id(market_title_test_id).get_by_text("Metadata").click()
@@ -185,7 +164,7 @@ def test_market_info_metadata(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_model(page: Page):
# 6002-MDET-208
page.get_by_test_id(market_title_test_id).get_by_text("Risk model").click()
@@ -196,7 +175,7 @@ def test_market_info_risk_model(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_margin_scaling_factors(page: Page):
# 6002-MDET-209
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -204,17 +183,17 @@ def test_market_info_margin_scaling_factors(page: Page):
).click()
fields = [
["Linear Slippage Factor", "0.001"],
["Quadratic Slippage Factor", "0"],
["Search Level", "1.1"],
["Initial Margin", "1.5"],
["Collateral Release", "1.7"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_factors(page: Page):
# 6002-MDET-210
page.get_by_test_id(market_title_test_id).get_by_text(
"Risk factors").click()
page.get_by_test_id(market_title_test_id).get_by_text("Risk factors").click()
fields = [
["Long", "0.05153"],
["Short", "0.05422"],
@@ -225,7 +204,7 @@ def test_market_info_risk_factors(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_price_monitoring_bounds(page: Page):
# 6002-MDET-211
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -234,27 +213,27 @@ def test_market_info_price_monitoring_bounds(page: Page):
expect(page.locator("p.col-span-1").nth(0)).to_contain_text(
"99.9999% probability price bounds"
)
expect(page.locator("p.col-span-1").nth(1)
).to_contain_text("Within 86,400 seconds")
expect(page.locator("p.col-span-1").nth(1)).to_contain_text("Within 86,400 seconds")
fields = [
["Highest Price", "138.66685 BTC"],
["Lowest Price", "83.11038 BTC"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_monitoring_parameters(page: Page):
# 6002-MDET-212
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidity monitoring parameters"
).click()
fields = [
["Triggering Ratio", "0.7"],
["Time Window", "3,600"],
["Scaling Factor", "1"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
# Liquidity resolves to 3 results
def test_market_info_liquidit(page: Page):
# 6002-MDET-213
@@ -267,7 +246,7 @@ def test_market_info_liquidit(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_price_range(page: Page):
# 6002-MDET-214
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -280,22 +259,19 @@ def test_market_info_liquidity_price_range(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_proposal(page: Page, vega: VegaServiceNull):
# 6002-MDET-301
page.get_by_test_id(market_title_test_id).get_by_text("Proposal").click()
first_link = (
page.get_by_test_id(
"accordion-content").get_by_test_id("external-link").first
page.get_by_test_id("accordion-content").get_by_test_id("external-link").first
)
second_link = (
page.get_by_test_id(
"accordion-content").get_by_test_id("external-link").nth(1)
page.get_by_test_id("accordion-content").get_by_test_id("external-link").nth(1)
)
expect(first_link).to_have_text("View governance proposal")
expect(first_link).to_have_attribute(
"href", re.compile(
rf'(\/proposals\/{vega.find_market_id("BTC:DAI_2023")})')
"href", re.compile(rf'(\/proposals\/{vega.find_market_id("BTC:DAI_2023")})')
)
expect(second_link).to_have_text("Propose a change to market")
@@ -304,14 +280,13 @@ def test_market_info_proposal(page: Page, vega: VegaServiceNull):
"href", re.compile(r"(\/proposals\/propose\/update-market)")
)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_succession_line(page: Page, vega: VegaServiceNull):
page.get_by_test_id(market_title_test_id).get_by_text(
"Succession line").click()
page.get_by_test_id(market_title_test_id).get_by_text("Succession line").click()
market_id = vega.find_market_id("BTC:DAI_2023")
succession_line = page.get_by_test_id("succession-line-item")
expect(succession_line.get_by_test_id(
"external-link")).to_have_text("BTC:DAI_2023")
expect(succession_line.get_by_test_id("external-link")).to_have_text("BTC:DAI_2023")
expect(succession_line.get_by_test_id("external-link")).to_have_attribute(
"href", re.compile(rf"(\/proposals\/{market_id})")
)
@@ -29,15 +29,13 @@ def verify_data_grid(page: Page, data_test_id, expected_pattern):
logger.info(f"Matched: {expected} == {actual}")
else:
logger.info(f"Not Matched: {expected} != {actual}")
raise AssertionError(
f"Pattern does not match: {expected} != {actual}")
raise AssertionError(f"Pattern does not match: {expected} != {actual}")
else: # it's not a regex, so we escape it
if re.search(re.escape(expected), actual):
logger.info(f"Matched: {expected} == {actual}")
else:
logger.info(f"Not Matched: {expected} != {actual}")
raise AssertionError(
f"Pattern does not match: {expected} != {actual}")
raise AssertionError(f"Pattern does not match: {expected} != {actual}")
def submit_order(vega: VegaServiceNull, wallet_name, market_id, side, volume, price):
@@ -93,7 +91,7 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
"average_entry_price": "107.50",
"mark_price": "107.50",
"margin": "8.50269",
"leverage": "Cross1.0x",
"leverage": "1.0x",
"liquidation": "0.00",
"realised_pnl": "0.00",
"unrealised_pnl": "0.00",
@@ -106,8 +104,7 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
# 7004-POSI-002
size_and_notional = table.locator("[col-id='openVolume']")
expect(size_and_notional.get_by_test_id(
primary_id)).to_have_text(position["size"])
expect(size_and_notional.get_by_test_id(primary_id)).to_have_text(position["size"])
expect(size_and_notional.get_by_test_id(secondary_id)).to_have_text(
position["notional"]
)
@@ -128,11 +125,10 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
position["leverage"]
)
# need to ne check why it is not visible
# liquidation = table.locator("[col-id='liquidationPrice']")
# expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
# position["liquidation"]
# )
liquidation = table.locator("[col-id='liquidationPrice']")
expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
position["liquidation"]
)
realisedPNL = table.locator("[col-id='realisedPNL']")
expect(realisedPNL).to_have_text(position["realised_pnl"])
@@ -28,9 +28,8 @@ def test_usage_breakdown(continuous_market, page: Page):
usage_breakdown = page.get_by_test_id("usage-breakdown")
# Verify headers
headers = ["Market", "Account type", "Balance"]
ag_headers = usage_breakdown.locator(
".ag-header-cell-text").element_handles()
headers = ["Market", "Account type", "Balance", "Margin health"]
ag_headers = usage_breakdown.locator(".ag-header-cell-text").element_handles()
for i, header_element in enumerate(ag_headers):
header_text = header_element.text_content()
assert header_text == headers[i]
@@ -39,10 +38,30 @@ def test_usage_breakdown(continuous_market, page: Page):
expect(usage_breakdown.locator('[class="mb-2 text-sm"]')).to_have_text(
"You have 1,000,000.00 tDAI in total."
)
expect(usage_breakdown.locator(
COL_ID_USED).first).to_have_text("8.50269 (0%)")
expect(usage_breakdown.locator(COL_ID_USED).first).to_have_text("8.50269 (0%)")
expect(usage_breakdown.locator(COL_ID_USED).nth(1)).to_have_text(
"999,991.49731 (99%)"
)
# Maintenance Level
expect(
usage_breakdown.locator(
".ag-center-cols-container [col-id='market.id'] .ag-cell-value"
).first
).to_have_text("2.85556 above maintenance level")
# Margin health tooltip
usage_breakdown.get_by_test_id("margin-health-chart-track").hover()
tooltip_data = [
("maintenance level", "5.64713"),
("search level", "6.21184"),
("initial level", "8.47069"),
("balance", "8.50269"),
("release level", "9.60012"),
]
for index, (label, value) in enumerate(tooltip_data):
expect(page.get_by_test_id(TOOLTIP_LABEL).nth(index)).to_have_text(label)
expect(page.get_by_test_id(TOOLTIP_VALUE).nth(index)).to_have_text(value)
page.get_by_test_id("dialog-close").click()
@@ -7,7 +7,7 @@ from wallet_config import MM_WALLET, PARTY_A, PARTY_B
from vega_sim.service import MarketStateUpdateType
import vega_sim.api.governance as governance
@pytest.mark.skip("Skipping to unblock CI, working on fix")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
@@ -46,9 +46,7 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
side="SIDE_BUY",
volume=1,
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto("/#/rewards")
next_epoch(vega=vega)
vega.update_market_state(
market_id=continuous_market,
@@ -57,9 +55,8 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
forward_time_to_enactment=True,
)
next_epoch(vega=vega)
page.reload()
expect(page.locator(".from-vega-cdark-400")).to_be_visible(timeout=15000)
page.goto("/#/rewards")
expect(page.locator(".from-vega-cdark-400")).to_be_visible()
governance.submit_oracle_data(
wallet=vega.wallet,
payload={"trading.terminated": "true"},
@@ -8,7 +8,7 @@ from actions.utils import next_epoch
market_banner = "market-banner"
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("risk_accepted")
def test_succession_line(vega: VegaServiceNull, page: Page):
parent_market_id = setup_continuous_market(vega)
@@ -20,14 +20,12 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
successor_name = "successor market name"
successor_id = propose_successor(
vega, parent_market_id, tdai_id, successor_name)
successor_id = propose_successor(vega, parent_market_id, tdai_id, successor_name)
# Check that the banner notifying about the successor proposal is shown
banner = page.get_by_test_id(market_banner)
expect(banner).to_be_attached()
expect(banner.get_by_text(
"A successor to this market has been proposed")).to_be_visible()
expect(banner.get_by_text("A successor to this market has been proposed")).to_be_visible()
next_epoch(vega)
@@ -47,6 +45,7 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
# the succession line
page.reload()
#tbd issue - 5546
page.get_by_test_id("Info").click()
page.get_by_role("button", name="Succession line").click()
@@ -79,7 +78,6 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
page.wait_for_selector('[data-testid="market-banner"]', state="attached")
expect(banner.get_by_text("This market has been succeeded")).to_be_visible()
@pytest.mark.usefixtures("risk_accepted")
def test_banners(vega: VegaServiceNull, page: Page):
@@ -93,9 +91,9 @@ def test_banners(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
vega.submit_termination_and_settlement_data(
settlement_key=GOVERNANCE_WALLET.name,
settlement_price=100,
market_id=parent_market_id,
settlement_key=GOVERNANCE_WALLET.name,
settlement_price=100,
market_id=parent_market_id,
)
successor_name = "successor market name"
@@ -110,7 +108,7 @@ def test_banners(vega: VegaServiceNull, page: Page):
# Check that the banner notifying about the successor proposal and market has been settled are shown still after reload
page.reload()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
expect(banner.get_by_text("1/2")).to_be_visible()
expect(banner.get_by_text("1/2")).to_be_visible()
# Check that the banner notifying about the successor proposal is not visible after close those banners
banner.get_by_test_id("icon-cross").click()
expect(banner.get_by_text("This market has been settled")).to_be_visible()
@@ -121,8 +119,7 @@ def test_banners(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
page.reload()
expect(banner).to_be_attached()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
def propose_successor(
vega: VegaServiceNull, parent_market_id, tdai_id, market_name
@@ -140,7 +137,6 @@ def propose_successor(
)
return market_id
def provide_successor_liquidity(
vega: VegaServiceNull, market_id
):
-6
View File
@@ -3,9 +3,6 @@ fragment MarginFields on MarginLevels {
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
asset {
id
}
@@ -36,9 +33,6 @@ subscription MarginsSubscription($partyId: ID!) {
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
timestamp
}
}
+3 -9
View File
@@ -3,21 +3,21 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } };
export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } };
export type MarginsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, timestamp: any } };
export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } };
export const MarginFieldsFragmentDoc = gql`
fragment MarginFields on MarginLevels {
@@ -25,9 +25,6 @@ export const MarginFieldsFragmentDoc = gql`
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
asset {
id
}
@@ -88,9 +85,6 @@ export const MarginsSubscriptionDocument = gql`
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
timestamp
}
}
@@ -105,7 +105,6 @@ export interface AccountFields extends Account {
// The total balance of these accounts will be used for the 'used' column in the
// collateral table
const USE_ACCOUNT_TYPES = [
AccountType.ACCOUNT_TYPE_ORDER_MARGIN,
AccountType.ACCOUNT_TYPE_MARGIN,
AccountType.ACCOUNT_TYPE_BOND,
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+28 -2
View File
@@ -4,6 +4,14 @@ import * as Types from '@vegaprotocol/types';
import type { AccountFields } from './accounts-data-provider';
import { getAccountData } from './accounts-data-provider';
const marginHealthChartTestId = 'margin-health-chart';
jest.mock('./margin-health-chart', () => ({
MarginHealthChart: () => {
return <div data-testid={marginHealthChartTestId}></div>;
},
}));
const singleRow = {
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
@@ -41,10 +49,10 @@ describe('BreakdownTable', () => {
render(<BreakdownTable data={singleRowData} />);
});
const headers = await screen.findAllByRole('columnheader');
expect(headers).toHaveLength(3);
expect(headers).toHaveLength(4);
expect(
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
).toEqual(['Market', 'Account type', 'Balance']);
).toEqual(['Market', 'Account type', 'Balance', 'Margin health']);
});
it('should apply correct formatting', async () => {
@@ -62,6 +70,24 @@ describe('BreakdownTable', () => {
cells.slice(0, -1).forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
expect(screen.getByTestId(marginHealthChartTestId)).toBeInTheDocument();
});
it('displays margin health chart only for margin account', async () => {
await act(async () => {
render(
<BreakdownTable
data={[
{
...singleRow,
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
market: null,
},
]}
/>
);
});
expect(screen.queryByTestId(marginHealthChartTestId)).toBeNull();
});
it('should get correct account data', () => {
+20 -2
View File
@@ -16,13 +16,14 @@ import { ProgressBarCell } from '@vegaprotocol/datagrid';
import { AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import type { ColDef } from 'ag-grid-community';
import { accountValuesComparator } from './accounts-table';
import { MarginHealthChart } from './margin-health-chart';
import { MarketNameCell } from '@vegaprotocol/datagrid';
import { AccountType } from '@vegaprotocol/types';
const defaultColDef = {
resizable: true,
sortable: true,
minWidth: 100,
flex: 1,
};
interface BreakdownTableProps extends AgGridReactProps {
@@ -38,7 +39,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
{
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.code',
maxWidth: 150,
width: 90,
pinned: true,
sort: 'desc',
cellRenderer: ({
@@ -110,6 +111,23 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
},
comparator: accountValuesComparator,
},
{
headerName: t('Margin health'),
field: 'market.id',
maxWidth: 500,
sortable: false,
cellRenderer: ({
data,
}: VegaICellRendererParams<AccountFields, 'market.id'>) =>
data?.market?.id &&
data.type === AccountType['ACCOUNT_TYPE_MARGIN'] &&
data?.asset.id ? (
<MarginHealthChart
marketId={data.market.id}
assetId={data.asset.id}
/>
) : null,
},
];
return defs;
}, [t]);
+8 -11
View File
@@ -20,20 +20,14 @@ const update = (
return produce(data || [], (draft) => {
const { marketId } = delta;
const index = draft.findIndex((node) => node.market.id === marketId);
const deltaData = {
maintenanceLevel: delta.maintenanceLevel,
searchLevel: delta.searchLevel,
initialLevel: delta.initialLevel,
collateralReleaseLevel: delta.collateralReleaseLevel,
marginFactor: delta.marginFactor,
marginMode: delta.marginMode,
orderMarginLevel: delta.orderMarginLevel,
};
if (index !== -1) {
const currNode = draft[index];
draft[index] = {
...currNode,
...deltaData,
maintenanceLevel: delta.maintenanceLevel,
searchLevel: delta.searchLevel,
initialLevel: delta.initialLevel,
collateralReleaseLevel: delta.collateralReleaseLevel,
};
} else {
draft.unshift({
@@ -42,7 +36,10 @@ const update = (
__typename: 'Market',
id: delta.marketId,
},
...deltaData,
maintenanceLevel: delta.maintenanceLevel,
searchLevel: delta.searchLevel,
initialLevel: delta.initialLevel,
collateralReleaseLevel: delta.collateralReleaseLevel,
asset: {
__typename: 'Asset',
id: delta.asset,
@@ -5,7 +5,6 @@ import {
import { act, render, screen } from '@testing-library/react';
import type { MarginFieldsFragment } from './__generated__/Margins';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { MarginMode } from '@vegaprotocol/types';
const asset: AssetFieldsFragment = {
id: 'assetId',
@@ -19,9 +18,6 @@ const margins: MarginFieldsFragment = {
initialLevel: '800',
searchLevel: '600',
maintenanceLevel: '400',
marginFactor: '',
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
orderMarginLevel: '',
market: {
id: 'marketId',
},
+1 -86
View File
@@ -96,11 +96,6 @@ describe('TransferForm', () => {
});
it.each([
{
targetText: 'Include transfer fee',
tooltipText:
'The fee will be taken from the amount you are transferring.',
},
{
targetText: 'Transfer fee',
tooltipText: /transfer\.fee\.factor/,
@@ -276,9 +271,6 @@ describe('TransferForm', () => {
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
await userEvent.clear(amountInput);
await userEvent.type(amountInput, '50');
@@ -288,10 +280,7 @@ describe('TransferForm', () => {
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('100.00');
// If transfering from a vested account 'include fees' checkbox should
// be disabled and fees should be 0
expect(checkbox).not.toBeChecked();
expect(checkbox).toBeDisabled();
// If transfering from a vested account fees should be 0
const expectedFee = '0';
const total = new BigNumber(amount).plus(expectedFee).toFixed();
@@ -396,78 +385,7 @@ describe('TransferForm', () => {
});
});
});
describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => {
const mockSubmit = jest.fn();
renderComponent({ ...props, submitTransfer: mockSubmit });
// check current pubkey not shown
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
pubKeyOptions
);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
// Select a pubkey
await userEvent.selectOptions(
screen.getByLabelText('To Vega key'),
props.pubKeys[1]
);
// Select asset
await selectAsset(asset);
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
// 1003-TRAN-022
expect(checkbox).not.toBeChecked();
await userEvent.clear(amountInput);
await userEvent.type(amountInput, amount);
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
const expectedFee = new BigNumber(amount)
.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
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
amount
);
await submit();
await waitFor(() => {
// 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(mockSubmit).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(expectedAmount, asset.decimals),
oneOff: {},
});
});
});
it('validates fields when checkbox is not checked', async () => {
renderComponent(props);
@@ -497,11 +415,8 @@ describe('TransferForm', () => {
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
await userEvent.type(amountInput, amount);
expect(checkbox).not.toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
+12 -54
View File
@@ -15,7 +15,6 @@ import {
TradingRichSelect,
TradingSelect,
Tooltip,
TradingCheckbox,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
@@ -135,32 +134,17 @@ export const TransferForm = ({
const accountBalance =
account && addDecimal(account.balance, account.asset.decimals);
const [includeFee, setIncludeFee] = useState(false);
// Max amount given selected asset and from account
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
const transferAmount = useMemo(() => {
if (!amount) return undefined;
if (includeFee && feeFactor) {
return new BigNumber(1).minus(feeFactor).times(amount).toString();
}
return amount;
}, [amount, includeFee, feeFactor]);
const fee = useMemo(() => {
if (!transferAmount) return undefined;
if (includeFee) {
return new BigNumber(amount).minus(transferAmount).toString();
}
return (
feeFactor && new BigNumber(feeFactor).times(transferAmount).toString()
);
}, [amount, includeFee, transferAmount, feeFactor]);
const fee = useMemo(
() => feeFactor && new BigNumber(feeFactor).times(amount).toString(),
[amount, feeFactor]
);
const onSubmit = useCallback(
(fields: FormFields) => {
if (!transferAmount) {
if (!amount) {
throw new Error('Submitted transfer with no amount selected');
}
@@ -173,7 +157,7 @@ export const TransferForm = ({
const transfer = normalizeTransfer(
fields.toVegaKey,
transferAmount,
amount,
type,
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
{
@@ -183,7 +167,7 @@ export const TransferForm = ({
);
submitTransfer(transfer);
},
[submitTransfer, transferAmount, assets]
[submitTransfer, amount, assets]
);
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
@@ -279,7 +263,6 @@ export const TransferForm = ({
) {
setValue('toVegaKey', pubKey);
setToVegaKeyMode('select');
setIncludeFee(false);
}
}}
>
@@ -449,27 +432,9 @@ export const TransferForm = ({
</TradingInputError>
)}
</TradingFormGroup>
<div className="mb-4">
<Tooltip
description={t(
`The fee will be taken from the amount you are transferring.`
)}
>
<div>
<TradingCheckbox
name="include-transfer-fee"
disabled={!transferAmount || fromVested}
label={t('Include transfer fee')}
checked={includeFee}
onCheckedChange={() => setIncludeFee((x) => !x)}
/>
</div>
</Tooltip>
</div>
{transferAmount && fee && (
{amount && fee && (
<TransferFee
amount={transferAmount}
transferAmount={transferAmount}
amount={amount}
feeFactor={feeFactor}
fee={fromVested ? '0' : fee}
decimals={asset?.decimals}
@@ -484,29 +449,22 @@ export const TransferForm = ({
export const TransferFee = ({
amount,
transferAmount,
feeFactor,
fee,
decimals,
}: {
amount: string;
transferAmount: string;
feeFactor: string | null;
fee?: string;
decimals?: number;
}) => {
const t = useT();
if (!feeFactor || !amount || !transferAmount || !fee) return null;
if (
isNaN(Number(feeFactor)) ||
isNaN(Number(amount)) ||
isNaN(Number(transferAmount)) ||
isNaN(Number(fee))
) {
if (!feeFactor || !amount || !fee) return null;
if (isNaN(Number(feeFactor)) || isNaN(Number(amount)) || isNaN(Number(fee))) {
return null;
}
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
const totalValue = new BigNumber(amount).plus(fee).toString();
return (
<div className="mb-4 flex flex-col gap-2 text-xs">
@@ -29,7 +29,6 @@ export const assetsProvider = makeDataProvider<
>({
query: AssetsDocument,
getData,
errorPolicy: 'all',
});
export const assetsMapProvider = makeDerivedDataProvider<
@@ -20,9 +20,6 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
code
}
}
marketTimestamps {
open
}
candlesConnection(
interval: $interval
since: $since
+1 -4
View File
@@ -12,7 +12,7 @@ export type CandlesQueryVariables = Types.Exact<{
}>;
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
export type CandlesEventsSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -46,9 +46,6 @@ export const CandlesDocument = gql`
code
}
}
marketTimestamps {
open
}
candlesConnection(interval: $interval, since: $since, pagination: {last: 5000}) {
edges {
node {
@@ -15,10 +15,6 @@ export const candlesQuery = (
id: 'market-0',
decimalPlaces: 5,
positionDecimalPlaces: 0,
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-04-06T09:15:00Z',
},
tradableInstrument: {
instrument: {
id: '',
+100 -104
View File
@@ -13,9 +13,6 @@ const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
market: {
decimalPlaces: 1,
positionDecimalPlaces: 1,
marketTimestamps: {
open: '2022-05-10T11:00:00Z',
},
candlesConnection: {
edges: nodes.map((node) => ({ node })),
},
@@ -24,107 +21,106 @@ const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
} as CandlesQuery;
};
const dataMocks: {
[key in Schema.Interval]?: Partial<CandleFieldsFragment>[];
} = {
[Schema.Interval.INTERVAL_I1M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:05:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I5M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:25:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I15M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T13:15:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T17:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I6H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-11T18:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1D]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T00:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-15T00:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_BLOCK]: [],
};
const dataMocks: { [key in Schema.Interval]: Partial<CandleFieldsFragment>[] } =
{
[Schema.Interval.INTERVAL_I1M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:05:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I5M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:25:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I15M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T13:15:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T17:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I6H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-11T18:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1D]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T00:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-15T00:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_BLOCK]: [],
};
describe('VegaDataSource', () => {
const marketId = 'marketId';
-23
View File
@@ -172,30 +172,13 @@ export class VegaDataSource implements DataSource {
},
fetchPolicy: 'no-cache',
});
if (data?.market?.candlesConnection?.edges) {
const decimalPlaces = data.market.decimalPlaces;
const positionDecimalPlaces = data.market.positionDecimalPlaces;
const openSince =
typeof data.market.marketTimestamps.open === 'string' &&
data.market.marketTimestamps.open.length > 0
? new Date(data.market.marketTimestamps.open)
: // this should never happen, but just in case let's have it as
// Date(0) if the market data is incomplete for some reason
new Date(0);
if (this.from < openSince) {
// overwrite `from` if requested value is before the market's open date
this.from = openSince;
}
const candles = data.market.candlesConnection.edges
.map((edge) => edge?.node)
.filter((node): node is CandleFieldsFragment => !!node)
.filter(
(node) => sinceMarketOpen(node, openSince) && !emptyCandle(node)
)
.map((node) =>
parseCandle(node, decimalPlaces, positionDecimalPlaces)
)
@@ -343,9 +326,3 @@ function parseCandle(
volume: Number(addDecimal(candle.volume, positionDecimalPlaces)),
};
}
const sinceMarketOpen = (candle: CandleFieldsFragment, openSince: Date) =>
new Date(candle.periodStart) >= openSince;
const emptyCandle = (candle: CandleFieldsFragment) =>
candle.high === '' && candle.low === '';
@@ -22,9 +22,11 @@ import {
type QueryOptions,
type ApolloClient,
} from '@apollo/client';
import { type ApolloError } from '@apollo/client';
import { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import { GraphQLError } from 'graphql';
import { type Subscription, type Observable } from 'zen-observable-ts';
import { waitFor } from '@testing-library/react';
type Item = {
cursor: string;
@@ -115,6 +117,24 @@ const paginatedSubscribe = makeDataProvider<
},
});
const mockErrorPolicyGuard: (errors: GraphQLErrors) => boolean = jest
.fn()
.mockImplementation(() => true);
const errorGuardedSubscribe = makeDataProvider<
QueryData,
Data,
SubscriptionData,
Delta,
Variables
>({
query,
subscriptionQuery,
update,
getData,
getDelta,
errorPolicyGuard: mockErrorPolicyGuard,
});
const derivedSubscribe = makeDerivedDataProvider(
[paginatedSubscribe, subscribe],
combineData,
@@ -384,6 +404,34 @@ describe('data provider', () => {
subscription.unsubscribe();
});
it('should retry with ignore error policy if errorPolicyGuard returns true', async () => {
const subscription = errorGuardedSubscribe(callback, client, variables);
const graphQLError = new GraphQLError(
'',
undefined,
undefined,
undefined,
['market', 'data'],
undefined,
{
type: 'Internal',
}
);
const graphQLErrors = [graphQLError];
const error = new ApolloError({ graphQLErrors });
await rejectQuery(error);
const data = generateData(0, 5);
await resolveQuery({
data,
});
expect(mockErrorPolicyGuard).toHaveBeenNthCalledWith(1, graphQLErrors);
await waitFor(() =>
expect(getData).toHaveBeenCalledWith({ data }, variables)
);
subscription.unsubscribe();
});
});
describe('derived data provider', () => {
+21 -12
View File
@@ -9,6 +9,7 @@ import type {
ApolloQueryResult,
QueryOptions,
} from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import type { Subscription } from 'zen-observable-ts';
import isEqualWith from 'lodash/isEqualWith';
import { isNotFoundGraphQLError } from './helpers';
@@ -160,7 +161,7 @@ interface DataProviderParams<
resetDelay?: number;
pollInterval?: number;
additionalContext?: Record<string, unknown>;
errorPolicy?: ErrorPolicy;
errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean;
getQueryVariables?: (variables: Variables) => QueryVariables;
getSubscriptionVariables?: (
variables: Variables
@@ -175,7 +176,7 @@ interface DataProviderParams<
* @param fetchPolicy
* @param resetDelay
* @param additionalContext add property to the context of the query, ie. 'isEnlargedTimeout'
* @param errorPolicy Apollos error policy, will be used when querying
* @param errorPolicyGuard indicate which gql errors can be tolerate
* @returns subscribe function
*/
function makeDataProviderInternal<
@@ -196,7 +197,7 @@ function makeDataProviderInternal<
fetchPolicy,
resetDelay,
additionalContext,
errorPolicy = 'none',
errorPolicyGuard,
getQueryVariables,
getSubscriptionVariables,
pollInterval,
@@ -330,10 +331,20 @@ function makeDataProviderInternal<
const callQuery = (
pagination?: Pagination,
policy?: ErrorPolicy
): Promise<ApolloQueryResult<QueryData>> => {
const options = getQueryOptions(pagination, policy);
return client.query<QueryData>(options);
};
): Promise<ApolloQueryResult<QueryData>> =>
client
.query<QueryData>(getQueryOptions(pagination, policy))
.catch((err) => {
if (
err.graphQLErrors &&
errorPolicyGuard &&
errorPolicyGuard(err.graphQLErrors)
) {
return callQuery(pagination, 'ignore');
} else {
throw err;
}
});
const load = async () => {
if (!pagination) {
@@ -353,7 +364,7 @@ function makeDataProviderInternal<
}
}
const res = await callQuery(paginationVariables, errorPolicy);
const res = await callQuery(paginationVariables);
const insertionData = getData(res.data, variables);
const insertionPageInfo = pagination.getPageInfo(res.data);
@@ -406,14 +417,12 @@ function makeDataProviderInternal<
const paginationVariables = pagination
? { first: pagination.first }
: undefined;
if (pollInterval) {
callWatchQuery(paginationVariables, errorPolicy);
callWatchQuery();
return;
}
try {
onNext(await callQuery(paginationVariables, errorPolicy));
onNext(await callQuery(paginationVariables));
} catch (e) {
onError(e as Error);
} finally {
+7
View File
@@ -27,3 +27,10 @@ const hasNotFoundGraphQLErrors = (errors: GraphQLErrors, path?: string[]) => {
(!path || path.every((item, i) => item === e?.path?.[i]))
);
};
export const marketDataErrorPolicyGuard = (errors: GraphQLErrors) =>
errors.every(
(e) =>
e.message.match(/no market data for market:/i) ||
e.message.match(/Conditions list is empty/)
);
@@ -13,7 +13,6 @@ import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
import { DealTicket } from './deal-ticket';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
import { MarginModeSelector } from './margin-mode-selector';
interface DealTicketContainerProps {
marketId: string;
@@ -52,31 +51,21 @@ export const DealTicketContainer = ({
reload={reload}
>
{market && marketData ? (
<>
{featureFlags.ISOLATED_MARGIN && (
<>
<MarginModeSelector marketId={marketId} />
<hr className="border-vega-clight-500 dark:border-vega-cdark-500 mb-4" />
</>
)}
{featureFlags.STOP_ORDERS && showStopOrder ? (
<StopOrder
market={market}
marketPrice={marketPrice}
submit={(stopOrdersSubmission) =>
create({ stopOrdersSubmission })
}
/>
) : (
<DealTicket
{...props}
market={market}
marketPrice={marketPrice}
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
/>
)}
</>
featureFlags.STOP_ORDERS && showStopOrder ? (
<StopOrder
market={market}
marketPrice={marketPrice}
submit={(stopOrdersSubmission) => create({ stopOrdersSubmission })}
/>
) : (
<DealTicket
{...props}
market={market}
marketPrice={marketPrice}
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
/>
)
) : (
<p>{t('Could not load market')}</p>
)}
@@ -263,11 +263,8 @@ export const DealTicket = ({
marketId: market.id,
openVolume,
orders,
marginAccountBalance: marginAccountBalance,
generalAccountBalance: generalAccountBalance,
orderMarginAccountBalance: '0', // TODO: Get real balance
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN, // TODO: unhardcode this and get users margin mode for the market
averageEntryPrice: marketPrice || '0', // TODO: This assumes the order will be entirely filled at the current market price
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
skip:
!normalizedOrder ||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
@@ -1,246 +0,0 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
TradingButton as Button,
TradingInput as Input,
FormGroup,
LeverageSlider,
} from '@vegaprotocol/ui-toolkit';
import { MarginMode, useVegaWallet } from '@vegaprotocol/wallet';
import * as Types from '@vegaprotocol/types';
import {
type VegaTransactionStore,
useVegaTransactionStore,
} from '@vegaprotocol/web3';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { useT } from '../../use-t';
import classnames from 'classnames';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useMaxLeverage } from '@vegaprotocol/positions';
const defaultLeverage = 10;
interface MarginDialogProps {
open: boolean;
onClose: () => void;
marketId: string;
partyId: string;
create: VegaTransactionStore['create'];
}
const CrossMarginModeDialog = ({
open,
onClose,
marketId,
create,
}: MarginDialogProps) => {
const t = useT();
return (
<Dialog
title={t('Cross margin')}
size="small"
open={open}
onChange={(isOpen) => {
if (!isOpen) {
onClose();
}
}}
>
<div className="text-sm mb-4">
<p className="mb-1">
{t('You are setting this market to cross-margin mode.')}
</p>
<p className="mb-1">
{t(
'Your max leverage on each position will be determined by the risk model of the market.'
)}
</p>
<p>
{t(
'All available funds in your general account will be used to finance your margin if the market moves against you.'
)}
</p>
</div>
<Button
className="w-full"
onClick={() => {
create({
updateMarginMode: {
marketId,
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
},
});
onClose();
}}
>
{t('Confirm')}
</Button>
</Dialog>
);
};
const IsolatedMarginModeDialog = ({
open,
onClose,
marketId,
partyId,
marginFactor,
create,
}: MarginDialogProps & { marginFactor: string }) => {
const [leverage, setLeverage] = useState(
Number((1 / Number(marginFactor)).toFixed(1))
);
const { data: maxLeverage } = useMaxLeverage(marketId, partyId);
const max = Math.floor((maxLeverage || 1) * 10) / 10;
useEffect(() => {
setLeverage(Number((1 / Number(marginFactor)).toFixed(1)));
}, [marginFactor]);
useEffect(() => {
if (maxLeverage && leverage > max) {
setLeverage(max);
}
}, [max, maxLeverage, leverage]);
const t = useT();
return (
<Dialog
title={t('Isolated margin')}
size="small"
open={open}
onChange={(isOpen) => {
if (!isOpen) {
onClose();
}
}}
>
<div className="text-sm mb-4">
<p className="mb-1">
{t('You are setting this market to isolated margin mode.')}
</p>
<p className="mb-1">
{t(
'Set the leverage you want below. The maximum leverage you can take is determined by the risk model of the market.'
)}
</p>
<p className="mb-1">
{t(
'Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.'
)}
</p>
</div>
<form
onSubmit={() => {
create({
updateMarginMode: {
marketId,
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: `${1 / leverage}`,
},
});
onClose();
}}
>
<FormGroup label={t('Leverage')} labelFor="leverage-input" compact>
<div className="mb-2">
<LeverageSlider
max={max}
step={0.1}
value={[leverage]}
onValueChange={([value]) => setLeverage(value)}
/>
</div>
<Input
type="number"
id="leverage-input"
min={1}
max={max}
step={0.1}
value={leverage}
onChange={(e) => setLeverage(Number(e.target.value))}
/>
</FormGroup>
<Button className="w-full" type="submit">
{t('Confirm')}
</Button>
</form>
</Dialog>
);
};
export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
const t = useT();
const [dialog, setDialog] = useState<'cross' | 'isolated' | ''>();
const { pubKey: partyId, isReadOnly } = useVegaWallet();
const { data: margin } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: {
partyId: partyId || '',
marketId,
},
skip: !partyId,
});
useEffect(() => {
if (!partyId) {
setDialog('');
}
}, [partyId]);
const create = useVegaTransactionStore((state) => state.create);
const marginMode = margin?.marginMode;
const marginFactor =
margin?.marginFactor && margin?.marginFactor !== '0'
? margin?.marginFactor
: undefined;
const disabled = isReadOnly;
const onClose = () => setDialog(undefined);
const enabledModeClassName = 'bg-vega-clight-500 dark:bg-vega-cdark-500';
return (
<>
<div className="mb-4 grid h-8 leading-8 font-alpha text-xs grid-cols-2">
<button
disabled={disabled}
onClick={() => partyId && setDialog('cross')}
className={classnames('rounded', {
[enabledModeClassName]:
!marginMode ||
marginMode === Types.MarginMode.MARGIN_MODE_CROSS_MARGIN,
})}
>
{t('Cross')}
</button>
<button
disabled={disabled}
onClick={() => partyId && setDialog('isolated')}
className={classnames('rounded', {
[enabledModeClassName]:
marginMode === Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
})}
>
{t('Isolated {{leverage}}x', {
leverage: marginFactor
? (1 / Number(marginFactor)).toFixed(1)
: defaultLeverage,
})}
</button>
</div>
{partyId && (
<CrossMarginModeDialog
partyId={partyId}
open={dialog === 'cross'}
onClose={onClose}
marketId={marketId}
create={create}
/>
)}
{partyId && (
<IsolatedMarginModeDialog
partyId={partyId}
open={dialog === 'isolated'}
onClose={onClose}
marketId={marketId}
create={create}
marginFactor={marginFactor || `${1 / defaultLeverage}`}
/>
)}
</>
);
};
@@ -1,11 +1,7 @@
import { useMemo } from 'react';
import { parseISO, isValid, isAfter } from 'date-fns';
import classNames from 'classnames';
import {
useProposalOfMarketQuery,
type ProposalOfMarketQuery,
type SingleProposal,
} from '@vegaprotocol/proposals';
import { useProposalOfMarketQuery } from '@vegaprotocol/proposals';
import { DocsLinks } from '@vegaprotocol/environment';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
@@ -40,15 +36,12 @@ export const TradingModeTooltip = ({
marketTradingMode,
});
// We only fetch Proposals (and not BatchProposals)
const proposal = proposalData?.proposal as SingleProposal<
ProposalOfMarketQuery['proposal']
>;
if (!market || !marketData) {
return null;
}
const enactmentDate = parseISO(proposal?.terms.enactmentDatetime);
const enactmentDate = parseISO(
proposalData?.proposal?.terms.enactmentDatetime
);
const compiledGrid =
!skipGrid && compileGridData(t, market, marketData, onSelect);
@@ -0,0 +1,33 @@
import { renderHook } from '@testing-library/react';
import { usePositionEstimate } from './use-position-estimate';
import * as positionsModule from '@vegaprotocol/positions';
import type {
EstimatePositionQuery,
EstimatePositionQueryVariables,
} from '@vegaprotocol/positions';
import type { QueryResult } from '@apollo/client';
let mockData: object | undefined = {};
describe('usePositionEstimate', () => {
const args = {
marketId: 'marketId',
openVolume: '10',
orders: [],
collateralAvailable: '200',
skip: false,
};
it('should return proper data', () => {
jest
.spyOn(positionsModule, 'useEstimatePositionQuery')
.mockReturnValue({ data: mockData } as unknown as QueryResult<
EstimatePositionQuery,
EstimatePositionQueryVariables
>);
const { result, rerender } = renderHook(() => usePositionEstimate(args));
expect(result.current).toEqual(mockData);
mockData = undefined;
rerender(true);
expect(result.current).toEqual({});
});
});
@@ -13,12 +13,7 @@ export const usePositionEstimate = ({
marketId,
openVolume,
orders,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
collateralAvailable,
skip,
}: PositionEstimateProps) => {
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
@@ -29,12 +24,7 @@ export const usePositionEstimate = ({
marketId,
openVolume,
orders,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
collateralAvailable,
},
skip,
fetchPolicy: 'no-cache',
@@ -323,9 +323,6 @@ export const compileFeatureFlags = (refresh = false): FeatureFlags => {
STOP_ORDERS: TRUTHY.includes(
windowOrDefault('NX_STOP_ORDERS', process.env['NX_STOP_ORDERS']) as string
),
ISOLATED_MARGIN: TRUTHY.includes(
windowOrDefault('NX_STOP_ORDERS', process.env['NX_STOP_ORDERS']) as string
),
SUCCESSOR_MARKETS: TRUTHY.includes(
windowOrDefault(
'NX_SUCCESSOR_MARKETS',

Some files were not shown because too many files have changed in this diff Show More