Compare commits

...
101 changed files with 1915 additions and 1576 deletions
@@ -1,9 +1,11 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
... on Proposal {
id
rationale {
title
description
}
}
}
}
@@ -8,16 +8,18 @@ export type ExplorerProposalQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
... on Proposal {
id
rationale {
title
description
}
}
}
}
@@ -3,7 +3,11 @@ 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 } from './__generated__/Proposal';
import {
ExplorerProposalDocument,
type ExplorerProposalQuery,
type ExplorerProposalQueryVariables,
} from './__generated__/Proposal';
import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) {
@@ -23,7 +27,10 @@ describe('Proposal link component', () => {
});
it('Renders the ID on error', async () => {
const mock = {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -40,17 +47,22 @@ describe('Proposal link component', () => {
});
it('Renders the proposal title when the query returns a result', async () => {
const mock = {
const proposalId = '123';
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '123',
id: proposalId,
},
},
result: {
data: {
proposal: {
id: '123',
__typename: 'Proposal',
id: proposalId,
rationale: {
title: 'test-title',
description: 'test description',
@@ -60,13 +72,16 @@ describe('Proposal link component', () => {
},
};
const res = render(renderComponent('123', [mock]));
expect(res.getByText('123')).toBeInTheDocument();
const res = render(renderComponent(proposalId, [mock]));
expect(res.getByText(proposalId)).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument();
});
it('Leaves the proposal id when the market is not found', async () => {
const mock = {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -1,7 +1,11 @@
import { useExplorerProposalQuery } from './__generated__/Proposal';
import {
useExplorerProposalQuery,
type ExplorerProposalQuery,
} 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;
@@ -16,8 +20,13 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
variables: { id },
});
const proposal = data?.proposal as Extract<
ExplorerProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const base = ENV.dataSources.governanceUrl;
const label = data?.proposal?.rationale.title || id;
const label = proposal?.rationale.title || id;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
@@ -1,6 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidationStrategyInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
@@ -94,6 +95,8 @@ 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,7 +1,9 @@
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
... on Proposal {
id
state
rejectionReason
}
}
}
@@ -8,15 +8,17 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
... on Proposal {
id
state
rejectionReason
}
}
}
`;
@@ -14,16 +14,18 @@ export function format(date: string | undefined, def: string) {
return new Date().toLocaleDateString() || def;
}
export function getDate(
data: ExplorerProposalStatusQuery | undefined,
terms: Terms
): string {
type Proposal = Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
const DEFAULT = t('Unknown');
if (!data?.proposal?.state) {
if (!proposal?.state) {
return DEFAULT;
}
switch (data.proposal.state) {
switch (proposal.state) {
case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED':
@@ -62,9 +64,11 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
},
});
const proposal = data?.proposal as Proposal;
return (
<Lozenge className="font-sans text-xs float-right">
{getDate(data, terms)}
{getDate(proposal, terms)}
</Lozenge>
);
};
@@ -2,17 +2,8 @@ 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;
}
@@ -29,29 +20,38 @@ type IconAndLabel = {
* @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name
*/
export function getIconAndLabelForStatus(
res: ProposalQueryResult
): IconAndLabel {
export function useIconAndLabelForStatus(id: string): IconAndLabel {
const { data, loading, error } = useExplorerProposalStatusQuery({
variables: {
id,
},
});
const proposal = data?.proposal as Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const DEFAULT: IconAndLabel = {
icon: 'error',
label: t('Proposal state unknown'),
};
if (res.loading) {
if (loading) {
return {
icon: 'more',
label: t('Loading data'),
};
}
if (!res?.data?.proposal || res.error) {
if (!data?.proposal || error) {
return {
icon: 'error',
label: res.error?.message || DEFAULT.label,
label: error?.message || DEFAULT.label,
};
}
switch (res.data.proposal.state) {
switch (proposal.state) {
case 'STATE_DECLINED':
return {
icon: 'stop',
@@ -99,13 +99,7 @@ export function getIconAndLabelForStatus(
/**
*/
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = getIconAndLabelForStatus(
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
const { icon, label } = useIconAndLabelForStatus(id);
return (
<div className="float-left mr-3">
@@ -61,53 +61,4 @@ 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,7 +10,8 @@ export interface TxListNavigationProps {
loading?: boolean;
hasPreviousPage: boolean;
hasMoreTxs: boolean;
children: React.ReactNode;
children?: React.ReactNode;
isEmpty?: boolean;
}
/**
* Displays a list of transactions with filters and controls to navigate through the list.
@@ -21,9 +22,8 @@ export const TxsListNavigation = ({
refreshTxs,
nextPage,
previousPage,
hasMoreTxs,
hasPreviousPage,
children,
isEmpty,
loading = false,
}: TxListNavigationProps) => {
return (
@@ -35,7 +35,6 @@ export const TxsListNavigation = ({
<Button
className="mr-2"
size="xs"
disabled={!hasPreviousPage || loading}
onClick={() => {
previousPage();
}}
@@ -44,7 +43,7 @@ export const TxsListNavigation = ({
</Button>
<Button
size="xs"
disabled={!hasMoreTxs}
disabled={isEmpty}
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('last', count);
url.searchParams.append('first', 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?last=10';
const expectedUrl = 'https://example.com/transactions?first=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions',
};
const expectedUrl =
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
'https://example.com/transactions?first=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 = 25,
count = 50,
before,
after,
filters,
party,
}: IUseTxsData) => {
const [, setSearchParams] = useSearchParams();
let hasMoreTxs = true;
let hasMoreTxs = false;
let txsData: BlockExplorerTransactionResult[] = [];
const url = getTxsDataUrl({
@@ -60,8 +60,8 @@ export const useTxsData = ({
}
const nextPage = useCallback(() => {
const after = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { after };
const before = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { before };
if (filters) {
params.filters = Array.from(filters).join(',');
}
@@ -69,8 +69,8 @@ export const useTxsData = ({
}, [filters, data, setSearchParams]);
const previousPage = useCallback(() => {
const before = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { before };
const after = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { after };
if (filters && filters.size > 0 && filters.size === 1) {
params.filters = Array.from(filters)[0];
}
@@ -51,9 +51,10 @@ export const TxsListFiltered = () => {
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={true}
hasPreviousPage={hasMoreTxs}
loading={loading}
hasMoreTxs={hasMoreTxs}
isEmpty={txsData.length === 0}
>
<TxsFilter
filters={filters}
@@ -70,7 +71,16 @@ export const TxsListFiltered = () => {
txs={txsData}
loadMoreTxs={nextPage}
error={error}
className="mb-28 w-full min-w-[400px]"
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}
/>
</>
);
@@ -215,7 +215,7 @@ context(
});
// 3003-PMAN-001
it(
it.skip(
'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 { ProposalQuery } from '../proposals/proposal/__generated__/Proposal';
import { type Proposal } from '../proposals/types';
const nodesToShow = 6;
@@ -39,7 +39,7 @@ const HomeProposals = ({
proposals,
protocolUpgradeProposals,
}: {
proposals: ProposalQuery['proposal'][];
proposals: Proposal[];
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
}) => {
const { t } = useTranslation();
@@ -1,16 +1,11 @@
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 ReactNode } from 'react';
import { type ProposalInfoLabelVariant } from '../proposal-info-label';
import { type Proposal } from '../../types';
export const CurrentProposalState = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
export const CurrentProposalState = ({ proposal }: { proposal: Proposal }) => {
const { t } = useTranslation();
let proposalStatus: ReactNode;
let variant = 'tertiary' as ProposalInfoLabelVariant;
@@ -1,272 +0,0 @@
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();
});
@@ -1,143 +0,0 @@
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;
};
@@ -1 +0,0 @@
export { CurrentProposalStatus } from './current-proposal-status';
@@ -6,11 +6,10 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalChangeTableProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: 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 { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { type MockedResponse } from '@apollo/client/testing';
import { type Proposal } from '../../types';
jest.mock('@vegaprotocol/proposals', () => ({
...jest.requireActual('@vegaprotocol/proposals'),
@@ -36,7 +36,7 @@ jest.mock('@vegaprotocol/proposals', () => ({
}));
const renderComponent = (
proposal: ProposalQuery['proposal'],
proposal: Proposal,
isListItem = true,
mocks: MockedResponse[] = [],
voteState?: VoteState
@@ -64,6 +64,7 @@ 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',
@@ -102,6 +103,7 @@ describe('Proposal header', () => {
it('Renders Update market proposal', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'New market id',
@@ -130,6 +132,7 @@ 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',
@@ -159,6 +162,7 @@ describe('Proposal header', () => {
it('Renders New asset proposal - BuiltInAsset', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
terms: {
change: {
@@ -184,6 +188,7 @@ describe('Proposal header', () => {
it('Renders Update network', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'Network parameter',
@@ -213,6 +218,7 @@ describe('Proposal header', () => {
it('Renders Freeform proposal - short rationale', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
id: 'short',
rationale: {
@@ -234,6 +240,7 @@ 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: {
@@ -259,6 +266,7 @@ 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: {
@@ -280,6 +288,7 @@ describe('Proposal header', () => {
it('Renders asset change proposal header', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
terms: {
change: {
@@ -297,6 +306,7 @@ 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: {
@@ -313,6 +323,7 @@ describe('Proposal header', () => {
it('Renders proposal state: Enacted', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
@@ -325,6 +336,7 @@ describe('Proposal header', () => {
it('Renders proposal state: Passed', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
@@ -338,6 +350,7 @@ 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: {
@@ -352,6 +365,7 @@ describe('Proposal header', () => {
it('Renders proposal state: Open', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
state: ProposalState.STATE_OPEN,
votes: {
@@ -8,8 +8,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type ReactNode } from 'react';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
@@ -26,16 +25,17 @@ 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: ProposalQuery['proposal'];
proposal: Proposal;
isListItem?: boolean;
voteState?: VoteState | null;
}) => {
@@ -53,7 +53,7 @@ export const ProposalHeader = ({
const titleContent = shorten(title ?? '', 100);
const getAsset = (proposal: ProposalQuery['proposal']) => {
const getAsset = (proposal: Proposal) => {
const terms = proposal?.terms;
if (
terms?.change.__typename === 'NewMarket' &&
@@ -266,7 +266,6 @@ export const ProposalMarketData = ({
/>
</>
))}
<h2 className={marketDataHeaderStyles}>
{t('Liquidity monitoring parameters')}
</h2>
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -14,9 +13,10 @@ 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: ProposalQuery['proposal'];
proposal: Proposal | null;
}
export const formatEndOfProgramTimestamp = (value: string) => {
@@ -1,6 +1,4 @@
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,
@@ -8,11 +6,12 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { type Proposal } from '../../types';
export const ProposalCancelTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { t } = useTranslation();
const details = useCancelTransferProposalDetails(proposal?.id);
@@ -1,6 +1,4 @@
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';
@@ -21,11 +19,12 @@ import {
addDecimalsFormatNumberQuantum,
formatDateWithLocalTimezone,
} from '@vegaprotocol/utils';
import { type Proposal } from '../../types';
export const ProposalTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { t } = useTranslation();
const [show, setShow] = useState(false);
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -12,6 +11,7 @@ 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: ProposalQuery['proposal'];
proposal: Proposal | null;
}
/**
@@ -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: ProposalQuery['proposal'];
proposal: Proposal | null;
}
export const ProposalUpdateMarketState = ({
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -12,9 +11,10 @@ import {
} from '../proposal-referral-program-details';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { type Proposal } from '../../types';
interface ProposalReferralProgramDetailsProps {
proposal: ProposalQuery['proposal'];
proposal: Proposal | null;
}
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: ProposalQuery['proposal']) => {
const renderComponent = (proposal: IProposal) => {
render(
<MemoryRouter>
<MockedProvider>
<VegaWalletProvider config={vegaWalletConfig}>
<Proposal
restData={{}}
proposal={proposal as ProposalQuery['proposal']}
proposal={proposal}
networkParams={mockNetworkParams}
/>
</VegaWalletProvider>
@@ -12,14 +12,13 @@ import { UserVote } from '../vote-details';
import { ListAsset } from '../list-asset';
import Routes from '../../../routes';
import { ProposalMarketData } from '../proposal-market-data';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MarketInfo } from '@vegaprotocol/markets';
import type { AssetQuery } from '@vegaprotocol/assets';
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 {
@@ -28,9 +27,10 @@ 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: ProposalQuery['proposal'];
proposal: IProposal;
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 { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
const renderComponent = (
proposal: ProposalQuery['proposal'],
proposal: Proposal,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -1,21 +1,20 @@
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 { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
export const ProposalsListItemDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: 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 { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalsListItemProps {
proposal?: ProposalQuery['proposal'] | null;
proposal?: Proposal | null;
}
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
@@ -17,8 +17,8 @@ import {
lastMonth,
nextMonth,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../../types';
const openProposalClosesNextMonth = generateProposal({
id: 'proposal1',
@@ -63,7 +63,7 @@ const closedProtocolUpgradeProposal = generateProtocolUpgradeProposal({
});
const renderComponent = (
proposals: ProposalQuery['proposal'][],
proposals: 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: Array<ProposalQuery['proposal']>;
proposals: Proposal[];
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
lastBlockHeight?: string;
}
interface SortedProposalsProps {
open: ProposalQuery['proposal'][];
closed: ProposalQuery['proposal'][];
open: Proposal[];
closed: Proposal[];
}
interface SortedProtocolUpgradeProposalsProps {
@@ -31,7 +31,7 @@ interface SortedProtocolUpgradeProposalsProps {
closed: ProtocolUpgradeProposalFieldsFragment[];
}
export const orderByDate = (arr: ProposalQuery['proposal'][]) =>
export const orderByDate = (arr: Proposal[]) =>
orderBy(
arr,
[
@@ -91,14 +91,10 @@ export const ProposalsList = ({
);
return {
open:
initialSorting.open.length > 0
? orderByDate(initialSorting.open as ProposalQuery['proposal'][])
: [],
initialSorting.open.length > 0 ? orderByDate(initialSorting.open) : [],
closed:
initialSorting.closed.length > 0
? orderByDate(
initialSorting.closed as ProposalQuery['proposal'][]
).reverse()
? orderByDate(initialSorting.closed).reverse()
: [],
};
}, [proposals]);
@@ -125,9 +121,7 @@ export const ProposalsList = ({
};
}, [protocolUpgradeProposals, lastBlockHeight]);
const filterPredicate = (
p: ProposalFieldsFragment | ProposalQuery['proposal']
) =>
const filterPredicate = (p: ProposalFieldsFragment | Proposal) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -12,7 +12,7 @@ import {
nextWeek,
lastMonth,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
const rejectedProposalClosesNextWeek = generateProposal({
id: 'rejected1',
@@ -35,7 +35,7 @@ const rejectedProposalClosedLastMonth = generateProposal({
},
});
const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
const renderComponent = (proposals: 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 { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalsListProps {
proposals: ProposalQuery['proposal'][];
proposals: Proposal[];
}
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const filterPredicate = (p: ProposalQuery['proposal']) =>
const filterPredicate = (p: Proposal) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -9,8 +9,7 @@ import {
nextWeek,
} from '../../test-helpers/mocks';
import { CompactVotes, VoteBreakdown } from './vote-breakdown';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { type MockedResponse } from '@apollo/client/testing';
import {
generateNoVotes,
generateProposal,
@@ -18,7 +17,8 @@ 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 AppState } from '../../../../contexts/app-state/app-state-context';
import { type Proposal } from '../../types';
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: ProposalQuery['proposal'],
proposal: Proposal,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -1,3 +1,4 @@
import { type ReactNode } from 'react';
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
@@ -5,10 +6,8 @@ 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
@@ -20,7 +19,7 @@ export const CompactVotes = ({ number }: { number: BigNumber }) => (
);
interface VoteBreakdownProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}
interface VoteProgressProps {
@@ -5,14 +5,13 @@ 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, 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';
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';
interface UserVoteProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
transaction: VegaTxState | null;
@@ -3,13 +3,12 @@ import {
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { BigNumber } from '../../../lib/bignumber';
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
import { type Proposal } from '../types';
export const useProposalNetworkParams = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_updateMarket_requiredMajority,
@@ -2,15 +2,10 @@ 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: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
export const useVoteInformation = ({ proposal }: { proposal: Proposal }) => {
const {
appState: { totalSupply, decimals },
} = useAppState();
@@ -86,228 +86,65 @@ query Proposal(
$includeUpdateReferralProgram: Boolean!
) {
proposal(id: $proposalId) {
id
rationale {
title
description
}
reference
state
datetime
rejectionReason
party {
... on Proposal {
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
}
}
}
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
}
}
}
}
}
}
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 PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
... on SimpleRiskModel {
params {
factorLong
factorShort
}
quoteName
}
}
}
priceMonitoringParameters {
triggers {
horizonSecs
probability
auctionExtensionSecs
}
}
liquidityMonitoringParameters {
targetStakeParameters {
timeWindow
scalingFactor
}
}
positionDecimalPlaces
linearSlippageFactor
}
... on UpdateMarket {
marketId
updateMarketConfiguration {
instrument {
name
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
}
}
}
}
}
}
... on FutureProduct {
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
# }
# }
# }
# }
# }
# }
# }
quoteName
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
... on UpdatePerpetualProduct {
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
@@ -348,14 +185,19 @@ query Proposal(
}
}
}
dataSourceSpecBinding {
settlementDataProperty
settlementScheduleProperty
}
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
}
}
}
metadata
priceMonitoringParameters {
triggers {
horizonSecs
@@ -369,71 +211,231 @@ query Proposal(
scalingFactor
}
}
riskParameters {
... on UpdateMarketSimpleRiskModel {
simple {
factorLong
factorShort
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
}
}
}
}
... on UpdateMarketLogNormalRiskModel {
logNormal {
riskAversionParameter
tau
params {
r
sigma
mu
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 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,6 +17,7 @@ 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);
@@ -67,6 +68,8 @@ export const ProposalContainer = () => {
skip: !params.proposalId,
});
const proposal = data?.proposal as IProposal;
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
@@ -79,12 +82,12 @@ export const ProposalContainer = () => {
},
} = useFetch(
`${ENV.rest}governance?proposalId=${
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
data?.proposal.terms.change.marketId
proposal?.terms.change.__typename === 'UpdateMarket' &&
proposal.terms.change.marketId
}`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -97,7 +100,7 @@ export const ProposalContainer = () => {
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -108,8 +111,8 @@ export const ProposalContainer = () => {
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: data?.proposal?.id || '',
skip: !data?.proposal?.id,
marketId: proposal?.id || '',
skip: !proposal?.id,
},
});
@@ -148,23 +151,22 @@ export const ProposalContainer = () => {
fetchPolicy: 'network-only',
variables: {
assetId:
(data?.proposal?.terms.change.__typename === 'NewAsset' &&
data?.proposal?.id) ||
(data?.proposal?.terms.change.__typename === 'UpdateAsset' &&
data.proposal.terms.change.assetId) ||
(proposal?.terms.change.__typename === 'NewAsset' && proposal?.id) ||
(proposal?.terms.change.__typename === 'UpdateAsset' &&
proposal.terms.change.assetId) ||
'',
},
skip: !['NewAsset', 'UpdateAsset'].includes(
data?.proposal?.terms?.change?.__typename || ''
proposal?.terms?.change?.__typename || ''
),
});
useEffect(() => {
if (
previouslyEnactedMarketProposalsRestData &&
data?.proposal?.terms.change.__typename === 'UpdateMarket'
proposal?.terms.change.__typename === 'UpdateMarket'
) {
const change = data?.proposal?.terms?.change as { marketId: string };
const change = proposal?.terms?.change as { marketId: string };
const filteredProposals =
// @ts-ignore rest data is not typed
@@ -188,8 +190,8 @@ export const ProposalContainer = () => {
}, [
previouslyEnactedMarketProposalsRestData,
params.proposalId,
data?.proposal?.terms.change.__typename,
data?.proposal?.terms.change,
proposal?.terms.change.__typename,
proposal?.terms.change,
]);
useEffect(() => {
@@ -242,7 +244,7 @@ export const ProposalContainer = () => {
>
{data?.proposal ? (
<Proposal
proposal={data.proposal}
proposal={proposal}
networkParams={networkParams}
restData={restData}
marketData={marketData}
@@ -8,6 +8,7 @@ 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> = {}
@@ -43,8 +44,8 @@ export function generateProtocolUpgradeProposal(
}
export function generateProposal(
override: PartialDeep<ProposalQuery['proposal']> = {}
): ProposalQuery['proposal'] {
override: PartialDeep<Proposal> = {}
): Proposal {
const defaultProposal: ProposalQuery['proposal'] = {
__typename: 'Proposal',
id: faker.datatype.uuid(),
@@ -92,15 +93,16 @@ export function generateProposal(
},
};
return mergeWith<
ProposalQuery['proposal'],
PartialDeep<ProposalQuery['proposal']>
>(defaultProposal, override, (objValue, srcValue) => {
if (!isArray(objValue)) {
return;
return mergeWith<Proposal, PartialDeep<Proposal>>(
defaultProposal,
override,
(objValue, srcValue) => {
if (!isArray(objValue)) {
return;
}
return srcValue;
}
return srcValue;
});
);
}
type Vote = Pick<Schema.Vote, '__typename' | 'value' | 'party' | 'datetime'>;
@@ -0,0 +1,11 @@
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' }
>;
@@ -11,6 +11,7 @@ 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
@@ -36,7 +37,7 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
const pennantChart = (
<CandlesChartContainer
marketId={marketId}
interval={toPennantInterval(interval)}
interval={toPennantInterval(interval as SupportedInterval)}
chartType={chartType}
overlays={overlays}
studies={studies}
@@ -63,7 +64,7 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
libraryPath={CHARTING_LIBRARY_PATH}
libraryHash={CHARTING_LIBRARY_HASH}
marketId={marketId}
interval={toTradingViewResolution(interval)}
interval={toTradingViewResolution(interval as SupportedInterval)}
onIntervalChange={(newInterval) => {
setInterval(fromTradingViewResolution(newInterval));
}}
@@ -83,7 +84,11 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
}
};
const toTradingViewResolution = (interval: Interval) => {
const toTradingViewResolution = (interval: SupportedInterval) => {
if (!SUPPORTED_INTERVALS.includes(interval)) {
throw new Error(`interval ${interval} is not supported`);
}
const resolution = TRADINGVIEW_INTERVAL_MAP[interval];
if (!resolution) {
@@ -107,7 +112,11 @@ const fromTradingViewResolution = (resolution: string) => {
return interval as Interval;
};
const toPennantInterval = (interval: Interval) => {
const toPennantInterval = (interval: SupportedInterval) => {
if (!SUPPORTED_INTERVALS.includes(interval)) {
throw new Error(`interval ${interval} is not supported`);
}
const pennantInterval = PENNANT_INTERVAL_MAP[interval];
if (!pennantInterval) {
@@ -18,21 +18,13 @@ import {
TradingDropdownTrigger,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { Interval } from '@vegaprotocol/types';
import { type 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';
const INTERVALS = [
Interval.INTERVAL_I1M,
Interval.INTERVAL_I5M,
Interval.INTERVAL_I15M,
Interval.INTERVAL_I1H,
Interval.INTERVAL_I6H,
Interval.INTERVAL_I1D,
];
import { SUPPORTED_INTERVALS } from './constants';
const chartTypeIcon = new Map<ChartType, IconName>([
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
@@ -94,7 +86,7 @@ export const ChartMenu = () => {
setInterval(value as Interval);
}}
>
{INTERVALS.map((timeInterval) => (
{SUPPORTED_INTERVALS.map((timeInterval) => (
<TradingDropdownRadioItem
key={timeInterval}
inset
@@ -0,0 +1,12 @@
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;
@@ -11,27 +11,35 @@ 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}")
@@ -42,5 +50,6 @@ 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,15 +10,18 @@ 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("/")
@@ -30,7 +33,8 @@ class TestGetStarted:
expect(page.locator(".list-none")).to_contain_text(
"1.Connect2.Deposit funds3.Open a position"
)
DEFAULT_WALLET_NAME = "MarketSim" # This is the default wallet name within VegaServiceNull and CANNOT be changed
# This is the default wallet name within VegaServiceNull and CANNOT be changed
DEFAULT_WALLET_NAME = "MarketSim"
# Calling get_keypairs will internally call _load_tokens for the given wallet
keypairs = vega.wallet.get_keypairs(DEFAULT_WALLET_NAME)
@@ -137,7 +141,8 @@ 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,16 +36,19 @@ 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%"],
@@ -54,10 +57,11 @@ 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"],
@@ -66,10 +70,11 @@ 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"],
@@ -80,17 +85,32 @@ def test_market_info_market_volume(page: Page):
]
validate_info_section(page, fields)
@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("Insurance pool").click()
fields = [["Balance", "0.00 tDAI"]]
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)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidation(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"]]
validate_info_section(page, fields)
@pytest.mark.skip("core issue #5681")
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 = [
@@ -106,7 +126,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()
@@ -121,7 +141,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()
@@ -135,10 +155,11 @@ 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 = [
@@ -155,7 +176,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()
@@ -164,7 +185,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()
@@ -175,7 +196,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(
@@ -183,17 +204,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"],
@@ -204,7 +225,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(
@@ -213,27 +234,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
@@ -246,7 +267,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(
@@ -259,19 +280,22 @@ 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")
@@ -280,13 +304,14 @@ 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})")
)
@@ -128,10 +128,11 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
position["leverage"]
)
liquidation = table.locator("[col-id='liquidationPrice']")
expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
position["liquidation"]
)
# 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"]
# )
realisedPNL = table.locator("[col-id='realisedPNL']")
expect(realisedPNL).to_have_text(position["realised_pnl"])
@@ -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,12 +20,14 @@ 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)
@@ -45,7 +47,6 @@ 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()
@@ -78,6 +79,7 @@ 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):
@@ -91,9 +93,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"
@@ -108,7 +110,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()
@@ -119,7 +121,8 @@ 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
@@ -137,6 +140,7 @@ def propose_successor(
)
return market_id
def provide_successor_liquidity(
vega: VegaServiceNull, market_id
):
+1 -1
View File
@@ -38,7 +38,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
{
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.code',
width: 90,
maxWidth: 150,
pinned: true,
sort: 'desc',
cellRenderer: ({
@@ -29,6 +29,7 @@ export const assetsProvider = makeDataProvider<
>({
query: AssetsDocument,
getData,
errorPolicy: 'all',
});
export const assetsMapProvider = makeDerivedDataProvider<
@@ -20,6 +20,9 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
code
}
}
marketTimestamps {
open
}
candlesConnection(
interval: $interval
since: $since
+4 -1
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 } }, 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 } }, 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 CandlesEventsSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -46,6 +46,9 @@ export const CandlesDocument = gql`
code
}
}
marketTimestamps {
open
}
candlesConnection(interval: $interval, since: $since, pagination: {last: 5000}) {
edges {
node {
@@ -15,6 +15,10 @@ export const candlesQuery = (
id: 'market-0',
decimalPlaces: 5,
positionDecimalPlaces: 0,
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-04-06T09:15:00Z',
},
tradableInstrument: {
instrument: {
id: '',
+104 -100
View File
@@ -13,6 +13,9 @@ const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
market: {
decimalPlaces: 1,
positionDecimalPlaces: 1,
marketTimestamps: {
open: '2022-05-10T11:00:00Z',
},
candlesConnection: {
edges: nodes.map((node) => ({ node })),
},
@@ -21,106 +24,107 @@ 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,13 +172,30 @@ 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)
)
@@ -326,3 +343,9 @@ 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,11 +22,9 @@ import {
type QueryOptions,
type ApolloClient,
} from '@apollo/client';
import { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import { type ApolloError } from '@apollo/client';
import { GraphQLError } from 'graphql';
import { type Subscription, type Observable } from 'zen-observable-ts';
import { waitFor } from '@testing-library/react';
type Item = {
cursor: string;
@@ -117,24 +115,6 @@ 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,
@@ -404,34 +384,6 @@ 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', () => {
+12 -21
View File
@@ -9,7 +9,6 @@ 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';
@@ -161,7 +160,7 @@ interface DataProviderParams<
resetDelay?: number;
pollInterval?: number;
additionalContext?: Record<string, unknown>;
errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean;
errorPolicy?: ErrorPolicy;
getQueryVariables?: (variables: Variables) => QueryVariables;
getSubscriptionVariables?: (
variables: Variables
@@ -176,7 +175,7 @@ interface DataProviderParams<
* @param fetchPolicy
* @param resetDelay
* @param additionalContext add property to the context of the query, ie. 'isEnlargedTimeout'
* @param errorPolicyGuard indicate which gql errors can be tolerate
* @param errorPolicy Apollos error policy, will be used when querying
* @returns subscribe function
*/
function makeDataProviderInternal<
@@ -197,7 +196,7 @@ function makeDataProviderInternal<
fetchPolicy,
resetDelay,
additionalContext,
errorPolicyGuard,
errorPolicy = 'none',
getQueryVariables,
getSubscriptionVariables,
pollInterval,
@@ -331,20 +330,10 @@ function makeDataProviderInternal<
const callQuery = (
pagination?: Pagination,
policy?: ErrorPolicy
): 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;
}
});
): Promise<ApolloQueryResult<QueryData>> => {
const options = getQueryOptions(pagination, policy);
return client.query<QueryData>(options);
};
const load = async () => {
if (!pagination) {
@@ -364,7 +353,7 @@ function makeDataProviderInternal<
}
}
const res = await callQuery(paginationVariables);
const res = await callQuery(paginationVariables, errorPolicy);
const insertionData = getData(res.data, variables);
const insertionPageInfo = pagination.getPageInfo(res.data);
@@ -417,12 +406,14 @@ function makeDataProviderInternal<
const paginationVariables = pagination
? { first: pagination.first }
: undefined;
if (pollInterval) {
callWatchQuery();
callWatchQuery(paginationVariables, errorPolicy);
return;
}
try {
onNext(await callQuery(paginationVariables));
onNext(await callQuery(paginationVariables, errorPolicy));
} catch (e) {
onError(e as Error);
} finally {
-7
View File
@@ -27,10 +27,3 @@ 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/)
);
@@ -263,8 +263,11 @@ export const DealTicket = ({
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
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
skip:
!normalizedOrder ||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
@@ -65,7 +65,7 @@ const CrossMarginModeDialog = ({
onClick={() => {
create({
updateMarginMode: {
market_id: marketId,
marketId,
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
},
});
@@ -131,7 +131,7 @@ const IsolatedMarginModeDialog = ({
onSubmit={() => {
create({
updateMarginMode: {
market_id: marketId,
marketId,
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: `${1 / leverage}`,
},
@@ -1,7 +1,11 @@
import { useMemo } from 'react';
import { parseISO, isValid, isAfter } from 'date-fns';
import classNames from 'classnames';
import { useProposalOfMarketQuery } from '@vegaprotocol/proposals';
import {
useProposalOfMarketQuery,
type ProposalOfMarketQuery,
type SingleProposal,
} from '@vegaprotocol/proposals';
import { DocsLinks } from '@vegaprotocol/environment';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
@@ -36,12 +40,15 @@ 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(
proposalData?.proposal?.terms.enactmentDatetime
);
const enactmentDate = parseISO(proposal?.terms.enactmentDatetime);
const compiledGrid =
!skipGrid && compileGridData(t, market, marketData, onSelect);
@@ -1,33 +0,0 @@
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,7 +13,12 @@ export const usePositionEstimate = ({
marketId,
openVolume,
orders,
collateralAvailable,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
skip,
}: PositionEstimateProps) => {
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
@@ -24,7 +29,12 @@ export const usePositionEstimate = ({
marketId,
openVolume,
orders,
collateralAvailable,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
},
skip,
fetchPolicy: 'no-cache',
+1
View File
@@ -39,6 +39,7 @@
"Key": "Key",
"Key details": "Key details",
"Liquidity": "Liquidity",
"Liquidations": "Liquidations",
"Liquidity monitoring parameters": "Liquidity monitoring parameters",
"Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.": "Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.",
"Liquidity price range": "Liquidity price range",
@@ -198,6 +198,12 @@ query MarketInfo($marketId: ID!) {
performanceHysteresisEpochs
slaCompetitionFactor
}
liquidationStrategy {
disposalTimeStep
disposalFraction
fullDisposalSize
maxFractionConsumed
}
tradableInstrument {
instrument {
id
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ import {
InstrumentInfoPanel,
InsurancePoolInfoPanel,
KeyDetailsInfoPanel,
LiquidationStrategyInfoPanel,
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel,
@@ -151,7 +152,7 @@ export const MarketInfoAccordion = ({
<AccordionItem
key={id}
itemId={id}
title={t('Insurance pool')}
title={t('Liquidations')}
content={
<InsurancePoolInfoPanel market={market} account={a} />
}
@@ -269,6 +270,11 @@ export const MarketInfoAccordion = ({
);
}
)}
<AccordionItem
itemId="liquidation-strategy"
title={t('Liquidation strategy')}
content={<LiquidationStrategyInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
@@ -1,7 +1,6 @@
import {
makeDataProvider,
makeDerivedDataProvider,
marketDataErrorPolicyGuard,
} from '@vegaprotocol/data-provider';
import {
MarketInfoDocument,
@@ -33,7 +32,7 @@ export const marketInfoProvider = makeDataProvider<
>({
query: MarketInfoDocument,
getData,
errorPolicyGuard: marketDataErrorPolicyGuard,
errorPolicy: 'all',
pollInterval: 5000,
});
@@ -65,7 +65,11 @@ import {
useSuccessorMarketIdsQuery,
useSuccessorMarketQuery,
} from '../../__generated__';
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
import {
useSuccessorMarketProposalDetailsQuery,
type SuccessorMarketProposalDetailsQuery,
type SingleProposal,
} from '@vegaprotocol/proposals';
import { getQuoteName, getAsset } from '../../market-utils';
import classNames from 'classnames';
import compact from 'lodash/compact';
@@ -206,6 +210,16 @@ export const KeyDetailsInfoPanel = ({
skip: !featureFlags.SUCCESSOR_MARKETS || !market.proposal?.id,
});
const successorProposal = successorProposalDetails?.proposal as
| SingleProposal<SuccessorMarketProposalDetailsQuery['proposal']>
| undefined;
const successorConfiguration =
successorProposal?.terms.change.__typename === 'NewMarket' &&
successorProposal.terms.change.successorConfiguration?.__typename ===
'SuccessorConfiguration' &&
successorProposal.terms.change.successorConfiguration;
// The following queries are needed as the parent market could also have been a successor market.
// Note: the parent market is only passed to this component if the successor markets flag is enabled,
// so that check is not needed in the skip.
@@ -223,6 +237,9 @@ export const KeyDetailsInfoPanel = ({
},
skip: !parentMarket?.proposal?.id,
});
const parentProposal = parentSuccessorProposalDetails?.proposal as
| SingleProposal<SuccessorMarketProposalDetailsQuery['proposal']>
| undefined;
const assetDecimals = getAsset(market).decimals;
@@ -246,17 +263,13 @@ export const KeyDetailsInfoPanel = ({
</KeyValueTable>
<MarketInfoTable
data={
featureFlags.SUCCESSOR_MARKETS
featureFlags.SUCCESSOR_MARKETS && successorConfiguration
? {
name: market.tradableInstrument.instrument.name,
parentMarketID:
parentMarketIdData?.market?.parentMarketID || '-',
insurancePoolFraction:
(successorProposalDetails?.proposal?.terms.change
.__typename === 'NewMarket' &&
successorProposalDetails.proposal.terms.change
.successorConfiguration?.insurancePoolFraction) ||
'-',
successorConfiguration.insurancePoolFraction || '-',
status: market.state && MarketStateMapping[market.state],
tradingMode:
market.tradingMode &&
@@ -281,10 +294,9 @@ export const KeyDetailsInfoPanel = ({
name: parentMarket?.tradableInstrument?.instrument?.name,
parentMarketID: grandparentMarketIdData?.market?.parentMarketID,
insurancePoolFraction:
parentSuccessorProposalDetails?.proposal?.terms.change
.__typename === 'NewMarket' &&
parentSuccessorProposalDetails.proposal.terms.change
.successorConfiguration?.insurancePoolFraction,
parentProposal?.terms.change.__typename === 'NewMarket' &&
parentProposal?.terms.change.successorConfiguration
?.insurancePoolFraction,
status:
parentMarket?.state && MarketStateMapping[parentMarket.state],
tradingMode:
@@ -738,6 +750,30 @@ export const PriceMonitoringBoundsInfoPanel = ({
);
};
export const LiquidationStrategyInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const marketData = {
disposalFraction: market.liquidationStrategy?.disposalFraction,
disposalTimeStep: market.liquidationStrategy?.disposalTimeStep,
fullDisposalSize: market.liquidationStrategy?.fullDisposalSize,
maxFractionConsumed: market.liquidationStrategy?.maxFractionConsumed,
};
const parentMarketData = parentMarket
? {
disposalFraction: parentMarket.liquidationStrategy?.disposalFraction,
disposalTimeStep: parentMarket.liquidationStrategy?.disposalTimeStep,
fullDisposalSize: parentMarket.liquidationStrategy?.fullDisposalSize,
maxFractionConsumed:
parentMarket.liquidationStrategy?.maxFractionConsumed,
}
: undefined;
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
};
export const LiquidityMonitoringParametersInfoPanel = ({
market,
parentMarket,
@@ -796,7 +832,6 @@ export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
</div>
</>
)}
<MarketInfoTable
key="eth-call-spec"
data={{
@@ -833,7 +868,6 @@ export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
<SyntaxHighlighter data={abis} />
</AccordionPanel>
</Accordion>
<h3 className={header}>{t('Normalisers')}</h3>
{sourceType.normalisers?.map((normaliser, i) => (
<MarketInfoTable key={i} data={normaliser} />
@@ -844,7 +878,7 @@ export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
<>
<MarketInfoTable key={i} data={filter.key} />
<h3 className={header}>{t('Conditions')}</h3>
{filter.conditions?.map((condition, i) => (
{filter.conditions?.map((condition) => (
<span>
{ConditionOperatorMapping[condition.operator]} {condition.value}
</span>
@@ -1,4 +1,3 @@
import { marketDataErrorPolicyGuard } from '@vegaprotocol/data-provider';
import { makeDataProvider } from '@vegaprotocol/data-provider';
import {
MarketsDataDocument,
@@ -54,7 +53,7 @@ export const marketsDataProvider = makeDataProvider<
>({
query: MarketsDataDocument,
getData,
errorPolicyGuard: marketDataErrorPolicyGuard,
errorPolicy: 'all',
});
type Variables = { marketIds: string[] };
@@ -73,7 +72,7 @@ export const marketsLiveDataProvider = makeDataProvider<
getData,
getDelta,
update,
errorPolicyGuard: marketDataErrorPolicyGuard,
errorPolicy: 'all',
getQueryVariables: () => ({}),
getSubscriptionVariables: ({ marketIds }: Variables) =>
marketIds.map((marketId) => ({ marketId })),
+1 -2
View File
@@ -2,7 +2,6 @@ import { useYesterday } from '@vegaprotocol/react-helpers';
import {
makeDataProvider,
makeDerivedDataProvider,
marketDataErrorPolicyGuard,
useDataProvider,
} from '@vegaprotocol/data-provider';
import {
@@ -45,7 +44,7 @@ export const marketsProvider = makeDataProvider<
query: MarketsDocument,
getData,
fetchPolicy: 'cache-first',
errorPolicyGuard: marketDataErrorPolicyGuard,
errorPolicy: 'all',
});
export const marketsMapProvider = makeDerivedDataProvider<
+12 -2
View File
@@ -42,13 +42,23 @@ query EstimatePosition(
$marketId: ID!
$openVolume: String!
$orders: [OrderInfo!]
$collateralAvailable: String
$averageEntryPrice: String!
$marginAccountBalance: String!
$generalAccountBalance: String!
$orderMarginAccountBalance: String!
$marginMode: MarginMode!
$marginFactor: String
) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
orders: $orders
collateralAvailable: $collateralAvailable
averageEntryPrice: $averageEntryPrice
marginAccountBalance: $marginAccountBalance
generalAccountBalance: $generalAccountBalance
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
# Everywhere in the codebase we expect price values of the underlying to have the right
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
# query will return a full value requiring formatting using asset.decimals. For consistency
+19 -4
View File
@@ -23,7 +23,12 @@ export type EstimatePositionQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
openVolume: Types.Scalars['String'];
orders?: Types.InputMaybe<Array<Types.OrderInfo> | Types.OrderInfo>;
collateralAvailable?: Types.InputMaybe<Types.Scalars['String']>;
averageEntryPrice: Types.Scalars['String'];
marginAccountBalance: Types.Scalars['String'];
generalAccountBalance: Types.Scalars['String'];
orderMarginAccountBalance: Types.Scalars['String'];
marginMode: Types.MarginMode;
marginFactor?: Types.InputMaybe<Types.Scalars['String']>;
}>;
@@ -124,12 +129,17 @@ export function usePositionsSubscriptionSubscription(baseOptions: Apollo.Subscri
export type PositionsSubscriptionSubscriptionHookResult = ReturnType<typeof usePositionsSubscriptionSubscription>;
export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<PositionsSubscriptionSubscription>;
export const EstimatePositionDocument = gql`
query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $collateralAvailable: String) {
query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $averageEntryPrice: String!, $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
orders: $orders
collateralAvailable: $collateralAvailable
averageEntryPrice: $averageEntryPrice
marginAccountBalance: $marginAccountBalance
generalAccountBalance: $generalAccountBalance
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
scaleLiquidationPriceToMarketDecimals: true
) {
margin {
@@ -177,7 +187,12 @@ export const EstimatePositionDocument = gql`
* marketId: // value for 'marketId'
* openVolume: // value for 'openVolume'
* orders: // value for 'orders'
* collateralAvailable: // value for 'collateralAvailable'
* averageEntryPrice: // value for 'averageEntryPrice'
* marginAccountBalance: // value for 'marginAccountBalance'
* generalAccountBalance: // value for 'generalAccountBalance'
* orderMarginAccountBalance: // value for 'orderMarginAccountBalance'
* marginMode: // value for 'marginMode'
* marginFactor: // value for 'marginFactor'
* },
* });
*/
@@ -6,13 +6,19 @@ import { EstimatePositionDocument } from './__generated__/Positions';
import type { EstimatePositionQuery } from './__generated__/Positions';
import { LiquidationPrice } from './liquidation-price';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { MarginMode } from '@vegaprotocol/types';
describe('LiquidationPrice', () => {
const props = {
marketId: 'market-id',
openVolume: '100',
collateralAvailable: '1000',
decimalPlaces: 2,
averageEntryPrice: '100',
generalAccountBalance: '100',
marginAccountBalance: '100',
orderMarginAccountBalance: '100',
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
marginFactor: '1',
};
const worstCaseOpenVolume = '200';
const bestCaseOpenVolume = '100';
@@ -22,7 +28,12 @@ describe('LiquidationPrice', () => {
variables: {
marketId: props.marketId,
openVolume: props.openVolume,
collateralAvailable: props.collateralAvailable,
averageEntryPrice: props.averageEntryPrice,
generalAccountBalance: props.generalAccountBalance,
marginAccountBalance: props.marginAccountBalance,
orderMarginAccountBalance: props.orderMarginAccountBalance,
marginMode: props.marginMode,
marginFactor: props.marginFactor,
},
},
result: {
+20 -3
View File
@@ -2,26 +2,43 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit';
import { useEstimatePositionQuery } from './__generated__/Positions';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useT } from '../use-t';
import { MarginMode } from '@vegaprotocol/types';
export const LiquidationPrice = ({
marketId,
openVolume,
collateralAvailable,
averageEntryPrice,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
marginMode = MarginMode.MARGIN_MODE_CROSS_MARGIN,
marginFactor,
decimalPlaces,
className,
}: {
marketId: string;
openVolume: string;
collateralAvailable: string;
averageEntryPrice: string;
generalAccountBalance: string;
marginAccountBalance: string;
orderMarginAccountBalance: string;
marginMode: MarginMode;
marginFactor: string;
decimalPlaces: number;
className?: string;
}) => {
const t = useT();
const { data: currentData, previousData } = useEstimatePositionQuery({
variables: {
marketId,
openVolume,
collateralAvailable,
averageEntryPrice,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
marginMode,
marginFactor,
},
fetchPolicy: 'no-cache',
skip: !openVolume || openVolume === '0',
@@ -42,6 +42,7 @@ import {
export interface Position {
marginMode: MarginFieldsFragment['marginMode'];
marginFactor: MarginFieldsFragment['marginFactor'];
maintenanceLevel: MarginFieldsFragment['maintenanceLevel'] | undefined;
assetId: string;
assetSymbol: string;
@@ -152,6 +153,7 @@ export const getMetrics = (
: undefined;
metrics.push({
marginMode,
marginFactor: marginFactor || '0',
maintenanceLevel: margin?.maintenanceLevel,
assetId: asset.id,
assetSymbol: asset.symbol,
+6 -1
View File
@@ -410,7 +410,12 @@ export const PositionsTable = ({
className="block text-right grow"
marketId={data.marketId}
openVolume={data.openVolume}
collateralAvailable={data.totalBalance}
generalAccountBalance={data.generalAccountBalance}
marginAccountBalance={data.marginAccountBalance}
orderMarginAccountBalance={data.orderAccountBalance}
averageEntryPrice={data.averageEntryPrice}
marginFactor={data.marginFactor}
marginMode={data.marginMode}
decimalPlaces={data.marketDecimalPlaces}
/>
</div>
+1
View File
@@ -184,6 +184,7 @@ export const singleRow: Position = {
generalAccountBalance: '12345600',
maintenanceLevel: '12300000',
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
marginFactor: '1',
orderAccountBalance: '0',
partyId: 'partyId',
assetId: 'asset-id',
+1
View File
@@ -1,3 +1,4 @@
export * from './lib';
export * from './utils';
export * from './components';
export * from './types';
@@ -39,8 +39,7 @@ export const proposalsDataProvider = makeDataProvider<
*
* GQL Path: `terms.change.instrument.futureProduct.settlementAsset`
*/
errorPolicyGuard: (errors) =>
errors.every((e) => e.message.match(/failed to get asset for ID/)),
errorPolicy: 'all',
});
const ProposalTypeMap: Record<
@@ -39,22 +39,26 @@ subscription OnProposal {
query ProposalOfMarket($marketId: ID!) {
proposal(id: $marketId) {
id
terms {
enactmentDatetime
... on Proposal {
id
terms {
enactmentDatetime
}
}
}
}
query SuccessorMarketProposalDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
... on Proposal {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
}
}
}
}
@@ -75,11 +79,13 @@ query InstrumentDetails($marketId: ID!) {
query NewTransferDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewTransfer {
...NewTransferFields
... on Proposal {
id
terms {
change {
... on NewTransfer {
...NewTransferFields
}
}
}
}
@@ -88,11 +94,13 @@ query NewTransferDetails($proposalId: ID!) {
query CancelTransferDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on CancelTransfer {
...CancelTransferFields
... on Proposal {
id
terms {
change {
... on CancelTransfer {
...CancelTransferFields
}
}
}
}
@@ -25,14 +25,14 @@ export type ProposalOfMarketQueryVariables = Types.Exact<{
}>;
export type ProposalOfMarketQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null } } | null };
export type ProposalOfMarketQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null } } | null };
export type SuccessorMarketProposalDetailsQueryVariables = Types.Exact<{
proposalId: Types.Scalars['ID'];
}>;
export type SuccessorMarketProposalDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string, insurancePoolFraction: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export type SuccessorMarketProposalDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string, insurancePoolFraction: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export type InstrumentDetailsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -46,14 +46,14 @@ export type NewTransferDetailsQueryVariables = Types.Exact<{
}>;
export type NewTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer', source: string, sourceType: Types.AccountType, destination: string, destinationType: Types.AccountType, fraction_of_balance: string, amount: string, transferType: Types.GovernanceTransferType, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string }, kind: { __typename: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename: 'RecurringGovernanceTransfer', startEpoch: number, endEpoch?: number | null } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export type NewTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer', source: string, sourceType: Types.AccountType, destination: string, destinationType: Types.AccountType, fraction_of_balance: string, amount: string, transferType: Types.GovernanceTransferType, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string }, kind: { __typename: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename: 'RecurringGovernanceTransfer', startEpoch: number, endEpoch?: number | null } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export type CancelTransferDetailsQueryVariables = Types.Exact<{
proposalId: Types.Scalars['ID'];
}>;
export type CancelTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer', transferId: string } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export type CancelTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer', transferId: string } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null };
export const ProposalEventFieldsFragmentDoc = gql`
fragment ProposalEventFields on Proposal {
@@ -146,9 +146,11 @@ export type OnProposalSubscriptionResult = Apollo.SubscriptionResult<OnProposalS
export const ProposalOfMarketDocument = gql`
query ProposalOfMarket($marketId: ID!) {
proposal(id: $marketId) {
id
terms {
enactmentDatetime
... on Proposal {
id
terms {
enactmentDatetime
}
}
}
}
@@ -184,13 +186,15 @@ export type ProposalOfMarketQueryResult = Apollo.QueryResult<ProposalOfMarketQue
export const SuccessorMarketProposalDetailsDocument = gql`
query SuccessorMarketProposalDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
... on Proposal {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
}
}
}
}
@@ -269,11 +273,13 @@ export type InstrumentDetailsQueryResult = Apollo.QueryResult<InstrumentDetailsQ
export const NewTransferDetailsDocument = gql`
query NewTransferDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewTransfer {
...NewTransferFields
... on Proposal {
id
terms {
change {
... on NewTransfer {
...NewTransferFields
}
}
}
}
@@ -311,11 +317,13 @@ export type NewTransferDetailsQueryResult = Apollo.QueryResult<NewTransferDetail
export const CancelTransferDetailsDocument = gql`
query CancelTransferDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on CancelTransfer {
...CancelTransferFields
... on Proposal {
id
terms {
change {
... on CancelTransfer {
...CancelTransferFields
}
}
}
}
@@ -1,5 +1,9 @@
import type { CancelTransferFieldsFragment } from '../proposals-data-provider';
import { useCancelTransferDetailsQuery } from './__generated__/Proposal';
import { type SingleProposal } from '../../types';
import { type CancelTransferFieldsFragment } from '../proposals-data-provider';
import {
useCancelTransferDetailsQuery,
type CancelTransferDetailsQuery,
} from './__generated__/Proposal';
export const useCancelTransferProposalDetails = (
proposalId?: string | null
@@ -11,8 +15,12 @@ export const useCancelTransferProposalDetails = (
skip: !proposalId || proposalId.length === 0,
});
if (data?.proposal?.terms.change.__typename === 'CancelTransfer') {
return data?.proposal?.terms.change as CancelTransferFieldsFragment;
const proposal = data?.proposal as SingleProposal<
CancelTransferDetailsQuery['proposal']
>;
if (proposal?.terms.change.__typename === 'CancelTransfer') {
return proposal?.terms.change as CancelTransferFieldsFragment;
}
return undefined;
@@ -1,5 +1,9 @@
import type { NewTransferFieldsFragment } from '../proposals-data-provider';
import { useNewTransferDetailsQuery } from './__generated__/Proposal';
import { type SingleProposal } from '../../types';
import { type NewTransferFieldsFragment } from '../proposals-data-provider';
import {
useNewTransferDetailsQuery,
type NewTransferDetailsQuery,
} from './__generated__/Proposal';
export const useNewTransferProposalDetails = (proposalId?: string | null) => {
const { data } = useNewTransferDetailsQuery({
@@ -9,8 +13,12 @@ export const useNewTransferProposalDetails = (proposalId?: string | null) => {
skip: !proposalId || proposalId.length === 0,
});
if (data?.proposal?.terms.change.__typename === 'NewTransfer') {
return data?.proposal?.terms.change as NewTransferFieldsFragment;
const proposal = data?.proposal as SingleProposal<
NewTransferDetailsQuery['proposal']
>;
if (proposal?.terms.change.__typename === 'NewTransfer') {
return proposal?.terms.change as NewTransferFieldsFragment;
}
return undefined;
@@ -2,22 +2,28 @@ import omit from 'lodash/omit';
import {
useInstrumentDetailsQuery,
useSuccessorMarketProposalDetailsQuery,
type SuccessorMarketProposalDetailsQuery,
} from './__generated__/Proposal';
import { type SingleProposal } from '../../types';
export const useSuccessorMarketProposalDetails = (
proposalId?: string | null
) => {
const { data: proposal } = useSuccessorMarketProposalDetailsQuery({
const { data } = useSuccessorMarketProposalDetailsQuery({
variables: {
proposalId: proposalId || '',
},
skip: !proposalId || proposalId.length === 0,
});
const proposal = data?.proposal as SingleProposal<
SuccessorMarketProposalDetailsQuery['proposal']
>;
const successorDetails =
(proposal?.proposal &&
proposal.proposal?.terms.change.__typename === 'NewMarket' &&
proposal.proposal.terms.change.successorConfiguration) ||
(proposal &&
proposal?.terms.change.__typename === 'NewMarket' &&
proposal.terms.change.successorConfiguration) ||
undefined;
const { data: market } = useInstrumentDetailsQuery({
+6
View File
@@ -0,0 +1,6 @@
type Batch = { __typename?: 'BatchProposal' | undefined } | null | undefined;
type Single = { __typename?: 'Proposal' | undefined } | null | undefined;
export type SingleProposal<T extends Batch | Single> = Extract<
T,
{ __typename?: 'Proposal' }
>;
+600 -25
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -38,7 +38,6 @@ import type { ProductType, ProposalProductType } from './product';
export const AccountTypeMapping: {
[T in AccountType]: string;
} = {
ACCOUNT_TYPE_ORDER_MARGIN: 'Per asset market account',
ACCOUNT_TYPE_BOND: 'Bond account',
ACCOUNT_TYPE_EXTERNAL: 'External account',
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Infrastructure fees account',
@@ -49,6 +48,7 @@ export const AccountTypeMapping: {
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global reward account',
ACCOUNT_TYPE_INSURANCE: 'Insurance account',
ACCOUNT_TYPE_MARGIN: 'Margin account',
ACCOUNT_TYPE_ORDER_MARGIN: 'Per asset market account',
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending transfers account',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD:
'Pending fee referral reward account',
@@ -116,9 +116,14 @@ export const IntervalMapping: {
INTERVAL_BLOCK: '1 block',
INTERVAL_I15M: 'I15M',
INTERVAL_I1D: 'I1D',
INTERVAL_I7D: 'I7D',
INTERVAL_I1H: 'I1H',
INTERVAL_I4H: 'I4H',
INTERVAL_I8H: 'I8H',
INTERVAL_I12H: 'I12H',
INTERVAL_I1M: 'I1M',
INTERVAL_I5M: 'I5M',
INTERVAL_I30M: 'I30M',
INTERVAL_I6H: 'I6H',
};
@@ -38,14 +38,14 @@ export function Dialog({
);
const wrapperClasses = classNames(
// Dimensions
'max-w-[90vw] p-4 md:p-8',
'w-screen sm:max-w-[90vw] p-4 md:p-8',
// Need to apply background and text colors again as content is rendered in a portal
'dark:bg-black bg-white dark:text-white',
getIntentBorder(intent),
{
'w-[520px]': size === 'small',
'w-[680px]': size === 'medium',
'w-[720px] lg:w-[940px]': size === 'large',
'sm:w-[520px]': size === 'small',
'sm:w-[680px]': size === 'medium',
'sm:w-[720px] lg:w-[940px]': size === 'large',
}
);
+1 -1
View File
@@ -456,7 +456,7 @@ export enum MarginMode {
MARGIN_MODE_ISOLATED_MARGIN,
}
export interface UpdateMarginMode {
market_id: string;
marketId: string;
mode: MarginMode;
marginFactor?: string;
}

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