Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1b54307c4 | ||
|
|
fff248d63e |
@@ -10,7 +10,7 @@ on:
|
||||
inputs:
|
||||
console-test-branch:
|
||||
type: choice
|
||||
description: 'main: v0.73.13, develop: v0.74.0'
|
||||
description: 'main: v0.73.5, develop: v0.73.5'
|
||||
options:
|
||||
- main
|
||||
- develop
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
# run tests
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 1 --dist loadfile --durations=45
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# upload traces
|
||||
|
||||
@@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V
|
||||
|
||||
This repository is managed using [Nx](https://nx.dev).
|
||||
|
||||
## 🔎 Applications in this repo
|
||||
# 🔎 Applications in this repo
|
||||
|
||||
### [Block explorer](./apps/explorer)
|
||||
|
||||
@@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts.
|
||||
|
||||
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
|
||||
|
||||
## 🧱 Libraries in this repo
|
||||
# 🧱 Libraries in this repo
|
||||
|
||||
### [UI toolkit](./libs/ui-toolkit)
|
||||
|
||||
@@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve
|
||||
|
||||
Generic react helpers that can be used across multiple applications, along with other utilities.
|
||||
|
||||
## 💻 Develop
|
||||
# 💻 Develop
|
||||
|
||||
### Set up
|
||||
|
||||
@@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
|
||||
|
||||
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
|
||||
|
||||
## 🐋 Hosting a console
|
||||
# 🐋 Hosting a console
|
||||
|
||||
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
|
||||
|
||||
@@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To
|
||||
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
|
||||
```
|
||||
|
||||
## 📑 License
|
||||
# 📑 License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
@@ -24,6 +24,10 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
|
||||
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-for')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
@@ -69,6 +73,10 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
'have.text',
|
||||
'Waiting for Node Vote'
|
||||
);
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-against')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
|
||||
@@ -7,7 +7,6 @@ export type AssetBalanceProps = {
|
||||
price: string;
|
||||
showAssetLink?: boolean;
|
||||
showAssetSymbol?: boolean;
|
||||
rounded?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -19,17 +18,12 @@ const AssetBalance = ({
|
||||
price,
|
||||
showAssetLink = true,
|
||||
showAssetSymbol = false,
|
||||
rounded = false,
|
||||
}: AssetBalanceProps) => {
|
||||
const { data: asset, loading } = useAssetDataProvider(assetId);
|
||||
|
||||
const label =
|
||||
!loading && asset && asset.decimals
|
||||
? addDecimalsFixedFormatNumber(
|
||||
price,
|
||||
asset.decimals,
|
||||
rounded ? 0 : undefined
|
||||
)
|
||||
? addDecimalsFixedFormatNumber(price, asset.decimals)
|
||||
: price;
|
||||
|
||||
return (
|
||||
|
||||
@@ -41,7 +41,6 @@ export const Header = () => {
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.TREASURY,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => pages.find((r) => r.path === n))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type HashProps = React.HTMLProps<HTMLSpanElement> & {
|
||||
export type HashProps = {
|
||||
text: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
@@ -2,5 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
|
||||
export { default as PartyLink } from './party-link/party-link';
|
||||
export { default as NodeLink } from './node-link/node-link';
|
||||
export { default as MarketLink } from './market-link/market-link';
|
||||
export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link';
|
||||
export * from './asset-link/asset-link';
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type NetworkParameterLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
parameter: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Links a given network parameter to the relevant page and anchor on the page
|
||||
*/
|
||||
const NetworkParameterLink = ({
|
||||
parameter,
|
||||
...props
|
||||
}: NetworkParameterLinkProps) => {
|
||||
return (
|
||||
<Link
|
||||
className="underline"
|
||||
{...props}
|
||||
to={`/${Routes.NETWORK_PARAMETERS}#${parameter}`}
|
||||
>
|
||||
<Hash text={parameter} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkParameterLink;
|
||||
@@ -32,17 +32,9 @@ export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
networkLabel?: string;
|
||||
truncateLength?: number;
|
||||
};
|
||||
|
||||
const PartyLink = ({
|
||||
id,
|
||||
truncate = false,
|
||||
truncateLength = 4,
|
||||
networkLabel = t('Network'),
|
||||
...props
|
||||
}: PartyLinkProps) => {
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
const { data } = useExplorerNodeNamesQuery();
|
||||
const name = useMemo(() => getNameForParty(id, data), [data, id]);
|
||||
const useName = name !== id;
|
||||
@@ -52,7 +44,7 @@ const PartyLink = ({
|
||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="network">
|
||||
{networkLabel}
|
||||
{t('Network')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -78,11 +70,7 @@ const PartyLink = ({
|
||||
{useName ? (
|
||||
name
|
||||
) : (
|
||||
<Hash
|
||||
text={
|
||||
truncate ? truncateMiddle(id, truncateLength, truncateLength) : id
|
||||
}
|
||||
/>
|
||||
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
|
||||
)}
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
@@ -26,7 +26,7 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
>;
|
||||
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
const label = proposal?.rationale?.title || id;
|
||||
const label = proposal?.rationale.title || id;
|
||||
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
|
||||
@@ -175,7 +175,6 @@ describe('Amend order details', () => {
|
||||
|
||||
const res = renderExistingAmend('123', 1, amend);
|
||||
expect(await res.findByText('New size')).toBeInTheDocument();
|
||||
expect(await res.findByText('Size ±')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders Reference if provided', async () => {
|
||||
|
||||
@@ -82,7 +82,7 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('Size ±')}
|
||||
{t('New size')}
|
||||
</h2>
|
||||
<h5
|
||||
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
|
||||
@@ -93,16 +93,6 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
</h5>
|
||||
</div>
|
||||
) : null}
|
||||
{o && (
|
||||
<div className="">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New size')}
|
||||
</h2>
|
||||
<h5 className="mb-0 text-lg font-medium text-gray-500">
|
||||
{o ? o.size : null}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{amend.price && amend.price !== '0' ? (
|
||||
<div className="">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
||||
import { type AgGridReact } from 'ag-grid-react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGrid } from '@vegaprotocol/datagrid';
|
||||
@@ -11,7 +12,12 @@ import { type ColDef } from 'ag-grid-community';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { ProposalStateMapping } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { JsonViewerDialog } from '../dialogs/json-viewer-dialog';
|
||||
@@ -25,7 +31,15 @@ type ProposalsTableProps = {
|
||||
data: ProposalListFieldsFragment[] | null;
|
||||
};
|
||||
export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
]);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const requiredMajorityPercentage = useMemo(() => {
|
||||
const requiredMajority =
|
||||
params?.governance_proposal_market_requiredMajority ?? 1;
|
||||
return new BigNumber(requiredMajority).times(100);
|
||||
}, [params?.governance_proposal_market_requiredMajority]);
|
||||
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
@@ -76,6 +90,33 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
maxWidth: 100,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Voting'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center pt-2 uppercase">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'cDate',
|
||||
maxWidth: 150,
|
||||
@@ -143,7 +184,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
},
|
||||
},
|
||||
],
|
||||
[tokenLink]
|
||||
[requiredMajorityPercentage, tokenLink]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -5,10 +5,5 @@ query ExplorerProposalStatus($id: ID!) {
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
... on BatchProposal {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -8,7 +8,7 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | { __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`
|
||||
@@ -19,11 +19,6 @@ export const ExplorerProposalStatusDocument = gql`
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
... on BatchProposal {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BatchItem } from './batch-item';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
type Item = components['schemas']['vegaBatchProposalTermsChange'];
|
||||
|
||||
describe('BatchItem', () => {
|
||||
it('Renders "Unknown proposal type" by default', () => {
|
||||
const item = {};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Unknown proposal type" for unknown items', () => {
|
||||
const item = {
|
||||
newLochNessMonster: {
|
||||
location: 'Loch Ness',
|
||||
},
|
||||
} as unknown as Item;
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "New spot market"', () => {
|
||||
const item = {
|
||||
newSpotMarket: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('New spot market')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Cancel transfer"', () => {
|
||||
const item = {
|
||||
cancelTransfer: {
|
||||
changes: {
|
||||
transferId: 'transfer123',
|
||||
},
|
||||
},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
|
||||
expect(screen.getByText('transf')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Cancel transfer" without an id', () => {
|
||||
const item = {
|
||||
cancelTransfer: {
|
||||
changes: {},
|
||||
},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "New freeform"', () => {
|
||||
const item = {
|
||||
newFreeform: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('New freeform proposal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "New market"', () => {
|
||||
const item = {
|
||||
newMarket: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('New market')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "New transfer"', () => {
|
||||
const item = {
|
||||
newTransfer: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('New transfer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update asset" with assetId', () => {
|
||||
const item = {
|
||||
updateAsset: {
|
||||
assetId: 'asset123',
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update asset')).toBeInTheDocument();
|
||||
expect(screen.getByText('asset123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update asset" even if assetId is not set', () => {
|
||||
const item = {
|
||||
updateAsset: {
|
||||
assetId: undefined,
|
||||
},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Update asset')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update market state" with marketId', () => {
|
||||
const item = {
|
||||
updateMarketState: {
|
||||
changes: {
|
||||
marketId: 'market123',
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update market state')).toBeInTheDocument();
|
||||
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update market state" even if marketId is not set', () => {
|
||||
const item = {
|
||||
updateMarketState: {
|
||||
changes: {
|
||||
marketId: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update market state')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update network parameter" with parameter', () => {
|
||||
const item = {
|
||||
updateNetworkParameter: {
|
||||
changes: {
|
||||
key: 'parameter123',
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<BatchItem item={item} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
|
||||
expect(screen.getByText('parameter123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update network parameter" even if parameter is not set', () => {
|
||||
const item = {
|
||||
updateNetworkParameter: {
|
||||
changes: {
|
||||
key: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update referral program"', () => {
|
||||
const item = {
|
||||
updateReferralProgram: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(screen.getByText('Update referral program')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update spot market" with marketId', () => {
|
||||
const item = {
|
||||
updateSpotMarket: {
|
||||
marketId: 'market123',
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update spot market')).toBeInTheDocument();
|
||||
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update spot market" even if marketId is not set', () => {
|
||||
const item = {
|
||||
updateSpotMarket: {
|
||||
marketId: undefined,
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update spot market')).toBeInTheDocument();
|
||||
});
|
||||
it('Renders "Update market" with marketId', () => {
|
||||
const item = {
|
||||
updateMarket: {
|
||||
marketId: 'market123',
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update market')).toBeInTheDocument();
|
||||
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update market" even if marketId is not set', () => {
|
||||
const item = {
|
||||
updateMarket: {
|
||||
marketId: undefined,
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BatchItem item={item} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Update market')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders "Update volume discount program"', () => {
|
||||
const item = {
|
||||
updateVolumeDiscountProgram: {},
|
||||
};
|
||||
render(<BatchItem item={item} />);
|
||||
expect(
|
||||
screen.getByText('Update volume discount program')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import Hash from '../../../links/hash';
|
||||
|
||||
type Item = components['schemas']['vegaBatchProposalTermsChange'];
|
||||
|
||||
export interface BatchItemProps {
|
||||
item: Item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a one line summary for an item in a batch proposal. Could
|
||||
* easily be adapted to summarise individual proposals, but there is no
|
||||
* place for that yet.
|
||||
*
|
||||
* Details (like IDs) should be shown and linked if available, but handled
|
||||
* if not available. This is adequate as the ProposalSummary component contains
|
||||
* a JSON viewer for the full proposal.
|
||||
*/
|
||||
export const BatchItem = ({ item }: BatchItemProps) => {
|
||||
if (item.cancelTransfer) {
|
||||
const transferId = item?.cancelTransfer?.changes?.transferId || false;
|
||||
return (
|
||||
<span>
|
||||
{t('Cancel transfer')}
|
||||
{transferId && (
|
||||
<Hash className="ml-1" truncate={true} text={transferId} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
} else if (item.newFreeform) {
|
||||
return <span>{t('New freeform proposal')}</span>;
|
||||
} else if (item.newMarket) {
|
||||
return <span>{t('New market')}</span>;
|
||||
} else if (item.newSpotMarket) {
|
||||
return <span>{t('New spot market')}</span>;
|
||||
} else if (item.newTransfer) {
|
||||
return <span>{t('New transfer')}</span>;
|
||||
} else if (item.updateAsset) {
|
||||
const assetId = item?.updateAsset?.assetId || false;
|
||||
return (
|
||||
<span>
|
||||
{t('Update asset')}
|
||||
{assetId && <AssetLink className="ml-1" assetId={assetId} />}
|
||||
</span>
|
||||
);
|
||||
} else if (item.updateMarket) {
|
||||
const marketId = item?.updateMarket?.marketId || false;
|
||||
return (
|
||||
<span>
|
||||
{t('Update market')}{' '}
|
||||
{marketId && <MarketLink className="ml-1" id={marketId} />}
|
||||
</span>
|
||||
);
|
||||
} else if (item.updateMarketState) {
|
||||
const marketId = item?.updateMarketState?.changes?.marketId || false;
|
||||
return (
|
||||
<span>
|
||||
{t('Update market state')}
|
||||
{marketId && <MarketLink className="ml-1" id={marketId} />}
|
||||
</span>
|
||||
);
|
||||
} else if (item.updateNetworkParameter) {
|
||||
const param = item?.updateNetworkParameter?.changes?.key || false;
|
||||
return (
|
||||
<span>
|
||||
{t('Update network parameter')}
|
||||
{param && <NetworkParameterLink className="ml-1" parameter={param} />}
|
||||
</span>
|
||||
);
|
||||
} else if (item.updateReferralProgram) {
|
||||
return <span>{t('Update referral program')}</span>;
|
||||
} else if (item.updateSpotMarket) {
|
||||
const marketId = item?.updateSpotMarket?.marketId || '';
|
||||
return (
|
||||
<span>
|
||||
{t('Update spot market')}
|
||||
<MarketLink className="ml-1" id={marketId} />
|
||||
</span>
|
||||
);
|
||||
} else if (item.updateVolumeDiscountProgram) {
|
||||
return <span>{t('Update volume discount program')}</span>;
|
||||
}
|
||||
|
||||
return <span>{t('Unknown proposal type')}</span>;
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ProposalTerms } from '../tx-proposal';
|
||||
import { useState } from 'react';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
|
||||
import ProposalLink from '../../../links/proposal-link/proposal-link';
|
||||
import truncate from 'lodash/truncate';
|
||||
@@ -7,12 +9,7 @@ import ReactMarkdown from 'react-markdown';
|
||||
import { ProposalDate } from './proposal-date';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import type { ProposalTerms } from '../tx-proposal';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { BatchItem } from './batch-item';
|
||||
|
||||
type Rationale = components['schemas']['vegaProposalRationale'];
|
||||
type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes'];
|
||||
|
||||
type ProposalTermsDialog = {
|
||||
open: boolean;
|
||||
@@ -24,7 +21,6 @@ interface ProposalSummaryProps {
|
||||
id: string;
|
||||
rationale?: Rationale;
|
||||
terms?: ProposalTerms;
|
||||
batch?: Batch;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +31,6 @@ export const ProposalSummary = ({
|
||||
id,
|
||||
rationale,
|
||||
terms,
|
||||
batch,
|
||||
}: ProposalSummaryProps) => {
|
||||
const [dialog, setDialog] = useState<ProposalTermsDialog>({
|
||||
open: false,
|
||||
@@ -64,9 +59,7 @@ export const ProposalSummary = ({
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
|
||||
{id && <ProposalStatusIcon id={id} />}
|
||||
{rationale?.title && (
|
||||
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
|
||||
)}
|
||||
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
|
||||
{rationale?.description && (
|
||||
<div className="pt-2 text-sm leading-tight">
|
||||
<ReactMarkdown
|
||||
@@ -79,18 +72,6 @@ export const ProposalSummary = ({
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
{batch && (
|
||||
<section className="pt-2 text-sm leading-tight my-3">
|
||||
<h2 className="text-lg pb-1">{t('Changes')}</h2>
|
||||
<ol>
|
||||
{batch.map((change, index) => (
|
||||
<li className="ml-4 list-decimal" key={`batch-${index}`}>
|
||||
<BatchItem item={change} />
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
)}
|
||||
<div className="pt-5">
|
||||
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
|
||||
{t('View terms')}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TxDetailsShared } from '../shared/tx-details-shared';
|
||||
import { TableWithTbody } from '../../../table';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
|
||||
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TableCell, TableRow } from '../../../table';
|
||||
|
||||
type Update = components['schemas']['v1UpdatePartyProfile'];
|
||||
|
||||
interface TxDetailsUpdatePartyProfileProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Party profiles can be an alias and arbitrary key/values pairs.
|
||||
* This component displays the alias, if any, but not the metadata. When there is
|
||||
* some wider usage, we can decide how to render it. For now, it's available in the
|
||||
* full TX details.
|
||||
*/
|
||||
export const TxDetailsUpdatePartyProfile = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsUpdatePartyProfileProps) => {
|
||||
if (!txData?.command.updatePartyProfile) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const update: Update = txData.command.updatePartyProfile;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{update.alias && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('New alias')}</TableCell>
|
||||
<TableCell>{update.alias}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
status
|
||||
reason
|
||||
fromAccountType
|
||||
from
|
||||
to
|
||||
toAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
|
||||
|
||||
|
||||
export const ExplorerTransferStatusDocument = gql`
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
status
|
||||
reason
|
||||
fromAccountType
|
||||
from
|
||||
to
|
||||
toAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTransferStatusQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTransferStatusQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerTransferStatusQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
|
||||
}
|
||||
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
|
||||
}
|
||||
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
|
||||
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
|
||||
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
|
||||
+2
-2
@@ -111,7 +111,7 @@ export function TransferParticipants({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-white dark:fill-black"
|
||||
className="fill-vega-light-100 dark:fill-black"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
@@ -120,7 +120,7 @@ export function TransferParticipants({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-vega-light-200 dark:fill-vega-dark-200"
|
||||
className="fill-vega-light-100 dark:fill-vega-dark-200"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
|
||||
+52
-178
@@ -1,223 +1,97 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { AssetLink, MarketLink } from '../../../../links';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import type { components } from '../../../../../../types/explorer';
|
||||
import type { Recurring } from '../transfer-details';
|
||||
import {
|
||||
DispatchMetricLabels,
|
||||
DistributionStrategy,
|
||||
} from '@vegaprotocol/types';
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { DispatchMetricLabels } from '@vegaprotocol/types';
|
||||
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
export type Strategy = components['schemas']['vegaDispatchStrategy'];
|
||||
|
||||
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
|
||||
export const headerClasses =
|
||||
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
|
||||
|
||||
const metricLabels: Record<Metric, string> = {
|
||||
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
|
||||
...DispatchMetricLabels,
|
||||
};
|
||||
|
||||
// Maps the two (non-null) values of entityScope to the icon that represents it
|
||||
const entityScopeIcons: Record<
|
||||
string,
|
||||
typeof VegaIconNames[keyof typeof VegaIconNames]
|
||||
> = {
|
||||
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
|
||||
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
|
||||
};
|
||||
|
||||
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
|
||||
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
|
||||
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
|
||||
};
|
||||
|
||||
interface TransferRewardsProps {
|
||||
recurring: Recurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders recurring transfers/game details in a way that is, perhaps, easy to understand
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferRewards({ recurring }: TransferRewardsProps) {
|
||||
const metric =
|
||||
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
|
||||
|
||||
if (!recurring || !recurring.dispatchStrategy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Destructure to make things a bit more readable
|
||||
const {
|
||||
entityScope,
|
||||
individualScope,
|
||||
teamScope,
|
||||
distributionStrategy,
|
||||
lockPeriod,
|
||||
markets,
|
||||
stakingRequirement,
|
||||
windowLength,
|
||||
notionalTimeWeightedAveragePositionRequirement,
|
||||
rankTable,
|
||||
nTopPerformers,
|
||||
} = recurring.dispatchStrategy;
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
|
||||
<ul className="relative block rounded-lg py-6 text-left p-6">
|
||||
{entityScope && entityScopeIcons[entityScope] ? (
|
||||
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
|
||||
<ul className="relative block rounded-lg py-6 text-center p-6">
|
||||
{recurring.dispatchStrategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Scope')}</strong>:{' '}
|
||||
<VegaIcon name={entityScopeIcons[entityScope]} />
|
||||
|
||||
{individualScope ? individualScopeLabels[individualScope] : null}
|
||||
{getScopeLabel(entityScope, teamScope)}
|
||||
<strong>{t('Asset')}</strong>:{' '}
|
||||
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
|
||||
</li>
|
||||
) : null}
|
||||
{recurring.dispatchStrategy &&
|
||||
recurring.dispatchStrategy.assetForMetric && (
|
||||
<li>
|
||||
<strong>{t('Asset for metric')}</strong>:{' '}
|
||||
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
|
||||
</li>
|
||||
)}
|
||||
{recurring.dispatchStrategy.metric &&
|
||||
metricLabels[recurring.dispatchStrategy.metric] && (
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>:{' '}
|
||||
{metricLabels[recurring.dispatchStrategy.metric]}
|
||||
</li>
|
||||
)}
|
||||
{lockPeriod && (
|
||||
<li>
|
||||
<strong>{t('Reward lock')}</strong>:
|
||||
{recurring.dispatchStrategy.lockPeriod}{' '}
|
||||
{recurring.dispatchStrategy.lockPeriod === '1'
|
||||
? t('epoch')
|
||||
: t('epochs')}
|
||||
</li>
|
||||
)}
|
||||
|
||||
{markets && markets.length > 0 ? (
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
|
||||
</li>
|
||||
{recurring.dispatchStrategy.markets &&
|
||||
recurring.dispatchStrategy.markets.length > 0 ? (
|
||||
<li>
|
||||
<strong>{t('Markets in scope')}</strong>:
|
||||
<ul className="inline-block ml-1">
|
||||
{markets.map((m) => (
|
||||
<li key={m} className="inline-block mr-2">
|
||||
<ul>
|
||||
{recurring.dispatchStrategy.markets.map((m) => (
|
||||
<li key={m}>
|
||||
<MarketLink id={m} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{stakingRequirement && stakingRequirement !== '0' ? (
|
||||
<li>
|
||||
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{windowLength && windowLength !== '0' ? (
|
||||
<li>
|
||||
<strong>{t('Window length')}</strong>:{' '}
|
||||
{recurring.dispatchStrategy.windowLength}{' '}
|
||||
{recurring.dispatchStrategy.windowLength === '1'
|
||||
? t('epoch')
|
||||
: t('epochs')}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{notionalTimeWeightedAveragePositionRequirement &&
|
||||
notionalTimeWeightedAveragePositionRequirement !== '' ? (
|
||||
<li>
|
||||
<strong>{t('Notional TWAP')}</strong>:{' '}
|
||||
{notionalTimeWeightedAveragePositionRequirement}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{nTopPerformers && (
|
||||
<li>
|
||||
<strong>{t('Elligible team members:')}</strong> top{' '}
|
||||
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
|
||||
</li>
|
||||
)}
|
||||
|
||||
{distributionStrategy &&
|
||||
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
|
||||
<li>
|
||||
<strong>{t('Distribution strategy')}</strong>:{' '}
|
||||
{distributionStrategyLabel[distributionStrategy]}
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<strong>{t('Factor')}</strong>: {recurring.factor}
|
||||
</li>
|
||||
</ul>
|
||||
<div className="px-6 pt-1 pb-5">
|
||||
{rankTable && rankTable.length > 0 ? (
|
||||
<table className="border-collapse border border-gray-400 ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border border-gray-300 bg-gray-300 px-3">
|
||||
<strong>{t('Start rank')}</strong>
|
||||
</th>
|
||||
<th className="border border-gray-300 bg-gray-300 px-3">
|
||||
<strong>{t('Share of reward pool')}</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rankTable.map((row, i) => {
|
||||
return (
|
||||
<tr key={`rank-${i}`}>
|
||||
<td className="border border-slate-300 text-center">
|
||||
{row.startRank}
|
||||
</td>
|
||||
<td className="border border-slate-300 text-center">
|
||||
{row.shareRatio}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getScopeLabel(
|
||||
scope: components['schemas']['vegaEntityScope'] | undefined,
|
||||
teamScope: readonly string[] | undefined
|
||||
): string {
|
||||
if (scope === 'ENTITY_SCOPE_TEAMS') {
|
||||
if (teamScope && teamScope.length !== 0) {
|
||||
return ` ${teamScope.length} teams`;
|
||||
} else {
|
||||
return t('All teams');
|
||||
}
|
||||
} else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
|
||||
return t('Individuals');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
export function getRewardTitle(
|
||||
scope?: components['schemas']['vegaEntityScope']
|
||||
) {
|
||||
if (scope === 'ENTITY_SCOPE_TEAMS') {
|
||||
return t('Game');
|
||||
}
|
||||
return t('Reward metrics');
|
||||
interface TransferRecurringStrategyProps {
|
||||
strategy: Strategy;
|
||||
}
|
||||
|
||||
const individualScopeLabels: Record<
|
||||
components['schemas']['vegaIndividualScope'],
|
||||
string
|
||||
> = {
|
||||
// Unspecified and All are not rendered
|
||||
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
|
||||
INDIVIDUAL_SCOPE_ALL: '',
|
||||
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
|
||||
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
|
||||
};
|
||||
/**
|
||||
* Simple renderer for a dispatch strategy in a recurring transfer
|
||||
*
|
||||
* @param strategy Dispatch strategy object
|
||||
*/
|
||||
export function TransferRecurringStrategy({
|
||||
strategy,
|
||||
}: TransferRecurringStrategyProps) {
|
||||
if (!strategy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{strategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Asset for metric')}</strong>:{' '}
|
||||
<AssetLink assetId={strategy.assetForMetric} />
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {strategy.metric}
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
|
||||
interface TransferStatusProps {
|
||||
status: TransferStatus | undefined;
|
||||
error: ApolloError | undefined;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
||||
if (!status) {
|
||||
status = TransferStatus.STATUS_PENDING;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Status')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center p-6">
|
||||
{loading ? (
|
||||
<div className="leading-10 mt-12">
|
||||
<Loader size={'small'} />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="leading-10 my-2">
|
||||
<TransferStatusIcon status={status} />
|
||||
</p>
|
||||
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TransferStatusIconProps {
|
||||
status: TransferStatus;
|
||||
}
|
||||
|
||||
export function TransferStatusIcon({ status }: TransferStatusIconProps) {
|
||||
return (
|
||||
<span title={TransferStatusMapping[status]}>
|
||||
<Icon
|
||||
name={getIconForStatus(status)}
|
||||
className={getColourForStatus(status)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple mapping from status to icon name
|
||||
* @param status TransferStatus
|
||||
* @returns IconName
|
||||
*/
|
||||
export function getIconForStatus(status: TransferStatus): IconName {
|
||||
switch (status) {
|
||||
case TransferStatus.STATUS_PENDING:
|
||||
return IconNames.TIME;
|
||||
case TransferStatus.STATUS_DONE:
|
||||
return IconNames.TICK;
|
||||
case TransferStatus.STATUS_REJECTED:
|
||||
return IconNames.CROSS;
|
||||
case TransferStatus.STATUS_CANCELLED:
|
||||
return IconNames.CROSS;
|
||||
default:
|
||||
return IconNames.TIME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple mapping from status to colour
|
||||
* @param status TransferStatus
|
||||
* @returns string Tailwind classname
|
||||
*/
|
||||
export function getColourForStatus(status: TransferStatus): string {
|
||||
switch (status) {
|
||||
case TransferStatus.STATUS_PENDING:
|
||||
return 'text-yellow-500';
|
||||
case TransferStatus.STATUS_DONE:
|
||||
return 'text-green-500';
|
||||
case TransferStatus.STATUS_REJECTED:
|
||||
return 'text-red-500';
|
||||
case TransferStatus.STATUS_CANCELLED:
|
||||
return 'text-red-600';
|
||||
default:
|
||||
return 'text-yellow-500';
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,12 @@ import type { components } from '../../../../../types/explorer';
|
||||
import { TransferRepeat } from './blocks/transfer-repeat';
|
||||
import { TransferRewards } from './blocks/transfer-rewards';
|
||||
import { TransferParticipants } from './blocks/transfer-participants';
|
||||
import { useExplorerTransferStatusQuery } from './__generated__/Transfer';
|
||||
import { TransferStatusView } from './blocks/transfer-status';
|
||||
import { TransferStatus } from '@vegaprotocol/types';
|
||||
|
||||
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
|
||||
export const wrapperClasses =
|
||||
'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
|
||||
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
|
||||
export const headerClasses =
|
||||
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
|
||||
|
||||
@@ -19,7 +16,6 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
interface TransferDetailsProps {
|
||||
transfer: Transfer;
|
||||
from: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,24 +24,13 @@ interface TransferDetailsProps {
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
|
||||
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
|
||||
const recurring = transfer.recurring;
|
||||
|
||||
// Currently all this is passed in to TransferStatus, but the extra details
|
||||
// may be useful in the future.
|
||||
const { data, error, loading } = useExplorerTransferStatusQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
const status = error
|
||||
? TransferStatus.STATUS_REJECTED
|
||||
: data?.transfer?.transfer.status;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap">
|
||||
<div className="flex gap-5 flex-wrap">
|
||||
<TransferParticipants from={from} transfer={transfer} />
|
||||
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
|
||||
<TransferStatusView status={status} error={error} loading={loading} />
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
<TransferRewards recurring={transfer.recurring} />
|
||||
) : null}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import {
|
||||
getScopeLabel,
|
||||
getRewardTitle,
|
||||
TransferRewards,
|
||||
} from './blocks/transfer-rewards';
|
||||
import { render } from '@testing-library/react';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import type { Recurring } from './transfer-details';
|
||||
import {
|
||||
DispatchMetric,
|
||||
DistributionStrategy,
|
||||
EntityScope,
|
||||
IndividualScope,
|
||||
} from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
describe('getScopeLabel', () => {
|
||||
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
|
||||
const scope = 'ENTITY_SCOPE_TEAMS';
|
||||
const teamScope = ['team1', 'team2', 'team3'];
|
||||
const expectedLabel = ' 3 teams';
|
||||
|
||||
const result = getScopeLabel(scope, teamScope);
|
||||
|
||||
expect(result).toEqual(expectedLabel);
|
||||
});
|
||||
|
||||
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
|
||||
const scope = 'ENTITY_SCOPE_TEAMS';
|
||||
const teamScope = undefined;
|
||||
const expectedLabel = 'All teams';
|
||||
|
||||
const result = getScopeLabel(scope, teamScope);
|
||||
|
||||
expect(result).toEqual(expectedLabel);
|
||||
});
|
||||
|
||||
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
|
||||
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
|
||||
const teamScope = undefined;
|
||||
const expectedLabel = 'Individuals';
|
||||
|
||||
const result = getScopeLabel(scope, teamScope);
|
||||
|
||||
expect(result).toEqual(expectedLabel);
|
||||
});
|
||||
|
||||
it('should return an empty string for unknown scope', () => {
|
||||
const scope = 'UNKNOWN_SCOPE';
|
||||
const teamScope = undefined;
|
||||
const expectedLabel = '';
|
||||
|
||||
const result = getScopeLabel(
|
||||
scope as unknown as components['schemas']['vegaEntityScope'],
|
||||
teamScope
|
||||
);
|
||||
|
||||
expect(result).toEqual(expectedLabel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRewardTitle', () => {
|
||||
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
|
||||
const scope = 'ENTITY_SCOPE_TEAMS';
|
||||
const expectedTitle = 'Game';
|
||||
|
||||
const result = getRewardTitle(scope);
|
||||
|
||||
expect(result).toEqual(expectedTitle);
|
||||
});
|
||||
|
||||
it('should return the correct title for other scopes', () => {
|
||||
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
|
||||
const expectedTitle = 'Reward metrics';
|
||||
|
||||
const result = getRewardTitle(scope);
|
||||
|
||||
expect(result).toEqual(expectedTitle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TransferRewards', () => {
|
||||
it('should render nothing if recurring dispatchStrategy is not provided', () => {
|
||||
const { container } = render(
|
||||
<TransferRewards recurring={null as unknown as Recurring} />
|
||||
);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
|
||||
const { container } = render(
|
||||
<TransferRewards recurring={{} as unknown as Recurring} />
|
||||
);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the reward details correctly', () => {
|
||||
const recurring = {
|
||||
dispatchStrategy: {
|
||||
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
|
||||
assetForMetric: '123',
|
||||
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
|
||||
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
|
||||
teamScope: [],
|
||||
distributionStrategy:
|
||||
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
|
||||
lockPeriod: 'lockPeriod',
|
||||
markets: ['market1', 'market2'],
|
||||
stakingRequirement: '1',
|
||||
windowLength: 'windowLength',
|
||||
notionalTimeWeightedAveragePositionRequirement:
|
||||
'notionalTimeWeightedAveragePositionRequirement',
|
||||
rankTable: [
|
||||
{ startRank: 1, shareRatio: 0.2 },
|
||||
{ startRank: 2, shareRatio: 0.3 },
|
||||
],
|
||||
nTopPerformers: 'nTopPerformers',
|
||||
},
|
||||
};
|
||||
|
||||
const { getByText } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<TransferRewards recurring={recurring} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(getByText('Game')).toBeInTheDocument();
|
||||
expect(getByText('Scope')).toBeInTheDocument();
|
||||
expect(getByText('Asset for metric')).toBeInTheDocument();
|
||||
expect(getByText('Metric')).toBeInTheDocument();
|
||||
expect(getByText('Reward lock')).toBeInTheDocument();
|
||||
expect(getByText('Markets in scope')).toBeInTheDocument();
|
||||
expect(getByText('Staking requirement')).toBeInTheDocument();
|
||||
expect(getByText('Window length')).toBeInTheDocument();
|
||||
expect(getByText('Notional TWAP')).toBeInTheDocument();
|
||||
expect(getByText('Elligible team members:')).toBeInTheDocument();
|
||||
expect(getByText('Distribution strategy')).toBeInTheDocument();
|
||||
expect(getByText('Start rank')).toBeInTheDocument();
|
||||
expect(getByText('Share of reward pool')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
|
||||
const recurring = {
|
||||
dispatchStrategy: {
|
||||
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
|
||||
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
|
||||
teamScope: ['team1', 'team2', 'team3'],
|
||||
distributionStrategy:
|
||||
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
|
||||
lockPeriod: 'lockPeriod',
|
||||
markets: ['market1', 'market2'],
|
||||
stakingRequirement: 'stakingRequirement',
|
||||
windowLength: 'windowLength',
|
||||
notionalTimeWeightedAveragePositionRequirement:
|
||||
'notionalTimeWeightedAveragePositionRequirement',
|
||||
nTopPerformers: 'nTopPerformers',
|
||||
},
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<TransferRewards recurring={recurring} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(container.querySelector('table')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import { ProposalSummary } from './proposal/summary';
|
||||
import Hash from '../../links/hash';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type Proposal = components['schemas']['v1BatchProposalSubmission'];
|
||||
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||
|
||||
interface TxBatchProposalProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const TxBatchProposal = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxBatchProposalProps) => {
|
||||
if (!txData || !txData.command.batchProposalSubmission) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
let deterministicId = '';
|
||||
|
||||
const proposal: Proposal = txData.command.batchProposalSubmission;
|
||||
const sig = txData?.signature?.value;
|
||||
if (sig) {
|
||||
deterministicId = txSignatureToDeterministicId(sig);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{t('Batch proposal')}</TableCell>
|
||||
</TableRow>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
hideTypeRow={true}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Batch size')}</TableCell>
|
||||
<TableCell>
|
||||
{proposal.terms?.changes?.length || t('No changes')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal ID')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={deterministicId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
{proposal && (
|
||||
<ProposalSummary
|
||||
id={deterministicId}
|
||||
rationale={proposal?.rationale}
|
||||
terms={proposal.terms}
|
||||
batch={proposal.terms?.changes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -32,9 +32,6 @@ import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
|
||||
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
|
||||
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
||||
import { TxDetailsJoinTeam } from './tx-join-team';
|
||||
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
|
||||
import { TxBatchProposal } from './tx-batch-proposal';
|
||||
import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -136,12 +133,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsApplyReferralCode;
|
||||
case 'Join Team':
|
||||
return TxDetailsJoinTeam;
|
||||
case 'Update Margin Mode':
|
||||
return TxDetailsUpdateMarginMode;
|
||||
case 'Batch Proposal':
|
||||
return TxBatchProposal;
|
||||
case 'Update Party Profile':
|
||||
return TxDetailsUpdatePartyProfile;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
|
||||
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
|
||||
import { MarketLink } from '../../links';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { TransferDetails } from './transfer/transfer-details';
|
||||
import { proposalToTransfer } from '../lib/proposal-to-transfer';
|
||||
|
||||
export type Proposal = components['schemas']['v1ProposalSubmission'];
|
||||
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||
@@ -106,12 +104,6 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
? ProposalSignatureBundleNewAsset
|
||||
: ProposalSignatureBundleUpdateAsset;
|
||||
|
||||
let transfer, from;
|
||||
if (proposal.terms?.newTransfer?.changes) {
|
||||
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
|
||||
from = proposal.terms.newTransfer.changes.source;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
@@ -157,26 +149,14 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
</>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
<ProposalSummary
|
||||
id={deterministicId}
|
||||
rationale={proposal.rationale}
|
||||
terms={proposal?.terms}
|
||||
/>
|
||||
|
||||
{proposalRequiresSignatureBundle(proposal) && (
|
||||
<SignatureBundleComponent id={deterministicId} tx={tx} />
|
||||
)}
|
||||
|
||||
{transfer && (
|
||||
<div className="mt-8">
|
||||
<TransferDetails
|
||||
transfer={transfer}
|
||||
from={from || ''}
|
||||
id={deterministicId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
SPECIAL_CASE_NETWORK_ID,
|
||||
} from '../../links/party-link/party-link';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import Hash from '../../links/hash';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
@@ -61,7 +60,7 @@ export const TxDetailsTransfer = ({
|
||||
}
|
||||
|
||||
const from = txData.submitter;
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
@@ -72,7 +71,7 @@ export const TxDetailsTransfer = ({
|
||||
<TableRow modifier="bordered" data-testid="id">
|
||||
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={id} />
|
||||
{txSignatureToDeterministicId(txData.signature.value)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TxDetailsShared
|
||||
@@ -106,7 +105,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
<TransferDetails from={from} transfer={transfer} id={id} />
|
||||
<TransferDetails from={from} transfer={transfer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { MarketLink } from '../../links';
|
||||
|
||||
interface TxDetailsUpdateMarginModeProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
type Mode = components['schemas']['UpdateMarginModeMode'];
|
||||
|
||||
const MarginModeLabels: Record<Mode, string> = {
|
||||
MODE_CROSS_MARGIN: t('Cross margin'),
|
||||
MODE_ISOLATED_MARGIN: t('Isolated margin'),
|
||||
MODE_UNSPECIFIED: t('Unspecified'),
|
||||
};
|
||||
|
||||
export const TxDetailsUpdateMarginMode = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsUpdateMarginModeProps) => {
|
||||
if (!txData || !txData.command.updateMarginMode) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const u: components['schemas']['v1UpdateMarginMode'] =
|
||||
txData.command.updateMarginMode;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{u.marketId && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market ID')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={u.marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{u.mode && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('New margin mode')}</TableCell>
|
||||
<TableCell>{MarginModeLabels[u.mode]}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{u.marginFactor && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Margin factor')}</TableCell>
|
||||
<TableCell>{u.marginFactor}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { components } from '../../../../types/explorer';
|
||||
|
||||
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
|
||||
type ActualTransfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
/**
|
||||
* Converts a governance proposal for a transfer in to a transfer command that the
|
||||
* TransferDetails component can then render. The types are very similar, but do not
|
||||
* map precisely to each other due to some missing fields and some different field
|
||||
* names.
|
||||
*
|
||||
* @param proposal Governance proposal for a transfer
|
||||
* @returns transfer a Transfer object as if it had been submitted
|
||||
*/
|
||||
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
|
||||
return {
|
||||
amount: proposal.amount,
|
||||
asset: proposal.asset,
|
||||
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
|
||||
// fromAccountType does exist and is just named differently on the proposal
|
||||
fromAccountType: proposal.sourceType,
|
||||
oneOff: proposal.oneOff,
|
||||
recurring: proposal.recurring,
|
||||
// There is no reference applied on governance initiated transfers
|
||||
reference: '',
|
||||
to: proposal.destination,
|
||||
toAccountType: proposal.destinationType,
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,6 @@ export type FilterOption =
|
||||
| 'Amend Order'
|
||||
| 'Apply Referral Code'
|
||||
| 'Batch Market Instructions'
|
||||
| 'Batch Proposal'
|
||||
| 'Cancel LiquidityProvision Order'
|
||||
| 'Cancel Order'
|
||||
| 'Cancel Transfer Funds'
|
||||
@@ -44,9 +43,7 @@ export type FilterOption =
|
||||
| 'Submit Order'
|
||||
| 'Transfer Funds'
|
||||
| 'Undelegate'
|
||||
| 'Update Party Profile'
|
||||
| 'Update Referral Set'
|
||||
| 'Update Margin Mode'
|
||||
| 'Validator Heartbeat'
|
||||
| 'Vote on Proposal'
|
||||
| 'Withdraw';
|
||||
@@ -62,25 +59,17 @@ export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Stop Orders Submission',
|
||||
'Stop Orders Cancellation',
|
||||
'Submit Order',
|
||||
'Update Margin Mode',
|
||||
],
|
||||
'Transfers and Withdrawals': [
|
||||
'Transfer Funds',
|
||||
'Cancel Transfer Funds',
|
||||
'Withdraw',
|
||||
],
|
||||
Governance: [
|
||||
'Batch Proposal',
|
||||
'Delegate',
|
||||
'Undelegate',
|
||||
'Vote on Proposal',
|
||||
'Proposal',
|
||||
],
|
||||
Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
|
||||
Referrals: [
|
||||
'Apply Referral Code',
|
||||
'Create Referral Set',
|
||||
'Join Team',
|
||||
'Update Party Profile',
|
||||
'Update Referral Set',
|
||||
],
|
||||
'External Data': ['Chain Event', 'Submit Oracle Data'],
|
||||
|
||||
@@ -12,5 +12,4 @@ export const Routes = {
|
||||
ORACLES: 'oracles',
|
||||
NETWORK_PARAMETERS: 'network-parameters',
|
||||
DISCLAIMER: 'disclaimer',
|
||||
TREASURY: 'treasury',
|
||||
};
|
||||
|
||||
@@ -30,7 +30,6 @@ import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||
import { Disclaimer } from './pages/disclaimer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
import { NetworkTreasury } from './treasury';
|
||||
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
@@ -230,17 +229,6 @@ export const useRouterConfig = () => {
|
||||
]
|
||||
: [];
|
||||
|
||||
const treasuryRoutes: Route[] = [
|
||||
{
|
||||
path: Routes.TREASURY,
|
||||
handle: {
|
||||
name: t('Treasury'),
|
||||
text: t('Treasury'),
|
||||
breadcrumb: () => <Link to={Routes.TREASURY}>{t('Treasury')}</Link>,
|
||||
},
|
||||
element: <NetworkTreasury />,
|
||||
},
|
||||
];
|
||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
@@ -370,7 +358,6 @@ export const useRouterConfig = () => {
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
...treasuryRoutes,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
query ExplorerTreasury {
|
||||
assetsConnection(pagination: { last: 1000 }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
networkTreasuryAccount {
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
query ExplorerTreasuryTransfers {
|
||||
transfersConnection(
|
||||
partyId: "network"
|
||||
direction: ToOrFrom
|
||||
pagination: { last: 200 }
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
transfer {
|
||||
timestamp
|
||||
from
|
||||
amount
|
||||
to
|
||||
status
|
||||
reason
|
||||
toAccountType
|
||||
fromAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
id
|
||||
status
|
||||
kind {
|
||||
... on OneOffTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringTransfer {
|
||||
startEpoch
|
||||
}
|
||||
... on OneOffGovernanceTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringGovernanceTransfer {
|
||||
endEpoch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTreasuryQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerTreasuryQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, networkTreasuryAccount?: { __typename?: 'AccountBalance', balance: string } | null } } | null> | null } | null };
|
||||
|
||||
|
||||
export const ExplorerTreasuryDocument = gql`
|
||||
query ExplorerTreasury {
|
||||
assetsConnection(pagination: {last: 1000}) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
networkTreasuryAccount {
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTreasuryQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTreasuryQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTreasuryQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerTreasuryQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTreasuryQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||
}
|
||||
export function useExplorerTreasuryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||
}
|
||||
export type ExplorerTreasuryQueryHookResult = ReturnType<typeof useExplorerTreasuryQuery>;
|
||||
export type ExplorerTreasuryLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryLazyQuery>;
|
||||
export type ExplorerTreasuryQueryResult = Apollo.QueryResult<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>;
|
||||
@@ -1,84 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTreasuryTransfersQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerTreasuryTransfersQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', timestamp: any, from: string, amount: string, to: string, status: Types.TransferStatus, reason?: string | null, toAccountType: Types.AccountType, fromAccountType: Types.AccountType, id: string, asset?: { __typename?: 'Asset', id: string } | null, kind: { __typename?: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename?: 'OneOffTransfer', deliverOn?: any | null } | { __typename?: 'RecurringGovernanceTransfer', endEpoch?: number | null } | { __typename?: 'RecurringTransfer', startEpoch: number } } } } | null> | null } | null };
|
||||
|
||||
|
||||
export const ExplorerTreasuryTransfersDocument = gql`
|
||||
query ExplorerTreasuryTransfers {
|
||||
transfersConnection(
|
||||
partyId: "network"
|
||||
direction: ToOrFrom
|
||||
pagination: {last: 200}
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
transfer {
|
||||
timestamp
|
||||
from
|
||||
amount
|
||||
to
|
||||
status
|
||||
reason
|
||||
toAccountType
|
||||
fromAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
id
|
||||
status
|
||||
kind {
|
||||
... on OneOffTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringTransfer {
|
||||
startEpoch
|
||||
}
|
||||
... on OneOffGovernanceTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringGovernanceTransfer {
|
||||
endEpoch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTreasuryTransfersQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTreasuryTransfersQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTreasuryTransfersQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerTreasuryTransfersQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTreasuryTransfersQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||
}
|
||||
export function useExplorerTreasuryTransfersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||
}
|
||||
export type ExplorerTreasuryTransfersQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersQuery>;
|
||||
export type ExplorerTreasuryTransfersLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersLazyQuery>;
|
||||
export type ExplorerTreasuryTransfersQueryResult = Apollo.QueryResult<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>;
|
||||
@@ -1,37 +0,0 @@
|
||||
// NOTE: These are a temporary measure, pulled from an old branch on console.
|
||||
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { USDc } from './usdc';
|
||||
import { Vega } from './vega';
|
||||
import { USDt } from './usdt';
|
||||
|
||||
export interface AssetIconProps {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A poorly implemented, limited support for asset icons.
|
||||
*
|
||||
* These are committed as 'deprecated' to discourage use outside the Treasury page. Rather
|
||||
* than use this, a better approach would be to use source contract addresses to match assets.
|
||||
* This will be done separately.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export function AssetIcon({ symbol }: AssetIconProps) {
|
||||
const s = symbol.toLowerCase();
|
||||
switch (s) {
|
||||
case 'a4a16e250a09a86061ec83c2f9466fc9dc33d332f86876ee74b6f128a5cd6710': // mainnet
|
||||
case 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d': // mainnet
|
||||
return <USDc size={32} />;
|
||||
case 'd1984e3d365faa05bcafbe41f50f90e3663ee7c0da22bb1e24b164e9532691b2': // mainnet
|
||||
case 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55': // testnet
|
||||
return <Vega size={32} />;
|
||||
case 'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba': // mainnet
|
||||
case 'ede4076aef07fd79502d14326c54ab3911558371baaf697a19d077f4f89de399': // testnet
|
||||
return <USDt size={32} />;
|
||||
default:
|
||||
return <Icon name={IconNames.BANK_ACCOUNT} size={8} />;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const USDc = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 2000 2000">
|
||||
<path
|
||||
d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z"
|
||||
fill="#2775ca"
|
||||
/>
|
||||
<path
|
||||
d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const USDt = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 339.43 295.27">
|
||||
<path
|
||||
fill="#50af95"
|
||||
d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const Vega = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 42 42">
|
||||
<rect width="42" height="42" rx="21" fill="black" />
|
||||
<path d="M13 27.2726H16.4545V10H13V27.2726Z" fill="white" />
|
||||
<path d="M25.667 23.8181H29.1215V10H25.667V23.8181Z" fill="white" />
|
||||
<path d="M19.333 33.6059H22.7875V30.1514H19.333V33.6059Z" fill="white" />
|
||||
<path
|
||||
d="M22.7871 30.7271H26.2416V27.2726H22.7871V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M29.1211 27.2726H31.9999V23.8181H29.1211V27.2726Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16.4551 30.7271H19.3339V27.2726H16.4551V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,171 +0,0 @@
|
||||
import type { DeepPartial } from '@apollo/client/utilities';
|
||||
import { parseResultsToAccounts } from './network-accounts-table';
|
||||
import {
|
||||
ExplorerTreasuryDocument,
|
||||
type ExplorerTreasuryQuery,
|
||||
} from '../__generated__/Treasury';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { NetworkAccountsTable } from './network-accounts-table';
|
||||
|
||||
describe('parseResultsToAccounts', () => {
|
||||
it('should return an array of non-zero treasury accounts', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'has0assets',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset3',
|
||||
networkTreasuryAccount: {
|
||||
balance: '50',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'hasnonetworktreasuryaccount',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
assetId: 'asset1',
|
||||
balance: '100',
|
||||
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||
},
|
||||
{
|
||||
assetId: 'asset3',
|
||||
balance: '50',
|
||||
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array if no non-zero accounts are found', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset2',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle missing data', () => {
|
||||
const result = parseResultsToAccounts(
|
||||
undefined as unknown as ExplorerTreasuryQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NetworkAccountsTable', () => {
|
||||
const mockData: ExplorerTreasuryQuery = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset2',
|
||||
networkTreasuryAccount: {
|
||||
balance: '50',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerTreasuryDocument,
|
||||
},
|
||||
result: {
|
||||
data: mockData,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it('should render network accounts (as many as match - often just 1)', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<NetworkAccountsTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// Wait for the data to load
|
||||
await screen.findByText('Loading...');
|
||||
|
||||
// Assert that the network accounts are rendered
|
||||
expect(screen.getByText('asset1')).toBeInTheDocument();
|
||||
expect(screen.getByText('asset2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle loading state', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<NetworkAccountsTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// Assert that the loading state is rendered
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
type ExplorerTreasuryQuery,
|
||||
useExplorerTreasuryQuery,
|
||||
} from '../__generated__/Treasury';
|
||||
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||
import { AssetLink } from '../../../components/links';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { AssetIcon } from './asset-icon';
|
||||
import { type NonZeroAccount } from '../network-treasury';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
|
||||
export const NetworkAccountsTable = () => {
|
||||
const { data, loading, error } = useExplorerTreasuryQuery({
|
||||
// This needs to ignore error as old assets may no longer properly resolve
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const shouldRound = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const c = parseResultsToAccounts(data);
|
||||
return (
|
||||
<section className="md:flex md:flex-row flex-wrap">
|
||||
{c.map((a) => (
|
||||
<div className="basis-1/2 md:basis-1/4">
|
||||
<div className="bg-white rounded overflow-hidden shadow-lg dark:bg-black dark:border-slate-500 dark:border">
|
||||
<div className="text-center p-6 bg-gray-100 dark:bg-slate-900 border-b dark:border-slate-500">
|
||||
<p className="flex justify-center">
|
||||
<AssetIcon symbol={a.assetId} />
|
||||
</p>
|
||||
<p className="mt-3" data-testid="name">
|
||||
<AssetLink assetId={a.assetId} />
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center py-5" data-testid="balance">
|
||||
<AssetBalance
|
||||
assetId={a.assetId}
|
||||
price={a.balance}
|
||||
showAssetSymbol={true}
|
||||
rounded={shouldRound}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function parseResultsToAccounts(
|
||||
data: ExplorerTreasuryQuery
|
||||
): NonZeroAccount[] {
|
||||
const nonZeroAccounts: NonZeroAccount[] = [];
|
||||
if (data?.assetsConnection?.edges) {
|
||||
const edges = removePaginationWrapper(data?.assetsConnection?.edges);
|
||||
if (edges) {
|
||||
edges.forEach((edge) => {
|
||||
if (
|
||||
edge.networkTreasuryAccount &&
|
||||
edge.networkTreasuryAccount?.balance !== '0'
|
||||
) {
|
||||
nonZeroAccounts.push({
|
||||
assetId: edge.id,
|
||||
balance: edge.networkTreasuryAccount?.balance,
|
||||
type: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nonZeroAccounts;
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import {
|
||||
typeLabel,
|
||||
getToAccountTypeLabel,
|
||||
filterAccountTransfers,
|
||||
} from './network-transfers-table';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { NetworkTransfersTable } from './network-transfers-table';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
ExplorerTreasuryTransfersDocument,
|
||||
type ExplorerTreasuryTransfersQuery,
|
||||
} from '../__generated__/TreasuryTransfers';
|
||||
import type { DeepPartial } from '@apollo/client/utilities';
|
||||
|
||||
describe('typeLabel', () => {
|
||||
it('should return "Transfer" for "OneOffTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
|
||||
});
|
||||
|
||||
it('should return "Transfer" for "RecurringTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
|
||||
});
|
||||
|
||||
it('should return "Unknown" for unknown kind', () => {
|
||||
expect(typeLabel()).toBe('Unknown');
|
||||
expect(typeLabel('')).toBe('Unknown');
|
||||
expect(typeLabel('InvalidKind')).toBe('Unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToAccountTypeLabel', () => {
|
||||
it('should return "Treasury" when type is ACCOUNT_TYPE_NETWORK_TREASURY', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_NETWORK_TREASURY)
|
||||
).toBe('Treasury');
|
||||
});
|
||||
|
||||
it('should return "Fees" when type is any of the fee account types', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE)
|
||||
).toBe('Fees');
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_MAKER)).toBe(
|
||||
'Fees'
|
||||
);
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY)).toBe(
|
||||
'Fees'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES)
|
||||
).toBe('Fees');
|
||||
expect(
|
||||
getToAccountTypeLabel(
|
||||
AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD
|
||||
)
|
||||
).toBe('Fees');
|
||||
});
|
||||
|
||||
it('should return "Insurance" when type is ACCOUNT_TYPE_GLOBAL_INSURANCE', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE)
|
||||
).toBe('Insurance');
|
||||
});
|
||||
|
||||
it('should return "Rewards" when type is any of the reward account types', () => {
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD)).toBe(
|
||||
'Rewards'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING)
|
||||
).toBe('Rewards');
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTED_REWARDS)).toBe(
|
||||
'Rewards'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTING_REWARDS)
|
||||
).toBe('Rewards');
|
||||
});
|
||||
|
||||
it('should return "Other" for any other type', () => {
|
||||
expect(getToAccountTypeLabel(undefined)).toBe('Other');
|
||||
expect(getToAccountTypeLabel('unknown' as AccountType)).toBe('Other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterAccountTransfers', () => {
|
||||
it('filters out transactions that are not to or from a treasury account', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = filterAccountTransfers(
|
||||
data as ExplorerTreasuryTransfersQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should return an empty array if no transfers match the filter', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = filterAccountTransfers(
|
||||
data as ExplorerTreasuryTransfersQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NetworkTransfersTable', () => {
|
||||
it('renders table headers correctly', async () => {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerTreasuryTransfersDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
id: '123',
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
amount: '100',
|
||||
asset: {
|
||||
id: '1',
|
||||
},
|
||||
timestamp: '2022-01-01T00:00:00Z',
|
||||
from: 'network',
|
||||
to: '7100a8a82ef45adb9efa070cc821c6c5c48172d6dc5f842431549490fe5897a0',
|
||||
reason: '',
|
||||
status: 'COMPLETED',
|
||||
kind: {
|
||||
__typename: 'OneOffGovernanceTransfer',
|
||||
deliverOn: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={true}>
|
||||
<MemoryRouter>
|
||||
<NetworkTransfersTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Amount')).toBeInTheDocument();
|
||||
expect(screen.getByText('Asset')).toBeInTheDocument();
|
||||
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||
expect(screen.getByText('From')).toBeInTheDocument();
|
||||
expect(screen.getByText('To')).toBeInTheDocument();
|
||||
expect(screen.getByText('Status')).toBeInTheDocument();
|
||||
expect(screen.getByText('Type')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('from-account').textContent).toEqual('Treasury');
|
||||
expect(screen.getByTestId('to-account').textContent).toEqual('7100…97a0');
|
||||
expect(screen.getByTestId('transfer-kind').textContent).toEqual(
|
||||
'Governance'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,253 +0,0 @@
|
||||
import { AsyncRenderer, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { AssetLink, PartyLink } from '../../../components/links';
|
||||
import {
|
||||
type ExplorerTreasuryTransfersQuery,
|
||||
useExplorerTreasuryTransfersQuery,
|
||||
} from '../__generated__/TreasuryTransfers';
|
||||
import { TimeAgo } from '../../../components/time-ago';
|
||||
import { TransferStatusIcon } from '../../../components/txs/details/transfer/blocks/transfer-status';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const colours = {
|
||||
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
|
||||
OUTGOING: '!fill-vega-pink-600 text-vega-pink-600 mr-2',
|
||||
};
|
||||
|
||||
export const theadClasses =
|
||||
'py-2 border text-center bg-vega-light-150 dark:bg-vega-dark-150';
|
||||
|
||||
export function getToAccountTypeLabel(type?: AccountType): string {
|
||||
switch (type) {
|
||||
case AccountType.ACCOUNT_TYPE_NETWORK_TREASURY:
|
||||
return t('Treasury');
|
||||
case AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE:
|
||||
case AccountType.ACCOUNT_TYPE_FEES_MAKER:
|
||||
case AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY:
|
||||
case AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD:
|
||||
return t('Fees');
|
||||
case AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE:
|
||||
return t('Insurance');
|
||||
case AccountType.ACCOUNT_TYPE_GLOBAL_REWARD:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING:
|
||||
case AccountType.ACCOUNT_TYPE_VESTED_REWARDS:
|
||||
case AccountType.ACCOUNT_TYPE_VESTING_REWARDS:
|
||||
return t('Rewards');
|
||||
default:
|
||||
return t('Other');
|
||||
}
|
||||
}
|
||||
|
||||
export function typeLabel(kind?: string): string {
|
||||
switch (kind) {
|
||||
case 'OneOffTransfer':
|
||||
case 'RecurringTransfer':
|
||||
return t('Transfer');
|
||||
case 'OneOffGovernanceTransfer':
|
||||
case 'RecurringGovernanceTransfer':
|
||||
return t('Governance');
|
||||
default:
|
||||
return t('Unknown');
|
||||
}
|
||||
}
|
||||
|
||||
export function filterAccountTransfers(data: ExplorerTreasuryTransfersQuery) {
|
||||
return data.transfersConnection?.edges
|
||||
?.filter((edge) => {
|
||||
if (
|
||||
edge?.node.transfer.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
edge?.node.transfer.fromAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map((edge) => {
|
||||
return edge?.node.transfer;
|
||||
});
|
||||
}
|
||||
|
||||
export const NetworkTransfersTable = () => {
|
||||
const { data, loading, error } = useExplorerTreasuryTransfersQuery({
|
||||
// This needs to ignore error as old assets may no longer properly resolve
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const shouldRound = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
const shouldTruncate = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg', 'xl'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
const shouldHideColumns = useMemo(
|
||||
() => ['xs', 'sm'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const c = filterAccountTransfers(data);
|
||||
if (!c) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<table className="table-fixed border-spacing-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={theadClasses}>{t('Amount')}</th>
|
||||
<th className={theadClasses}>{t('Asset')}</th>
|
||||
<th className={theadClasses}>{t('Age')}</th>
|
||||
<th className={theadClasses}>{t('From')}</th>
|
||||
<th className={theadClasses}>{t('To')}</th>
|
||||
<th
|
||||
className={`${theadClasses} ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Status')}
|
||||
</th>
|
||||
<th
|
||||
className={`${theadClasses} ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Type')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{c.map((a) => {
|
||||
const isIncoming =
|
||||
a?.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY;
|
||||
return (
|
||||
<tr>
|
||||
{a && a.amount && a.asset && (
|
||||
<td
|
||||
className={`px-2 py-1 border whitespace-nowrap text-right ${
|
||||
isIncoming ? colours.INCOMING : colours.OUTGOING
|
||||
}`}
|
||||
title={a.amount}
|
||||
>
|
||||
{a &&
|
||||
a.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY ? (
|
||||
<Icon
|
||||
name={IconNames.PLUS}
|
||||
className={colours.INCOMING}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={IconNames.MINUS}
|
||||
className={colours.OUTGOING}
|
||||
/>
|
||||
)}
|
||||
<AssetBalance
|
||||
assetId={a.asset.id}
|
||||
price={a.amount}
|
||||
showAssetLink={false}
|
||||
rounded={shouldRound}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
<td className="px-2 py-1 border whitespace-nowrap">
|
||||
{a && a.amount && a.asset && (
|
||||
<AssetLink
|
||||
assetId={a.asset.id}
|
||||
showAssetSymbol={true}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1 border">
|
||||
{a && a.timestamp && <TimeAgo date={a.timestamp} />}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-1 border"
|
||||
data-testid="from-account"
|
||||
>
|
||||
{a && a.from && (
|
||||
<PartyLink
|
||||
id={a.from}
|
||||
truncate={true}
|
||||
truncateLength={shouldTruncate ? 4 : 15}
|
||||
networkLabel={t('Treasury')}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1 border" data-testid="to-account">
|
||||
{a && a.to && (
|
||||
<PartyLink
|
||||
id={a.to}
|
||||
networkLabel={t('Treasury')}
|
||||
truncate={true}
|
||||
truncateLength={shouldTruncate ? 4 : 15}
|
||||
/>
|
||||
)}
|
||||
{a && !a.to && (
|
||||
<span
|
||||
className="underline decoration-dotted"
|
||||
title={AccountTypeMapping[a.toAccountType]}
|
||||
>
|
||||
{getToAccountTypeLabel(a.toAccountType)}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className={`px-2 py-1 border text-center ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{a && a.status && (
|
||||
<TransferStatusIcon status={a.status} />
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className={`px-2 py-1 border ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className="underline decoration-dotted"
|
||||
title={a?.kind.__typename}
|
||||
data-testid="transfer-kind"
|
||||
>
|
||||
{a && typeLabel(a.kind.__typename)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './network-treasury';
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { NetworkAccountsTable } from './components/network-accounts-table';
|
||||
import { NetworkTransfersTable } from './components/network-transfers-table';
|
||||
|
||||
export type NonZeroAccount = {
|
||||
assetId: string;
|
||||
balance: string;
|
||||
type: AccountType;
|
||||
};
|
||||
|
||||
export const NetworkTreasury = () => {
|
||||
useDocumentTitle(['Network Treasury']);
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
|
||||
<div>
|
||||
<NetworkAccountsTable />
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<h2 className="text-3xl mb-2">{t('Transfers')}</h2>
|
||||
<NetworkTransfersTable />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Explorer VEGA",
|
||||
"name": "Vega Protocol - Explorer",
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@@ -2,7 +2,7 @@ export const proposalsData = {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
proposalNode: {
|
||||
node: {
|
||||
id: 'e8ba9d268e12514644fd1fc7ff289292f4ce6489cc32cc73133aea52c04aef89',
|
||||
rationale: {
|
||||
title: 'Add asset Wrapped Ether',
|
||||
@@ -56,7 +56,7 @@ export const proposalsData = {
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
proposalNode: {
|
||||
node: {
|
||||
id: 'd848fc7881f13d366df5f61ab139d5fcfa72bf838151bb51b54381870e357931',
|
||||
rationale: {
|
||||
title: 'Add asset Dai Stablecoin',
|
||||
@@ -110,7 +110,60 @@ export const proposalsData = {
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
proposalNode: {
|
||||
node: {
|
||||
id: 'ccbd651b4a1167fd73c4a0340ac759fa0a31ca487ad46a13254b741ad71947ed',
|
||||
rationale: {
|
||||
title: 'New DAI market',
|
||||
description: 'New DAI market',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '0VFQusmmESdrP5GuL8naB6lxfoE3RPGaEeo7abdN',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-26T19:36:19.26034Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-26T19:36:42Z',
|
||||
enactmentDatetime: '2023-03-22T13:57:37Z',
|
||||
change: {
|
||||
instrument: {
|
||||
name: 'UNIDAI Monthly (Dec 2022)',
|
||||
code: 'UNIDAI.MF21',
|
||||
product: {
|
||||
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'bc70383f0e9515b15542cf4c63590cd2ca46b3363ba7c4a72af0e62112b3951b',
|
||||
rationale: {
|
||||
title: 'USDC-III',
|
||||
@@ -164,7 +217,60 @@ export const proposalsData = {
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
proposalNode: {
|
||||
node: {
|
||||
id: '9d9b2a9d0179d0e4ccb317f6c4a5db0b905d893190bfb5e5499985ef313281c8',
|
||||
rationale: {
|
||||
title: 'New BTC market',
|
||||
description: 'New BTC market',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: 'AXeRWS3TvLBFDgWOSHQpKFJf3NTbnWK6310q02fZ',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-26T19:36:19.26034Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-26T19:36:42Z',
|
||||
enactmentDatetime: '2023-03-22T13:57:37Z',
|
||||
change: {
|
||||
instrument: {
|
||||
name: 'ETHBTC Quarterly (Feb 2023)',
|
||||
code: 'ETHBTC.QM21',
|
||||
product: {
|
||||
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '9c48796e7988769ededc2b2b02220b00e93f65f23e8141bf1fd23a6983d95943',
|
||||
rationale: {
|
||||
title: 'Update governance.proposal.asset.requiredMajority',
|
||||
|
||||
@@ -236,7 +236,7 @@ context(
|
||||
});
|
||||
|
||||
// 1002-STKE-041 1002-STKE-053
|
||||
it.skip(
|
||||
it(
|
||||
'Able to remove part of a stake against a validator',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
|
||||
@@ -7,7 +7,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
describe('Links and buttons', function () {
|
||||
it.skip('should have link for proposal page', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
@@ -27,7 +27,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
cy.getByTestId('app-announcement').should('not.exist');
|
||||
});
|
||||
|
||||
it.skip('should show open or enacted proposals without proposal summary', function () {
|
||||
it('should show open or enacted proposals without proposal summary', function () {
|
||||
cy.get('body').then(($body) => {
|
||||
if (!$body.find('[data-testid="proposals-list-item"]').length) {
|
||||
cy.createMarket();
|
||||
@@ -51,7 +51,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have external link for governance', function () {
|
||||
it('should have external link for governance', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
@@ -59,7 +59,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have link for validator page', function () {
|
||||
it('should have link for validator page', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
.first()
|
||||
@@ -68,7 +68,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have external link for validators', function () {
|
||||
it('should have external link for validators', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
@@ -79,21 +79,21 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have information on active nodes', function () {
|
||||
it('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it.skip('should have information on consensus nodes', function () {
|
||||
it('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
|
||||
it.skip('should contain link to specific validators', function () {
|
||||
it('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '2')
|
||||
.each(($validator) => {
|
||||
@@ -101,7 +101,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have link for rewards page', function () {
|
||||
it('should have link for rewards page', function () {
|
||||
cy.getByTestId('home-rewards').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
@@ -110,7 +110,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have link for withdrawal page', function () {
|
||||
it('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('home-vega-token').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
@@ -132,7 +132,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
// 0006-NETW-003 0006-NETW-008 0006-NETW-009 0006-NETW-010 0006-NETW-012 0006-NETW-013 0006-NETW-017 0006-NETW-018 0006-NETW-019 0006-NETW-020
|
||||
it.skip('should have option to switch to different network node', function () {
|
||||
it('should have option to switch to different network node', function () {
|
||||
cy.getByTestId('git-network-data').within(() => {
|
||||
cy.getByTestId('link').click();
|
||||
});
|
||||
@@ -189,7 +189,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
cy.viewport('iphone-xr');
|
||||
});
|
||||
|
||||
it.skip('should have burger button', () => {
|
||||
it('should have burger button', () => {
|
||||
cy.getByTestId('button-menu-drawer').should('be.visible').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
});
|
||||
|
||||
@@ -33,12 +33,12 @@ context(
|
||||
verifyTabHighlighted(navigation.proposals);
|
||||
});
|
||||
|
||||
it.skip('should have GOVERNANCE header visible', function () {
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
verifyPageHeader('Proposals');
|
||||
});
|
||||
|
||||
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
|
||||
it.skip('new proposal page should have button for link to more information on proposals', function () {
|
||||
it('new proposal page should have button for link to more information on proposals', function () {
|
||||
cy.getByTestId('new-proposal-link').click();
|
||||
cy.url().should('include', '/proposals/propose/raw');
|
||||
cy.contains('To see Explorer data on proposals visit').within(() => {
|
||||
@@ -73,7 +73,7 @@ context(
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
it.skip('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001 // 3002-PROP-001
|
||||
cy.getByTestId(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
|
||||
@@ -46,11 +46,11 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
it.skip('Should have validators tab highlighted', function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
verifyTabHighlighted(navigation.validators);
|
||||
});
|
||||
|
||||
it.skip('Should have validators ON VEGA header visible', function () {
|
||||
it('Should have validators ON VEGA header visible', function () {
|
||||
verifyPageHeader('Validators');
|
||||
});
|
||||
|
||||
@@ -192,7 +192,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
// 1002-STKE-006
|
||||
it.skip('Should be able to see validator name', function () {
|
||||
it('Should be able to see validator name', function () {
|
||||
cy.getByTestId(validatorTitle).should('not.be.empty');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Governance VEGA",
|
||||
"name": "Vega Protocol - Governance",
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classnames from 'classnames';
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Dispatch, SetStateAction, ReactNode } from 'react';
|
||||
|
||||
interface CollapsibleToggleProps {
|
||||
@@ -30,7 +30,7 @@ export const CollapsibleToggle = ({
|
||||
<div className="flex items-center gap-3">
|
||||
{children}
|
||||
<div className={classes} data-testid="toggle-icon-wrapper">
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
@@ -13,10 +12,10 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
|
||||
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
|
||||
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||
import Routes from '../routes';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLinks, useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/__generated__/Proposals';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
import {
|
||||
getNotRejectedProposals,
|
||||
getNotRejectedProtocolUpgradeProposals,
|
||||
@@ -32,7 +31,7 @@ import {
|
||||
orderByUpgradeBlockHeight,
|
||||
} from '../proposals/components/proposals-list/proposals-list';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { type Proposal, type BatchProposal } from '../proposals/types';
|
||||
import { type Proposal } from '../proposals/types';
|
||||
|
||||
const nodesToShow = 6;
|
||||
|
||||
@@ -40,7 +39,7 @@ const HomeProposals = ({
|
||||
proposals,
|
||||
protocolUpgradeProposals,
|
||||
}: {
|
||||
proposals: Array<Proposal | BatchProposal>;
|
||||
proposals: Proposal[];
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -61,9 +60,12 @@ const HomeProposals = ({
|
||||
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
||||
))}
|
||||
|
||||
{compact(proposals).map((proposal) => {
|
||||
return <ProposalsListItem key={proposal.id} proposal={proposal} />;
|
||||
})}
|
||||
{proposals.map(
|
||||
(proposal) =>
|
||||
proposal?.id && (
|
||||
<ProposalsListItem key={proposal.id} proposal={proposal} />
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div className="mt-6">
|
||||
@@ -173,6 +175,7 @@ export const ValidatorDetailsLink = ({
|
||||
};
|
||||
|
||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -183,6 +186,11 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -204,18 +212,15 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
|
||||
useRefreshAfterEpoch(validatorsData?.epoch.timestamps.expiry, refetch);
|
||||
|
||||
const proposals = useMemo(() => {
|
||||
if (!proposalsData?.proposalsConnection?.edges?.length) return [];
|
||||
return proposalsData
|
||||
? getNotRejectedProposals(
|
||||
compact(
|
||||
proposalsData.proposalsConnection.edges.map(
|
||||
(edge) => edge?.proposalNode
|
||||
)
|
||||
const proposals = useMemo(
|
||||
() =>
|
||||
proposalsData
|
||||
? getNotRejectedProposals(
|
||||
removePaginationWrapper(proposalsData.proposalsConnection?.edges)
|
||||
)
|
||||
)
|
||||
: [];
|
||||
}, [proposalsData]);
|
||||
: [],
|
||||
[proposalsData]
|
||||
);
|
||||
|
||||
const sortedProposals = useMemo(
|
||||
() => orderByDate(proposals).reverse(),
|
||||
|
||||
@@ -1,493 +0,0 @@
|
||||
fragment UpdateMarketStates on UpdateMarketState {
|
||||
__typename
|
||||
updateType
|
||||
market {
|
||||
decimalPlaces
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
__typename
|
||||
... on Future {
|
||||
quoteName
|
||||
}
|
||||
... on Perpetual {
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
updateType
|
||||
price
|
||||
}
|
||||
|
||||
fragment UpdateReferralPrograms on UpdateReferralProgram {
|
||||
__typename
|
||||
benefitTiers {
|
||||
minimumEpochs
|
||||
minimumRunningNotionalTakerVolume
|
||||
referralDiscountFactor
|
||||
referralRewardFactor
|
||||
}
|
||||
endOfProgram: endOfProgramTimestamp
|
||||
windowLength
|
||||
stakingTiers {
|
||||
minimumStakedTokens
|
||||
referralRewardMultiplier
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateVolumeDiscountPrograms on UpdateVolumeDiscountProgram {
|
||||
__typename
|
||||
benefitTiers {
|
||||
minimumRunningNotionalTakerVolume
|
||||
volumeDiscountFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
}
|
||||
|
||||
# I prefix due to clash in libs/proposals
|
||||
fragment IUpdateMarketFields on UpdateMarket {
|
||||
__typename
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# I prefix due to clash in libs/proposals
|
||||
fragment INewMarketFields on NewMarket {
|
||||
__typename
|
||||
decimalPlaces
|
||||
metadata
|
||||
riskParameters {
|
||||
... on LogNormalRiskModel {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
mu
|
||||
r
|
||||
sigma
|
||||
}
|
||||
}
|
||||
... on SimpleRiskModel {
|
||||
params {
|
||||
factorLong
|
||||
factorShort
|
||||
}
|
||||
}
|
||||
}
|
||||
successorConfiguration {
|
||||
parentMarketId
|
||||
}
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
product {
|
||||
... on FutureProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
tradingTerminationProperty
|
||||
}
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on PerpetualProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
priceMonitoringParameters {
|
||||
triggers {
|
||||
horizonSecs
|
||||
probability
|
||||
auctionExtensionSecs
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
}
|
||||
}
|
||||
positionDecimalPlaces
|
||||
linearSlippageFactor
|
||||
}
|
||||
|
||||
# I prefix due to clash in lib/proposals
|
||||
fragment INewAssetFields on NewAsset {
|
||||
__typename
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
source {
|
||||
... on BuiltinAsset {
|
||||
maxFaucetAmountMint
|
||||
}
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
withdrawThreshold
|
||||
lifetimeLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# I prefix due to clash in libs/proposals
|
||||
fragment IUpdateAssetFields on UpdateAsset {
|
||||
__typename
|
||||
assetId
|
||||
quantum
|
||||
source {
|
||||
... on UpdateERC20 {
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# I prefix due to clash in libs/proposals
|
||||
fragment IUpdateNetworkParameterFields on UpdateNetworkParameter {
|
||||
__typename
|
||||
networkParameter {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fragment VoteFields on ProposalVotes {
|
||||
yes {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
no {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
}
|
||||
|
||||
fragment ProposalTermsFields on ProposalTerms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
__typename
|
||||
...UpdateMarketStates
|
||||
...UpdateReferralPrograms
|
||||
...UpdateVolumeDiscountPrograms
|
||||
...INewMarketFields
|
||||
...IUpdateMarketFields
|
||||
...INewAssetFields
|
||||
...IUpdateNetworkParameterFields
|
||||
...IUpdateAssetFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment ProposalFields on Proposal {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
reference
|
||||
state
|
||||
datetime
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
errorDetails
|
||||
terms {
|
||||
...ProposalTermsFields
|
||||
}
|
||||
votes {
|
||||
...VoteFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment BatchProposalFields on BatchProposal {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
reference
|
||||
state
|
||||
datetime
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
errorDetails
|
||||
batchTerms {
|
||||
closingDatetime
|
||||
changes {
|
||||
enactmentDatetime
|
||||
}
|
||||
}
|
||||
subProposals {
|
||||
datetime
|
||||
terms {
|
||||
...ProposalTermsFields
|
||||
}
|
||||
}
|
||||
votes {
|
||||
...VoteFields
|
||||
}
|
||||
}
|
||||
|
||||
query Proposals {
|
||||
proposalsConnection {
|
||||
edges {
|
||||
proposalNode {
|
||||
__typename
|
||||
... on Proposal {
|
||||
...ProposalFields
|
||||
}
|
||||
... on BatchProposal {
|
||||
...BatchProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query Proposal($proposalId: ID!) {
|
||||
proposal(id: $proposalId) {
|
||||
... on Proposal {
|
||||
...ProposalFields
|
||||
}
|
||||
... on BatchProposal {
|
||||
...BatchProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+2
-6
@@ -3,13 +3,9 @@ import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
import { type ReactNode } from 'react';
|
||||
import { type ProposalInfoLabelVariant } from '../proposal-info-label';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const CurrentProposalState = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
}) => {
|
||||
export const CurrentProposalState = ({ proposal }: { proposal: Proposal }) => {
|
||||
const { t } = useTranslation();
|
||||
let proposalStatus: ReactNode;
|
||||
let variant = 'tertiary' as ProposalInfoLabelVariant;
|
||||
|
||||
@@ -79,22 +79,13 @@ export const ListAsset = ({
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.asset.source.__typename !== 'ERC20') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.asset.source.__typename !== 'ERC20') return null;
|
||||
if (data.asset.status !== Schema.AssetStatus.STATUS_PENDING_LISTING) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (errorAsset || errorBundle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (errorAsset || errorBundle) return null;
|
||||
const { assetSource, signatures, vegaAssetId, nonce } =
|
||||
assetData.erc20ListAssetBundle;
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<h3 className="mb-2 text-xl">{t('ListAsset')}</h3>
|
||||
|
||||
+6
-40
@@ -2,53 +2,19 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import {
|
||||
AssetDetail,
|
||||
AssetDetailsTable,
|
||||
useAssetQuery,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import {
|
||||
type INewAssetFieldsFragment,
|
||||
type IUpdateAssetFieldsFragment,
|
||||
} from '../../__generated__/Proposals';
|
||||
import { AssetDetail, AssetDetailsTable } from '@vegaprotocol/assets';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
|
||||
export const ProposalAssetDetails = ({
|
||||
change,
|
||||
assetId,
|
||||
asset,
|
||||
originalAsset,
|
||||
}: {
|
||||
change: IUpdateAssetFieldsFragment | INewAssetFieldsFragment;
|
||||
assetId: string;
|
||||
asset: AssetFieldsFragment;
|
||||
originalAsset?: AssetFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
||||
|
||||
const { data } = useAssetQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
assetId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
let asset = removePaginationWrapper(data?.assetsConnection?.edges)[0];
|
||||
|
||||
const originalAsset = asset;
|
||||
|
||||
if (change.__typename === 'UpdateAsset') {
|
||||
asset = {
|
||||
...asset,
|
||||
quantum: change.quantum,
|
||||
source: { ...asset.source },
|
||||
};
|
||||
|
||||
if (asset.source.__typename === 'ERC20') {
|
||||
asset.source.lifetimeLimit = change.source.lifetimeLimit;
|
||||
asset.source.withdrawThreshold = change.source.withdrawThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-asset-details">
|
||||
<CollapsibleToggle
|
||||
|
||||
+19
-35
@@ -6,46 +6,16 @@ import {
|
||||
KeyValueTableRow,
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalChangeTableProps {
|
||||
proposal: Proposal | BatchProposal;
|
||||
proposal: Proposal;
|
||||
}
|
||||
|
||||
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const closingTimeRow =
|
||||
proposal.__typename === 'Proposal' ? (
|
||||
<KeyValueTableRow>
|
||||
{isFuture(new Date(proposal.terms?.closingDatetime))
|
||||
? t('closesOn')
|
||||
: t('closedOn')}
|
||||
{formatDateWithLocalTimezone(new Date(proposal.terms?.closingDatetime))}
|
||||
</KeyValueTableRow>
|
||||
) : proposal.__typename === 'BatchProposal' ? (
|
||||
<KeyValueTableRow>
|
||||
{isFuture(new Date(proposal.batchTerms?.closingDatetime))
|
||||
? t('closesOn')
|
||||
: t('closedOn')}
|
||||
{formatDateWithLocalTimezone(
|
||||
new Date(proposal.batchTerms?.closingDatetime)
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
) : null;
|
||||
|
||||
const enactmentRow =
|
||||
proposal.__typename === 'Proposal' &&
|
||||
proposal.terms.change.__typename !== 'NewFreeform' ? (
|
||||
<KeyValueTableRow>
|
||||
{isFuture(new Date(proposal.terms?.enactmentDatetime || 0))
|
||||
? t('proposedEnactment')
|
||||
: t('enactedOn')}
|
||||
{formatDateWithLocalTimezone(
|
||||
new Date(proposal.terms?.enactmentDatetime || 0)
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
) : null;
|
||||
const terms = proposal?.terms;
|
||||
|
||||
return (
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
@@ -54,8 +24,22 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
|
||||
{t('id')}
|
||||
{proposal?.id}
|
||||
</KeyValueTableRow>
|
||||
{closingTimeRow}
|
||||
{enactmentRow}
|
||||
<KeyValueTableRow>
|
||||
{isFuture(new Date(terms?.closingDatetime))
|
||||
? t('closesOn')
|
||||
: t('closedOn')}
|
||||
{formatDateWithLocalTimezone(new Date(terms?.closingDatetime))}
|
||||
</KeyValueTableRow>
|
||||
{terms?.change.__typename !== 'NewFreeform' ? (
|
||||
<KeyValueTableRow>
|
||||
{isFuture(new Date(terms?.enactmentDatetime || 0))
|
||||
? t('proposedEnactment')
|
||||
: t('enactedOn')}
|
||||
{formatDateWithLocalTimezone(
|
||||
new Date(terms?.enactmentDatetime || 0)
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
) : null}
|
||||
<KeyValueTableRow>
|
||||
{t('proposedBy')}
|
||||
<span style={{ wordBreak: 'break-word' }}>{proposal?.party.id}</span>
|
||||
|
||||
+30
-53
@@ -18,21 +18,20 @@ import {
|
||||
nextWeek,
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
networkParamsQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
import {
|
||||
InstrumentDetailsDocument,
|
||||
useNewTransferProposalDetails,
|
||||
type InstrumentDetailsQuery,
|
||||
type InstrumentDetailsQueryVariables,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||
import { type MockedResponse } from '@apollo/client/testing';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/proposals', () => ({
|
||||
...jest.requireActual('@vegaprotocol/proposals'),
|
||||
useSuccessorMarketProposalDetails: () => ({
|
||||
code: 'PARENT_CODE',
|
||||
parentMarketId: 'PARENT_ID',
|
||||
}),
|
||||
useNewTransferProposalDetails: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -45,7 +44,7 @@ const renderComponent = (
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<BrowserRouter>
|
||||
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
@@ -62,39 +61,10 @@ describe('Proposal header', () => {
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Renders New market proposal', async () => {
|
||||
const parentMarketId = 'parent-id';
|
||||
const parentCode = 'parent-code';
|
||||
const parentName = 'parent-name';
|
||||
const mock: MockedResponse<
|
||||
InstrumentDetailsQuery,
|
||||
InstrumentDetailsQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: InstrumentDetailsDocument,
|
||||
variables: {
|
||||
marketId: parentMarketId,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
code: parentCode,
|
||||
name: parentName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('Renders New market proposal', () => {
|
||||
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New some market',
|
||||
@@ -103,9 +73,6 @@ describe('Proposal header', () => {
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
successorConfiguration: {
|
||||
parentMarketId,
|
||||
},
|
||||
instrument: {
|
||||
__typename: 'InstrumentConfiguration',
|
||||
name: 'Some market',
|
||||
@@ -120,9 +87,7 @@ describe('Proposal header', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
undefined,
|
||||
[mock]
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||
'New some market'
|
||||
@@ -131,13 +96,14 @@ describe('Proposal header', () => {
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'tGBP settled future.'
|
||||
);
|
||||
expect(
|
||||
await screen.findByTestId('proposal-successor-info')
|
||||
).toHaveTextContent(parentCode);
|
||||
expect(screen.getByTestId('proposal-successor-info')).toHaveTextContent(
|
||||
'PARENT_CODE'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders Update market proposal', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New market id',
|
||||
@@ -166,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',
|
||||
@@ -195,10 +162,8 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders New asset proposal - BuiltInAsset', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New asset',
|
||||
},
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewAsset',
|
||||
@@ -212,7 +177,9 @@ describe('Proposal header', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent('New asset');
|
||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||
'New asset proposal'
|
||||
);
|
||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'Symbol: BIA. Max faucet amount mint: 300'
|
||||
@@ -221,6 +188,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders Update network', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'Network parameter',
|
||||
@@ -250,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: {
|
||||
@@ -271,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: {
|
||||
@@ -296,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: {
|
||||
@@ -352,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: {
|
||||
@@ -364,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: {
|
||||
@@ -377,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: {
|
||||
@@ -391,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: {
|
||||
@@ -459,6 +434,8 @@ describe('Proposal header', () => {
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('@vegaprotocol/proposals');
|
||||
|
||||
describe('<NewTransferSummary />', () => {
|
||||
it('renders null if no details are provided', () => {
|
||||
(useNewTransferProposalDetails as jest.Mock).mockReturnValue(null);
|
||||
|
||||
+220
-505
@@ -1,4 +1,4 @@
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Lozenge,
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { shorten } from '@vegaprotocol/utils';
|
||||
import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import { type ReactNode } from 'react';
|
||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||
import { CurrentProposalState } from '../current-proposal-state';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
import {
|
||||
useCancelTransferProposalDetails,
|
||||
useInstrumentDetailsQuery,
|
||||
useNewTransferProposalDetails,
|
||||
useSuccessorMarketProposalDetails,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import {
|
||||
CONSOLE_MARKET_PAGE,
|
||||
@@ -26,510 +27,217 @@ import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { type VoteState } from '../vote-details/use-user-vote';
|
||||
import { VoteBreakdown } from '../vote-breakdown';
|
||||
import {
|
||||
GovernanceTransferKindMapping,
|
||||
type ProposalRejectionReason,
|
||||
ProposalRejectionReasonMapping,
|
||||
ProposalState,
|
||||
} from '@vegaprotocol/types';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type ProposalTermsFieldsFragment } from '../../__generated__/Proposals';
|
||||
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
|
||||
const ProposalTypeTags = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return (
|
||||
<div data-testid="proposal-type">
|
||||
<ProposalTypeTag terms={proposal.terms} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return (
|
||||
<div data-testid="proposal-type" className="flex gap-1">
|
||||
{proposal.subProposals?.map((subProposal, i) => {
|
||||
if (!subProposal?.terms) return null;
|
||||
return <ProposalTypeTag key={i} terms={subProposal.terms} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ProposalTypeTag = ({ terms }: { terms: ProposalTermsFieldsFragment }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
switch (terms.change.__typename) {
|
||||
// Speical case for markets where we want to show the product type in the tag
|
||||
case 'NewMarket': {
|
||||
return (
|
||||
<ProposalInfoLabel variant="secondary">
|
||||
{t(
|
||||
terms.change?.instrument?.product?.__typename
|
||||
? `NewMarket${terms.change.instrument.product.__typename}`
|
||||
: 'NewMarket'
|
||||
)}
|
||||
</ProposalInfoLabel>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<ProposalInfoLabel variant="secondary">
|
||||
{t(terms.change.__typename)}
|
||||
</ProposalInfoLabel>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ProposalDetails = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const featureFlags = useFeatureFlags((store) => store.flags);
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
const renderDetails = (terms: ProposalTermsFieldsFragment) => {
|
||||
switch (terms.change?.__typename) {
|
||||
case 'NewMarket': {
|
||||
const getAsset = (terms: ProposalTermsFieldsFragment) => {
|
||||
if (
|
||||
terms?.change.__typename === 'NewMarket' &&
|
||||
(terms.change.instrument.product?.__typename === 'FutureProduct' ||
|
||||
terms.change.instrument.product?.__typename ===
|
||||
'PerpetualProduct')
|
||||
) {
|
||||
return terms.change.instrument.product.settlementAsset;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{terms.change.successorConfiguration && (
|
||||
<ParentMarketCode
|
||||
parentMarketId={
|
||||
terms.change.successorConfiguration.parentMarketId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{t('Code')}: {terms.change.instrument.code}.
|
||||
</span>{' '}
|
||||
{terms && getAsset(terms)?.symbol ? (
|
||||
<>
|
||||
<span className="font-semibold">{getAsset(terms)?.symbol}</span>{' '}
|
||||
{t('settled future')}.
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
return (
|
||||
<span>
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
terms.change?.market?.id &&
|
||||
terms.change.updateType ? (
|
||||
<>
|
||||
{t(terms.change.updateType)}:{' '}
|
||||
{truncateMiddle(terms.change.market.id)}
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case 'UpdateMarket': {
|
||||
return (
|
||||
<>
|
||||
<span>{t('UpdateToMarket')}:</span>{' '}
|
||||
<span className="inline-flex items-start gap-2">
|
||||
<span className="break-all">{terms.change.marketId} </span>
|
||||
<span className="inline-flex items-end gap-0">
|
||||
<CopyWithTooltip
|
||||
text={terms.change.marketId}
|
||||
description={t('copyToClipboard')}
|
||||
>
|
||||
<button className="inline-block px-1">
|
||||
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<Tooltip description={t('OpenInConsole')} align="center">
|
||||
<button
|
||||
className="inline-block px-1"
|
||||
onClick={() => {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(
|
||||
':marketId',
|
||||
// @ts-ignore ts doesn't like this field even though its already a string above???
|
||||
terms.change.marketId
|
||||
)
|
||||
);
|
||||
window.open(marketPageLink, '_blank');
|
||||
}}
|
||||
>
|
||||
<VegaIcon size={20} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
return null;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
return null;
|
||||
}
|
||||
case 'NewAsset': {
|
||||
return (
|
||||
<>
|
||||
<span>{t('Symbol')}:</span>{' '}
|
||||
<Lozenge>{terms.change.symbol}.</Lozenge>{' '}
|
||||
{terms.change.source.__typename === 'ERC20' && (
|
||||
<>
|
||||
<span>{t('ERC20ContractAddress')}:</span>{' '}
|
||||
<Lozenge>{terms.change.source.contractAddress}</Lozenge>
|
||||
</>
|
||||
)}{' '}
|
||||
{terms.change.source.__typename === 'BuiltinAsset' && (
|
||||
<>
|
||||
<span>{t('MaxFaucetAmountMint')}:</span>{' '}
|
||||
<Lozenge>{terms.change.source.maxFaucetAmountMint}</Lozenge>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'UpdateNetworkParameter': {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="Change <lozenge>{{key}}</lozenge> to <lozenge>{{value}}</lozenge>"
|
||||
values={{
|
||||
key: terms.change.networkParameter.key,
|
||||
value: terms.change.networkParameter.value,
|
||||
}}
|
||||
components={{
|
||||
// @ts-ignore children passed by i18next
|
||||
lozenge: <Lozenge />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'NewFreeform': {
|
||||
return <span />;
|
||||
}
|
||||
case 'UpdateAsset': {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="Asset ID: <lozenge>{{id}}</lozenge>"
|
||||
values={{
|
||||
id: truncateMiddle(terms.change.assetId),
|
||||
}}
|
||||
components={{
|
||||
// @ts-ignore children passed by i18next
|
||||
lozenge: <Lozenge />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'NewTransfer':
|
||||
return featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<NewTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
case 'CancelTransfer':
|
||||
return featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<CancelTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let details = null;
|
||||
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
details = (
|
||||
<div>
|
||||
<div>{renderDetails(proposal.terms)}</div>
|
||||
<VoteStateText
|
||||
state={proposal.state}
|
||||
closingDatetime={proposal.terms.closingDatetime}
|
||||
enactmentDatetime={proposal.terms.enactmentDatetime}
|
||||
rejectionReason={proposal.rejectionReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal' && proposal.subProposals) {
|
||||
details = (
|
||||
<div>
|
||||
<h3 className="text-xl border-b border-default pb-3 mb-3">
|
||||
Proposals in batch
|
||||
</h3>
|
||||
<ul className="flex flex-col gap-2 border-b border-default pb-3 mb-3">
|
||||
{proposal.subProposals.map((p, i) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<li key={i}>
|
||||
<div>{renderDetails(p.terms)}</div>
|
||||
<SubProposalStateText
|
||||
state={proposal.state}
|
||||
enactmentDatetime={p.terms.enactmentDatetime}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<BatchProposalStateText
|
||||
state={proposal.state}
|
||||
closingDatetime={proposal.batchTerms?.closingDatetime}
|
||||
rejectionReason={proposal.rejectionReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="proposal-details"
|
||||
className="break-words mb-6 text-vega-light-200"
|
||||
>
|
||||
{details}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VoteStateText = ({
|
||||
state,
|
||||
closingDatetime,
|
||||
enactmentDatetime,
|
||||
rejectionReason,
|
||||
}: {
|
||||
state: ProposalState;
|
||||
closingDatetime: string;
|
||||
enactmentDatetime: string;
|
||||
rejectionReason: ProposalRejectionReason | null | undefined;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const nowToCloseInHours = differenceInHours(
|
||||
new Date(closingDatetime),
|
||||
new Date()
|
||||
);
|
||||
|
||||
const props = {
|
||||
'data-testid': 'vote-details',
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
case ProposalState.STATE_ENACTED: {
|
||||
return (
|
||||
<p {...props}>
|
||||
{t('enactedOn{{date}}', {
|
||||
enactmentDate:
|
||||
enactmentDatetime &&
|
||||
format(new Date(enactmentDatetime), DATE_FORMAT_DETAILED),
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_PASSED:
|
||||
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||
return (
|
||||
<p {...props}>
|
||||
{t('enactsOn{{date}}', {
|
||||
enactmentDate:
|
||||
enactmentDatetime &&
|
||||
format(new Date(enactmentDatetime), DATE_FORMAT_DETAILED),
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_OPEN: {
|
||||
return (
|
||||
<p {...props}>
|
||||
<span className={nowToCloseInHours < 6 ? 'text-vega-orange' : ''}>
|
||||
{t('{{time}} left to vote', {
|
||||
time: formatDistanceToNowStrict(new Date(closingDatetime)),
|
||||
})}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_DECLINED: {
|
||||
return <p {...props}>{t(state)}</p>;
|
||||
}
|
||||
case ProposalState.STATE_REJECTED: {
|
||||
const props = { 'data-testid': 'vote-status' };
|
||||
|
||||
if (rejectionReason) {
|
||||
return (
|
||||
<p {...props}>{t(ProposalRejectionReasonMapping[rejectionReason])}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <p {...props}>{t('Proposal rejected')}</p>;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders state details relevant to the sub proposal, namely the enactment
|
||||
* date and time
|
||||
*/
|
||||
const SubProposalStateText = ({
|
||||
state,
|
||||
enactmentDatetime,
|
||||
}: {
|
||||
state: ProposalState;
|
||||
enactmentDatetime: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const props = {
|
||||
'data-testid': 'vote-details',
|
||||
className: 'm-0',
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
case ProposalState.STATE_ENACTED: {
|
||||
return (
|
||||
<p {...props}>
|
||||
{t('enactedOn{{date}}', {
|
||||
enactmentDate:
|
||||
enactmentDatetime &&
|
||||
format(new Date(enactmentDatetime), DATE_FORMAT_DETAILED),
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_OPEN:
|
||||
case ProposalState.STATE_PASSED:
|
||||
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||
return (
|
||||
<p {...props}>
|
||||
{t('enactsOn{{date}}', {
|
||||
enactmentDate:
|
||||
enactmentDatetime &&
|
||||
format(new Date(enactmentDatetime), DATE_FORMAT_DETAILED),
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_REJECTED:
|
||||
case ProposalState.STATE_DECLINED: {
|
||||
// If voting is still open we render a single clost time for all sub proposals
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders state details relevant for the entire batch. IE. if the proposal was
|
||||
* rejected or declined, or the vote close time. Does not render enactment times as
|
||||
* those are relevant to the sub proposal
|
||||
*/
|
||||
const BatchProposalStateText = ({
|
||||
state,
|
||||
closingDatetime,
|
||||
rejectionReason,
|
||||
}: {
|
||||
state: ProposalState;
|
||||
closingDatetime: string;
|
||||
rejectionReason: ProposalRejectionReason | null | undefined;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const nowToCloseInHours = differenceInHours(
|
||||
new Date(closingDatetime),
|
||||
new Date()
|
||||
);
|
||||
|
||||
const props = {
|
||||
'data-testid': 'vote-details',
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
case ProposalState.STATE_ENACTED:
|
||||
case ProposalState.STATE_PASSED:
|
||||
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||
return null;
|
||||
}
|
||||
case ProposalState.STATE_OPEN: {
|
||||
return (
|
||||
<p {...props}>
|
||||
<span className={nowToCloseInHours < 6 ? 'text-vega-orange' : ''}>
|
||||
{t('{{time}} left to vote', {
|
||||
time: formatDistanceToNowStrict(new Date(closingDatetime)),
|
||||
})}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case ProposalState.STATE_DECLINED: {
|
||||
return <p {...props}>{t(state)}</p>;
|
||||
}
|
||||
case ProposalState.STATE_REJECTED: {
|
||||
const props = { 'data-testid': 'vote-status' };
|
||||
|
||||
if (rejectionReason) {
|
||||
return (
|
||||
<p {...props}>{t(ProposalRejectionReasonMapping[rejectionReason])}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <p {...props}>{t('Proposal rejected')}</p>;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
import { GovernanceTransferKindMapping } from '@vegaprotocol/types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
proposal: Proposal;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const change = proposal?.terms.change;
|
||||
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
let details: ReactNode;
|
||||
let proposalType = '';
|
||||
let fallbackTitle = '';
|
||||
|
||||
const title = proposal?.rationale.title.trim();
|
||||
|
||||
const fallbackTitle = t(
|
||||
proposal.__typename === 'Proposal'
|
||||
? 'Unknown proposal'
|
||||
: 'Unknown batch proposal'
|
||||
);
|
||||
const titleContent = shorten(title ?? '', 100);
|
||||
|
||||
const getAsset = (proposal: Proposal) => {
|
||||
const terms = proposal?.terms;
|
||||
if (
|
||||
terms?.change.__typename === 'NewMarket' &&
|
||||
(terms.change.instrument.product?.__typename === 'FutureProduct' ||
|
||||
terms.change.instrument.product?.__typename === 'PerpetualProduct')
|
||||
) {
|
||||
return terms.change.instrument.product.settlementAsset;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
switch (change?.__typename) {
|
||||
case 'NewMarket': {
|
||||
proposalType =
|
||||
featureFlags.PRODUCT_PERPETUALS &&
|
||||
change?.instrument?.product?.__typename
|
||||
? `NewMarket${change?.instrument?.product?.__typename}`
|
||||
: 'NewMarket';
|
||||
fallbackTitle = t('NewMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
{featureFlags.SUCCESSOR_MARKETS && (
|
||||
<SuccessorCode proposalId={proposal?.id} />
|
||||
)}
|
||||
<span>
|
||||
{t('Code')}: {change.instrument.code}.
|
||||
</span>{' '}
|
||||
{proposal?.terms && getAsset(proposal)?.symbol ? (
|
||||
<>
|
||||
<span className="font-semibold">
|
||||
{getAsset(proposal)?.symbol}
|
||||
</span>{' '}
|
||||
{t('settled future')}.
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
proposalType =
|
||||
featureFlags.UPDATE_MARKET_STATE && change?.updateType
|
||||
? t(change.updateType)
|
||||
: 'UpdateMarketState';
|
||||
fallbackTitle = t('UpdateMarketStateProposal');
|
||||
details = (
|
||||
<span>
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
change?.market?.id &&
|
||||
change.updateType ? (
|
||||
<>
|
||||
{t(change.updateType)}: {truncateMiddle(change.market.id)}
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateMarket': {
|
||||
proposalType = 'UpdateMarket';
|
||||
fallbackTitle = t('UpdateMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('UpdateToMarket')}:</span>{' '}
|
||||
<span className="inline-flex items-start gap-2">
|
||||
<span className="break-all">{change.marketId} </span>
|
||||
<span className="inline-flex items-end gap-0">
|
||||
<CopyWithTooltip
|
||||
text={change.marketId}
|
||||
description={t('copyToClipboard')}
|
||||
>
|
||||
<button className="inline-block px-1">
|
||||
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<Tooltip description={t('OpenInConsole')} align="center">
|
||||
<button
|
||||
className="inline-block px-1"
|
||||
onClick={() => {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(':marketId', change.marketId)
|
||||
);
|
||||
window.open(marketPageLink, '_blank');
|
||||
}}
|
||||
>
|
||||
<VegaIcon size={20} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
proposalType = 'UpdateReferralProgram';
|
||||
fallbackTitle = t('UpdateReferralProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
proposalType = 'UpdateVolumeDiscountProgram';
|
||||
fallbackTitle = t('UpdateVolumeDiscountProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'NewAsset': {
|
||||
proposalType = 'NewAsset';
|
||||
fallbackTitle = t('NewAssetProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
|
||||
{change.source.__typename === 'ERC20' && (
|
||||
<>
|
||||
<span>{t('ERC20ContractAddress')}:</span>{' '}
|
||||
<Lozenge>{change.source.contractAddress}</Lozenge>
|
||||
</>
|
||||
)}{' '}
|
||||
{change.source.__typename === 'BuiltinAsset' && (
|
||||
<>
|
||||
<span>{t('MaxFaucetAmountMint')}:</span>{' '}
|
||||
<Lozenge>{change.source.maxFaucetAmountMint}</Lozenge>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateNetworkParameter': {
|
||||
proposalType = 'NetworkParameter';
|
||||
fallbackTitle = t('NetworkParameterProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('Change')}:</span>{' '}
|
||||
<Lozenge>{change.networkParameter.key}</Lozenge>{' '}
|
||||
<span>{t('to')}</span>{' '}
|
||||
<span className="whitespace-nowrap">
|
||||
<Lozenge>{change.networkParameter.value}</Lozenge>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'NewFreeform': {
|
||||
proposalType = 'Freeform';
|
||||
fallbackTitle = t('FreeformProposal');
|
||||
details = <span />;
|
||||
break;
|
||||
}
|
||||
case 'UpdateAsset': {
|
||||
proposalType = 'UpdateAsset';
|
||||
fallbackTitle = t('UpdateAssetProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('AssetID')}:</span>{' '}
|
||||
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'NewTransfer':
|
||||
proposalType = 'NewTransfer';
|
||||
fallbackTitle = t('NewTransferProposal');
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<NewTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
proposalType = 'CancelTransfer';
|
||||
fallbackTitle = t('CancelTransferProposal');
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<CancelTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-4 mb-6 text-sm">
|
||||
<ProposalTypeTags proposal={proposal} />
|
||||
<div data-testid="proposal-type">
|
||||
<ProposalInfoLabel variant="secondary">
|
||||
{t(`${proposalType}`)}
|
||||
</ProposalInfoLabel>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
{(voteState === 'Yes' || voteState === 'No') && (
|
||||
@@ -556,43 +264,50 @@ export const ProposalHeader = ({
|
||||
<div data-testid="proposal-title" className="break-all">
|
||||
{isListItem ? (
|
||||
<header>
|
||||
<SubHeading title={titleContent || fallbackTitle} />
|
||||
<SubHeading
|
||||
title={titleContent || fallbackTitle || t('Unknown proposal')}
|
||||
/>
|
||||
</header>
|
||||
) : (
|
||||
<Heading title={titleContent || fallbackTitle} />
|
||||
<Heading
|
||||
title={titleContent || fallbackTitle || t('Unknown proposal')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ProposalDetails proposal={proposal} />
|
||||
|
||||
{details && (
|
||||
<div
|
||||
data-testid="proposal-details"
|
||||
className="break-words mb-6 text-vega-light-200"
|
||||
>
|
||||
{details}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ParentMarketCode = ({
|
||||
parentMarketId,
|
||||
export const SuccessorCode = ({
|
||||
proposalId,
|
||||
}: {
|
||||
parentMarketId: string;
|
||||
proposalId?: string | null;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { data } = useInstrumentDetailsQuery({
|
||||
variables: {
|
||||
marketId: parentMarketId,
|
||||
},
|
||||
});
|
||||
const successor = useSuccessorMarketProposalDetails(proposalId);
|
||||
|
||||
if (!data?.market?.tradableInstrument.instrument.code) return null;
|
||||
|
||||
return (
|
||||
return successor.parentMarketId || successor.code ? (
|
||||
<span className="block" data-testid="proposal-successor-info">
|
||||
{t('Successor market to')}:{' '}
|
||||
<Link
|
||||
to={`${Routes.PROPOSALS}/${parentMarketId}`}
|
||||
to={`${Routes.PROPOSALS}/${successor.parentMarketId}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{data.market.tradableInstrument.instrument.code}
|
||||
{successor.code || successor.parentMarketId}
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const NewTransferSummary = ({
|
||||
|
||||
@@ -3,15 +3,13 @@ import { useTranslation } from 'react-i18next';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import {
|
||||
type BatchProposalFieldsFragment,
|
||||
type ProposalFieldsFragment,
|
||||
} from '../../__generated__/Proposals';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
export const ProposalJson = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | BatchProposalFieldsFragment;
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
+15
-3
@@ -57,21 +57,33 @@ describe('applyImmutableKeysFromEarlierVersion', () => {
|
||||
describe('ProposalMarketChanges', () => {
|
||||
it('renders correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is not visible when showChanges is false', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is visible when showChanges is true', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
||||
expect(getByTestId('json-diff')).toBeInTheDocument();
|
||||
|
||||
+4
-38
@@ -7,8 +7,6 @@ import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { JsonValue } from '../../../../components/json-diff';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { ENV } from '../../../../config';
|
||||
|
||||
const immutableKeys = [
|
||||
'decimalPlaces',
|
||||
@@ -42,51 +40,19 @@ export const applyImmutableKeysFromEarlierVersion = (
|
||||
};
|
||||
|
||||
interface ProposalMarketChangesProps {
|
||||
marketId: string;
|
||||
originalProposal: JsonValue;
|
||||
latestEnactedProposal: JsonValue | undefined;
|
||||
updatedProposal: JsonValue;
|
||||
}
|
||||
|
||||
export const ProposalMarketChanges = ({
|
||||
marketId,
|
||||
originalProposal,
|
||||
latestEnactedProposal,
|
||||
updatedProposal,
|
||||
}: ProposalMarketChangesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showChanges, setShowChanges] = useState(false);
|
||||
|
||||
const {
|
||||
state: { data },
|
||||
} = useFetch(`${ENV.rest}governance?proposalId=${marketId}`, undefined, true);
|
||||
|
||||
const {
|
||||
state: { data: enactedProposalData },
|
||||
} = useFetch(
|
||||
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
// @ts-ignore no types here :-/
|
||||
const enacted = enactedProposalData?.connection?.edges
|
||||
.filter(
|
||||
// @ts-ignore no type here
|
||||
({ node }) => node?.proposal?.terms?.updateMarket?.marketId === marketId
|
||||
)
|
||||
// @ts-ignore no type here
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
|
||||
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
const latestEnactedProposal = enacted?.length
|
||||
? enacted[enacted.length - 1]
|
||||
: undefined;
|
||||
|
||||
const originalProposal =
|
||||
// @ts-ignore no types with useFetch TODO: check this is good
|
||||
data?.data?.proposal?.terms?.newMarket?.changes;
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-market-changes">
|
||||
<CollapsibleToggle
|
||||
|
||||
+9
-21
@@ -18,7 +18,6 @@ import {
|
||||
getDataSourceSpecForTradingTermination,
|
||||
getSigners,
|
||||
MarginScalingFactorsPanel,
|
||||
marketInfoProvider,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Button,
|
||||
@@ -29,8 +28,8 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import type { MarketInfo } from '@vegaprotocol/markets';
|
||||
import { create } from 'zustand';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
type MarketDataDialogState = {
|
||||
isOpen: boolean;
|
||||
@@ -49,29 +48,18 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
|
||||
const marketDataHeaderStyles =
|
||||
'font-alpha calt text-base border-b border-vega-dark-200 mt-2 py-2';
|
||||
|
||||
export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
export const ProposalMarketData = ({
|
||||
marketData,
|
||||
parentMarketData,
|
||||
}: {
|
||||
marketData: MarketInfo;
|
||||
parentMarketData?: MarketInfo;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, open, close } = useMarketDataDialogStore();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
const { data: marketData } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: proposalId,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: parentMarketData } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
skip: !marketData?.parentMarketID,
|
||||
variables: {
|
||||
marketId: marketData?.parentMarketID || '',
|
||||
},
|
||||
});
|
||||
|
||||
if (!marketData || !parentMarketData) {
|
||||
if (!marketData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+66
-43
@@ -7,6 +7,7 @@ import {
|
||||
formatReferralRewardMultiplier,
|
||||
ProposalReferralProgramDetails,
|
||||
} from './proposal-referral-program-details';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
@@ -58,65 +59,87 @@ describe('ProposalReferralProgramDetails helper functions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const mockChange = {
|
||||
__typename: 'UpdateReferralProgram' as const,
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumEpochs: 6,
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
referralDiscountFactor: '0.001',
|
||||
referralRewardFactor: '0.001',
|
||||
const mockReferralProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateReferralProgram',
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumEpochs: 6,
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
referralDiscountFactor: '0.001',
|
||||
referralRewardFactor: '0.001',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 24,
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
referralDiscountFactor: '0.005',
|
||||
referralRewardFactor: '0.005',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 48,
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
],
|
||||
endOfProgram: '2026-10-03T10:34:34Z',
|
||||
windowLength: 3,
|
||||
stakingTiers: [
|
||||
{
|
||||
minimumStakedTokens: '1',
|
||||
referralRewardMultiplier: '1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '2',
|
||||
referralRewardMultiplier: '2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '5',
|
||||
referralRewardMultiplier: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
minimumEpochs: 24,
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
referralDiscountFactor: '0.005',
|
||||
referralRewardFactor: '0.005',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 48,
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
],
|
||||
endOfProgram: '2026-10-03T10:34:34Z',
|
||||
windowLength: 3,
|
||||
stakingTiers: [
|
||||
{
|
||||
minimumStakedTokens: '1',
|
||||
referralRewardMultiplier: '1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '2',
|
||||
referralRewardMultiplier: '2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '5',
|
||||
referralRewardMultiplier: '3',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
describe('<ProposalReferralProgramDetails />', () => {
|
||||
it('should not render if proposal is null', () => {
|
||||
render(<ProposalReferralProgramDetails change={null} />);
|
||||
render(<ProposalReferralProgramDetails proposal={null} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if __typename is not UpdateReferralProgram', () => {
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
render(<ProposalReferralProgramDetails proposal={updateMarketProposal} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are no relevant fields', () => {
|
||||
const emptyChange = {};
|
||||
// @ts-ignore change deliberately empty
|
||||
render(<ProposalReferralProgramDetails change={emptyChange} />);
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {},
|
||||
},
|
||||
});
|
||||
|
||||
render(<ProposalReferralProgramDetails proposal={incompleteProposal} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-referral-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should render relevant fields if present', () => {
|
||||
render(<ProposalReferralProgramDetails change={mockChange} />);
|
||||
render(<ProposalReferralProgramDetails proposal={mockReferralProposal} />);
|
||||
expect(
|
||||
screen.getByTestId('proposal-referral-program-window-length')
|
||||
).toBeInTheDocument();
|
||||
|
||||
+10
-9
@@ -13,10 +13,10 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { type UpdateReferralProgramsFragment } from '../../__generated__/Proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
change: UpdateReferralProgramsFragment | null;
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const formatEndOfProgramTimestamp = (value: string) => {
|
||||
@@ -44,19 +44,20 @@ export const formatReferralRewardMultiplier = (value: string) => {
|
||||
};
|
||||
|
||||
export const ProposalReferralProgramDetails = ({
|
||||
change,
|
||||
proposal,
|
||||
}: ProposalReferralProgramDetailsProps) => {
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
const { t } = useTranslation();
|
||||
if (proposal?.terms?.change?.__typename !== 'UpdateReferralProgram') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (change?.__typename !== 'UpdateReferralProgram') return null;
|
||||
|
||||
const benefitTiers = change?.benefitTiers.slice();
|
||||
const stakingTiers = change?.stakingTiers.slice();
|
||||
const windowLength = change?.windowLength;
|
||||
const endOfProgramTimestamp = change?.endOfProgram;
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers.slice();
|
||||
const stakingTiers = proposal?.terms?.change?.stakingTiers.slice();
|
||||
const windowLength = proposal?.terms?.change?.windowLength;
|
||||
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
||||
|
||||
if (
|
||||
!benefitTiers &&
|
||||
|
||||
+4
-3
@@ -6,14 +6,15 @@ import {
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalCancelTransferDetails = ({
|
||||
proposalId,
|
||||
proposal,
|
||||
}: {
|
||||
proposalId: string;
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const details = useCancelTransferProposalDetails(proposalId);
|
||||
const details = useCancelTransferProposalDetails(proposal?.id);
|
||||
|
||||
if (!details) {
|
||||
return null;
|
||||
|
||||
+4
-3
@@ -19,16 +19,17 @@ import {
|
||||
addDecimalsFormatNumberQuantum,
|
||||
formatDateWithLocalTimezone,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalTransferDetails = ({
|
||||
proposalId,
|
||||
proposal,
|
||||
}: {
|
||||
proposalId: string;
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const details = useNewTransferProposalDetails(proposalId);
|
||||
const details = useNewTransferProposalDetails(proposal?.id);
|
||||
if (!details) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+94
-51
@@ -1,5 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposalUpdateBenefitTiers } from './proposal-update-benefit-tiers-details';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
@@ -9,71 +10,109 @@ jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockChange1 = {
|
||||
__typename: 'UpdateNetworkParameter' as const,
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({
|
||||
tiers: [
|
||||
{
|
||||
minimum_quantum_balance: '10000',
|
||||
reward_multiplier: '0.05',
|
||||
},
|
||||
{
|
||||
minimum_quantum_balance: '500000000000',
|
||||
reward_multiplier: '0.1',
|
||||
},
|
||||
{
|
||||
minimum_quantum_balance: '10000000000000',
|
||||
reward_multiplier: '10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
const mockVestingBenefitTierProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({
|
||||
tiers: [
|
||||
{
|
||||
minimum_quantum_balance: '10000',
|
||||
reward_multiplier: '0.05',
|
||||
},
|
||||
{
|
||||
minimum_quantum_balance: '500000000000',
|
||||
reward_multiplier: '0.1',
|
||||
},
|
||||
{
|
||||
minimum_quantum_balance: '10000000000000',
|
||||
reward_multiplier: '10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const mockChange2 = {
|
||||
__typename: 'UpdateNetworkParameter' as const,
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({
|
||||
tiers: [
|
||||
{
|
||||
minimum_activity_streak: '10000',
|
||||
vesting_multiplier: '5',
|
||||
reward_multiplier: '0.1',
|
||||
},
|
||||
{
|
||||
minimum_activity_streak: '10000000000000',
|
||||
vesting_multiplier: '100',
|
||||
reward_multiplier: '10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
const mockActivityStreakBenefitTierProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({
|
||||
tiers: [
|
||||
{
|
||||
minimum_activity_streak: '10000',
|
||||
vesting_multiplier: '5',
|
||||
reward_multiplier: '0.1',
|
||||
},
|
||||
{
|
||||
minimum_activity_streak: '10000000000000',
|
||||
vesting_multiplier: '100',
|
||||
reward_multiplier: '10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
});
|
||||
describe('ProposalUpdateBenefitTiers', () => {
|
||||
it('should not render if proposal is null', () => {
|
||||
render(<ProposalUpdateBenefitTiers change={null} />);
|
||||
render(<ProposalUpdateBenefitTiers proposal={null} />);
|
||||
expect(screen.queryByTestId('proposal-update-benefit-tiers')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if __typename is not UpdateNetworkParameter', () => {
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
render(<ProposalUpdateBenefitTiers proposal={updateMarketProposal} />);
|
||||
expect(screen.queryByTestId('proposal-update-benefit-tiers')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are no relevant fields', () => {
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<ProposalUpdateBenefitTiers proposal={incompleteProposal} />);
|
||||
expect(screen.queryByTestId('proposal-update-benefit-tiers')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are relevant fields that are empty', () => {
|
||||
const incompleteProposal = {
|
||||
__typename: 'UpdateNetworkParameter' as const,
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({}),
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
networkParameter: {
|
||||
key: 'blah.blah.benefitTiers',
|
||||
value: JSON.stringify({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
render(<ProposalUpdateBenefitTiers change={incompleteProposal} />);
|
||||
render(<ProposalUpdateBenefitTiers proposal={incompleteProposal} />);
|
||||
expect(screen.queryByTestId('proposal-update-benefit-tiers')).toBeNull();
|
||||
});
|
||||
|
||||
it('should render a valid vesting benefit tier proposal', () => {
|
||||
render(<ProposalUpdateBenefitTiers change={mockChange1} />);
|
||||
render(
|
||||
<ProposalUpdateBenefitTiers proposal={mockVestingBenefitTierProposal} />
|
||||
);
|
||||
|
||||
// 3 tiers in the sample data
|
||||
expect(screen.getByText('Tier 1')).toBeInTheDocument();
|
||||
@@ -94,7 +133,11 @@ describe('ProposalUpdateBenefitTiers', () => {
|
||||
});
|
||||
|
||||
it('should render a valid activity streak benefit tier proposal', () => {
|
||||
render(<ProposalUpdateBenefitTiers change={mockChange2} />);
|
||||
render(
|
||||
<ProposalUpdateBenefitTiers
|
||||
proposal={mockActivityStreakBenefitTierProposal}
|
||||
/>
|
||||
);
|
||||
|
||||
// 3 tiers in the sample data
|
||||
expect(screen.getByText('Tier 1')).toBeInTheDocument();
|
||||
|
||||
+6
-6
@@ -11,7 +11,7 @@ import {
|
||||
} from '../proposal-referral-program-details';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { type IUpdateNetworkParameterFieldsFragment } from '../../__generated__/Proposals';
|
||||
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 {
|
||||
change: IUpdateNetworkParameterFieldsFragment | null;
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,17 +55,17 @@ interface ProposalReferralProgramDetailsProps {
|
||||
* It only renders known fields so that they can be formatted correctly.
|
||||
*/
|
||||
export const ProposalUpdateBenefitTiers = ({
|
||||
change,
|
||||
proposal,
|
||||
}: ProposalReferralProgramDetailsProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (
|
||||
change?.__typename !== 'UpdateNetworkParameter' ||
|
||||
change?.networkParameter.key.slice(-13) !== '.benefitTiers'
|
||||
proposal?.terms?.change?.__typename !== 'UpdateNetworkParameter' ||
|
||||
proposal?.terms?.change?.networkParameter.key.slice(-13) !== '.benefitTiers'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiersString = change?.networkParameter.value;
|
||||
const benefitTiersString = proposal?.terms?.change?.networkParameter.value;
|
||||
const benefitTiers = getBenefitTiers(benefitTiersString);
|
||||
|
||||
if (!benefitTiers) {
|
||||
|
||||
+61
-48
@@ -1,73 +1,86 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { ProposalUpdateMarketState } from './proposal-update-market-state';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { MarketUpdateType } from '@vegaprotocol/types';
|
||||
|
||||
describe('<ProposalUpdateMarketState />', () => {
|
||||
const suspendProposal = {
|
||||
__typename: 'UpdateMarketState' as const,
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'suspendProposal Name',
|
||||
code: 'suspendProposal Code',
|
||||
product: {
|
||||
__typename: 'Future' as const,
|
||||
quoteName: 'USD',
|
||||
const suspendProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarketState',
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'suspendProposal Name',
|
||||
code: 'suspendProposal Code',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'USD',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_SUSPEND,
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_SUSPEND,
|
||||
};
|
||||
});
|
||||
|
||||
const resumeProposal = {
|
||||
__typename: 'UpdateMarketState' as const,
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'resumeProposal Name',
|
||||
code: 'resumeProposal Code',
|
||||
product: {
|
||||
__typename: 'Future' as const,
|
||||
quoteName: 'USD',
|
||||
const resumeProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarketState',
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'resumeProposal Name',
|
||||
code: 'resumeProposal Code',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'USD',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_RESUME,
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_RESUME,
|
||||
};
|
||||
});
|
||||
|
||||
const terminateProposal = {
|
||||
__typename: 'UpdateMarketState' as const,
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'terminateProposal Name',
|
||||
code: 'terminateProposal Code',
|
||||
product: {
|
||||
__typename: 'Future' as const,
|
||||
quoteName: 'USD',
|
||||
const terminateProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarketState',
|
||||
market: {
|
||||
id: '1',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'terminateProposal Name',
|
||||
code: 'terminateProposal Code',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'USD',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||
price: '123',
|
||||
},
|
||||
},
|
||||
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||
price: '123',
|
||||
};
|
||||
});
|
||||
|
||||
it('should render nothing if proposal is null', () => {
|
||||
render(<ProposalUpdateMarketState change={null} />);
|
||||
render(<ProposalUpdateMarketState proposal={null} />);
|
||||
expect(screen.queryByTestId('proposal-update-market-state')).toBeNull();
|
||||
});
|
||||
|
||||
it('should toggle details when CollapsibleToggle is clicked', () => {
|
||||
render(<ProposalUpdateMarketState change={suspendProposal} />);
|
||||
render(<ProposalUpdateMarketState proposal={suspendProposal} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('proposal-update-market-state-table')
|
||||
@@ -81,7 +94,7 @@ describe('<ProposalUpdateMarketState />', () => {
|
||||
});
|
||||
|
||||
it('should display suspend market information when showDetails is true', () => {
|
||||
render(<ProposalUpdateMarketState change={suspendProposal} />);
|
||||
render(<ProposalUpdateMarketState proposal={suspendProposal} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
|
||||
|
||||
@@ -90,7 +103,7 @@ describe('<ProposalUpdateMarketState />', () => {
|
||||
});
|
||||
|
||||
it('should display resume market information when showDetails is true', () => {
|
||||
render(<ProposalUpdateMarketState change={resumeProposal} />);
|
||||
render(<ProposalUpdateMarketState proposal={resumeProposal} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
|
||||
|
||||
@@ -99,7 +112,7 @@ describe('<ProposalUpdateMarketState />', () => {
|
||||
});
|
||||
|
||||
it('should display terminate market information when showDetails is true', () => {
|
||||
render(<ProposalUpdateMarketState change={terminateProposal} />);
|
||||
render(<ProposalUpdateMarketState proposal={terminateProposal} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
|
||||
|
||||
|
||||
+12
-10
@@ -8,27 +8,29 @@ import { Row } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type UpdateMarketStatesFragment } from '../../__generated__/Proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalUpdateMarketStateProps {
|
||||
change: UpdateMarketStatesFragment | null;
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const ProposalUpdateMarketState = ({
|
||||
change,
|
||||
proposal,
|
||||
}: ProposalUpdateMarketStateProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
let market;
|
||||
let isTerminate = false;
|
||||
|
||||
if (!change) {
|
||||
if (!proposal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (change.__typename === 'UpdateMarketState') {
|
||||
market = change?.market;
|
||||
isTerminate = change?.updateType === 'MARKET_STATE_UPDATE_TYPE_TERMINATE';
|
||||
if (proposal?.terms.change.__typename === 'UpdateMarketState') {
|
||||
market = proposal?.terms?.change?.market;
|
||||
isTerminate =
|
||||
proposal?.terms?.change?.updateType ===
|
||||
'MARKET_STATE_UPDATE_TYPE_TERMINATE';
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -43,7 +45,7 @@ export const ProposalUpdateMarketState = ({
|
||||
|
||||
{showDetails && (
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
{change.__typename === 'UpdateMarketState' && (
|
||||
{proposal?.terms.change.__typename === 'UpdateMarketState' && (
|
||||
<KeyValueTable data-testid="proposal-update-market-state-table">
|
||||
<KeyValueTableRow>
|
||||
{t('marketId')}
|
||||
@@ -57,10 +59,10 @@ export const ProposalUpdateMarketState = ({
|
||||
{t('marketCode')}
|
||||
{market?.tradableInstrument?.instrument?.code}
|
||||
</KeyValueTableRow>
|
||||
{isTerminate && market && (
|
||||
{isTerminate && (
|
||||
<Row
|
||||
field="termination-price"
|
||||
value={change?.price}
|
||||
value={proposal?.terms?.change?.price}
|
||||
assetSymbol={
|
||||
market?.tradableInstrument?.instrument?.product
|
||||
?.__typename === 'Future' ||
|
||||
|
||||
+80
-40
@@ -1,5 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposalVolumeDiscountProgramDetails } from './proposal-volume-discount-program-details';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
@@ -9,56 +10,95 @@ jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockChange = {
|
||||
__typename: 'UpdateVolumeDiscountProgram' as const,
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
volumeDiscountFactor: '0.05',
|
||||
const mockReferralProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateVolumeDiscountProgram',
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
volumeDiscountFactor: '0.05',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '50000',
|
||||
volumeDiscountFactor: '0.1',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '100000',
|
||||
volumeDiscountFactor: '0.15',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '250000',
|
||||
volumeDiscountFactor: '0.2',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
volumeDiscountFactor: '0.25',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
volumeDiscountFactor: '0.3',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1500000',
|
||||
volumeDiscountFactor: '0.35',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '2000000',
|
||||
volumeDiscountFactor: '0.4',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '1970-01-01T00:00:01.791568493Z',
|
||||
windowLength: 7,
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '50000',
|
||||
volumeDiscountFactor: '0.1',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '100000',
|
||||
volumeDiscountFactor: '0.15',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '250000',
|
||||
volumeDiscountFactor: '0.2',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '500000',
|
||||
volumeDiscountFactor: '0.25',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
volumeDiscountFactor: '0.3',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '1500000',
|
||||
volumeDiscountFactor: '0.35',
|
||||
},
|
||||
{
|
||||
minimumRunningNotionalTakerVolume: '2000000',
|
||||
volumeDiscountFactor: '0.4',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '1970-01-01T00:00:01.791568493Z',
|
||||
windowLength: 7,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
describe('ProposalVolumeDiscountProgramDetails', () => {
|
||||
it('should not render if proposal is null', () => {
|
||||
render(<ProposalVolumeDiscountProgramDetails change={null} />);
|
||||
render(<ProposalVolumeDiscountProgramDetails proposal={null} />);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if __typename is not UpdateVolumeDiscountProgram', () => {
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={updateMarketProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render if there are no relevant fields', () => {
|
||||
const incompleteProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateVolumeDiscountProgram',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={incompleteProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should render relevant fields if present', () => {
|
||||
render(<ProposalVolumeDiscountProgramDetails change={mockChange} />);
|
||||
render(
|
||||
<ProposalVolumeDiscountProgramDetails proposal={mockReferralProposal} />
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('proposal-volume-discount-program-window-length')
|
||||
).toBeInTheDocument();
|
||||
|
||||
+7
-7
@@ -11,10 +11,10 @@ import {
|
||||
} from '../proposal-referral-program-details';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { type UpdateVolumeDiscountProgramsFragment } from '../../__generated__/Proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
change: UpdateVolumeDiscountProgramsFragment | null;
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const formatVolumeDiscountFactor = (value: string) => {
|
||||
@@ -22,16 +22,16 @@ export const formatVolumeDiscountFactor = (value: string) => {
|
||||
};
|
||||
|
||||
export const ProposalVolumeDiscountProgramDetails = ({
|
||||
change,
|
||||
proposal,
|
||||
}: ProposalReferralProgramDetailsProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (change?.__typename !== 'UpdateVolumeDiscountProgram') {
|
||||
if (proposal?.terms?.change?.__typename !== 'UpdateVolumeDiscountProgram') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiers = change.benefitTiers;
|
||||
const windowLength = change?.windowLength;
|
||||
const endOfProgramTimestamp = change?.endOfProgramTimestamp;
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||
const windowLength = proposal?.terms?.change?.windowLength;
|
||||
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgramTimestamp;
|
||||
|
||||
if (!benefitTiers && !windowLength && !endOfProgramTimestamp) {
|
||||
return null;
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { type ProposalTermsFieldsFragment } from '../../__generated__/Proposals';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||
import { ProposalMarketChanges } from '../proposal-market-changes';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
import { ProposalReferralProgramDetails } from '../proposal-referral-program-details';
|
||||
import {
|
||||
ProposalCancelTransferDetails,
|
||||
ProposalTransferDetails,
|
||||
} from '../proposal-transfer';
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
|
||||
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||
|
||||
export const ProposalChangeDetails = ({
|
||||
proposal,
|
||||
terms,
|
||||
restData,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
// eslint-disable-next-line
|
||||
restData: any;
|
||||
}) => {
|
||||
switch (terms.change.__typename) {
|
||||
case 'NewAsset': {
|
||||
if (proposal.id && terms.change.source.__typename === 'ERC20') {
|
||||
return (
|
||||
<div>
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={terms.change.source.withdrawThreshold}
|
||||
lifetimeLimit={terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
<ProposalAssetDetails change={terms.change} assetId={proposal.id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'UpdateAsset': {
|
||||
if (proposal.id) {
|
||||
return (
|
||||
<ProposalAssetDetails
|
||||
change={terms.change}
|
||||
assetId={terms.change.assetId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'NewMarket': {
|
||||
if (proposal.id) {
|
||||
return <ProposalMarketData proposalId={proposal.id} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'UpdateMarket': {
|
||||
if (proposal.id) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ProposalMarketData proposalId={proposal.id} />
|
||||
<ProposalMarketChanges
|
||||
marketId={terms.change.marketId}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
case 'NewTransfer': {
|
||||
if (proposal.id) {
|
||||
return <ProposalTransferDetails proposalId={proposal.id} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'CancelTransfer': {
|
||||
if (proposal.id) {
|
||||
return <ProposalCancelTransferDetails proposalId={proposal.id} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
return <ProposalUpdateMarketState change={terms.change} />;
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
return <ProposalReferralProgramDetails change={terms.change} />;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
return <ProposalVolumeDiscountProgramDetails change={terms.change} />;
|
||||
}
|
||||
case 'UpdateNetworkParameter': {
|
||||
if (
|
||||
terms.change.networkParameter.key === 'rewards.vesting.benefitTiers' ||
|
||||
terms.change.networkParameter.key ===
|
||||
'rewards.activityStreak.benefitTiers'
|
||||
) {
|
||||
return <ProposalUpdateBenefitTiers change={terms.change} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
case 'NewFreeform':
|
||||
case 'NewSpotMarket':
|
||||
case 'UpdateSpotMarket': {
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { mockNetworkParams } from '../../test-helpers/mocks';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
@@ -37,12 +38,6 @@ jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('./proposal-change-details', () => ({
|
||||
ProposalChangeDetails: () => (
|
||||
<div data-testid="proposal-change-details"></div>
|
||||
),
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
@@ -61,7 +56,11 @@ const renderComponent = (proposal: IProposal) => {
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal restData={{}} proposal={proposal} />
|
||||
<Proposal
|
||||
restData={{}}
|
||||
proposal={proposal}
|
||||
networkParams={mockNetworkParams}
|
||||
/>
|
||||
</VegaWalletProvider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
@@ -100,6 +99,27 @@ it('renders each section', async () => {
|
||||
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-change-details')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders whitelist section if proposal is new asset and source is erc20', async () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewAsset',
|
||||
name: 'foo',
|
||||
symbol: 'FOO',
|
||||
decimals: 18,
|
||||
quantum: '1',
|
||||
source: {
|
||||
__typename: 'ERC20',
|
||||
lifetimeLimit: '1',
|
||||
withdrawThreshold: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
renderComponent(proposal);
|
||||
|
||||
expect(screen.getByTestId('proposal-list-asset')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -5,25 +5,152 @@ import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||
import { ProposalReferralProgramDetails } from '../proposal-referral-program-details';
|
||||
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||
import { UserVote } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
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 { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import { type Proposal as IProposal, type BatchProposal } from '../../types';
|
||||
import { ProposalChangeDetails } from './proposal-change-details';
|
||||
import {
|
||||
ProposalCancelTransferDetails,
|
||||
ProposalTransferDetails,
|
||||
} from '../proposal-transfer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
|
||||
export interface ProposalProps {
|
||||
proposal: IProposal | BatchProposal;
|
||||
proposal: IProposal;
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
marketData?: MarketInfo | null;
|
||||
parentMarketData?: MarketInfo | null;
|
||||
assetData?: AssetQuery | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
originalMarketProposalRestData?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mostRecentlyEnactedAssociatedMarketProposal?: any;
|
||||
}
|
||||
|
||||
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
export const Proposal = ({
|
||||
proposal,
|
||||
networkParams,
|
||||
restData,
|
||||
marketData,
|
||||
parentMarketData,
|
||||
assetData,
|
||||
originalMarketProposalRestData,
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
}: ProposalProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||
|
||||
if (!proposal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let asset = assetData
|
||||
? removePaginationWrapper(assetData.assetsConnection?.edges)[0]
|
||||
: undefined;
|
||||
|
||||
const originalAsset = asset;
|
||||
|
||||
if (proposal.terms.change.__typename === 'UpdateAsset' && asset) {
|
||||
asset = {
|
||||
...asset,
|
||||
quantum: proposal.terms.change.quantum,
|
||||
source: { ...asset.source },
|
||||
};
|
||||
|
||||
if (asset.source.__typename === 'ERC20') {
|
||||
asset.source.lifetimeLimit = proposal.terms.change.source.lifetimeLimit;
|
||||
asset.source.withdrawThreshold =
|
||||
proposal.terms.change.source.withdrawThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
let minVoterBalance = null;
|
||||
|
||||
if (networkParams) {
|
||||
switch (proposal.terms.change.__typename) {
|
||||
case 'UpdateMarket':
|
||||
case 'UpdateMarketState':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateMarket_minVoterBalance;
|
||||
break;
|
||||
case 'NewMarket':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_market_minVoterBalance;
|
||||
break;
|
||||
case 'NewAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_asset_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateAsset_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateNetworkParameter':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateNetParam_minVoterBalance;
|
||||
break;
|
||||
case 'NewFreeform':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
break;
|
||||
case 'NewTransfer':
|
||||
// TODO: check minVoterBalance for 'NewTransfer'
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
// TODO: check minVoterBalance for 'CancelTransfer'
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateReferralProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_referralProgram_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateVolumeDiscountProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
||||
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
|
||||
<>
|
||||
{proposal.terms.change.__typename === 'NewTransfer' && (
|
||||
/** Governance New Transfer Details */
|
||||
<div className="mb-4">
|
||||
<ProposalTransferDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'CancelTransfer' && (
|
||||
/** Governance Cancel Transfer Details */
|
||||
<div className="mb-4">
|
||||
<ProposalCancelTransferDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<section data-testid="proposal">
|
||||
<div className="flex items-center gap-1 mb-6">
|
||||
@@ -54,36 +181,91 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
{proposal.__typename === 'Proposal' ? (
|
||||
<ProposalChangeDetails
|
||||
proposal={proposal}
|
||||
terms={proposal.terms}
|
||||
restData={restData}
|
||||
{marketData && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketData
|
||||
marketData={marketData}
|
||||
parentMarketData={parentMarketData ? parentMarketData : undefined}
|
||||
/>
|
||||
) : proposal.__typename === 'BatchProposal' ? (
|
||||
proposal.subProposals?.map((p, i) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<ProposalChangeDetails
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
terms={p.terms}
|
||||
restData={restData}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateMarketState' && (
|
||||
<div className="mb-4">
|
||||
<ProposalUpdateMarketState proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketChanges
|
||||
originalProposal={
|
||||
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||
?.changes || {}
|
||||
}
|
||||
latestEnactedProposal={
|
||||
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal?.terms
|
||||
?.updateMarket?.changes || {}
|
||||
}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} originalAsset={originalAsset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateReferralProgram' && (
|
||||
<div className="mb-4">
|
||||
<ProposalReferralProgramDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateVolumeDiscountProgram' && (
|
||||
<div className="mb-4">
|
||||
<ProposalVolumeDiscountProgramDetails proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateNetworkParameter' &&
|
||||
proposal.terms.change.networkParameter.key.slice(-13) ===
|
||||
'.benefitTiers' && (
|
||||
<div className="mb-4">
|
||||
<ProposalUpdateBenefitTiers proposal={proposal} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{governanceTransferDetails}
|
||||
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<UserVote
|
||||
proposal={proposal}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
networkParams?.spam_protection_voting_min_tokens
|
||||
}
|
||||
submit={submit}
|
||||
dialog={Dialog}
|
||||
transaction={transaction}
|
||||
|
||||
+135
-11
@@ -1,25 +1,149 @@
|
||||
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 { MockedProvider } from '@apollo/client/testing';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { format } from 'date-fns';
|
||||
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import {
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
fiveMinutes,
|
||||
fiveHours,
|
||||
fiveDays,
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const renderComponent = (id: string) =>
|
||||
const renderComponent = (
|
||||
proposal: Proposal,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||
) =>
|
||||
render(
|
||||
<Router>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalsListItemDetails id={id} />
|
||||
</VegaWalletContext.Provider>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalsListItemDetails proposal={proposal} />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Proposals list item details', () => {
|
||||
it('links to single proposal page', () => {
|
||||
const proposalId = 'proposal-id';
|
||||
renderComponent(proposalId);
|
||||
expect(screen.getByRole('link')).toHaveAttribute(
|
||||
'href',
|
||||
expect.stringContaining(proposalId)
|
||||
it('Renders proposal state: Enacted', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_ENACTED,
|
||||
terms: {
|
||||
enactmentDatetime: lastWeek.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
format(lastWeek, DATE_FORMAT_DETAILED)
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Passed', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Waiting for node vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||
terms: {
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - 5 minutes left to vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
closingDatetime: fiveMinutes.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
'5 minutes left to vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - 5 hours left to vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
closingDatetime: fiveHours.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
'5 hours left to vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - 5 days left to vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
closingDatetime: fiveDays.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||
'5 days left to vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Rejected', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_REJECTED,
|
||||
terms: {
|
||||
enactmentDatetime: lastWeek.toString(),
|
||||
},
|
||||
rejectionReason:
|
||||
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Invalid future product'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+89
-4
@@ -1,16 +1,101 @@
|
||||
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 {
|
||||
ProposalRejectionReasonMapping,
|
||||
ProposalState,
|
||||
} from '@vegaprotocol/types';
|
||||
import Routes from '../../../routes';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalsListItemDetails = ({ id }: { id: string }) => {
|
||||
export const ProposalsListItemDetails = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const state = proposal?.state;
|
||||
const nowToEnactmentInHours = differenceInHours(
|
||||
new Date(proposal?.terms.closingDatetime),
|
||||
new Date()
|
||||
);
|
||||
|
||||
let voteDetails: ReactNode;
|
||||
let voteStatus: ReactNode;
|
||||
|
||||
switch (state) {
|
||||
case ProposalState.STATE_ENACTED: {
|
||||
voteDetails =
|
||||
proposal?.terms.enactmentDatetime &&
|
||||
t('enactedOn{{date}}', {
|
||||
enactmentDate:
|
||||
proposal?.terms.enactmentDatetime &&
|
||||
format(
|
||||
new Date(proposal?.terms.enactmentDatetime),
|
||||
DATE_FORMAT_DETAILED
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ProposalState.STATE_PASSED: {
|
||||
voteDetails =
|
||||
proposal?.terms.change.__typename !== 'NewFreeform' &&
|
||||
t('enactsOn{{date}}', {
|
||||
enactmentDate:
|
||||
proposal?.terms.enactmentDatetime &&
|
||||
format(
|
||||
new Date(proposal.terms.enactmentDatetime),
|
||||
DATE_FORMAT_DETAILED
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||
voteDetails =
|
||||
proposal?.terms.change.__typename !== 'NewFreeform' &&
|
||||
t('enactsOn{{date}}', {
|
||||
enactmentDate:
|
||||
proposal?.terms.enactmentDatetime &&
|
||||
format(
|
||||
new Date(proposal.terms.enactmentDatetime),
|
||||
DATE_FORMAT_DETAILED
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ProposalState.STATE_OPEN: {
|
||||
voteDetails = (
|
||||
<span className={nowToEnactmentInHours < 6 ? 'text-vega-orange' : ''}>
|
||||
{formatDistanceToNowStrict(new Date(proposal?.terms.closingDatetime))}{' '}
|
||||
{t('left to vote')}
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ProposalState.STATE_REJECTED: {
|
||||
voteStatus = proposal?.rejectionReason && (
|
||||
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-4 items-start text-sm">
|
||||
<Link to={`${Routes.PROPOSALS}/${id}`}>
|
||||
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 text-vega-light-300 mb-2">
|
||||
{voteDetails && <span data-testid="vote-details">{voteDetails}</span>}
|
||||
{voteDetails && voteStatus && <span>·</span>}
|
||||
{voteStatus && <span data-testid="vote-status">{voteStatus}</span>}
|
||||
</div>
|
||||
|
||||
{proposal?.id && (
|
||||
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
|
||||
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@ import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListItemProps {
|
||||
proposal?: Proposal | BatchProposal;
|
||||
proposal?: Proposal | null;
|
||||
}
|
||||
|
||||
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
|
||||
@@ -16,7 +16,7 @@ export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
|
||||
<li id={proposal.id} data-testid="proposals-list-item">
|
||||
<RoundedWrapper paddingBottom={true} heightFull={true}>
|
||||
<ProposalHeader proposal={proposal} voteState={voteState} />
|
||||
<ProposalsListItemDetails id={proposal.id} />
|
||||
<ProposalsListItemDetails proposal={proposal} />
|
||||
</RoundedWrapper>
|
||||
</li>
|
||||
);
|
||||
|
||||
+12
-14
@@ -3,12 +3,14 @@ import {
|
||||
generateProtocolUpgradeProposal,
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalsList } from './proposals-list';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import {
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
@@ -18,16 +20,6 @@ import {
|
||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
jest.mock('../vote-details/use-user-vote', () => ({
|
||||
useUserVote: jest.fn().mockImplementation(() => ({ voteState: 'NotCast' })),
|
||||
}));
|
||||
|
||||
jest.mock('../proposals-list-item', () => ({
|
||||
ProposalsListItem: ({ proposal }: { proposal: { id: string } }) => (
|
||||
<div data-testid="proposals-list-item" id={proposal.id} />
|
||||
),
|
||||
}));
|
||||
|
||||
const openProposalClosesNextMonth = generateProposal({
|
||||
id: 'proposal1',
|
||||
state: ProposalState.STATE_OPEN,
|
||||
@@ -77,10 +69,12 @@ const renderComponent = (
|
||||
<Router>
|
||||
<MockedProvider mocks={[networkParamsQueryMock]}>
|
||||
<AppStateProvider>
|
||||
<ProposalsList
|
||||
proposals={proposals}
|
||||
protocolUpgradeProposals={protocolUpgradeProposals || []}
|
||||
/>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalsList
|
||||
proposals={proposals}
|
||||
protocolUpgradeProposals={protocolUpgradeProposals || []}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
@@ -94,6 +88,10 @@ afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
jest.mock('../vote-details/use-user-vote', () => ({
|
||||
useUserVote: jest.fn().mockImplementation(() => ({ voteState: 'NotCast' })),
|
||||
}));
|
||||
|
||||
describe('Proposals list', () => {
|
||||
it('Render a page title and link to the make proposal form', async () => {
|
||||
render(renderComponent([]));
|
||||
|
||||
@@ -11,20 +11,19 @@ import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { type ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { type BatchProposal, type Proposal } from '../../types';
|
||||
|
||||
type Proposals = Array<Proposal | BatchProposal>;
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Proposals;
|
||||
proposals: Proposal[];
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
lastBlockHeight?: string;
|
||||
}
|
||||
|
||||
interface SortedProposalsProps {
|
||||
open: Proposals;
|
||||
closed: Proposals;
|
||||
open: Proposal[];
|
||||
closed: Proposal[];
|
||||
}
|
||||
|
||||
interface SortedProtocolUpgradeProposalsProps {
|
||||
@@ -32,26 +31,15 @@ interface SortedProtocolUpgradeProposalsProps {
|
||||
closed: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}
|
||||
|
||||
export const orderByDate = (arr: Proposals) =>
|
||||
export const orderByDate = (arr: Proposal[]) =>
|
||||
orderBy(
|
||||
arr,
|
||||
[
|
||||
(p) => {
|
||||
if (p.__typename === 'BatchProposal') {
|
||||
// Batch proposals can have different enactment dates, this could be improved by ordering
|
||||
// by soonest enactment date in the batch
|
||||
return new Date(p.batchTerms?.closingDatetime || p.datetime);
|
||||
}
|
||||
|
||||
if (p.__typename === 'Proposal') {
|
||||
return p?.terms?.enactmentDatetime
|
||||
? new Date(p?.terms?.enactmentDatetime).getTime()
|
||||
: // has to be defaulted to 0 because new Date(null).getTime() -> NaN which is first when ordered
|
||||
new Date(p?.terms?.closingDatetime || 0).getTime();
|
||||
}
|
||||
|
||||
throw new Error('invalid proposal');
|
||||
},
|
||||
(p) =>
|
||||
p?.terms?.enactmentDatetime
|
||||
? new Date(p?.terms?.enactmentDatetime).getTime()
|
||||
: // has to be defaulted to 0 because new Date(null).getTime() -> NaN which is first when ordered
|
||||
new Date(p?.terms?.closingDatetime || 0).getTime(),
|
||||
(p) => new Date(p?.datetime).getTime(),
|
||||
],
|
||||
['asc', 'asc']
|
||||
@@ -88,40 +76,18 @@ export const ProposalsList = ({
|
||||
|
||||
const sortedProposals: SortedProposalsProps = useMemo(() => {
|
||||
const initialSorting = proposals.reduce(
|
||||
(acc, proposal) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
if (isFuture(new Date(proposal?.terms.closingDatetime))) {
|
||||
acc.open.push(proposal);
|
||||
} else {
|
||||
acc.closed.push(proposal);
|
||||
}
|
||||
return acc;
|
||||
(acc: SortedProposalsProps, proposal) => {
|
||||
if (isFuture(new Date(proposal?.terms.closingDatetime))) {
|
||||
acc.open.push(proposal);
|
||||
} else {
|
||||
acc.closed.push(proposal);
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
if (
|
||||
// this could be improved by sorting by soonest enactment date of all the
|
||||
// sub proposals
|
||||
isFuture(
|
||||
new Date(
|
||||
proposal.batchTerms?.closingDatetime || proposal.datetime
|
||||
)
|
||||
)
|
||||
) {
|
||||
acc.open.push(proposal);
|
||||
} else {
|
||||
acc.closed.push(proposal);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
open: [],
|
||||
closed: [],
|
||||
} as SortedProposalsProps
|
||||
}
|
||||
);
|
||||
return {
|
||||
open:
|
||||
@@ -155,7 +121,7 @@ export const ProposalsList = ({
|
||||
};
|
||||
}, [protocolUpgradeProposals, lastBlockHeight]);
|
||||
|
||||
const filterPredicate = (p: Proposal | BatchProposal) =>
|
||||
const filterPredicate = (p: ProposalFieldsFragment | Proposal) =>
|
||||
p?.id?.includes(filterString) ||
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
|
||||
+46
-9
@@ -1,8 +1,17 @@
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { RejectedProposalsList } from './rejected-proposals-list';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { nextWeek, lastMonth } from '../../test-helpers/mocks';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import {
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
nextWeek,
|
||||
lastMonth,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const rejectedProposalClosesNextWeek = generateProposal({
|
||||
@@ -27,15 +36,31 @@ const rejectedProposalClosedLastMonth = generateProposal({
|
||||
});
|
||||
|
||||
const renderComponent = (proposals: Proposal[]) => (
|
||||
<RejectedProposalsList proposals={proposals} />
|
||||
<Router>
|
||||
<MockedProvider mocks={[networkParamsQueryMock]}>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<RejectedProposalsList proposals={proposals} />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
jest.mock('../proposals-list-item', () => ({
|
||||
ProposalsListItem: () => <div data-testid="proposals-list-item" />,
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
jest.mock('../vote-details/use-user-vote', () => ({
|
||||
useUserVote: jest.fn().mockImplementation(() => ({ voteState: 'NotCast' })),
|
||||
}));
|
||||
|
||||
describe('Rejected proposals list', () => {
|
||||
it('Renders a list of proposals', () => {
|
||||
it('Renders a list of proposals', async () => {
|
||||
render(
|
||||
renderComponent([
|
||||
rejectedProposalClosedLastMonth,
|
||||
@@ -43,13 +68,25 @@ describe('Rejected proposals list', () => {
|
||||
])
|
||||
);
|
||||
|
||||
expect(screen.getAllByTestId('proposals-list-item')).toHaveLength(2);
|
||||
await waitFor(() => {
|
||||
const rejectedProposals = within(
|
||||
screen.getByTestId('rejected-proposals')
|
||||
);
|
||||
const rejectedProposalsItems = rejectedProposals.getAllByTestId(
|
||||
'proposals-list-item'
|
||||
);
|
||||
expect(rejectedProposalsItems).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('Displays text when there are no proposals', async () => {
|
||||
render(renderComponent([]));
|
||||
|
||||
expect(screen.getByTestId('no-rejected-proposals')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('rejected-proposals')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('no-rejected-proposals')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('rejected-proposals')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -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 BatchProposal, type Proposal } from '../../types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<Proposal | BatchProposal>;
|
||||
proposals: Proposal[];
|
||||
}
|
||||
|
||||
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [filterString, setFilterString] = useState('');
|
||||
|
||||
const filterPredicate = (p: Proposal | BatchProposal) =>
|
||||
const filterPredicate = (p: Proposal) =>
|
||||
p?.id?.includes(filterString) ||
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
|
||||
+22
-26
@@ -64,7 +64,7 @@ describe('VoteBreakdown', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('Renders majority reached', async () => {
|
||||
it('Renders majority reached', () => {
|
||||
const yesVotes = 100;
|
||||
const noVotes = 0;
|
||||
|
||||
@@ -82,10 +82,10 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(await screen.findByTestId('token-majority-met')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('token-majority-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders majority not reached', async () => {
|
||||
it('Renders majority not reached', () => {
|
||||
const yesVotes = 20;
|
||||
const noVotes = 80;
|
||||
|
||||
@@ -103,12 +103,10 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(
|
||||
await screen.findByTestId('token-majority-not-met')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('token-majority-not-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation reached', async () => {
|
||||
it('Renders participation reached', () => {
|
||||
const yesVotes = 1000;
|
||||
const noVotes = 0;
|
||||
|
||||
@@ -126,12 +124,10 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(
|
||||
await screen.findByTestId('token-participation-met')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('token-participation-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation not reached', async () => {
|
||||
it('Renders participation not reached', () => {
|
||||
const yesVotes = 0;
|
||||
const noVotes = 0;
|
||||
|
||||
@@ -149,11 +145,11 @@ describe('VoteBreakdown', () => {
|
||||
})
|
||||
);
|
||||
expect(
|
||||
await screen.findByTestId('token-participation-not-met')
|
||||
screen.getByTestId('token-participation-not-met')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', async () => {
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
@@ -174,12 +170,12 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(await screen.findByTestId('vote-status')).toHaveTextContent(
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by liquidity vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', async () => {
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
@@ -200,12 +196,12 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(await screen.findByTestId('vote-status')).toHaveTextContent(
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by token vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to fail', async () => {
|
||||
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
@@ -226,12 +222,12 @@ describe('VoteBreakdown', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(await screen.findByTestId('vote-status')).toHaveTextContent(
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to fail'
|
||||
);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - token majority', async () => {
|
||||
it('Progress bar displays status - token majority', () => {
|
||||
const yesVotes = 80;
|
||||
const noVotes = 20;
|
||||
|
||||
@@ -250,13 +246,13 @@ describe('VoteBreakdown', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const element = await screen.findByTestId('token-majority-progress');
|
||||
const element = screen.getByTestId('token-majority-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
|
||||
expect(style.width).toBe(`${yesVotes}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - token participation', async () => {
|
||||
it('Progress bar displays status - token participation', () => {
|
||||
const yesVotes = 40;
|
||||
const noVotes = 20;
|
||||
const totalVotes = yesVotes + noVotes;
|
||||
@@ -278,13 +274,13 @@ describe('VoteBreakdown', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const element = await screen.findByTestId('token-participation-progress');
|
||||
const element = screen.getByTestId('token-participation-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP majority', async () => {
|
||||
it('Progress bar displays status - LP majority', () => {
|
||||
const yesVotesLP = 0.8;
|
||||
const noVotesLP = 0.2;
|
||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||
@@ -311,12 +307,12 @@ describe('VoteBreakdown', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const element = await screen.findByTestId('lp-majority-progress');
|
||||
const element = screen.getByTestId('lp-majority-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP participation', async () => {
|
||||
it('Progress bar displays status - LP participation', () => {
|
||||
const yesVotesLP = 400;
|
||||
const noVotesLP = 600;
|
||||
const totalVotesLP = yesVotesLP + noVotesLP;
|
||||
@@ -345,7 +341,7 @@ describe('VoteBreakdown', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const element = await screen.findByTestId('lp-participation-progress');
|
||||
const element = screen.getByTestId('lp-participation-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
|
||||
+25
-257
@@ -1,20 +1,13 @@
|
||||
import compact from 'lodash/compact';
|
||||
import countBy from 'lodash/countBy';
|
||||
import { useState, type ReactNode } from 'react';
|
||||
import { type ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { CompactNumber } from '@vegaprotocol/react-helpers';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import {
|
||||
type ProposalTermsFieldsFragment,
|
||||
type VoteFieldsFragment,
|
||||
} from '../../__generated__/Proposals';
|
||||
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
<CompactNumber
|
||||
@@ -25,6 +18,10 @@ export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
/>
|
||||
);
|
||||
|
||||
interface VoteBreakdownProps {
|
||||
proposal: Proposal;
|
||||
}
|
||||
|
||||
interface VoteProgressProps {
|
||||
percentageFor: BigNumber;
|
||||
colourfulBg?: boolean;
|
||||
@@ -58,7 +55,7 @@ const VoteProgress = ({
|
||||
className={progressClasses}
|
||||
style={{ width: `${percentageFor}%` }}
|
||||
data-testid={testId}
|
||||
/>
|
||||
></div>
|
||||
<div className={textClasses}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
@@ -78,14 +75,14 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
<div data-testid={testId}>
|
||||
{reached ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<VegaIcon name={VegaIconNames.TICK} size={20} />
|
||||
<Icon name="tick" size={4} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('met')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={20} />
|
||||
<Icon name="cross" size={4} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('not met')}
|
||||
</span>
|
||||
@@ -95,225 +92,7 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const VoteBreakdown = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return <VoteBreakdownNormal proposal={proposal} />;
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return <VoteBreakdownBatch proposal={proposal} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
const [fullBreakdown, setFullBreakdown] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const voteInfo = useBatchVoteInformation({
|
||||
terms: compact(
|
||||
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
|
||||
),
|
||||
votes: proposal.votes,
|
||||
});
|
||||
|
||||
if (!voteInfo) return null;
|
||||
|
||||
const batchWillPass = voteInfo.every((i) => i.willPass);
|
||||
|
||||
const passingCount = countBy(voteInfo, (v) => v.willPass);
|
||||
|
||||
if (proposal.state === ProposalState.STATE_OPEN) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{batchWillPass ? (
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.TICK}
|
||||
className="text-vega-green"
|
||||
size={20}
|
||||
/>
|
||||
{t(
|
||||
'Currently expected to pass: conditions met for {{count}} of {{total}} proposals',
|
||||
{
|
||||
count: passingCount['true'] || 0,
|
||||
total: voteInfo.length,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
className="text-vega-pink"
|
||||
size={20}
|
||||
/>
|
||||
{t(
|
||||
'Currently expected to fail: {{count}} of {{total}} proposals are passing',
|
||||
{
|
||||
count: passingCount['true'] || 0,
|
||||
total: voteInfo.length,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className="underline"
|
||||
onClick={() => setFullBreakdown((x) => !x)}
|
||||
>
|
||||
{fullBreakdown ? 'Hide vote breakdown' : 'Show vote breakdown'}
|
||||
</button>
|
||||
</div>
|
||||
{fullBreakdown && (
|
||||
<div>
|
||||
{proposal.subProposals?.map((p, i) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<VoteBreakdownBatchSubProposal
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
proposal.state === ProposalState.STATE_DECLINED ||
|
||||
proposal.state === ProposalState.STATE_PASSED
|
||||
) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{batchWillPass ? (
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.TICK}
|
||||
className="text-vega-green"
|
||||
size={20}
|
||||
/>
|
||||
{t(
|
||||
'Proposal passed: conditions met for {{count}} of {{total}} proposals',
|
||||
{
|
||||
count: passingCount['true'] || 0,
|
||||
total: voteInfo.length,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
className="text-vega-pink"
|
||||
size={20}
|
||||
/>
|
||||
{t('Proposal failed: {{count}} of {{total}} proposals passed', {
|
||||
count: passingCount['true'] || 0,
|
||||
total: voteInfo.length,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className="underline"
|
||||
onClick={() => setFullBreakdown((x) => !x)}
|
||||
>
|
||||
{fullBreakdown ? 'Hide vote breakdown' : 'Show vote breakdown'}
|
||||
</button>
|
||||
</div>
|
||||
{fullBreakdown && (
|
||||
<div>
|
||||
{proposal.subProposals?.map((p, i) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<VoteBreakdownBatchSubProposal
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatchSubProposal = ({
|
||||
proposal,
|
||||
votes,
|
||||
terms,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
});
|
||||
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{t(terms.change.__typename)}</h4>
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VoteBreakdownNormal = ({ proposal }: { proposal: Proposal }) => {
|
||||
const voteInfo = useVoteInformation({
|
||||
votes: proposal.votes,
|
||||
terms: proposal.terms,
|
||||
});
|
||||
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = proposal?.terms?.change?.__typename === 'UpdateMarket';
|
||||
|
||||
return (
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const VoteBreakDownUI = ({
|
||||
voteInfo,
|
||||
isProposalOpen,
|
||||
isUpdateMarket,
|
||||
}: {
|
||||
voteInfo: ReturnType<typeof useVoteInformation>;
|
||||
isProposalOpen: boolean;
|
||||
isUpdateMarket: boolean;
|
||||
}) => {
|
||||
const defaultDP = 2;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!voteInfo) return null;
|
||||
|
||||
export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
const {
|
||||
totalTokensPercentage,
|
||||
participationMet,
|
||||
@@ -335,8 +114,12 @@ const VoteBreakDownUI = ({
|
||||
majorityLPMet,
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
} = voteInfo;
|
||||
} = useVoteInformation({ proposal });
|
||||
|
||||
const { t } = useTranslation();
|
||||
const defaultDP = 2;
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = proposal?.terms?.change?.__typename === 'UpdateMarket';
|
||||
const participationThresholdProgress = BigNumber.min(
|
||||
totalTokensPercentage.dividedBy(requiredParticipation).multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
@@ -369,38 +152,23 @@ const VoteBreakDownUI = ({
|
||||
{isProposalOpen && (
|
||||
<div
|
||||
data-testid="vote-status"
|
||||
className="flex items-center gap-2 mb-2 text-bold"
|
||||
className="flex items-center gap-1 mb-2 text-bold"
|
||||
>
|
||||
<span>
|
||||
{willPass ? (
|
||||
<VegaIcon
|
||||
name={VegaIconNames.TICK}
|
||||
size={20}
|
||||
className="text-vega-green"
|
||||
/>
|
||||
<Icon name="tick" size={5} className="text-vega-green" />
|
||||
) : (
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
size={20}
|
||||
className="text-vega-pink"
|
||||
/>
|
||||
<Icon name="cross" size={5} className="text-vega-pink" />
|
||||
)}
|
||||
</span>
|
||||
<span>{t('currentlySetTo')} </span>
|
||||
{willPass ? (
|
||||
<p className="m-0">
|
||||
<Trans
|
||||
i18nKey={'Currently expected to <0>pass</0>'}
|
||||
components={[<span className="text-vega-green" />]}
|
||||
/>
|
||||
<span>
|
||||
<span className="text-vega-green">{t('pass')}</span>
|
||||
{isUpdateMarket && <span> {updateMarketVotePassMethod}</span>}
|
||||
</p>
|
||||
</span>
|
||||
) : (
|
||||
<p className="m-0">
|
||||
<Trans
|
||||
i18nKey={'Currently expected to <0>fail</0>'}
|
||||
components={[<span className="text-vega-pink" />]}
|
||||
/>
|
||||
</p>
|
||||
<span className="text-vega-pink">{t('fail')}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,10 +8,12 @@ import { SubHeading } from '../../../../components/heading';
|
||||
import { type VoteValue } from '@vegaprotocol/types';
|
||||
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type VoteState } from './use-user-vote';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface UserVoteProps {
|
||||
proposal: Proposal | BatchProposal;
|
||||
proposal: Proposal;
|
||||
minVoterBalance: string | null | undefined;
|
||||
spamProtectionMinTokens: string | null | undefined;
|
||||
transaction: VegaTxState | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
@@ -21,6 +23,8 @@ interface UserVoteProps {
|
||||
|
||||
export const UserVote = ({
|
||||
proposal,
|
||||
minVoterBalance,
|
||||
spamProtectionMinTokens,
|
||||
submit,
|
||||
transaction,
|
||||
dialog,
|
||||
@@ -42,17 +46,12 @@ export const UserVote = ({
|
||||
{pubKey ? (
|
||||
proposal && (
|
||||
<VoteButtonsContainer
|
||||
changeType={
|
||||
proposal.__typename === 'BatchProposal'
|
||||
? // @ts-ignore should not be null/undefined
|
||||
proposal.subProposals[0]?.terms?.change.__typename
|
||||
: // @ts-ignore should not be null/undefined
|
||||
proposal.terms?.change.__typename
|
||||
}
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
proposalState={proposal.state}
|
||||
proposalId={proposal.id ?? ''}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={spamProtectionMinTokens}
|
||||
className="flex"
|
||||
submit={submit}
|
||||
transaction={transaction}
|
||||
|
||||
@@ -19,18 +19,14 @@ import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { type ProposalChangeType } from '../../types';
|
||||
|
||||
interface VoteButtonsContainerProps {
|
||||
changeType: ProposalChangeType;
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
minVoterBalance: string | null | undefined;
|
||||
spamProtectionMinTokens: string | null | undefined;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
@@ -39,94 +35,20 @@ interface VoteButtonsContainerProps {
|
||||
|
||||
export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
|
||||
const { data, loading, error } = useVoteButtonsQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const { params: networkParams } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_minVoterBalance,
|
||||
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
|
||||
NetworkParams.governance_proposal_asset_minVoterBalance,
|
||||
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
|
||||
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
|
||||
NetworkParams.governance_proposal_freeform_minVoterBalance,
|
||||
NetworkParams.governance_proposal_referralProgram_minVoterBalance,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance,
|
||||
NetworkParams.governance_proposal_transfer_minVoterBalance,
|
||||
NetworkParams.spam_protection_voting_min_tokens,
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_transfer_requiredMajority,
|
||||
]);
|
||||
|
||||
let minVoterBalance = null;
|
||||
|
||||
if (networkParams) {
|
||||
switch (props.changeType) {
|
||||
case 'UpdateMarket':
|
||||
case 'UpdateMarketState':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateMarket_minVoterBalance;
|
||||
break;
|
||||
case 'NewMarket':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_market_minVoterBalance;
|
||||
break;
|
||||
case 'NewAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_asset_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateAsset_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateNetworkParameter':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateNetParam_minVoterBalance;
|
||||
break;
|
||||
case 'NewFreeform':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
case 'NewTransfer':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_transfer_requiredMajority;
|
||||
break;
|
||||
case 'UpdateReferralProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_referralProgram_minVoterBalance;
|
||||
break;
|
||||
case 'UpdateVolumeDiscountProgram':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||
<VoteButtons
|
||||
{...props}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
networkParams.spam_protection_voting_min_tokens
|
||||
}
|
||||
currentStakeAvailable={toBigNum(
|
||||
data?.party?.stakingSummary.currentStakeAvailable || 0,
|
||||
decimals
|
||||
@@ -136,17 +58,8 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface VoteButtonsProps {
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
interface VoteButtonsProps extends VoteButtonsContainerProps {
|
||||
currentStakeAvailable: BigNumber;
|
||||
minVoterBalance: string | null;
|
||||
spamProtectionMinTokens: string | null;
|
||||
}
|
||||
|
||||
export const VoteButtons = ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { useProposalNetworkParams } from './use-proposal-network-params';
|
||||
import { generateProposal } from '../test-helpers/generate-proposals';
|
||||
|
||||
jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
...jest.requireActual('@vegaprotocol/network-parameters'),
|
||||
@@ -28,84 +29,118 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
|
||||
describe('use-proposal-network-params', () => {
|
||||
it('returns the correct params for an update market proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams());
|
||||
|
||||
const expectedObj = {
|
||||
requiredMajority: expect.any(BigNumber),
|
||||
requiredMajorityLP: expect.any(BigNumber),
|
||||
requiredParticipation: expect.any(BigNumber),
|
||||
requiredParticipationLP: expect.any(BigNumber),
|
||||
};
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current).toEqual({
|
||||
NewMarket: expectedObj,
|
||||
NewSpotMarket: expectedObj,
|
||||
UpdateMarket: expectedObj,
|
||||
UpdateMarketState: expectedObj,
|
||||
UpdateSpotMarket: expectedObj,
|
||||
UpdateNetworkParameter: expectedObj,
|
||||
NewAsset: expectedObj,
|
||||
UpdateAsset: expectedObj,
|
||||
NewFreeform: expectedObj,
|
||||
UpdateReferralProgram: expectedObj,
|
||||
UpdateVolumeDiscountProgram: expectedObj,
|
||||
NewTransfer: expectedObj,
|
||||
CancelTransfer: expectedObj,
|
||||
requiredMajority: '0.1',
|
||||
requiredMajorityLP: '0.2',
|
||||
requiredParticipation: new BigNumber(0.15),
|
||||
requiredParticipationLP: new BigNumber(0.25),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct values for the proposal change type', () => {
|
||||
it('returns the correct params for a market proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams());
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current?.UpdateMarket.requiredMajority.toString()).toEqual('0.1');
|
||||
expect(current?.UpdateMarket.requiredMajorityLP.toString()).toEqual('0.2');
|
||||
expect(current?.UpdateMarket.requiredParticipation.toString()).toEqual(
|
||||
'0.15'
|
||||
);
|
||||
expect(current?.UpdateMarket.requiredParticipationLP.toString()).toEqual(
|
||||
'0.25'
|
||||
);
|
||||
expect(current).toEqual({
|
||||
requiredMajority: '0.3',
|
||||
requiredParticipation: new BigNumber(0.35),
|
||||
});
|
||||
});
|
||||
|
||||
expect(current?.NewMarket.requiredMajority.toString()).toEqual('0.3');
|
||||
expect(current?.NewMarket.requiredMajorityLP.toString()).toEqual('0');
|
||||
expect(current?.NewMarket.requiredParticipation.toString()).toEqual('0.35');
|
||||
expect(current?.NewMarket.requiredParticipationLP.toString()).toEqual('0');
|
||||
it('returns the correct params for an asset proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewAsset',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(current?.NewAsset.requiredMajority.toString()).toEqual('0.4');
|
||||
expect(current?.NewAsset.requiredMajorityLP.toString()).toEqual('0');
|
||||
expect(current?.NewAsset.requiredParticipation.toString()).toEqual('0.45');
|
||||
expect(current?.NewAsset.requiredParticipationLP.toString()).toEqual('0');
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current?.UpdateAsset.requiredMajority.toString()).toEqual('0.5');
|
||||
expect(current?.UpdateAsset.requiredMajorityLP.toString()).toEqual('0');
|
||||
expect(current?.UpdateAsset.requiredParticipation.toString()).toEqual(
|
||||
'0.55'
|
||||
);
|
||||
expect(current).toEqual({
|
||||
requiredMajority: '0.4',
|
||||
requiredParticipation: new BigNumber(0.45),
|
||||
});
|
||||
});
|
||||
|
||||
expect(current?.UpdateNetworkParameter.requiredMajority.toString()).toEqual(
|
||||
'0.6'
|
||||
);
|
||||
expect(
|
||||
current?.UpdateNetworkParameter.requiredMajorityLP.toString()
|
||||
).toEqual('0');
|
||||
expect(
|
||||
current?.UpdateNetworkParameter.requiredParticipation.toString()
|
||||
).toEqual('0.65');
|
||||
expect(
|
||||
current?.UpdateNetworkParameter.requiredParticipationLP.toString()
|
||||
).toEqual('0');
|
||||
it('returns the correct params for an update asset proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateAsset',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(current?.NewFreeform.requiredMajority.toString()).toEqual('0.7');
|
||||
expect(current?.NewFreeform.requiredMajorityLP.toString()).toEqual('0');
|
||||
expect(current?.NewFreeform.requiredParticipation.toString()).toEqual(
|
||||
'0.75'
|
||||
);
|
||||
expect(current?.NewFreeform.requiredParticipationLP.toString()).toEqual(
|
||||
'0'
|
||||
);
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current).toEqual({
|
||||
requiredMajority: '0.5',
|
||||
requiredParticipation: new BigNumber(0.55),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct params for a network params proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current).toEqual({
|
||||
requiredMajority: '0.6',
|
||||
requiredParticipation: new BigNumber(0.65),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct params for a freeform proposal', () => {
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewFreeform',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useProposalNetworkParams({ proposal }));
|
||||
|
||||
expect(current).toEqual({
|
||||
requiredMajority: '0.7',
|
||||
requiredParticipation: new BigNumber(0.75),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,33 +3,35 @@ import {
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { type ProposalChangeType } from '../types';
|
||||
import { type Proposal } from '../types';
|
||||
|
||||
const REQUIRED_PARAMS = [
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredParticipationLP,
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_market_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_asset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredMajority,
|
||||
];
|
||||
|
||||
export const useProposalNetworkParams = () => {
|
||||
const { params } = useNetworkParams(REQUIRED_PARAMS);
|
||||
export const useProposalNetworkParams = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredParticipationLP,
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_market_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_asset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredMajority,
|
||||
]);
|
||||
|
||||
const fallback = {
|
||||
requiredMajority: new BigNumber(1),
|
||||
@@ -39,152 +41,86 @@ export const useProposalNetworkParams = () => {
|
||||
};
|
||||
|
||||
if (!params) {
|
||||
return;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const result: Record<
|
||||
ProposalChangeType,
|
||||
{
|
||||
requiredMajority: BigNumber;
|
||||
requiredParticipation: BigNumber;
|
||||
requiredMajorityLP: BigNumber;
|
||||
requiredParticipationLP: BigNumber;
|
||||
}
|
||||
> = {
|
||||
NewMarket: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_market_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_market_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
NewSpotMarket: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_market_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_market_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
UpdateMarket: {
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajority || 1
|
||||
),
|
||||
requiredMajorityLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajorityLP || 0
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipation || 1
|
||||
),
|
||||
requiredParticipationLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipationLP || 0
|
||||
),
|
||||
},
|
||||
UpdateMarketState: {
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajority || 1
|
||||
),
|
||||
requiredMajorityLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajorityLP || 0
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipation || 1
|
||||
),
|
||||
requiredParticipationLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipationLP || 0
|
||||
),
|
||||
},
|
||||
UpdateSpotMarket: {
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajority || 1
|
||||
),
|
||||
requiredMajorityLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredMajorityLP || 0
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipation || 1
|
||||
),
|
||||
requiredParticipationLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipationLP || 0
|
||||
),
|
||||
},
|
||||
UpdateNetworkParameter: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_updateNetParam_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateNetParam_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
NewAsset: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_asset_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_asset_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
UpdateAsset: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_updateAsset_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateAsset_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
NewFreeform: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_freeform_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_freeform_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
UpdateReferralProgram: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_referralProgram_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_referralProgram_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
UpdateVolumeDiscountProgram: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredParticipation ||
|
||||
1
|
||||
),
|
||||
},
|
||||
NewTransfer: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
CancelTransfer: {
|
||||
...fallback,
|
||||
requiredMajority: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredMajority || 1
|
||||
),
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredParticipation || 1
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
switch (proposal?.terms.change.__typename) {
|
||||
case 'UpdateMarket':
|
||||
case 'UpdateMarketState':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_updateMarket_requiredMajority,
|
||||
requiredMajorityLP:
|
||||
params.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipation
|
||||
),
|
||||
requiredParticipationLP: new BigNumber(
|
||||
params.governance_proposal_updateMarket_requiredParticipationLP
|
||||
),
|
||||
};
|
||||
case 'UpdateNetworkParameter':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_updateNetParam_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateNetParam_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'NewAsset':
|
||||
return {
|
||||
requiredMajority: params.governance_proposal_asset_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_asset_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'UpdateAsset':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_updateAsset_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_updateAsset_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'NewMarket':
|
||||
return {
|
||||
requiredMajority: params.governance_proposal_market_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_market_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'NewFreeform':
|
||||
return {
|
||||
requiredMajority: params.governance_proposal_freeform_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_freeform_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'UpdateReferralProgram':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_referralProgram_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_referralProgram_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'UpdateVolumeDiscountProgram':
|
||||
return {
|
||||
requiredMajority:
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_VolumeDiscountProgram_requiredParticipation
|
||||
),
|
||||
};
|
||||
case 'NewTransfer':
|
||||
case 'CancelTransfer':
|
||||
return {
|
||||
requiredMajority: params.governance_proposal_transfer_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredParticipation
|
||||
),
|
||||
};
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,35 +79,33 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.requiredMajorityLPPercentage).toEqual(new BigNumber(50));
|
||||
expect(current?.requiredMajorityPercentage).toEqual(new BigNumber(50));
|
||||
expect(current?.noTokens).toEqual(new BigNumber(60));
|
||||
expect(current?.noVotes).toEqual(new BigNumber(60));
|
||||
expect(current?.noEquityLikeShareWeight).toEqual(new BigNumber(70));
|
||||
expect(current?.yesTokens).toEqual(new BigNumber(40));
|
||||
expect(current?.yesVotes).toEqual(new BigNumber(40));
|
||||
expect(current?.yesEquityLikeShareWeight).toEqual(new BigNumber(30));
|
||||
expect(current?.totalTokensVoted).toEqual(new BigNumber(100));
|
||||
expect(current?.totalVotes).toEqual(new BigNumber(100));
|
||||
expect(current?.totalEquityLikeShareWeight).toEqual(new BigNumber(100));
|
||||
expect(current?.yesPercentage).toEqual(new BigNumber(40));
|
||||
expect(current?.yesLPPercentage).toEqual(new BigNumber(30));
|
||||
expect(current?.noPercentage).toEqual(new BigNumber(60));
|
||||
expect(current?.noLPPercentage).toEqual(new BigNumber(70));
|
||||
expect(current?.requiredParticipation).toEqual(new BigNumber(50));
|
||||
expect(current?.participationMet).toEqual(true);
|
||||
expect(current?.requiredParticipationLP).toEqual(new BigNumber(50));
|
||||
expect(current?.participationLPMet).toEqual(true);
|
||||
expect(current?.majorityMet).toEqual(false);
|
||||
expect(current?.majorityLPMet).toEqual(false);
|
||||
expect(current?.totalTokensPercentage).toEqual(new BigNumber(100));
|
||||
expect(current?.totalLPTokensPercentage).toEqual(new BigNumber(100));
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(false);
|
||||
expect(current.requiredMajorityPercentage).toEqual(new BigNumber(50));
|
||||
expect(current.requiredMajorityLPPercentage).toEqual(new BigNumber(50));
|
||||
expect(current.noTokens).toEqual(new BigNumber(60));
|
||||
expect(current.noVotes).toEqual(new BigNumber(60));
|
||||
expect(current.noEquityLikeShareWeight).toEqual(new BigNumber(70));
|
||||
expect(current.yesTokens).toEqual(new BigNumber(40));
|
||||
expect(current.yesVotes).toEqual(new BigNumber(40));
|
||||
expect(current.yesEquityLikeShareWeight).toEqual(new BigNumber(30));
|
||||
expect(current.totalTokensVoted).toEqual(new BigNumber(100));
|
||||
expect(current.totalVotes).toEqual(new BigNumber(100));
|
||||
expect(current.totalEquityLikeShareWeight).toEqual(new BigNumber(100));
|
||||
expect(current.yesPercentage).toEqual(new BigNumber(40));
|
||||
expect(current.yesLPPercentage).toEqual(new BigNumber(30));
|
||||
expect(current.noPercentage).toEqual(new BigNumber(60));
|
||||
expect(current.noLPPercentage).toEqual(new BigNumber(70));
|
||||
expect(current.requiredParticipation).toEqual(new BigNumber(50));
|
||||
expect(current.participationMet).toEqual(true);
|
||||
expect(current.requiredParticipationLP).toEqual(new BigNumber(50));
|
||||
expect(current.participationLPMet).toEqual(true);
|
||||
expect(current.majorityMet).toEqual(false);
|
||||
expect(current.majorityLPMet).toEqual(false);
|
||||
expect(current.totalTokensPercentage).toEqual(new BigNumber(100));
|
||||
expect(current.totalLPTokensPercentage).toEqual(new BigNumber(100));
|
||||
expect(current.willPassByTokenVote).toEqual(false);
|
||||
expect(current.willPassByLPVote).toEqual(false);
|
||||
});
|
||||
|
||||
it('correctly returns majority, participation and will-pass status for a proposal with no votes', () => {
|
||||
@@ -125,13 +123,11 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.participationMet).toEqual(false);
|
||||
expect(current?.majorityMet).toEqual(false);
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current.participationMet).toEqual(false);
|
||||
expect(current.majorityMet).toEqual(false);
|
||||
expect(current.willPassByTokenVote).toEqual(false);
|
||||
});
|
||||
|
||||
it('correctly shows lack of participation for a failing proposal lacking votes', () => {
|
||||
@@ -149,11 +145,9 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.participationMet).toEqual(false);
|
||||
expect(current.participationMet).toEqual(false);
|
||||
});
|
||||
|
||||
it('correctly shows participation but lack of majority for a failing proposal with enough votes but not enough majority', () => {
|
||||
@@ -171,13 +165,11 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.participationMet).toEqual(true);
|
||||
expect(current?.majorityMet).toEqual(false);
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current.participationMet).toEqual(true);
|
||||
expect(current.majorityMet).toEqual(false);
|
||||
expect(current.willPassByTokenVote).toEqual(false);
|
||||
});
|
||||
|
||||
it('correctly shows participation, majority and will-pass data for successful proposal', () => {
|
||||
@@ -195,13 +187,11 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.participationMet).toEqual(true);
|
||||
expect(current?.majorityMet).toEqual(true);
|
||||
expect(current?.willPassByTokenVote).toEqual(true);
|
||||
expect(current.participationMet).toEqual(true);
|
||||
expect(current.majorityMet).toEqual(true);
|
||||
expect(current.willPassByTokenVote).toEqual(true);
|
||||
});
|
||||
|
||||
it('correctly shows whether an update market proposal will pass by token or LP vote - both failing', () => {
|
||||
@@ -231,12 +221,10 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(false);
|
||||
expect(current.willPassByTokenVote).toEqual(false);
|
||||
expect(current.willPassByLPVote).toEqual(false);
|
||||
});
|
||||
|
||||
it('correctly shows whether an update market proposal failing token but passing LP voting', () => {
|
||||
@@ -266,11 +254,9 @@ describe('use-vote-information', () => {
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
} = renderHook(() => useVoteInformation({ proposal }));
|
||||
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(true);
|
||||
expect(current.willPassByTokenVote).toEqual(false);
|
||||
expect(current.willPassByLPVote).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,167 +1,25 @@
|
||||
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 { addDecimal } from '@vegaprotocol/utils';
|
||||
import {
|
||||
type ProposalTermsFieldsFragment,
|
||||
type ProposalFieldsFragment,
|
||||
type VoteFieldsFragment,
|
||||
} from '../__generated__/Proposals';
|
||||
import { type ProposalChangeType } from '../types';
|
||||
import { type Proposal } from '../types';
|
||||
|
||||
export const useVoteInformation = ({
|
||||
votes,
|
||||
terms,
|
||||
}: {
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
}) => {
|
||||
export const useVoteInformation = ({ proposal }: { proposal: Proposal }) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
} = useAppState();
|
||||
|
||||
const params = useProposalNetworkParams();
|
||||
|
||||
if (!params) return;
|
||||
|
||||
const paramsForChange = params[terms.change.__typename];
|
||||
|
||||
return getVoteData(
|
||||
terms.change.__typename,
|
||||
paramsForChange,
|
||||
votes,
|
||||
totalSupply,
|
||||
decimals
|
||||
);
|
||||
};
|
||||
|
||||
export const useBatchVoteInformation = ({
|
||||
votes,
|
||||
terms,
|
||||
}: {
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment[];
|
||||
}) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
} = useAppState();
|
||||
|
||||
const params = useProposalNetworkParams();
|
||||
|
||||
if (!params) return;
|
||||
|
||||
return terms.map((t) => {
|
||||
const paramsForChange = params[t.change.__typename];
|
||||
return getVoteData(
|
||||
t.change.__typename,
|
||||
paramsForChange,
|
||||
votes,
|
||||
totalSupply,
|
||||
decimals
|
||||
);
|
||||
requiredMajority,
|
||||
requiredParticipation,
|
||||
requiredMajorityLP,
|
||||
requiredParticipationLP,
|
||||
} = useProposalNetworkParams({
|
||||
proposal,
|
||||
});
|
||||
};
|
||||
|
||||
const getVoteData = (
|
||||
changeType: ProposalChangeType,
|
||||
params: {
|
||||
requiredMajority: BigNumber;
|
||||
requiredMajorityLP: BigNumber;
|
||||
requiredParticipation: BigNumber;
|
||||
requiredParticipationLP: BigNumber;
|
||||
},
|
||||
votes: ProposalFieldsFragment['votes'],
|
||||
totalSupply: BigNumber,
|
||||
decimals: number
|
||||
) => {
|
||||
const requiredMajorityPercentage = params.requiredMajority
|
||||
? new BigNumber(params.requiredMajority).times(100)
|
||||
: new BigNumber(100);
|
||||
|
||||
const requiredMajorityLPPercentage = params.requiredMajorityLP
|
||||
? new BigNumber(params.requiredMajorityLP).times(100)
|
||||
: new BigNumber(100);
|
||||
|
||||
const noTokens = new BigNumber(
|
||||
addDecimal(votes.no.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(votes.yes.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
const totalEquityLikeShareWeight = yesEquityLikeShareWeight.plus(
|
||||
noEquityLikeShareWeight
|
||||
);
|
||||
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
||||
|
||||
const noPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: noTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
|
||||
const noLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||
? new BigNumber(0)
|
||||
: noEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalEquityLikeShareWeight);
|
||||
|
||||
const participationMet = totalTokensVoted.isGreaterThan(
|
||||
totalSupply.multipliedBy(params.requiredParticipation)
|
||||
);
|
||||
|
||||
const participationLPMet = params.requiredParticipationLP
|
||||
? totalEquityLikeShareWeight.isGreaterThan(params.requiredParticipationLP)
|
||||
: false;
|
||||
|
||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||
requiredMajorityPercentage
|
||||
);
|
||||
|
||||
const majorityLPMet = yesLPPercentage.isGreaterThanOrEqualTo(
|
||||
requiredMajorityLPPercentage
|
||||
);
|
||||
|
||||
const totalTokensPercentage = totalTokensVoted
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
||||
|
||||
const willPassByTokenVote =
|
||||
participationMet &&
|
||||
new BigNumber(yesPercentage).isGreaterThanOrEqualTo(
|
||||
requiredMajorityPercentage
|
||||
);
|
||||
|
||||
const willPassByLPVote =
|
||||
participationLPMet &&
|
||||
new BigNumber(yesLPPercentage).isGreaterThanOrEqualTo(
|
||||
requiredMajorityLPPercentage
|
||||
);
|
||||
|
||||
let willPass = false;
|
||||
|
||||
if (changeType === 'UpdateMarket' || changeType === 'UpdateMarketState') {
|
||||
willPass = willPassByTokenVote && willPassByLPVote;
|
||||
} else {
|
||||
willPass = willPassByTokenVote;
|
||||
}
|
||||
|
||||
return {
|
||||
const {
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
noTokens,
|
||||
@@ -182,17 +40,152 @@ const getVoteData = (
|
||||
totalLPTokensPercentage,
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
yesVotes: new BigNumber(votes.yes.totalNumber ?? 0),
|
||||
noVotes: new BigNumber(votes.no.totalNumber ?? 0),
|
||||
totalVotes: new BigNumber(votes.yes.totalNumber ?? 0).plus(
|
||||
votes.no.totalNumber ?? 0
|
||||
} = useMemo(() => {
|
||||
const requiredMajorityPercentage = requiredMajority
|
||||
? new BigNumber(requiredMajority).times(100)
|
||||
: new BigNumber(100);
|
||||
|
||||
const requiredMajorityLPPercentage = requiredMajorityLP
|
||||
? new BigNumber(requiredMajorityLP).times(100)
|
||||
: new BigNumber(100);
|
||||
|
||||
const noTokens = new BigNumber(
|
||||
addDecimal(proposal?.votes.no.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const noEquityLikeShareWeight = !proposal?.votes.no
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(proposal?.votes.yes.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const yesEquityLikeShareWeight = !proposal?.votes.yes
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
const totalEquityLikeShareWeight = yesEquityLikeShareWeight.plus(
|
||||
noEquityLikeShareWeight
|
||||
);
|
||||
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
||||
|
||||
const noPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: noTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
|
||||
const noLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||
? new BigNumber(0)
|
||||
: noEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalEquityLikeShareWeight);
|
||||
|
||||
const participationMet = totalTokensVoted.isGreaterThan(
|
||||
totalSupply.multipliedBy(requiredParticipation)
|
||||
);
|
||||
|
||||
const participationLPMet = requiredParticipationLP
|
||||
? totalEquityLikeShareWeight.isGreaterThan(requiredParticipationLP)
|
||||
: false;
|
||||
|
||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||
requiredMajorityPercentage
|
||||
);
|
||||
|
||||
const majorityLPMet = yesLPPercentage.isGreaterThanOrEqualTo(
|
||||
requiredMajorityLPPercentage
|
||||
);
|
||||
|
||||
const totalTokensPercentage = totalTokensVoted
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
||||
|
||||
const willPassByTokenVote =
|
||||
participationMet &&
|
||||
new BigNumber(yesPercentage).isGreaterThanOrEqualTo(
|
||||
requiredMajorityPercentage
|
||||
);
|
||||
|
||||
const willPassByLPVote =
|
||||
participationLPMet &&
|
||||
new BigNumber(yesLPPercentage).isGreaterThanOrEqualTo(
|
||||
requiredMajorityLPPercentage
|
||||
);
|
||||
|
||||
return {
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
noTokens,
|
||||
noEquityLikeShareWeight,
|
||||
yesTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
totalTokensVoted,
|
||||
totalEquityLikeShareWeight,
|
||||
yesPercentage,
|
||||
yesLPPercentage,
|
||||
noPercentage,
|
||||
noLPPercentage,
|
||||
participationMet,
|
||||
participationLPMet,
|
||||
majorityMet,
|
||||
majorityLPMet,
|
||||
totalTokensPercentage,
|
||||
totalLPTokensPercentage,
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
};
|
||||
}, [
|
||||
decimals,
|
||||
proposal?.votes.no.totalEquityLikeShareWeight,
|
||||
proposal?.votes.no.totalTokens,
|
||||
proposal?.votes.yes.totalEquityLikeShareWeight,
|
||||
proposal?.votes.yes.totalTokens,
|
||||
requiredMajority,
|
||||
requiredMajorityLP,
|
||||
requiredParticipation,
|
||||
requiredParticipationLP,
|
||||
totalSupply,
|
||||
]);
|
||||
|
||||
return {
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
totalTokensPercentage,
|
||||
totalLPTokensPercentage,
|
||||
participationMet,
|
||||
participationLPMet,
|
||||
totalTokensVoted,
|
||||
totalEquityLikeShareWeight,
|
||||
noPercentage,
|
||||
noLPPercentage,
|
||||
yesPercentage,
|
||||
yesLPPercentage,
|
||||
noTokens,
|
||||
noEquityLikeShareWeight,
|
||||
yesTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
yesVotes: new BigNumber(proposal?.votes.yes.totalNumber ?? 0),
|
||||
noVotes: new BigNumber(proposal?.votes.no.totalNumber ?? 0),
|
||||
totalVotes: new BigNumber(proposal?.votes.yes.totalNumber ?? 0).plus(
|
||||
proposal?.votes.no.totalNumber ?? 0
|
||||
),
|
||||
requiredParticipation: new BigNumber(params.requiredParticipation).times(
|
||||
100
|
||||
),
|
||||
requiredParticipationLP: new BigNumber(
|
||||
params.requiredParticipationLP
|
||||
).times(100),
|
||||
willPass,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
requiredParticipation: new BigNumber(requiredParticipation).times(100),
|
||||
requiredParticipationLP:
|
||||
requiredParticipationLP &&
|
||||
new BigNumber(requiredParticipationLP).times(100),
|
||||
majorityMet,
|
||||
majorityLPMet,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
fragment NewMarketProductField on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on NewMarket {
|
||||
instrument {
|
||||
product {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateMarketState on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateMarketState {
|
||||
updateType
|
||||
market {
|
||||
decimalPlaces
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
__typename
|
||||
... on Future {
|
||||
quoteName
|
||||
}
|
||||
... on Perpetual {
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
updateType
|
||||
price
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateReferralProgram on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateReferralProgram {
|
||||
benefitTiers {
|
||||
minimumEpochs
|
||||
minimumRunningNotionalTakerVolume
|
||||
referralDiscountFactor
|
||||
referralRewardFactor
|
||||
}
|
||||
endOfProgram: endOfProgramTimestamp
|
||||
windowLength
|
||||
stakingTiers {
|
||||
minimumStakedTokens
|
||||
referralRewardMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateVolumeDiscountProgram on Proposal {
|
||||
terms {
|
||||
change {
|
||||
... on UpdateVolumeDiscountProgram {
|
||||
benefitTiers {
|
||||
minimumRunningNotionalTakerVolume
|
||||
volumeDiscountFactor
|
||||
}
|
||||
endOfProgramTimestamp
|
||||
windowLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query Proposal(
|
||||
$proposalId: ID!
|
||||
$includeNewMarketProductField: Boolean!
|
||||
$includeUpdateMarketState: Boolean!
|
||||
$includeUpdateReferralProgram: Boolean!
|
||||
) {
|
||||
proposal(id: $proposalId) {
|
||||
... on Proposal {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
reference
|
||||
state
|
||||
datetime
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
errorDetails
|
||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||
...UpdateVolumeDiscountProgram
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
... on NewMarket {
|
||||
decimalPlaces
|
||||
metadata
|
||||
riskParameters {
|
||||
... on LogNormalRiskModel {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
mu
|
||||
r
|
||||
sigma
|
||||
}
|
||||
}
|
||||
... on SimpleRiskModel {
|
||||
params {
|
||||
factorLong
|
||||
factorShort
|
||||
}
|
||||
}
|
||||
}
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
product {
|
||||
... on FutureProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
tradingTerminationProperty
|
||||
}
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on PerpetualProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
priceMonitoringParameters {
|
||||
triggers {
|
||||
horizonSecs
|
||||
probability
|
||||
auctionExtensionSecs
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
}
|
||||
}
|
||||
positionDecimalPlaces
|
||||
linearSlippageFactor
|
||||
}
|
||||
... on UpdateMarket {
|
||||
marketId
|
||||
updateMarketConfiguration {
|
||||
instrument {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 UpdateNetworkParameter {
|
||||
networkParameter {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
... on UpdateAsset {
|
||||
quantum
|
||||
assetId
|
||||
source {
|
||||
... on UpdateERC20 {
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
votes {
|
||||
yes {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
no {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user