Compare commits

..
Author SHA1 Message Date
Matthew Russell 02597faeba feat: use new limit param for number formatting 2024-01-13 18:45:49 -05:00
Matthew Russell b216312a3e feat: update formatNumberRounded to accept a limit 2024-01-13 18:45:49 -05:00
Matthew Russell ab04c68e1b feat: add formatting for stat values 2024-01-13 18:45:49 -05:00
Matthew Russell 6049cb1dc7 chore: use built in team creation methods 2024-01-13 18:45:49 -05:00
Matthew Russell 9a04045d32 feat: responsive adjustments 2024-01-13 18:45:49 -05:00
Matthew Russell fa99dfde79 fix: missing i18n 2024-01-13 18:45:49 -05:00
Matthew Russell 8b826bce45 fix: use correct bg color for gradient 2024-01-13 18:45:49 -05:00
Matthew Russell 0f5d28c32a fix: incorrect member count 2024-01-13 18:45:49 -05:00
Matthew Russell 204adb4910 feat: add i18n 2024-01-13 18:45:49 -05:00
Matthew Russell 38edc24e02 feat: add favorite game and last 5 logic 2024-01-13 18:45:49 -05:00
Matthew Russell c476706cdf feat: add games list data 2024-01-13 18:45:48 -05:00
Matthew Russell f523366a64 feat: wire up games played 2024-01-13 18:44:55 -05:00
Matthew Russell 07285973d1 feat: wire up data, remove team pnl, improve key creation 2024-01-13 18:44:55 -05:00
Matthew Russell 72611fbb31 feat: add members table 2024-01-13 18:44:55 -05:00
Matthew Russell dc99dd592d feat: add members table 2024-01-13 18:44:55 -05:00
Matthew Russell be5d98c011 test: add team creation in sim 2024-01-13 18:44:55 -05:00
Matthew Russell 88e45271b1 feat: wire up avatar 2024-01-13 18:44:55 -05:00
Matthew Russell bec78a4c1a feat: add teams query 2024-01-13 18:44:54 -05:00
Matthew Russell b89b901f71 feat: richer cell content 2024-01-13 18:43:45 -05:00
Matthew Russell a5eb460d98 feat: add trading table component 2024-01-13 18:43:45 -05:00
Matthew Russell da43624478 feat: add favorite game and fix bg cover 2024-01-13 18:43:45 -05:00
Matthew Russell 6e6207e7f9 feat: update path to be nested under competitions 2024-01-13 18:43:45 -05:00
Matthew Russell 3957c2d911 feat: add cover bg and tooltips for stats 2024-01-13 18:43:45 -05:00
Matthew Russell 11010525df feat: add joined state 2024-01-13 18:43:45 -05:00
Matthew Russell c1d3d83045 feat: add basic table 2024-01-13 18:43:45 -05:00
Matthew Russell 9c7adb812c feat: add route and initial page layout 2024-01-13 18:43:45 -05:00
Matthew Russell 96cbbfc3a0 chore: tidy handling of proposal union type 2024-01-13 18:38:01 -05:00
Matthew Russell cb80d6c20b chore: handle proposal union type 2024-01-13 17:43:14 -05:00
Matthew Russell b74a04ed94 chore: fix ts errors in trading app 2024-01-13 16:23:33 -05:00
Matthew Russell fca8c19898 chore: fix ts errors in candles-chart 2024-01-13 16:21:57 -05:00
Matthew Russell cfb7715124 chore: fix type errors in market lib 2024-01-13 15:41:14 -05:00
Matthew Russell 1004347b33 chore: fix type errors in proposals lib 2024-01-13 15:40:58 -05:00
Matthew Russell d14469f2f9 chore: fix global type mappings 2024-01-12 18:50:37 -05:00
Matthew Russell da7dc1309b chore: fix type issues 2024-01-12 18:42:46 -05:00
Matthew Russell e57bf9a207 chore: use extract to narrow type 2024-01-12 18:21:22 -05:00
Matthew Russell 44842169e7 chore: regen types 2024-01-12 10:42:09 -05:00
m.ray d32f27fcb1 feat(trading): mobile responsiveness - market selector (#5582) 2024-01-11 11:23:48 +00:00
m.ray d05cd6a2ed fix(trading): tiny scroll for rewards (#5586) 2024-01-10 15:49:58 +00:00
m.ray c003e5fa30 fix(trading): liquidity table improve readability and remove grouping (#5598) 2024-01-10 15:49:35 +00:00
Ben f62d3289ab chore(trading): retry on http error (#5601) 2024-01-10 15:44:01 +00:00
Ben a3d3d18c5c chore(trading): fix price monitoring test (#5600) 2024-01-10 14:56:17 +00:00
77ca101781 chore(trading): merge main back in develop (#5597)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-10 10:31:15 +00:00
Ben d238c37d0c chore(trading): vesting test (#5595) 2024-01-10 10:07:59 +00:00
Edd 79feb485f6 fix(governance): improve error message for configuration error (#5589) 2024-01-09 17:21:56 +00:00
Ben 6aa5c3b6e3 chore(trading): rewards page e2e (#5578) 2024-01-09 15:54:40 +00:00
Ben 82abc13fda chore(trading): skip test due to issue (#5583) 2024-01-08 16:16:03 +00:00
Edd 933f07cddf fix(explorer): minor tidyup of deterministic order view (#5573) 2024-01-08 13:25:03 +00:00
Matthew Russell c8067669c2 fix(trading): ensure fees queries always fetch on mount and poll (#5566) 2024-01-05 12:43:30 +00:00
78f5a9c520 feat(trading): activity streaks, reward hoarder bonus and active rewards (#5491)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: bwallacee <ben@vega.xyz>
2024-01-05 11:16:59 +00:00
Ben 462066959d chore(trading): update vega (#5563) 2024-01-03 12:00:00 +00:00
daro-maj f12f5ab961 chore(trading): fix volume issue for market selector tests (#5564) 2024-01-03 12:04:49 +01:00
98b2460d8b fix(trading): mobile friendly referral and fees views (#5530)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-02 15:50:04 +00:00
Matthew Russell 67c182f3de chore(trading-view): minor tv chart tweaks (#5545) 2024-01-02 13:20:01 +00:00
68e0009ab0 feat(trading,markets,proposals): market banners (#5494)
Co-authored-by: bwallacee <ben@vega.xyz>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-02 11:04:43 +00:00
Bartłomiej Głownia ad74b12908 feat(trading): do not show error message if there is no faucet tx error (#5539) 2024-01-02 10:19:10 +01:00
Matthew Russell 7fff33e493 feat(trading): competition feature flag and team page (#5549) 2023-12-29 15:50:51 +00:00
daro-maj e7cb2c8276 chore(trading): fix fees tests (#5548) 2023-12-29 15:25:33 +00:00
4adaeea40a feat(trading): pane settings view (#5509)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-28 12:33:18 +00:00
Matthew Russell d47d894147 fix(deal-ticket): stray curly in stop order deal ticket (#5547) 2023-12-28 12:27:56 +00:00
Bartłomiej Głownia 515932d401 fix(trading): use correct interpolation prefix in market info label (#5537) 2023-12-27 18:47:19 +00:00
daro-maj 723ff2805d chore(trading): check e2e tests issue (#5544) 2023-12-27 14:07:27 +01:00
Ben 424cced4be chore(trading): vegaservice import (#5536) 2023-12-27 08:17:20 +00:00
Daniel b2c7ee82c5 feat(ci): validators testnet deployment (#5541) 2023-12-22 18:05:31 +00:00
m.rayandDaniel 4cc63be4f9 chore(trading): update validator testnet pipelines (#5543)
Co-authored-by: Daniel <daniel.1302@gmail.com>
2023-12-22 18:03:45 +00:00
Jeremy Letang 9fbb7a13e6 Merge pull request #5526 from vegaprotocol/5409-funding-countdown
feat(trading): poll market info, and last fundingPeriod
2023-12-22 15:21:40 +01:00
Dariusz Majcherczyk 1874095222 feat: skip test 2023-12-22 14:36:38 +01:00
Dariusz Majcherczyk e459e92127 feat: update sim version 2023-12-22 13:58:20 +01:00
Bartłomiej Głownia 4dd65b4923 feat: rollback using market.data as a source of current fundingPeriod startTime 2023-12-22 12:01:32 +01:00
Bartłomiej Głownia 5d792d2458 feat: pool market info, use market data as source of current funding period startTime 2023-12-21 16:42:25 +00:00
Ben 59b2f75b13 chore(trading): update market-sim (#5535) 2023-12-21 13:51:34 +00:00
ArtandMatthew Russell 95775679ca fix(trading): reward pot total value (#5531)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-21 13:41:37 +00:00
m.ray 4cffee29f8 chore(trading): rollback current traded volume change (#5527) 2023-12-21 10:38:09 +00:00
Edd bd70b0c233 fix(governance): fix lp vote counts (#5521) 2023-12-21 09:30:23 +00:00
Ben 145792f216 chore(trading): static vega port trading e2e (#5520) 2023-12-20 15:49:10 +00:00
m.ray 10add7c236 fix(trading): update copy on tooltips fills update (#5512) 2023-12-20 08:12:35 +00:00
Edd 362a2031c7 fix(explorer): pass through tendermint error responses from usefetch (#5507) 2023-12-20 08:08:56 +00:00
Edd 5aaeb87059 feat(governance): enable snaps (#5511) 2023-12-19 18:38:49 +00:00
Matthew Russell d240dddab5 fix(trading): alpha lyrae font (#5518) 2023-12-19 16:52:56 +00:00
Ben 0af1d0ba82 chore(trading): fees e2e tests (#5496) 2023-12-19 12:22:15 +00:00
Bartłomiej Głownia 665ab6693a feat(trading): show commission earned on referrer stats from last 30 days (#5467) 2023-12-18 15:33:40 +01:00
Ben 902ee2696c chore(trading): black formatting and removed double fixtures (#5506) 2023-12-18 10:54:40 +00:00
Art 3beeb0140c feat(wallet): disconnect if wallet unreachable (#5501) 2023-12-18 10:39:18 +00:00
m.ray 4b208e90bf fix(trading): internal timestamp date unix formatting (#5505) 2023-12-14 15:10:17 +00:00
Bartłomiej Głownia ec837854cf chore(trading): avoid portfolio and market page re-rendering on sidebar view change (#5499) 2023-12-14 14:12:19 +00:00
Bartłomiej Głownia 8b94a75ba4 feat(positions): improve tooltips (#5481) 2023-12-13 14:36:57 +00:00
Ben 08c57b6759 chore(trading): run against local console (#5500) 2023-12-13 12:46:16 +00:00
Ben bb2184498f chore(trading): update vega version (#5498) 2023-12-13 11:16:52 +00:00
Bartłomiej Głownia 8b07ce3024 fix(trading): fix tabs covered by orderbook splash (#5497) 2023-12-13 09:10:54 +00:00
Bartłomiej Głownia 8a110584dd feat(trading): improve desposit and withraw asset selection (#5493) 2023-12-13 08:47:22 +00:00
Bartłomiej Głownia b82615a3d8 fix(trading): do not add candles if they are older than requested date range (#5484) 2023-12-13 08:06:02 +00:00
Matthew Russell f178b85846 feat(trading): trading view (#5348) 2023-12-12 17:33:41 -08:00
Bartłomiej Głownia 0796f2b31f feat(environment): users controlled feature flags (#5425) 2023-12-12 13:53:20 +01:00
dalebennett1992 67be224138 Merge pull request #5492 from vegaprotocol/test/referrals2
test: add referrals tests
2023-12-12 10:33:57 +00:00
dalebennett1992 51c426ef4b test: test ids 2023-12-12 10:16:47 +00:00
dalebennett1992 fa28d31ef3 test: add referrals tests 2023-12-12 10:15:20 +00:00
m.ray 201a586b05 chore(trading): enable close position (#5490) 2023-12-11 15:51:22 +00:00
m.ray 7d96d9bcd1 chore(trading): update env trading (#5478) 2023-12-08 15:44:55 +00:00
Art 345be81142 fix(trading): rewordings in referrals, qusd tooltip, onboarding (#5477) 2023-12-08 14:48:12 +00:00
Matthew Russell 2221e9faca fix(trading): referrals page final commission value and onboarding dialog (#5474) 2023-12-08 09:14:59 +00:00
Ben 7c0139e107 chore(trading): e2e test fix (#5466) 2023-12-07 11:12:56 +00:00
Witoldandbwallacee a157ff1e05 fix(trading): amend formula for max leverage calc (#5462)
Co-authored-by: bwallacee <ben@vega.xyz>
2023-12-06 09:58:59 -08:00
Art 2e9fa0c52e chore(trading): referrals text and ui tweaks, bug fixes (#5408) 2023-12-06 17:51:39 +00:00
Ben c0790c1e93 chore(trading): update market-sim (#5463) 2023-12-06 16:31:44 +00:00
Bartłomiej Głownia 13c1044f8e chore(i18n): sync json translation files with locize (#5444) 2023-12-06 14:58:17 +00:00
Matthew Russell cf9f313e4c feat(trading): error boundaries for panes, sidebars, pages (#5438) 2023-12-06 13:31:40 +00:00
Art f56d34fe6e fix(environment): node switcher on mobile (#5455) 2023-12-06 13:31:08 +00:00
m.rayandcandida-d 1379366caa chore(trading): update LP table tooltips (#5442)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
2023-12-06 13:30:22 +00:00
Art 8958c12fe3 fix(governance): markdown link styling, referral details type error, contracts provider error (#5460) 2023-12-06 14:18:44 +01:00
m.ray fef13874db chore(trading): update generic data provider (#5431) 2023-12-06 12:32:51 +00:00
Edd 40f02ecf89 feat(explorer): add type details to orders in tx list and details (#5432) 2023-12-06 10:45:35 +00:00
Bartłomiej Głownia 41cd6b1455 fix(trading): avoid funding LineChart recreate instance on FundingContainer re-render (#5454) 2023-12-06 10:37:44 +00:00
m.rayandMatthew Russell 3cd393dac0 fix(trading): rewards page updates (#5437)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-05 20:05:45 -08:00
Matthew Russell a52e60d6a2 Merge pull request #5453 from vegaprotocol/chore/sync-main
chore(trading): sync main
2023-12-05 12:35:31 -08:00
420 changed files with 14325 additions and 5816 deletions
+1
View File
@@ -77,6 +77,7 @@
"fixStyle": "inline-type-imports"
}
],
"@typescript-eslint/no-useless-constructor": 0,
"curly": ["error", "multi-line"]
}
},
+1
View File
@@ -1,3 +1,4 @@
* text eol=lf
*.png binary
*.ico binary
*.woff2 binary
+3
View File
@@ -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
View File
@@ -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
+6 -2
View File
@@ -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
@@ -24,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
}
}
}
}
@@ -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
}
}
}
}
@@ -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
);
@@ -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>
@@ -44,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
);
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
return (
<div className="inline-flex border rounded signature-component relative pr-[20px]">
<span
<div
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
title={`Version ${signature.version}`}
title={`${signature.algo}`}
>
{signature.algo}
</span>
<span>v{signature.version}</span>
</div>
<div
className={
isOpen
@@ -1,7 +1,9 @@
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
... on Proposal {
id
state
rejectionReason
}
}
}
@@ -8,15 +8,17 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
... on Proposal {
id
state
rejectionReason
}
}
}
`;
@@ -14,16 +14,18 @@ export function format(date: string | undefined, def: string) {
return new Date().toLocaleDateString() || def;
}
export function getDate(
data: ExplorerProposalStatusQuery | undefined,
terms: Terms
): string {
type Proposal = Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
const DEFAULT = t('Unknown');
if (!data?.proposal?.state) {
if (!proposal?.state) {
return DEFAULT;
}
switch (data.proposal.state) {
switch (proposal.state) {
case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED':
@@ -62,9 +64,11 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
},
});
const proposal = data?.proposal as Proposal;
return (
<Lozenge className="font-sans text-xs float-right">
{getDate(data, terms)}
{getDate(proposal, terms)}
</Lozenge>
);
};
@@ -2,17 +2,8 @@ import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import type * as Apollo from '@apollo/client';
import type * as Types from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
type ProposalQueryResult = Apollo.QueryResult<
ExplorerProposalStatusQuery,
Types.Exact<{
id: string;
}>
>;
interface ProposalStatusIconProps {
id: string;
}
@@ -29,29 +20,38 @@ type IconAndLabel = {
* @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name
*/
export function getIconAndLabelForStatus(
res: ProposalQueryResult
): IconAndLabel {
export function useIconAndLabelForStatus(id: string): IconAndLabel {
const { data, loading, error } = useExplorerProposalStatusQuery({
variables: {
id,
},
});
const proposal = data?.proposal as Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const DEFAULT: IconAndLabel = {
icon: 'error',
label: t('Proposal state unknown'),
};
if (res.loading) {
if (loading) {
return {
icon: 'more',
label: t('Loading data'),
};
}
if (!res?.data?.proposal || res.error) {
if (!data?.proposal || error) {
return {
icon: 'error',
label: res.error?.message || DEFAULT.label,
label: error?.message || DEFAULT.label,
};
}
switch (res.data.proposal.state) {
switch (proposal.state) {
case 'STATE_DECLINED':
return {
icon: 'stop',
@@ -99,13 +99,7 @@ export function getIconAndLabelForStatus(
/**
*/
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = getIconAndLabelForStatus(
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
const { icon, label } = useIconAndLabelForStatus(id);
return (
<div className="float-left mr-3">
@@ -80,6 +80,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';
}
@@ -54,7 +54,7 @@ const Block = () => {
</Button>
</Link>
</div>
{blockData && (
{blockData && 'result' in blockData && (
<>
<TableWithTbody className="mb-8">
<TableRow modifier="bordered">
+1 -1
View File
@@ -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>
+292 -289
View File
@@ -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;
};
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+17 -9
View File
@@ -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 />
@@ -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 =
+14 -9
View File
@@ -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]
);
};
+7 -6
View File
@@ -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,
},
});
@@ -1,16 +1,11 @@
import { useTranslation } from 'react-i18next';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalInfoLabel } from '../proposal-info-label';
import type { ReactNode } from 'react';
import type { ProposalInfoLabelVariant } from '../proposal-info-label';
import { type ReactNode } from 'react';
import { type ProposalInfoLabelVariant } from '../proposal-info-label';
import { type Proposal } from '../../types';
export const CurrentProposalState = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
export const CurrentProposalState = ({ proposal }: { proposal: Proposal }) => {
const { t } = useTranslation();
let proposalStatus: ReactNode;
let variant = 'tertiary' as ProposalInfoLabelVariant;
@@ -1,272 +0,0 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { CurrentProposalStatus } from './current-proposal-status';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: {
query: NetworkParamsDocument,
},
result: {
data: {
networkParametersConnection: {
edges: [
{
node: {
__typename: 'NetworkParameter',
key: 'governance.proposal.updateNetParam.requiredMajority',
value: '0.00000001',
},
},
{
node: {
__typename: 'NetworkParameter',
key: 'governance.proposal.updateNetParam.requiredParticipation',
value: '0.000000001',
},
},
],
},
},
},
};
const renderComponent = ({
proposal,
}: {
proposal: ProposalQuery['proposal'];
}) => {
render(
<AppStateProvider>
<MockedProvider mocks={[networkParamsQueryMock]}>
<CurrentProposalStatus proposal={proposal} />
</MockedProvider>
</AppStateProvider>
);
};
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(60 * 60 * 1000);
});
afterEach(() => {
jest.useRealTimers();
});
it('Proposal open - renders will fail state if the proposal will fail', async () => {
const failedProposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal: failedProposal });
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('fail.')).toBeInTheDocument();
});
it('Proposal open - renders will pass state if the proposal will pass', async () => {
const proposal = generateProposal();
renderComponent({ proposal });
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('pass.')).toBeInTheDocument();
});
it('Proposal enacted - renders vote passed and time since enactment', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
enactmentDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal passed - renders vote passed and time since vote closed', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(await screen.findByText('Vote passed.')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal waiting for node vote - will pass - renders if the vote will pass and status', async () => {
const failedProposal = generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal: failedProposal });
expect(
await screen.findByText('Waiting for nodes to validate asset.')
).toBeInTheDocument();
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('fail.')).toBeInTheDocument();
});
it('Proposal waiting for node vote - will fail - renders if the vote will pass and status', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
});
renderComponent({ proposal });
expect(
await screen.findByText('Waiting for nodes to validate asset.')
).toBeInTheDocument();
expect(await screen.findByText('Currently expected to')).toBeInTheDocument();
expect(await screen.findByText('pass.')).toBeInTheDocument();
});
it('Proposal failed - renders vote failed reason and vote closed ago', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
errorDetails: 'foo',
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('foo')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders rejection reason there are no error details', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
rejectionReason: ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(
await screen.findByText('PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE')
).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders unknown reason if there are no error details or rejection reason', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('unknown reason')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders participation not met if participation is not met', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('Participation not met')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
it('Proposal failed - renders majority not met if majority is not met', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_FAILED,
terms: {
closingDatetime: new Date(0).toISOString(),
},
votes: {
__typename: 'ProposalVotes',
yes: {
__typename: 'ProposalVoteSide',
totalNumber: '0',
totalTokens: '0',
totalEquityLikeShareWeight: '0',
},
no: {
__typename: 'ProposalVoteSide',
totalNumber: '1',
totalTokens: '25242474195500835440000',
totalEquityLikeShareWeight: '0',
},
},
});
renderComponent({ proposal });
expect(
await screen.findByText('Vote closed. Failed due to:')
).toBeInTheDocument();
expect(await screen.findByText('Majority not met')).toBeInTheDocument();
expect(await screen.findByText('about 1 hour ago')).toBeInTheDocument();
});
@@ -1,143 +0,0 @@
import type { ReactNode } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { ProposalState } from '@vegaprotocol/types';
import { useVoteInformation } from '../../hooks';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
export const StatusPass = ({ children }: { children: ReactNode }) => (
<span className="text-vega-green">{children}</span>
);
export const StatusFail = ({ children }: { children: ReactNode }) => (
<span className="text-danger">{children}</span>
);
const WillPass = ({
willPass,
children,
}: {
willPass: boolean;
children?: ReactNode;
}) => {
const { t } = useTranslation();
if (willPass) {
return (
<>
{children}
<StatusPass>{t('pass')}.</StatusPass>
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
</>
);
} else {
return (
<>
{children}
<StatusFail>{t('fail')}.</StatusFail>
<span className="ml-2">{t('finalOutcomeMayDiffer')}</span>
</>
);
}
};
export const CurrentProposalStatus = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { willPassByTokenVote, majorityMet, participationMet } =
useVoteInformation({
proposal,
});
const { t } = useTranslation();
const daysClosedAgo = formatDistanceToNow(
new Date(proposal?.terms.closingDatetime),
{ addSuffix: true }
);
const daysEnactedAgo =
proposal?.terms.enactmentDatetime &&
formatDistanceToNow(new Date(proposal.terms.enactmentDatetime), {
addSuffix: true,
});
if (proposal?.state === ProposalState.STATE_OPEN) {
return (
<WillPass willPass={willPassByTokenVote}>{t('currentlySetTo')}</WillPass>
);
}
if (
proposal?.state === ProposalState.STATE_FAILED ||
proposal?.state === ProposalState.STATE_DECLINED ||
proposal?.state === ProposalState.STATE_REJECTED
) {
if (!participationMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('participationNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
if (!majorityMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('majorityNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>
{proposal?.errorDetails ||
proposal?.rejectionReason ||
t('unknownReason')}
</StatusFail>
<span>&nbsp;{daysClosedAgo}</span>
</>
);
}
if (
proposal?.state === ProposalState.STATE_ENACTED ||
proposal?.state === ProposalState.STATE_PASSED
) {
return (
<>
<span>{t('votePassed')}</span>
<StatusPass>
&nbsp;
{proposal?.state === ProposalState.STATE_ENACTED
? t('Enacted')
: t('Passed')}
</StatusPass>
<span>
&nbsp;
{proposal?.state === ProposalState.STATE_ENACTED
? daysEnactedAgo
: daysClosedAgo}
</span>
</>
);
}
if (proposal?.state === ProposalState.STATE_WAITING_FOR_NODE_VOTE) {
return (
<WillPass willPass={willPassByTokenVote}>
<span>{t('WaitingForNodeVote')}</span>{' '}
<span>{t('currentlySetTo')}</span>
</WillPass>
);
}
return null;
};
@@ -1 +0,0 @@
export { CurrentProposalStatus } from './current-proposal-status';
@@ -6,11 +6,10 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalChangeTableProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
@@ -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',
@@ -131,6 +132,7 @@ describe('Proposal header', () => {
it('Renders New asset proposal - ERC20', () => {
renderComponent(
// @ts-ignore we aren't using batch yet
generateProposal({
rationale: {
title: 'New asset: Fake currency',
@@ -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: {
@@ -2,8 +2,7 @@ import { useTranslation } from 'react-i18next';
import { Lozenge, 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,22 +11,24 @@ import {
useNewTransferProposalDetails,
useSuccessorMarketProposalDetails,
} from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } 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;
@@ -39,7 +40,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 +55,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 +84,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 ? (
<>
@@ -177,14 +179,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;
@@ -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;
@@ -1,6 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import { useCancelTransferProposalDetails } from '@vegaprotocol/proposals';
import {
KeyValueTable,
@@ -8,11 +6,12 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { type Proposal } from '../../types';
export const ProposalCancelTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { t } = useTranslation();
const details = useCancelTransferProposalDetails(proposal?.id);
@@ -1,6 +1,4 @@
import { useState } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
@@ -21,11 +19,12 @@ import {
addDecimalsFormatNumberQuantum,
formatDateWithLocalTimezone,
} from '@vegaprotocol/utils';
import { type Proposal } from '../../types';
export const ProposalTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { t } = useTranslation();
const [show, setShow] = useState(false);
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -12,6 +11,7 @@ import {
} from '../proposal-referral-program-details';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { type Proposal } from '../../types';
// These types are not generated as it's not known how dynamic these are
type VestingBenefitTier = {
@@ -43,7 +43,7 @@ export const formatVolumeDiscountFactor = (value: string) => {
};
interface ProposalReferralProgramDetailsProps {
proposal: ProposalQuery['proposal'];
proposal: Proposal | null;
}
/**
@@ -5,13 +5,13 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { Row } from '@vegaprotocol/markets';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { useState } from 'react';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { type Proposal } from '../../types';
interface ProposalUpdateMarketStateProps {
proposal: ProposalQuery['proposal'];
proposal: Proposal | null;
}
export const ProposalUpdateMarketState = ({
@@ -1,5 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
KeyValueTable,
KeyValueTableRow,
@@ -12,9 +11,10 @@ import {
} from '../proposal-referral-program-details';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { type Proposal } from '../../types';
interface ProposalReferralProgramDetailsProps {
proposal: ProposalQuery['proposal'];
proposal: Proposal | null;
}
export const formatVolumeDiscountFactor = (value: string) => {
@@ -1,13 +1,13 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { mockNetworkParams } from '../../test-helpers/mocks';
import { type Proposal as IProposal } from '../../types';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -50,14 +50,14 @@ const vegaWalletConfig: VegaWalletConfig = {
},
};
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);
@@ -132,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 */
@@ -1,7 +1,7 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import type { MockedResponse } from '@apollo/client/testing';
import { type MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react';
import { format } from 'date-fns';
@@ -18,10 +18,10 @@ import {
lastWeek,
nextWeek,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
const renderComponent = (
proposal: ProposalQuery['proposal'],
proposal: Proposal,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -1,21 +1,20 @@
import { type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit';
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { ReactNode } from 'react';
import {
ProposalRejectionReasonMapping,
ProposalState,
} from '@vegaprotocol/types';
import Routes from '../../../routes';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
export const ProposalsListItemDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
proposal: Proposal;
}) => {
const { t } = useTranslation();
const state = proposal?.state;
@@ -2,10 +2,10 @@ import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { useUserVote } from '../vote-details/use-user-vote';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalsListItemProps {
proposal?: ProposalQuery['proposal'] | null;
proposal?: Proposal | null;
}
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
@@ -17,8 +17,8 @@ import {
lastMonth,
nextMonth,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../../types';
const openProposalClosesNextMonth = generateProposal({
id: 'proposal1',
@@ -63,7 +63,7 @@ const closedProtocolUpgradeProposal = generateProtocolUpgradeProposal({
});
const renderComponent = (
proposals: ProposalQuery['proposal'][],
proposals: Proposal,
protocolUpgradeProposals?: ProtocolUpgradeProposalFieldsFragment[]
) => (
<Router>
@@ -10,20 +10,20 @@ import Routes from '../../../routes';
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { ExternalLinks } from '@vegaprotocol/environment';
import { type ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../../types';
interface ProposalsListProps {
proposals: Array<ProposalQuery['proposal']>;
proposals: Proposal[];
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
lastBlockHeight?: string;
}
interface SortedProposalsProps {
open: ProposalQuery['proposal'][];
closed: ProposalQuery['proposal'][];
open: Proposal[];
closed: Proposal[];
}
interface SortedProtocolUpgradeProposalsProps {
@@ -31,7 +31,7 @@ interface SortedProtocolUpgradeProposalsProps {
closed: ProtocolUpgradeProposalFieldsFragment[];
}
export const orderByDate = (arr: ProposalQuery['proposal'][]) =>
export const orderByDate = (arr: Proposal[]) =>
orderBy(
arr,
[
@@ -91,14 +91,10 @@ export const ProposalsList = ({
);
return {
open:
initialSorting.open.length > 0
? orderByDate(initialSorting.open as ProposalQuery['proposal'][])
: [],
initialSorting.open.length > 0 ? orderByDate(initialSorting.open) : [],
closed:
initialSorting.closed.length > 0
? orderByDate(
initialSorting.closed as ProposalQuery['proposal'][]
).reverse()
? orderByDate(initialSorting.closed).reverse()
: [],
};
}, [proposals]);
@@ -125,9 +121,7 @@ export const ProposalsList = ({
};
}, [protocolUpgradeProposals, lastBlockHeight]);
const filterPredicate = (
p: ProposalFieldsFragment | ProposalQuery['proposal']
) =>
const filterPredicate = (p: ProposalFieldsFragment | Proposal) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -12,7 +12,7 @@ import {
nextWeek,
lastMonth,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
const rejectedProposalClosesNextWeek = generateProposal({
id: 'rejected1',
@@ -35,7 +35,7 @@ const rejectedProposalClosedLastMonth = generateProposal({
},
});
const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
const renderComponent = (proposals: Proposal[]) => (
<Router>
<MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider>
@@ -3,17 +3,17 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { type Proposal } from '../../types';
interface ProposalsListProps {
proposals: ProposalQuery['proposal'][];
proposals: Proposal[];
}
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const filterPredicate = (p: ProposalQuery['proposal']) =>
const filterPredicate = (p: Proposal) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -9,8 +9,7 @@ import {
nextWeek,
} from '../../test-helpers/mocks';
import { CompactVotes, VoteBreakdown } from './vote-breakdown';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { type MockedResponse } from '@apollo/client/testing';
import {
generateNoVotes,
generateProposal,
@@ -18,7 +17,8 @@ import {
} from '../../test-helpers/generate-proposals';
import { ProposalState } from '@vegaprotocol/types';
import { BigNumber } from '../../../../lib/bignumber';
import type { AppState } from '../../../../contexts/app-state/app-state-context';
import { type AppState } from '../../../../contexts/app-state/app-state-context';
import { type Proposal } from '../../types';
const mockTotalSupply = new BigNumber(100);
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
@@ -41,7 +41,7 @@ jest.mock('../../../../contexts/app-state/app-state-context', () => ({
}));
const renderComponent = (
proposal: ProposalQuery['proposal'],
proposal: Proposal,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
@@ -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;
@@ -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
@@ -372,71 +212,233 @@ query Proposal(
scalingFactor
}
}
riskParameters {
... on UpdateMarketSimpleRiskModel {
simple {
factorLong
factorShort
positionDecimalPlaces
linearSlippageFactor
quadraticSlippageFactor
}
... 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 {
triggeringRatio
targetStakeParameters {
timeWindow
scalingFactor
}
}
riskParameters {
... on UpdateMarketSimpleRiskModel {
simple {
factorLong
factorShort
}
}
... on UpdateMarketLogNormalRiskModel {
logNormal {
riskAversionParameter
tau
params {
r
sigma
mu
}
}
}
}
}
}
}
... on NewAsset {
name
symbol
decimals
quantum
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
... on NewAsset {
name
symbol
decimals
quantum
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
}
}
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
... on UpdateNetworkParameter {
networkParameter {
key
value
}
}
}
... on UpdateAsset {
quantum
assetId
source {
... on UpdateERC20 {
lifetimeLimit
withdrawThreshold
... on UpdateAsset {
quantum
assetId
source {
... on UpdateERC20 {
lifetimeLimit
withdrawThreshold
}
}
}
}
}
}
votes {
yes {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
no {
totalTokens
totalNumber
totalEquityLikeShareWeight
votes {
yes {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
no {
totalTokens
totalNumber
totalEquityLikeShareWeight
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -15,10 +15,12 @@ import {
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
import { type Proposal as IProposal } from '../types';
export const ProposalContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const [
mostRecentlyEnactedAssociatedMarketProposal,
setMostRecentlyEnactedAssociatedMarketProposal,
@@ -59,13 +61,15 @@ export const ProposalContainer = () => {
errorPolicy: 'ignore',
variables: {
proposalId: params.proposalId || '',
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
includeNewMarketProductField: !!featureFlags.PRODUCT_PERPETUALS,
includeUpdateMarketState: !!featureFlags.UPDATE_MARKET_STATE,
includeUpdateReferralProgram: !!featureFlags.REFERRALS,
},
skip: !params.proposalId,
});
const proposal = data?.proposal as IProposal;
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
@@ -78,12 +82,12 @@ export const ProposalContainer = () => {
},
} = useFetch(
`${ENV.rest}governance?proposalId=${
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
data?.proposal.terms.change.marketId
proposal?.terms.change.__typename === 'UpdateMarket' &&
proposal.terms.change.marketId
}`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -96,7 +100,7 @@ export const ProposalContainer = () => {
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
@@ -107,8 +111,8 @@ export const ProposalContainer = () => {
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: data?.proposal?.id || '',
skip: !data?.proposal?.id,
marketId: proposal?.id || '',
skip: !proposal?.id,
},
});
@@ -120,7 +124,7 @@ export const ProposalContainer = () => {
variables: {
marketId: marketData?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
skip: !featureFlags.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
});
const {
@@ -133,7 +137,7 @@ export const ProposalContainer = () => {
variables: {
marketId: parentMarketId?.market?.parentMarketID || '',
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!featureFlags.SUCCESSOR_MARKETS ||
!isSuccessor ||
!parentMarketId?.market?.parentMarketID,
},
@@ -147,23 +151,22 @@ export const ProposalContainer = () => {
fetchPolicy: 'network-only',
variables: {
assetId:
(data?.proposal?.terms.change.__typename === 'NewAsset' &&
data?.proposal?.id) ||
(data?.proposal?.terms.change.__typename === 'UpdateAsset' &&
data.proposal.terms.change.assetId) ||
(proposal?.terms.change.__typename === 'NewAsset' && proposal?.id) ||
(proposal?.terms.change.__typename === 'UpdateAsset' &&
proposal.terms.change.assetId) ||
'',
},
skip: !['NewAsset', 'UpdateAsset'].includes(
data?.proposal?.terms?.change?.__typename || ''
proposal?.terms?.change?.__typename || ''
),
});
useEffect(() => {
if (
previouslyEnactedMarketProposalsRestData &&
data?.proposal?.terms.change.__typename === 'UpdateMarket'
proposal?.terms.change.__typename === 'UpdateMarket'
) {
const change = data?.proposal?.terms?.change as { marketId: string };
const change = proposal?.terms?.change as { marketId: string };
const filteredProposals =
// @ts-ignore rest data is not typed
@@ -187,8 +190,8 @@ export const ProposalContainer = () => {
}, [
previouslyEnactedMarketProposalsRestData,
params.proposalId,
data?.proposal?.terms.change.__typename,
data?.proposal?.terms.change,
proposal?.terms.change.__typename,
proposal?.terms.change,
]);
useEffect(() => {
@@ -241,7 +244,7 @@ export const ProposalContainer = () => {
>
{data?.proposal ? (
<Proposal
proposal={data.proposal}
proposal={proposal}
networkParams={networkParams}
restData={restData}
marketData={marketData}
@@ -17,7 +17,7 @@ import {
} from './__generated__/Proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
return flow([
@@ -43,15 +43,16 @@ export function getNotRejectedProtocolUpgradeProposals<
}
export const ProposalsContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { data, loading, error } = useProposalsQuery({
pollInterval: 5000,
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,
},
});
@@ -95,7 +95,7 @@ export const ProtocolUpgradeProposalContainer = () => {
time={
pending && time ? (
convertToCountdownString(time, '0:00:00:00')
) : blockInfo?.result ? (
) : blockInfo && 'result' in blockInfo && blockInfo?.result ? (
<span title={blockInfo.result.block.header.time}>
{formatDateWithLocalTimezone(
new Date(blockInfo.result.block.header.time)
@@ -10,7 +10,7 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import { ProposalState } from '@vegaprotocol/types';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
@@ -33,15 +33,16 @@ export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
}
export const RejectedProposalsContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { data, loading, error } = useProposalsQuery({
pollInterval: 5000,
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,
},
});
@@ -8,6 +8,7 @@ import mergeWith from 'lodash/mergeWith';
import { type PartialDeep } from 'type-fest';
import { type ProposalQuery } from '../proposal/__generated__/Proposal';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { type Proposal } from '../types';
export function generateProtocolUpgradeProposal(
override: PartialDeep<ProtocolUpgradeProposalFieldsFragment> = {}
@@ -43,8 +44,8 @@ export function generateProtocolUpgradeProposal(
}
export function generateProposal(
override: PartialDeep<ProposalQuery['proposal']> = {}
): ProposalQuery['proposal'] {
override: PartialDeep<Proposal> = {}
): Proposal {
const defaultProposal: ProposalQuery['proposal'] = {
__typename: 'Proposal',
id: faker.datatype.uuid(),
@@ -92,15 +93,16 @@ export function generateProposal(
},
};
return mergeWith<
ProposalQuery['proposal'],
PartialDeep<ProposalQuery['proposal']>
>(defaultProposal, override, (objValue, srcValue) => {
if (!isArray(objValue)) {
return;
return mergeWith<Proposal, PartialDeep<Proposal>>(
defaultProposal,
override,
(objValue, srcValue) => {
if (!isArray(objValue)) {
return;
}
return srcValue;
}
return srcValue;
});
);
}
type Vote = Pick<Schema.Vote, '__typename' | 'value' | 'party' | 'datetime'>;
@@ -116,7 +118,8 @@ export const generateYesVotes = (
fixedTokenValue?: number,
totalEquityLikeShareWeight?: string
): Votes => {
const votes = Array.from(Array(numberOfVotes)).map(() => {
const votes = [];
for (let i = 0; i < numberOfVotes; i++) {
const vote: Vote = {
__typename: 'Vote',
value: Schema.VoteValue.VALUE_YES,
@@ -152,8 +155,9 @@ export const generateYesVotes = (
datetime: faker.date.past().toISOString(),
};
return vote;
});
votes.push(vote);
}
return {
__typename: 'ProposalVoteSide',
totalNumber: votes.length.toString(),
@@ -172,7 +176,8 @@ export const generateNoVotes = (
fixedTokenValue?: number,
totalEquityLikeShareWeight?: string
): Votes => {
const votes = Array.from(Array(numberOfVotes)).map(() => {
const votes = [];
for (let i = 0; i < numberOfVotes; i++) {
const vote: Vote = {
__typename: 'Vote',
value: Schema.VoteValue.VALUE_NO,
@@ -207,8 +212,9 @@ export const generateNoVotes = (
},
datetime: faker.date.past().toISOString(),
};
return vote;
});
votes.push(vote);
}
return {
__typename: 'ProposalVoteSide',
totalNumber: votes.length.toString(),
@@ -0,0 +1,11 @@
import type { ProposalQuery } from './proposal/__generated__/Proposal';
/**
* The default Proposal type needs extracting from the ProposalNode union type
* as lots of fields on the original type don't exist on BatchProposal. Eventually
* we will support BatchProposal but for now we don't
*/
export type Proposal = Extract<
ProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
+4
View File
@@ -104,6 +104,10 @@
list-style: circle;
}
.react-markdown-container a {
text-decoration: underline;
}
.jsondiffpatch-delta,
.jsondiffpatch-delta pre {
font-family: 'Roboto Mono', monospace !important;
+1
View File
@@ -25,3 +25,4 @@ NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TEAM_COMPETITION=true
+2 -3
View File
@@ -22,10 +22,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=false
NX_REFERRALS=true
# NX_DISABLE_CLOSE_POSITION=false
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_DISABLE_CLOSE_POSITION=true
+4 -1
View File
@@ -24,4 +24,7 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_REFERRALS=true
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+3 -1
View File
@@ -23,9 +23,11 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+2
View File
@@ -54,3 +54,5 @@ To run the UI automation tests with a mocked API, run:
```bash
yarn nx run trading-e2e:e2e
```
To run tests with market sim please read [the readme](e2e/README.md).
+11 -6
View File
@@ -1,8 +1,9 @@
import { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { ErrorBoundary } from '../../components/error-boundary';
import { FeesContainer } from '../../components/fees-container';
import { useT } from '../../lib/use-t';
import { usePageTitleStore } from '../../stores';
import { titlefy } from '@vegaprotocol/utils';
import { useEffect } from 'react';
export const Fees = () => {
const t = useT();
@@ -10,13 +11,17 @@ export const Fees = () => {
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
useEffect(() => {
updateTitle(titlefy([title]));
}, [updateTitle, title]);
return (
<div className="container p-4 mx-auto">
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
<FeesContainer />
</div>
<ErrorBoundary feature="fees">
<div className="container p-4 mx-auto">
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
<FeesContainer />
</div>
</ErrorBoundary>
);
};
@@ -6,6 +6,7 @@ import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { LiquidityContainer } from '../../components/liquidity-container';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
const enum LiquidityTabs {
Active = 'active',
@@ -58,19 +59,28 @@ export const LiquidityViewContainer = ({
name={t('My liquidity provision')}
hidden={!pubKey}
>
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
<ErrorBoundary feature="liquidity-party">
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
</ErrorBoundary>
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
<ErrorBoundary feature="liquidity-active">
<LiquidityContainer
marketId={marketId}
filter={{ active: true }}
/>
</ErrorBoundary>
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityContainer
marketId={marketId}
filter={{ active: false }}
/>
<ErrorBoundary feature="liquidity-inactive">
<LiquidityContainer
marketId={marketId}
filter={{ active: false }}
/>
</ErrorBoundary>
</Tab>
</Tabs>
</div>
@@ -188,12 +188,11 @@ const useNow = () => {
return now;
};
const useEvery = (marketId: string) => {
const { data: marketTradingMode } = useMarketTradingMode(marketId);
const useEvery = (marketId: string, skip: boolean) => {
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
skip,
});
let every: number | undefined = undefined;
const sourceType =
@@ -211,8 +210,10 @@ const useEvery = (marketId: string) => {
return every;
};
const useStartTime = (marketId: string) => {
const useStartTime = (marketId: string, skip: boolean) => {
const { data: fundingPeriods } = useFundingPeriodsQuery({
pollInterval: 5000,
skip,
variables: {
marketId: marketId,
pagination: { first: 1 },
@@ -246,8 +247,10 @@ const useFormatCountdown = (
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
const now = useNow();
const startTime = useStartTime(marketId);
const every = useEvery(marketId);
const { data: marketTradingMode } = useMarketTradingMode(marketId);
const skip = !marketTradingMode || isMarketInAuction(marketTradingMode);
const startTime = useStartTime(marketId, skip);
const every = useEvery(marketId, skip);
return (
<div data-testid="funding-countdown">
+9 -28
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
@@ -9,8 +9,6 @@ import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links } from '../../lib/links';
import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next';
@@ -61,9 +59,6 @@ export const MarketPage = () => {
const t = useT();
const { marketId } = useParams();
const navigate = useNavigate();
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update);
@@ -72,31 +67,11 @@ export const MarketPage = () => {
const { data, loading } = useMarket(marketId);
useEffect(() => {
if (data?.id && data.id !== lastMarketId && !closed) {
if (data?.id && data.id !== lastMarketId) {
update({ marketId: data.id });
}
}, [update, lastMarketId, data?.id]);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews(
{ type: closed ? ViewType.Info : ViewType.Order },
currentRouteId
);
}
}, [setViews, view, currentRouteId, largeScreen]);
const pinnedAsset = data && getAsset(data);
const tradeView = useMemo(() => {
if (pinnedAsset) {
if (largeScreen) {
return <TradeGrid market={data} pinnedAsset={pinnedAsset} />;
}
return <TradePanels market={data} pinnedAsset={pinnedAsset} />;
}
}, [largeScreen, data, pinnedAsset]);
if (loading) {
return (
<Splash>
@@ -132,6 +107,8 @@ export const MarketPage = () => {
);
}
const pinnedAsset = data && getAsset(data);
return (
<>
<TitleUpdater
@@ -139,7 +116,11 @@ export const MarketPage = () => {
marketName={data?.tradableInstrument.instrument.name}
decimalPlaces={data?.decimalPlaces}
/>
{tradeView}
{largeScreen ? (
<TradeGrid market={data} pinnedAsset={pinnedAsset} />
) : (
<TradePanels market={data} pinnedAsset={pinnedAsset} />
)}
</>
);
};
+94 -54
View File
@@ -1,11 +1,10 @@
import { memo } from 'react';
import type { ReactNode } from 'react';
import { memo, type ReactNode } from 'react';
import { LayoutPriority } from 'allotment';
import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { OracleBanner, useMarket } from '@vegaprotocol/markets';
import type { Market } from '@vegaprotocol/markets';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { type PinnedAsset } from '@vegaprotocol/accounts';
import { type Market } from '@vegaprotocol/markets';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import {
ResizableGrid,
@@ -13,29 +12,19 @@ import {
usePaneLayout,
} from '../../components/resizable-grid';
import { TradingViews } from './trade-views';
import {
MarketSuccessorBanner,
MarketSuccessorProposalBanner,
MarketTerminationBanner,
} from '../../components/market-banner';
import { FLAGS } from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
import { MarketBanner } from '../../components/market-banner';
interface TradeGridProps {
market: Market | null;
pinnedAsset?: PinnedAsset;
market: Market;
pinnedAsset?: PinnedAsset | undefined;
}
const MainGrid = memo(
({
marketId,
pinnedAsset,
}: {
marketId: string;
pinnedAsset?: PinnedAsset;
}) => {
({ market, pinnedAsset }: { market: Market; pinnedAsset?: PinnedAsset }) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT();
const { data: market } = useMarket(marketId);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle-1',
@@ -60,30 +49,44 @@ const MainGrid = memo(
id="chart"
overflowHidden
name={t('Chart')}
menu={<TradingViews.candles.menu />}
menu={<TradingViews.chart.menu />}
>
<TradingViews.candles.component marketId={marketId} />
<ErrorBoundary feature="chart">
<TradingViews.chart.component marketId={market.id} />
</ErrorBoundary>
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.depth.component marketId={marketId} />
<ErrorBoundary feature="depth">
<TradingViews.depth.component marketId={market.id} />
</ErrorBoundary>
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.liquidity.component marketId={marketId} />
<ErrorBoundary feature="liquidity">
<TradingViews.liquidity.component marketId={market.id} />
</ErrorBoundary>
</Tab>
{market &&
market.tradableInstrument.instrument.product.__typename ===
'Perpetual' ? (
<Tab id="funding-history" name={t('Funding history')}>
<TradingViews.funding.component marketId={marketId} />
<ErrorBoundary feature="funding-history">
<TradingViews.funding.component marketId={market.id} />
</ErrorBoundary>
</Tab>
) : null}
{market &&
market.tradableInstrument.instrument.product.__typename ===
'Perpetual' ? (
<Tab id="funding-payments" name={t('Funding payments')}>
<TradingViews.fundingPayments.component
marketId={marketId}
/>
<Tab
id="funding-payments"
name={t('Funding payments')}
settings={<TradingViews.fundingPayments.settings />}
>
<ErrorBoundary feature="funding-payments">
<TradingViews.fundingPayments.component
marketId={market.id}
/>
</ErrorBoundary>
</Tab>
) : null}
</Tabs>
@@ -96,10 +99,18 @@ const MainGrid = memo(
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-right">
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.orderbook.component marketId={marketId} />
<ErrorBoundary feature="orderbook">
<TradingViews.orderbook.component marketId={market.id} />
</ErrorBoundary>
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.trades.component marketId={marketId} />
<Tab
id="trades"
name={t('Trades')}
settings={<TradingViews.trades.settings />}
>
<ErrorBoundary feature="trades">
<TradingViews.trades.component marketId={market.id} />
</ErrorBoundary>
</Tab>
</Tabs>
</TradeGridChild>
@@ -117,43 +128,79 @@ const MainGrid = memo(
id="positions"
name={t('Positions')}
menu={<TradingViews.positions.menu />}
settings={<TradingViews.positions.settings />}
>
<TradingViews.positions.component />
<ErrorBoundary feature="positions">
<TradingViews.positions.component />
</ErrorBoundary>
</Tab>
<Tab
id="open-orders"
name={t('Open')}
menu={<TradingViews.activeOrders.menu />}
settings={<TradingViews.activeOrders.settings />}
>
<TradingViews.activeOrders.component />
<ErrorBoundary feature="activeOrders">
<TradingViews.activeOrders.component />
</ErrorBoundary>
</Tab>
<Tab id="closed-orders" name={t('Closed')}>
<TradingViews.closedOrders.component />
<Tab
id="closed-orders"
name={t('Closed')}
settings={<TradingViews.closedOrders.settings />}
>
<ErrorBoundary feature="closedOrders">
<TradingViews.closedOrders.component />
</ErrorBoundary>
</Tab>
<Tab id="rejected-orders" name={t('Rejected')}>
<TradingViews.rejectedOrders.component />
<Tab
id="rejected-orders"
name={t('Rejected')}
settings={<TradingViews.rejectedOrders.settings />}
>
<ErrorBoundary feature="rejectedOrders">
<TradingViews.rejectedOrders.component />
</ErrorBoundary>
</Tab>
<Tab
id="orders"
name={t('All')}
menu={<TradingViews.orders.menu />}
settings={<TradingViews.orders.settings />}
>
<TradingViews.orders.component />
<ErrorBoundary feature="orders">
<TradingViews.orders.component />
</ErrorBoundary>
</Tab>
{FLAGS.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}>
<TradingViews.stopOrders.component />
{featureFlags.STOP_ORDERS ? (
<Tab
id="stop-orders"
name={t('Stop orders')}
settings={<TradingViews.stopOrders.settings />}
>
<ErrorBoundary feature="stop-orders">
<TradingViews.stopOrders.component />
</ErrorBoundary>
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<Tab
id="fills"
name={t('Fills')}
settings={<TradingViews.fills.settings />}
>
<TradingViews.fills.component />
</Tab>
<Tab
id="accounts"
name={t('Collateral')}
menu={<TradingViews.collateral.menu />}
settings={<TradingViews.collateral.settings />}
>
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
<ErrorBoundary feature="collateral">
<TradingViews.collateral.component
pinnedAsset={pinnedAsset}
/>
</ErrorBoundary>
</Tab>
</Tabs>
</TradeGridChild>
@@ -173,17 +220,10 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
return (
<div className={wrapperClasses}>
<div>
{FLAGS.SUCCESSOR_MARKETS && (
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
</>
)}
<MarketTerminationBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
<MarketBanner market={market} />
</div>
<div className="min-h-0 p-0.5">
<MainGrid marketId={market?.id || ''} pinnedAsset={pinnedAsset} />
<MainGrid market={market} pinnedAsset={pinnedAsset} />
</div>
</div>
);
@@ -1,27 +1,30 @@
import type { PinnedAsset } from '@vegaprotocol/accounts';
import type { Market } from '@vegaprotocol/markets';
import { OracleBanner } from '@vegaprotocol/markets';
import type { TradingView } from './trade-views';
import { TradingViews } from './trade-views';
import { type PinnedAsset } from '@vegaprotocol/accounts';
import { type Market } from '@vegaprotocol/markets';
// TODO: handle oracle banner
// import { OracleBanner } from '@vegaprotocol/markets';
import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import classNames from 'classnames';
import {
MarketSuccessorBanner,
MarketSuccessorProposalBanner,
MarketTerminationBanner,
} from '../../components/market-banner';
import { FLAGS } from '@vegaprotocol/environment';
Popover,
Splash,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { MarketBanner } from '../../components/market-banner';
import { ErrorBoundary } from '../../components/error-boundary';
import { type TradingView } from './trade-views';
import { TradingViews } from './trade-views';
interface TradePanelsProps {
market: Market | null;
market: Market;
pinnedAsset?: PinnedAsset;
}
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('candles');
const [view, setView] = useState<TradingView>('chart');
const viewCfg = TradingViews[view];
const renderView = () => {
const Component = TradingViews[view].component;
@@ -34,39 +37,44 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
// Watch out here, we don't know what component is being rendered
// so watch out for clashes in props
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
return (
<ErrorBoundary feature={view}>
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
</ErrorBoundary>
);
};
const renderMenu = () => {
const viewCfg = TradingViews[view];
if ('menu' in viewCfg) {
const Menu = viewCfg.menu;
if ('menu' in viewCfg || 'settings' in viewCfg) {
return (
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu />
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
{'menu' in viewCfg ? <viewCfg.menu /> : null}
{'settings' in viewCfg ? (
<Popover
align="end"
trigger={
<span className="ml-1 flex items-center justify-center h-6 w-6">
<VegaIcon name={VegaIconNames.COG} size={16} />
</span>
}
>
<div className="p-4 flex justify-end">
<viewCfg.settings />
</div>
</Popover>
) : null}
</div>
);
}
return null;
};
return (
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
<div>
{FLAGS.SUCCESSOR_MARKETS && (
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
</>
)}
<MarketTerminationBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
<MarketBanner market={market} />
</div>
<div>{renderMenu()}</div>
<div className="h-full">
<div className="h-full relative">
<AutoSizer>
{({ width, height }) => (
<div style={{ width, height }} className="overflow-auto">
@@ -104,7 +112,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
key={key}
view={key}
isActive={isActive}
onClick={() => setView(key)}
onClick={() => {
setView(key);
}}
/>
);
})}
@@ -143,7 +153,7 @@ const useViewLabel = (view: TradingView) => {
const t = useT();
const labels = {
candles: t('Candles'),
chart: t('Chart'),
depth: t('Depth'),
liquidity: t('Liquidity'),
funding: t('Funding'),
@@ -1,28 +1,46 @@
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import {
CandlesChartContainer,
CandlesMenu,
} from '@vegaprotocol/candles-chart';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { TradesContainer } from '../../components/trades-container';
import {
TradesContainer,
TradesSettings,
} from '../../components/trades-container';
import { OrderbookContainer } from '../../components/orderbook-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { AccountsContainer } from '../../components/accounts-container';
import {
FillsContainer,
FillsSettings,
} from '../../components/fills-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { LiquidityContainer } from '../../components/liquidity-container';
import { FundingContainer } from '../../components/funding-container';
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import {
FundingPaymentsContainer,
FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import {
StopOrdersContainer,
StopOrdersSettings,
} from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu';
import { ChartContainer, ChartMenu } from '../../components/chart-container';
export type TradingView = keyof typeof TradingViews;
export const TradingViews = {
candles: {
component: CandlesChartContainer,
menu: CandlesMenu,
chart: {
component: ChartContainer,
menu: ChartMenu,
},
depth: {
component: DepthChartContainer,
@@ -35,37 +53,46 @@ export const TradingViews = {
},
fundingPayments: {
component: FundingPaymentsContainer,
settings: FundingPaymentsSettings,
},
orderbook: {
component: OrderbookContainer,
},
trades: {
component: TradesContainer,
settings: TradesSettings,
},
positions: {
component: PositionsContainer,
menu: PositionsMenu,
settings: PositionsSettings,
},
activeOrders: {
component: () => <OrdersContainer filter={Filter.Open} />,
menu: OpenOrdersMenu,
settings: () => <OrdersSettings filter={Filter.Open} />,
},
closedOrders: {
component: () => <OrdersContainer filter={Filter.Closed} />,
settings: () => <OrdersSettings filter={Filter.Closed} />,
},
rejectedOrders: {
component: () => <OrdersContainer filter={Filter.Rejected} />,
settings: () => <OrdersSettings filter={Filter.Rejected} />,
},
orders: {
component: OrdersContainer,
menu: OpenOrdersMenu,
settings: OrdersSettings,
},
stopOrders: {
component: StopOrdersContainer,
settings: StopOrdersSettings,
},
collateral: {
component: AccountsContainer,
menu: AccountsMenu,
settings: AccountsSettings,
},
fills: { component: FillsContainer },
fills: { component: FillsContainer, settings: FillsSettings },
} as const;
@@ -42,7 +42,7 @@ export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
},
});
const useMarketsStore = create<DataGridSlice>()(
export const useMarketsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_market_list_store',
})
@@ -15,6 +15,8 @@ import {
useLinks,
} from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
import { MarketsSettings } from './markets-settings';
export const MarketsPage = () => {
const t = useT();
@@ -33,12 +35,19 @@ export const MarketsPage = () => {
<div className="h-full pt-0.5 pb-3 px-1.5">
<div className="h-full my-1 border rounded-sm border-default">
<Tabs storageKey="console-markets">
<Tab id="open-markets" name={t('Open markets')}>
<OpenMarkets />
<Tab
id="open-markets"
name={t('Open markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-open">
<OpenMarkets />
</ErrorBoundary>
</Tab>
<Tab
id="proposed-markets"
name={t('Proposed markets')}
settings={<MarketsSettings />}
menu={
<TradingAnchorButton
size="extra-small"
@@ -49,10 +58,18 @@ export const MarketsPage = () => {
</TradingAnchorButton>
}
>
<Proposed />
<ErrorBoundary feature="markets-proposed">
<Proposed />
</ErrorBoundary>
</Tab>
<Tab id="closed-markets" name={t('Closed markets')}>
<Closed />
<Tab
id="closed-markets"
name={t('Closed markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-closed">
<Closed />
</ErrorBoundary>
</Tab>
</Tabs>
</div>
@@ -0,0 +1,8 @@
import { GridSettings } from '../../components/grid-settings/grid-settings';
import { useMarketsStore } from './market-list-table';
export const MarketsSettings = () => (
<GridSettings
updateGridStore={useMarketsStore((store) => store.updateGridStore)}
/>
);
@@ -4,9 +4,26 @@ import {
SidebarButton,
SidebarDivider,
ViewType,
useSidebar,
} from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useEffect } from 'react';
const ViewInitializer = () => {
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews({ type: ViewType.Order }, currentRouteId);
}
}, [setViews, view, currentRouteId, largeScreen]);
return null;
};
export const MarketsSidebar = () => {
const t = useT();
@@ -37,6 +54,7 @@ export const MarketsSidebar = () => {
path=":marketId"
element={
<>
<ViewInitializer />
<SidebarDivider />
<SidebarButton
view={ViewType.Order}
@@ -5,14 +5,29 @@ import { titlefy } from '@vegaprotocol/utils';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { AccountsContainer } from '../../components/accounts-container';
import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { DepositsContainer } from '../../components/deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
import { PositionsContainer } from '../../components/positions-container';
import {
FillsContainer,
FillsSettings,
} from '../../components/fills-container';
import {
FundingPaymentsContainer,
FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import { PositionsMenu } from '../../components/positions-menu';
import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container';
import {
ResizableGrid,
@@ -25,6 +40,7 @@ import { DepositsMenu } from '../../components/deposits-menu';
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -38,11 +54,21 @@ const WithdrawalsIndicator = () => {
);
};
export const Portfolio = () => {
const t = useT();
const SidebarViewInitializer = () => {
const currentRouteId = useGetCurrentRouteId();
const { getView, setViews } = useSidebar();
const view = getView(currentRouteId);
// Make transfer sidebar open by default
useEffect(() => {
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
}
}, [view, setViews, currentRouteId]);
return null;
};
export const Portfolio = () => {
const t = useT();
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
@@ -52,17 +78,11 @@ export const Portfolio = () => {
updateTitle(titlefy([t('Portfolio')]));
}, [updateTitle, t]);
// Make transfer sidebar open by default
useEffect(() => {
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
}
}, [view, setViews, currentRouteId]);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
return (
<div className={wrapperClasses}>
<SidebarViewInitializer />
<ResizableGrid vertical onChange={handleOnLayoutChange}>
<ResizableGridPanel minSize={75}>
<PortfolioGridChild>
@@ -71,20 +91,35 @@ export const Portfolio = () => {
id="positions"
name={t('Positions')}
menu={<PositionsMenu />}
settings={<PositionsSettings />}
>
<PositionsContainer allKeys />
<ErrorBoundary feature="portfolio-positions">
<PositionsContainer allKeys />
</ErrorBoundary>
</Tab>
<Tab id="orders" name={t('Orders')}>
<OrdersContainer />
<Tab id="orders" name={t('Orders')} settings={<OrdersSettings />}>
<ErrorBoundary feature="portfolio-orders">
<OrdersContainer />
</ErrorBoundary>
</Tab>
<Tab id="fills" name={t('Fills')}>
<FillsContainer />
<Tab id="fills" name={t('Fills')} settings={<FillsSettings />}>
<ErrorBoundary feature="portfolio-fills">
<FillsContainer />
</ErrorBoundary>
</Tab>
<Tab id="funding-payments" name={t('Funding payments')}>
<FundingPaymentsContainer />
<Tab
id="funding-payments"
name={t('Funding payments')}
settings={<FundingPaymentsSettings />}
>
<ErrorBoundary feature="portfolio-funding-payments">
<FundingPaymentsContainer />
</ErrorBoundary>
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
<LedgerContainer />
<ErrorBoundary feature="portfolio-ledger">
<LedgerContainer />
</ErrorBoundary>
</Tab>
</Tabs>
</PortfolioGridChild>
@@ -99,12 +134,17 @@ export const Portfolio = () => {
<Tab
id="collateral"
name={t('Collateral')}
settings={<AccountsSettings />}
menu={<AccountsMenu />}
>
<AccountsContainer />
<ErrorBoundary feature="portfolio-accounts">
<AccountsContainer />
</ErrorBoundary>
</Tab>
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
<DepositsContainer />
<ErrorBoundary feature="portfolio-deposit">
<DepositsContainer />
</ErrorBoundary>
</Tab>
<Tab
id="withdrawals"
@@ -112,7 +152,9 @@ export const Portfolio = () => {
indicator={<WithdrawalsIndicator />}
menu={<WithdrawalsMenu />}
>
<WithdrawalsContainer />
<ErrorBoundary feature="portfolio-deposit">
<WithdrawalsContainer />
</ErrorBoundary>
</Tab>
</Tabs>
</PortfolioGridChild>
@@ -13,15 +13,40 @@ import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { RainbowButton } from './buttons';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
import { Routes } from '../../lib/links';
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
import { Statistics, useStats } from './referral-statistics';
import { useReferralProgram } from './hooks/use-referral-program';
import { useT } from '../../lib/use-t';
import { ns, useT } from '../../lib/use-t';
import { useFundsAvailable } from './hooks/use-funds-available';
import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { QUSDTooltip } from './qusd-tooltip';
import { Trans } from 'react-i18next';
const RELOAD_DELAY = 3000;
const SPAM_PROTECTION_ERR = 'SPAM_PROTECTION_ERR';
const SpamProtectionErr = ({
requiredFunds,
}: {
requiredFunds?: string | number | bigint;
}) => {
if (!requiredFunds) return null;
// eslint-disable-next-line react/jsx-no-undef
return (
<Trans
defaults="To protect the network from spam, you must have at least {{requiredFunds}} <0>qUSD</0> of any asset on the network to proceed."
values={{
requiredFunds,
}}
components={[<QUSDTooltip key="qusd" />]}
ns={ns}
/>
);
};
const validateCode = (value: string, t: ReturnType<typeof useT>) => {
const number = +`0x${value}`;
if (!value || value.length !== 64) {
@@ -32,20 +57,23 @@ const validateCode = (value: string, t: ReturnType<typeof useT>) => {
return true;
};
export const ApplyCodeFormContainer = () => {
export const ApplyCodeFormContainer = ({
onSuccess,
}: {
onSuccess?: () => void;
}) => {
const { pubKey } = useVegaWallet();
const { data: referee } = useReferral({ pubKey, role: 'referee' });
const { data: referrer } = useReferral({ pubKey, role: 'referrer' });
const isInReferralSet = useIsInReferralSet(pubKey);
// go to main page if the current pubkey is already a referrer or referee
if (referee || referrer) {
// Navigate to the index page when already in the referral set.
if (isInReferralSet) {
return <Navigate to={Routes.REFERRALS} />;
}
return <ApplyCodeForm />;
return <ApplyCodeForm onSuccess={onSuccess} />;
};
export const ApplyCodeForm = () => {
export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
const t = useT();
const program = useReferralProgram();
const navigate = useNavigate();
@@ -54,10 +82,15 @@ export const ApplyCodeForm = () => {
);
const [status, setStatus] = useState<
'requested' | 'failed' | 'successful' | null
'requested' | 'no-funds' | 'successful' | null
>(null);
const txHash = useRef<string | null>(null);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const { isEligible, requiredFunds } = useFundsAvailable();
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((s) => s.setViews);
const {
register,
handleSubmit,
@@ -73,6 +106,17 @@ export const ApplyCodeForm = () => {
code: validateCode(codeField, t) ? codeField : undefined,
});
/**
* Validates if a connected party can apply a code (min funds span protection)
*/
const validateFundsAvailable = useCallback(() => {
if (requiredFunds && !isEligible) {
const err = SPAM_PROTECTION_ERR;
return err;
}
return true;
}, [isEligible, requiredFunds]);
/**
* Validates the set a user tries to apply to.
*/
@@ -96,6 +140,15 @@ export const ApplyCodeForm = () => {
if (code) setValue('code', code);
}, [params, setValue]);
useEffect(() => {
const err = validateFundsAvailable();
if (err !== true) {
setStatus('no-funds');
} else {
setStatus(null);
}
}, [isEligible, validateFundsAvailable]);
const onSubmit = ({ code }: FieldValues) => {
if (isReadOnly || !pubKey || !code || code.length === 0) {
return;
@@ -167,10 +220,11 @@ export const ApplyCodeForm = () => {
useEffect(() => {
if (status === 'successful') {
setTimeout(() => {
if (onSuccess) onSuccess();
navigate(Routes.REFERRALS);
}, RELOAD_DELAY);
}
}, [navigate, status]);
}, [navigate, onSuccess, status]);
// show "code applied" message when successfully applied
if (status === 'successful') {
@@ -207,6 +261,18 @@ export const ApplyCodeForm = () => {
};
}
if (status === 'no-funds') {
return {
disabled: false,
children: t('Deposit funds'),
type: 'button' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
onClick: ((event) => {
event.preventDefault();
setViews({ type: ViewType.Deposit }, currentRouteId);
}) as MouseEventHandler,
};
}
if (status === 'requested') {
return {
disabled: true,
@@ -230,13 +296,15 @@ export const ApplyCodeForm = () => {
<>
<div
data-testid="referral-apply-code-form"
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto w-2/3 max-w-md rounded-lg p-8"
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto md:w-2/3 max-w-md rounded-lg p-8"
>
<h3 className="calt mb-4 text-center text-2xl">
{t('Apply a referral code')}
</h3>
<p className="mb-4 text-center text-base">
{t('Enter a referral code to get trading discounts.')}
{t(
'Apply a referral code to access the discount benefits of the current program.'
)}
</p>
<form
className={classNames('flex w-full flex-col gap-4', {
@@ -251,8 +319,10 @@ export const ApplyCodeForm = () => {
{...register('code', {
required: t('You have to provide a code to apply it.'),
validate: (value) => {
const err = validateCode(value, t);
if (err !== true) return err;
const codeErr = validateCode(value, t);
if (codeErr !== true) return codeErr;
const fundsErr = validateFundsAvailable();
if (fundsErr !== true) return fundsErr;
return validateSet();
},
})}
@@ -262,10 +332,26 @@ export const ApplyCodeForm = () => {
</label>
<RainbowButton variant="border" {...getButtonProps()} />
</form>
{errors.code && (
<InputError className="overflow-auto break-words">
{errors.code.message?.toString()}
{status === 'no-funds' ? (
<InputError intent="warning" className="overflow-auto break-words">
<span>
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
</span>
</InputError>
) : (
errors.code && (
<InputError intent="warning" className="overflow-auto break-words">
{errors.code.message === SPAM_PROTECTION_ERR ? (
<span>
<SpamProtectionErr
requiredFunds={requiredFunds?.toString()}
/>
</span>
) : (
errors.code.message?.toString()
)}
</InputError>
)
)}
</div>
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
@@ -3,9 +3,11 @@ export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[37%_0px] bg-[length:1440px] bg-no-repeat bg-local';
// TODO: Update the links to use the correct referral related pages
export const REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
export const ABOUT_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
export const REFERRAL_DOCS_LINK =
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
export const ABOUT_REFERRAL_DOCS_LINK =
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
export const DISCLAIMER_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
@@ -19,14 +19,22 @@ import {
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
import { useStakeAvailable } from './hooks/use-stake-available';
import {
ABOUT_REFERRAL_DOCS_LINK,
DISCLAIMER_REFERRAL_DOCS_LINK,
} from './constants';
import { useReferral } from './hooks/use-referral';
import { ABOUT_REFERRAL_DOCS_LINK } from './constants';
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
import { useT } from '../../lib/use-t';
import { Navigate } from 'react-router-dom';
import { Routes } from '../../lib/links';
import { useReferralProgram } from './hooks/use-referral-program';
export const CreateCodeContainer = () => {
const { pubKey } = useVegaWallet();
const isInReferralSet = useIsInReferralSet(pubKey);
// Navigate to the index page when already in the referral set.
if (isInReferralSet) {
return <Navigate to={Routes.REFERRALS} />;
}
return <CreateCodeForm />;
};
@@ -41,14 +49,14 @@ export const CreateCodeForm = () => {
return (
<div
data-testid="referral-create-code-form"
className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
className="md:w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
>
<h3 className="mb-4 text-2xl text-center calt">
{t('Create a referral code')}
</h3>
<p className="mb-4 text-center text-base">
{t(
'Generate a referral code to share with your friends and start earning commission.'
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
)}
</p>
@@ -98,10 +106,7 @@ const CreateCodeDialog = ({
const { stakeAvailable: currentStakeAvailable, requiredStake } =
useStakeAvailable();
const { data: referralSets } = useReferral({
pubKey,
role: 'referrer',
});
const { details: programDetails } = useReferralProgram();
const onSubmit = () => {
if (isReadOnly || !pubKey) {
@@ -201,7 +206,7 @@ const CreateCodeDialog = ({
);
}
if (!referralSets) {
if (!programDetails) {
return (
<div className="flex flex-col gap-4">
{(status === 'idle' || status === 'loading' || status === 'error') && (
@@ -237,7 +242,9 @@ const CreateCodeDialog = ({
intent={Intent.Primary}
onClick={() => onSubmit()}
{...getButtonProps()}
></TradingButton>
>
{t('Yes')}
</TradingButton>
{status === 'idle' && (
<TradingButton
fill={true}
@@ -255,9 +262,6 @@ const CreateCodeDialog = ({
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
{t('About the referral program')}
</ExternalLink>
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
{t('Disclaimer')}
</ExternalLink>
</div>
</div>
);
@@ -268,7 +272,7 @@ const CreateCodeDialog = ({
{(status === 'idle' || status === 'loading' || status === 'error') && (
<p>
{t(
'Generate a referral code to share with your friends and start earning commission.'
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
)}
</p>
)}
@@ -299,9 +303,6 @@ const CreateCodeDialog = ({
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
{t('About the referral program')}
</ExternalLink>
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
{t('Disclaimer')}
</ExternalLink>
</div>
</div>
);
@@ -53,7 +53,7 @@ export const NotFound = () => {
const navigate = useNavigate();
return (
<div className="pt-32">
<LayoutWithSky className="pt-32">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
@@ -75,6 +75,6 @@ export const NotFound = () => {
{t('Go back and try again')}
</RainbowButton>
</p>
</div>
</LayoutWithSky>
);
};
@@ -0,0 +1,20 @@
query FundsAvailable($partyId: ID!) {
party(id: $partyId) {
accountsConnection {
edges {
node {
balance
asset {
decimals
symbol
id
}
}
}
}
}
networkParameter(key: "spam.protection.applyReferral.min.funds") {
key
value
}
}
@@ -0,0 +1,63 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type FundsAvailableQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type FundsAvailableQuery = { __typename?: 'Query', party?: { __typename?: 'Party', accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, asset: { __typename?: 'Asset', decimals: number, symbol: string, id: string } } } | null> | null } | null } | null, networkParameter?: { __typename?: 'NetworkParameter', key: string, value: string } | null };
export const FundsAvailableDocument = gql`
query FundsAvailable($partyId: ID!) {
party(id: $partyId) {
accountsConnection {
edges {
node {
balance
asset {
decimals
symbol
id
}
}
}
}
}
networkParameter(key: "spam.protection.applyReferral.min.funds") {
key
value
}
}
`;
/**
* __useFundsAvailableQuery__
*
* To run a query within a React component, call `useFundsAvailableQuery` and pass it any options that fit your needs.
* When your component renders, `useFundsAvailableQuery` 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 } = useFundsAvailableQuery({
* variables: {
* partyId: // value for 'partyId'
* },
* });
*/
export function useFundsAvailableQuery(baseOptions: Apollo.QueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
}
export function useFundsAvailableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
}
export type FundsAvailableQueryHookResult = ReturnType<typeof useFundsAvailableQuery>;
export type FundsAvailableLazyQueryHookResult = ReturnType<typeof useFundsAvailableLazyQuery>;
export type FundsAvailableQueryResult = Apollo.QueryResult<FundsAvailableQuery, FundsAvailableQueryVariables>;
@@ -0,0 +1,48 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
import compact from 'lodash/compact';
import BigNumber from 'bignumber.js';
/**
* Gets the funds for given public key and required min for
* the referral program.
*
* (Uses currently connected public key if left empty)
*/
export const useFundsAvailable = (pubKey?: string) => {
const { pubKey: currentPubKey } = useVegaWallet();
const partyId = pubKey || currentPubKey;
const { data, stopPolling } = useFundsAvailableQuery({
variables: { partyId: partyId || '' },
skip: !partyId,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
pollInterval: 5000,
});
const fundsAvailable = data
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
: undefined;
const requiredFunds = data
? BigNumber(data.networkParameter?.value || '0')
: undefined;
const sumOfFunds =
fundsAvailable
?.filter((fa) => fa.balance)
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
BigNumber(0);
if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
stopPolling();
}
return {
fundsAvailable,
requiredFunds,
isEligible:
fundsAvailable != null &&
requiredFunds != null &&
sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
};
};
@@ -1,14 +1,8 @@
import { getNumberFormat } from '@vegaprotocol/utils';
import { addDays } from 'date-fns';
import sortBy from 'lodash/sortBy';
import omit from 'lodash/omit';
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
const STAKING_TIERS_MAPPING: Record<number, string> = {
1: 'Tradestarter',
2: 'Mid level degen',
3: 'Reward hoarder',
};
import BigNumber from 'bignumber.js';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MOCK = {
@@ -16,46 +10,76 @@ const MOCK = {
currentReferralProgram: {
id: 'abc',
version: 1,
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
windowLength: 10,
benefitTiers: [
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '30000',
referralDiscountFactor: '0.01',
referralRewardFactor: '0.01',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '20000',
referralDiscountFactor: '0.05',
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '100000',
referralDiscountFactor: '0.1',
referralRewardFactor: '0.05',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '10000',
referralDiscountFactor: '0.001',
referralRewardFactor: '0.001',
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '1000000',
referralDiscountFactor: '0.1',
referralRewardFactor: '0.075',
},
{
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '5000000',
referralDiscountFactor: '0.1',
referralRewardFactor: '0.1',
},
{
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '25000000',
referralDiscountFactor: '0.1',
referralRewardFactor: '0.125',
},
{
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '75000000',
referralDiscountFactor: '0.1',
referralRewardFactor: '0.15',
},
{
minimumEpochs: 1,
minimumRunningNotionalTakerVolume: '150000000',
referralDiscountFactor: '0.07',
referralRewardFactor: '0.175',
},
],
stakingTiers: [
{
minimumStakedTokens: '10000',
referralRewardMultiplier: '1',
minimumStakedTokens: '100000000000000000000',
referralRewardMultiplier: '1.025',
},
{
minimumStakedTokens: '20000',
referralRewardMultiplier: '2',
minimumStakedTokens: '1000000000000000000000',
referralRewardMultiplier: '1.05',
},
{
minimumStakedTokens: '30000',
referralRewardMultiplier: '3',
minimumStakedTokens: '5000000000000000000000',
referralRewardMultiplier: '1.1',
},
{
minimumStakedTokens: '50000000000000000000000',
referralRewardMultiplier: '1.2',
},
{
minimumStakedTokens: '250000000000000000000000',
referralRewardMultiplier: '1.25',
},
{
minimumStakedTokens: '500000000000000000000000',
referralRewardMultiplier: '1.3',
},
],
endOfProgramTimestamp: '2024-12-31T01:00:00Z',
windowLength: 30,
},
loading: false,
error: undefined,
},
loading: false,
error: undefined,
};
export const useReferralProgram = () => {
@@ -79,9 +103,9 @@ export const useReferralProgram = () => {
return {
tier: i + 1, // sorted in asc order, hence first is the lowest tier
rewardFactor: Number(t.referralRewardFactor),
commission: Number(t.referralRewardFactor) * 100 + '%',
commission: BigNumber(t.referralRewardFactor).times(100).toFixed(2) + '%',
discountFactor: Number(t.referralDiscountFactor),
discount: Number(t.referralDiscountFactor) * 100 + '%',
discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%',
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
volume: getNumberFormat(0).format(
Number(t.minimumRunningNotionalTakerVolume)
@@ -90,13 +114,11 @@ export const useReferralProgram = () => {
};
});
const stakingTiers = sortBy(
data.currentReferralProgram.stakingTiers,
(t) => t.referralRewardMultiplier
const stakingTiers = sortBy(data.currentReferralProgram.stakingTiers, (t) =>
parseFloat(t.referralRewardMultiplier)
).map((t, i) => {
return {
tier: i + 1,
label: STAKING_TIERS_MAPPING[i + 1],
...t,
};
});
@@ -75,10 +75,7 @@ export const useReferralToasts = () => {
data-testid="toast-apply-code"
size="xs"
onClick={() => {
const matched = matchPath(
Routes.REFERRALS_APPLY_CODE,
pathname
);
const matched = matchPath(Routes.REFERRALS, pathname);
if (!matched) navigate(Routes.REFERRALS_APPLY_CODE);
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
hidden: true,
@@ -2,7 +2,11 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useCallback } from 'react';
import { useRefereesQuery } from './__generated__/Referees';
import compact from 'lodash/compact';
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
import pick from 'lodash/pick';
import type {
ReferralSetsQuery,
ReferralSetsQueryVariables,
} from './__generated__/ReferralSets';
import { useReferralSetsQuery } from './__generated__/ReferralSets';
import { useStakeAvailable } from './use-stake-available';
@@ -118,3 +122,96 @@ export const useReferral = (args: UseReferralArgs) => {
refetch,
};
};
type Referee = NonNullable<
NonNullable<ReturnType<typeof useReferral>['data']>['referee']
>;
type RefereeProperties = (keyof Referee)[];
const findReferee = (referee: Referee, referees: Referee[]) =>
referees.find((r) => r.refereeId === referee?.refereeId) || referee;
const updateReferee = (
referee: Referee,
referees: Referee[],
properties: RefereeProperties
) => ({
...referee,
...pick(findReferee(referee, referees), properties),
});
export const useUpdateReferees = (
referral: ReturnType<typeof useReferral>,
aggregationEpochs: number,
properties: RefereeProperties,
skip?: boolean
): ReturnType<typeof useReferral> => {
const { data, loading, error, refetch } = useRefereesQuery({
variables: {
code: referral?.data?.code as string,
aggregationEpochs,
},
skip: skip || !referral?.data?.code,
fetchPolicy: 'cache-and-network',
context: { isEnlargedTimeout: true },
});
const refetchAll = useCallback(() => {
refetch();
referral.refetch();
}, [refetch, referral]);
if (!referral.data || skip) {
return referral;
}
const referees = compact(
removePaginationWrapper(data?.referralSetReferees.edges)
);
return {
data: data && {
...referral.data,
referees: referral.data.referees.map((referee) =>
updateReferee(referee, referees, properties)
),
referee:
referral.data.referee &&
updateReferee(referral.data.referee, referees, properties),
},
loading: loading || referral.loading,
error: error || referral.error,
refetch: refetchAll,
};
};
const retrieveReferralSetData = (data: ReferralSetsQuery | undefined) =>
data?.referralSets.edges && data.referralSets.edges.length > 0
? data.referralSets.edges[0]?.node
: undefined;
export const useIsInReferralSet = (pubKey: string | null) => {
const [asRefereeVariables, asRefereeSkip] = prepareVariables({
pubKey,
role: 'referee',
});
const [asReferrerVariables, asReferrerSkip] = prepareVariables({
pubKey,
role: 'referrer',
});
const { data: asRefereeData } = useReferralSetsQuery({
variables: asRefereeVariables,
skip: asRefereeSkip,
fetchPolicy: 'cache-and-network',
});
const { data: asReferrerData } = useReferralSetsQuery({
variables: asReferrerVariables,
skip: asReferrerSkip,
fetchPolicy: 'cache-and-network',
});
return Boolean(
retrieveReferralSetData(asRefereeData) ||
retrieveReferralSetData(asReferrerData)
);
};
@@ -13,7 +13,6 @@ export const useStakeAvailable = (pubKey?: string) => {
const { data } = useStakeAvailableQuery({
variables: { partyId: partyId || '' },
skip: !partyId,
// TODO: remove when network params available
errorPolicy: 'ignore',
});
@@ -1,5 +1,5 @@
import { useT } from '../../lib/use-t';
import { Table } from './table';
import { Table } from '../../components/table';
export const HowItWorksTable = () => {
const t = useT();
@@ -5,21 +5,26 @@ import { useT } from '../../lib/use-t';
export const LandingBanner = () => {
const t = useT();
return (
<div className={classNames('relative mb-20')}>
<div className={classNames('relative mb-10 lg:mb-20')}>
<div className="">
<div
aria-hidden
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden"
className="absolute top-20 right-[220px] md:right-[240px] max-sm:hidden"
>
<AnimatedDudeWithWire />
</div>
<div className="pt-20 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
{t('Earn commission & stake rewards')}
<div className="pt-10 lg:pt-20 sm:w-[50%]">
<h1 className="text-3xl _text-[6vw] lg:!text-6xl leading-[1em] font-alpha calt mb-10">
{t('Vega community referrals')}
</h1>
<p className="text-lg mb-10">
<p className="text-lg mb-1">
{t(
'Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
'Referral programs can be proposed and created via community governance.'
)}
</p>
<p className="text-lg mb-1">
{t(
'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.'
)}
</p>
</div>
@@ -13,7 +13,7 @@ export const Layout = ({
<div
className={classNames(
'max-w-[1440px]',
'mx-auto px-16 md:px-32 pb-32',
'mx-auto px-4 lg:px-32 pb-32',
'relative z-0',
className
)}

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