Compare commits
157
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db6a1f077b | ||
|
|
5389a429ab | ||
|
|
c1d51b84d6 | ||
|
|
153367b1f1 | ||
|
|
7833a6a5e7 | ||
|
|
5c5e30f7b8 | ||
|
|
44d842ff87 | ||
|
|
6cf9fcd11d | ||
|
|
fa7903580f | ||
|
|
6befe5d96f | ||
|
|
de86d1927d | ||
|
|
e34973cdee | ||
|
|
18dac4ea5e | ||
|
|
6aea10c27b | ||
|
|
27a9d5f247 | ||
|
|
261f32aa5b | ||
|
|
053775bef6 | ||
|
|
0660eda334 | ||
|
|
f22a3bc2d2 | ||
|
|
fde77ebccb | ||
|
|
e309669736 | ||
|
|
b1621d1191 | ||
|
|
39907f07db | ||
|
|
3dab5f3d9b | ||
|
|
3cf9ae7582 | ||
|
|
557894e2ef | ||
|
|
baf9875c69 | ||
|
|
51199b02ce | ||
|
|
bb826c88f0 | ||
|
|
0da20b750f | ||
|
|
e16c447564 | ||
|
|
a2170d27d6 | ||
|
|
00d840b7b3 | ||
|
|
2a4a05630f | ||
|
|
97f1f40f2c | ||
|
|
a8e6963521 | ||
|
|
a2bffa1dfd | ||
|
|
9253e8067a | ||
|
|
58972f0a11 | ||
|
|
a3c55fd7c3 | ||
|
|
c63cba1071 | ||
|
|
bc9d87fe30 | ||
|
|
6a21862378 | ||
|
|
0fb4348e9c | ||
|
|
b751bcf17d | ||
|
|
d32f27fcb1 | ||
|
|
d05cd6a2ed | ||
|
|
c003e5fa30 | ||
|
|
f62d3289ab | ||
|
|
a3d3d18c5c | ||
|
|
77ca101781 | ||
|
|
d238c37d0c | ||
|
|
79feb485f6 | ||
|
|
6aa5c3b6e3 | ||
|
|
82abc13fda | ||
|
|
933f07cddf | ||
|
|
c8067669c2 | ||
|
|
78f5a9c520 | ||
|
|
462066959d | ||
|
|
f12f5ab961 | ||
|
|
98b2460d8b | ||
|
|
67c182f3de | ||
|
|
68e0009ab0 | ||
|
|
ad74b12908 | ||
|
|
7fff33e493 | ||
|
|
e7cb2c8276 | ||
|
|
4adaeea40a | ||
|
|
d47d894147 | ||
|
|
515932d401 | ||
|
|
723ff2805d | ||
|
|
424cced4be | ||
|
|
b2c7ee82c5 | ||
|
|
4cc63be4f9 | ||
|
|
9fbb7a13e6 | ||
|
|
1874095222 | ||
|
|
e459e92127 | ||
|
|
4dd65b4923 | ||
|
|
5d792d2458 | ||
|
|
59b2f75b13 | ||
|
|
95775679ca | ||
|
|
4cffee29f8 | ||
|
|
bd70b0c233 | ||
|
|
145792f216 | ||
|
|
10add7c236 | ||
|
|
362a2031c7 | ||
|
|
5aaeb87059 | ||
|
|
d240dddab5 | ||
|
|
0af1d0ba82 | ||
|
|
665ab6693a | ||
|
|
902ee2696c | ||
|
|
3beeb0140c | ||
|
|
4b208e90bf | ||
|
|
ec837854cf | ||
|
|
8b94a75ba4 | ||
|
|
08c57b6759 | ||
|
|
bb2184498f | ||
|
|
8b07ce3024 | ||
|
|
8a110584dd | ||
|
|
b82615a3d8 | ||
|
|
f178b85846 | ||
|
|
0796f2b31f | ||
|
|
67be224138 | ||
|
|
51c426ef4b | ||
|
|
fa28d31ef3 | ||
|
|
201a586b05 | ||
|
|
7d96d9bcd1 | ||
|
|
345be81142 | ||
|
|
2221e9faca | ||
|
|
7c0139e107 | ||
|
|
a157ff1e05 | ||
|
|
2e9fa0c52e | ||
|
|
c0790c1e93 | ||
|
|
13c1044f8e | ||
|
|
cf9f313e4c | ||
|
|
f56d34fe6e | ||
|
|
1379366caa | ||
|
|
8958c12fe3 | ||
|
|
fef13874db | ||
|
|
40f02ecf89 | ||
|
|
41cd6b1455 | ||
|
|
3cd393dac0 | ||
|
|
a52e60d6a2 | ||
|
|
bc13f1b359 | ||
|
|
eb81f4ae44 | ||
|
|
51ab02a2e2 | ||
|
|
ffada1b93d | ||
|
|
9dda3f712b | ||
|
|
df20dbeee0 | ||
|
|
cdfd8a2d00 | ||
|
|
1e5c523bc4 | ||
|
|
37cd69ba6e | ||
|
|
2c11045dd9 | ||
|
|
9aef41a119 | ||
|
|
80ab8821d0 | ||
|
|
8a3657a9b9 | ||
|
|
7100b0e9fc | ||
|
|
614a83b7d6 | ||
|
|
3dc77b0eff | ||
|
|
a59f7dfd29 | ||
|
|
61471228aa | ||
|
|
127e784ceb | ||
|
|
70d748fb15 | ||
|
|
e06f4818fc | ||
|
|
8182da3b31 | ||
|
|
c8c56307bb | ||
|
|
a8cd7f157f | ||
|
|
4f18caa486 | ||
|
|
5c7c626bbc | ||
|
|
e4c4c20631 | ||
|
|
0697302d07 | ||
|
|
2d926c0ce0 | ||
|
|
f57d6a7c7b | ||
|
|
15f905046f | ||
|
|
4f7918f64e | ||
|
|
52ab0562b0 | ||
|
|
4e2b0d1b1d | ||
|
|
eac26c1966 |
@@ -77,6 +77,7 @@
|
||||
"fixStyle": "inline-type-imports"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-useless-constructor": 0,
|
||||
"curly": ["error", "multi-line"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
* text eol=lf
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.woff2 binary
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
create-docker-image:
|
||||
name: Create docker image for console-test
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# check-out frontend-monorepo
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
name: run-tests
|
||||
runs-on: 8-cores
|
||||
needs: [create-docker-image, console-test-branch]
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# load docker image
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
# run tests
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
|
||||
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
|
||||
@@ -227,3 +227,46 @@ jobs:
|
||||
name: worker-logs
|
||||
path: ./logs/
|
||||
retention-days: 15
|
||||
#----------------------------------------------
|
||||
# ----- upload market-sim logs -----
|
||||
#----------------------------------------------
|
||||
- name: Prepare and Zip market-sim-logs
|
||||
if: always()
|
||||
run: |
|
||||
parent_dir="/tmp/market-sim-logs"
|
||||
echo "Creating parent directory at $parent_dir"
|
||||
mkdir -p "$parent_dir"
|
||||
|
||||
echo "Waiting for vega-sim-* folders to be created..."
|
||||
sleep 10 # Waits 10 seconds to ensure all folders are created
|
||||
|
||||
echo "Before searching for vega-sim-* folders in /tmp..."
|
||||
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
|
||||
|
||||
echo "After searching for vega-sim-* folders in /tmp..."
|
||||
|
||||
if [ -z "$folders" ]; then
|
||||
echo "No vega-sim-* folders found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Moving vega-sim-* folders to $parent_dir"
|
||||
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
|
||||
|
||||
echo "Checking if $parent_dir is not empty..."
|
||||
if [ "$(ls -A $parent_dir)" ]; then
|
||||
echo "Zipping the parent directory..."
|
||||
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
|
||||
else
|
||||
echo "$parent_dir is empty. No zip file created."
|
||||
exit 0
|
||||
fi
|
||||
shell: /usr/bin/bash -e {0}
|
||||
|
||||
- name: Upload market-sim-logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: market-sim-logs
|
||||
path: market-sim-logs.zip
|
||||
retention-days: 15
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Auto-format all files
|
||||
yarn nx format:write
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
# Lint all staged files - this brings more value as pre-commit
|
||||
# yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
# yarn nx affected -t test --exclude trading
|
||||
|
||||
@@ -44,7 +44,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.getByTestId('icon-cross').click();
|
||||
});
|
||||
|
||||
it.skip('Proposal page displayed on mobile', function () {
|
||||
it('Proposal page displayed on mobile', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
@@ -55,7 +55,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view new asset proposal', function () {
|
||||
it.skip('Able to view new asset proposal', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
const newAssetProposalBody = getNewAssetTxBody();
|
||||
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
||||
|
||||
@@ -12,7 +12,8 @@ import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-web
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from './routes/router-config';
|
||||
import { useRouterConfig } from './routes/router-config';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@@ -38,7 +39,10 @@ function App() {
|
||||
}
|
||||
>
|
||||
<Suspense fallback={splashLoading}>
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
<RouterProvider
|
||||
router={createBrowserRouter(useRouterConfig())}
|
||||
fallbackElement={splashLoading}
|
||||
/>
|
||||
</Suspense>
|
||||
</NodeGuard>
|
||||
<NodeSwitcherDialog
|
||||
|
||||
@@ -12,7 +12,8 @@ import { type VegaICellRendererParams } from '@vegaprotocol/datagrid';
|
||||
import { useRef, useLayoutEffect } from 'react';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { type RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
import { type ColDef } from 'ag-grid-community';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
|
||||
type AssetsTableProps = {
|
||||
data: AssetFieldsFragment[] | null;
|
||||
@@ -23,7 +24,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef<AgGridReact>(null);
|
||||
const showColumnsOnDesktop = () => {
|
||||
ref.current?.columnApi.setColumnsVisible(
|
||||
ref.current?.api.setColumnsVisible(
|
||||
['id', 'type', 'status'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import { isNavigable } from '../../routes/router-config';
|
||||
import { routerConfig } from '../../routes/router-config';
|
||||
import { useRouterConfig } from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
@@ -26,6 +26,7 @@ const routeToNavigationItem = (r: Navigable) => (
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const routerConfig = useRouterConfig();
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const pages = routerConfig[0].children || [];
|
||||
const mainItems = compact(
|
||||
|
||||
@@ -60,7 +60,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="whitespace-nowrap">
|
||||
<span>
|
||||
{useName && <Icon size={4} name="cube" className="mr-2" />}
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
query ExplorerProposal($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
... on Proposal {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -8,16 +8,18 @@ export type ExplorerProposalQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
|
||||
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalDocument = gql`
|
||||
query ExplorerProposal($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
... on Proposal {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import ProposalLink from './proposal-link';
|
||||
import { ExplorerProposalDocument } from './__generated__/Proposal';
|
||||
import {
|
||||
ExplorerProposalDocument,
|
||||
type ExplorerProposalQuery,
|
||||
type ExplorerProposalQueryVariables,
|
||||
} from './__generated__/Proposal';
|
||||
import { GraphQLError } from 'graphql';
|
||||
|
||||
function renderComponent(id: string, mocks: MockedResponse[]) {
|
||||
@@ -23,7 +27,10 @@ describe('Proposal link component', () => {
|
||||
});
|
||||
|
||||
it('Renders the ID on error', async () => {
|
||||
const mock = {
|
||||
const mock: MockedResponse<
|
||||
ExplorerProposalQuery,
|
||||
ExplorerProposalQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
@@ -40,17 +47,22 @@ describe('Proposal link component', () => {
|
||||
});
|
||||
|
||||
it('Renders the proposal title when the query returns a result', async () => {
|
||||
const mock = {
|
||||
const proposalId = '123';
|
||||
const mock: MockedResponse<
|
||||
ExplorerProposalQuery,
|
||||
ExplorerProposalQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
id: proposalId,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposal: {
|
||||
id: '123',
|
||||
__typename: 'Proposal',
|
||||
id: proposalId,
|
||||
rationale: {
|
||||
title: 'test-title',
|
||||
description: 'test description',
|
||||
@@ -60,13 +72,16 @@ describe('Proposal link component', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
const res = render(renderComponent(proposalId, [mock]));
|
||||
expect(res.getByText(proposalId)).toBeInTheDocument();
|
||||
expect(await res.findByText('test-title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leaves the proposal id when the market is not found', async () => {
|
||||
const mock = {
|
||||
const mock: MockedResponse<
|
||||
ExplorerProposalQuery,
|
||||
ExplorerProposalQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useExplorerProposalQuery } from './__generated__/Proposal';
|
||||
import {
|
||||
useExplorerProposalQuery,
|
||||
type ExplorerProposalQuery,
|
||||
} from './__generated__/Proposal';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ENV } from '../../../config/env';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
text?: string;
|
||||
@@ -16,8 +20,13 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
const proposal = data?.proposal as Extract<
|
||||
ExplorerProposalQuery['proposal'],
|
||||
{ __typename?: 'Proposal' }
|
||||
>;
|
||||
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
const label = data?.proposal?.rationale.title || id;
|
||||
const label = proposal?.rationale.title || id;
|
||||
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
['id', 'state', 'asset'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
BORDER_COLOURS,
|
||||
NestedDataList,
|
||||
sortNestedDataByChildren,
|
||||
} from './nested-data-list';
|
||||
import { NestedDataList, sortNestedDataByChildren } from './nested-data-list';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const mockData = {
|
||||
@@ -61,38 +57,6 @@ describe('NestedDataList', () => {
|
||||
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
|
||||
});
|
||||
|
||||
it('should repeat the border colours in the correct order', () => {
|
||||
const colourMockData = {
|
||||
t0: {
|
||||
t1: {
|
||||
t2: {
|
||||
t3: {
|
||||
t4: {
|
||||
t5: {
|
||||
t6: {
|
||||
t7: {
|
||||
t8: {
|
||||
hello: 'world',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const tree = render(<NestedDataList data={colourMockData} />);
|
||||
const { getByTestId } = tree;
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const item = getByTestId(`T${i}`);
|
||||
const expected = BORDER_COLOURS.light[i % 5];
|
||||
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should sort the data by values with children', () => {
|
||||
const mockData = {
|
||||
nonce: '5980890939790185837',
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Time } from '../time';
|
||||
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
||||
import SizeInMarket from '../size-in-market/size-in-market';
|
||||
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
|
||||
import { OrderTypeMapping } from '@vegaprotocol/types';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
@@ -59,7 +60,7 @@ const DeterministicOrderDetails = ({
|
||||
const o = data.orderByID;
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="mb-0">
|
||||
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
|
||||
@@ -69,7 +70,7 @@ const DeterministicOrderDetails = ({
|
||||
<span className="mx-5 text-base">@</span>
|
||||
<PriceInMarket price={o.price} marketId={o.market.id} />
|
||||
</h2>
|
||||
<p className="text-gray-200">
|
||||
<p className="text-gray-400 dark:text-gray-600">
|
||||
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
||||
</p>
|
||||
{o.peggedOrder ? (
|
||||
@@ -83,15 +84,14 @@ const DeterministicOrderDetails = ({
|
||||
/>
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{o.reference ? (
|
||||
<p className="text-gray-500 mt-4">
|
||||
<span>{t('Reference')}</span>: {o.reference}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid md:grid-cols-4 gap-x-6 mt-4">
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Status')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||
@@ -99,21 +99,33 @@ const DeterministicOrderDetails = ({
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Size')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<SizeInMarket size={o.size} marketId={o.market.id} />
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Version')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{o.version}
|
||||
</h5>
|
||||
</div>
|
||||
{o.type ? (
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Type')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{OrderTypeMapping[o.type]}
|
||||
</h5>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
type VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { type RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
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 {
|
||||
@@ -43,11 +44,11 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
['voting', 'cDate', 'eDate', 'type'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
gridRef.current?.columnApi.setColumnWidth(
|
||||
gridRef.current?.api.setColumnWidth(
|
||||
'actions',
|
||||
window.innerWidth > BREAKPOINT_MD ? 221 : 80
|
||||
);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useState } from 'react';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface SignatureProps {
|
||||
signature: BlockExplorerTransactionResult['signature'];
|
||||
}
|
||||
|
||||
const valueClass =
|
||||
'font-mono px-2.5 py-0.5 text-xs max-w-[200px] cursor-pointer';
|
||||
const valueClassClosed = 'text-ellipsis overflow-hidden';
|
||||
const valueClassOpen = 'break-words text-left';
|
||||
|
||||
/**
|
||||
* Viewer component for a vega signature. Featuers copy and pasting, truncation
|
||||
*
|
||||
* @param signature
|
||||
*/
|
||||
export const Signature = ({ signature }: SignatureProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
if (!signature || !signature.value || !signature.version || !signature.algo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex border rounded signature-component relative pr-[20px]">
|
||||
<div
|
||||
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
|
||||
title={`${signature.algo}`}
|
||||
>
|
||||
<span>v{signature.version}</span>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
isOpen
|
||||
? `${valueClass} ${valueClassOpen}`
|
||||
: `${valueClass} ${valueClassClosed}`
|
||||
}
|
||||
>
|
||||
<CopyWithTooltip text={signature.value}>
|
||||
<span title={signature.value}>{signature.value}</span>
|
||||
</CopyWithTooltip>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="absolute top-[-3px] right-0 pr-2"
|
||||
title={t('Show full signature')}
|
||||
>
|
||||
<VegaIcon name={isOpen ? VegaIconNames.EYE_OFF : VegaIconNames.EYE} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
... on Proposal {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -8,15 +8,17 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalStatusDocument = gql`
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
... on Proposal {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -14,16 +14,18 @@ export function format(date: string | undefined, def: string) {
|
||||
return new Date().toLocaleDateString() || def;
|
||||
}
|
||||
|
||||
export function getDate(
|
||||
data: ExplorerProposalStatusQuery | undefined,
|
||||
terms: Terms
|
||||
): string {
|
||||
type Proposal = Extract<
|
||||
ExplorerProposalStatusQuery['proposal'],
|
||||
{ __typename?: 'Proposal' }
|
||||
>;
|
||||
|
||||
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
|
||||
const DEFAULT = t('Unknown');
|
||||
if (!data?.proposal?.state) {
|
||||
if (!proposal?.state) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
switch (data.proposal.state) {
|
||||
switch (proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_ENACTED':
|
||||
@@ -62,9 +64,11 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const proposal = data?.proposal as Proposal;
|
||||
|
||||
return (
|
||||
<Lozenge className="font-sans text-xs float-right">
|
||||
{getDate(data, terms)}
|
||||
{getDate(proposal, terms)}
|
||||
</Lozenge>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,17 +2,8 @@ import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type * as Apollo from '@apollo/client';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type ProposalQueryResult = Apollo.QueryResult<
|
||||
ExplorerProposalStatusQuery,
|
||||
Types.Exact<{
|
||||
id: string;
|
||||
}>
|
||||
>;
|
||||
|
||||
interface ProposalStatusIconProps {
|
||||
id: string;
|
||||
}
|
||||
@@ -29,29 +20,38 @@ type IconAndLabel = {
|
||||
* @param data a data result from useExplorerProposalStatusQuery
|
||||
* @returns Icon name
|
||||
*/
|
||||
export function getIconAndLabelForStatus(
|
||||
res: ProposalQueryResult
|
||||
): IconAndLabel {
|
||||
export function useIconAndLabelForStatus(id: string): IconAndLabel {
|
||||
const { data, loading, error } = useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const proposal = data?.proposal as Extract<
|
||||
ExplorerProposalStatusQuery['proposal'],
|
||||
{ __typename?: 'Proposal' }
|
||||
>;
|
||||
|
||||
const DEFAULT: IconAndLabel = {
|
||||
icon: 'error',
|
||||
label: t('Proposal state unknown'),
|
||||
};
|
||||
|
||||
if (res.loading) {
|
||||
if (loading) {
|
||||
return {
|
||||
icon: 'more',
|
||||
label: t('Loading data'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!res?.data?.proposal || res.error) {
|
||||
if (!data?.proposal || error) {
|
||||
return {
|
||||
icon: 'error',
|
||||
label: res.error?.message || DEFAULT.label,
|
||||
label: error?.message || DEFAULT.label,
|
||||
};
|
||||
}
|
||||
|
||||
switch (res.data.proposal.state) {
|
||||
switch (proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return {
|
||||
icon: 'stop',
|
||||
@@ -99,13 +99,7 @@ export function getIconAndLabelForStatus(
|
||||
/**
|
||||
*/
|
||||
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
|
||||
const { icon, label } = getIconAndLabelForStatus(
|
||||
useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
const { icon, label } = useIconAndLabelForStatus(id);
|
||||
|
||||
return (
|
||||
<div className="float-left mr-3">
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerReferralCodeOwnerQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerReferralCodeOwnerQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', createdAt: any, updatedAt: any, referrer: string } } | null> } };
|
||||
|
||||
|
||||
export const ExplorerReferralCodeOwnerDocument = gql`
|
||||
query ExplorerReferralCodeOwner($id: ID!) {
|
||||
referralSets(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
createdAt
|
||||
updatedAt
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerReferralCodeOwnerQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerReferralCodeOwnerQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerReferralCodeOwnerQuery` 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 } = useExplorerReferralCodeOwnerQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerReferralCodeOwnerQuery(baseOptions: Apollo.QueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||
}
|
||||
export function useExplorerReferralCodeOwnerLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||
}
|
||||
export type ExplorerReferralCodeOwnerQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerQuery>;
|
||||
export type ExplorerReferralCodeOwnerLazyQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerLazyQuery>;
|
||||
export type ExplorerReferralCodeOwnerQueryResult = Apollo.QueryResult<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>;
|
||||
@@ -0,0 +1,11 @@
|
||||
query ExplorerReferralCodeOwner($id: ID!) {
|
||||
referralSets(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
createdAt
|
||||
updatedAt
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { ReferralCodeOwner } from './referral-code-owner';
|
||||
import type { ReferralCodeOwnerProps } from './referral-code-owner';
|
||||
import { ExplorerReferralCodeOwnerDocument } from './__generated__/code-owner';
|
||||
const renderComponent = (
|
||||
props: ReferralCodeOwnerProps,
|
||||
mocks: MockedResponse[]
|
||||
) => {
|
||||
return render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MemoryRouter>
|
||||
<ReferralCodeOwner {...props} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('ReferralCodeOwner', () => {
|
||||
it('should render loading state', () => {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
expect(getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error state', async () => {
|
||||
const errorMessage = 'Error fetching referrer: ABC123';
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
error: new Error('nope'),
|
||||
},
|
||||
];
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(errorMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render link to referring party', async () => {
|
||||
const referrerId = 'DEF789';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
referralSets: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'ReferralSet',
|
||||
referrer: referrerId,
|
||||
createdAt: '123',
|
||||
updatedAt: '456',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(referrerId)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { TableCell } from '../../../table';
|
||||
import { useExplorerReferralCodeOwnerQuery } from './__generated__/code-owner';
|
||||
import { PartyLink } from '../../../links';
|
||||
|
||||
export interface ReferralCodeOwnerProps {
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the owner of a referral code
|
||||
*/
|
||||
export const ReferralCodeOwner = ({ code }: ReferralCodeOwnerProps) => {
|
||||
const { data, error, loading } = useExplorerReferralCodeOwnerQuery({
|
||||
variables: {
|
||||
id: code,
|
||||
},
|
||||
});
|
||||
const referrer = data?.referralSets.edges[0]?.node.referrer || '';
|
||||
return (
|
||||
<TableCell>
|
||||
{loading && 'Loading...'}
|
||||
{error && `Error fetching referrer: ${code}`}
|
||||
{referrer.length > 0 && <PartyLink id={referrer} />}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { ReferralTeam } from './team';
|
||||
import type { CreateReferralSet } from './team';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
describe('ReferralTeam', () => {
|
||||
const team = {
|
||||
name: 'Test Team',
|
||||
teamUrl: 'https://example.com/team',
|
||||
avatarUrl: 'https://example.com/avatar',
|
||||
closed: false,
|
||||
};
|
||||
|
||||
const mockTx: CreateReferralSet = {
|
||||
team,
|
||||
};
|
||||
|
||||
const mockId = '123456';
|
||||
const mockCreator = 'JohnDoe';
|
||||
|
||||
it('should render the team name', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Test Team')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the team ID', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Id')).toBeInTheDocument();
|
||||
expect(getByText(mockId)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the creator', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Creator')).toBeInTheDocument();
|
||||
expect(getByText(mockCreator)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the team URL', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Team URL')).toBeInTheDocument();
|
||||
expect(getByText(team.teamUrl)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the avatar URL', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Avatar')).toBeInTheDocument();
|
||||
expect(getByText(team.avatarUrl)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the open status as a tick if closed is falsy', () => {
|
||||
const { getByTestId } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByTestId('open-yes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the open status as a cross if it is truthy', () => {
|
||||
const m = {
|
||||
team: {
|
||||
closed: true,
|
||||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={m} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByTestId('open-no')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
VegaIcon,
|
||||
Icon,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import Hash from '../../../links/hash';
|
||||
import { t } from 'i18next';
|
||||
import { PartyLink } from '../../../links';
|
||||
|
||||
export type CreateReferralSet = components['schemas']['v1CreateReferralSet'];
|
||||
export type ReferralTeam = CreateReferralSet['team'];
|
||||
|
||||
export interface ReferralTeamProps {
|
||||
tx: CreateReferralSet;
|
||||
id: string;
|
||||
creator: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the details for a team in a CreateReferralSet or UpdateReferralSet transaction.
|
||||
*
|
||||
* Intentionally does not render the avatar image or link to the team url.
|
||||
*/
|
||||
export const ReferralTeam = ({ tx, id, creator }: ReferralTeamProps) => {
|
||||
if (!tx.team) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="inline-block mr-2 leading-none">
|
||||
<VegaIcon name={VegaIconNames.TEAM} />
|
||||
</div>
|
||||
{tx.team.name && (
|
||||
<h3 className="inline-block leading-loose">{tx.team.name}</h3>
|
||||
)}
|
||||
|
||||
<div className="min-w-fit max-w-2xl block">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
{t('Id')}
|
||||
<Hash text={id} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('Creator')}
|
||||
<PartyLink id={creator} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
{tx.team.teamUrl && (
|
||||
<KeyValueTableRow>
|
||||
{t('Team URL')}
|
||||
<Hash text={tx.team.teamUrl} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{tx.team.avatarUrl && (
|
||||
<KeyValueTableRow>
|
||||
{t('Avatar')}
|
||||
<Hash text={tx.team.avatarUrl} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow>
|
||||
{t('Open')}
|
||||
<span data-testid={!tx.team.closed ? 'open-yes' : 'open-no'}>
|
||||
{!tx.team.closed ? <Icon name="tick" /> : <Icon name="cross" />}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import { Time } from '../../../time';
|
||||
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
import { TxDataView } from '../../tx-data-view';
|
||||
import Hash from '../../../links/hash';
|
||||
import { Signature } from '../../../signature/signature';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -26,7 +27,8 @@ export const sharedHeaderProps = {
|
||||
className: 'align-top',
|
||||
};
|
||||
|
||||
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||
// The incoming type field is usually the right thing to show. Exceptions are listed here
|
||||
const LabelOverrides: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||
'Stop Orders Submission': 'Stop Order',
|
||||
'Stop Orders Cancellation': 'Cancel Stop Order',
|
||||
};
|
||||
@@ -49,7 +51,7 @@ export const TxDetailsShared = ({
|
||||
const time: string = blockData?.result.block.header.time || '';
|
||||
const height: string = blockData?.result.block.header.height || txData.block;
|
||||
|
||||
const type = Labels[txData.type] || txData.type;
|
||||
const type = LabelOverrides[txData.type] || txData.type;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -75,6 +77,12 @@ export const TxDetailsShared = ({
|
||||
<BlockLink height={height} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Signature')}</TableCell>
|
||||
<TableCell>
|
||||
<Signature signature={txData.signature} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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 { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||
|
||||
interface TxDetailsApplyReferralCodeProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsApplyReferralCode = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsApplyReferralCodeProps) => {
|
||||
if (!txData || !txData.command.applyReferralCode || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const referralCode = txData.command.applyReferralCode.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Applied Code')}</TableCell>
|
||||
<TableCell>{referralCode}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Referrer')}</TableCell>
|
||||
<ReferralCodeOwner code={referralCode} />
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
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 { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import { ReferralTeam } from './referrals/team';
|
||||
|
||||
interface TxDetailsCreateReferralProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsCreateReferralSet = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsCreateReferralProps) => {
|
||||
if (!txData || !txData.command.createReferralSet || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
|
||||
const isTeam = txData.command.createReferralSet.isTeam || false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||
<TableCell>{id}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
<ReferralTeam
|
||||
tx={txData.command.createReferralSet}
|
||||
id={id}
|
||||
creator={txData.submitter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,6 +28,10 @@ import { TxProposal } from './tx-proposal';
|
||||
import { TxDetailsTransfer } from './tx-transfer';
|
||||
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
|
||||
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||
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';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -121,6 +125,14 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsStopOrderSubmission;
|
||||
case 'Transfer Funds':
|
||||
return TxDetailsTransfer;
|
||||
case 'Create Referral Set':
|
||||
return TxDetailsCreateReferralSet;
|
||||
case 'Update Referral Set':
|
||||
return TxDetailsUpdateReferralSet;
|
||||
case 'Apply Referral Code':
|
||||
return TxDetailsApplyReferralCode;
|
||||
case 'Join Team':
|
||||
return TxDetailsJoinTeam;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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 { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||
|
||||
interface TxDetailsJoinTeamProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsJoinTeam = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsJoinTeamProps) => {
|
||||
if (!txData || !txData.command.joinTeam || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const team = txData.command.joinTeam.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Team')}</TableCell>
|
||||
<TableCell>{team}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Referrer')}</TableCell>
|
||||
<ReferralCodeOwner code={team} />
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
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 { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import { ReferralTeam } from './referrals/team';
|
||||
|
||||
interface TxDetailsUpdateReferralProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A copy of create referral set, effectively.
|
||||
* Updating a referral set without a team doesn't make sense,
|
||||
* but is valid, so this component ignores sense.
|
||||
*/
|
||||
export const TxDetailsUpdateReferralSet = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsUpdateReferralProps) => {
|
||||
if (!txData || !txData.command.updateReferralSet || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
|
||||
const isTeam = txData.command.updateReferralSet.isTeam || false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||
<TableCell>{id}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
<ReferralTeam
|
||||
tx={txData.command.updateReferralSet}
|
||||
id={id}
|
||||
creator={txData.submitter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,6 +28,7 @@ export type FilterOption =
|
||||
| 'Delegate'
|
||||
| 'Ethereum Key Rotate Submission'
|
||||
| 'Issue Signatures'
|
||||
| 'Join Team'
|
||||
| 'Key Rotate Submission'
|
||||
| 'Liquidity Provision Order'
|
||||
| 'Node Signature'
|
||||
@@ -47,45 +48,46 @@ export type FilterOption =
|
||||
| 'Vote on Proposal'
|
||||
| 'Withdraw';
|
||||
|
||||
// Alphabetised list of transaction types to appear at the top level
|
||||
export const PrimaryFilterOptions: FilterOption[] = [
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Order',
|
||||
'Cancel Transfer Funds',
|
||||
'Delegate',
|
||||
'Liquidity Provision Order',
|
||||
'Proposal',
|
||||
'Stop Orders Submission',
|
||||
'Stop Orders Cancellation',
|
||||
'Submit Oracle Data',
|
||||
'Submit Order',
|
||||
'Transfer Funds',
|
||||
'Undelegate',
|
||||
'Vote on Proposal',
|
||||
'Withdraw',
|
||||
];
|
||||
export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Market Instructions': [
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Order',
|
||||
'Liquidity Provision Order',
|
||||
'Stop Orders Submission',
|
||||
'Stop Orders Cancellation',
|
||||
'Submit Order',
|
||||
],
|
||||
'Transfers and Withdrawals': [
|
||||
'Transfer Funds',
|
||||
'Cancel Transfer Funds',
|
||||
'Withdraw',
|
||||
],
|
||||
Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
|
||||
Referrals: [
|
||||
'Apply Referral Code',
|
||||
'Create Referral Set',
|
||||
'Join Team',
|
||||
'Update Referral Set',
|
||||
],
|
||||
'External Data': ['Chain Event', 'Submit Oracle Data'],
|
||||
Validators: [
|
||||
'Ethereum Key Rotate Submission',
|
||||
'Issue Signatures',
|
||||
'Key Rotate Submission',
|
||||
'Node Signature',
|
||||
'Node Vote',
|
||||
'Protocol Upgrade',
|
||||
'Register new Node',
|
||||
'State Variable Proposal',
|
||||
'Validator Heartbeat',
|
||||
],
|
||||
};
|
||||
|
||||
// Alphabetised list of transaction types to nest under a 'More...' submenu
|
||||
export const SecondaryFilterOptions: FilterOption[] = [
|
||||
'Chain Event',
|
||||
'Ethereum Key Rotate Submission',
|
||||
'Issue Signatures',
|
||||
'Key Rotate Submission',
|
||||
'Node Signature',
|
||||
'Node Vote',
|
||||
'Protocol Upgrade',
|
||||
'Register new Node',
|
||||
'State Variable Proposal',
|
||||
'Validator Heartbeat',
|
||||
];
|
||||
|
||||
export const AllFilterOptions: FilterOption[] = [
|
||||
...PrimaryFilterOptions,
|
||||
...SecondaryFilterOptions,
|
||||
];
|
||||
export const AllFilterOptions: FilterOption[] =
|
||||
Object.values(filterOptions).flat();
|
||||
|
||||
export interface TxFilterProps {
|
||||
filters: Set<FilterOption>;
|
||||
@@ -122,46 +124,33 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{PrimaryFilterOptions.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={() => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
|
||||
{Object.entries(filterOptions).map(([key, value]) => (
|
||||
<DropdownMenuSub key={key}>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t(key)}
|
||||
<Icon name="chevron-right" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{value.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={(checked) => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" className="inline" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
))}
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t('More Types')}
|
||||
<Icon name="chevron-right" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{SecondaryFilterOptions.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={(checked) => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" className="inline" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -61,53 +61,4 @@ describe('TxsListNavigation', () => {
|
||||
|
||||
expect(nextPageMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('disables "Older" button if hasMoreTxs is false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={false}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Older')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables "Newer" button if hasPreviousPage is false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={true}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Newer')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables both buttons when more and previous are false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={false}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Newer')).toBeDisabled();
|
||||
expect(screen.getByText('Older')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,8 @@ export interface TxListNavigationProps {
|
||||
loading?: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
hasMoreTxs: boolean;
|
||||
children: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
isEmpty?: boolean;
|
||||
}
|
||||
/**
|
||||
* Displays a list of transactions with filters and controls to navigate through the list.
|
||||
@@ -21,9 +22,8 @@ export const TxsListNavigation = ({
|
||||
refreshTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
hasMoreTxs,
|
||||
hasPreviousPage,
|
||||
children,
|
||||
isEmpty,
|
||||
loading = false,
|
||||
}: TxListNavigationProps) => {
|
||||
return (
|
||||
@@ -35,7 +35,6 @@ export const TxsListNavigation = ({
|
||||
<Button
|
||||
className="mr-2"
|
||||
size="xs"
|
||||
disabled={!hasPreviousPage || loading}
|
||||
onClick={() => {
|
||||
previousPage();
|
||||
}}
|
||||
@@ -44,7 +43,7 @@ export const TxsListNavigation = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
disabled={!hasMoreTxs}
|
||||
disabled={isEmpty}
|
||||
onClick={() => {
|
||||
nextPage();
|
||||
}}
|
||||
|
||||
@@ -48,6 +48,8 @@ const displayString: StringMap = {
|
||||
StopOrdersSubmission: 'Stop',
|
||||
StopOrdersCancellation: 'Cancel stop',
|
||||
'Stop Orders Cancellation': 'Cancel stop',
|
||||
'Apply Referral Code': 'Referral',
|
||||
'Create Referral Set': 'Create referral',
|
||||
};
|
||||
|
||||
export function getLabelForStopOrderType(
|
||||
@@ -80,6 +82,12 @@ export function getLabelForOrderType(
|
||||
if (command.orderSubmission.icebergOpts) {
|
||||
return 'Iceberg';
|
||||
}
|
||||
if (command.orderSubmission.type === 'TYPE_MARKET') {
|
||||
return 'Market order';
|
||||
}
|
||||
if (command.orderSubmission.type === 'TYPE_LIMIT') {
|
||||
return 'Limit order';
|
||||
}
|
||||
}
|
||||
return 'Order';
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ describe('TxDetailsTransfer', () => {
|
||||
},
|
||||
},
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value:
|
||||
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
|
||||
},
|
||||
|
||||
@@ -20,6 +20,8 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||
type: 'Submit Order',
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value: '123',
|
||||
},
|
||||
code: 0,
|
||||
|
||||
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
|
||||
url.searchParams.append('first', count);
|
||||
url.searchParams.append('after', params.after);
|
||||
} else {
|
||||
url.searchParams.append('last', count);
|
||||
url.searchParams.append('first', count);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
|
||||
count: 10,
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?last=10';
|
||||
const expectedUrl = 'https://example.com/transactions?first=10';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl =
|
||||
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
|
||||
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
@@ -31,14 +31,14 @@ export interface IUseTxsData {
|
||||
}
|
||||
|
||||
export const useTxsData = ({
|
||||
count = 25,
|
||||
count = 50,
|
||||
before,
|
||||
after,
|
||||
filters,
|
||||
party,
|
||||
}: IUseTxsData) => {
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
let hasMoreTxs = true;
|
||||
let hasMoreTxs = false;
|
||||
let txsData: BlockExplorerTransactionResult[] = [];
|
||||
|
||||
const url = getTxsDataUrl({
|
||||
@@ -60,8 +60,8 @@ export const useTxsData = ({
|
||||
}
|
||||
|
||||
const nextPage = useCallback(() => {
|
||||
const after = data?.transactions.at(-1)?.cursor || '';
|
||||
const params: URLSearchParamsInit = { after };
|
||||
const before = data?.transactions.at(-1)?.cursor || '';
|
||||
const params: URLSearchParamsInit = { before };
|
||||
if (filters) {
|
||||
params.filters = Array.from(filters).join(',');
|
||||
}
|
||||
@@ -69,8 +69,8 @@ export const useTxsData = ({
|
||||
}, [filters, data, setSearchParams]);
|
||||
|
||||
const previousPage = useCallback(() => {
|
||||
const before = data?.transactions[0]?.cursor || '';
|
||||
const params: URLSearchParamsInit = { before };
|
||||
const after = data?.transactions[0]?.cursor || '';
|
||||
const params: URLSearchParamsInit = { after };
|
||||
if (filters && filters.size > 0 && filters.size === 1) {
|
||||
params.filters = Array.from(filters)[0];
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ const Block = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{blockData && (
|
||||
{blockData && 'result' in blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -73,7 +73,7 @@ export const Layout = () => {
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
</div>
|
||||
<div className={fixedWidthClasses}>
|
||||
<main className="p-4">
|
||||
<main className="md:p-4">
|
||||
{!isHome && <BreadcrumbsContainer className="mb-4" />}
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
@@ -18,7 +18,6 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from './route-names';
|
||||
import { NetworkParameters } from './network-parameters';
|
||||
import type { Params, RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -29,7 +28,7 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||
import { Disclaimer } from './pages/disclaimer';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
|
||||
export type Navigable = {
|
||||
@@ -60,311 +59,315 @@ type Route = RouteItem & {
|
||||
children?: RouteItem[];
|
||||
};
|
||||
|
||||
const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
export const useRouterConfig = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
const partiesRoutes: Route[] = featureFlags.EXPLORER_PARTIES
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartyAccountsByAsset />,
|
||||
handle: {
|
||||
breadcrumb: () => {
|
||||
return t('Assets');
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartyAccountsByAsset />,
|
||||
handle: {
|
||||
breadcrumb: () => {
|
||||
return t('Assets');
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const assetsRoutes: Route[] = featureFlags.EXPLORER_ASSETS
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
handle: {
|
||||
name: t('Assets'),
|
||||
text: t('Assets'),
|
||||
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <AssetsPage />,
|
||||
},
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
handle: {
|
||||
name: t('Assets'),
|
||||
text: t('Assets'),
|
||||
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <AssetsPage />,
|
||||
]
|
||||
: [];
|
||||
|
||||
const genesisRoutes: Route[] = featureFlags.EXPLORER_GENESIS
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
handle: {
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const governanceRoutes: Route[] = featureFlags.EXPLORER_GOVERNANCE
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
handle: {
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Proposals />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const marketsRoutes: Route[] = featureFlags.EXPLORER_MARKETS
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
handle: {
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketsPage />,
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes: Route[] =
|
||||
featureFlags.EXPLORER_NETWORK_PARAMETERS
|
||||
? [
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
]
|
||||
: [];
|
||||
|
||||
const genesisRoutes: Route[] = FLAGS.EXPLORER_GENESIS
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
handle: {
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const governanceRoutes: Route[] = FLAGS.EXPLORER_GOVERNANCE
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
handle: {
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Proposals />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const marketsRoutes: Route[] = FLAGS.EXPLORER_MARKETS
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
handle: {
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketsPage />,
|
||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const linkTo = (...segments: (string | undefined)[]) =>
|
||||
compact(segments).join('/');
|
||||
|
||||
const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
element: <Layout />,
|
||||
handle: {
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
handle: {
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
||||
{truncateMiddle(remove0x(params.txHash as string))}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes: Route[] = FLAGS.EXPLORER_NETWORK_PARAMETERS
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
handle: {
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
</Link>
|
||||
),
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||
{params.block}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = FLAGS.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
handle: {
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
|
||||
},
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.ORACLES, params.id)}>
|
||||
{truncateMiddle(params.id as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const linkTo = (...segments: (string | undefined)[]) =>
|
||||
compact(segments).join('/');
|
||||
|
||||
export const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
element: <Layout />,
|
||||
handle: {
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
|
||||
{
|
||||
path: Routes.DISCLAIMER,
|
||||
element: <Disclaimer />,
|
||||
handle: {
|
||||
name: t('Disclaimer'),
|
||||
text: t('Disclaimer'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
],
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
{
|
||||
path: Routes.RESTRICTED,
|
||||
element: <RestrictedPage />,
|
||||
handle: {
|
||||
name: t('Restricted'),
|
||||
text: t('Restricted'),
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
handle: {
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
||||
{truncateMiddle(remove0x(params.txHash as string))}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||
{params.block}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
handle: {
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
|
||||
},
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.ORACLES, params.id)}>
|
||||
{truncateMiddle(params.id as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.DISCLAIMER,
|
||||
element: <Disclaimer />,
|
||||
handle: {
|
||||
name: t('Disclaimer'),
|
||||
text: t('Disclaimer'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.RESTRICTED,
|
||||
element: <RestrictedPage />,
|
||||
handle: {
|
||||
name: t('Restricted'),
|
||||
text: t('Restricted'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter(routerConfig);
|
||||
];
|
||||
return routerConfig;
|
||||
};
|
||||
|
||||
@@ -51,9 +51,10 @@ export const TxsListFiltered = () => {
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={true}
|
||||
hasPreviousPage={hasMoreTxs}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
isEmpty={txsData.length === 0}
|
||||
>
|
||||
<TxsFilter
|
||||
filters={filters}
|
||||
@@ -70,7 +71,16 @@ export const TxsListFiltered = () => {
|
||||
txs={txsData}
|
||||
loadMoreTxs={nextPage}
|
||||
error={error}
|
||||
className="mb-28 w-full min-w-[400px]"
|
||||
className="mb-4 w-full min-w-[400px]"
|
||||
/>
|
||||
<TxsListNavigation
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasMoreTxs}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
isEmpty={txsData.length === 0}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,10 @@ const Tx = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!data || !data?.transaction) {
|
||||
errorMessage = 'Transaction not found';
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<PageHeader
|
||||
@@ -49,7 +53,7 @@ const Tx = () => {
|
||||
<TxDetails
|
||||
className="mb-28"
|
||||
txData={data?.transaction}
|
||||
pubKey={data?.transaction.submitter}
|
||||
pubKey={data?.transaction?.submitter}
|
||||
/>
|
||||
</RenderFetched>
|
||||
</section>
|
||||
|
||||
@@ -23,6 +23,8 @@ const txData: BlockExplorerTransactionResult = {
|
||||
type: 'type',
|
||||
command: {} as ValidatorHeartbeat,
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value: '123',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,8 +11,8 @@ interface TxDetailsProps {
|
||||
export const txDetailsTruncateLength = 30;
|
||||
|
||||
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!txData || !pubKey) {
|
||||
return <>{t('Transaction could not be found')}</>;
|
||||
}
|
||||
return (
|
||||
<section className="mb-10" key={txData.hash}>
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface BlockExplorerTransactionResult {
|
||||
cursor: string;
|
||||
command: components['schemas']['blockexplorerv1transaction'];
|
||||
signature: {
|
||||
version: string;
|
||||
algo: string;
|
||||
value: string;
|
||||
};
|
||||
error?: string;
|
||||
|
||||
+2
-2
@@ -8,13 +8,13 @@ type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
|
||||
type XOR<T, U> = T | U extends object
|
||||
? (Without<T, U> & U) | (Without<U, T> & T)
|
||||
: T | U;
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
type OneOf<T extends any[]> = T extends [infer Only]
|
||||
? Only
|
||||
: T extends [infer A, infer B, ...infer Rest]
|
||||
? OneOf<[XOR<A, B>, ...Rest]>
|
||||
: never;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface paths {
|
||||
'/info': {
|
||||
|
||||
@@ -47,7 +47,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
.and('contain.text', 'USDC (fake)');
|
||||
});
|
||||
|
||||
it('Unable to submit proposal with public key', function () {
|
||||
it.skip('Unable to submit proposal with public key', function () {
|
||||
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
|
||||
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
@@ -55,7 +55,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
cy.getByTestId('dialog-content')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h1').should('have.text', 'Transaction failed');
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'have.text',
|
||||
'Transaction failed'
|
||||
);
|
||||
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,6 +112,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
||||
performanceHysteresisEpochs: 2,
|
||||
slaCompetitionFactor: '0.1',
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
quadraticSlippageFactor: '0',
|
||||
instrument: {
|
||||
name: 'Token test market',
|
||||
@@ -196,6 +197,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
||||
timeWindow: '3600',
|
||||
scalingFactor: 10,
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
triggeringRatio: '0.7',
|
||||
auctionExtension: '1',
|
||||
},
|
||||
@@ -240,6 +242,7 @@ export function createSuccessorMarketProposalTxBody(
|
||||
decimalPlaces: '5',
|
||||
positionDecimalPlaces: '5',
|
||||
linearSlippageFactor: '0.001',
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
quadraticSlippageFactor: '0',
|
||||
liquiditySlaParameters: {
|
||||
priceRange: '0.5',
|
||||
@@ -334,6 +337,7 @@ export function createSuccessorMarketProposalTxBody(
|
||||
timeWindow: '3600',
|
||||
scalingFactor: 10,
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
triggeringRatio: '0.7',
|
||||
auctionExtension: '1',
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -23,7 +23,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -22,7 +22,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -21,7 +21,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React, { Suspense } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,21 +15,23 @@ import {
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
import { useConnectors } from './lib/vega-connectors';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const useVegaWalletEagerConnect = () => {
|
||||
const vegaConnecting = useEagerConnect(Connectors);
|
||||
const connectors = useConnectors();
|
||||
const vegaConnecting = useEagerConnect(connectors);
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = React.useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(Connectors['view']);
|
||||
connect(connectors.view);
|
||||
}
|
||||
return vegaConnecting;
|
||||
};
|
||||
|
||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWeb3React();
|
||||
const { VEGA_URL } = useEnvironment();
|
||||
@@ -79,10 +81,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (!featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
run();
|
||||
}
|
||||
}, [token, appDispatch, staking, vesting]);
|
||||
}, [
|
||||
token,
|
||||
appDispatch,
|
||||
staking,
|
||||
vesting,
|
||||
featureFlags.GOVERNANCE_NETWORK_DOWN,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (account && pubKey) {
|
||||
@@ -147,16 +155,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
};
|
||||
|
||||
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
|
||||
if (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
|
||||
if (featureFlags.GOVERNANCE_NETWORK_LIMITS) {
|
||||
getNetworkLimits();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopPoll();
|
||||
};
|
||||
}, [appDispatch, VEGA_URL, t]);
|
||||
}, [appDispatch, VEGA_URL, t, featureFlags.GOVERNANCE_NETWORK_LIMITS]);
|
||||
|
||||
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashError />
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
@@ -96,7 +96,9 @@ const cache: InMemoryCacheConfig = {
|
||||
const Web3Container = ({
|
||||
chainId,
|
||||
}: {
|
||||
/** Ethereum chain id */
|
||||
chainId: number;
|
||||
/** Ethereum provider url */
|
||||
providerUrl: string;
|
||||
}) => {
|
||||
const InitializeHandlers = () => {
|
||||
@@ -123,6 +125,9 @@ const Web3Container = ({
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
const vegaChainId = useChainId(VEGA_URL);
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
@@ -157,7 +162,8 @@ const Web3Container = ({
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!vegaChainId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -169,6 +175,7 @@ const Web3Container = ({
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
chainId: vegaChainId,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
|
||||
@@ -23,10 +23,13 @@ export const Heading = ({
|
||||
})}
|
||||
>
|
||||
<h1
|
||||
className={classNames('font-alpha calt text-5xl break-words', {
|
||||
'mt-0': !marginTop,
|
||||
'mb-0': !marginBottom,
|
||||
})}
|
||||
className={classNames(
|
||||
'font-alpha calt text-5xl [word-break:break-word]',
|
||||
{
|
||||
'mt-0': !marginTop,
|
||||
'mb-0': !marginBottom,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
@@ -7,16 +7,16 @@ import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { Connectors } from '../../lib/vega-connectors';
|
||||
import { useConnectors } from '../../lib/vega-connectors';
|
||||
import { RiskMessage } from './risk-message';
|
||||
|
||||
export const VegaWalletDialogs = () => {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
|
||||
const connectors = useConnectors();
|
||||
return (
|
||||
<>
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
connectors={connectors}
|
||||
riskMessage={<RiskMessage />}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const VegaWalletDialogs = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
<ViewAsDialog connector={Connectors.view} />
|
||||
<ViewAsDialog connector={connectors.view} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,12 +49,8 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
? activeProvider
|
||||
: defaultProvider;
|
||||
|
||||
if (
|
||||
account &&
|
||||
activeProvider &&
|
||||
typeof activeProvider.getSigner === 'function'
|
||||
) {
|
||||
signer = provider.getSigner();
|
||||
if (account && provider && typeof provider.getSigner === 'function') {
|
||||
signer = provider.getSigner(account);
|
||||
}
|
||||
|
||||
const tokenVestingAddress =
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import {
|
||||
PARTY_NOT_FOUND,
|
||||
filterAcceptableGraphqlErrors,
|
||||
isPartyNotFoundError,
|
||||
} from './party';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param message
|
||||
* @returns GraphQLError
|
||||
*/
|
||||
function createMockApolloErrors(message: string): GraphQLError {
|
||||
return {
|
||||
message,
|
||||
extensions: {
|
||||
code: message.toUpperCase().replace(/ /g, '_'),
|
||||
},
|
||||
locations: [],
|
||||
originalError: new Error(message),
|
||||
path: [],
|
||||
nodes: [],
|
||||
positions: [1],
|
||||
name: message,
|
||||
source: {
|
||||
body: message,
|
||||
name: message,
|
||||
locationOffset: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('filterAcceptableGraphqlErrors', () => {
|
||||
it('should return undefined if the error is a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('failed to get party for ID')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the error if it is not a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('Some other error')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are multiple errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [
|
||||
createMockApolloErrors('failed to get party for ID'),
|
||||
createMockApolloErrors('Some other error'),
|
||||
],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are no errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined if the error is undefined', () => {
|
||||
const result = filterAcceptableGraphqlErrors(undefined);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPartyNotFoundError', () => {
|
||||
it('should return true if the error message includes PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'failed to get party for ID' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the error message does not include PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'Some other error' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
// Will trip if the error message changes, which should not be a problem, but there
|
||||
// might be logic that depends on it
|
||||
it('expects party not found error to remain consistent', () => {
|
||||
const error = 'failed to get party for ID';
|
||||
|
||||
expect(PARTY_NOT_FOUND).toStrictEqual(error);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
|
||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||
|
||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
@@ -6,3 +8,23 @@ export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* If a party has no accounts or data, then this GraphQL query believes it does not exist
|
||||
* Not having any rewards is a valid state, so in some cases we can filter this error out.
|
||||
*
|
||||
* @param error ApolloError | undefined
|
||||
* @returns ApolloError | undefined
|
||||
*/
|
||||
export function filterAcceptableGraphqlErrors(
|
||||
error?: ApolloError
|
||||
): ApolloError | undefined {
|
||||
// Currently the only error we expect is when a party has no accounts
|
||||
if (error && error.graphQLErrors.length === 1) {
|
||||
if (isPartyNotFoundError(error.graphQLErrors[0])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
@@ -13,13 +14,17 @@ export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
export const useConnectors = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
return useMemo(
|
||||
() => ({
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
||||
}),
|
||||
[featureFlags.METAMASK_SNAPS]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ 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, FLAGS } 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/proposals/__generated__/Proposals';
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
orderByUpgradeBlockHeight,
|
||||
} from '../proposals/components/proposals-list/proposals-list';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import type { ProposalQuery } from '../proposals/proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../proposals/types';
|
||||
|
||||
const nodesToShow = 6;
|
||||
|
||||
@@ -39,7 +39,7 @@ const HomeProposals = ({
|
||||
proposals,
|
||||
protocolUpgradeProposals,
|
||||
}: {
|
||||
proposals: ProposalQuery['proposal'][];
|
||||
proposals: Proposal[];
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -175,6 +175,7 @@ export const ValidatorDetailsLink = ({
|
||||
};
|
||||
|
||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -186,9 +187,9 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+4
-9
@@ -1,16 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProposalInfoLabelVariant } from '../proposal-info-label';
|
||||
import { type ReactNode } from 'react';
|
||||
import { type ProposalInfoLabelVariant } from '../proposal-info-label';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const CurrentProposalState = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
export const CurrentProposalState = ({ proposal }: { proposal: Proposal }) => {
|
||||
const { t } = useTranslation();
|
||||
let proposalStatus: ReactNode;
|
||||
let variant = 'tertiary' as ProposalInfoLabelVariant;
|
||||
|
||||
-272
@@ -1,272 +0,0 @@
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { CurrentProposalStatus } from './current-proposal-status';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
request: {
|
||||
query: NetworkParamsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateNetParam.requiredMajority',
|
||||
value: '0.00000001',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateNetParam.requiredParticipation',
|
||||
value: '0.000000001',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
}) => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider mocks={[networkParamsQueryMock]}>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(60 * 60 * 1000);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('Proposal open - renders will fail state if the proposal will fail', async () => {
|
||||
const failedProposal = generateProposal({
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
});
|
||||
renderComponent({ proposal: failedProposal });
|
||||
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
|
||||
expect(await screen.findByText('fail.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal open - renders will pass state if the proposal will pass', async () => {
|
||||
const proposal = generateProposal();
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
|
||||
expect(await screen.findByText('pass.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal enacted - renders vote passed and time since enactment', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_ENACTED,
|
||||
terms: {
|
||||
enactmentDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal passed - renders vote passed and time since vote closed', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal waiting for node vote - will pass - renders if the vote will pass and status', async () => {
|
||||
const failedProposal = generateProposal({
|
||||
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
});
|
||||
renderComponent({ proposal: failedProposal });
|
||||
expect(
|
||||
await screen.findByText('Waiting for nodes to validate asset.')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
|
||||
expect(await screen.findByText('fail.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal waiting for node vote - will fail - renders if the vote will pass and status', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Waiting for nodes to validate asset.')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
|
||||
expect(await screen.findByText('pass.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal failed - renders vote failed reason and vote closed ago', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
errorDetails: 'foo',
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Vote closed. Failed due to:')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('foo')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal failed - renders rejection reason there are no error details', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
rejectionReason: ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE,
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Vote closed. Failed due to:')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal failed - renders unknown reason if there are no error details or rejection reason', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Vote closed. Failed due to:')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('unknown reason')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal failed - renders participation not met if participation is not met', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Vote closed. Failed due to:')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('Participation not met')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Proposal failed - renders majority not met if majority is not met', async () => {
|
||||
const proposal = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
terms: {
|
||||
closingDatetime: new Date(0).toISOString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '0',
|
||||
totalTokens: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: '1',
|
||||
totalTokens: '25242474195500835440000',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent({ proposal });
|
||||
expect(
|
||||
await screen.findByText('Vote closed. Failed due to:')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('Majority not met')).toBeInTheDocument();
|
||||
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
|
||||
});
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
export const StatusPass = ({ children }: { children: ReactNode }) => (
|
||||
<span className="text-vega-green">{children}</span>
|
||||
);
|
||||
|
||||
export const StatusFail = ({ children }: { children: ReactNode }) => (
|
||||
<span className="text-danger">{children}</span>
|
||||
);
|
||||
|
||||
const WillPass = ({
|
||||
willPass,
|
||||
children,
|
||||
}: {
|
||||
willPass: boolean;
|
||||
children?: ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
if (willPass) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<StatusPass>{t('pass')}.</StatusPass>
|
||||
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<StatusFail>{t('fail')}.</StatusFail>
|
||||
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const CurrentProposalStatus = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
const { willPassByTokenVote, majorityMet, participationMet } =
|
||||
useVoteInformation({
|
||||
proposal,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
|
||||
const daysClosedAgo = formatDistanceToNow(
|
||||
new Date(proposal?.terms.closingDatetime),
|
||||
{ addSuffix: true }
|
||||
);
|
||||
|
||||
const daysEnactedAgo =
|
||||
proposal?.terms.enactmentDatetime &&
|
||||
formatDistanceToNow(new Date(proposal.terms.enactmentDatetime), {
|
||||
addSuffix: true,
|
||||
});
|
||||
|
||||
if (proposal?.state === ProposalState.STATE_OPEN) {
|
||||
return (
|
||||
<WillPass willPass={willPassByTokenVote}>{t('currentlySetTo')}</WillPass>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
proposal?.state === ProposalState.STATE_FAILED ||
|
||||
proposal?.state === ProposalState.STATE_DECLINED ||
|
||||
proposal?.state === ProposalState.STATE_REJECTED
|
||||
) {
|
||||
if (!participationMet) {
|
||||
return (
|
||||
<>
|
||||
<span>{t('voteFailedReason')}</span>
|
||||
<StatusFail>{t('participationNotMet')}</StatusFail>
|
||||
<span> {daysClosedAgo}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!majorityMet) {
|
||||
return (
|
||||
<>
|
||||
<span>{t('voteFailedReason')}</span>
|
||||
<StatusFail>{t('majorityNotMet')}</StatusFail>
|
||||
<span> {daysClosedAgo}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>{t('voteFailedReason')}</span>
|
||||
<StatusFail>
|
||||
{proposal?.errorDetails ||
|
||||
proposal?.rejectionReason ||
|
||||
t('unknownReason')}
|
||||
</StatusFail>
|
||||
<span> {daysClosedAgo}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (
|
||||
proposal?.state === ProposalState.STATE_ENACTED ||
|
||||
proposal?.state === ProposalState.STATE_PASSED
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<span>{t('votePassed')}</span>
|
||||
<StatusPass>
|
||||
|
||||
{proposal?.state === ProposalState.STATE_ENACTED
|
||||
? t('Enacted')
|
||||
: t('Passed')}
|
||||
</StatusPass>
|
||||
<span>
|
||||
|
||||
{proposal?.state === ProposalState.STATE_ENACTED
|
||||
? daysEnactedAgo
|
||||
: daysClosedAgo}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (proposal?.state === ProposalState.STATE_WAITING_FOR_NODE_VOTE) {
|
||||
return (
|
||||
<WillPass willPass={willPassByTokenVote}>
|
||||
<span>{t('WaitingForNodeVote')}</span>{' '}
|
||||
<span>{t('currentlySetTo')}</span>
|
||||
</WillPass>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { CurrentProposalStatus } from './current-proposal-status';
|
||||
+3
@@ -7,8 +7,10 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
|
||||
export const ProposalAssetDetails = ({
|
||||
asset,
|
||||
originalAsset,
|
||||
}: {
|
||||
asset: AssetFieldsFragment;
|
||||
originalAsset?: AssetFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
||||
@@ -27,6 +29,7 @@ export const ProposalAssetDetails = ({
|
||||
<div className="mb-10 pb-4">
|
||||
<AssetDetailsTable
|
||||
asset={asset}
|
||||
originalAsset={originalAsset}
|
||||
omitRows={[
|
||||
AssetDetail.STATUS,
|
||||
AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
|
||||
|
||||
+2
-3
@@ -6,11 +6,10 @@ import {
|
||||
KeyValueTableRow,
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalChangeTableProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}
|
||||
|
||||
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
|
||||
|
||||
+20
-7
@@ -19,12 +19,12 @@ import {
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { type MockedResponse } from '@apollo/client/testing';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/proposals', () => ({
|
||||
...jest.requireActual('@vegaprotocol/proposals'),
|
||||
@@ -36,7 +36,7 @@ jest.mock('@vegaprotocol/proposals', () => ({
|
||||
}));
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
proposal: Proposal,
|
||||
isListItem = true,
|
||||
mocks: MockedResponse[] = [],
|
||||
voteState?: VoteState
|
||||
@@ -62,9 +62,9 @@ describe('Proposal header', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('Renders New market proposal', () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = true;
|
||||
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New some market',
|
||||
@@ -103,6 +103,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders Update market proposal', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New market id',
|
||||
@@ -125,12 +126,13 @@ describe('Proposal header', () => {
|
||||
screen.queryByTestId('proposal-description')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'Market change: MarketId'
|
||||
'Update to market ID: MarketId'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders New asset proposal - ERC20', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'New asset: Fake currency',
|
||||
@@ -160,6 +162,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders New asset proposal - BuiltInAsset', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
@@ -185,6 +188,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders Update network', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
rationale: {
|
||||
title: 'Network parameter',
|
||||
@@ -214,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: {
|
||||
@@ -235,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: {
|
||||
@@ -260,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: {
|
||||
@@ -281,6 +288,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it('Renders asset change proposal header', () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
@@ -298,6 +306,7 @@ describe('Proposal header', () => {
|
||||
|
||||
it("Renders unknown proposal if it's a different proposal type", () => {
|
||||
renderComponent(
|
||||
// @ts-ignore we aren't using batch yet
|
||||
generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
@@ -314,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: {
|
||||
@@ -326,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: {
|
||||
@@ -339,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: {
|
||||
@@ -353,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: {
|
||||
|
||||
+55
-15
@@ -1,9 +1,14 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Lozenge, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Lozenge,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { shorten } from '@vegaprotocol/utils';
|
||||
import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type ReactNode } from 'react';
|
||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||
import { CurrentProposalState } from '../current-proposal-state';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
@@ -12,25 +17,34 @@ import {
|
||||
useNewTransferProposalDetails,
|
||||
useSuccessorMarketProposalDetails,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
CONSOLE_MARKET_PAGE,
|
||||
DApp,
|
||||
useFeatureFlags,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { VoteState } from '../vote-details/use-user-vote';
|
||||
import { type VoteState } from '../vote-details/use-user-vote';
|
||||
import { VoteBreakdown } from '../vote-breakdown';
|
||||
import { GovernanceTransferKindMapping } from '@vegaprotocol/types';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
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 = '';
|
||||
@@ -39,7 +53,7 @@ export const ProposalHeader = ({
|
||||
|
||||
const titleContent = shorten(title ?? '', 100);
|
||||
|
||||
const getAsset = (proposal: ProposalQuery['proposal']) => {
|
||||
const getAsset = (proposal: Proposal) => {
|
||||
const terms = proposal?.terms;
|
||||
if (
|
||||
terms?.change.__typename === 'NewMarket' &&
|
||||
@@ -54,13 +68,14 @@ export const ProposalHeader = ({
|
||||
switch (change?.__typename) {
|
||||
case 'NewMarket': {
|
||||
proposalType =
|
||||
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
|
||||
featureFlags.PRODUCT_PERPETUALS &&
|
||||
change?.instrument?.product?.__typename
|
||||
? `NewMarket${change?.instrument?.product?.__typename}`
|
||||
: 'NewMarket';
|
||||
fallbackTitle = t('NewMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
{featureFlags.SUCCESSOR_MARKETS && (
|
||||
<SuccessorCode proposalId={proposal?.id} />
|
||||
)}
|
||||
<span>
|
||||
@@ -82,13 +97,13 @@ export const ProposalHeader = ({
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
proposalType =
|
||||
FLAGS.UPDATE_MARKET_STATE && change?.updateType
|
||||
featureFlags.UPDATE_MARKET_STATE && change?.updateType
|
||||
? t(change.updateType)
|
||||
: 'UpdateMarketState';
|
||||
fallbackTitle = t('UpdateMarketStateProposal');
|
||||
details = (
|
||||
<span>
|
||||
{FLAGS.UPDATE_MARKET_STATE &&
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
change?.market?.id &&
|
||||
change.updateType ? (
|
||||
<>
|
||||
@@ -104,8 +119,33 @@ export const ProposalHeader = ({
|
||||
fallbackTitle = t('UpdateMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('MarketChange')}:</span>{' '}
|
||||
<span>{truncateMiddle(change.marketId)}</span>
|
||||
<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;
|
||||
@@ -177,14 +217,14 @@ export const ProposalHeader = ({
|
||||
case 'NewTransfer':
|
||||
proposalType = 'NewTransfer';
|
||||
fallbackTitle = t('NewTransferProposal');
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<NewTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
proposalType = 'CancelTransfer';
|
||||
fallbackTitle = t('CancelTransferProposal');
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<CancelTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
|
||||
+4
-4
@@ -1,5 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
@@ -14,9 +13,10 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const formatEndOfProgramTimestamp = (value: string) => {
|
||||
@@ -54,8 +54,8 @@ export const ProposalReferralProgramDetails = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||
const stakingTiers = proposal?.terms?.change?.stakingTiers;
|
||||
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;
|
||||
|
||||
|
||||
+2
-3
@@ -1,6 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import { useCancelTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
KeyValueTable,
|
||||
@@ -8,11 +6,12 @@ import {
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalCancelTransferDetails = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const details = useCancelTransferProposalDetails(proposal?.id);
|
||||
|
||||
+2
-3
@@ -1,6 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -21,11 +19,12 @@ import {
|
||||
addDecimalsFormatNumberQuantum,
|
||||
formatDateWithLocalTimezone,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalTransferDetails = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
+2
-2
@@ -1,5 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from '../proposal-referral-program-details';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
// These types are not generated as it's not known how dynamic these are
|
||||
type VestingBenefitTier = {
|
||||
@@ -43,7 +43,7 @@ export const formatVolumeDiscountFactor = (value: string) => {
|
||||
};
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -5,13 +5,13 @@ import {
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Row } from '@vegaprotocol/markets';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalUpdateMarketStateProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const ProposalUpdateMarketState = ({
|
||||
|
||||
+2
-2
@@ -1,5 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
@@ -12,9 +11,10 @@ import {
|
||||
} from '../proposal-referral-program-details';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalReferralProgramDetailsProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: Proposal | null;
|
||||
}
|
||||
|
||||
export const formatVolumeDiscountFactor = (value: string) => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { mockNetworkParams } from '../../test-helpers/mocks';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
...jest.requireActual('@vegaprotocol/network-parameters'),
|
||||
@@ -48,16 +48,17 @@ const vegaWalletConfig: VegaWalletConfig = {
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
chainId: 'VEGA_CHAIN_ID',
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||
const renderComponent = (proposal: IProposal) => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal
|
||||
restData={{}}
|
||||
proposal={proposal as ProposalQuery['proposal']}
|
||||
proposal={proposal}
|
||||
networkParams={mockNetworkParams}
|
||||
/>
|
||||
</VegaWalletProvider>
|
||||
|
||||
@@ -12,25 +12,25 @@ import { UserVote } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MarketInfo } from '@vegaprotocol/markets';
|
||||
import type { AssetQuery } from '@vegaprotocol/assets';
|
||||
import { type MarketInfo } from '@vegaprotocol/markets';
|
||||
import { type AssetQuery } from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ProposalMarketChanges } from '../proposal-market-changes';
|
||||
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import { type NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import {
|
||||
ProposalCancelTransferDetails,
|
||||
ProposalTransferDetails,
|
||||
} from '../proposal-transfer';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalQuery['proposal'];
|
||||
proposal: IProposal;
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
marketData?: MarketInfo | null;
|
||||
parentMarketData?: MarketInfo | null;
|
||||
@@ -53,6 +53,7 @@ export const Proposal = ({
|
||||
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);
|
||||
@@ -65,10 +66,13 @@ export const Proposal = ({
|
||||
? 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') {
|
||||
@@ -129,7 +133,7 @@ export const Proposal = ({
|
||||
}
|
||||
|
||||
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
||||
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
|
||||
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
|
||||
<>
|
||||
{proposal.terms.change.__typename === 'NewTransfer' && (
|
||||
/** Governance New Transfer Details */
|
||||
@@ -228,7 +232,7 @@ export const Proposal = ({
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} />
|
||||
<ProposalAssetDetails asset={asset} originalAsset={originalAsset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { type MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { format } from 'date-fns';
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
proposal: Proposal,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||
) =>
|
||||
|
||||
+3
-4
@@ -1,21 +1,20 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
ProposalRejectionReasonMapping,
|
||||
ProposalState,
|
||||
} from '@vegaprotocol/types';
|
||||
import Routes from '../../../routes';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const ProposalsListItemDetails = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const state = proposal?.state;
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListItemProps {
|
||||
proposal?: ProposalQuery['proposal'] | null;
|
||||
proposal?: Proposal | null;
|
||||
}
|
||||
|
||||
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
|
||||
|
||||
+3
-3
@@ -17,8 +17,8 @@ import {
|
||||
lastMonth,
|
||||
nextMonth,
|
||||
} from '../../test-helpers/mocks';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const openProposalClosesNextMonth = generateProposal({
|
||||
id: 'proposal1',
|
||||
@@ -63,7 +63,7 @@ const closedProtocolUpgradeProposal = generateProtocolUpgradeProposal({
|
||||
});
|
||||
|
||||
const renderComponent = (
|
||||
proposals: ProposalQuery['proposal'][],
|
||||
proposals: Proposal[],
|
||||
protocolUpgradeProposals?: ProtocolUpgradeProposalFieldsFragment[]
|
||||
) => (
|
||||
<Router>
|
||||
|
||||
@@ -10,20 +10,20 @@ import Routes from '../../../routes';
|
||||
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { type ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalQuery['proposal']>;
|
||||
proposals: Proposal[];
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
lastBlockHeight?: string;
|
||||
}
|
||||
|
||||
interface SortedProposalsProps {
|
||||
open: ProposalQuery['proposal'][];
|
||||
closed: ProposalQuery['proposal'][];
|
||||
open: Proposal[];
|
||||
closed: Proposal[];
|
||||
}
|
||||
|
||||
interface SortedProtocolUpgradeProposalsProps {
|
||||
@@ -31,7 +31,7 @@ interface SortedProtocolUpgradeProposalsProps {
|
||||
closed: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}
|
||||
|
||||
export const orderByDate = (arr: ProposalQuery['proposal'][]) =>
|
||||
export const orderByDate = (arr: Proposal[]) =>
|
||||
orderBy(
|
||||
arr,
|
||||
[
|
||||
@@ -91,14 +91,10 @@ export const ProposalsList = ({
|
||||
);
|
||||
return {
|
||||
open:
|
||||
initialSorting.open.length > 0
|
||||
? orderByDate(initialSorting.open as ProposalQuery['proposal'][])
|
||||
: [],
|
||||
initialSorting.open.length > 0 ? orderByDate(initialSorting.open) : [],
|
||||
closed:
|
||||
initialSorting.closed.length > 0
|
||||
? orderByDate(
|
||||
initialSorting.closed as ProposalQuery['proposal'][]
|
||||
).reverse()
|
||||
? orderByDate(initialSorting.closed).reverse()
|
||||
: [],
|
||||
};
|
||||
}, [proposals]);
|
||||
@@ -125,9 +121,7 @@ export const ProposalsList = ({
|
||||
};
|
||||
}, [protocolUpgradeProposals, lastBlockHeight]);
|
||||
|
||||
const filterPredicate = (
|
||||
p: ProposalFieldsFragment | ProposalQuery['proposal']
|
||||
) =>
|
||||
const filterPredicate = (p: ProposalFieldsFragment | Proposal) =>
|
||||
p?.id?.includes(filterString) ||
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import {
|
||||
nextWeek,
|
||||
lastMonth,
|
||||
} from '../../test-helpers/mocks';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const rejectedProposalClosesNextWeek = generateProposal({
|
||||
id: 'rejected1',
|
||||
@@ -35,7 +35,7 @@ const rejectedProposalClosedLastMonth = generateProposal({
|
||||
},
|
||||
});
|
||||
|
||||
const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
|
||||
const renderComponent = (proposals: Proposal[]) => (
|
||||
<Router>
|
||||
<MockedProvider mocks={[networkParamsQueryMock]}>
|
||||
<AppStateProvider>
|
||||
|
||||
+3
-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 { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: ProposalQuery['proposal'][];
|
||||
proposals: Proposal[];
|
||||
}
|
||||
|
||||
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [filterString, setFilterString] = useState('');
|
||||
|
||||
const filterPredicate = (p: ProposalQuery['proposal']) =>
|
||||
const filterPredicate = (p: Proposal) =>
|
||||
p?.id?.includes(filterString) ||
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
|
||||
+6
-6
@@ -9,8 +9,7 @@ import {
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { CompactVotes, VoteBreakdown } from './vote-breakdown';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { type MockedResponse } from '@apollo/client/testing';
|
||||
import {
|
||||
generateNoVotes,
|
||||
generateProposal,
|
||||
@@ -18,7 +17,8 @@ import {
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { type AppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
const mockTotalSupply = new BigNumber(100);
|
||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||
@@ -41,7 +41,7 @@ jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
}));
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
proposal: Proposal,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||
) =>
|
||||
@@ -281,8 +281,8 @@ describe('VoteBreakdown', () => {
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP majority', () => {
|
||||
const yesVotesLP = 800;
|
||||
const noVotesLP = 200;
|
||||
const yesVotesLP = 0.8;
|
||||
const noVotesLP = 0.2;
|
||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||
|
||||
renderComponent(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -5,10 +6,8 @@ import { useVoteInformation } from '../../hooks';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { CompactNumber } from '@vegaprotocol/react-helpers';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
<CompactNumber
|
||||
@@ -20,7 +19,7 @@ export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
);
|
||||
|
||||
interface VoteBreakdownProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}
|
||||
|
||||
interface VoteProgressProps {
|
||||
@@ -105,8 +104,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
noEquityLikeShareWeight,
|
||||
totalEquityLikeShareWeight,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
@@ -135,6 +132,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
.multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
);
|
||||
|
||||
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
@@ -202,50 +200,24 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
yesEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={yesEquityLikeShareWeight} />
|
||||
</button>
|
||||
<button>{yesLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{yesLPPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
noEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={noEquityLikeShareWeight} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{noLPPercentage.toFixed(0)}%</button>
|
||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,13 +254,8 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={totalEquityLikeShareWeight} />
|
||||
</button>
|
||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
||||
</Tooltip>
|
||||
<span>
|
||||
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -5,14 +5,13 @@ import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||
import { VoteButtonsContainer } from './vote-buttons';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { VoteState } from './use-user-vote';
|
||||
import { type VoteValue } from '@vegaprotocol/types';
|
||||
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type VoteState } from './use-user-vote';
|
||||
import { type Proposal } from '../../types';
|
||||
|
||||
interface UserVoteProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
minVoterBalance: string | null | undefined;
|
||||
spamProtectionMinTokens: string | null | undefined;
|
||||
transaction: VegaTxState | null;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
|
||||
interface VoteButtonsContainerProps {
|
||||
voteState: VoteState | null;
|
||||
@@ -42,8 +43,10 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||
<VoteButtons
|
||||
{...props}
|
||||
currentStakeAvailable={toBigNum(
|
||||
|
||||
@@ -3,13 +3,12 @@ import {
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
|
||||
import { type Proposal } from '../types';
|
||||
|
||||
export const useProposalNetworkParams = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
|
||||
@@ -54,8 +54,8 @@ describe('use-vote-information', () => {
|
||||
it('returns all required vote information', () => {
|
||||
const yesVotes = 40;
|
||||
const noVotes = 60;
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '70';
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.70';
|
||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
@@ -195,10 +195,10 @@ describe('use-vote-information', () => {
|
||||
});
|
||||
|
||||
it('correctly shows whether an update market proposal will pass by token or LP vote - both failing', () => {
|
||||
const yesVotes = 20;
|
||||
const noVotes = 70;
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '60';
|
||||
const yesVotes = 0.2;
|
||||
const noVotes = 0.7;
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.60';
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const proposal = generateProposal({
|
||||
|
||||
@@ -2,15 +2,10 @@ import { useMemo } from 'react';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { useProposalNetworkParams } from './use-proposal-network-params';
|
||||
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { type Proposal } from '../types';
|
||||
|
||||
export const useVoteInformation = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
export const useVoteInformation = ({ proposal }: { proposal: Proposal }) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
} = useAppState();
|
||||
@@ -61,7 +56,7 @@ export const useVoteInformation = ({
|
||||
const noEquityLikeShareWeight = !proposal?.votes.no
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight);
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(proposal?.votes.yes.totalTokens ?? 0, decimals)
|
||||
@@ -70,7 +65,7 @@ export const useVoteInformation = ({
|
||||
const yesEquityLikeShareWeight = !proposal?.votes.yes
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight);
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
@@ -81,12 +76,7 @@ export const useVoteInformation = ({
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
|
||||
const yesLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalEquityLikeShareWeight);
|
||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
||||
|
||||
const noPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
@@ -103,9 +93,7 @@ export const useVoteInformation = ({
|
||||
);
|
||||
|
||||
const participationLPMet = requiredParticipationLP
|
||||
? totalEquityLikeShareWeight.isGreaterThan(
|
||||
totalSupply.multipliedBy(requiredParticipationLP)
|
||||
)
|
||||
? totalEquityLikeShareWeight.isGreaterThan(requiredParticipationLP)
|
||||
: false;
|
||||
|
||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||
@@ -120,9 +108,7 @@ export const useVoteInformation = ({
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
||||
|
||||
const willPassByTokenVote =
|
||||
participationMet &&
|
||||
|
||||
@@ -86,230 +86,65 @@ query Proposal(
|
||||
$includeUpdateReferralProgram: Boolean!
|
||||
) {
|
||||
proposal(id: $proposalId) {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
reference
|
||||
state
|
||||
datetime
|
||||
rejectionReason
|
||||
party {
|
||||
... on Proposal {
|
||||
id
|
||||
}
|
||||
errorDetails
|
||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||
...UpdateVolumeDiscountProgram
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
... on NewMarket {
|
||||
decimalPlaces
|
||||
metadata
|
||||
riskParameters {
|
||||
... on LogNormalRiskModel {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
mu
|
||||
r
|
||||
sigma
|
||||
}
|
||||
}
|
||||
... on SimpleRiskModel {
|
||||
params {
|
||||
factorLong
|
||||
factorShort
|
||||
}
|
||||
}
|
||||
}
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
product {
|
||||
... on FutureProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
tradingTerminationProperty
|
||||
}
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
reference
|
||||
state
|
||||
datetime
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
errorDetails
|
||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||
...UpdateVolumeDiscountProgram
|
||||
terms {
|
||||
closingDatetime
|
||||
enactmentDatetime
|
||||
change {
|
||||
... on NewMarket {
|
||||
decimalPlaces
|
||||
metadata
|
||||
riskParameters {
|
||||
... on LogNormalRiskModel {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
mu
|
||||
r
|
||||
sigma
|
||||
}
|
||||
}
|
||||
... on PerpetualProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
... on SimpleRiskModel {
|
||||
params {
|
||||
factorLong
|
||||
factorShort
|
||||
}
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
priceMonitoringParameters {
|
||||
triggers {
|
||||
horizonSecs
|
||||
probability
|
||||
auctionExtensionSecs
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
triggeringRatio
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
}
|
||||
}
|
||||
positionDecimalPlaces
|
||||
linearSlippageFactor
|
||||
quadraticSlippageFactor
|
||||
}
|
||||
... on UpdateMarket {
|
||||
marketId
|
||||
updateMarketConfiguration {
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
product {
|
||||
... on UpdateFutureProduct {
|
||||
quoteName
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on FutureProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
# dataSourceSpecForTradingTermination {
|
||||
# sourceType {
|
||||
# ... on DataSourceDefinitionInternal {
|
||||
# sourceType {
|
||||
# ... on DataSourceSpecConfigurationTime {
|
||||
# conditions {
|
||||
# operator
|
||||
# value
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# ... on DataSourceDefinitionExternal {
|
||||
# sourceType {
|
||||
# ... on DataSourceSpecConfiguration {
|
||||
# signers {
|
||||
# signer {
|
||||
# ... on PubKey {
|
||||
# key
|
||||
# }
|
||||
# ... on ETHAddress {
|
||||
# address
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# filters {
|
||||
# key {
|
||||
# name
|
||||
# type
|
||||
# }
|
||||
# conditions {
|
||||
# operator
|
||||
# value
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
quoteName
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
tradingTerminationProperty
|
||||
}
|
||||
}
|
||||
... on UpdatePerpetualProduct {
|
||||
quoteName
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
@@ -350,14 +185,19 @@ query Proposal(
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
settlementScheduleProperty
|
||||
}
|
||||
... on PerpetualProduct {
|
||||
settlementAsset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
}
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata
|
||||
priceMonitoringParameters {
|
||||
triggers {
|
||||
horizonSecs
|
||||
@@ -366,77 +206,236 @@ query Proposal(
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
triggeringRatio
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
}
|
||||
}
|
||||
riskParameters {
|
||||
... on UpdateMarketSimpleRiskModel {
|
||||
simple {
|
||||
factorLong
|
||||
factorShort
|
||||
positionDecimalPlaces
|
||||
linearSlippageFactor
|
||||
}
|
||||
... on UpdateMarket {
|
||||
marketId
|
||||
updateMarketConfiguration {
|
||||
instrument {
|
||||
code
|
||||
product {
|
||||
... on UpdateFutureProduct {
|
||||
quoteName
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# dataSourceSpecForTradingTermination {
|
||||
# sourceType {
|
||||
# ... on DataSourceDefinitionInternal {
|
||||
# sourceType {
|
||||
# ... on DataSourceSpecConfigurationTime {
|
||||
# conditions {
|
||||
# operator
|
||||
# value
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# ... on DataSourceDefinitionExternal {
|
||||
# sourceType {
|
||||
# ... on DataSourceSpecConfiguration {
|
||||
# signers {
|
||||
# signer {
|
||||
# ... on PubKey {
|
||||
# key
|
||||
# }
|
||||
# ... on ETHAddress {
|
||||
# address
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# filters {
|
||||
# key {
|
||||
# name
|
||||
# type
|
||||
# }
|
||||
# conditions {
|
||||
# operator
|
||||
# value
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
tradingTerminationProperty
|
||||
}
|
||||
}
|
||||
... on UpdatePerpetualProduct {
|
||||
quoteName
|
||||
dataSourceSpecForSettlementData {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
operator
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSourceSpecBinding {
|
||||
settlementDataProperty
|
||||
settlementScheduleProperty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on UpdateMarketLogNormalRiskModel {
|
||||
logNormal {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
r
|
||||
sigma
|
||||
mu
|
||||
metadata
|
||||
priceMonitoringParameters {
|
||||
triggers {
|
||||
horizonSecs
|
||||
probability
|
||||
auctionExtensionSecs
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
}
|
||||
}
|
||||
riskParameters {
|
||||
... on UpdateMarketSimpleRiskModel {
|
||||
simple {
|
||||
factorLong
|
||||
factorShort
|
||||
}
|
||||
}
|
||||
... on UpdateMarketLogNormalRiskModel {
|
||||
logNormal {
|
||||
riskAversionParameter
|
||||
tau
|
||||
params {
|
||||
r
|
||||
sigma
|
||||
mu
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on NewAsset {
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
source {
|
||||
... on BuiltinAsset {
|
||||
maxFaucetAmountMint
|
||||
}
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
... on NewAsset {
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
source {
|
||||
... on BuiltinAsset {
|
||||
maxFaucetAmountMint
|
||||
}
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on UpdateNetworkParameter {
|
||||
networkParameter {
|
||||
key
|
||||
value
|
||||
... on UpdateNetworkParameter {
|
||||
networkParameter {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
... on UpdateAsset {
|
||||
quantum
|
||||
assetId
|
||||
source {
|
||||
... on UpdateERC20 {
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
... on UpdateAsset {
|
||||
quantum
|
||||
assetId
|
||||
source {
|
||||
... on UpdateERC20 {
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
votes {
|
||||
yes {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
no {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
votes {
|
||||
yes {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
no {
|
||||
totalTokens
|
||||
totalNumber
|
||||
totalEquityLikeShareWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+226
-227
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